@sentry/junior 0.184.0 → 0.186.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 (129) hide show
  1. package/dist/{agent-hooks-XTZVALGQ.js → agent-hooks-4B3Z4WRK.js} +13 -11
  2. package/dist/api/acp/schema.d.ts +20 -2
  3. package/dist/api/code/overview.d.ts +39 -0
  4. package/dist/api/locations/detail.d.ts +1 -1
  5. package/dist/api/people/code.d.ts +22 -0
  6. package/dist/api/schema/code.d.ts +46 -0
  7. package/dist/api/schema/conversation.d.ts +108 -20
  8. package/dist/api/schema/location.d.ts +1 -1
  9. package/dist/api/schema/person.d.ts +1 -1
  10. package/dist/api/schema.d.ts +4 -4
  11. package/dist/api/schema.js +12 -1
  12. package/dist/api.js +368 -78
  13. package/dist/app.js +1412 -538
  14. package/dist/{catalog-runtime-5357XR4F.js → catalog-runtime-AFJCAOHC.js} +4 -4
  15. package/dist/chat/api-turns/accepted-reply.d.ts +11 -0
  16. package/dist/chat/api-turns/cancellation.d.ts +3 -5
  17. package/dist/chat/api-turns/mailbox-input.d.ts +7 -0
  18. package/dist/chat/api-turns/routing.d.ts +18 -3
  19. package/dist/chat/api-turns/work.d.ts +4 -9
  20. package/dist/chat/app/conversation-work.d.ts +1 -1
  21. package/dist/chat/app/factory.d.ts +4 -0
  22. package/dist/chat/app/services.d.ts +4 -3
  23. package/dist/chat/conversations/history.d.ts +83 -9
  24. package/dist/chat/conversations/turn-lifecycle.d.ts +6 -3
  25. package/dist/chat/providers/slack/input.d.ts +32 -0
  26. package/dist/chat/{runtime → providers/slack}/processing-reaction.d.ts +6 -8
  27. package/dist/chat/{runtime/slack-resume.d.ts → providers/slack/resume.d.ts} +16 -19
  28. package/dist/chat/providers/slack/runtime.d.ts +1 -12
  29. package/dist/chat/providers/slack/turn.d.ts +10 -25
  30. package/dist/chat/resource-events/actor.d.ts +7 -2
  31. package/dist/chat/resource-events/app-publisher.d.ts +18 -0
  32. package/dist/chat/resource-events/catalog.d.ts +19 -0
  33. package/dist/chat/resource-events/ingest.d.ts +0 -1
  34. package/dist/chat/resource-events/notification.d.ts +28 -3
  35. package/dist/chat/resource-events/runtime-catalog.d.ts +7 -2
  36. package/dist/chat/resource-events/store.d.ts +2 -14
  37. package/dist/chat/resource-events/tool-support.d.ts +0 -5
  38. package/dist/chat/resource-events/workspace.d.ts +7 -3
  39. package/dist/chat/runtime/turn-execution.d.ts +7 -2
  40. package/dist/chat/sandbox/egress/credentials.d.ts +5 -4
  41. package/dist/chat/sandbox/egress/session.d.ts +10 -11
  42. package/dist/chat/sandbox/errors.d.ts +1 -1
  43. package/dist/chat/sandbox/ref.d.ts +1 -1
  44. package/dist/chat/sandbox/snapshot/events.d.ts +8 -1
  45. package/dist/chat/sandbox/snapshot/not-ready-error.d.ts +6 -1
  46. package/dist/chat/scheduled-tasks/tool-support.d.ts +5 -5
  47. package/dist/chat/scheduled-tasks/tools/create.d.ts +2 -2
  48. package/dist/chat/scheduled-tasks/tools/delete.d.ts +2 -2
  49. package/dist/chat/scheduled-tasks/tools/list.d.ts +2 -2
  50. package/dist/chat/scheduled-tasks/tools/run-now.d.ts +2 -2
  51. package/dist/chat/scheduled-tasks/tools/update.d.ts +2 -2
  52. package/dist/chat/scheduled-tasks/types.d.ts +1 -1
  53. package/dist/chat/sentry-links.d.ts +2 -0
  54. package/dist/chat/services/provider-retry.d.ts +2 -0
  55. package/dist/chat/services/turn-failure-response.d.ts +4 -0
  56. package/dist/chat/services/turn-session-routing.d.ts +20 -5
  57. package/dist/chat/task-execution/paused-turn.d.ts +0 -2
  58. package/dist/chat/task-execution/slack-resource-event.d.ts +47 -0
  59. package/dist/chat/task-execution/slack-work.d.ts +0 -21
  60. package/dist/chat/task-execution/state.d.ts +2 -2
  61. package/dist/chat/task-execution/store.d.ts +1 -1
  62. package/dist/chat/task-execution/turn-wake.d.ts +12 -0
  63. package/dist/chat/tool-support/action-review.d.ts +1 -1
  64. package/dist/chat/tools/event-tasks.d.ts +1 -1
  65. package/dist/chat/tools/resource-events.d.ts +2 -2
  66. package/dist/chat/tools/search-conversation-events.d.ts +1 -1
  67. package/dist/chat/tools/types.d.ts +1 -1
  68. package/dist/chat/tools/watch-resource-events.d.ts +2 -2
  69. package/dist/{chunk-Z3QAXJR7.js → chunk-2IBDVE62.js} +12 -9
  70. package/dist/{chunk-4RONS3WR.js → chunk-2U7EPTRZ.js} +11 -5
  71. package/dist/{chunk-3S7ULUWW.js → chunk-3CVEE6LA.js} +1 -1
  72. package/dist/{chunk-IZDYYYGP.js → chunk-43LODHJM.js} +24 -384
  73. package/dist/{chunk-LHXIZLFF.js → chunk-64WF2GQ3.js} +1 -1
  74. package/dist/{chunk-YKX6MTLU.js → chunk-7EOWT62G.js} +1 -1
  75. package/dist/{chunk-UBOIHNTH.js → chunk-7EU4YSZ4.js} +362 -188
  76. package/dist/{chunk-UF6BUA5W.js → chunk-ACGWTJ74.js} +3 -3
  77. package/dist/{chunk-6UDPINOX.js → chunk-AD64YMEC.js} +19 -44
  78. package/dist/{chunk-MLSQX2EX.js → chunk-BRKB5R7P.js} +1 -1
  79. package/dist/{chunk-E57WPXWY.js → chunk-CM6RTYSO.js} +5 -5
  80. package/dist/{chunk-MAPJFUMV.js → chunk-CV5CPRVG.js} +3 -3
  81. package/dist/chunk-D4YV5TDV.js +45 -0
  82. package/dist/{chunk-HIPUIPJM.js → chunk-DHYZ4VKG.js} +383 -254
  83. package/dist/{chunk-DUKIDPF6.js → chunk-DOP3FIQC.js} +1 -1
  84. package/dist/{chunk-BT53RIGJ.js → chunk-EWRPUXE3.js} +12 -50
  85. package/dist/{chunk-BHYRYYLY.js → chunk-FFYDRLXW.js} +1 -1
  86. package/dist/chunk-GFBR4S3Q.js +406 -0
  87. package/dist/chunk-GQMQCY7H.js +58 -0
  88. package/dist/{chunk-JNO2GUDS.js → chunk-GSB3NTNS.js} +926 -1856
  89. package/dist/{chunk-2ZQPVX7C.js → chunk-H7X2G2KS.js} +25 -12
  90. package/dist/{chunk-WQFEWULF.js → chunk-HKFLRIBD.js} +2 -2
  91. package/dist/{chunk-SHJK75Z2.js → chunk-HWXP6RHG.js} +38 -4
  92. package/dist/{chunk-RF6J3DIO.js → chunk-INQHUE72.js} +84 -1
  93. package/dist/{chunk-4CNDGK25.js → chunk-JFPY6XFF.js} +911 -993
  94. package/dist/{chunk-6WGC3SO4.js → chunk-MCLWOBPN.js} +80 -18
  95. package/dist/{chunk-PJPRXO2T.js → chunk-NRMAOGRS.js} +1 -1
  96. package/dist/{chunk-ACPDVEJO.js → chunk-O6GQO7VP.js} +201 -218
  97. package/dist/{chunk-M5YWNMTE.js → chunk-OZG7E22Y.js} +24 -6
  98. package/dist/{chunk-WVIA7V3M.js → chunk-PFAZSRGR.js} +29 -1
  99. package/dist/{chunk-LCP4EDKR.js → chunk-PZZOGHVR.js} +14 -4
  100. package/dist/{chunk-QABCY33S.js → chunk-QBL2AVFF.js} +2 -2
  101. package/dist/{chunk-FKEZZMXS.js → chunk-QU3URVWG.js} +9 -94
  102. package/dist/{chunk-M3QYTBQ6.js → chunk-STOCCPWH.js} +11 -2
  103. package/dist/{chunk-K7VDC2QW.js → chunk-TXLBLGNQ.js} +6 -6
  104. package/dist/{chunk-JA43DV4Y.js → chunk-VOGOTEW7.js} +1 -1
  105. package/dist/{chunk-HUXFMDBZ.js → chunk-VQIWBJOP.js} +1 -1
  106. package/dist/{chunk-JM2COLGC.js → chunk-VQZYIVYN.js} +6 -6
  107. package/dist/{chunk-BCHYQS4B.js → chunk-VXL2LBPK.js} +1 -1
  108. package/dist/{chunk-6VOYP4M6.js → chunk-YXSBZRP7.js} +1 -1
  109. package/dist/cli/chat.js +35 -33
  110. package/dist/cli/check.js +5 -5
  111. package/dist/cli/plugins.js +14 -12
  112. package/dist/cli/snapshot-create.js +10 -9
  113. package/dist/cli/upgrade.js +7 -6
  114. package/dist/{db-MA52H62H.js → db-7XL4UPAE.js} +8 -6
  115. package/dist/instrumentation.js +3 -3
  116. package/dist/nitro.js +7 -6
  117. package/dist/{oauth-callback-server-DXGHKN6S.js → oauth-callback-server-RAOYHWDI.js} +30 -27
  118. package/dist/{runner-4IXUAQNK.js → runner-KMPBWRPM.js} +24 -19
  119. package/dist/{skills-X3DY7AXO.js → skills-IPCALGLQ.js} +5 -5
  120. package/dist/spawn-OSOWTMBG.js +41 -0
  121. package/dist/task-runner-IQNBA3RY.js +34 -0
  122. package/dist/{validation-Z6JXSTJS.js → validation-BSRCCX6N.js} +5 -5
  123. package/dist/version.js +1 -1
  124. package/dist/{work-OKWRFBDW.js → work-T6UFTOJY.js} +24 -19
  125. package/dist/worker-L35OG7VA.js +24 -0
  126. package/package.json +5 -5
  127. package/dist/spawn-43WGS6CH.js +0 -37
  128. package/dist/task-runner-VDRKITPV.js +0 -32
  129. package/dist/worker-B236AE72.js +0 -22
