@sentry/junior 0.137.0 → 0.138.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 (87) hide show
  1. package/dist/{agent-hooks-MZOKEP3J.js → agent-hooks-SCR74Q3E.js} +11 -9
  2. package/dist/api.js +21 -18
  3. package/dist/app.d.ts +7 -0
  4. package/dist/app.js +248 -689
  5. package/dist/{catalog-runtime-MD66YLFV.js → catalog-runtime-IU3QF4GG.js} +5 -5
  6. package/dist/chat/agent/request.d.ts +32 -2
  7. package/dist/chat/agent-dispatch/heartbeat.d.ts +5 -0
  8. package/dist/chat/agent-dispatch/types.d.ts +1 -1
  9. package/dist/chat/agent-invocations/errors.d.ts +8 -0
  10. package/dist/chat/agent-invocations/spawn.d.ts +12 -0
  11. package/dist/chat/agent-invocations/store.d.ts +44 -0
  12. package/dist/chat/agent-invocations/types.d.ts +449 -0
  13. package/dist/chat/agent-invocations/work.d.ts +31 -0
  14. package/dist/chat/config.d.ts +1 -0
  15. package/dist/chat/conversations/sql/store.d.ts +8 -0
  16. package/dist/chat/conversations/store.d.ts +7 -0
  17. package/dist/chat/experimental.d.ts +16 -0
  18. package/dist/chat/runtime/agent-runner.d.ts +2 -1
  19. package/dist/chat/services/auth-pause.d.ts +0 -1
  20. package/dist/chat/services/mcp-auth-orchestration.d.ts +3 -2
  21. package/dist/chat/services/plugin-auth-orchestration.d.ts +3 -2
  22. package/dist/chat/state/turn-session.d.ts +0 -2
  23. package/dist/chat/task-execution/state.d.ts +23 -11
  24. package/dist/chat/task-execution/store.d.ts +20 -11
  25. package/dist/chat/task-execution/worker.d.ts +2 -2
  26. package/dist/chat/tools/runtime/spawn-agent.d.ts +31 -0
  27. package/dist/chat/tools/types.d.ts +2 -0
  28. package/dist/chunk-3Y47VMHV.js +6 -0
  29. package/dist/chunk-5IDFPCON.js +46 -0
  30. package/dist/chunk-5LTJ4ITU.js +1523 -0
  31. package/dist/{chunk-WPPXIAFB.js → chunk-5MXWHTCK.js} +17 -434
  32. package/dist/{chunk-UOXATPSI.js → chunk-A7WCHXZV.js} +7 -5
  33. package/dist/{chunk-5G34IEK2.js → chunk-BOLJOV5T.js} +2 -56
  34. package/dist/{chunk-UG6ZYPBI.js → chunk-BYW4B4PR.js} +73 -60
  35. package/dist/{chunk-XZ7NPXAB.js → chunk-C7ZVPO4Z.js} +1 -1
  36. package/dist/{chunk-RB4NJKEX.js → chunk-DCLLQGS6.js} +119 -50
  37. package/dist/{chunk-DQXAAVTR.js → chunk-E5UYAIKJ.js} +44 -1467
  38. package/dist/{chunk-6QW2RRQT.js → chunk-ELKW5FIB.js} +5 -5
  39. package/dist/chunk-EM225ITJ.js +56 -0
  40. package/dist/{chunk-SYCX4DOK.js → chunk-FEUNPTGV.js} +7 -60
  41. package/dist/{chunk-YRPC6MJY.js → chunk-FNLBTB4K.js} +1 -1
  42. package/dist/chunk-HIWJWWMT.js +668 -0
  43. package/dist/chunk-IUFM5JWV.js +24 -0
  44. package/dist/{chunk-T6Z5S6PQ.js → chunk-JNNJZURV.js} +1 -1
  45. package/dist/{chunk-6ZK6FZIV.js → chunk-KCBZSJRU.js} +6 -27
  46. package/dist/{chunk-5PNJMOY5.js → chunk-KDCHVT25.js} +1 -1
  47. package/dist/{chunk-BZAMASJP.js → chunk-KFRHRSCJ.js} +1 -1
  48. package/dist/chunk-LPVUNO2W.js +320 -0
  49. package/dist/{chunk-EWDSB24B.js → chunk-N7WARMC6.js} +16 -776
  50. package/dist/chunk-PB3CXNVL.js +41 -0
  51. package/dist/chunk-Q6QZTTLS.js +55 -0
  52. package/dist/{chunk-36EG34YI.js → chunk-QS527AV5.js} +1 -1
  53. package/dist/chunk-R776HKPK.js +81 -0
  54. package/dist/{chunk-KJ4HGRHN.js → chunk-RZIBSCYC.js} +507 -596
  55. package/dist/chunk-SD3OFRWY.js +714 -0
  56. package/dist/{chunk-ZFS67WIR.js → chunk-SIPKWSXK.js} +57 -6
  57. package/dist/chunk-TCS76X5D.js +441 -0
  58. package/dist/{chunk-K2NCQJKH.js → chunk-TDJCLNDH.js} +3 -3
  59. package/dist/{chunk-POBSY47Q.js → chunk-TUUDXEXU.js} +336 -302
  60. package/dist/{chunk-YNFERGKP.js → chunk-VPZFVJND.js} +1 -1
  61. package/dist/{chunk-UZQ4VY3M.js → chunk-YU5QUF67.js} +1 -1
  62. package/dist/chunk-ZBSMCXKV.js +710 -0
  63. package/dist/{chunk-3Q342EJJ.js → chunk-ZXNTJJIE.js} +2 -2
  64. package/dist/cli/chat.js +65 -26
  65. package/dist/cli/check.js +6 -6
  66. package/dist/cli/plugins.js +14 -12
  67. package/dist/cli/snapshot-create.js +10 -9
  68. package/dist/cli/upgrade.js +7 -7
  69. package/dist/conversation-work-KATFHLG3.js +56 -0
  70. package/dist/db/schema/agent-invocations.d.ts +544 -0
  71. package/dist/db/schema.d.ts +543 -1
  72. package/dist/{db-66E7VDNC.js → db-UO2IWXGA.js} +7 -7
  73. package/dist/experimental-HH6OM6UH.js +13 -0
  74. package/dist/instrumentation.js +4 -4
  75. package/dist/nitro.js +4 -3
  76. package/dist/{oauth-callback-server-7QPD4Q7P.js → oauth-callback-server-MAQI43LX.js} +26 -18
  77. package/dist/{runner-L2BKJKOD.js → runner-PIUQEXKE.js} +26 -14
  78. package/dist/{skills-HOQ2NSPW.js → skills-4ARJADHQ.js} +6 -6
  79. package/dist/spawn-G2AWPMCT.js +38 -0
  80. package/dist/{validation-ENHI7S2C.js → validation-S3EE3HWL.js} +6 -6
  81. package/dist/version.js +1 -1
  82. package/dist/work-UARRUAW3.js +46 -0
  83. package/dist/worker-DOCIRVD3.js +24 -0
  84. package/migrations/0021_first_warhawk.sql +35 -0
  85. package/migrations/meta/0021_snapshot.json +2176 -0
  86. package/migrations/meta/_journal.json +8 -1
  87. package/package.json +5 -5
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-HIWJWWMT.js";
4
+ import {
5
+ bindSpawnAgent
6
+ } from "./chunk-PB3CXNVL.js";
7
+ import {
8
+ createAgentInvocationWorker,
9
+ enqueueAgentInvocation,
10
+ listPendingAgentInvocationMailboxAppends,
11
+ routeAgentInvocationWork
12
+ } from "./chunk-ZBSMCXKV.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,31 +27,32 @@ 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-E5UYAIKJ.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-5LTJ4ITU.js";
55
+ import {
57
56
  buildDeliveredTurnStatePatch,
58
57
  completeAuthPauseTurn,
59
58
  finalizeFailedTurnReplyWithEvent,
@@ -66,24 +65,28 @@ import {
66
65
  requireTurnFailureEventId,
67
66
  resolveTurnSessionRouting,
68
67
  scheduleSessionCompletedPluginTasks
69
- } from "./chunk-SYCX4DOK.js";
68
+ } from "./chunk-FEUNPTGV.js";
69
+ import {
70
+ ConversationTurnLifecycleService
71
+ } from "./chunk-Q6QZTTLS.js";
70
72
  import {
71
73
  GET,
72
74
  conversationVisibilityFromSlackChannelType,
73
75
  recordTaskExecution,
74
76
  resolveSlackChannelTypeFromMessage,
75
77
  resolveSlackConversationContext
76
- } from "./chunk-6QW2RRQT.js";
78
+ } from "./chunk-ELKW5FIB.js";
77
79
  import "./chunk-4CODA7P5.js";
