@sentry/junior 0.137.0 → 0.139.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 (103) hide show
  1. package/dist/{agent-hooks-MZOKEP3J.js → agent-hooks-MJXMD7JP.js} +11 -9
  2. package/dist/api/schema/task.d.ts +100 -1
  3. package/dist/api/schema.d.ts +2 -2
  4. package/dist/api/schema.js +9 -1
  5. package/dist/api.js +202 -55
  6. package/dist/app.d.ts +7 -0
  7. package/dist/app.js +601 -1356
  8. package/dist/{catalog-runtime-MD66YLFV.js → catalog-runtime-GCCFYPIK.js} +6 -6
  9. package/dist/chat/agent/request.d.ts +44 -4
  10. package/dist/chat/agent-dispatch/heartbeat.d.ts +5 -0
  11. package/dist/chat/agent-dispatch/types.d.ts +1 -1
  12. package/dist/chat/agent-invocations/errors.d.ts +8 -0
  13. package/dist/chat/agent-invocations/spawn.d.ts +12 -0
  14. package/dist/chat/agent-invocations/store.d.ts +44 -0
  15. package/dist/chat/agent-invocations/types.d.ts +449 -0
  16. package/dist/chat/agent-invocations/work.d.ts +31 -0
  17. package/dist/chat/config.d.ts +1 -0
  18. package/dist/chat/conversations/sql/store.d.ts +8 -0
  19. package/dist/chat/conversations/store.d.ts +7 -0
  20. package/dist/chat/credentials/context.d.ts +14 -0
  21. package/dist/chat/experimental.d.ts +16 -0
  22. package/dist/chat/resource-events/actor.d.ts +23 -0
  23. package/dist/chat/runtime/agent-runner.d.ts +2 -1
  24. package/dist/chat/services/auth-pause.d.ts +0 -1
  25. package/dist/chat/services/mcp-auth-orchestration.d.ts +3 -2
  26. package/dist/chat/services/plugin-auth-orchestration.d.ts +3 -2
  27. package/dist/chat/state/turn-session.d.ts +1 -5
  28. package/dist/chat/task-execution/state.d.ts +23 -11
  29. package/dist/chat/task-execution/store.d.ts +20 -11
  30. package/dist/chat/task-execution/worker.d.ts +2 -2
  31. package/dist/chat/tasks/execution-stats.d.ts +34 -3
  32. package/dist/chat/tasks/read.d.ts +3 -1
  33. package/dist/chat/tools/runtime/spawn-agent.d.ts +31 -0
  34. package/dist/chat/tools/types.d.ts +2 -0
  35. package/dist/{chunk-T6Z5S6PQ.js → chunk-2ECYPYKP.js} +1 -1
  36. package/dist/chunk-3T2JAGRH.js +115 -0
  37. package/dist/chunk-3Y47VMHV.js +6 -0
  38. package/dist/chunk-4V4QIYJH.js +41 -0
  39. package/dist/{chunk-SYCX4DOK.js → chunk-5GSZWRE2.js} +434 -65
  40. package/dist/chunk-5IDFPCON.js +46 -0
  41. package/dist/{chunk-36EG34YI.js → chunk-6OHTDR4F.js} +1 -1
  42. package/dist/chunk-6SU2DT27.js +441 -0
  43. package/dist/{chunk-UG6ZYPBI.js → chunk-75I3UBIL.js} +73 -60
  44. package/dist/chunk-AEQISMMU.js +711 -0
  45. package/dist/{chunk-5PNJMOY5.js → chunk-C2N6MNO4.js} +9 -2
  46. package/dist/{chunk-BZAMASJP.js → chunk-CXH25EL4.js} +2 -2
  47. package/dist/{chunk-K2NCQJKH.js → chunk-DW4PMHNW.js} +3 -3
  48. package/dist/chunk-EM225ITJ.js +56 -0
  49. package/dist/{chunk-UOXATPSI.js → chunk-G52UXKGO.js} +7 -5
  50. package/dist/chunk-GI52BW74.js +207 -0
  51. package/dist/{chunk-3Q342EJJ.js → chunk-GWQSZT4C.js} +2 -2
  52. package/dist/{chunk-EPBBYMNE.js → chunk-HUO3UTAQ.js} +1 -1
  53. package/dist/{chunk-WPPXIAFB.js → chunk-IIESAYDL.js} +17 -434
  54. package/dist/chunk-IUFM5JWV.js +24 -0
  55. package/dist/{chunk-UZQ4VY3M.js → chunk-J3S57AXY.js} +1 -1
  56. package/dist/{chunk-DQXAAVTR.js → chunk-J6EJQCHY.js} +46 -1471
  57. package/dist/{chunk-6ZK6FZIV.js → chunk-KCBZSJRU.js} +6 -27
  58. package/dist/chunk-MQ5RYATN.js +1523 -0
  59. package/dist/{chunk-5G34IEK2.js → chunk-MXTVUV3I.js} +2 -56
  60. package/dist/{chunk-YRPC6MJY.js → chunk-MXVD2BDS.js} +1 -1
  61. package/dist/{chunk-POBSY47Q.js → chunk-PJW6U46K.js} +337 -306
  62. package/dist/chunk-Q6QZTTLS.js +55 -0
  63. package/dist/{chunk-ZFS67WIR.js → chunk-QTBKPZDA.js} +57 -6
  64. package/dist/chunk-R776HKPK.js +81 -0
  65. package/dist/{chunk-77VDLF3Z.js → chunk-RUIXF3YQ.js} +10 -0
  66. package/dist/chunk-U27DSXKB.js +714 -0
  67. package/dist/{chunk-XZ7NPXAB.js → chunk-U7CMWQ7Z.js} +1 -1
  68. package/dist/{chunk-RB4NJKEX.js → chunk-UDBUS533.js} +125 -51
  69. package/dist/chunk-WQRX57EA.js +668 -0
  70. package/dist/chunk-WZH4JV7R.js +320 -0
  71. package/dist/{chunk-HCIVH3JS.js → chunk-YCTDA7BD.js} +28 -0
  72. package/dist/{chunk-YNFERGKP.js → chunk-YGZ2OLXL.js} +1 -1
  73. package/dist/{chunk-EWDSB24B.js → chunk-ZQCGQMF2.js} +16 -776
  74. package/dist/{chunk-KJ4HGRHN.js → chunk-ZYQSPQYR.js} +515 -601
  75. package/dist/cli/chat.js +68 -29
  76. package/dist/cli/check.js +7 -7
  77. package/dist/cli/plugins.js +15 -13
  78. package/dist/cli/snapshot-create.js +11 -10
  79. package/dist/cli/upgrade.js +8 -8
  80. package/dist/conversation-work-KATFHLG3.js +56 -0
  81. package/dist/db/schema/agent-invocations.d.ts +544 -0
  82. package/dist/db/schema/task-executions.d.ts +23 -2
  83. package/dist/db/schema.d.ts +563 -2
  84. package/dist/{db-66E7VDNC.js → db-UXPPGHDF.js} +7 -7
  85. package/dist/experimental-HH6OM6UH.js +13 -0
  86. package/dist/instrumentation.js +4 -4
  87. package/dist/nitro.js +4 -3
  88. package/dist/{oauth-callback-server-7QPD4Q7P.js → oauth-callback-server-4ZYMD5UA.js} +28 -19
  89. package/dist/{runner-L2BKJKOD.js → runner-MZSEONRZ.js} +32 -18
  90. package/dist/{sandbox-egress-signals-A5HWIQH4.js → sandbox-egress-signals-EKPAVWQL.js} +2 -2
  91. package/dist/{skills-HOQ2NSPW.js → skills-IMVPXSQ7.js} +7 -7
  92. package/dist/spawn-S5LSVOCE.js +38 -0
  93. package/dist/{validation-ENHI7S2C.js → validation-ZKCY2BVR.js} +7 -7
  94. package/dist/version.js +1 -1
  95. package/dist/work-GG3XDYRX.js +46 -0
  96. package/dist/worker-23AUELCJ.js +24 -0
  97. package/migrations/0021_first_warhawk.sql +35 -0
  98. package/migrations/0022_task_execution_status.sql +3 -0
  99. package/migrations/meta/0021_snapshot.json +2176 -0
  100. package/migrations/meta/0022_snapshot.json +2186 -0
  101. package/migrations/meta/_journal.json +15 -1
  102. package/package.json +4 -4
  103. package/dist/chunk-6QW2RRQT.js +0 -233
package/dist/app.js CHANGED
@@ -1,27 +1,25 @@
1
1
  import {
2
- CONVERSATION_WORK_CHECK_IN_INTERVAL_MS,
3
- CONVERSATION_WORK_STALE_ENQUEUE_MS,
2
+ processConversationWork
3
+ } from "./chunk-WQRX57EA.js";
4
+ import {
5
+ bindSpawnAgent
6
+ } from "./chunk-4V4QIYJH.js";
7
+ import {
8
+ createAgentInvocationWorker,
9
+ enqueueAgentInvocation,
10
+ listPendingAgentInvocationMailboxAppends,
11
+ routeAgentInvocationWork
12
+ } from "./chunk-AEQISMMU.js";
13
+ import {
4
14
  GET as GET2,
5
15
  GET2 as GET3,
6
16
  ResumeTurnBusyError,
7
- ackMessages,
8
- appendAndEnqueueInboundMessage,
9
- beginConversationResume,
10
17
  buildAuthPauseResponse,
11
18
  buildSlackOutputMessage,
12
- checkInConversationWork,
13
- clearConsumedConversationWake,
14
- clearExpiredConversationLease,
15
- completeConversationWork,
16
- countPendingConversationMessages,
17
19
  createSlackAdapterAssistantStatusSession,
18
- deadLetterAttempt,
19
- drainConversationMailbox,
20
- ensureConversationWake,
21
20
  getAssistantThreadContext,
22
21
  getAwaitingAgentContinueRequest,
23
22
  getChannelId,
24
- getConversationWorkState,
25
23
  getMessageTs,
26
24
  getRunId,
27
25
  getSlackApiErrorCode,
@@ -29,61 +27,84 @@ import {
29
27
  getThreadId,
30
28
  getThreadTs,
31
29
  getTurnRequestDeadline,
32
- hasRunnableConversationWork,
33
- isFinalAttempt,
34
- isInvalidConversationRecordError,
35
30
  isRetryableSlackPostError,
36
31
  isSlackTitlePermissionError,
37
- listActiveConversationIds,
38
32
  publishAppHomeView,
39
33
  receiveLocalOAuthCredential,
40
- recordAttemptFailure,
41
- releaseConversationWork,
42
- removeActiveConversation,
43
- requestConversationContinuation,
44
34
  resumeSlackTurn,
45
35
  runWithTurnRequestDeadline,
46
36
  scheduleAgentContinue,
47
37
  sendSlackReply,
48
38
  shouldKeepProcessingReactionForToolInvocation,
49
39
  splitSlackReplyText,
50
- startConversationWork,
51
40
  startSlackProcessingReaction,
52
41
  stripLeadingBotMention
53
- } from "./chunk-DQXAAVTR.js";
42
+ } from "./chunk-J6EJQCHY.js";
54
43
  import "./chunk-FA6POGLH.js";
55
44
  import {
56
- ConversationTurnLifecycleService,
45
+ CONVERSATION_WORK_STALE_ENQUEUE_MS,
46
+ appendAndEnqueueInboundMessage,
47
+ clearExpiredConversationLease,
48
+ ensureConversationWake,
49
+ getConversationWorkState,
50
+ hasRunnableConversationWork,
51
+ isInvalidConversationRecordError,
52
+ listActiveConversationIds,
53
+ removeActiveConversation
54
+ } from "./chunk-MQ5RYATN.js";
55
+ import {
57
56
  buildDeliveredTurnStatePatch,
57
+ claimDispatchMailboxAppend,
58
58
  completeAuthPauseTurn,
59
+ confirmDispatchMailboxAppend,
60
+ createOrGetDispatch,
59
61
  finalizeFailedTurnReplyWithEvent,
60
62
  getAgentTurnDiagnosticsAttributes,
63
+ getDispatchConversationId,
64
+ getDispatchInputMessageId,
65
+ getDispatchInputMessageIds,
66
+ getDispatchRecord,
67
+ getDispatchTurnId,
68
+ getPluginDispatchProjection,
61
69
  getTurnUserMessage,
62
70
  getTurnUserReplyAttachmentContext,
63
71
  getTurnUserSlackMessageTs,
72
+ isTerminalDispatchStatus,
73
+ listPendingDispatchMailboxAppends,
74
+ markDispatchAwaitingResume,
75
+ markDispatchBlocked,
76
+ markDispatchCompleted,
77
+ markDispatchFailed,
78
+ markDispatchRunning,
64
79
  persistAuthPauseTurnState,
65
80
  processPluginTask,
66
81
  requireTurnFailureEventId,
67
82
  resolveTurnSessionRouting,
68
83
  scheduleSessionCompletedPluginTasks
69
- } from "./chunk-SYCX4DOK.js";
84
+ } from "./chunk-5GSZWRE2.js";
85
+ import {
86
+ ConversationTurnLifecycleService
87
+ } from "./chunk-Q6QZTTLS.js";
70
88
  import {
71
89
  GET,
72
90
  conversationVisibilityFromSlackChannelType,
73
- recordTaskExecution,
74
91
  resolveSlackChannelTypeFromMessage,
75
92
  resolveSlackConversationContext
76
- } from "./chunk-6QW2RRQT.js";
93
+ } from "./chunk-3T2JAGRH.js";
77
94
  import "./chunk-4CODA7P5.js";
95
+ import {
96
+ recordTaskExecution
97
+ } from "./chunk-GI52BW74.js";
78
98
  import "./chunk-UTL3MHPC.js";