@@ -1,36 +1,45 @@
1
1
  import {
2
2
  createWebAuthorization,
3
3
  deleteWebAuthorization
4
- } from "./chunk-LCP4EDKR.js";
4
+ } from "./chunk-PZZOGHVR.js";
5
+ import {
6
+ AgentRunError,
7
+ executeTurn
8
+ } from "./chunk-GQMQCY7H.js";
5
9
  import {
6
10
  buildDeliveredTurnStatePatch,
7
11
  completeAuthPauseTurn,
8
12
  finalizeFailedTurnReplyWithEvent,
9
13
  getTurnUserMessage
10
- } from "./chunk-M5YWNMTE.js";
14
+ } from "./chunk-OZG7E22Y.js";
11
15
  import {
12
16
  processPluginTask,
13
17
  scheduleSessionCompletedPluginTasks
14
- } from "./chunk-6WGC3SO4.js";
18
+ } from "./chunk-MCLWOBPN.js";
15
19
  import {
16
20
  ConversationTurnLifecycleService
17
- } from "./chunk-M3QYTBQ6.js";
21
+ } from "./chunk-STOCCPWH.js";
18
22
  import {
19
23
  coerceThreadConversationState,
20
24
  getPersistedSandboxState,
21
25
  getPersistedThreadState,
22
26
  persistThreadStateById
23
- } from "./chunk-E57WPXWY.js";
27
+ } from "./chunk-CM6RTYSO.js";
24
28
  import {
25
29
  healthReportSchema
26
30
  } from "./chunk-UTL3MHPC.js";