78
80
  import "./chunk-UTL3MHPC.js";
79
81
  import {
80
- ConversationQueueMessageRejectedError,
81
- conversationQueueMessageSchema,
82
82
  getVercelConversationWorkQueue,
83
- isConversationQueueMessageRejectedError,
84
83
  resolveConversationWorkQueueTopic,
85
84
  verifyConversationQueueMessage
86
- } from "./chunk-6ZK6FZIV.js";
85
+ } from "./chunk-KCBZSJRU.js";
86
+ import {
87
+ conversationQueueMessageSchema,
88
+ isConversationQueueMessageRejectedError
89
+ } from "./chunk-IUFM5JWV.js";
87
90
  import {
88
91
  PLUGIN_TASK_QUEUE_TOPIC,
89
92
  createVercelQueueClient,
@@ -92,7 +95,7 @@ import {
92
95
  import {
93
96
  validatePluginEgressCredentialHooks,
94
97
  validatePluginRegistrations
95
- } from "./chunk-UZQ4VY3M.js";
98
+ } from "./chunk-YU5QUF67.js";
96
99
  import {
97
100
  defineJuniorPlugins,
98
101
  pluginCatalogConfigFromEnv,
@@ -100,8 +103,6 @@ import {
100
103
  pluginRuntimeRegistrationsFromPluginSet
101
104
  } from "./chunk-FIANEE7Z.js";
102
105
  import {
103
- AuthorizationFlowDisabledError,
104
- PluginCredentialFailureError,
105
106
  SANDBOX_EGRESS_PROXY_PATH,
106
107
  appendSlackLegacyAttachmentText,
107
108
  buildSteeringPiMessage,
@@ -127,22 +128,29 @@ import {
127
128
  setConfigDefaults,
128
129
  shouldEmitDevAgentTrace,
129
130
  shouldPropagateSandboxEgressTrace
130
- } from "./chunk-KJ4HGRHN.js";
131
+ } from "./chunk-RZIBSCYC.js";
132
+ import {
133
+ AuthorizationFlowDisabledError,
134
+ PluginCredentialFailureError
135
+ } from "./chunk-LPVUNO2W.js";
131
136
  import "./chunk-DIB4GX3G.js";
132
137
  import {
133
- RetryableDeliveryError,
134
138
  createUserTokenStore,
135
139
  deleteMcpAuthSessionsForUserProvider,
136
140
  deleteMcpServerSessionId,
137
- deleteMcpStoredOAuthCredentials,
141
+ deleteMcpStoredOAuthCredentials
142
+ } from "./chunk-N7WARMC6.js";
143
+ import {
144
+ RetryableDeliveryError
145
+ } from "./chunk-R776HKPK.js";
146
+ import {
138
147
  formatProviderLabel,
139
148
  postSlackMessage,
140
149
  startOAuthFlow
141
- } from "./chunk-EWDSB24B.js";
150
+ } from "./chunk-SD3OFRWY.js";
142
151
  import {
143
152
  ConversationTurnBoundaryError,
144
153
  CooperativeTurnYieldError,
145
- JUNIOR_THREAD_STATE_TTL_MS,
146
154
  TurnInputCommitLostError,
147
155
  TurnInputDeferredError,
148
156
  abandonAgentTurnSessionRecord,
@@ -195,47 +203,56 @@ import {
195
203
  trimTrailingAssistantMessages,
196
204
  updateConversationStats,
197
205
  upsertConversationMessage
198
- } from "./chunk-POBSY47Q.js";
206
+ } from "./chunk-TUUDXEXU.js";
207
+ import {
208
+ JUNIOR_THREAD_STATE_TTL_MS
209
+ } from "./chunk-3Y47VMHV.js";
199
210
  import "./chunk-EPBBYMNE.js";
211
+ import {
212
+ getExperimentalFeatures,
213
+ setExperimentalFeatures
214
+ } from "./chunk-5IDFPCON.js";
200
215
  import {
201
216
  getSandboxResourceConfig,
202
217
  setSandboxResourceConfig
203
- } from "./chunk-UOXATPSI.js";
218
+ } from "./chunk-A7WCHXZV.js";
204
219
  import {
205
220
  createSchedulerSqlStore,
206
221
  findMatchingEventTasks
207
- } from "./chunk-K2NCQJKH.js";
222
+ } from "./chunk-TDJCLNDH.js";
208
223
  import {
209
224
  parseSlackThreadId
210
- } from "./chunk-YRPC6MJY.js";
225
+ } from "./chunk-FNLBTB4K.js";
211
226
  import "./chunk-CDQ4YIZM.js";