79
99
  import {
80
- ConversationQueueMessageRejectedError,
81
- conversationQueueMessageSchema,
82
100
  getVercelConversationWorkQueue,
83
- isConversationQueueMessageRejectedError,
84
101
  resolveConversationWorkQueueTopic,
85
102
  verifyConversationQueueMessage
86
- } from "./chunk-6ZK6FZIV.js";
103
+ } from "./chunk-KCBZSJRU.js";
104
+ import {
105
+ conversationQueueMessageSchema,
106
+ isConversationQueueMessageRejectedError
107
+ } from "./chunk-IUFM5JWV.js";
87
108
  import {
88
109
  PLUGIN_TASK_QUEUE_TOPIC,
89
110
  createVercelQueueClient,
@@ -92,7 +113,7 @@ import {
92
113
  import {
93
114
  validatePluginEgressCredentialHooks,
94
115
  validatePluginRegistrations
95
- } from "./chunk-UZQ4VY3M.js";
116
+ } from "./chunk-J3S57AXY.js";
96
117
  import {
97
118
  defineJuniorPlugins,
98
119
  pluginCatalogConfigFromEnv,
@@ -100,8 +121,6 @@ import {
100
121
  pluginRuntimeRegistrationsFromPluginSet
101
122
  } from "./chunk-FIANEE7Z.js";
102
123
  import {
103
- AuthorizationFlowDisabledError,
104
- PluginCredentialFailureError,
105
124
  SANDBOX_EGRESS_PROXY_PATH,
106
125
  appendSlackLegacyAttachmentText,
107
126
  buildSteeringPiMessage,
@@ -127,22 +146,29 @@ import {
127
146
  setConfigDefaults,
128
147
  shouldEmitDevAgentTrace,
129
148
  shouldPropagateSandboxEgressTrace
130
- } from "./chunk-KJ4HGRHN.js";
149
+ } from "./chunk-ZYQSPQYR.js";
150
+ import {
151
+ AuthorizationFlowDisabledError,
152
+ PluginCredentialFailureError
153
+ } from "./chunk-WZH4JV7R.js";
131
154
  import "./chunk-DIB4GX3G.js";
132
155
  import {
133
- RetryableDeliveryError,
134
156
  createUserTokenStore,
135
157
  deleteMcpAuthSessionsForUserProvider,
136
158
  deleteMcpServerSessionId,
137
- deleteMcpStoredOAuthCredentials,
159
+ deleteMcpStoredOAuthCredentials
160
+ } from "./chunk-ZQCGQMF2.js";
161
+ import {
162
+ RetryableDeliveryError
163
+ } from "./chunk-R776HKPK.js";
164
+ import {
138
165
  formatProviderLabel,
139
166
  postSlackMessage,
140
167
  startOAuthFlow
141
- } from "./chunk-EWDSB24B.js";
168
+ } from "./chunk-U27DSXKB.js";
142
169
  import {
143
170
  ConversationTurnBoundaryError,
144
171
  CooperativeTurnYieldError,
145
- JUNIOR_THREAD_STATE_TTL_MS,
146
172
  TurnInputCommitLostError,
147
173
  TurnInputDeferredError,
148
174
  abandonAgentTurnSessionRecord,
@@ -195,47 +221,54 @@ import {
195
221
  trimTrailingAssistantMessages,
196
222
  updateConversationStats,
197
223
  upsertConversationMessage
198
- } from "./chunk-POBSY47Q.js";
199
- import "./chunk-EPBBYMNE.js";
224
+ } from "./chunk-PJW6U46K.js";
225
+ import "./chunk-3Y47VMHV.js";
226
+ import "./chunk-HUO3UTAQ.js";
227
+ import {
228
+ getExperimentalFeatures,
229
+ setExperimentalFeatures
230
+ } from "./chunk-5IDFPCON.js";
200
231
  import {
201
232
  getSandboxResourceConfig,
202
233
  setSandboxResourceConfig
203
- } from "./chunk-UOXATPSI.js";
234
+ } from "./chunk-G52UXKGO.js";
204
235
  import {
205
236
  createSchedulerSqlStore,
206
237
  findMatchingEventTasks
207
- } from "./chunk-K2NCQJKH.js";
238
+ } from "./chunk-DW4PMHNW.js";
208
239
  import {
209
240
  parseSlackThreadId
210
- } from "./chunk-YRPC6MJY.js";
241
+ } from "./chunk-MXVD2BDS.js";
211
242
  import "./chunk-CDQ4YIZM.js";
212
243
  import "./chunk-PDO5BLNM.js";
213
244
  import "./chunk-UIE3R5XU.js";
214
245
  import {
215
- SlackActionError,
216
246
  bindEventTaskCredentialSubject,
217
247
  bindScheduledTaskCredentialSubject,
218
248
  bindSlackDirectCredentialSubject,
219
249
  createPluginLogger,
220
250
  createPluginState,
221
251
  createResourceEventTeamIdResolver,
222
- downloadPrivateSlackFile,
223
252
  getPluginApiRoutes,
224
253
  getPluginRoutes,
225
254
  getPlugins,
226
- getSlackClient,
227
- getWorkspaceTeamId,
228
- isDmChannel,
229
- normalizeSlackConversationId,
230
- runWithSlackInstallationToken,
231
- runWithWorkspaceTeamId,
232
255
  setDashboardConversationLinkOptions,
233
256
  setPlugins,
234
257
  validatePlugins,
235
258
  verifyEventTaskCredentialSubject,
236
259
  verifyScheduledTaskCredentialSubject,
237
260
  verifySlackDirectCredentialSubject
238
- } from "./chunk-WPPXIAFB.js";
261
+ } from "./chunk-IIESAYDL.js";
262
+ import {
263
+ SlackActionError,
264
+ downloadPrivateSlackFile,
265
+ getSlackClient,
266
+ getWorkspaceTeamId,
267
+ isDmChannel,
268
+ normalizeSlackConversationId,
269
+ runWithSlackInstallationToken,
270
+ runWithWorkspaceTeamId
271
+ } from "./chunk-6SU2DT27.js";
239
272
  import {
240
273
  contextProvenance,
241
274
  createSlackDestination,
@@ -245,17 +278,22 @@ import {
245
278
  getSqlExecutor,
246
279
  instructionProvenanceFor,
247
280
  requireSlackDestination,
248
- withConversationEventLock
249
- } from "./chunk-ZFS67WIR.js";
281
+ withConversationEventLock,
282
+ withConversationMutationLock
283
+ } from "./chunk-QTBKPZDA.js";
250
284
  import {
285
+ juniorAgentBindings,
286
+ juniorAgentInvocations,
251
287
  juniorConversationEvents,
252
288
  juniorConversations
253
- } from "./chunk-RB4NJKEX.js";
289
+ } from "./chunk-UDBUS533.js";
254
290
  import {
255
- acquireActiveLock,
256
- getStateAdapter,
257
291
  sleep
258
- } from "./chunk-5G34IEK2.js";
292
+ } from "./chunk-EM225ITJ.js";
293
+ import {
294
+ acquireActiveLock,
295
+ getStateAdapter
296
+ } from "./chunk-MXTVUV3I.js";
259
297
  import {
260
298
  STANDARD_MODEL_PROFILE,
261
299
  botConfig,
@@ -273,19 +311,19 @@ import {
273
311
  modelIdForProfile,
274
312
  setSlackReactionConfig,
275
313
  standardModelId
276
- } from "./chunk-UG6ZYPBI.js";
314
+ } from "./chunk-75I3UBIL.js";
277
315
  import {
278
316
  juniorDestinations
279
317
  } from "./chunk-VFUK3X5B.js";
280
- import "./chunk-YNFERGKP.js";
281
- import "./chunk-3Q342EJJ.js";
318
+ import "./chunk-YGZ2OLXL.js";
319
+ import "./chunk-GWQSZT4C.js";
282
320
  import {
283
321
  pluginCatalogRuntime
284
- } from "./chunk-36EG34YI.js";
285
- import "./chunk-BZAMASJP.js";
322
+ } from "./chunk-6OHTDR4F.js";
323
+ import "./chunk-CXH25EL4.js";
286
324
  import {
287
- credentialSubjectSchema
288
- } from "./chunk-77VDLF3Z.js";
325
+ credentialContextForActor
326
+ } from "./chunk-RUIXF3YQ.js";
289
327
  import {
290
328
  createActor,
291
329
  createSlackActor,
@@ -314,7 +352,7 @@ import {
314
352
  withContext,
315
353
  withLogContext,
316
354
  withSpan
317
- } from "./chunk-T6Z5S6PQ.js";
355
+ } from "./chunk-2ECYPYKP.js";
318
356
  import {
319
357
  continueTrace
320
358
  } from "./chunk-6GWA276C.js";
@@ -322,10 +360,10 @@ import "./chunk-OOUNJNMR.js";
322
360
  import "./chunk-H4HBKISO.js";
323
361
  import {
324
362
  JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE
325
- } from "./chunk-5PNJMOY5.js";
363
+ } from "./chunk-C2N6MNO4.js";
326
364
  import {
327
365
  JUNIOR_VERSION
328
- } from "./chunk-XZ7NPXAB.js";
366
+ } from "./chunk-U7CMWQ7Z.js";
329
367
  import "./chunk-MLKGABMK.js";
330
368
 
331
369
  // src/app.ts
@@ -387,400 +425,20 @@ async function verifyDispatchCallbackRequest(request) {
387
425
  }
388
426
  }
389
427
 