31
+ import {
32
+ RESOURCE_EVENT_AUTHOR_ID,
33
+ isResourceEventConversationMessage,
34
+ isResourceEventMailboxMetadata
35
+ } from "./chunk-DHYZ4VKG.js";
27
36
  import {
28
37
  parseSlackThreadId
29
- } from "./chunk-YKX6MTLU.js";
38
+ } from "./chunk-7EOWT62G.js";
30
39
  import {
31
40
  appendAndEnqueueExclusiveInboundMessage,
32
41
  appendAndEnqueueInboundMessage
33
- } from "./chunk-Z3QAXJR7.js";
42
+ } from "./chunk-2IBDVE62.js";
34
43
  import {
35
44
  TurnInputCommitLostError,
36
45
  buildDeterministicTurnId,
@@ -41,10 +50,11 @@ import {
41
50
  markTurnClosed,
42
51
  markTurnFailed,
43
52
  startActiveTurn
44
- } from "./chunk-JA43DV4Y.js";
53
+ } from "./chunk-VOGOTEW7.js";
45
54
  import {
46
55
  abandonTurnRecord,
47
56
  buildConversationContext,
57
+ commitAcceptedReply,
48
58
  getTurnRecord,
49
59
  hydrateConversationMessages,
50
60
  listTurnSummaries,
@@ -56,35 +66,39 @@ import {
56
66
  recordDeliveredAssistantMessage,
57
67
  saveTurnCheckpoint,
58
68
  upsertConversationMessage
59
- } from "./chunk-2ZQPVX7C.js";
69
+ } from "./chunk-H7X2G2KS.js";
60
70
  import {
61
71
  normalizeSlackConversationId
62
- } from "./chunk-4CNDGK25.js";
72
+ } from "./chunk-JFPY6XFF.js";
63
73
  import {
64
74
  getConversationEventStore,
65
75
  getConversationStore,
66
76
  getDb
67
- } from "./chunk-IZDYYYGP.js";
77
+ } from "./chunk-43LODHJM.js";
68
78
  import {
69
79
  juniorConversationParticipants,
70
80
  juniorConversations,
71
81
  juniorIdentities,
72
82
  juniorUsers
73
- } from "./chunk-BCHYQS4B.js";
83
+ } from "./chunk-VXL2LBPK.js";
74
84
  import {
75
85
  juniorDestinations
76
86
  } from "./chunk-VFUK3X5B.js";
87
+ import {
88
+ getSlackBotToken
89
+ } from "./chunk-AD64YMEC.js";
77
90
  import {
78
91
  canExposeConversationPayload
79
- } from "./chunk-BHYRYYLY.js";
92
+ } from "./chunk-FFYDRLXW.js";
80
93
  import {
81
94
  credentialContextForActor
82
95
  } from "./chunk-CEMMLV5L.js";
83
96
  import {
84
97
  logException,
98
+ logWarn,
85
99
  setTags,
86
100
  withLogContext
87
- } from "./chunk-3S7ULUWW.js";
101
+ } from "./chunk-3CVEE6LA.js";
88
102
 
89
103
  // src/handlers/health.ts
90
104
  function readHealthReport() {
@@ -174,11 +188,33 @@ function parseApiTurnMessages(messages) {
174
188
  return { message: entry.message, metadata: entry.metadata.data };
175
189
  });
176
190
  }