212
227
  import "./chunk-PDO5BLNM.js";
213
228
  import "./chunk-UIE3R5XU.js";
214
229
  import {
215
- SlackActionError,
216
230
  bindEventTaskCredentialSubject,
217
231
  bindScheduledTaskCredentialSubject,
218
232
  bindSlackDirectCredentialSubject,
219
233
  createPluginLogger,
220
234
  createPluginState,
221
235
  createResourceEventTeamIdResolver,
222
- downloadPrivateSlackFile,
223
236
  getPluginApiRoutes,
224
237
  getPluginRoutes,
225
238
  getPlugins,
226
- getSlackClient,
227
- getWorkspaceTeamId,
228
- isDmChannel,
229
- normalizeSlackConversationId,
230
- runWithSlackInstallationToken,
231
- runWithWorkspaceTeamId,
232
239
  setDashboardConversationLinkOptions,
233
240
  setPlugins,
234
241
  validatePlugins,
235
242
  verifyEventTaskCredentialSubject,
236
243
  verifyScheduledTaskCredentialSubject,
237
244
  verifySlackDirectCredentialSubject
238
- } from "./chunk-WPPXIAFB.js";
245
+ } from "./chunk-5MXWHTCK.js";
246
+ import {
247
+ SlackActionError,
248
+ downloadPrivateSlackFile,
249
+ getSlackClient,
250
+ getWorkspaceTeamId,
251
+ isDmChannel,
252
+ normalizeSlackConversationId,
253
+ runWithSlackInstallationToken,
254
+ runWithWorkspaceTeamId
255
+ } from "./chunk-TCS76X5D.js";
239
256
  import {
240
257
  contextProvenance,
241
258
  createSlackDestination,
@@ -245,17 +262,22 @@ import {
245
262
  getSqlExecutor,
246
263
  instructionProvenanceFor,
247
264
  requireSlackDestination,
248
- withConversationEventLock
249
- } from "./chunk-ZFS67WIR.js";
265
+ withConversationEventLock,
266
+ withConversationMutationLock
267
+ } from "./chunk-SIPKWSXK.js";
250
268
  import {
269
+ juniorAgentBindings,
270
+ juniorAgentInvocations,
251
271
  juniorConversationEvents,
252
272
  juniorConversations
253
- } from "./chunk-RB4NJKEX.js";
273
+ } from "./chunk-DCLLQGS6.js";
254
274
  import {
255
- acquireActiveLock,
256
- getStateAdapter,
257
275
  sleep
258
- } from "./chunk-5G34IEK2.js";
276
+ } from "./chunk-EM225ITJ.js";
277
+ import {
278
+ acquireActiveLock,
279
+ getStateAdapter
280
+ } from "./chunk-BOLJOV5T.js";
259
281
  import {
260
282
  STANDARD_MODEL_PROFILE,
261
283
  botConfig,
@@ -273,16 +295,16 @@ import {
273
295
  modelIdForProfile,
274
296
  setSlackReactionConfig,
275
297
  standardModelId
276
- } from "./chunk-UG6ZYPBI.js";
298
+ } from "./chunk-BYW4B4PR.js";
277
299
  import {
278
300
  juniorDestinations
279
301
  } from "./chunk-VFUK3X5B.js";