390
- // src/chat/agent-dispatch/store.ts
391
- import { createHash } from "crypto";
392
- import {
393
- destinationSchema,
394
- destinationVisibilitySchema,
395
- isSlackDestination,
396
- replyAttributionSchema,
397
- sourceSchema
398
- } from "@sentry/junior-plugin-api";
399
- import { z as z2 } from "zod";
400
- var DISPATCH_PREFIX = "junior:agent_dispatch";
401
- var DISPATCH_LOCK_TTL_MS = 10 * 60 * 1e3;
402
- var DISPATCH_MAILBOX_APPEND_INDEX_LOCK_TTL_MS = 1e4;
403
- var DISPATCH_MAILBOX_APPEND_INDEX_MAX_LENGTH = 1e4;
404
- var nonEmptyExactStringSchema = z2.string().min(1).refine(
405
- (value) => value === value.trim() && value.toLowerCase() !== "unknown"
406
- );
407
- var pendingDispatchMailboxAppendIndexSchema = z2.array(
408
- nonEmptyExactStringSchema
409
- );
410
- var dispatchStatusSchema = z2.enum([
411
- "pending",
412
- "running",
413
- "awaiting_resume",
414
- "completed",
415
- "failed",
416
- "blocked"
417
- ]);
418
- var dispatchActorSchema = z2.object({
419
- platform: z2.literal("system"),
420
- name: nonEmptyExactStringSchema
421
- }).strict();
422
- var dispatchRecordSchema = z2.object({
423
- actor: dispatchActorSchema,
424
- createdAtMs: z2.number().finite(),
425
- credentialSubject: credentialSubjectSchema.optional(),
426
- destination: destinationSchema,
427
- destinationVisibility: destinationVisibilitySchema,
428
- errorMessage: z2.string().optional(),
429
- id: nonEmptyExactStringSchema,
430
- idempotencyKey: z2.string().min(1),
431
- input: z2.string().min(1),
432
- metadata: z2.record(z2.string(), z2.string()).optional(),
433
- plugin: nonEmptyExactStringSchema,
434
- replyAttribution: replyAttributionSchema.optional(),
435
- resultMessageTs: z2.string().optional(),
436
- source: sourceSchema,
437
- status: dispatchStatusSchema,
438
- updatedAtMs: z2.number().finite()
439
- }).strict().superRefine((record, ctx) => {
440
- if (!isSlackDestination(record.destination)) {
441
- ctx.addIssue({
442
- code: z2.ZodIssueCode.custom,
443
- message: "Dispatch destination platform must be slack",
444
- path: ["destination"]
445
- });
446
- return;
447
- }
448
- const subject = record.credentialSubject;
449
- if (!subject) {
450
- return;
451
- }
452
- if (subject.allowedWhen === "private-direct-conversation") {
453
- if (!record.destination.channelId.startsWith("D")) {
454
- ctx.addIssue({
455
- code: z2.ZodIssueCode.custom,
456
- message: "Dispatch credentialSubject requires a private direct Slack destination",
457
- path: ["credentialSubject"]
458
- });
459
- return;
460
- }
461
- if (subject.binding.type !== "slack-direct-conversation" || subject.binding.teamId !== record.destination.teamId || subject.binding.channelId !== record.destination.channelId) {
462
- ctx.addIssue({
463
- code: z2.ZodIssueCode.custom,
464
- message: "Dispatch credentialSubject binding must match destination",
465
- path: ["credentialSubject", "binding"]
466
- });
467
- }
468
- return;
469
- }
470
- if (subject.binding.type !== subject.allowedWhen || subject.binding.plugin !== record.plugin || subject.binding.taskId !== subject.taskId) {
471
- ctx.addIssue({
472
- code: z2.ZodIssueCode.custom,
473
- message: "Dispatch credentialSubject binding must match task",
474
- path: ["credentialSubject", "binding"]
475
- });
476
- }
477
- });
478
- var storedDispatchRecordSchema = dispatchRecordSchema.extend({
479
- attempt: z2.number().int().nonnegative().optional(),
480
- lastCallbackAtMs: z2.number().finite().optional(),
481
- leaseExpiresAtMs: z2.number().finite().optional(),
482
- maxAttempts: z2.number().int().positive().optional(),
483
- version: z2.number().int().positive().optional()
484
- });
485
- function getDispatchStorageKey(id) {
486
- return `${DISPATCH_PREFIX}:record:${id}`;
487
- }
488
- function dispatchLockKey(id) {
489
- return `${DISPATCH_PREFIX}:lock:${id}`;
490
- }
491
- function pendingDispatchMailboxAppendIndexKey() {
492
- return `${DISPATCH_PREFIX}:incomplete`;
493
- }
494
- function pendingDispatchMailboxAppendIndexLockKey() {
495
- return `${DISPATCH_PREFIX}:incomplete:lock`;
496
- }
497
- function normalizeMetadata(metadata) {
498
- if (!metadata) {
499
- return void 0;
500
- }
501
- const entries = Object.entries(metadata).filter(
502
- (entry) => typeof entry[0] === "string" && typeof entry[1] === "string"
503
- );
504
- return entries.length > 0 ? Object.fromEntries(entries) : void 0;
505
- }
506
- function buildDispatchId(plugin, idempotencyKey) {
507
- const digest = createHash("sha256").update(plugin).update("\0").update(idempotencyKey).digest("hex").slice(0, 32);
508
- return `dispatch_${digest}`;
509
- }
510
- function parseDispatchRecord(value) {
511
- const parsed = parseStoredDispatchRecord(value);
512
- return parsed?.record;
513
- }
514
- function parseStoredDispatchRecord(value) {
515
- const parsed = storedDispatchRecordSchema.safeParse(value);
516
- if (!parsed.success) {
517
- return void 0;
518
- }
519
- const {
520
- attempt: _attempt,
521
- lastCallbackAtMs: _lastCallbackAtMs,
522
- leaseExpiresAtMs: _leaseExpiresAtMs,
523
- maxAttempts: _maxAttempts,
524
- version: _version,
525
- ...record
526
- } = parsed.data;
527
- return {
528
- ...typeof parsed.data.leaseExpiresAtMs === "number" ? { legacyLeaseExpiresAtMs: parsed.data.leaseExpiresAtMs } : {},
529
- record
530
- };
531
- }
532
- function getDispatchConversationId(dispatch2) {
533
- return `agent-dispatch:${dispatch2.id}`;
534
- }
535
- function getDispatchInputMessageId(dispatchId) {
536
- return `agent-dispatch:${dispatchId}`;
537
- }
538
- function getLegacyDispatchInputMessageId(dispatchId) {
539
- return `dispatch:${dispatchId}:user`;
540
- }
541
- function getDispatchInputMessageIds(dispatchId) {
542
- return [
543
- getDispatchInputMessageId(dispatchId),
544
- getLegacyDispatchInputMessageId(dispatchId)
545
- ];
546
- }
547
- function getDispatchTurnId(dispatchId) {
548
- return `dispatch:${dispatchId}`;
549
- }
550
- function toDispatchProjection(record) {
551
- return {
552
- id: record.id,
553
- status: record.status,
554
- ...record.resultMessageTs ? { resultMessageTs: record.resultMessageTs } : {},
555
- ...record.errorMessage ? { errorMessage: record.errorMessage } : {}
556
- };
557
- }
558
- function isTerminalDispatchStatus(status) {
559
- return status === "completed" || status === "failed" || status === "blocked";
560
- }
561
- async function withDispatchLock(dispatchId, task) {
562
- const state = getStateAdapter();
563
- await state.connect();
564
- const lock = await state.acquireLock(
565
- dispatchLockKey(dispatchId),
566
- DISPATCH_LOCK_TTL_MS
567
- );
568
- if (!lock) {
569
- throw new Error(`Could not acquire dispatch lock for ${dispatchId}`);
570
- }
571
- try {
572
- return await task(state);
573
- } finally {
574
- await state.releaseLock(lock);
575
- }
576
- }
577
- async function putRecord(state, record) {
578
- const parsed = dispatchRecordSchema.safeParse(record);
579
- if (!parsed.success) {
580
- throw new Error(
581
- `Dispatch record is invalid: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"} ${issue.message}`).join("; ")}`
582
- );
583
- }
584
- const next = parsed.data;
585
- await state.set(
586
- getDispatchStorageKey(next.id),
587
- next,
588
- JUNIOR_THREAD_STATE_TTL_MS
589
- );
590
- }
591
- async function updatePendingDispatchMailboxAppendIndex(state, update) {
592
- const lock = await state.acquireLock(
593
- pendingDispatchMailboxAppendIndexLockKey(),
594
- DISPATCH_MAILBOX_APPEND_INDEX_LOCK_TTL_MS
595
- );
596
- if (!lock) {
597
- throw new Error("Could not acquire dispatch mailbox append index lock");
598
- }
599
- try {
600
- const stored = await state.get(pendingDispatchMailboxAppendIndexKey());
601
- const ids = stored === void 0 || stored === null ? [] : pendingDispatchMailboxAppendIndexSchema.parse(stored);
602
- const next = [...new Set(update([...new Set(ids)]))].slice(
603
- -DISPATCH_MAILBOX_APPEND_INDEX_MAX_LENGTH
604
- );
605
- if (next.length === ids.length && next.every((id, index) => id === ids[index])) {
606
- return;
607
- }
608
- await state.set(
609
- pendingDispatchMailboxAppendIndexKey(),
610
- next,
611
- JUNIOR_THREAD_STATE_TTL_MS
612
- );
613
- } finally {
614
- await state.releaseLock(lock);
615
- }
616
- }
617
- async function getDispatchRecord(id) {
618
- const state = getStateAdapter();
619
- await state.connect();
620
- return parseDispatchRecord(await state.get(getDispatchStorageKey(id)));
621
- }
622
- async function createOrGetDispatch(args) {
623
- const id = buildDispatchId(args.plugin, args.options.idempotencyKey);
624
- return await withDispatchLock(id, async (state) => {
625
- const existing = parseDispatchRecord(
626
- await state.get(getDispatchStorageKey(id))
627
- );
628
- if (existing) {
629
- return { record: existing, status: "already_exists" };
630
- }
631
- const metadata = normalizeMetadata(args.options.metadata);
632
- const record = {
633
- actor: { platform: "system", name: args.plugin },
634
- createdAtMs: args.nowMs,
635
- ...args.options.credentialSubject ? { credentialSubject: args.options.credentialSubject } : {},
636
- destination: args.options.destination,
637
- destinationVisibility: args.options.destinationVisibility,
638
- id,
639
- idempotencyKey: args.options.idempotencyKey,
640
- input: args.options.input,
641
- ...metadata ? { metadata } : {},
642
- plugin: args.plugin,
643
- ...args.options.replyAttribution ? { replyAttribution: args.options.replyAttribution } : {},
644
- status: "pending",
645
- source: args.options.source,
646
- updatedAtMs: args.nowMs
647
- };
648
- await updatePendingDispatchMailboxAppendIndex(
649
- state,
650
- (ids) => ids.includes(id) ? ids : [...ids, id]
651
- );
652
- await putRecord(state, record);
653
- return { record, status: "created" };
654
- });
655
- }
656
- async function transitionDispatch(id, transition) {
657
- return await withDispatchLock(id, async (state) => {
658
- const current = parseDispatchRecord(
659
- await state.get(getDispatchStorageKey(id))
660
- );
661
- if (!current) {
662
- return void 0;
663
- }
664
- const transitioned = transition(current);
665
- if (transitioned === current) {
666
- return current;
667
- }
668
- const next = {
669
- ...transitioned,
670
- updatedAtMs: Date.now()
671
- };
672
- await putRecord(state, next);
673
- return next;
674
- });
675
- }
676
- async function markDispatchRunning(id) {
677
- return await transitionDispatch(
678
- id,
679
- (record) => isTerminalDispatchStatus(record.status) ? record : { ...record, status: "running", errorMessage: void 0 }
680
- );
681
- }
682
- async function markDispatchAwaitingResume(id) {
683
- return await transitionDispatch(
684
- id,
685
- (record) => isTerminalDispatchStatus(record.status) ? record : {
686
- ...record,
687
- status: "awaiting_resume"
688
- }
689
- );
690
- }
691
- async function markDispatchBlocked(id, errorMessage, resultMessageTs) {
692
- return await transitionDispatch(
693
- id,
694
- (record) => isTerminalDispatchStatus(record.status) ? record : {
695
- ...record,
696
- errorMessage,
697
- ...resultMessageTs ? { resultMessageTs } : {},
698
- status: "blocked"
699
- }
700
- );
701
- }
702
- async function markDispatchCompleted(id, resultMessageTs) {
703
- const previous = await getDispatchRecord(id);
704
- const next = await transitionDispatch(
705
- id,
706
- (record) => isTerminalDispatchStatus(record.status) ? record : {
707
- ...record,
708
- errorMessage: void 0,
709
- ...resultMessageTs ? { resultMessageTs } : {},
710
- status: "completed"
711
- }
712
- );
713
- if (next?.status === "completed" && previous?.status !== "completed" && next.plugin === "junior") {
714
- const eventTaskId = next.metadata?.eventTaskId;
715
- if (eventTaskId) {
716
- await recordTaskExecution("event", eventTaskId, {
717
- conversationId: getDispatchConversationId(next),
718
- executionId: next.id,
719
- nowMs: next.updatedAtMs
720
- });
721
- }
722
- }
723
- return next;
724
- }
725
- async function markDispatchFailed(id, errorMessage, resultMessageTs) {
726
- return await transitionDispatch(
727
- id,
728
- (record) => isTerminalDispatchStatus(record.status) ? record : {
729
- ...record,
730
- errorMessage,
731
- ...resultMessageTs ? { resultMessageTs } : {},
732
- status: "failed"
733
- }
734
- );
735
- }
736
- async function confirmDispatchMailboxAppend(id) {
737
- const state = getStateAdapter();
738
- await state.connect();
739
- await updatePendingDispatchMailboxAppendIndex(
740
- state,
741
- (ids) => ids.filter((candidate) => candidate !== id)
742
- );
743
- }
744
- async function listPendingDispatchMailboxAppends() {
745
- const state = getStateAdapter();
746
- await state.connect();
747
- const stored = await state.get(pendingDispatchMailboxAppendIndexKey());
748
- return stored === void 0 || stored === null ? [] : [...new Set(pendingDispatchMailboxAppendIndexSchema.parse(stored))];
749
- }
750
- async function claimDispatchMailboxAppend(id, nowMs) {
751
- return await withDispatchLock(id, async (state) => {
752
- const stored = parseStoredDispatchRecord(
753
- await state.get(getDispatchStorageKey(id))
754
- );
755
- if (!stored || stored.legacyLeaseExpiresAtMs && stored.legacyLeaseExpiresAtMs > nowMs) {
756
- return void 0;
757
- }
758
- await putRecord(state, stored.record);
759
- return stored.record;
760
- });
761
- }
762
- async function getPluginDispatchProjection(args) {
763
- const record = await getDispatchRecord(args.id);
764
- if (!record || record.plugin !== args.plugin) {
765
- return void 0;
766
- }
767
- return toDispatchProjection(record);
768
- }
769
-
770
428
  // src/chat/agent-dispatch/work.ts
771
- import { z as z3 } from "zod";
772
- var agentDispatchMailboxMetadataSchema = z3.object({
773
- dispatchId: z3.string().min(1),
774
- kind: z3.literal("agent_dispatch")
429
+ import { z as z2 } from "zod";
430
+ var agentDispatchMailboxMetadataSchema = z2.object({
431
+ dispatchId: z2.string().min(1),
432
+ kind: z2.literal("agent_dispatch")
775
433
  }).strict();
776
434
  var AGENT_DISPATCH_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
777
435
  function buildDispatchRoutingContext(dispatch2) {
778
436
  return {
779
437
  actor: dispatch2.actor,
780
- credentialContext: {
781
- actor: dispatch2.actor,
782
- ...dispatch2.credentialSubject ? { subject: dispatch2.credentialSubject } : {}
783
- },
438
+ credentialContext: credentialContextForActor(
439
+ dispatch2.actor,
440
+ dispatch2.credentialSubject
441
+ ),
784
442
  destinationVisibility: dispatch2.destinationVisibility,
785
443
  dispatch: {
786
444
  actor: dispatch2.actor,
@@ -1020,7 +678,7 @@ function createAgentDispatchConversationWorker(options) {
1020
678
  `Dispatch ${dispatch2.id} belongs to ${expectedConversationId}, not ${context.conversationId}`
1021
679
  );
1022
680
  }
1023
- if (context.destination.platform !== dispatch2.destination.platform || context.destination.teamId !== dispatch2.destination.teamId || context.destination.channelId !== dispatch2.destination.channelId) {
681
+ if (!context.destination || context.destination.platform !== dispatch2.destination.platform || context.destination.teamId !== dispatch2.destination.teamId || context.destination.channelId !== dispatch2.destination.channelId) {
1024
682
  throw new Error(
1025
683
  `Dispatch ${dispatch2.id} destination does not match its conversation lease`
1026
684
  );
@@ -1159,6 +817,10 @@ function heartbeatIdempotencyKey(reason, conversationId, nowMs) {
1159
817
  return `heartbeat:${reason}:${conversationId}:${nowMs}`;
1160
818
  }
1161
819
  async function recoverConversationWork(args) {
820
+ if (!getChatConfig().conversationWorkEnabled) {
821
+ logWarn("conversation.work.recovery.disabled");
822
+ return { expiredLeaseCount: 0, pendingCount: 0 };
823
+ }
1162
824
  const result = {
1163
825
  expiredLeaseCount: 0,
1164
826
  pendingCount: 0
@@ -1203,12 +865,24 @@ async function recoverConversationWork(args) {
1203
865
  return;
1204
866
  }
1205
867
  if (work.execution.lease && work.execution.lease.expiresAtMs <= args.nowMs) {
1206
- const cleared = await clearExpiredConversationLease({
868
+ const recovery = await clearExpiredConversationLease({
1207
869
  conversationId,
1208
870
  nowMs: args.nowMs,
1209
871
  state: args.state
1210
872
  });
1211
- if (!cleared) {
873
+ if (recovery === "not_expired") {
874
+ return;
875
+ }
876
+ if (recovery === "stopped") {
877
+ logException(
878
+ new Error("Conversation work stopped after repeated failed attempts"),
879
+ "conversation.work.retry.exhausted",
880
+ {
881
+ "app.run.id": work.execution.runId ?? "unknown",
882
+ "app.worker.last_progress_at_ms": work.execution.lastProgressAtMs ?? work.createdAtMs,
883
+ "app.worker.retry_count": (work.execution.retryCount ?? 0) + 1
884
+ }
885
+ );
1212
886
  return;
1213
887
  }
1214
888
  const wake2 = await ensureConversationWake({
@@ -1263,7 +937,7 @@ import {
1263
937
  // src/chat/agent-dispatch/validation.ts
1264
938
  import {
1265
939
  dispatchOptionsSchema,
1266
- isSlackDestination as isSlackDestination2
940
+ isSlackDestination
1267
941
  } from "@sentry/junior-plugin-api";
1268
942
  function hasIssueAtPath(issues, path) {
1269
943
  return issues.some(
@@ -1360,7 +1034,7 @@ function validateDispatchOptions(options) {
1360
1034
  }
1361
1035
  const candidate = parsed.data;
1362
1036
  const { credentialSubject, destination } = candidate;
1363
- if (!isSlackDestination2(destination)) {
1037
+ if (!isSlackDestination(destination)) {
1364
1038
  throw new Error("Dispatch destination platform must be slack");
1365
1039
  }
1366
1040
  if (credentialSubject !== void 0) {
@@ -1585,6 +1259,15 @@ function shouldSkipRun(task, run) {
1585
1259
  }
1586
1260
  return void 0;
1587
1261
  }
1262
+ async function recordScheduledExecution(args) {
1263
+ const dispatchId = args.run.dispatchId;
1264
+ await recordTaskExecution("scheduled", args.run.taskId, {
1265
+ ...dispatchId ? { conversationId: getDispatchConversationId({ id: dispatchId }) } : {},
1266
+ executionId: args.run.id,
1267
+ nowMs: args.nowMs,
1268
+ status: args.status
1269
+ });
1270
+ }
1588
1271
  async function applyDispatchResult(args) {
1589
1272
  if (args.dispatch.status === "completed") {
1590
1273
  const completed = await args.store.markRunCompleted({
@@ -1599,14 +1282,11 @@ async function applyDispatchResult(args) {
1599
1282
  run: args.run,
1600
1283
  status: "completed"
1601
1284
  });
1602
- const dispatchId = args.run.dispatchId;
1603
- if (dispatchId) {
1604
- await recordTaskExecution("scheduled", args.run.taskId, {
1605
- conversationId: getDispatchConversationId({ id: dispatchId }),
1606
- executionId: args.run.id,
1607
- nowMs: args.nowMs
1608
- });
1609
- }
1285
+ await recordScheduledExecution({
1286
+ nowMs: args.nowMs,
1287
+ run: args.run,
1288
+ status: "completed"
1289
+ });
1610
1290
  return true;
1611
1291
  }
1612
1292
  if (args.dispatch.status === "blocked") {
@@ -1623,6 +1303,11 @@ async function applyDispatchResult(args) {
1623
1303
  run: args.run,
1624
1304
  status: "blocked"
1625
1305
  });
1306
+ await recordScheduledExecution({
1307
+ nowMs: args.nowMs,
1308
+ run: args.run,
1309
+ status: "blocked"
1310
+ });
1626
1311
  return true;
1627
1312
  }
1628
1313
  if (args.dispatch.status === "failed") {
@@ -1639,6 +1324,11 @@ async function applyDispatchResult(args) {
1639
1324
  run: args.run,
1640
1325
  status: "failed"
1641
1326
  });
1327
+ await recordScheduledExecution({
1328
+ nowMs: args.nowMs,
1329
+ run: args.run,
1330
+ status: "failed"
1331
+ });
1642
1332
  return true;
1643
1333
  }
1644
1334
  return false;
@@ -1661,6 +1351,11 @@ async function finishClaimedRun(args) {
1661
1351
  run: args.run,
1662
1352
  status: args.status
1663
1353
  });
1354
+ await recordScheduledExecution({
1355
+ nowMs: args.nowMs,
1356
+ run: args.run,
1357
+ status: args.status
1358
+ });
1664
1359
  }
1665
1360
  async function runScheduledTaskHeartbeat(args) {
1666
1361
  const store = createSchedulerSqlStore(getDb());
@@ -1765,6 +1460,7 @@ async function runScheduledTaskHeartbeat(args) {
1765
1460
  var DEFAULT_PLUGIN_LIMIT = 25;
1766
1461
  var PLUGIN_HEARTBEAT_TIMEOUT_MS = 25e3;
1767
1462
  var DISPATCH_MAILBOX_APPEND_LIMIT = 100;
1463
+ var AGENT_INVOCATION_MAILBOX_APPEND_LIMIT = 100;
1768
1464
  async function runWithTimeout(promise, timeoutMs) {
1769
1465
  let timeout;
1770
1466
  try {
@@ -1851,6 +1547,23 @@ async function recoverPendingDispatchMailboxAppends(args) {
1851
1547
  }
1852
1548
  }
1853
1549
  }
1550
+ async function recoverPendingAgentInvocationMailboxAppends(args) {
1551
+ const invocations = await listPendingAgentInvocationMailboxAppends(
1552
+ AGENT_INVOCATION_MAILBOX_APPEND_LIMIT
1553
+ );
1554
+ for (const invocation of invocations) {
1555
+ try {
1556
+ await enqueueAgentInvocation(invocation, {
1557
+ nowMs: args.nowMs,
1558
+ queue: args.conversationWorkQueue
1559
+ });
1560
+ } catch (error) {
1561
+ logException(error, "agent.invocation.mailbox.append_recovery.failed", {
1562
+ "app.agent.invocation_id": invocation.invocationId
1563
+ });
1564
+ }
1565
+ }
1566
+ }
1854
1567
  async function runHeartbeat(args) {
1855
1568
  const queue = args.conversationWorkQueue ?? getVercelConversationWorkQueue();
1856
1569
  await recoverConversationWork({
@@ -1861,6 +1574,10 @@ async function runHeartbeat(args) {
1861
1574
  conversationWorkQueue: queue,
1862
1575
  nowMs: args.nowMs
1863
1576
  });
1577
+ await recoverPendingAgentInvocationMailboxAppends({
1578
+ conversationWorkQueue: queue,
1579
+ nowMs: args.nowMs
1580
+ });
1864
1581
  try {
1865
1582
  const dispatchCount = await runScheduledTaskHeartbeat({
1866
1583
  conversationWorkQueue: queue,
@@ -1919,7 +1636,7 @@ async function GET4(request, waitUntil, options = {}) {
1919
1636
  import { timingSafeEqual as timingSafeEqual3 } from "crypto";
1920
1637
 
1921
1638
  // src/chat/conversations/sql/purge.ts
1922
- import { and, asc, eq as eq2, inArray, isNull, sql } from "drizzle-orm";
1639
+ import { and, asc, eq as eq2, inArray, isNull, or, sql } from "drizzle-orm";
1923
1640
 
1924
1641
  // src/chat/conversations/sql/privacy.ts
1925
1642
  import { eq } from "drizzle-orm";
@@ -2010,6 +1727,16 @@ async function selectExpiredRoots(executor, args) {
2010
1727
  select 1 from junior_conversation_events events
2011
1728
  where events.conversation_id = tree.conversation_id
2012
1729
  )
1730
+ or exists (
1731
+ select 1 from junior_agent_invocations invocations
1732
+ where invocations.parent_conversation_id = tree.conversation_id
1733
+ or invocations.child_conversation_id = tree.conversation_id
1734
+ )
1735
+ or exists (
1736
+ select 1 from junior_agent_bindings bindings
1737
+ where bindings.parent_conversation_id = tree.conversation_id
1738
+ or bindings.child_conversation_id = tree.conversation_id
1739
+ )
2013
1740
  or (
2014
1741
  ${juniorDestinations.visibility} is distinct from 'public'
2015
1742
  and exists (
@@ -2045,69 +1772,85 @@ async function selectExpiredRoots(executor, args) {
2045
1772
  }));
2046
1773
  }
2047
1774
  async function purgeConversationTree(executor, args) {
2048
- return await executor.transaction(async () => {
2049
- const initialRoots = await executor.db().select({
2050
- conversationId: juniorConversations.conversationId,
2051
- parentConversationId: juniorConversations.parentConversationId
2052
- }).from(juniorConversations).where(eq2(juniorConversations.conversationId, args.rootConversationId));
2053
- const initialRoot = initialRoots[0];
2054
- if (!initialRoot || args.retention && initialRoot.parentConversationId !== null) {
2055
- return { purged: false, conversations: 0 };
2056
- }
2057
- const tree = await discoverConversationTree(executor, initialRoot);
2058
- return await withConversationEventLock(
2059
- executor,
2060
- args.rootConversationId,
2061
- async () => {
2062
- for (const conversation of tree.slice(1)) {
2063
- await withConversationEventLock(
2064
- executor,
2065
- conversation.conversationId,
2066
- async () => void 0
2067
- );
2068
- }
2069
- const roots = await executor.db().select({
2070
- conversationId: juniorConversations.conversationId,
2071
- destinationId: juniorConversations.destinationId,
2072
- parentConversationId: juniorConversations.parentConversationId
2073
- }).from(juniorConversations).where(
2074
- eq2(juniorConversations.conversationId, args.rootConversationId)
2075
- ).for("update");
2076
- const root = roots[0];
2077
- if (!root || args.retention && root.parentConversationId !== null) {
2078
- return { purged: false, conversations: 0 };
2079
- }
2080
- const resolvedScrubMetadata = args.scrubMetadataFromRootVisibility ? (await resolveRootVisibility(executor, args.rootConversationId)).visibility !== "public" : args.scrubMetadata;
2081
- const destinations = root.destinationId ? await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq2(juniorDestinations.id, root.destinationId)).for("share") : [];
2082
- const isPublic = destinations[0]?.visibility === "public";
2083
- const ids = tree.map((conversation) => conversation.conversationId);
2084
- if (args.retention) {
2085
- const currentActivity = await executor.db().select({
1775
+ return await withConversationMutationLock(
1776
+ executor,
1777
+ args.rootConversationId,
1778
+ async () => {
1779
+ const initialRoots = await executor.db().select({
1780
+ conversationId: juniorConversations.conversationId,
1781
+ parentConversationId: juniorConversations.parentConversationId
1782
+ }).from(juniorConversations).where(eq2(juniorConversations.conversationId, args.rootConversationId));
1783
+ const initialRoot = initialRoots[0];
1784
+ if (!initialRoot || args.retention && initialRoot.parentConversationId !== null) {
1785
+ return { purged: false, conversations: 0 };
1786
+ }
1787
+ const tree = await discoverConversationTree(executor, initialRoot);
1788
+ return await withConversationEventLock(
1789
+ executor,
1790
+ args.rootConversationId,
1791
+ async () => {
1792
+ for (const conversation of tree.slice(1)) {
1793
+ await withConversationEventLock(
1794
+ executor,
1795
+ conversation.conversationId,
1796
+ async () => void 0
1797
+ );
1798
+ }
1799
+ const roots = await executor.db().select({
2086
1800
  conversationId: juniorConversations.conversationId,
2087
- lastActivityAt: juniorConversations.lastActivityAt
2088
- }).from(juniorConversations).where(inArray(juniorConversations.conversationId, ids));
2089
- if (currentActivity.length !== ids.length) {
1801
+ destinationId: juniorConversations.destinationId,
1802
+ parentConversationId: juniorConversations.parentConversationId
1803
+ }).from(juniorConversations).where(
1804
+ eq2(juniorConversations.conversationId, args.rootConversationId)
1805
+ ).for("update");
1806
+ const root = roots[0];
1807
+ if (!root || args.retention && root.parentConversationId !== null) {
2090
1808
  return { purged: false, conversations: 0 };
2091
1809
  }
2092
- const windowMs = isPublic ? args.retention.publicWindowMs : args.retention.privateWindowMs;
2093
- const effectiveLastActivityAt = Math.max(
2094
- ...currentActivity.map(
2095
- (conversation) => conversation.lastActivityAt.getTime()
1810
+ const resolvedScrubMetadata = args.scrubMetadataFromRootVisibility ? (await resolveRootVisibility(executor, args.rootConversationId)).visibility !== "public" : args.scrubMetadata;
1811
+ const destinations = root.destinationId ? await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq2(juniorDestinations.id, root.destinationId)).for("share") : [];
1812
+ const isPublic = destinations[0]?.visibility === "public";
1813
+ const ids = tree.map((conversation) => conversation.conversationId);
1814
+ if (args.retention) {
1815
+ const currentActivity = await executor.db().select({
1816
+ conversationId: juniorConversations.conversationId,
1817
+ lastActivityAt: juniorConversations.lastActivityAt
1818
+ }).from(juniorConversations).where(inArray(juniorConversations.conversationId, ids));
1819
+ if (currentActivity.length !== ids.length) {
1820
+ return { purged: false, conversations: 0 };
1821
+ }
1822
+ const windowMs = isPublic ? args.retention.publicWindowMs : args.retention.privateWindowMs;
1823
+ const effectiveLastActivityAt = Math.max(
1824
+ ...currentActivity.map(
1825
+ (conversation) => conversation.lastActivityAt.getTime()
1826
+ )
1827
+ );
1828
+ if (effectiveLastActivityAt >= args.nowMs - windowMs) {
1829
+ return { purged: false, conversations: 0 };
1830
+ }
1831
+ }
1832
+ await executor.db().delete(juniorConversationEvents).where(inArray(juniorConversationEvents.conversationId, ids));
1833
+ await executor.db().delete(juniorAgentInvocations).where(
1834
+ or(
1835
+ inArray(juniorAgentInvocations.parentConversationId, ids),
1836
+ inArray(juniorAgentInvocations.childConversationId, ids)
2096
1837
  )
2097
1838
  );
2098
- if (effectiveLastActivityAt >= args.nowMs - windowMs) {
2099
- return { purged: false, conversations: 0 };
2100
- }
1839
+ await executor.db().delete(juniorAgentBindings).where(
1840
+ or(
1841
+ inArray(juniorAgentBindings.parentConversationId, ids),
1842
+ inArray(juniorAgentBindings.childConversationId, ids)
1843
+ )
1844
+ );
1845
+ await executor.db().update(juniorConversations).set({
1846
+ transcriptPurgedAt: new Date(args.nowMs),
1847
+ ...(args.retention ? !isPublic : resolvedScrubMetadata) ? { title: null, channelName: null, actor: null } : {}
1848
+ }).where(inArray(juniorConversations.conversationId, ids));
1849
+ return { purged: true, conversations: ids.length };
2101
1850
  }
2102
- await executor.db().delete(juniorConversationEvents).where(inArray(juniorConversationEvents.conversationId, ids));
2103
- await executor.db().update(juniorConversations).set({
2104
- transcriptPurgedAt: new Date(args.nowMs),
2105
- ...(args.retention ? !isPublic : resolvedScrubMetadata) ? { title: null, channelName: null, actor: null } : {}
2106
- }).where(inArray(juniorConversations.conversationId, ids));
2107
- return { purged: true, conversations: ids.length };
2108
- }
2109
- );
2110
- });
1851
+ );
1852
+ }
1853
+ );
2111
1854
  }
2112
1855
 
2113
1856
  // src/chat/conversations/retention.ts
@@ -2219,9 +1962,9 @@ function buildJwksUrl(value) {
2219
1962
  return url;
2220
1963
  }
2221
1964
  async function getJwks(issuer) {
2222
- const now2 = Date.now();
1965
+ const now = Date.now();
2223
1966
  const cached = jwksByIssuer.get(issuer);
2224
- if (cached && cached.expiresAtMs > now2) {
1967
+ if (cached && cached.expiresAtMs > now) {
2225
1968
  return cached.jwks;
2226
1969
  }
2227
1970
  if (cached) {
@@ -2246,7 +1989,7 @@ async function getJwks(issuer) {
2246
1989
  }
2247
1990
  jwksByIssuer.set(issuer, {
2248
1991
  jwks,
2249
- expiresAtMs: now2 + OIDC_DISCOVERY_CACHE_TTL_MS
1992
+ expiresAtMs: now + OIDC_DISCOVERY_CACHE_TTL_MS
2250
1993
  });
2251
1994
  return jwks;
2252
1995
  }
@@ -2572,7 +2315,7 @@ import {
2572
2315
  Message,
2573
2316
  ThreadImpl
2574
2317
  } from "chat";
2575
- import { z as z5 } from "zod";
2318
+ import { z as z4 } from "zod";
2576
2319
 
2577
2320
  // src/chat/ingress/message-router.ts
2578
2321
  function normalizeIncomingSlackThreadId(threadId, message) {
@@ -2743,14 +2486,14 @@ async function ensureSlackMessageActorIdentity(message, teamId, lookupSlackUser2
2743
2486
  }
2744
2487
 
2745
2488
  // src/chat/slack/user.ts
2746
- import { z as z4 } from "zod";
2747
- var slackUserInfoSchema = z4.object({
2748
- ok: z4.literal(true),
2749
- user: z4.object({
2750
- name: z4.string().nullish(),
2751
- profile: z4.object({
2752
- email: z4.string().nullish(),
2753
- real_name: z4.string().nullish()
2489
+ import { z as z3 } from "zod";
2490
+ var slackUserInfoSchema = z3.object({
2491
+ ok: z3.literal(true),
2492
+ user: z3.object({
2493
+ name: z3.string().nullish(),
2494
+ profile: z3.object({
2495
+ email: z3.string().nullish(),
2496
+ real_name: z3.string().nullish()
2754
2497
  }).optional()
2755
2498
  })
2756
2499
  });
@@ -2827,6 +2570,20 @@ async function lookupSlackUser(teamId, userId) {
2827
2570
  }
2828
2571
  }
2829
2572
 
2573
+ // src/chat/resource-events/actor.ts
2574
+ var RESOURCE_EVENT_SLACK_AUTHOR_ID = "UJRNEVENT";
2575
+ var RESOURCE_EVENT_SYSTEM_ACTOR = {
2576
+ platform: "system",
2577
+ name: "resource-event"
2578
+ };
2579
+ function isResourceEventConversationMessage(message) {
2580
+ return Boolean(message.meta?.eventType) || message.author?.userId === RESOURCE_EVENT_SLACK_AUTHOR_ID;
2581
+ }
2582
+ function isResourceEventSlackMessage(message) {
2583
+ const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
2584
+ return raw?.event_type === "resource_event";
2585
+ }
2586
+
2830
2587
  // src/chat/slack/message-control.ts
2831
2588
  var LEADING_MENTION_BEFORE_STEERING_RE = /^(\s*(?:<@[^>]+>|@[A-Za-z0-9._-]+\b)[\s,:-]*)!!\s*/;
2832
2589
  function stripLeadingSteeringOverride(text) {
@@ -2834,17 +2591,17 @@ function stripLeadingSteeringOverride(text) {
2834
2591
  }
2835
2592
 
2836
2593
  // src/chat/task-execution/slack-work.ts
2837
- var slackConversationRouteSchema = z5.enum(["mention", "subscribed"]);
2594
+ var slackConversationRouteSchema = z4.enum(["mention", "subscribed"]);
2838
2595
  function hasSteeringOverride(text) {
2839
2596
  return stripLeadingSteeringOverride(text) !== text;
2840
2597
  }
2841
- var serializedDateSchema = z5.iso.datetime();
2842
- var mdastPointSchema = z5.object({
2843
- column: z5.number().int().positive(),
2844
- line: z5.number().int().positive(),
2845
- offset: z5.number().int().nonnegative().optional()
2598
+ var serializedDateSchema = z4.iso.datetime();
2599
+ var mdastPointSchema = z4.object({
2600
+ column: z4.number().int().positive(),
2601
+ line: z4.number().int().positive(),
2602
+ offset: z4.number().int().nonnegative().optional()
2846
2603
  }).strict();
2847
- var mdastPositionSchema = z5.object({
2604
+ var mdastPositionSchema = z4.object({
2848
2605
  end: mdastPointSchema,
2849
2606
  start: mdastPointSchema
2850
2607
  }).strict();
@@ -2956,7 +2713,7 @@ function isFormattedContentNode(value) {
2956
2713
  return false;
2957
2714
  }
2958
2715
  }
2959
- var formattedContentSchema = z5.custom(
2716
+ var formattedContentSchema = z4.custom(
2960
2717
  (value) => {
2961
2718
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
2962
2719
  return false;
@@ -2966,79 +2723,79 @@ var formattedContentSchema = z5.custom(
2966
2723
  },
2967
2724
  "must be a valid formatted-content root"
2968
2725
  );
2969
- var serializedMessageSchema = z5.object({
2970
- _type: z5.literal("chat:Message"),
2971
- attachments: z5.array(
2972
- z5.object({
2973
- type: z5.enum(["image", "file", "video", "audio"]),
2974
- url: z5.string().optional(),
2975
- name: z5.string().optional(),
2976
- mimeType: z5.string().optional(),
2977
- size: z5.number().finite().optional(),
2978
- width: z5.number().finite().optional(),
2979
- height: z5.number().finite().optional(),
2980
- fetchMetadata: z5.record(z5.string(), z5.string()).optional()
2726
+ var serializedMessageSchema = z4.object({
2727
+ _type: z4.literal("chat:Message"),
2728
+ attachments: z4.array(
2729
+ z4.object({
2730
+ type: z4.enum(["image", "file", "video", "audio"]),
2731
+ url: z4.string().optional(),
2732
+ name: z4.string().optional(),
2733
+ mimeType: z4.string().optional(),
2734
+ size: z4.number().finite().optional(),
2735
+ width: z4.number().finite().optional(),
2736
+ height: z4.number().finite().optional(),
2737
+ fetchMetadata: z4.record(z4.string(), z4.string()).optional()
2981
2738
  }).strict()
2982
2739
  ),
2983
- author: z5.object({
2984
- userId: z5.string(),
2985
- userName: z5.string(),
2986
- fullName: z5.string(),
2987
- isBot: z5.union([z5.boolean(), z5.literal("unknown")]),
2988
- isMe: z5.boolean()
2740
+ author: z4.object({
2741
+ userId: z4.string(),
2742
+ userName: z4.string(),
2743
+ fullName: z4.string(),
2744
+ isBot: z4.union([z4.boolean(), z4.literal("unknown")]),
2745
+ isMe: z4.boolean()
2989
2746
  }).strict(),
2990
2747
  formatted: formattedContentSchema,
2991
- id: z5.string().min(1),
2992
- isMention: z5.boolean().optional(),
2993
- links: z5.array(
2994
- z5.object({
2995
- url: z5.string(),
2996
- title: z5.string().optional(),
2997
- description: z5.string().optional(),
2998
- imageUrl: z5.string().optional(),
2999
- siteName: z5.string().optional()
2748
+ id: z4.string().min(1),
2749
+ isMention: z4.boolean().optional(),
2750
+ links: z4.array(
2751
+ z4.object({
2752
+ url: z4.string(),
2753
+ title: z4.string().optional(),
2754
+ description: z4.string().optional(),
2755
+ imageUrl: z4.string().optional(),
2756
+ siteName: z4.string().optional()
3000
2757
  }).strict()
3001
2758
  ).optional(),
3002
- metadata: z5.object({
2759
+ metadata: z4.object({
3003
2760
  dateSent: serializedDateSchema,
3004
- edited: z5.boolean(),
2761
+ edited: z4.boolean(),
3005
2762
  editedAt: serializedDateSchema.optional()
3006
2763
  }).strict(),
3007
- raw: z5.unknown(),
3008
- text: z5.string(),
3009
- threadId: z5.string().min(1)
2764
+ raw: z4.unknown(),
2765
+ text: z4.string(),
2766
+ threadId: z4.string().min(1)
3010
2767
  }).strict();
3011
- var serializedThreadSchema = z5.object({
3012
- _type: z5.literal("chat:Thread"),
3013
- adapterName: z5.literal("slack"),
3014
- channelId: z5.string().min(1),
3015
- channelVisibility: z5.enum(["private", "workspace", "external", "unknown"]).optional(),
2768
+ var serializedThreadSchema = z4.object({
2769
+ _type: z4.literal("chat:Thread"),
2770
+ adapterName: z4.literal("slack"),
2771
+ channelId: z4.string().min(1),
2772
+ channelVisibility: z4.enum(["private", "workspace", "external", "unknown"]).optional(),
3016
2773
  currentMessage: serializedMessageSchema.optional(),
3017
- id: z5.string().min(1),
3018
- isDM: z5.boolean()
2774
+ id: z4.string().min(1),
2775
+ isDM: z4.boolean()
3019
2776
  }).strict();
3020
- var slackInstallationSchema = z5.object({
3021
- enterpriseId: z5.string().optional(),
3022
- isEnterpriseInstall: z5.boolean().optional(),
3023
- teamId: z5.string().optional()
2777
+ var slackInstallationSchema = z4.object({
2778
+ enterpriseId: z4.string().optional(),
2779
+ isEnterpriseInstall: z4.boolean().optional(),
2780
+ teamId: z4.string().optional()
3024
2781
  }).strict();
3025
- var slackConversationMessageMetadataBaseSchema = z5.object({
2782
+ var slackConversationMessageMetadataBaseSchema = z4.object({
3026
2783
  installation: slackInstallationSchema.optional(),
3027
2784
  message: serializedMessageSchema,
3028
- platform: z5.literal("slack"),
2785
+ platform: z4.literal("slack"),
3029
2786
  route: slackConversationRouteSchema,
3030
2787
  thread: serializedThreadSchema
3031
2788
  });
3032
- var slackConversationMessageMetadataSchema = z5.union([
2789
+ var slackConversationMessageMetadataSchema = z4.union([
3033
2790
  slackConversationMessageMetadataBaseSchema.strict(),
3034
2791
  slackConversationMessageMetadataBaseSchema.extend({
3035
- kind: z5.literal("resource_event"),
3036
- resourceEvent: z5.object({
3037
- eventKey: z5.string(),
3038
- eventType: z5.string(),
3039
- namespace: z5.string(),
3040
- identifier: z5.string(),
3041
- subscriptionId: z5.string()
2792
+ kind: z4.literal("resource_event"),
2793
+ resourceEvent: z4.object({
2794
+ eventKey: z4.string(),
2795
+ eventType: z4.string(),
2796
+ namespace: z4.string(),
2797
+ identifier: z4.string(),
2798
+ subscriptionId: z4.string()
3042
2799
  }).strict()
3043
2800
  }).strict()
3044
2801
  ]);
@@ -3071,7 +2828,7 @@ function slackSerializedResourceEventMessage(input) {
3071
2828
  _type: "chat:Message",
3072
2829
  attachments: [],
3073
2830
  author: {
3074
- userId: "UJRNEVENT",
2831
+ userId: RESOURCE_EVENT_SLACK_AUTHOR_ID,
3075
2832
  userName: "junior-event",
3076
2833
  fullName: "Junior event",
3077
2834
  isBot: true,
@@ -3089,7 +2846,7 @@ function slackSerializedResourceEventMessage(input) {
3089
2846
  resource_event_type: input.eventType,
3090
2847
  thread_ts: input.threadTs,
3091
2848
  type: "message",
3092
- user: "UJRNEVENT"
2849
+ user: RESOURCE_EVENT_SLACK_AUTHOR_ID
3093
2850
  },
3094
2851
  text: input.text,
3095
2852
  threadId: `slack:${input.channelId}:${input.threadTs}`
@@ -3133,7 +2890,7 @@ function createSlackResourceEventInboundMessage(input) {
3133
2890
  receivedAtMs: Date.now(),
3134
2891
  input: {
3135
2892
  text: input.text,
3136
- authorId: "UJRNEVENT",
2893
+ authorId: RESOURCE_EVENT_SLACK_AUTHOR_ID,
3137
2894
  metadata: {
3138
2895
  kind: "resource_event",
3139
2896
  installation: {
@@ -3173,10 +2930,6 @@ function routeForRecords(records) {
3173
2930
  return metadata.route === "mention";
3174
2931
  }) ? "mention" : "subscribed";
3175
2932
  }
3176
- function isResourceEventNotificationMessage(message) {
3177
- const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
3178
- return raw?.event_type === "resource_event";
3179
- }
3180
2933
  function restoreMessage(args) {
3181
2934
  const metadata = parseSlackMetadata(args.record.input.metadata);
3182
2935
  if (!metadata) {
@@ -3192,7 +2945,7 @@ function restoreMessage(args) {
3192
2945
  async function bindSlackActorIdentities(args) {
3193
2946
  const byAuthorId = /* @__PURE__ */ new Map();
3194
2947
  for (const message of args.messages) {
3195
- if (isResourceEventNotificationMessage(message)) {
2948
+ if (isResourceEventSlackMessage(message)) {
3196
2949
  continue;
3197
2950
  }
3198
2951
  const authorId = requireSlackAuthorId(message);
@@ -3352,7 +3105,7 @@ function createSlackConversationWorker(options) {
3352
3105
  if (route === "mention") {
3353
3106
  await options.runtime.handleNewMention(thread, latestMessage, {
3354
3107
  conversationId: context.conversationId,
3355
- destination: context.destination,
3108
+ destination,
3356
3109
  messageContext,
3357
3110
  drainSteeringMessages,
3358
3111
  ack,
@@ -3365,7 +3118,7 @@ function createSlackConversationWorker(options) {
3365
3118
  latestMessage,
3366
3119
  {
3367
3120
  conversationId: context.conversationId,
3368
- destination: context.destination,
3121
+ destination,
3369
3122
  messageContext,
3370
3123
  drainSteeringMessages,
3371
3124
  ack,
@@ -4143,663 +3896,114 @@ async function handleSlackWebhook(args) {
4143
3896
  if (shouldPersistBeforeAck(parsed)) {
4144
3897
  const failureResponse = await withLogContext(
4145
3898
  eventLogContext,
4146
- async () => {
4147
- try {
4148
- await handleSlackEvent({
4149
- body: parsed,
4150
- services: args.services
4151
- });
4152
- } catch (error) {
4153
- if (error instanceof SlackEventPersistenceError) {
4154
- logException(error.cause, "slack.event.persist.failed");
4155
- } else {
4156
- logException(error, "slack.event.routing.failed");
4157
- }
4158
- return new Response("Slack event handling failed", { status: 503 });
4159
- }
4160
- return void 0;
4161
- }
4162
- );
4163
- if (failureResponse) {
4164
- return failureResponse;
4165
- }
4166
- } else {
4167
- enqueue(
4168
- args.waitUntil,
4169
- withLogContext(
4170
- eventLogContext,
4171
- () => handleSlackEvent({
4172
- body: parsed,
4173
- services: args.services
4174
- }).catch((error) => {
4175
- logException(error, "slack.event.enqueue.failed");
4176
- })
4177
- )
4178
- );
4179
- }
4180
- }
4181
- return new Response("ok", { status: 200 });
4182
- }
4183
-
4184
- // src/handlers/webhooks.ts
4185
- async function handleWebhookRequest(request, platform, run) {
4186
- const requestContext = createRequestContext(request, { platform });
4187
- const requestUrl = new URL(request.url);
4188
- return await withContext(requestContext, async () => {
4189
- try {
4190
- return await withSpan(
4191
- "http.server.request",
4192
- "http.server",
4193
- requestContext,
4194
- async () => {
4195
- try {
4196
- const response = await run();
4197
- if (response.status >= 400) {
4198
- let responseBodySnippet;
4199
- try {
4200
- responseBodySnippet = (await response.clone().text()).slice(
4201
- 0,
4202
- 300
4203
- );
4204
- } catch {
4205
- responseBodySnippet = void 0;
4206
- }
4207
- logWarn("webhook.response.unsuccessful", {
4208
- "http.response.status_code": response.status,
4209
- "http.request.header.x_slack_signature": request.headers.get("x-slack-signature") ?? void 0,
4210
- "http.request.header.x_slack_request_timestamp": request.headers.get("x-slack-request-timestamp") ?? void 0,
4211
- ...responseBodySnippet ? { "app.webhook.response_body": responseBodySnippet } : {}
4212
- });
4213
- }
4214
- setSpanAttributes({
4215
- "http.response.status_code": response.status
4216
- });
4217
- setSpanStatus(response.status >= 500 ? "error" : "ok");
4218
- return response;
4219
- } catch (error) {
4220
- setSpanStatus("error");
4221
- throw error;
4222
- }
4223
- },
4224
- {
4225
- "http.request.method": request.method,
4226
- "url.path": requestUrl.pathname
4227
- }
4228
- );
4229
- } catch (error) {
4230
- logException(error, "webhook.handler.failed");
4231
- throw error;
4232
- }
4233
- });
4234
- }
4235
-
4236
- // src/handlers/slack-webhook.ts
4237
- async function POST2(request, waitUntil, services) {
4238
- return handleWebhookRequest(
4239
- request,
4240
- "slack",
4241
- () => handleSlackWebhook({
4242
- request,
4243
- services,
4244
- waitUntil
4245
- })
4246
- );
4247
- }
4248
-
4249
- // src/chat/task-execution/vercel-callback.ts
4250
- import {
4251
- handleCallback,
4252
- registerDevConsumer
4253
- } from "@vercel/queue";
4254
-
4255
- // src/chat/task-execution/worker.ts
4256
- var CONVERSATION_WORK_DEFER_DELAY_MS = 15e3;
4257
- function now(options) {
4258
- return options.nowMs?.() ?? Date.now();
4259
- }
4260
- function selectContiguousActorBatch(messages) {
4261
- const first = messages[0];
4262
- if (!first) {
4263
- return [];
4264
- }
4265
- const nextActorIndex = messages.findIndex(
4266
- (message) => message.input.authorId !== first.input.authorId
4267
- );
4268
- return messages.slice(
4269
- 0,
4270
- nextActorIndex === -1 ? messages.length : nextActorIndex
4271
- );
4272
- }
4273
- function selectAttemptMessages(work) {
4274
- const messages = work.messages;
4275
- const interrupts = messages.filter(
4276
- (message) => message.delivery === "interrupt"
4277
- );
4278
- if (interrupts.length > 0) {
4279
- return selectContiguousActorBatch(interrupts);
4280
- }
4281
- return work.execution.status === "awaiting_resume" ? [] : selectContiguousActorBatch(messages);
4282
- }
4283
- function nudgeIdempotencyKey(reason, conversationId, nowMs) {
4284
- return `${reason}:${conversationId}:${nowMs}`;
4285
- }
4286
- async function requestLostLeaseRecovery(args) {
4287
- const resumeRequested = await requestConversationContinuation({
4288
- conversationId: args.conversationId,
4289
- destination: args.destination,
4290
- leaseToken: args.leaseToken,
4291
- conversationStore: args.options.conversationStore,
4292
- nowMs: args.nowMs,
4293
- state: args.options.state
4294
- });
4295
- if (!resumeRequested) {
4296
- return;
4297
- }
4298
- const released = await releaseConversationWork({
4299
- conversationId: args.conversationId,
4300
- leaseToken: args.leaseToken,
4301
- conversationStore: args.options.conversationStore,
4302
- nowMs: args.nowMs,
4303
- state: args.options.state
4304
- });
4305
- if (!released) {
4306
- return;
4307
- }
4308
- await ensureConversationWake({
4309
- conversationId: args.conversationId,
4310
- conversationStore: args.options.conversationStore,
4311
- idempotencyKey: nudgeIdempotencyKey(
4312
- "lost_lease",
4313
- args.conversationId,
4314
- args.nowMs
4315
- ),
4316
- nowMs: args.nowMs,
4317
- queue: args.options.queue,
4318
- replaceExistingWake: true,
4319
- state: args.options.state
4320
- });
4321
- }
4322
- async function recordFailedDeliveryAttempt(args) {
4323
- const failure = await recordAttemptFailure({
4324
- conversationId: args.conversationId,
4325
- inboundMessageIds: args.messageIds,
4326
- leaseToken: args.leaseToken,
4327
- conversationStore: args.options.conversationStore,
4328
- nowMs: args.nowMs,
4329
- state: args.options.state
4330
- });
4331
- for (const message of failure.deadLetteredMessages) {
4332
- logWarn("conversation.work.dead_lettered", {
4333
- "app.conversation.source": message.source,
4334
- "app.inbound.attempt_count": message.attemptCount ?? 0,
4335
- "app.inbound.message_id": message.inboundMessageId,
4336
- "app.inbound.pending_count": failure.pendingCount
4337
- });
4338
- }
4339
- return failure;
4340
- }
4341
- function isTerminalFailure(failure) {
4342
- return failure.status === "recorded" && failure.deadLetteredMessages.length > 0 && failure.pendingCount === 0;
4343
- }
4344
- function startLeaseCheckIn(args) {
4345
- const timer = setInterval(() => {
4346
- const nowMs = now(args.options);
4347
- void checkInConversationWork({
4348
- conversationId: args.conversationId,
4349
- leaseToken: args.leaseToken,
4350
- conversationStore: args.options.conversationStore,
4351
- nowMs,
4352
- state: args.options.state
4353
- }).then(
4354
- (checkedIn) => {
4355
- if (!checkedIn) {
4356
- args.onLostLease();
4357
- logWarn("conversation.work.check_in.failed");
4358
- }
4359
- },
4360
- (error) => {
4361
- logException(error, "conversation.work.check_in.failed");
4362
- }
4363
- );
4364
- }, args.options.checkInIntervalMs ?? CONVERSATION_WORK_CHECK_IN_INTERVAL_MS);
4365
- timer.unref?.();
4366
- return timer;
4367
- }
4368
- async function processConversationWork(message, options) {
4369
- return withLogContext(
4370
- { conversationId: message.conversationId },
4371
- () => processConversationWorkInContext(message, options)
4372
- );
4373
- }
4374
- async function processConversationWorkInContext(message, options) {
4375
- const conversationId = message.conversationId;
4376
- let initial;
4377
- try {
4378
- initial = await getConversationWorkState({
4379
- conversationId,
4380
- state: options.state
4381
- });
4382
- } catch (error) {
4383
- if (isInvalidConversationRecordError(error)) {
4384
- throw new ConversationQueueMessageRejectedError(
4385
- "invalid_record",
4386
- `Conversation record failed validation for ${conversationId}`,
4387
- { conversationId }
4388
- );
4389
- }
4390
- throw error;
4391
- }
4392
- if (!initial || countPendingConversationMessages(initial) === 0 && initial.execution.status === "idle" && !initial.execution.lease) {
4393
- if (initial) {
4394
- await clearConsumedConversationWake({
4395
- conversationId,
4396
- conversationStore: options.conversationStore,
4397
- nowMs: now(options),
4398
- state: options.state
4399
- });
4400
- }
4401
- return { status: "no_work" };
4402
- }
4403
- if (!initial.destination) {
4404
- throw new ConversationQueueMessageRejectedError(
4405
- "invalid_record",
4406
- `Conversation work is missing destination context for ${conversationId}`,
4407
- { conversationId }
4408
- );
4409
- }
4410
- const destination = initial.destination;
4411
- const lease = await startConversationWork({
4412
- conversationId,
4413
- conversationStore: options.conversationStore,
4414
- nowMs: now(options),
4415
- state: options.state
4416
- });
4417
- if (lease.status === "no_work") {
4418
- await clearConsumedConversationWake({
4419
- conversationId,
4420
- conversationStore: options.conversationStore,
4421
- nowMs: now(options),
4422
- state: options.state
4423
- });
4424
- return { status: "no_work" };
4425
- }
4426
- if (lease.status === "active") {
4427
- const nudgeNowMs = now(options);
4428
- await ensureConversationWake({
4429
- conversationId,
4430
- conversationStore: options.conversationStore,
4431
- delayMs: CONVERSATION_WORK_DEFER_DELAY_MS,
4432
- idempotencyKey: nudgeIdempotencyKey("active", conversationId, nudgeNowMs),
4433
- nowMs: nudgeNowMs,
4434
- queue: options.queue,
4435
- replaceExistingWake: true,
4436
- state: options.state
4437
- });
4438
- logInfo("conversation.work.nudge.deferred", {
4439
- "app.lease.expires_at_ms": lease.leaseExpiresAtMs
4440
- });
4441
- return { status: "active" };
4442
- }
4443
- const startedAtMs = now(options);
4444
- const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? getChatConfig().conversationWorkSoftYieldAfterMs);
4445
- let attemptMessageIds = [];
4446
- let attemptSelectedMessageIds = /* @__PURE__ */ new Set();
4447
- let attemptStartMessageIds = /* @__PURE__ */ new Set();
4448
- let leaseLost = false;
4449
- const markLeaseLost = () => {
4450
- leaseLost = true;
4451
- };
4452
- const timer = startLeaseCheckIn({
4453
- conversationId,
4454
- leaseToken: lease.leaseToken,
4455
- onLostLease: markLeaseLost,
4456
- options
4457
- });
4458
- logInfo("conversation.work.lease.acquired", {
4459
- "app.lease.expires_at_ms": lease.leaseExpiresAtMs,
4460
- "app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
4461
- });
4462
- const drain = (handle) => drainConversationMailbox({
4463
- conversationId,
4464
- leaseToken: lease.leaseToken,
4465
- conversationStore: options.conversationStore,
4466
- handle: async (messages) => {
4467
- const candidates = messages.filter(
4468
- (message2) => message2.delivery === "interrupt" && (attemptSelectedMessageIds.has(message2.inboundMessageId) || !attemptStartMessageIds.has(message2.inboundMessageId))
4469
- );
4470
- if (candidates.length === 0) {
4471
- return [];
4472
- }
4473
- return await handle(candidates) ?? candidates.map((message2) => message2.inboundMessageId);
4474
- },
4475
- nowMs: now(options),
4476
- state: options.state
4477
- });
4478
- const shouldYield = () => leaseLost || now(options) >= softYieldDeadlineMs;
4479
- const checkIn = async () => {
4480
- const checkedIn = await checkInConversationWork({
4481
- conversationId,
4482
- leaseToken: lease.leaseToken,
4483
- conversationStore: options.conversationStore,
4484
- nowMs: now(options),
4485
- state: options.state
4486
- });
4487
- if (!checkedIn) {
4488
- markLeaseLost();
4489
- }
4490
- return checkedIn;
4491
- };
4492
- const yieldWork = async () => {
4493
- const yieldNowMs = now(options);
4494
- await ensureConversationWake({
4495
- conversationId,
4496
- conversationStore: options.conversationStore,
4497
- idempotencyKey: nudgeIdempotencyKey("yield", conversationId, yieldNowMs),
4498
- nowMs: yieldNowMs,
4499
- queue: options.queue,
4500
- replaceExistingWake: true,
4501
- state: options.state
4502
- });
4503
- const released = await releaseConversationWork({
4504
- conversationId,
4505
- leaseToken: lease.leaseToken,
4506
- conversationStore: options.conversationStore,
4507
- nowMs: yieldNowMs,
4508
- state: options.state
4509
- });
4510
- if (!released) {
4511
- return { status: "lost_lease" };
4512
- }
4513
- logInfo("conversation.work.yielded", {
4514
- "app.worker.elapsed_ms": now(options) - startedAtMs,
4515
- "app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
4516
- });
4517
- return { status: "yielded" };
4518
- };
4519
- try {
4520
- let hasRun = false;
4521
- while (true) {
4522
- attemptMessageIds = [];
4523
- const leasedWork = await getConversationWorkState({
4524
- conversationId,
4525
- state: options.state
4526
- });
4527
- if (!leasedWork || leasedWork.lease?.leaseToken !== lease.leaseToken || leaseLost) {
4528
- markLeaseLost();
4529
- await requestLostLeaseRecovery({
4530
- conversationId,
4531
- destination,
4532
- leaseToken: lease.leaseToken,
4533
- nowMs: now(options),
4534
- options
4535
- });
4536
- return { status: "lost_lease" };
4537
- }
4538
- const resumePending = leasedWork.execution.status === "awaiting_resume";
4539
- const attemptMessages = selectAttemptMessages(leasedWork);
4540
- attemptStartMessageIds = new Set(
4541
- leasedWork.messages.map((message2) => message2.inboundMessageId)
4542
- );
4543
- if (hasRun && shouldYield()) {
4544
- if (resumePending) {
4545
- return await yieldWork();
4546
- }
4547
- break;
4548
- }
4549
- if (resumePending && attemptMessages.length === 0) {
4550
- const resumeStarted = await beginConversationResume({
4551
- conversationId,
4552
- leaseToken: lease.leaseToken,
4553
- conversationStore: options.conversationStore,
4554
- nowMs: now(options),
4555
- state: options.state
4556
- });
4557
- if (!resumeStarted) {
4558
- markLeaseLost();
4559
- await requestLostLeaseRecovery({
4560
- conversationId,
4561
- destination,
4562
- leaseToken: lease.leaseToken,
4563
- nowMs: now(options),
4564
- options
4565
- });
4566
- return { status: "lost_lease" };
3899
+ async () => {
3900
+ try {
3901
+ await handleSlackEvent({
3902
+ body: parsed,
3903
+ services: args.services
3904
+ });
3905
+ } catch (error) {
3906
+ if (error instanceof SlackEventPersistenceError) {
3907
+ logException(error.cause, "slack.event.persist.failed");
3908
+ } else {
3909
+ logException(error, "slack.event.routing.failed");
3910
+ }
3911
+ return new Response("Slack event handling failed", { status: 503 });
3912
+ }
3913
+ return void 0;
4567
3914
  }
4568
- }
4569
- attemptMessageIds = attemptMessages.map(
4570
- (message2) => message2.inboundMessageId
4571
3915
  );
4572
- attemptSelectedMessageIds = new Set(attemptMessageIds);
4573
- const ack = async () => {
4574
- const acknowledged = await ackMessages({
4575
- conversationId,
4576
- inboundMessageIds: attemptMessageIds,
4577
- leaseToken: lease.leaseToken,
4578
- conversationStore: options.conversationStore,
4579
- nowMs: now(options),
4580
- state: options.state
4581
- });
4582
- if (!acknowledged) {
4583
- markLeaseLost();
4584
- throw new Error(
4585
- `Conversation work lease lost before inbox ack for ${conversationId}`
4586
- );
4587
- }
4588
- };
4589
- const workerContext = {
4590
- attempt: {
4591
- ack,
4592
- conversationId,
4593
- destination,
4594
- drain,
4595
- isFinalAttempt: attemptMessages.some(
4596
- (message2) => isFinalAttempt(message2)
4597
- ),
4598
- messages: attemptMessages
4599
- },
4600
- conversationId,
4601
- destination,
4602
- shouldYield,
4603
- checkIn
4604
- };
4605
- const result = await options.run(workerContext);
4606
- hasRun = true;
4607
- if (result.status === "lost_lease") {
4608
- await requestLostLeaseRecovery({
4609
- conversationId,
4610
- destination,
4611
- leaseToken: lease.leaseToken,
4612
- nowMs: now(options),
4613
- options
4614
- });
4615
- return { status: "lost_lease" };
4616
- }
4617
- if (leaseLost) {
4618
- await requestLostLeaseRecovery({
4619
- conversationId,
4620
- destination,
4621
- leaseToken: lease.leaseToken,
4622
- nowMs: now(options),
4623
- options
4624
- });
4625
- return { status: "lost_lease" };
4626
- }
4627
- if (result.status === "yielded") {
4628
- const resumeRequested = await requestConversationContinuation({
4629
- conversationId,
4630
- destination,
4631
- leaseToken: lease.leaseToken,
4632
- conversationStore: options.conversationStore,
4633
- nowMs: now(options),
4634
- state: options.state
4635
- });
4636
- if (!resumeRequested) {
4637
- return { status: "lost_lease" };
4638
- }
4639
- return await yieldWork();
4640
- }
4641
- if (result.status === "deferred") {
4642
- const deferredNowMs = now(options);
4643
- const released = await releaseConversationWork({
4644
- conversationId,
4645
- leaseToken: lease.leaseToken,
4646
- conversationStore: options.conversationStore,
4647
- nowMs: deferredNowMs,
4648
- state: options.state
4649
- });
4650
- if (!released) {
4651
- return { status: "lost_lease" };
4652
- }
4653
- const wake = await ensureConversationWake({
4654
- conversationId,
4655
- conversationStore: options.conversationStore,
4656
- idempotencyKey: nudgeIdempotencyKey(
4657
- "deferred",
4658
- conversationId,
4659
- deferredNowMs
4660
- ),
4661
- nowMs: deferredNowMs,
4662
- queue: options.queue,
4663
- state: options.state
4664
- });
4665
- return wake.status === "enqueued" ? { status: "pending_requeued" } : { status: "completed" };
4666
- }
4667
- if (attemptMessageIds.length > 0) {
4668
- const failure = await recordFailedDeliveryAttempt({
4669
- conversationId,
4670
- leaseToken: lease.leaseToken,
4671
- nowMs: now(options),
4672
- messageIds: attemptMessageIds,
4673
- options
4674
- });
4675
- if (isTerminalFailure(failure)) {
4676
- await deadLetterAttempt({
4677
- conversationId,
4678
- leaseToken: lease.leaseToken,
4679
- conversationStore: options.conversationStore,
4680
- nowMs: now(options),
4681
- state: options.state
4682
- });
4683
- return { status: "failed" };
4684
- }
4685
- if (failure.status === "lost_lease") {
4686
- return { status: "lost_lease" };
4687
- }
4688
- if (failure.status === "recorded") {
4689
- break;
4690
- }
4691
- }
4692
- const next = await getConversationWorkState({
4693
- conversationId,
4694
- state: options.state
4695
- });
4696
- if (!next || next.lease?.leaseToken !== lease.leaseToken) {
4697
- return { status: "lost_lease" };
4698
- }
4699
- if (next.execution.status !== "awaiting_resume" && countPendingConversationMessages(next) === 0) {
4700
- break;
3916
+ if (failureResponse) {
3917
+ return failureResponse;
4701
3918
  }
3919
+ } else {
3920
+ enqueue(
3921
+ args.waitUntil,
3922
+ withLogContext(
3923
+ eventLogContext,
3924
+ () => handleSlackEvent({
3925
+ body: parsed,
3926
+ services: args.services
3927
+ }).catch((error) => {
3928
+ logException(error, "slack.event.enqueue.failed");
3929
+ })
3930
+ )
3931
+ );
4702
3932
  }
4703
- const completion = await completeConversationWork({
4704
- conversationId,
4705
- leaseToken: lease.leaseToken,
4706
- conversationStore: options.conversationStore,
4707
- nowMs: now(options),
4708
- state: options.state
4709
- });
4710
- if (completion === "lost_lease") {
4711
- return { status: "lost_lease" };
4712
- }
4713
- if (completion === "pending") {
4714
- const nudgeNowMs = now(options);
4715
- const wake = await ensureConversationWake({
4716
- conversationId,
4717
- conversationStore: options.conversationStore,
4718
- idempotencyKey: nudgeIdempotencyKey(
4719
- "pending",
4720
- conversationId,
4721
- nudgeNowMs
4722
- ),
4723
- nowMs: nudgeNowMs,
4724
- queue: options.queue,
4725
- state: options.state
4726
- });
4727
- return wake.status === "enqueued" ? { status: "pending_requeued" } : { status: "completed" };
4728
- }
4729
- logInfo("conversation.work.completed", {
4730
- "app.worker.elapsed_ms": now(options) - startedAtMs
4731
- });
4732
- return { status: "completed" };
4733
- } catch (error) {
4734
- const errorNowMs = now(options);
4735
- let recoveryRecorded = false;
3933
+ }
3934
+ return new Response("ok", { status: 200 });
3935
+ }
3936
+
3937
+ // src/handlers/webhooks.ts
3938
+ async function handleWebhookRequest(request, platform, run) {
3939
+ const requestContext = createRequestContext(request, { platform });
3940
+ const requestUrl = new URL(request.url);
3941
+ return await withContext(requestContext, async () => {
4736
3942
  try {
4737
- const failure = attemptMessageIds.length > 0 ? await recordFailedDeliveryAttempt({
4738
- conversationId,
4739
- leaseToken: lease.leaseToken,
4740
- nowMs: errorNowMs,
4741
- messageIds: attemptMessageIds,
4742
- options
4743
- }) : void 0;
4744
- if (failure && isTerminalFailure(failure)) {
4745
- await deadLetterAttempt({
4746
- conversationId,
4747
- leaseToken: lease.leaseToken,
4748
- conversationStore: options.conversationStore,
4749
- nowMs: errorNowMs,
4750
- state: options.state
4751
- });
4752
- } else {
4753
- const resumeRequested = await requestConversationContinuation({
4754
- conversationId,
4755
- destination,
4756
- leaseToken: lease.leaseToken,
4757
- conversationStore: options.conversationStore,
4758
- nowMs: errorNowMs,
4759
- state: options.state
4760
- });
4761
- if (resumeRequested) {
4762
- await ensureConversationWake({
4763
- conversationId,
4764
- conversationStore: options.conversationStore,
4765
- idempotencyKey: nudgeIdempotencyKey(
4766
- "error",
4767
- conversationId,
4768
- errorNowMs
4769
- ),
4770
- nowMs: errorNowMs,
4771
- queue: options.queue,
4772
- replaceExistingWake: true,
4773
- state: options.state
4774
- });
3943
+ return await withSpan(
3944
+ "http.server.request",
3945
+ "http.server",
3946
+ requestContext,
3947
+ async () => {
3948
+ try {
3949
+ const response = await run();
3950
+ if (response.status >= 400) {
3951
+ let responseBodySnippet;
3952
+ try {
3953
+ responseBodySnippet = (await response.clone().text()).slice(
3954
+ 0,
3955
+ 300
3956
+ );
3957
+ } catch {
3958
+ responseBodySnippet = void 0;
3959
+ }
3960
+ logWarn("webhook.response.unsuccessful", {
3961
+ "http.response.status_code": response.status,
3962
+ "http.request.header.x_slack_signature": request.headers.get("x-slack-signature") ?? void 0,
3963
+ "http.request.header.x_slack_request_timestamp": request.headers.get("x-slack-request-timestamp") ?? void 0,
3964
+ ...responseBodySnippet ? { "app.webhook.response_body": responseBodySnippet } : {}
3965
+ });
3966
+ }
3967
+ setSpanAttributes({
3968
+ "http.response.status_code": response.status
3969
+ });
3970
+ setSpanStatus(response.status >= 500 ? "error" : "ok");
3971
+ return response;
3972
+ } catch (error) {
3973
+ setSpanStatus("error");
3974
+ throw error;
3975
+ }
3976
+ },
3977
+ {
3978
+ "http.request.method": request.method,
3979
+ "url.path": requestUrl.pathname
4775
3980
  }
4776
- await releaseConversationWork({
4777
- conversationId,
4778
- leaseToken: lease.leaseToken,
4779
- conversationStore: options.conversationStore,
4780
- nowMs: errorNowMs,
4781
- state: options.state
4782
- });
4783
- }
4784
- recoveryRecorded = true;
4785
- } catch (recoveryError) {
4786
- logException(recoveryError, "conversation.work.requeue.failed");
4787
- }
4788
- if (!isProviderRetryError(error)) {
4789
- logException(error, "conversation.work.failed", {
4790
- "app.worker.elapsed_ms": now(options) - startedAtMs
4791
- });
4792
- }
4793
- if (!recoveryRecorded) {
3981
+ );
3982
+ } catch (error) {
3983
+ logException(error, "webhook.handler.failed");
4794
3984
  throw error;
4795
3985
  }
4796
- return { status: "failed" };
4797
- } finally {
4798
- clearInterval(timer);
4799
- }
3986
+ });
3987
+ }
3988
+
3989
+ // src/handlers/slack-webhook.ts
3990
+ async function POST2(request, waitUntil, services) {
3991
+ return handleWebhookRequest(
3992
+ request,
3993
+ "slack",
3994
+ () => handleSlackWebhook({
3995
+ request,
3996
+ services,
3997
+ waitUntil
3998
+ })
3999
+ );
4800
4000
  }
4801
4001
 
4802
4002
  // src/chat/task-execution/vercel-callback.ts
4003
+ import {
4004
+ handleCallback,
4005
+ registerDevConsumer
4006
+ } from "@vercel/queue";
4803
4007
  var CONVERSATION_WORK_VISIBILITY_TIMEOUT_BUFFER_SECONDS = 30;
4804
4008
  var CONVERSATION_WORK_DEV_CONSUMER_GROUP = "junior_conversation_work_dev";
4805
4009
  function parseConversationQueueMessage(message) {
@@ -4825,6 +4029,15 @@ async function processConversationQueueMessage(message, options) {
4825
4029
  });
4826
4030
  }
4827
4031
  async function handleConversationQueueMessage(message, metadata, options) {
4032
+ if (!getChatConfig().conversationWorkEnabled) {
4033
+ logWarn("conversation.work.processing.disabled", {
4034
+ "app.queue.consumer_group": metadata.consumerGroup,
4035
+ "app.queue.delivery_count": metadata.deliveryCount,
4036
+ "app.queue.message_id": metadata.messageId,
4037
+ "app.queue.topic_name": metadata.topicName
4038
+ });
4039
+ return;
4040
+ }
4828
4041
  const verification = verifyConversationQueueMessage(message);
4829
4042
  if (verification.status === "rejected") {
4830
4043
  logConversationQueueMessageRejected(verification.reason, metadata);
@@ -4951,7 +4164,7 @@ function registerVercelPluginTaskDevConsumer() {
4951
4164
  }
4952
4165
 
4953
4166
  // src/chat/services/subscribed-decision.ts
4954
- import { z as z6 } from "zod";
4167
+ import { z as z5 } from "zod";
4955
4168
 
4956
4169
  // src/chat/services/subscribed-reply-router-policy.ts
4957
4170
  function buildSubscribedReplyRouterPolicy(assistantName) {
@@ -4996,13 +4209,13 @@ Assess conversation floor ownership and whether the latest message continues wor
4996
4209
  }
4997
4210
 
4998
4211
  // src/chat/services/subscribed-decision.ts
4999
- var replyDecisionSchema = z6.object({
5000
- should_reply: z6.boolean().describe("Whether Junior should respond to this thread message."),
5001
- should_unsubscribe: z6.boolean().describe(
4212
+ var replyDecisionSchema = z5.object({
4213
+ should_reply: z5.boolean().describe("Whether Junior should respond to this thread message."),
4214
+ should_unsubscribe: z5.boolean().describe(
5002
4215
  "Whether Junior should unsubscribe from this thread because the user clearly asked it to stop participating."
5003
4216
  ),
5004
- confidence: z6.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
5005
- reason: z6.string().describe("Short reason for the decision; use an empty string if none.")
4217
+ confidence: z5.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
4218
+ reason: z5.string().describe("Short reason for the decision; use an empty string if none.")
5006
4219
  }).strict();
5007
4220
  var ROUTER_CONFIDENCE_THRESHOLD = 0.7;
5008
4221
  var ROUTER_CLASSIFIER_MAX_TOKENS = 400;
@@ -5457,10 +4670,6 @@ function buildLogContext(deps, args) {
5457
4670
  function actorUserName(message) {
5458
4671
  return getMessageActorIdentity(message)?.userName;
5459
4672
  }
5460
- function isResourceEventNotificationMessage2(message) {
5461
- const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
5462
- return raw?.event_type === "resource_event";
5463
- }
5464
4673
  function createSlackTurnRuntime(deps) {
5465
4674
  const logContext = (args) => buildLogContext(deps, args);
5466
4675
  const maybeHandleThreadOptOutDecision = async (args) => {
@@ -5881,7 +5090,7 @@ function createSlackTurnRuntime(deps) {
5881
5090
  const threadId = deps.getThreadId(thread, message);
5882
5091
  const channelId = deps.getChannelId(thread, message);
5883
5092
  const runId = deps.getRunId(thread, message);
5884
- const isResourceEventNotification = isResourceEventNotificationMessage2(message);
5093
+ const isResourceEventNotification = isResourceEventSlackMessage(message);
5885
5094
  const actorId = isResourceEventNotification ? void 0 : message.author.userId;
5886
5095
  const turnContext = logContext({
5887
5096
  threadId,
@@ -6649,14 +5858,16 @@ function createJuniorRuntimeServices(overrides = {}) {
6649
5858
  listThreadReplies: overrides.visionContext?.listThreadReplies ?? listThreadReplies,
6650
5859
  downloadFile: overrides.visionContext?.downloadFile ?? downloadPrivateSlackFile
6651
5860
  });
5861
+ const agentRunner = overrides.replyExecutor?.agentRunner ?? createAgentRunner(executeAgentRun, {
5862
+ bindSpawnAgent: (request) => bindSpawnAgent(request, { queue: getVercelConversationWorkQueue }),
5863
+ tracePropagation: overrides.sandbox?.tracePropagation
5864
+ });
6652
5865
  return {
6653
5866
  conversationMemory,
6654
5867
  contextCompactor,
6655
5868
  replyExecutor: {
6656
5869
  contextCompactor: overrides.replyExecutor?.contextCompactor ?? contextCompactor,
6657
- agentRunner: overrides.replyExecutor?.agentRunner ?? createAgentRunner(executeAgentRun, {
6658
- tracePropagation: overrides.sandbox?.tracePropagation
6659
- }),
5870
+ agentRunner,
6660
5871
  getAwaitingAgentContinueRequest: overrides.replyExecutor?.getAwaitingAgentContinueRequest ?? getAwaitingAgentContinueRequest,
6661
5872
  lookupSlackUser: overrides.replyExecutor?.lookupSlackUser ?? lookupSlackUser,
6662
5873
  scheduleAgentContinue: overrides.replyExecutor?.scheduleAgentContinue ?? scheduleAgentContinue,
@@ -6677,11 +5888,11 @@ function createJuniorRuntimeServices(overrides = {}) {
6677
5888
  import { createSlackSource as createSlackSource2 } from "@sentry/junior-plugin-api";
6678
5889
 
6679
5890
  // src/chat/slack/action-token.ts
6680
- import { z as z7 } from "zod";
6681
- var slackActionTokenSchema = z7.string().trim().min(1).brand();
6682
- var slackMessageEnvelopeSchema = z7.object({
6683
- raw: z7.object({
6684
- action_token: z7.unknown().optional()
5891
+ import { z as z6 } from "zod";
5892
+ var slackActionTokenSchema = z6.string().trim().min(1).brand();
5893
+ var slackMessageEnvelopeSchema = z6.object({
5894
+ raw: z6.object({
5895
+ action_token: z6.unknown().optional()
6685
5896
  }).optional()
6686
5897
  });
6687
5898
  function readSlackActionToken(message) {
@@ -6845,7 +6056,7 @@ function queuedInstructionActor(queued) {
6845
6056
  };
6846
6057
  }
6847
6058
  function queuedInstructionProvenance(queued, teamId) {
6848
- if (isResourceEventMessage(queued.message)) {
6059
+ if (isResourceEventSlackMessage(queued.message)) {
6849
6060
  return contextProvenance;
6850
6061
  }
6851
6062
  const identity = getMessageActorIdentity(queued.message);
@@ -6859,13 +6070,6 @@ function queuedInstructionProvenance(queued, teamId) {
6859
6070
  );
6860
6071
  return instructionProvenanceFor(author);
6861
6072
  }
6862
- function isResourceEventMessage(message) {
6863
- const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
6864
- return raw?.event_type === "resource_event";
6865
- }
6866
- function resourceEventCredentialContext(message) {
6867
- return isResourceEventMessage(message) ? { actor: { platform: "system", name: "resource-event" } } : void 0;
6868
- }
6869
6073
  async function resolveChannelName(thread) {
6870
6074
  const existingName = thread.channel.name?.trim();
6871
6075
  if (existingName) {
@@ -6994,7 +6198,7 @@ function createReplyToThread(deps) {
6994
6198
  )
6995
6199
  };
6996
6200
  await Promise.all(
6997
- (options.queuedMessages ?? []).filter((queued) => !isResourceEventMessage(queued.message)).map(
6201
+ (options.queuedMessages ?? []).filter((queued) => !isResourceEventSlackMessage(queued.message)).map(
6998
6202
  (queued) => ensureSlackMessageActorIdentity(
6999
6203
  queued.message,
7000
6204
  teamId,
@@ -7003,15 +6207,28 @@ function createReplyToThread(deps) {
7003
6207
  )
7004
6208
  );
7005
6209
  const effectiveUserText = currentText.userText;
7006
- const credentialContext = options.execution?.credentialContext ?? resourceEventCredentialContext(message) ?? {
7007
- actor: { type: "user", userId: message.author.userId }
7008
- };
7009
- const actor = "type" in credentialContext.actor ? await ensureSlackMessageActorIdentity(
7010
- message,
7011
- teamId,
7012
- deps.services.lookupSlackUser
7013
- ) : void 0;
7014
- const executionActor = "type" in credentialContext.actor ? actor : credentialContext.actor;
6210
+ let executionActor;
6211
+ let credentialContext;
6212
+ if (options.execution) {
6213
+ executionActor = options.execution.dispatch.actor;
6214
+ credentialContext = options.execution.credentialContext;
6215
+ } else if (isResourceEventSlackMessage(message)) {
6216
+ executionActor = RESOURCE_EVENT_SYSTEM_ACTOR;
6217
+ credentialContext = credentialContextForActor(executionActor);
6218
+ } else {
6219
+ executionActor = await ensureSlackMessageActorIdentity(
6220
+ message,
6221
+ teamId,
6222
+ deps.services.lookupSlackUser
6223
+ );
6224
+ if (executionActor) {
6225
+ credentialContext = credentialContextForActor(executionActor);
6226
+ }
6227
+ }
6228
+ if (!executionActor || !credentialContext) {
6229
+ throw new Error("Slack reply execution requires an actor");
6230
+ }
6231
+ const actor = "userId" in executionActor ? executionActor : void 0;
7015
6232
  const slackActorId = actor?.userId;
7016
6233
  const preparedState = options.preparedState ?? await deps.prepareTurnState({
7017
6234
  thread,
@@ -7085,7 +6302,7 @@ function createReplyToThread(deps) {
7085
6302
  attachments,
7086
6303
  {
7087
6304
  threadId,
7088
- actorId: isResourceEventMessage(queued.message) ? void 0 : queued.message.author.userId,
6305
+ actorId: isResourceEventSlackMessage(queued.message) ? void 0 : queued.message.author.userId,
7089
6306
  channelId,
7090
6307
  runId,
7091
6308
  conversation: preparedState.conversation,
@@ -7660,7 +6877,9 @@ function createReplyToThread(deps) {
7660
6877
  },
7661
6878
  routing: {
7662
6879
  credentialContext,
7663
- actor: options.execution ? executionActor : actor,
6880
+ // Always set the execution actor when known. Resource-event turns
6881
+ // carry the system principal; interactive turns carry the Slack user.
6882
+ actor: executionActor,
7664
6883
  slackConversation,
7665
6884
  source,
7666
6885
  destination,
@@ -7673,7 +6892,7 @@ function createReplyToThread(deps) {
7673
6892
  policy: {
7674
6893
  configuration: preparedState.configuration,
7675
6894
  channelConfiguration: preparedState.channelConfiguration,
7676
- authorizationFlowMode: options.execution?.authorizationFlowMode ?? (message.author.isBot === true ? "disabled" : void 0),
6895
+ disabledFeatures: options.execution?.disabledFeatures ?? (message.author.isBot === true ? ["interactive-auth"] : void 0),
7677
6896
  turnDeadlineAtMs: getTurnRequestDeadline()?.deadlineAtMs
7678
6897
  },
7679
6898
  state: {
@@ -8387,7 +7606,7 @@ function createSlackDispatchTurnRunner(options) {
8387
7606
  conversationId,
8388
7607
  destination: dispatch2.destination,
8389
7608
  execution: {
8390
- authorizationFlowMode: "disabled",
7609
+ disabledFeatures: ["interactive-auth"],
8391
7610
  channelConfiguration: options.getChannelConfiguration(
8392
7611
  dispatch2.destination.channelId
8393
7612
  ),
@@ -8688,27 +7907,57 @@ async function failContinuationStartup(args) {
8688
7907
  );
8689
7908
  }
8690
7909
  }
8691
- async function resolveContinuationActor(args) {
8692
- const stored = args.sessionRecordActor;
8693
- if (stored?.platform === "slack" && stored.teamId === args.teamId && stored.userId === args.userId) {
7910
+ async function resolveSlackResumeUserActor(args) {
7911
+ try {
7912
+ const work = await getConversationWorkState({
7913
+ conversationId: args.conversationId
7914
+ });
7915
+ const workActor = work?.actor;
7916
+ if (workActor?.teamId === args.teamId && workActor.slackUserId === args.userId) {
7917
+ try {
7918
+ return createSlackActor(args.teamId, args.userId, {
7919
+ email: workActor.email,
7920
+ fullName: workActor.fullName,
7921
+ userName: workActor.slackUserName
7922
+ });
7923
+ } catch {
7924
+ }
7925
+ }
7926
+ } catch {
7927
+ }
7928
+ try {
8694
7929
  return createSlackResumeActor({
8695
- actor: stored,
8696
7930
  teamId: args.teamId,
8697
7931
  userId: args.userId
8698
7932
  });
7933
+ } catch {
7934
+ return void 0;
8699
7935
  }
8700
- const work = await getConversationWorkState({
8701
- conversationId: args.conversationId
8702
- });
8703
- const workActor = work?.actor;
8704
- if (workActor && workActor.teamId === args.teamId && workActor.slackUserId === args.userId) {
8705
- return createSlackActor(args.teamId, args.userId, {
8706
- email: workActor.email,
8707
- fullName: workActor.fullName,
8708
- userName: workActor.slackUserName
7936
+ }
7937
+ async function resolveResumeExecutionIdentity(args) {
7938
+ const routing = args.routingContext;
7939
+ if (routing?.credentialContext) {
7940
+ const actor2 = routing.dispatch?.actor ?? routing.actor ?? (!("type" in routing.credentialContext.actor) ? routing.credentialContext.actor : void 0);
7941
+ return actor2 ? { actor: actor2, credentialContext: routing.credentialContext } : void 0;
7942
+ }
7943
+ let actor = routing?.actor;
7944
+ if (!actor && isResourceEventConversationMessage(args.userMessage)) {
7945
+ actor = RESOURCE_EVENT_SYSTEM_ACTOR;
7946
+ }
7947
+ if (!actor && args.userMessage.author?.userId) {
7948
+ actor = await resolveSlackResumeUserActor({
7949
+ conversationId: args.conversationId,
7950
+ teamId: args.teamId,
7951
+ userId: args.userMessage.author.userId
8709
7952
  });
8710
7953
  }
8711
- return void 0;
7954
+ if (!actor) {
7955
+ return void 0;
7956
+ }
7957
+ return {
7958
+ actor,
7959
+ credentialContext: credentialContextForActor(actor)
7960
+ };
8712
7961
  }
8713
7962
  function isContinuationResume(summary) {
8714
7963
  return summary.state === "awaiting_resume" && (summary.resumeReason === "timeout" || summary.resumeReason === "yield" || summary.resumeReason === "retry");
@@ -8726,7 +7975,6 @@ async function failUnresumableContinuation(args) {
8726
7975
  conversationId: args.conversationId
8727
7976
  });
8728
7977
  await recordAgentTurnSessionSummary({
8729
- actor: args.summary.actor,
8730
7978
  conversationId: args.conversationId,
8731
7979
  destination: routing.destination,
8732
7980
  dispatchId: args.summary.dispatchId,
@@ -8793,16 +8041,32 @@ async function continueSlackAgentRunInContext(payload, options, runOptions) {
8793
8041
  (message) => message.role === "user" && options.inputMessageIds?.includes(message.id)
8794
8042
  ) : void 0;
8795
8043
  const userMessage = getTurnUserMessage(conversation, payload.sessionId) ?? dispatchUserMessage;
8796
- const systemActor = activeSessionRecord.actor?.platform === "system" ? activeSessionRecord.actor : void 0;
8797
- const userActorId = userMessage?.author?.userId;
8798
- if (!userMessage || !systemActor && !userActorId) {
8799
- throw new Error(
8800
- `Unable to locate the persisted user message for agent continuation session "${payload.sessionId}"`
8801
- );
8044
+ if (!userMessage) {
8045
+ await failStrandedSessionWithFallback({
8046
+ conversationId: payload.conversationId,
8047
+ errorMessage: `Unable to locate the persisted user message for agent continuation session "${payload.sessionId}"`,
8048
+ sessionRecord: activeSessionRecord
8049
+ });
8050
+ return false;
8802
8051
  }
8803
8052
  if (conversation.processing.activeTurnId !== payload.sessionId) {
8804
8053
  return false;
8805
8054
  }
8055
+ const identity = await resolveResumeExecutionIdentity({
8056
+ conversationId: payload.conversationId,
8057
+ routingContext: options.routingContext,
8058
+ teamId: destination.teamId,
8059
+ userMessage
8060
+ });
8061
+ if (!identity) {
8062
+ await failStrandedSessionWithFallback({
8063
+ conversationId: payload.conversationId,
8064
+ errorMessage: "Unable to rebuild Slack actor for continuation",
8065
+ sessionRecord: activeSessionRecord
8066
+ });
8067
+ return false;
8068
+ }
8069
+ const { actor, credentialContext } = identity;
8806
8070
  const channelConfiguration = getChannelConfigurationServiceById(
8807
8071
  destination.channelId
8808
8072
  );
@@ -8810,34 +8074,6 @@ async function continueSlackAgentRunInContext(payload, options, runOptions) {
8810
8074
  excludeMessageId: userMessage.id
8811
8075
  });
8812
8076
  const sandboxRef = getPersistedSandboxState(currentState);
8813
- let actor;
8814
- let credentialContext;
8815
- if (options.routingContext?.credentialContext) {
8816
- credentialContext = options.routingContext.credentialContext;
8817
- } else if (systemActor) {
8818
- credentialContext = { actor: systemActor };
8819
- } else {
8820
- actor = await resolveContinuationActor({
8821
- conversationId: payload.conversationId,
8822
- sessionRecordActor: activeSessionRecord.actor,
8823
- teamId: destination.teamId,
8824
- userId: userActorId
8825
- });
8826
- if (!actor) {
8827
- await failStrandedSessionWithFallback({
8828
- conversationId: payload.conversationId,
8829
- errorMessage: "Stored Slack actor missing for continuation",
8830
- sessionRecord: activeSessionRecord
8831
- });
8832
- return false;
8833
- }
8834
- credentialContext = {
8835
- actor: {
8836
- type: "user",
8837
- userId: actor.userId
8838
- }
8839
- };
8840
- }
8841
8077
  const routing = await resolveTurnSessionRouting({
8842
8078
  conversationId: payload.conversationId
8843
8079
  });
@@ -8885,7 +8121,7 @@ async function continueSlackAgentRunInContext(payload, options, runOptions) {
8885
8121
  routing: {
8886
8122
  ...options.routingContext,
8887
8123
  credentialContext,
8888
- ...options.routingContext?.actor ?? actor ? { actor: options.routingContext?.actor ?? actor } : {},
8124
+ actor,
8889
8125
  destination: routingDestination,
8890
8126
  source,
8891
8127
  toolChannelId: artifacts.assistantContextChannelId ?? destination.channelId
@@ -9006,7 +8242,6 @@ async function failStrandedSessionWithFallback(args) {
9006
8242
  }
9007
8243
  if (args.sessionRecord.dispatchId) {
9008
8244
  await recordAgentTurnSessionSummary({
9009
- actor: args.sessionRecord.actor,
9010
8245
  conversationId: args.conversationId,
9011
8246
  destination: routing.destination,
9012
8247
  dispatchId: args.sessionRecord.dispatchId,
@@ -9071,7 +8306,6 @@ async function recoverStrandedRunningSession(args) {
9071
8306
  messages: sessionRecord.piMessages,
9072
8307
  errorMessage: "Recovered running session after hard worker death",
9073
8308
  modelId,
9074
- actor: sessionRecord.actor,
9075
8309
  surface: sessionRecord.surface
9076
8310
  });
9077
8311
  if (!parked) {
@@ -9279,12 +8513,18 @@ function createProductionConversationWorkOptions(options) {
9279
8513
  },
9280
8514
  runTurn: runtime.runDispatchTurn
9281
8515
  });
8516
+ const providerWorker = createAgentDispatchWorkRouter({
8517
+ dispatchWorker,
8518
+ fallbackWorker: slackWorker
8519
+ });
9282
8520
  return {
9283
8521
  conversationStore,
9284
8522
  queue,
9285
- run: createAgentDispatchWorkRouter({
9286
- dispatchWorker,
9287
- fallbackWorker: slackWorker
8523
+ run: routeAgentInvocationWork({
8524
+ invocationWorker: createAgentInvocationWorker({
8525
+ agentRunner
8526
+ }),
8527
+ fallbackWorker: providerWorker
9288
8528
  })
9289
8529
  };
9290
8530
  }
@@ -9395,7 +8635,7 @@ async function ingestResourceEvent(input, options) {
9395
8635
  }
9396
8636
 
9397
8637
  // src/chat/event-tasks/ingest.ts
9398
- import { createHash as createHash2 } from "crypto";
8638
+ import { createHash } from "crypto";
9399
8639
  import {
9400
8640
  createSlackSource as createSlackSource3,
9401
8641
  RESOURCE_EVENT_SUMMARY_MAX_LENGTH,
@@ -9403,7 +8643,7 @@ import {
9403
8643
  resourceEventSchema as resourceEventSchema2
9404
8644
  } from "@sentry/junior-plugin-api";
9405
8645
  function eventTaskDispatchKey(taskId, namespace, eventKey) {
9406
- return `event-task:${createHash2("sha256").update(`${taskId}\0${namespace}\0${eventKey}`).digest("hex").slice(0, 32)}`;
8646
+ return `event-task:${createHash("sha256").update(`${taskId}\0${namespace}\0${eventKey}`).digest("hex").slice(0, 32)}`;
9407
8647
  }
9408
8648
  function oneLine(value) {
9409
8649
  return value.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim().slice(0, 512);
@@ -9815,6 +9055,7 @@ async function createApp(options) {
9815
9055
  const previousConfigDefaults = getConfigDefaults();
9816
9056
  const previousSlackReactionConfig = getSlackReactionConfig();
9817
9057
  const previousSandboxResources = getSandboxResourceConfig();
9058
+ const previousExperimentalFeatures = getExperimentalFeatures();
9818
9059
  const previousDashboardLinkOptions = setDashboardConversationLinkOptions(dashboard);
9819
9060
  let pluginRoutes = [];
9820
9061
  let pluginApiRoutes = [];
@@ -9850,6 +9091,7 @@ async function createApp(options) {
9850
9091
  options?.sandbox?.egressTracePropagationDomains
9851
9092
  );
9852
9093
  setSandboxResourceConfig(options?.sandbox);
9094
+ setExperimentalFeatures(options?.experimental);
9853
9095
  setConfigDefaults(options?.configDefaults);
9854
9096
  warnUnregisteredConfigDefaults(options?.configDefaults);
9855
9097
  if (options?.slack) {
@@ -9873,12 +9115,15 @@ async function createApp(options) {
9873
9115
  setConfigDefaults(previousConfigDefaults);
9874
9116
  setSlackReactionConfig(previousSlackReactionConfig);
9875
9117
  setSandboxResourceConfig(previousSandboxResources);
9118
+ setExperimentalFeatures(previousExperimentalFeatures);
9876
9119
  setDashboardConversationLinkOptions(previousDashboardLinkOptions);
9877
9120
  throw error;
9878
9121
  }
9879
9122
  const waitUntil = options?.waitUntil ?? await defaultWaitUntil();
9880
9123
  const tracePropagation = { domains: sandboxEgressTracePropagationDomains };
9124
+ const conversationWorkQueue = getVercelConversationWorkQueue();
9881
9125
  const agentRunner = createAgentRunner(executeAgentRun, {
9126
+ bindSpawnAgent: (request) => bindSpawnAgent(request, { queue: conversationWorkQueue }),
9882
9127
  tracePropagation
9883
9128
  });
9884
9129
  const runtimeServiceOverrides = {