191
+ function parseLocalResourceEventMessages(messages) {
192
+ if (messages.length === 0) {
193
+ return [];
194
+ }
195
+ if (!messages.every(
196
+ (message) => message.source === "resource_event" && isResourceEventMailboxMetadata(message.input.metadata)
197
+ )) {
198
+ return [];
199
+ }
200
+ return messages.map((message) => ({
201
+ message,
202
+ kind: "resource_event"
203
+ }));
204
+ }
177
205
  async function resolveApiTurnWork(context) {
178
206
  const batch = parseApiTurnMessages(context.attempt.messages);
179
207
  if (batch.length > 0) {
180
208
  return { kind: "mailbox", batch };
181
209
  }
210
+ if (context.destination?.platform === "local") {
211
+ const resourceBatch = parseLocalResourceEventMessages(
212
+ context.attempt.messages
213
+ );
214
+ if (resourceBatch.length > 0) {
215
+ return { kind: "resource_event", batch: resourceBatch };
216
+ }
217
+ }
182
218
  if (context.attempt.messages.length > 0) {
183
219
  return void 0;
184
220
  }
@@ -192,10 +228,40 @@ async function isApiTurnWork(context) {
192
228
  // src/chat/api-turns/work.ts
193
229
  import { createHash } from "crypto";
194
230
  import {
231
+ createLocalSource,
195
232
  createWebSource,
196
233
  localDestinationSchema
197
234
  } from "@sentry/junior-plugin-api";
198
235
 
236
+ // src/chat/api-turns/accepted-reply.ts
237
+ async function commitWebAcceptedReply(args) {
238
+ const conversationMessageId = recordDeliveredAssistantMessage({
239
+ conversation: args.conversation,
240
+ sessionId: args.sessionId,
241
+ source: "web",
242
+ text: args.text,
243
+ userMessageId: args.userMessageId
244
+ });
245
+ try {
246
+ await persistWithRetry(
247
+ () => commitAcceptedReply({
248
+ ...args.agentMessage ? { agentMessage: args.agentMessage } : void 0,
249
+ conversation: args.conversation,
250
+ conversationMessageId,
251
+ conversationId: args.conversationId
252
+ })
253
+ );
254
+ } catch (error) {
255
+ logException(
256
+ new Error("Accepted assistant message persistence failed"),
257
+ "api.assistant.message_post_delivery_persist.failed",
258
+ {
259
+ "error.type": error instanceof Error ? error.name : typeof error
260
+ }
261
+ );
262
+ }
263
+ }
264
+
199
265
  // src/chat/api-turns/cancellation.ts
200
266
  function createApiTurnCancellation() {
201
267
  const active = /* @__PURE__ */ new Map();
@@ -217,7 +283,7 @@ function createApiTurnCancellation() {
217
283
  if (!entry) {
218
284
  return false;
219
285
  }
220
- entry.controller.abort(new Error("API Turn cancelled"));
286
+ entry.controller.abort(new DOMException("Turn cancelled", "AbortError"));
221
287
  return true;
222
288
  },
223
289
  disconnect(conversationId, signal) {
@@ -257,7 +323,7 @@ async function completeCancelledApiTurn(args) {
257
323
  await abandonTurnRecord({
258
324
  conversationId: args.conversationId,
259
325
  turnId: args.turnId,
260
- errorMessage: "API Turn cancelled"
326
+ errorMessage: "Turn cancelled"
261
327
  });
262
328
  clearPendingAuth(args.conversation, args.turnId);
263
329
  markConversationMessage(args.conversation, args.userMessageId, {
@@ -279,7 +345,9 @@ async function completeCancelledApiTurn(args) {
279
345
  conversation: args.conversation,
280
346
  sandboxRef: args.sandboxRef
281
347
  });
282
- await args.lifecycle.complete({
348
+ await new ConversationTurnLifecycleService(
349
+ getConversationEventStore()
350
+ ).complete({
283
351
  conversationId: args.conversationId,
284
352
  createdAtMs: Date.now(),
285
353
  outcome: "cancelled",
@@ -293,6 +361,19 @@ async function completeCancelledApiTurn(args) {
293
361
  await args.acknowledge();
294
362
  }
295
363
 
364
+ // src/chat/api-turns/mailbox-input.ts
365
+ function joinMailboxText(messages) {
366
+ return messages.map((message) => message.input.text.trim()).filter(Boolean).join("\n\n");
367
+ }
368
+ function localResourceEventActor() {
369
+ return {
370
+ platform: "local",
371
+ userId: RESOURCE_EVENT_AUTHOR_ID,
372
+ userName: "junior-event",
373
+ fullName: "Junior event"
374
+ };
375
+ }
376
+
296
377
  // src/chat/api-turns/work.ts
297
378
  function normalizeEmail(email) {
298
379
  return email.trim().toLowerCase();
@@ -504,66 +585,97 @@ function hasLostTurnInputCommit(error) {
504
585
  const cause = getConversationTurnBoundaryError(error)?.cause ?? error;
505
586
  return isTurnInputCommitLostError(error) || isTurnInputCommitLostError(cause);
506
587
  }
507
- function createApiTurnWorker(options) {
588
+ function createApiTurnWorker(agentRunner, cancellation) {
508
589
  return async (context) => {
509
590
  const resolved = await resolveApiTurnWork(context);
510
591
  if (!resolved) {
511
592
  throw new Error(
512
- `API turn worker received non-API work for ${context.conversationId}`
593
+ `Conversation API worker received other work for ${context.conversationId}`
513
594
  );
514
595
  }
515
596
  if (context.publishExternally) {
516
597
  throw new Error(
517
- `API turn work must not publish externally for ${context.conversationId}`
598
+ `Conversation API work cannot publish to a provider for ${context.conversationId}`
518
599
  );
519
600
  }
520
- const lifecycle = options.turnLifecycle ?? new ConversationTurnLifecycleService(getConversationEventStore());
601
+ const lifecycle = new ConversationTurnLifecycleService(
602
+ getConversationEventStore()
603
+ );
521
604
  const isResume = resolved.kind === "resume";
605
+ let isResourceEvent = resolved.kind === "resource_event";
522
606
  let actor;
523
- let text;
524
- let turnId;
525
- let userMessageId;
526
- let startedAtMs;
527
- let inputMessageIds;
607
+ let text = "";
608
+ let turnId = "";
609
+ let userMessageId = "";
610
+ let startedAtMs = Date.now();
611
+ let inputMessageIds = [];
528
612
  const storedConversation = await getConversationStore().get({
529
613
  conversationId: context.conversationId
530
614
  });
615
+ const mailboxDestination = resolved.kind === "mailbox" || resolved.kind === "resource_event" ? resolved.batch[0]?.message.destination : void 0;
616
+ const destination = resolveApiTurnDestination({
617
+ conversationId: context.conversationId,
618
+ existing: context.destination ?? mailboxDestination ?? storedConversation?.destination
619
+ });
620
+ const sourceFor = (resourceEvent) => resourceEvent ? createLocalSource(context.conversationId) : webSourceForConversation({
621
+ conversationId: context.conversationId,
622
+ visibility: storedConversation?.visibility
623
+ });
624
+ let source = sourceFor(isResourceEvent);
531
625
  if (resolved.kind === "mailbox") {
532
626
  const first = resolved.batch[0];
533
- text = resolved.batch.map((entry) => entry.message.input.text.trim()).filter(Boolean).join("\n\n");
627
+ text = joinMailboxText(resolved.batch.map((entry) => entry.message));
534
628
  actor = actorFromMetadata(first.metadata);
535
629
  turnId = apiTurnIdForMessage(first.metadata.messageId);
536
630
  userMessageId = first.metadata.messageId;
537
631
  startedAtMs = first.message.createdAtMs;
538
632
  inputMessageIds = resolved.batch.map((entry) => entry.metadata.messageId);
633
+ } else if (resolved.kind === "resource_event") {
634
+ const first = resolved.batch[0];
635
+ text = joinMailboxText(resolved.batch.map((entry) => entry.message));
636
+ actor = localResourceEventActor();
637
+ turnId = apiTurnIdForMessage(first.message.inboundMessageId);
638
+ userMessageId = first.message.inboundMessageId;
639
+ startedAtMs = first.message.createdAtMs;
640
+ inputMessageIds = resolved.batch.map(
641
+ (entry) => entry.message.inboundMessageId
642
+ );
539
643
  } else {
540
644
  turnId = resolved.turnId;
541
- const resumedActor = actorFromStoredConversation(
542
- storedConversation?.actor
543
- );
544
- if (!resumedActor) {
645
+ actor = actorFromStoredConversation(storedConversation?.actor);
646
+ }
647
+ const persisted = await getPersistedThreadState(context.conversationId);
648
+ const conversation = coerceThreadConversationState(persisted);
649
+ await hydrateConversationMessages({
650
+ conversation,
651
+ conversationId: context.conversationId
652
+ });
653
+ if (isResume) {
654
+ const userMessage = getTurnUserMessage(conversation, turnId);
655
+ if (!userMessage) {
545
656
  throw new Error(
546
- `API turn resume missing actor for ${context.conversationId}`
657
+ `Unable to locate the persisted user message for Turn "${turnId}"`
547
658
  );
548
659
  }
549
- actor = resumedActor;
550
- text = "";
551
- userMessageId = "";
552
- startedAtMs = Date.now();
553
- inputMessageIds = [];
660
+ if (isResourceEventConversationMessage(userMessage)) {
661
+ isResourceEvent = true;
662
+ actor = localResourceEventActor();
663
+ source = sourceFor(true);
664
+ }
665
+ userMessageId = userMessage.id;
666
+ text = userMessage.text;
667
+ startedAtMs = userMessage.createdAtMs;
668
+ inputMessageIds = [userMessageId];
669
+ }
670
+ if (!actor) {
671
+ throw new Error(
672
+ `Conversation API resume missing actor for ${context.conversationId}`
673
+ );
554
674
  }
555
- const destination = resolveApiTurnDestination({
556
- conversationId: context.conversationId,
557
- existing: context.destination ?? (resolved.kind === "mailbox" ? resolved.batch[0]?.message.destination : void 0) ?? storedConversation?.destination
558
- });
559
- const source = webSourceForConversation({
560
- conversationId: context.conversationId,
561
- visibility: storedConversation?.visibility
562
- });
563
675
  return await withLogContext(
564
676
  {
565
677
  conversationId: context.conversationId,
566
- platform: "web",
678
+ platform: isResourceEvent ? "local" : "web",
567
679
  userId: actor.userId,
568
680
  ...actor.userName ? { userName: actor.userName } : void 0
569
681
  },
@@ -577,25 +689,19 @@ function createApiTurnWorker(options) {
577
689
  await context.attempt.ack();
578
690
  } catch {
579
691
  throw new TurnInputCommitLostError(
580
- `Conversation work lease lost before API turn inbox ack for ${context.conversationId}`
692
+ `Conversation work lease lost before Conversation API message acknowledgement for ${context.conversationId}`
581
693
  );
582
694
  }
583
695
  acknowledged = true;
584
696
  };
585
- const persisted = await getPersistedThreadState(context.conversationId);
586
- const conversation = coerceThreadConversationState(persisted);
587
- await hydrateConversationMessages({
588
- conversation,
589
- conversationId: context.conversationId
590
- });
591
697
  let sandboxRef = getPersistedSandboxState(persisted);
592
698
  const initialSandboxRef = sandboxRef;
593
- const appSignal = options.cancellation?.signal(context.conversationId);
699
+ const appSignal = cancellation?.signal(context.conversationId);
594
700
  const stopSignal = context.stopSignal?.();
595
701
  const cancellationSignal = appSignal && stopSignal ? AbortSignal.any([appSignal, stopSignal]) : appSignal ?? stopSignal;
596
702
  const finishCancellation = () => {
597
703
  if (appSignal) {
598
- options.cancellation?.finish(context.conversationId, appSignal);
704
+ cancellation?.finish(context.conversationId, appSignal);
599
705
  }
600
706
  };
601
707
  const completeCancelledTurn = async () => {
@@ -603,10 +709,9 @@ function createApiTurnWorker(options) {
603
709
  await completeCancelledApiTurn({
604
710
  acknowledge,
605
711
  actorId: actor.userId,
606
- cancellation: options.cancellation,
712
+ cancellation,
607
713
  conversation,
608
714
  conversationId: context.conversationId,
609
- lifecycle,
610
715
  sandboxRef,
611
716
  signal: appSignal,
612
717
  turnId,
@@ -621,16 +726,6 @@ function createApiTurnWorker(options) {
621
726
  return { status: "completed" };
622
727
  };
623
728
  if (isResume) {
624
- const userMessage = getTurnUserMessage(conversation, turnId);
625
- if (!userMessage) {
626
- throw new Error(
627
- `Unable to locate the persisted user message for API turn "${turnId}"`
628
- );
629
- }
630
- userMessageId = userMessage.id;
631
- text = userMessage.text;
632
- startedAtMs = userMessage.createdAtMs;
633
- inputMessageIds = [userMessageId];
634
729
  if (cancellationSignal?.aborted) {
635
730
  return await completeCancelledTurn();
636
731
  }
@@ -649,7 +744,7 @@ function createApiTurnWorker(options) {
649
744
  meta: {
650
745
  explicitMention: true,
651
746
  replied: false,
652
- source: "web"
747
+ ...isResourceEvent ? void 0 : { source: "web" }
653
748
  }
654
749
  });
655
750
  await persistConversationMessages({
@@ -676,37 +771,23 @@ function createApiTurnWorker(options) {
676
771
  let failureCode = "persistence_failed";
677
772
  let modelFailureEventId;
678
773
  let modelFailureCaptureAttempted = false;
679
- let reply;
774
+ let completedSuccessfully = false;
680
775
  const deliverAssistantMessage = async (value) => {
776
+ const agentMessage = typeof value === "string" ? void 0 : value;
681
777
  const replyText = typeof value === "string" ? value : getAssistantReplyText(value);
682
778
  if (!replyText?.trim()) {
683
779
  return;
684
780
  }
685
781
  failureCode = "delivery_failed";
686
782
  assistantMessageDelivered = true;
687
- recordDeliveredAssistantMessage({
783
+ await commitWebAcceptedReply({
784
+ ...agentMessage ? { agentMessage } : void 0,
688
785
  conversation,
786
+ conversationId: context.conversationId,
689
787
  sessionId: turnId,
690
- source: "web",
691
788
  text: replyText,
692
789
  userMessageId
693
790
  });
694
- try {
695
- await persistWithRetry(
696
- () => persistConversationMessages({
697
- conversation,
698
- conversationId: context.conversationId
699
- })
700
- );
701
- } catch (error) {
702
- logException(
703
- new Error("Accepted assistant message persistence failed"),
704
- "api.assistant.message_post_delivery_persist.failed",
705
- {
706
- "error.type": error instanceof Error ? error.name : typeof error
707
- }
708
- );
709
- }
710
791
  failureCode = "agent_run_failed";
711
792
  };
712
793
  try {
@@ -751,53 +832,112 @@ function createApiTurnWorker(options) {
751
832
  failureCode = "agent_run_failed";
752
833
  currentRunId = `api-run:${stableHex(turnId, String(startedAtMs))}`;
753
834
  setTags({ runId: currentRunId });
754
- const outcome = await options.agentRunner.run({
755
- conversationId: context.conversationId,
756
- turnId,
757
- runId: currentRunId,
758
- instruction: {
759
- text,
760
- context: buildConversationContext(conversation, {
761
- excludeMessageId: userMessageId
762
- })
763
- },
764
- history: piMessages,
765
- actor,
766
- credentialContext: credentialContextForActor(actor),
767
- destination,
768
- publishExternally: false,
769
- source,
770
- surface: "api",
771
- ...cancellationSignal ? { signal: cancellationSignal } : void 0,
772
- authorization: createWebAuthorization({
773
- actorId: actor.userId,
774
- conversationId: context.conversationId
775
- }),
776
- state: {
777
- pendingAuth: conversation.processing.pendingAuth,
778
- sandboxRef
779
- },
780
- delivery: deliverAssistantMessage,
781
- durability: {
782
- onInputCommitted: acknowledge,
783
- shouldYield: context.shouldYield,
784
- onSandboxRefChanged: async (nextSandboxRef) => {
785
- sandboxRef = nextSandboxRef;
786
- await persistThreadStateById(context.conversationId, {
787
- conversation,
788
- sandboxRef
789
- });
835
+ const outcome = await executeTurn(
836
+ agentRunner,
837
+ {
838
+ conversationId: context.conversationId,
839
+ turnId,
840
+ runId: currentRunId,
841
+ instruction: {
842
+ text,
843
+ context: buildConversationContext(conversation, {
844
+ excludeMessageId: userMessageId
845
+ })
846
+ },
847
+ history: piMessages,
848
+ actor,
849
+ credentialContext: credentialContextForActor(actor),
850
+ destination,
851
+ publishExternally: false,
852
+ source,
853
+ surface: "api",
854
+ ...cancellationSignal ? { signal: cancellationSignal } : void 0,
855
+ authorization: createWebAuthorization({
856
+ actorId: actor.userId,
857
+ conversationId: context.conversationId
858
+ }),
859
+ state: {
860
+ pendingAuth: conversation.processing.pendingAuth,
861
+ sandboxRef
790
862
  },
791
- recordPendingAuth: async (pendingAuth) => {
792
- conversation.processing.pendingAuth = pendingAuth;
793
- await persistThreadStateById(context.conversationId, {
794
- conversation,
795
- sandboxRef
863
+ delivery: deliverAssistantMessage,
864
+ durability: {
865
+ onInputCommitted: acknowledge,
866
+ shouldYield: context.shouldYield,
867
+ onSandboxRefChanged: async (nextSandboxRef) => {
868
+ sandboxRef = nextSandboxRef;
869
+ await persistThreadStateById(context.conversationId, {
870
+ conversation,
871
+ sandboxRef
872
+ });
873
+ },
874
+ recordPendingAuth: async (pendingAuth) => {
875
+ conversation.processing.pendingAuth = pendingAuth;
876
+ await persistThreadStateById(context.conversationId, {
877
+ conversation,
878
+ sandboxRef
879
+ });
880
+ }
881
+ }
882
+ },
883
+ async (result) => {
884
+ if (cancellationSignal?.aborted) {
885
+ throw cancellationSignal.reason ?? new DOMException("Turn cancelled", "AbortError");
886
+ }
887
+ finishCancellation();
888
+ modelFailureCaptureAttempted = result.diagnostics.outcome !== "success";
889
+ const finalized = finalizeFailedTurnReplyWithEvent({
890
+ reply: result,
891
+ logException
892
+ });
893
+ const modelFailureReason = finalized.failureReason;
894
+ const reply = finalized.reply;
895
+ modelFailureEventId = finalized.eventId;
896
+ if (reply.diagnostics.outcome !== "success") {
897
+ await deliverAssistantMessage(reply.text);
898
+ }
899
+ const completedState = buildDeliveredTurnStatePatch({
900
+ conversation,
901
+ reply,
902
+ sessionId: turnId,
903
+ userMessageId
904
+ });
905
+ await persistThreadStateById(context.conversationId, {
906
+ conversation: completedState.conversation,
907
+ sandboxRef: reply.sandboxRef ?? sandboxRef
908
+ });
909
+ if (reply.piMessages?.length) {
910
+ const latest = await getTurnRecord(
911
+ context.conversationId,
912
+ turnId
913
+ );
914
+ await saveTurnCheckpoint({
915
+ mode: "completed",
916
+ conversationId: context.conversationId,
917
+ turnId,
918
+ sliceId: latest?.sliceId ?? 1,
919
+ messages: reply.piMessages,
920
+ durationMs: reply.diagnostics.durationMs,
921
+ usage: reply.diagnostics.usage,
922
+ destination,
923
+ publishExternally: false,
924
+ source,
925
+ actor,
926
+ surface: "api"
796
927
  });
797
928
  }
929
+ completedSuccessfully = reply.diagnostics.outcome === "success";
930
+ return completedSuccessfully ? {
931
+ outcome: assistantMessageDelivered ? "success" : "no_reply"
932
+ } : {
933
+ ...modelFailureEventId ? { eventId: modelFailureEventId } : void 0,
934
+ failureCode: "model_execution_failed",
935
+ ...modelFailureReason ? { failureReason: modelFailureReason } : void 0,
936
+ outcome: "failed"
937
+ };
798
938
  }
799
- });
800
- if (cancellationSignal?.aborted) {
939
+ );
940
+ if (outcome.status !== "completed" && cancellationSignal?.aborted) {
801
941
  return await completeCancelledTurn();
802
942
  }
803
943
  if (outcome.status === "suspended") {
@@ -813,57 +953,12 @@ function createApiTurnWorker(options) {
813
953
  sandboxRef
814
954
  });
815
955
  if (appSignal) {
816
- options.cancellation?.park(context.conversationId, appSignal);
956
+ cancellation?.park(context.conversationId, appSignal);
817
957
  }
818
958
  await acknowledge();
819
959
  return { status: "paused" };
820
960
  }
821
- finishCancellation();
822
- reply = outcome.result;
823
- modelFailureCaptureAttempted = reply.diagnostics.outcome !== "success";
824
- const finalized = finalizeFailedTurnReplyWithEvent({
825
- reply,
826
- logException
827
- });
828
- reply = finalized.reply;
829
- modelFailureEventId = finalized.eventId;
830
- if (reply.diagnostics.outcome !== "success") {
831
- await deliverAssistantMessage(reply.text);
832
- }
833
- const completedState = buildDeliveredTurnStatePatch({
834
- conversation,
835
- reply,
836
- sessionId: turnId,
837
- userMessageId
838
- });
839
- await persistThreadStateById(context.conversationId, {
840
- conversation: completedState.conversation,
841
- sandboxRef: reply.sandboxRef ?? sandboxRef
842
- });
843
- if (reply.piMessages?.length) {
844
- const latest = await getTurnRecord(context.conversationId, turnId);
845
- await saveTurnCheckpoint({
846
- mode: "completed",
847
- conversationId: context.conversationId,
848
- turnId,
849
- sliceId: latest?.sliceId ?? 1,
850
- messages: reply.piMessages,
851
- durationMs: reply.diagnostics.durationMs,
852
- usage: reply.diagnostics.usage,
853
- destination,
854
- publishExternally: false,
855
- source,
856
- actor,
857
- surface: "api"
858
- });
859
- }
860
- if (reply.diagnostics.outcome === "success") {
861
- await lifecycle.complete({
862
- conversationId: context.conversationId,
863
- createdAtMs: Date.now(),
864
- outcome: assistantMessageDelivered ? "success" : "no_reply",
865
- turnId
866
- });
961
+ if (completedSuccessfully) {
867
962
  try {
868
963
  await scheduleSessionCompletedPluginTasks(
869
964
  {
@@ -895,30 +990,23 @@ function createApiTurnWorker(options) {
895
990
  turnId
896
991
  });
897
992
  }
898
- } else {
899
- await lifecycle.fail({
900
- conversationId: context.conversationId,
901
- createdAtMs: Date.now(),
902
- ...modelFailureEventId ? { eventId: modelFailureEventId } : void 0,
903
- failureCode: "model_execution_failed",
904
- turnId
905
- });
906
993
  }
907
994
  await acknowledge();
908
995
  return { status: "completed" };
909
996
  } catch (error) {
910
- if (hasLostTurnInputCommit(error)) {
997
+ const failure = error instanceof AgentRunError ? error.cause : error;
998
+ if (hasLostTurnInputCommit(failure)) {
911
999
  return { status: "lost_lease" };
912
1000
  }
913
1001
  if (cancellationSignal?.aborted) {
914
1002
  return await completeCancelledTurn();
915
1003
  }
916
1004
  if (!context.attempt.isFinalAttempt) {
917
- throw error;
1005
+ throw failure;
918
1006
  }
919
1007
  const failureEventId = modelFailureCaptureAttempted && failureCode === "agent_run_failed" ? modelFailureEventId : captureApiBoundaryFailure({
920
1008
  conversationId: context.conversationId,
921
- error,
1009
+ error: failure,
922
1010
  failureCode,
923
1011
  runId: currentRunId,
924
1012
  turnId
@@ -1197,6 +1285,91 @@ async function readConversationAccessFromSql(db, conversationIds, viewer) {
1197
1285
  );
1198
1286
  }
1199
1287
 
1288
+ // src/chat/slack/user.ts
1289
+ import { z as z2 } from "zod";
1290
+ var slackUserInfoSchema = z2.object({
1291
+ ok: z2.literal(true),
1292
+ user: z2.object({
1293
+ name: z2.string().nullish(),
1294
+ profile: z2.object({
1295
+ email: z2.string().nullish(),
1296
+ real_name: z2.string().nullish()
1297
+ }).optional()
1298
+ })
1299
+ });
1300
+ var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
1301
+ var userCache = /* @__PURE__ */ new Map();
1302
+ function userCacheKey(teamId, userId) {
1303
+ return `${teamId}:${userId}`;
1304
+ }
1305
+ function readFromCache(teamId, userId) {
1306
+ const hit = userCache.get(userCacheKey(teamId, userId));
1307
+ if (!hit) return null;
1308
+ if (hit.expiresAt < Date.now()) {
1309
+ userCache.delete(userCacheKey(teamId, userId));
1310
+ return null;
1311
+ }
1312
+ return hit.value;
1313
+ }
1314
+ function writeToCache(teamId, userId, value) {
1315
+ userCache.set(userCacheKey(teamId, userId), {
1316
+ value,
1317
+ expiresAt: Date.now() + USER_CACHE_TTL_MS
1318
+ });
1319
+ }
1320
+ async function lookupSlackUser(teamId, userId) {
1321
+ if (!teamId || !userId) {
1322
+ return null;
1323
+ }
1324
+ const cached = readFromCache(teamId, userId);
1325
+ if (cached) {
1326
+ return cached;
1327
+ }
1328
+ const token = getSlackBotToken();
1329
+ if (!token) {
1330
+ return null;
1331
+ }
1332
+ try {
1333
+ const response = await fetch(
1334
+ `https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
1335
+ {
1336
+ headers: {
1337
+ authorization: `Bearer ${token}`
1338
+ }
1339
+ }
1340
+ );
1341
+ if (!response.ok) {
1342
+ logWarn("slack.user.lookup.failed", {
1343
+ "enduser.id": userId,
1344
+ "app.slack.team_id": teamId,
1345
+ "http.response.status_code": response.status
1346
+ });
1347
+ return null;
1348
+ }
1349
+ const parsed = slackUserInfoSchema.safeParse(await response.json());
1350
+ if (!parsed.success) {
1351
+ return null;
1352
+ }
1353
+ const payload = parsed.data;
1354
+ const userName = payload.user.name?.trim() || void 0;
1355
+ const fullName = payload.user.profile?.real_name?.trim() || void 0;
1356
+ const result = {
1357
+ userName,
1358
+ fullName,
1359
+ email: payload.user.profile?.email?.trim() || void 0
1360
+ };
1361
+ writeToCache(teamId, userId, result);
1362
+ return result;
1363
+ } catch (error) {
1364
+ logWarn("slack.user.lookup.failed", {
1365
+ "enduser.id": userId,
1366
+ "app.slack.team_id": teamId,
1367
+ "exception.message": error instanceof Error ? error.message : String(error)
1368
+ });
1369
+ return null;
1370
+ }
1371
+ }
1372
+
1200
1373
  // src/chat/slack/conversation-context.ts
1201
1374
  function normalizeConversationName(type, channelName) {
1202
1375
  const trimmed = channelName?.trim();
@@ -1285,6 +1458,7 @@ export {
1285
1458
  readHealthReport,
1286
1459
  GET,
1287
1460
  createVercelAttachmentStorage,
1461
+ lookupSlackUser,
1288
1462
  conversationVisibilityFromSlackChannelType,
1289
1463
  resolveSlackChannelTypeFromMessage,
1290
1464
  resolveSlackConversationContext,