280
- import "./chunk-YNFERGKP.js";
281
- import "./chunk-3Q342EJJ.js";
302
+ import "./chunk-VPZFVJND.js";
303
+ import "./chunk-ZXNTJJIE.js";
282
304
  import {
283
305
  pluginCatalogRuntime
284
- } from "./chunk-36EG34YI.js";
285
- import "./chunk-BZAMASJP.js";
306
+ } from "./chunk-QS527AV5.js";
307
+ import "./chunk-KFRHRSCJ.js";
286
308
  import {
287
309
  credentialSubjectSchema
288
310
  } from "./chunk-77VDLF3Z.js";
@@ -314,7 +336,7 @@ import {
314
336
  withContext,
315
337
  withLogContext,
316
338
  withSpan
317
- } from "./chunk-T6Z5S6PQ.js";
339
+ } from "./chunk-JNNJZURV.js";
318
340
  import {
319
341
  continueTrace
320
342
  } from "./chunk-6GWA276C.js";
@@ -322,10 +344,10 @@ import "./chunk-OOUNJNMR.js";
322
344
  import "./chunk-H4HBKISO.js";
323
345
  import {
324
346
  JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE
325
- } from "./chunk-5PNJMOY5.js";
347
+ } from "./chunk-KDCHVT25.js";
326
348
  import {
327
349
  JUNIOR_VERSION
328
- } from "./chunk-XZ7NPXAB.js";
350
+ } from "./chunk-C7ZVPO4Z.js";
329
351
  import "./chunk-MLKGABMK.js";
330
352
 
331
353
  // src/app.ts
@@ -1020,7 +1042,7 @@ function createAgentDispatchConversationWorker(options) {
1020
1042
  `Dispatch ${dispatch2.id} belongs to ${expectedConversationId}, not ${context.conversationId}`
1021
1043
  );
1022
1044
  }
1023
- if (context.destination.platform !== dispatch2.destination.platform || context.destination.teamId !== dispatch2.destination.teamId || context.destination.channelId !== dispatch2.destination.channelId) {
1045
+ if (!context.destination || context.destination.platform !== dispatch2.destination.platform || context.destination.teamId !== dispatch2.destination.teamId || context.destination.channelId !== dispatch2.destination.channelId) {
1024
1046
  throw new Error(
1025
1047
  `Dispatch ${dispatch2.id} destination does not match its conversation lease`
1026
1048
  );
@@ -1159,6 +1181,10 @@ function heartbeatIdempotencyKey(reason, conversationId, nowMs) {
1159
1181
  return `heartbeat:${reason}:${conversationId}:${nowMs}`;
1160
1182
  }
1161
1183
  async function recoverConversationWork(args) {
1184
+ if (!getChatConfig().conversationWorkEnabled) {
1185
+ logWarn("conversation.work.recovery.disabled");
1186
+ return { expiredLeaseCount: 0, pendingCount: 0 };
1187
+ }
1162
1188
  const result = {
1163
1189
  expiredLeaseCount: 0,
1164
1190
  pendingCount: 0
@@ -1203,12 +1229,24 @@ async function recoverConversationWork(args) {
1203
1229
  return;
1204
1230
  }
1205
1231
  if (work.execution.lease && work.execution.lease.expiresAtMs <= args.nowMs) {
1206
- const cleared = await clearExpiredConversationLease({
1232
+ const recovery = await clearExpiredConversationLease({
1207
1233
  conversationId,
1208
1234
  nowMs: args.nowMs,
1209
1235
  state: args.state
1210
1236
  });
1211
- if (!cleared) {
1237
+ if (recovery === "not_expired") {
1238
+ return;
1239
+ }
1240
+ if (recovery === "stopped") {
1241
+ logException(
1242
+ new Error("Conversation work stopped after repeated failed attempts"),
1243
+ "conversation.work.retry.exhausted",
1244
+ {
1245
+ "app.run.id": work.execution.runId ?? "unknown",
1246
+ "app.worker.last_progress_at_ms": work.execution.lastProgressAtMs ?? work.createdAtMs,
1247
+ "app.worker.retry_count": (work.execution.retryCount ?? 0) + 1
1248
+ }
1249
+ );
1212
1250
  return;
1213
1251
  }
1214
1252
  const wake2 = await ensureConversationWake({
@@ -1765,6 +1803,7 @@ async function runScheduledTaskHeartbeat(args) {
1765
1803
  var DEFAULT_PLUGIN_LIMIT = 25;
1766
1804
  var PLUGIN_HEARTBEAT_TIMEOUT_MS = 25e3;
1767
1805
  var DISPATCH_MAILBOX_APPEND_LIMIT = 100;
1806
+ var AGENT_INVOCATION_MAILBOX_APPEND_LIMIT = 100;
1768
1807
  async function runWithTimeout(promise, timeoutMs) {
1769
1808
  let timeout;
1770
1809
  try {
@@ -1851,6 +1890,23 @@ async function recoverPendingDispatchMailboxAppends(args) {
1851
1890
  }
1852
1891
  }
1853
1892
  }
1893
+ async function recoverPendingAgentInvocationMailboxAppends(args) {
1894
+ const invocations = await listPendingAgentInvocationMailboxAppends(
1895
+ AGENT_INVOCATION_MAILBOX_APPEND_LIMIT
1896
+ );
1897
+ for (const invocation of invocations) {
1898
+ try {
1899
+ await enqueueAgentInvocation(invocation, {
1900
+ nowMs: args.nowMs,
1901
+ queue: args.conversationWorkQueue
1902
+ });
1903
+ } catch (error) {
1904
+ logException(error, "agent.invocation.mailbox.append_recovery.failed", {
1905
+ "app.agent.invocation_id": invocation.invocationId
1906
+ });
1907
+ }
1908
+ }
1909
+ }
1854
1910
  async function runHeartbeat(args) {
1855
1911
  const queue = args.conversationWorkQueue ?? getVercelConversationWorkQueue();
1856
1912
  await recoverConversationWork({
@@ -1861,6 +1917,10 @@ async function runHeartbeat(args) {
1861
1917
  conversationWorkQueue: queue,
1862
1918
  nowMs: args.nowMs
1863
1919
  });
1920
+ await recoverPendingAgentInvocationMailboxAppends({
1921
+ conversationWorkQueue: queue,
1922
+ nowMs: args.nowMs
1923
+ });
1864
1924
  try {
1865
1925
  const dispatchCount = await runScheduledTaskHeartbeat({
1866
1926
  conversationWorkQueue: queue,
@@ -1919,7 +1979,7 @@ async function GET4(request, waitUntil, options = {}) {
1919
1979
  import { timingSafeEqual as timingSafeEqual3 } from "crypto";
1920
1980
 
1921
1981
  // src/chat/conversations/sql/purge.ts
1922
- import { and, asc, eq as eq2, inArray, isNull, sql } from "drizzle-orm";
1982
+ import { and, asc, eq as eq2, inArray, isNull, or, sql } from "drizzle-orm";
1923
1983
 
1924
1984
  // src/chat/conversations/sql/privacy.ts
1925
1985
  import { eq } from "drizzle-orm";
@@ -2010,6 +2070,16 @@ async function selectExpiredRoots(executor, args) {
2010
2070
  select 1 from junior_conversation_events events
2011
2071
  where events.conversation_id = tree.conversation_id
2012
2072
  )
2073
+ or exists (
2074
+ select 1 from junior_agent_invocations invocations
2075
+ where invocations.parent_conversation_id = tree.conversation_id
2076
+ or invocations.child_conversation_id = tree.conversation_id
2077
+ )
2078
+ or exists (
2079
+ select 1 from junior_agent_bindings bindings
2080
+ where bindings.parent_conversation_id = tree.conversation_id
2081
+ or bindings.child_conversation_id = tree.conversation_id
2082
+ )
2013
2083
  or (
2014
2084
  ${juniorDestinations.visibility} is distinct from 'public'
2015
2085
  and exists (
@@ -2045,69 +2115,85 @@ async function selectExpiredRoots(executor, args) {
2045
2115
  }));
2046
2116
  }
2047
2117
  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({
2118
+ return await withConversationMutationLock(
2119
+ executor,
2120
+ args.rootConversationId,
2121
+ async () => {
2122
+ const initialRoots = await executor.db().select({
2123
+ conversationId: juniorConversations.conversationId,
2124
+ parentConversationId: juniorConversations.parentConversationId
2125
+ }).from(juniorConversations).where(eq2(juniorConversations.conversationId, args.rootConversationId));
2126
+ const initialRoot = initialRoots[0];
2127
+ if (!initialRoot || args.retention && initialRoot.parentConversationId !== null) {
2128
+ return { purged: false, conversations: 0 };
2129
+ }
2130
+ const tree = await discoverConversationTree(executor, initialRoot);
2131
+ return await withConversationEventLock(
2132
+ executor,
2133
+ args.rootConversationId,
2134
+ async () => {
2135
+ for (const conversation of tree.slice(1)) {
2136
+ await withConversationEventLock(
2137
+ executor,
2138
+ conversation.conversationId,
2139
+ async () => void 0
2140
+ );
2141
+ }
2142
+ const roots = await executor.db().select({
2086
2143
  conversationId: juniorConversations.conversationId,
2087
- lastActivityAt: juniorConversations.lastActivityAt
2088
- }).from(juniorConversations).where(inArray(juniorConversations.conversationId, ids));
2089
- if (currentActivity.length !== ids.length) {
2144
+ destinationId: juniorConversations.destinationId,
2145
+ parentConversationId: juniorConversations.parentConversationId
2146
+ }).from(juniorConversations).where(
2147
+ eq2(juniorConversations.conversationId, args.rootConversationId)
2148
+ ).for("update");
2149
+ const root = roots[0];
2150
+ if (!root || args.retention && root.parentConversationId !== null) {
2090
2151
  return { purged: false, conversations: 0 };
2091
2152
  }
2092
- const windowMs = isPublic ? args.retention.publicWindowMs : args.retention.privateWindowMs;
2093
- const effectiveLastActivityAt = Math.max(
2094
- ...currentActivity.map(
2095
- (conversation) => conversation.lastActivityAt.getTime()
2153
+ const resolvedScrubMetadata = args.scrubMetadataFromRootVisibility ? (await resolveRootVisibility(executor, args.rootConversationId)).visibility !== "public" : args.scrubMetadata;
2154
+ const destinations = root.destinationId ? await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq2(juniorDestinations.id, root.destinationId)).for("share") : [];
2155
+ const isPublic = destinations[0]?.visibility === "public";
2156
+ const ids = tree.map((conversation) => conversation.conversationId);
2157
+ if (args.retention) {
2158
+ const currentActivity = await executor.db().select({
2159
+ conversationId: juniorConversations.conversationId,
2160
+ lastActivityAt: juniorConversations.lastActivityAt
2161
+ }).from(juniorConversations).where(inArray(juniorConversations.conversationId, ids));
2162
+ if (currentActivity.length !== ids.length) {
2163
+ return { purged: false, conversations: 0 };
2164
+ }
2165
+ const windowMs = isPublic ? args.retention.publicWindowMs : args.retention.privateWindowMs;
2166
+ const effectiveLastActivityAt = Math.max(
2167
+ ...currentActivity.map(
2168
+ (conversation) => conversation.lastActivityAt.getTime()
2169
+ )
2170
+ );
2171
+ if (effectiveLastActivityAt >= args.nowMs - windowMs) {
2172
+ return { purged: false, conversations: 0 };
2173
+ }
2174
+ }
2175
+ await executor.db().delete(juniorConversationEvents).where(inArray(juniorConversationEvents.conversationId, ids));
2176
+ await executor.db().delete(juniorAgentInvocations).where(
2177
+ or(
2178
+ inArray(juniorAgentInvocations.parentConversationId, ids),
2179
+ inArray(juniorAgentInvocations.childConversationId, ids)
2096
2180
  )
2097
2181
  );
2098
- if (effectiveLastActivityAt >= args.nowMs - windowMs) {
2099
- return { purged: false, conversations: 0 };
2100
- }
2182
+ await executor.db().delete(juniorAgentBindings).where(
2183
+ or(
2184
+ inArray(juniorAgentBindings.parentConversationId, ids),
2185
+ inArray(juniorAgentBindings.childConversationId, ids)
2186
+ )
2187
+ );
2188
+ await executor.db().update(juniorConversations).set({
2189
+ transcriptPurgedAt: new Date(args.nowMs),
2190
+ ...(args.retention ? !isPublic : resolvedScrubMetadata) ? { title: null, channelName: null, actor: null } : {}
2191
+ }).where(inArray(juniorConversations.conversationId, ids));
2192
+ return { purged: true, conversations: ids.length };
2101
2193
  }
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
- });
2194
+ );
2195
+ }
2196
+ );
2111
2197
  }
2112
2198
 
2113
2199
  // src/chat/conversations/retention.ts
@@ -2219,9 +2305,9 @@ function buildJwksUrl(value) {
2219
2305
  return url;
2220
2306
  }
2221
2307
  async function getJwks(issuer) {
2222
- const now2 = Date.now();
2308
+ const now = Date.now();
2223
2309
  const cached = jwksByIssuer.get(issuer);
2224
- if (cached && cached.expiresAtMs > now2) {
2310
+ if (cached && cached.expiresAtMs > now) {
2225
2311
  return cached.jwks;
2226
2312
  }
2227
2313
  if (cached) {
@@ -2246,7 +2332,7 @@ async function getJwks(issuer) {
2246
2332
  }
2247
2333
  jwksByIssuer.set(issuer, {
2248
2334
  jwks,
2249
- expiresAtMs: now2 + OIDC_DISCOVERY_CACHE_TTL_MS
2335
+ expiresAtMs: now + OIDC_DISCOVERY_CACHE_TTL_MS
2250
2336
  });
2251
2337
  return jwks;
2252
2338
  }
@@ -3352,7 +3438,7 @@ function createSlackConversationWorker(options) {
3352
3438
  if (route === "mention") {
3353
3439
  await options.runtime.handleNewMention(thread, latestMessage, {
3354
3440
  conversationId: context.conversationId,
3355
- destination: context.destination,
3441
+ destination,
3356
3442
  messageContext,
3357
3443
  drainSteeringMessages,
3358
3444
  ack,
@@ -3365,7 +3451,7 @@ function createSlackConversationWorker(options) {
3365
3451
  latestMessage,
3366
3452
  {
3367
3453
  conversationId: context.conversationId,
3368
- destination: context.destination,
3454
+ destination,
3369
3455
  messageContext,
3370
3456
  drainSteeringMessages,
3371
3457
  ack,
@@ -4251,555 +4337,6 @@ import {
4251
4337
  handleCallback,
4252
4338
  registerDevConsumer
4253
4339
  } 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" };
4567
- }
4568
- }
4569
- attemptMessageIds = attemptMessages.map(
4570
- (message2) => message2.inboundMessageId
4571
- );
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;
4701
- }
4702
- }
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;
4736
- 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
- });
4775
- }
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) {
4794
- throw error;
4795
- }
4796
- return { status: "failed" };
4797
- } finally {
4798
- clearInterval(timer);
4799
- }
4800
- }
4801
-
4802
- // src/chat/task-execution/vercel-callback.ts
4803
4340
  var CONVERSATION_WORK_VISIBILITY_TIMEOUT_BUFFER_SECONDS = 30;
4804
4341
  var CONVERSATION_WORK_DEV_CONSUMER_GROUP = "junior_conversation_work_dev";
4805
4342
  function parseConversationQueueMessage(message) {
@@ -4825,6 +4362,15 @@ async function processConversationQueueMessage(message, options) {
4825
4362
  });
4826
4363
  }
4827
4364
  async function handleConversationQueueMessage(message, metadata, options) {
4365
+ if (!getChatConfig().conversationWorkEnabled) {
4366
+ logWarn("conversation.work.processing.disabled", {
4367
+ "app.queue.consumer_group": metadata.consumerGroup,
4368
+ "app.queue.delivery_count": metadata.deliveryCount,
4369
+ "app.queue.message_id": metadata.messageId,
4370
+ "app.queue.topic_name": metadata.topicName
4371
+ });
4372
+ return;
4373
+ }
4828
4374
  const verification = verifyConversationQueueMessage(message);
4829
4375
  if (verification.status === "rejected") {
4830
4376
  logConversationQueueMessageRejected(verification.reason, metadata);
@@ -6649,14 +6195,16 @@ function createJuniorRuntimeServices(overrides = {}) {
6649
6195
  listThreadReplies: overrides.visionContext?.listThreadReplies ?? listThreadReplies,
6650
6196
  downloadFile: overrides.visionContext?.downloadFile ?? downloadPrivateSlackFile
6651
6197
  });
6198
+ const agentRunner = overrides.replyExecutor?.agentRunner ?? createAgentRunner(executeAgentRun, {
6199
+ bindSpawnAgent: (request) => bindSpawnAgent(request, { queue: getVercelConversationWorkQueue }),
6200
+ tracePropagation: overrides.sandbox?.tracePropagation
6201
+ });
6652
6202
  return {
6653
6203
  conversationMemory,
6654
6204
  contextCompactor,
6655
6205
  replyExecutor: {
6656
6206
  contextCompactor: overrides.replyExecutor?.contextCompactor ?? contextCompactor,
6657
- agentRunner: overrides.replyExecutor?.agentRunner ?? createAgentRunner(executeAgentRun, {
6658
- tracePropagation: overrides.sandbox?.tracePropagation
6659
- }),
6207
+ agentRunner,
6660
6208
  getAwaitingAgentContinueRequest: overrides.replyExecutor?.getAwaitingAgentContinueRequest ?? getAwaitingAgentContinueRequest,
6661
6209
  lookupSlackUser: overrides.replyExecutor?.lookupSlackUser ?? lookupSlackUser,
6662
6210
  scheduleAgentContinue: overrides.replyExecutor?.scheduleAgentContinue ?? scheduleAgentContinue,
@@ -7673,7 +7221,7 @@ function createReplyToThread(deps) {
7673
7221
  policy: {
7674
7222
  configuration: preparedState.configuration,
7675
7223
  channelConfiguration: preparedState.channelConfiguration,
7676
- authorizationFlowMode: options.execution?.authorizationFlowMode ?? (message.author.isBot === true ? "disabled" : void 0),
7224
+ disabledFeatures: options.execution?.disabledFeatures ?? (message.author.isBot === true ? ["interactive-auth"] : void 0),
7677
7225
  turnDeadlineAtMs: getTurnRequestDeadline()?.deadlineAtMs
7678
7226
  },
7679
7227
  state: {
@@ -8387,7 +7935,7 @@ function createSlackDispatchTurnRunner(options) {
8387
7935
  conversationId,
8388
7936
  destination: dispatch2.destination,
8389
7937
  execution: {
8390
- authorizationFlowMode: "disabled",
7938
+ disabledFeatures: ["interactive-auth"],
8391
7939
  channelConfiguration: options.getChannelConfiguration(
8392
7940
  dispatch2.destination.channelId
8393
7941
  ),
@@ -9279,12 +8827,18 @@ function createProductionConversationWorkOptions(options) {
9279
8827
  },
9280
8828
  runTurn: runtime.runDispatchTurn
9281
8829
  });
8830
+ const providerWorker = createAgentDispatchWorkRouter({
8831
+ dispatchWorker,
8832
+ fallbackWorker: slackWorker
8833
+ });
9282
8834
  return {
9283
8835
  conversationStore,
9284
8836
  queue,
9285
- run: createAgentDispatchWorkRouter({
9286
- dispatchWorker,
9287
- fallbackWorker: slackWorker
8837
+ run: routeAgentInvocationWork({
8838
+ invocationWorker: createAgentInvocationWorker({
8839
+ agentRunner
8840
+ }),
8841
+ fallbackWorker: providerWorker
9288
8842
  })
9289
8843
  };
9290
8844
  }
@@ -9815,6 +9369,7 @@ async function createApp(options) {
9815
9369
  const previousConfigDefaults = getConfigDefaults();
9816
9370
  const previousSlackReactionConfig = getSlackReactionConfig();
9817
9371
  const previousSandboxResources = getSandboxResourceConfig();
9372
+ const previousExperimentalFeatures = getExperimentalFeatures();
9818
9373
  const previousDashboardLinkOptions = setDashboardConversationLinkOptions(dashboard);
9819
9374
  let pluginRoutes = [];
9820
9375
  let pluginApiRoutes = [];
@@ -9850,6 +9405,7 @@ async function createApp(options) {
9850
9405
  options?.sandbox?.egressTracePropagationDomains
9851
9406
  );
9852
9407
  setSandboxResourceConfig(options?.sandbox);
9408
+ setExperimentalFeatures(options?.experimental);
9853
9409
  setConfigDefaults(options?.configDefaults);
9854
9410
  warnUnregisteredConfigDefaults(options?.configDefaults);
9855
9411
  if (options?.slack) {
@@ -9873,12 +9429,15 @@ async function createApp(options) {
9873
9429
  setConfigDefaults(previousConfigDefaults);
9874
9430
  setSlackReactionConfig(previousSlackReactionConfig);
9875
9431
  setSandboxResourceConfig(previousSandboxResources);
9432
+ setExperimentalFeatures(previousExperimentalFeatures);
9876
9433
  setDashboardConversationLinkOptions(previousDashboardLinkOptions);
9877
9434
  throw error;
9878
9435
  }
9879
9436
  const waitUntil = options?.waitUntil ?? await defaultWaitUntil();
9880
9437
  const tracePropagation = { domains: sandboxEgressTracePropagationDomains };
9438
+ const conversationWorkQueue = getVercelConversationWorkQueue();
9881
9439
  const agentRunner = createAgentRunner(executeAgentRun, {
9440
+ bindSpawnAgent: (request) => bindSpawnAgent(request, { queue: conversationWorkQueue }),
9882
9441
  tracePropagation
9883
9442
  });
9884
9443
  const runtimeServiceOverrides = {