@sentry/junior 0.108.0 → 0.110.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 (109) hide show
  1. package/dist/{agent-hooks-NU5HK3PS.js → agent-hooks-ELT4ODFF.js} +12 -13
  2. package/dist/api/conversations/access.d.ts +9 -0
  3. package/dist/api/conversations/aggregate.d.ts +12 -1
  4. package/dist/api/conversations/archive.d.ts +3 -5
  5. package/dist/api/conversations/detail.d.ts +156 -7
  6. package/dist/api/conversations/events.d.ts +2 -2
  7. package/dist/api/conversations/list.d.ts +57 -6
  8. package/dist/api/conversations/projection.d.ts +6 -7
  9. package/dist/api/conversations/reporting.d.ts +10 -4
  10. package/dist/api/conversations/routes.d.ts +2 -1
  11. package/dist/api/conversations/shared.d.ts +1 -1
  12. package/dist/api/conversations/stats.d.ts +37 -6
  13. package/dist/api/conversations/stats.query.d.ts +1 -1
  14. package/dist/api/conversations/usage.d.ts +9 -0
  15. package/dist/api/http.d.ts +7 -0
  16. package/dist/api/locations/detail.d.ts +102 -6
  17. package/dist/api/locations/list.d.ts +43 -5
  18. package/dist/api/locations/query.d.ts +4 -2
  19. package/dist/api/locations/routes.d.ts +2 -1
  20. package/dist/api/people/list.d.ts +28 -6
  21. package/dist/api/people/list.query.d.ts +1 -1
  22. package/dist/api/people/profile.d.ts +96 -7
  23. package/dist/api/people/profile.query.d.ts +4 -2
  24. package/dist/api/people/routes.d.ts +2 -1
  25. package/dist/api/people/shared.d.ts +1219 -2
  26. package/dist/api/route.d.ts +18 -4
  27. package/dist/api/schema/common.d.ts +6 -0
  28. package/dist/api/{conversations/schema.d.ts → schema/conversation.d.ts} +88 -9
  29. package/dist/api/{locations/schema.d.ts → schema/location.d.ts} +5 -0
  30. package/dist/api/{people/schema.d.ts → schema/person.d.ts} +37 -45
  31. package/dist/api/schema.d.ts +8 -22
  32. package/dist/api/schema.js +10 -2
  33. package/dist/api.d.ts +5 -1
  34. package/dist/api.js +838 -369
  35. package/dist/app.d.ts +1 -0
  36. package/dist/app.js +1564 -486
  37. package/dist/catalog-runtime-4GI22PXK.js +16 -0
  38. package/dist/chat/agent/resume.d.ts +11 -12
  39. package/dist/chat/conversations/history.d.ts +3 -0
  40. package/dist/chat/conversations/sql/privacy.d.ts +3 -15
  41. package/dist/chat/conversations/sql/store.d.ts +0 -7
  42. package/dist/chat/log-context.d.ts +40 -0
  43. package/dist/chat/logging.d.ts +3 -26
  44. package/dist/chat/pi/messages.d.ts +2 -2
  45. package/dist/chat/pi/sql-model-usage.d.ts +4 -1
  46. package/dist/chat/runtime/slack-resume.d.ts +2 -2
  47. package/dist/chat/services/auth-pause.d.ts +4 -0
  48. package/dist/chat/services/context-compaction.d.ts +1 -0
  49. package/dist/chat/services/turn-session-record.d.ts +0 -2
  50. package/dist/chat/slack/reply.d.ts +7 -19
  51. package/dist/chat/state/conversation.d.ts +1 -0
  52. package/dist/chat/task-execution/queue-signing.d.ts +10 -8
  53. package/dist/chat/task-execution/queue.d.ts +9 -7
  54. package/dist/chat/task-execution/slack-work.d.ts +6 -6
  55. package/dist/{chunk-UD6THJ2I.js → chunk-2T7TIEXI.js} +197 -12
  56. package/dist/{chunk-CEA3A3UA.js → chunk-2ZTTXTSZ.js} +24 -1
  57. package/dist/{chunk-XKB7LGIW.js → chunk-3S7YYHBI.js} +3 -139
  58. package/dist/chunk-3TV7OEHL.js +10 -0
  59. package/dist/{chunk-A5CO2EHL.js → chunk-3UBQI73J.js} +11 -15
  60. package/dist/{chunk-7FBGKXPE.js → chunk-42WRQLJ6.js} +10 -33
  61. package/dist/{chunk-TE4QHJH4.js → chunk-4QU44P7Q.js} +135 -595
  62. package/dist/{chunk-RT5BUHR5.js → chunk-6Y5L4BOM.js} +3 -5
  63. package/dist/{chunk-VIXFG2VC.js → chunk-C4MZ3GP5.js} +1 -2
  64. package/dist/{chunk-3RGQLX2F.js → chunk-C5XAFTHY.js} +1011 -29
  65. package/dist/{chunk-PUAMO4PI.js → chunk-FPK7XY3F.js} +139 -99
  66. package/dist/{chunk-H3QYZL7K.js → chunk-GK47NBDE.js} +28 -3
  67. package/dist/{chunk-O24R4DCK.js → chunk-IYKPU7H6.js} +94 -56
  68. package/dist/chunk-JQFJO5WH.js +477 -0
  69. package/dist/{chunk-VH6KWKG2.js → chunk-L5XQPJ7T.js} +3 -3
  70. package/dist/{chunk-2XXEKR44.js → chunk-MIAKQNYX.js} +43 -31
  71. package/dist/{chunk-KPMPQ6AA.js → chunk-RHHRKSPI.js} +249 -223
  72. package/dist/{chunk-YT72TOEK.js → chunk-SBOBTS27.js} +2 -2
  73. package/dist/{chunk-B5I5LMSP.js → chunk-W6RJZG7F.js} +5 -7
  74. package/dist/chunk-W75ZPAGG.js +44 -0
  75. package/dist/{chunk-SS67LUOK.js → chunk-WNOVRQ67.js} +6 -7
  76. package/dist/{chunk-YIPD3L6L.js → chunk-Y3H2DN6F.js} +8 -10
  77. package/dist/{chunk-DY7TRYB5.js → chunk-Y5FK3CNG.js} +1 -1
  78. package/dist/chunk-ZKIOWEFL.js +125 -0
  79. package/dist/cli/chat.js +23 -24
  80. package/dist/cli/check.js +9 -7
  81. package/dist/cli/plugins.js +16 -17
  82. package/dist/cli/snapshot-warmup.js +11 -10
  83. package/dist/cli/upgrade.js +314 -3505
  84. package/dist/db/db.d.ts +1 -1
  85. package/dist/db/schema/conversations.d.ts +17 -0
  86. package/dist/db/schema.d.ts +17 -0
  87. package/dist/{db-AMRBAT5D.js → db-Y6W6CWWW.js} +8 -7
  88. package/dist/instrumentation.js +4 -3
  89. package/dist/nitro.js +4 -4
  90. package/dist/reporting-schema.d.ts +46 -0
  91. package/dist/{runner-TQH5GAJ4.js → runner-BBFV5GLR.js} +17 -21
  92. package/dist/{skills-ITNHBARB.js → skills-AR4AF32L.js} +8 -6
  93. package/dist/{validation-AGQ46ZGV.js → validation-FBIEPTC2.js} +8 -6
  94. package/dist/version.d.ts +2 -0
  95. package/dist/version.js +7 -0
  96. package/migrations/0006_drop_legacy_conversation_messages.sql +1 -0
  97. package/migrations/0007_thick_sugar_man.sql +23 -0
  98. package/migrations/README.md +12 -9
  99. package/migrations/meta/0006_snapshot.json +948 -0
  100. package/migrations/meta/0007_snapshot.json +1013 -0
  101. package/migrations/meta/_journal.json +14 -0
  102. package/package.json +9 -5
  103. package/dist/catalog-runtime-FOFNPR3P.js +0 -14
  104. package/dist/chunk-AHJR2IFF.js +0 -115
  105. package/dist/chunk-B7PUBQQM.js +0 -53
  106. package/dist/chunk-G3E7SCME.js +0 -28
  107. package/dist/chunk-J3B3FPP2.js +0 -1061
  108. package/dist/chunk-OB42YVAE.js +0 -16
  109. package/dist/chunk-TWINAEZQ.js +0 -1283
package/dist/app.js CHANGED
@@ -6,45 +6,23 @@ import {
6
6
  processPluginTask,
7
7
  requireTurnFailureEventId,
8
8
  scheduleSessionCompletedPluginTasks
9
- } from "./chunk-A5CO2EHL.js";
10
- import {
11
- CONVERSATION_WORK_CHECK_IN_INTERVAL_MS,
12
- CONVERSATION_WORK_STALE_ENQUEUE_MS,
13
- ackMessages,
14
- appendInboundMessage,
15
- checkInConversationWork,
16
- clearConsumedConversationWake,
17
- clearExpiredConversationLease,
18
- completeConversationWork,
19
- deadLetterAttempt,
20
- drainConversationMailbox,
21
- getConversation,
22
- getConversationWorkState,
23
- isFinalAttempt,
24
- isInvalidConversationRecordError,
25
- listActiveConversationIds,
26
- markConversationWorkEnqueued,
27
- recordAttemptFailure,
28
- releaseConversationWork,
29
- removeActiveConversation,
30
- requestConversationContinuation,
31
- requestConversationWork,
32
- startConversationWork
33
- } from "./chunk-TWINAEZQ.js";
9
+ } from "./chunk-3UBQI73J.js";
34
10
  import {
35
11
  GET,
36
12
  buildSentryConversationUrl,
37
13
  conversationVisibilityFromSlackChannelType,
38
- resolveRootVisibility,
39
14
  resolveSlackChannelTypeFromMessage,
40
15
  resolveSlackConversationContext
41
- } from "./chunk-XKB7LGIW.js";
42
- import "./chunk-CEA3A3UA.js";
16
+ } from "./chunk-3S7YYHBI.js";
17
+ import "./chunk-2ZTTXTSZ.js";
43
18
  import {
19
+ ConversationQueueMessageRejectedError,
20
+ conversationQueueMessageSchema,
44
21
  getVercelConversationWorkQueue,
22
+ isConversationQueueMessageRejectedError,
45
23
  resolveConversationWorkQueueTopic,
46
24
  verifyConversationQueueMessage
47
- } from "./chunk-2XXEKR44.js";
25
+ } from "./chunk-MIAKQNYX.js";
48
26
  import {
49
27
  PLUGIN_TASK_QUEUE_TOPIC,
50
28
  createVercelQueueClient,
@@ -53,7 +31,7 @@ import {
53
31
  import {
54
32
  validatePluginEgressCredentialHooks,
55
33
  validatePluginRegistrations
56
- } from "./chunk-DY7TRYB5.js";
34
+ } from "./chunk-Y5FK3CNG.js";
57
35
  import {
58
36
  defineJuniorPlugins,
59
37
  pluginCatalogConfigFromEnv,
@@ -106,21 +84,28 @@ import {
106
84
  splitSlackReplyText,
107
85
  startOAuthFlow,
108
86
  truncateStatusText
109
- } from "./chunk-KPMPQ6AA.js";
87
+ } from "./chunk-RHHRKSPI.js";
110
88
  import {
111
89
  ConversationTurnBoundaryError,
112
90
  CooperativeTurnYieldError,
91
+ JUNIOR_THREAD_STATE_TTL_MS,
113
92
  TurnInputCommitLostError,
114
93
  TurnInputDeferredError,
115
94
  TurnSliceLimitExceededError,
95
+ abandonAgentTurnSessionRecord,
116
96
  buildConversationContext,
117
97
  buildDeterministicAssistantMessageId,
118
98
  buildDeterministicTurnId,
119
99
  buildTurnLimitResponse,
120
100
  clearPendingAuth,
121
101
  coerceThreadArtifactsState,
102
+ coerceThreadConversationState,
103
+ commitMessages,
122
104
  completeDeliveredTurn,
123
105
  createConversationMemoryService,
106
+ failAgentTurnSessionRecord,
107
+ getAgentTurnSessionRecord,
108
+ getAgentTurnSessionRecordForResume,
124
109
  getChannelConfigurationService,
125
110
  getChannelConfigurationServiceById,
126
111
  getConversationMessageSlackTs,
@@ -135,6 +120,9 @@ import {
135
120
  isPendingAuthLatestRequest,
136
121
  isTurnInputCommitLostError,
137
122
  isTurnInputDeferredError,
123
+ listAgentTurnSessionSummariesForConversation,
124
+ loadConversationProjection,
125
+ loadProjection,
138
126
  markConversationMessage,
139
127
  markTurnClosed,
140
128
  markTurnFailed,
@@ -148,33 +136,23 @@ import {
148
136
  persistThreadStateById,
149
137
  persistWithRetry,
150
138
  persistYieldSessionRecord,
139
+ recordAgentTurnSessionSummary,
140
+ recordAuthorizationCompleted,
151
141
  recordDeliveredAssistantMessage,
152
142
  startActiveTurn,
153
143
  turnHasReply,
154
144
  updateConversationStats,
155
145
  upsertConversationMessage
156
- } from "./chunk-3RGQLX2F.js";
157
- import {
158
- JUNIOR_THREAD_STATE_TTL_MS,
159
- abandonAgentTurnSessionRecord,
160
- coerceThreadConversationState,
161
- commitMessages,
162
- failAgentTurnSessionRecord,
163
- getAgentTurnSessionRecord,
164
- getAgentTurnSessionRecordForResume,
165
- listAgentTurnSessionSummariesForConversation,
166
- loadConversationProjection,
167
- loadProjection,
168
- recordAgentTurnSessionSummary,
169
- recordAuthorizationCompleted
170
- } from "./chunk-J3B3FPP2.js";
171
- import "./chunk-B5I5LMSP.js";
146
+ } from "./chunk-C5XAFTHY.js";
147
+ import "./chunk-W6RJZG7F.js";
172
148
  import "./chunk-MU6HHZEN.js";
173
149
  import "./chunk-PDO5BLNM.js";
174
150
  import "./chunk-UIE3R5XU.js";
175
151
  import {
176
152
  bindScheduledTaskCredentialSubject,
177
153
  bindSlackDirectCredentialSubject,
154
+ createPluginLogger,
155
+ createPluginState,
178
156
  getDashboardConversationLink,
179
157
  getPluginApiRoutes,
180
158
  getPluginRoutes,
@@ -185,16 +163,12 @@ import {
185
163
  validatePlugins,
186
164
  verifyScheduledTaskCredentialSubject,
187
165
  verifySlackDirectCredentialSubject
188
- } from "./chunk-UD6THJ2I.js";
189
- import {
190
- createPluginLogger,
191
- createPluginState
192
- } from "./chunk-AHJR2IFF.js";
193
- import "./chunk-G3E7SCME.js";
166
+ } from "./chunk-2T7TIEXI.js";
194
167
  import {
195
168
  acquireActiveLock,
169
+ getDefaultRedisStateAdapterFor,
196
170
  getStateAdapter
197
- } from "./chunk-H3QYZL7K.js";
171
+ } from "./chunk-GK47NBDE.js";
198
172
  import {
199
173
  SlackActionError,
200
174
  downloadPrivateSlackFile,
@@ -205,18 +179,25 @@ import {
205
179
  normalizeSlackConversationId,
206
180
  runWithSlackInstallationToken,
207
181
  runWithWorkspaceTeamId
208
- } from "./chunk-VH6KWKG2.js";
182
+ } from "./chunk-L5XQPJ7T.js";
209
183
  import {
210
184
  contextProvenance,
185
+ createSlackDestination,
186
+ destinationKey,
211
187
  getConversationEventStore,
212
188
  getConversationStore,
213
189
  getDb,
214
190
  getSqlExecutor,
215
191
  instructionProvenanceFor,
216
- juniorConversationEvents,
217
- juniorConversations,
192
+ parseDestination,
193
+ requireSlackDestination,
194
+ sameDestination,
218
195
  withConversationEventLock
219
- } from "./chunk-TE4QHJH4.js";
196
+ } from "./chunk-4QU44P7Q.js";
197
+ import {
198
+ juniorConversationEvents,
199
+ juniorConversations
200
+ } from "./chunk-JQFJO5WH.js";
220
201
  import {
221
202
  sleep
222
203
  } from "./chunk-4ZNGQH7C.js";
@@ -240,7 +221,7 @@ import {
240
221
  standardModelId,
241
222
  stripRuntimeTurnContext,
242
223
  trimTrailingAssistantMessages
243
- } from "./chunk-SS67LUOK.js";
224
+ } from "./chunk-WNOVRQ67.js";
244
225
  import {
245
226
  juniorDestinations
246
227
  } from "./chunk-VFUK3X5B.js";
@@ -249,32 +230,28 @@ import {
249
230
  readSlackRawMessageContext,
250
231
  resolveSlackChannelIdFromMessage,
251
232
  resolveSlackChannelIdFromThreadId
252
- } from "./chunk-RT5BUHR5.js";
253
- import {
254
- createSlackDestination,
255
- destinationKey,
256
- parseDestination,
257
- requireSlackDestination,
258
- sameDestination
259
- } from "./chunk-B7PUBQQM.js";
233
+ } from "./chunk-6Y5L4BOM.js";
260
234
  import {
261
235
  discoverSkills
262
- } from "./chunk-YT72TOEK.js";
236
+ } from "./chunk-SBOBTS27.js";
237
+ import {
238
+ pluginCatalogRuntime
239
+ } from "./chunk-3TV7OEHL.js";
263
240
  import {
264
241
  buildOAuthTokenRequest,
265
242
  credentialSubjectSchema,
266
243
  hasRequiredOAuthScope,
267
- parseOAuthTokenResponse,
268
- pluginCatalogRuntime
269
- } from "./chunk-YIPD3L6L.js";
244
+ parseOAuthTokenResponse
245
+ } from "./chunk-Y3H2DN6F.js";
270
246
  import {
271
247
  createActor,
272
248
  createSlackActor,
273
249
  createSlackResumeActor,
274
250
  isActorUserId,
275
251
  isUserActor,
276
- parseActorUserId
277
- } from "./chunk-VIXFG2VC.js";
252
+ parseActorUserId,
253
+ parseStoredSlackActor
254
+ } from "./chunk-C4MZ3GP5.js";
278
255
  import {
279
256
  buildTurnFailureResponse,
280
257
  createChatSdkLogger,
@@ -290,22 +267,27 @@ import {
290
267
  setTags,
291
268
  withContext,
292
269
  withSpan
293
- } from "./chunk-O24R4DCK.js";
270
+ } from "./chunk-IYKPU7H6.js";
294
271
  import {
272
+ isRecord,
273
+ parseSlackChannelId,
274
+ parseSlackMessageTs,
275
+ toOptionalNumber,
295
276
  toOptionalString
296
- } from "./chunk-OB42YVAE.js";
277
+ } from "./chunk-42WRQLJ6.js";
297
278
  import {
298
279
  continueTrace
299
280
  } from "./chunk-6GWA276C.js";
300
281
  import "./chunk-B3IT5END.js";
301
- import {
302
- JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE,
303
- parseSlackChannelId,
304
- parseSlackMessageTs
305
- } from "./chunk-7FBGKXPE.js";
306
282
  import {
307
283
  homeDir
308
284
  } from "./chunk-3V7OFJZC.js";
285
+ import {
286
+ JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE
287
+ } from "./chunk-W75ZPAGG.js";
288
+ import {
289
+ JUNIOR_VERSION
290
+ } from "./chunk-ZKIOWEFL.js";
309
291
  import "./chunk-MLKGABMK.js";
310
292
 
311
293
  // src/app.ts
@@ -353,53 +335,26 @@ function buildSlackReplyBlocks(text, footer) {
353
335
  }
354
336
 
355
337
  // src/chat/slack/reply.ts
356
- function planSlackAssistantMessagePosts(text) {
357
- return splitSlackReplyText(text).map((chunk, index) => ({
358
- text: chunk,
359
- stage: index === 0 ? "thread_reply" : "thread_reply_continuation"
360
- }));
361
- }
362
- function findLastTextPostIndex(posts) {
363
- for (let index = posts.length - 1; index >= 0; index -= 1) {
364
- if (posts[index]?.text.trim().length) {
365
- return index;
366
- }
367
- }
368
- return -1;
369
- }
370
- async function postSlackApiReplyPosts(args) {
371
- const lastTextPostIndex = findLastTextPostIndex(args.posts);
372
- let lastPostedMessageTs;
373
- for (const [index, post] of args.posts.entries()) {
374
- const hasVisibleDelivery = post.text.trim().length > 0;
375
- if (hasVisibleDelivery) {
376
- await args.beforePost?.();
377
- }
378
- let messageTs;
379
- try {
380
- if (post.text.trim().length > 0) {
381
- const footer = index === lastTextPostIndex ? args.footer : void 0;
382
- const blocks = buildSlackReplyBlocks(post.text, footer);
383
- const response = await postSlackMessage({
384
- channelId: args.channelId,
385
- threadTs: args.threadTs,
386
- text: post.text,
387
- ...blocks ? { blocks } : {}
388
- });
389
- messageTs = response.ts;
390
- lastPostedMessageTs = response.ts;
391
- }
392
- continue;
393
- } catch (error) {
394
- await args.onPostError?.({
395
- error,
396
- messageTs,
397
- stage: post.stage
398
- });
399
- throw error;
400
- }
338
+ async function sendSlackReply(args) {
339
+ const chunks = splitSlackReplyText(args.text);
340
+ const footer = buildSlackReplyFooter({
341
+ conversationId: args.conversationId
342
+ });
343
+ let lastMessageTs;
344
+ for (const [index, text] of chunks.entries()) {
345
+ const blocks = buildSlackReplyBlocks(
346
+ text,
347
+ index === chunks.length - 1 ? footer : void 0
348
+ );
349
+ const response = await postSlackMessage({
350
+ channelId: args.channelId,
351
+ threadTs: args.threadTs,
352
+ text,
353
+ ...blocks ? { blocks } : {}
354
+ });
355
+ lastMessageTs = response.ts;
401
356
  }
402
- return lastPostedMessageTs;
357
+ return lastMessageTs;
403
358
  }
404
359
 
405
360
  // src/chat/slack/errors.ts
@@ -927,8 +882,8 @@ async function runAgentDispatchSlice(callback, deps) {
927
882
  const turnId = getDispatchTurnId(dispatch.id);
928
883
  const logContext = {
929
884
  conversationId,
930
- slackThreadId: conversationId,
931
- slackChannelId: dispatch.destination.channelId,
885
+ messageConversationId: conversationId,
886
+ destinationName: dispatch.destination.channelId,
932
887
  runId: dispatch.id,
933
888
  actorType: dispatch.actor.platform,
934
889
  actorId: dispatch.actor.name,
@@ -1018,15 +973,15 @@ async function runAgentDispatchSlice(callback, deps) {
1018
973
  let resultMessageTs;
1019
974
  let assistantMessageDelivered = turnHasReply(conversation, turnId);
1020
975
  const deliverAssistantMessage = async (assistantMessage) => {
1021
- const posts = planSlackAssistantMessagePosts(assistantMessage.text);
1022
- if (posts.length === 0) {
976
+ if (!assistantMessage.text.trim()) {
1023
977
  return;
1024
978
  }
1025
979
  failureCode = "delivery_failed";
1026
980
  try {
1027
- resultMessageTs = await postSlackApiReplyPosts({
981
+ resultMessageTs = await sendSlackReply({
1028
982
  channelId: dispatch.destination.channelId,
1029
- posts
983
+ conversationId,
984
+ text: assistantMessage.text
1030
985
  });
1031
986
  } catch (error) {
1032
987
  if (isRetryableSlackPostError(error)) {
@@ -1222,145 +1177,1266 @@ async function runAgentDispatchSlice(callback, deps) {
1222
1177
  destinationVisibility: dispatch.destinationVisibility,
1223
1178
  source: dispatch.source,
1224
1179
  actor: dispatch.actor,
1225
- surface: "api",
1226
- logContext: {
1227
- threadId: conversationId,
1228
- channelId: dispatch.destination.channelId,
1229
- runId: dispatch.id,
1230
- assistantUserName: botConfig.userName
1231
- }
1180
+ surface: "api"
1181
+ });
1182
+ }
1183
+ if (statePersisted) {
1184
+ if (failure) {
1185
+ await turnLifecycle.fail({
1186
+ conversationId,
1187
+ turnId,
1188
+ createdAtMs: Date.now(),
1189
+ failureCode: "model_execution_failed",
1190
+ ...modelFailureEventId ? { eventId: modelFailureEventId } : {}
1191
+ });
1192
+ } else {
1193
+ await turnLifecycle.complete({
1194
+ conversationId,
1195
+ turnId,
1196
+ createdAtMs: Date.now(),
1197
+ outcome: assistantMessageDelivered ? "success" : "no_reply"
1198
+ });
1199
+ }
1200
+ lifecycleTerminalized = true;
1201
+ }
1202
+ dispatch = await markDispatch({
1203
+ dispatch,
1204
+ status: failure ? "failed" : "completed",
1205
+ ...failure ? { errorMessage: failure } : {},
1206
+ resultMessageTs
1207
+ });
1208
+ if (!failure) {
1209
+ try {
1210
+ await scheduleCompletedTasks({
1211
+ conversationId,
1212
+ sessionId: turnId
1213
+ });
1214
+ } catch (error) {
1215
+ logException(
1216
+ error,
1217
+ "plugin_session_completed_task_schedule_failed",
1218
+ logContext,
1219
+ {},
1220
+ "Plugin session.completed task scheduling failed"
1221
+ );
1222
+ }
1223
+ }
1224
+ } catch (error) {
1225
+ if (error instanceof AuthorizationFlowDisabledError) {
1226
+ if (lifecycleStarted && !lifecycleTerminalized) {
1227
+ await turnLifecycle.fail({
1228
+ conversationId,
1229
+ turnId,
1230
+ createdAtMs: Date.now(),
1231
+ failureCode: "agent_run_failed"
1232
+ });
1233
+ lifecycleTerminalized = true;
1234
+ }
1235
+ await markDispatch({
1236
+ dispatch,
1237
+ status: "blocked",
1238
+ errorMessage: `Dispatch requires ${error.provider} authorization.`
1239
+ });
1240
+ return;
1241
+ }
1242
+ if (error instanceof PluginCredentialFailureError) {
1243
+ if (lifecycleStarted && !lifecycleTerminalized) {
1244
+ await turnLifecycle.fail({
1245
+ conversationId,
1246
+ turnId,
1247
+ createdAtMs: Date.now(),
1248
+ failureCode: "agent_run_failed"
1249
+ });
1250
+ lifecycleTerminalized = true;
1251
+ }
1252
+ await markDispatch({
1253
+ dispatch,
1254
+ status: "blocked",
1255
+ errorMessage: error.message
1256
+ });
1257
+ return;
1258
+ }
1259
+ const eventId = logException(
1260
+ error,
1261
+ "agent_dispatch_run_failed",
1262
+ {
1263
+ ...logContext,
1264
+ modelId: standardModelId(botConfig)
1265
+ },
1266
+ {},
1267
+ "Agent dispatch failed"
1268
+ );
1269
+ if (lifecycleStarted && !lifecycleTerminalized) {
1270
+ await turnLifecycle.fail({
1271
+ conversationId,
1272
+ turnId,
1273
+ createdAtMs: Date.now(),
1274
+ failureCode,
1275
+ ...eventId ? { eventId } : {}
1232
1276
  });
1277
+ lifecycleTerminalized = true;
1278
+ }
1279
+ await markDispatch({
1280
+ dispatch,
1281
+ status: "failed",
1282
+ errorMessage: error instanceof Error ? error.message : String(error)
1283
+ });
1284
+ } finally {
1285
+ await stateAdapter.releaseLock(destinationLock);
1286
+ }
1287
+ }
1288
+
1289
+ // src/handlers/agent-dispatch.ts
1290
+ async function POST(request, waitUntil, options) {
1291
+ const payload = await verifyDispatchCallbackRequest(request);
1292
+ if (!payload) {
1293
+ return new Response("Unauthorized", { status: 401 });
1294
+ }
1295
+ waitUntil(
1296
+ () => runAgentDispatchSlice(payload, {
1297
+ agentRunner: options.agentRunner
1298
+ }).catch((error) => {
1299
+ logException(
1300
+ error,
1301
+ "agent_dispatch_handler_failed",
1302
+ {},
1303
+ { "app.dispatch.id": payload.id },
1304
+ "Agent dispatch handler failed"
1305
+ );
1306
+ })
1307
+ );
1308
+ return new Response("Accepted", { status: 202 });
1309
+ }
1310
+
1311
+ // src/handlers/heartbeat.ts
1312
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
1313
+
1314
+ // src/chat/task-execution/state.ts
1315
+ import { randomUUID } from "crypto";
1316
+ var CONVERSATION_PREFIX = "junior:conversation";
1317
+ var CONVERSATION_SCHEMA_VERSION = 1;
1318
+ var CONVERSATION_ACTIVITY_INDEX_MAX_LENGTH = 1e4;
1319
+ var CONVERSATION_INDEX_LOCK_TTL_MS = 1e4;
1320
+ var CONVERSATION_INDEX_LOCK_WAIT_MS = 2e3;
1321
+ var CONVERSATION_INDEX_LOCK_RETRY_MS = 25;
1322
+ var CONVERSATION_MUTATION_LOCK_TTL_MS = 1e4;
1323
+ var CONVERSATION_MUTATION_WAIT_MS = 1e4;
1324
+ var CONVERSATION_MUTATION_RETRY_MS = 25;
1325
+ var InvalidConversationRecordError = class extends Error {
1326
+ constructor(conversationId) {
1327
+ super(`Conversation record is invalid for ${conversationId}`);
1328
+ this.name = "InvalidConversationRecordError";
1329
+ }
1330
+ };
1331
+ function isInvalidConversationRecordError(error) {
1332
+ return error instanceof InvalidConversationRecordError;
1333
+ }
1334
+ var ConversationMutationFencedError = class extends Error {
1335
+ constructor(conversationId) {
1336
+ super(
1337
+ `Conversation mutation lock was lost before write for ${conversationId}`
1338
+ );
1339
+ this.name = "ConversationMutationFencedError";
1340
+ }
1341
+ };
1342
+ var CONVERSATION_BY_ACTIVITY_INDEX_KEY = `${CONVERSATION_PREFIX}:by-activity`;
1343
+ var CONVERSATION_ACTIVE_INDEX_KEY = `${CONVERSATION_PREFIX}:active`;
1344
+ var CONVERSATION_WORK_LEASE_TTL_MS = 9e4;
1345
+ var CONVERSATION_WORK_CHECK_IN_INTERVAL_MS = 15e3;
1346
+ var CONVERSATION_WORK_STALE_ENQUEUE_MS = 6e4;
1347
+ var CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS = 5;
1348
+ function conversationKey(conversationId) {
1349
+ return `${CONVERSATION_PREFIX}:${conversationId}`;
1350
+ }
1351
+ function indexLockKey(indexKey) {
1352
+ return `${indexKey}:lock`;
1353
+ }
1354
+ function mutationLockKey(conversationId) {
1355
+ return `${CONVERSATION_PREFIX}:mutation:${conversationId}`;
1356
+ }
1357
+ function now() {
1358
+ return Date.now();
1359
+ }
1360
+ function compareMessages(left, right) {
1361
+ return left.createdAtMs - right.createdAtMs || left.receivedAtMs - right.receivedAtMs || left.inboundMessageId.localeCompare(right.inboundMessageId);
1362
+ }
1363
+ function inputHasAttachments(input) {
1364
+ return Array.isArray(input.attachments) && input.attachments.length > 0;
1365
+ }
1366
+ function upgradedPendingMessage(stored, duplicate) {
1367
+ if (!inputHasAttachments(duplicate.input) || inputHasAttachments(stored.input)) {
1368
+ return stored;
1369
+ }
1370
+ return { ...stored, input: duplicate.input };
1371
+ }
1372
+ function compareIndexDescending(left, right) {
1373
+ return right.score - left.score || right.conversationId.localeCompare(left.conversationId);
1374
+ }
1375
+ function compareIndexAscending(left, right) {
1376
+ return left.score - right.score || left.conversationId.localeCompare(right.conversationId);
1377
+ }
1378
+ function uniqueStrings(values) {
1379
+ return [...new Set(values)];
1380
+ }
1381
+ function normalizeSource(value) {
1382
+ if (value === "api" || value === "internal" || value === "local" || value === "plugin" || value === "resource_event" || value === "scheduler" || value === "slack") {
1383
+ return value;
1384
+ }
1385
+ return void 0;
1386
+ }
1387
+ function normalizeExecutionStatus(value) {
1388
+ if (value === "awaiting_resume" || value === "failed" || value === "idle" || value === "pending" || value === "running") {
1389
+ return value;
1390
+ }
1391
+ return void 0;
1392
+ }
1393
+ function normalizeMetadata2(value) {
1394
+ if (!isRecord(value)) {
1395
+ return void 0;
1396
+ }
1397
+ return value;
1398
+ }
1399
+ function normalizeInput(value) {
1400
+ if (!isRecord(value)) {
1401
+ return void 0;
1402
+ }
1403
+ const text = typeof value.text === "string" ? value.text : void 0;
1404
+ const attachments = Array.isArray(value.attachments) ? [...value.attachments] : void 0;
1405
+ if (text === void 0 || !text.trim() && !attachments?.length) {
1406
+ return void 0;
1407
+ }
1408
+ return {
1409
+ text,
1410
+ authorId: toOptionalString(value.authorId),
1411
+ attachments,
1412
+ metadata: normalizeMetadata2(value.metadata)
1413
+ };
1414
+ }
1415
+ function normalizeMessage(value) {
1416
+ if (!isRecord(value)) {
1417
+ return void 0;
1418
+ }
1419
+ const conversationId = toOptionalString(value.conversationId);
1420
+ const inboundMessageId = toOptionalString(value.inboundMessageId);
1421
+ const source = normalizeSource(value.source);
1422
+ const destination = parseDestination(value.destination);
1423
+ const createdAtMs = toOptionalNumber(value.createdAtMs);
1424
+ const receivedAtMs = toOptionalNumber(value.receivedAtMs);
1425
+ const input = normalizeInput(value.input);
1426
+ if (!conversationId || !destination || !inboundMessageId || !source || typeof createdAtMs !== "number" || typeof receivedAtMs !== "number" || !input) {
1427
+ return void 0;
1428
+ }
1429
+ return {
1430
+ conversationId,
1431
+ destination,
1432
+ inboundMessageId,
1433
+ source,
1434
+ createdAtMs,
1435
+ receivedAtMs,
1436
+ input,
1437
+ injectedAtMs: toOptionalNumber(value.injectedAtMs),
1438
+ attemptCount: toOptionalNumber(value.attemptCount)
1439
+ };
1440
+ }
1441
+ function isFinalAttempt(message) {
1442
+ return (message.attemptCount ?? 0) >= CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS - 1;
1443
+ }
1444
+ function normalizeActor(value) {
1445
+ return parseStoredSlackActor(value);
1446
+ }
1447
+ function normalizeLease(value) {
1448
+ if (!isRecord(value)) {
1449
+ return void 0;
1450
+ }
1451
+ const token = toOptionalString(value.token);
1452
+ const acquiredAtMs = toOptionalNumber(value.acquiredAtMs);
1453
+ const lastCheckInAtMs = toOptionalNumber(value.lastCheckInAtMs);
1454
+ const expiresAtMs = toOptionalNumber(value.expiresAtMs);
1455
+ if (!token || typeof acquiredAtMs !== "number" || typeof lastCheckInAtMs !== "number" || typeof expiresAtMs !== "number") {
1456
+ return void 0;
1457
+ }
1458
+ return {
1459
+ token,
1460
+ acquiredAtMs,
1461
+ lastCheckInAtMs,
1462
+ expiresAtMs
1463
+ };
1464
+ }
1465
+ function normalizeExecution(conversationId, value) {
1466
+ if (!isRecord(value)) {
1467
+ return void 0;
1468
+ }
1469
+ const status = normalizeExecutionStatus(value.status);
1470
+ if (!status) {
1471
+ return void 0;
1472
+ }
1473
+ const pendingMessages2 = Array.isArray(value.pendingMessages) ? value.pendingMessages.map(normalizeMessage).filter((message) => Boolean(message)).filter((message) => message.conversationId === conversationId).filter((message) => message.injectedAtMs === void 0).sort(compareMessages) : [];
1474
+ const inboundMessageIds = Array.isArray(value.inboundMessageIds) ? uniqueStrings(
1475
+ value.inboundMessageIds.map((id) => typeof id === "string" ? id : void 0).filter((id) => Boolean(id))
1476
+ ) : [];
1477
+ const lease = normalizeLease(value.lease);
1478
+ const normalizedStatus = status === "idle" && lease ? "running" : status === "idle" && pendingMessages2.length > 0 ? "pending" : status;
1479
+ return {
1480
+ status: normalizedStatus,
1481
+ inboundMessageIds: uniqueStrings([
1482
+ ...inboundMessageIds,
1483
+ ...pendingMessages2.map((message) => message.inboundMessageId)
1484
+ ]),
1485
+ pendingCount: pendingMessages2.length,
1486
+ pendingMessages: pendingMessages2,
1487
+ lease,
1488
+ lastCheckpointAtMs: toOptionalNumber(value.lastCheckpointAtMs),
1489
+ lastEnqueuedAtMs: toOptionalNumber(value.lastEnqueuedAtMs),
1490
+ runId: toOptionalString(value.runId),
1491
+ updatedAtMs: toOptionalNumber(value.updatedAtMs)
1492
+ };
1493
+ }
1494
+ function normalizeConversation(conversationId, value) {
1495
+ if (!isRecord(value) || value.schemaVersion !== CONVERSATION_SCHEMA_VERSION) {
1496
+ return void 0;
1497
+ }
1498
+ const storedConversationId = toOptionalString(value.conversationId);
1499
+ const createdAtMs = toOptionalNumber(value.createdAtMs);
1500
+ const lastActivityAtMs = toOptionalNumber(value.lastActivityAtMs);
1501
+ const updatedAtMs = toOptionalNumber(value.updatedAtMs);
1502
+ const execution = normalizeExecution(conversationId, value.execution);
1503
+ const destination = value.destination === void 0 ? void 0 : parseDestination(value.destination);
1504
+ if (storedConversationId !== conversationId || typeof createdAtMs !== "number" || typeof lastActivityAtMs !== "number" || typeof updatedAtMs !== "number" || !execution || value.destination !== void 0 && !destination) {
1505
+ return void 0;
1506
+ }
1507
+ if (execution.pendingMessages.length > 0 && (!destination || execution.pendingMessages.some(
1508
+ (message) => !sameDestination(message.destination, destination)
1509
+ ))) {
1510
+ return void 0;
1511
+ }
1512
+ return {
1513
+ schemaVersion: CONVERSATION_SCHEMA_VERSION,
1514
+ conversationId,
1515
+ createdAtMs,
1516
+ lastActivityAtMs,
1517
+ updatedAtMs,
1518
+ execution,
1519
+ ...destination ? { destination } : {},
1520
+ ...toOptionalString(value.title) ? { title: toOptionalString(value.title) } : {},
1521
+ ...toOptionalString(value.channelName) ? { channelName: toOptionalString(value.channelName) } : {},
1522
+ ...normalizeActor(value.actor) ? { actor: normalizeActor(value.actor) } : {},
1523
+ ...normalizeSource(value.source) ? { source: normalizeSource(value.source) } : {}
1524
+ };
1525
+ }
1526
+ function emptyConversation(args) {
1527
+ return {
1528
+ schemaVersion: CONVERSATION_SCHEMA_VERSION,
1529
+ conversationId: args.conversationId,
1530
+ createdAtMs: args.nowMs,
1531
+ lastActivityAtMs: args.nowMs,
1532
+ updatedAtMs: args.nowMs,
1533
+ ...args.destination ? { destination: args.destination } : {},
1534
+ ...args.source ? { source: args.source } : {},
1535
+ execution: {
1536
+ status: "idle",
1537
+ inboundMessageIds: [],
1538
+ pendingCount: 0,
1539
+ pendingMessages: [],
1540
+ updatedAtMs: args.nowMs
1541
+ }
1542
+ };
1543
+ }
1544
+ function isLeaseActive(lease, nowMs) {
1545
+ return Boolean(lease && lease.expiresAtMs > nowMs);
1546
+ }
1547
+ function pendingMessages(conversation) {
1548
+ return [...conversation.execution.pendingMessages].sort(compareMessages);
1549
+ }
1550
+ function isRunnableStatus(status) {
1551
+ return status !== "failed" && status !== "idle";
1552
+ }
1553
+ function hasRunnableWork(conversation) {
1554
+ return isRunnableStatus(conversation.execution.status) || pendingMessages(conversation).length > 0;
1555
+ }
1556
+ function executionWithPendingMessages(execution, pending) {
1557
+ const pendingMessages2 = [...pending].sort(compareMessages);
1558
+ const status = execution.status === "idle" && execution.lease ? "running" : execution.status === "idle" && pendingMessages2.length > 0 ? "pending" : execution.status;
1559
+ return {
1560
+ ...execution,
1561
+ status,
1562
+ inboundMessageIds: uniqueStrings([
1563
+ ...execution.inboundMessageIds,
1564
+ ...pendingMessages2.map((message) => message.inboundMessageId)
1565
+ ]),
1566
+ pendingMessages: pendingMessages2,
1567
+ pendingCount: pendingMessages2.length
1568
+ };
1569
+ }
1570
+ function withExecutionUpdate(conversation, execution, nowMs) {
1571
+ return {
1572
+ ...conversation,
1573
+ updatedAtMs: nowMs,
1574
+ execution: {
1575
+ ...executionWithPendingMessages(execution, execution.pendingMessages),
1576
+ updatedAtMs: nowMs
1577
+ }
1578
+ };
1579
+ }
1580
+ async function getConnectedState(stateAdapter) {
1581
+ const state = stateAdapter ?? getStateAdapter();
1582
+ await state.connect();
1583
+ return state;
1584
+ }
1585
+ async function sleep2(ms) {
1586
+ await new Promise((resolve) => {
1587
+ const timer = setTimeout(resolve, ms);
1588
+ timer.unref?.();
1589
+ });
1590
+ }
1591
+ async function withIndexLock(state, indexKey, callback) {
1592
+ const startedAtMs = now();
1593
+ let lock;
1594
+ while (true) {
1595
+ lock = await state.acquireLock(
1596
+ indexLockKey(indexKey),
1597
+ CONVERSATION_INDEX_LOCK_TTL_MS
1598
+ );
1599
+ if (lock) {
1600
+ break;
1601
+ }
1602
+ if (now() - startedAtMs >= CONVERSATION_INDEX_LOCK_WAIT_MS) {
1603
+ throw new Error(
1604
+ `Could not acquire conversation index lock for ${indexKey}`
1605
+ );
1606
+ }
1607
+ await sleep2(CONVERSATION_INDEX_LOCK_RETRY_MS);
1608
+ }
1609
+ try {
1610
+ return await callback();
1611
+ } finally {
1612
+ await state.releaseLock(lock);
1613
+ }
1614
+ }
1615
+ function normalizeIndexEntry(value) {
1616
+ if (!isRecord(value)) {
1617
+ return void 0;
1618
+ }
1619
+ const conversationId = toOptionalString(value.conversationId);
1620
+ const score = toOptionalNumber(value.score);
1621
+ if (!conversationId || typeof score !== "number") {
1622
+ return void 0;
1623
+ }
1624
+ return { conversationId, score };
1625
+ }
1626
+ function uniqueIndexEntries(value) {
1627
+ if (!Array.isArray(value)) {
1628
+ return [];
1629
+ }
1630
+ const entries = /* @__PURE__ */ new Map();
1631
+ for (const item of value) {
1632
+ const entry = normalizeIndexEntry(item);
1633
+ if (!entry) {
1634
+ continue;
1635
+ }
1636
+ const existing = entries.get(entry.conversationId);
1637
+ if (!existing || entry.score > existing.score) {
1638
+ entries.set(entry.conversationId, entry);
1639
+ }
1640
+ }
1641
+ return [...entries.values()];
1642
+ }
1643
+ function retainedIndexEntries(indexKey, entries) {
1644
+ if (indexKey === CONVERSATION_BY_ACTIVITY_INDEX_KEY) {
1645
+ return entries.sort(compareIndexDescending).slice(0, CONVERSATION_ACTIVITY_INDEX_MAX_LENGTH);
1646
+ }
1647
+ if (indexKey === CONVERSATION_ACTIVE_INDEX_KEY) {
1648
+ return entries.sort(compareIndexAscending);
1649
+ }
1650
+ throw new Error(`Unknown conversation index ${indexKey}`);
1651
+ }
1652
+ function redisIndexKey(indexKey) {
1653
+ const prefix = getChatConfig().state.keyPrefix;
1654
+ return [...prefix ? [prefix] : [], indexKey].join(":");
1655
+ }
1656
+ function parseRedisIndexEntries(values) {
1657
+ if (!Array.isArray(values)) {
1658
+ return [];
1659
+ }
1660
+ const entries = [];
1661
+ for (let index = 0; index < values.length; index += 2) {
1662
+ const conversationId = toOptionalString(values[index]);
1663
+ const score = typeof values[index + 1] === "number" ? values[index + 1] : Number(values[index + 1]);
1664
+ if (!conversationId || !Number.isFinite(score)) {
1665
+ continue;
1666
+ }
1667
+ entries.push({ conversationId, score });
1668
+ }
1669
+ return entries;
1670
+ }
1671
+ function redisConversationIndexStore(client) {
1672
+ const upsertBoundedActivityScript = `
1673
+ redis.call("ZADD", KEYS[1], ARGV[1], ARGV[2])
1674
+ redis.call("PEXPIRE", KEYS[1], ARGV[3])
1675
+ local extra = redis.call("ZCARD", KEYS[1]) - tonumber(ARGV[4])
1676
+ if extra > 0 then
1677
+ redis.call("ZREMRANGEBYRANK", KEYS[1], 0, extra - 1)
1678
+ end
1679
+ return 1
1680
+ `;
1681
+ return {
1682
+ async list(args) {
1683
+ const key = redisIndexKey(args.indexKey);
1684
+ const limit = args.limit;
1685
+ const offset = Math.max(0, args.offset ?? 0);
1686
+ if (limit === 0) {
1687
+ return [];
1688
+ }
1689
+ const values = args.scoreMax !== void 0 ? await client.sendCommand([
1690
+ "ZRANGEBYSCORE",
1691
+ key,
1692
+ "-inf",
1693
+ String(args.scoreMax),
1694
+ "WITHSCORES",
1695
+ ...limit !== void 0 || offset > 0 ? ["LIMIT", String(offset), String(limit ?? 1e9)] : []
1696
+ ]) : await client.sendCommand([
1697
+ args.order === "asc" ? "ZRANGE" : "ZREVRANGE",
1698
+ key,
1699
+ String(offset),
1700
+ String(
1701
+ limit === void 0 ? -1 : offset + Math.max(0, limit - 1)
1702
+ ),
1703
+ "WITHSCORES"
1704
+ ]);
1705
+ return parseRedisIndexEntries(values);
1706
+ },
1707
+ async remove(args) {
1708
+ await client.sendCommand([
1709
+ "ZREM",
1710
+ redisIndexKey(args.indexKey),
1711
+ args.conversationId
1712
+ ]);
1713
+ },
1714
+ async upsert(args) {
1715
+ const key = redisIndexKey(args.indexKey);
1716
+ if (args.indexKey === CONVERSATION_BY_ACTIVITY_INDEX_KEY) {
1717
+ await client.sendCommand([
1718
+ "EVAL",
1719
+ upsertBoundedActivityScript,
1720
+ "1",
1721
+ key,
1722
+ String(args.score),
1723
+ args.conversationId,
1724
+ String(JUNIOR_THREAD_STATE_TTL_MS),
1725
+ String(CONVERSATION_ACTIVITY_INDEX_MAX_LENGTH)
1726
+ ]);
1727
+ return;
1728
+ }
1729
+ if (args.indexKey === CONVERSATION_ACTIVE_INDEX_KEY) {
1730
+ await client.sendCommand([
1731
+ "ZADD",
1732
+ key,
1733
+ String(args.score),
1734
+ args.conversationId
1735
+ ]);
1736
+ await client.sendCommand([
1737
+ "PEXPIRE",
1738
+ key,
1739
+ String(JUNIOR_THREAD_STATE_TTL_MS)
1740
+ ]);
1741
+ return;
1742
+ }
1743
+ throw new Error(`Unknown conversation index ${args.indexKey}`);
1744
+ }
1745
+ };
1746
+ }
1747
+ function emulatedConversationIndexStore(state) {
1748
+ const readIndex = async (indexKey) => uniqueIndexEntries(await state.get(indexKey));
1749
+ const writeIndex = async (indexKey, entries) => {
1750
+ await state.set(indexKey, entries, JUNIOR_THREAD_STATE_TTL_MS);
1751
+ };
1752
+ return {
1753
+ async list(args) {
1754
+ const entries = (await readIndex(args.indexKey)).filter(
1755
+ (entry) => args.scoreMax === void 0 ? true : entry.score <= args.scoreMax
1756
+ ).sort(
1757
+ args.order === "asc" ? compareIndexAscending : compareIndexDescending
1758
+ );
1759
+ const offset = Math.max(0, args.offset ?? 0);
1760
+ return entries.slice(
1761
+ offset,
1762
+ args.limit === void 0 ? entries.length : offset + args.limit
1763
+ );
1764
+ },
1765
+ async remove(args) {
1766
+ await withIndexLock(state, args.indexKey, async () => {
1767
+ const entries = await readIndex(args.indexKey);
1768
+ const next = entries.filter(
1769
+ (entry) => entry.conversationId !== args.conversationId
1770
+ );
1771
+ if (next.length === entries.length) {
1772
+ return;
1773
+ }
1774
+ await writeIndex(args.indexKey, next);
1775
+ });
1776
+ },
1777
+ async upsert(args) {
1778
+ await withIndexLock(state, args.indexKey, async () => {
1779
+ const entries = await readIndex(args.indexKey);
1780
+ const withoutCurrent = entries.filter(
1781
+ (entry) => entry.conversationId !== args.conversationId
1782
+ );
1783
+ const next = retainedIndexEntries(args.indexKey, [
1784
+ ...withoutCurrent,
1785
+ { conversationId: args.conversationId, score: args.score }
1786
+ ]);
1787
+ await writeIndex(args.indexKey, next);
1788
+ });
1789
+ }
1790
+ };
1791
+ }
1792
+ async function getConversationIndexStore(state) {
1793
+ const redisStateAdapter = await getDefaultRedisStateAdapterFor(state);
1794
+ if (redisStateAdapter) {
1795
+ return redisConversationIndexStore(redisStateAdapter.getClient());
1796
+ }
1797
+ return emulatedConversationIndexStore(state);
1798
+ }
1799
+ async function upsertIndexEntry(args) {
1800
+ const index = await getConversationIndexStore(args.state);
1801
+ await index.upsert({
1802
+ conversationId: args.conversationId,
1803
+ indexKey: args.indexKey,
1804
+ score: args.score
1805
+ });
1806
+ }
1807
+ async function removeIndexEntry(args) {
1808
+ const index = await getConversationIndexStore(args.state);
1809
+ await index.remove({
1810
+ conversationId: args.conversationId,
1811
+ indexKey: args.indexKey
1812
+ });
1813
+ }
1814
+ async function acquireMutationLock(state, conversationId) {
1815
+ const startedAtMs = now();
1816
+ while (true) {
1817
+ const lock = await state.acquireLock(
1818
+ mutationLockKey(conversationId),
1819
+ CONVERSATION_MUTATION_LOCK_TTL_MS
1820
+ );
1821
+ if (lock) {
1822
+ return lock;
1823
+ }
1824
+ if (now() - startedAtMs >= CONVERSATION_MUTATION_WAIT_MS) {
1825
+ throw new Error(
1826
+ `Could not acquire conversation mutation lock for ${conversationId}`
1827
+ );
1828
+ }
1829
+ await sleep2(CONVERSATION_MUTATION_RETRY_MS);
1830
+ }
1831
+ }
1832
+ async function withConversationMutation(args, callback) {
1833
+ const state = await getConnectedState(args.state);
1834
+ const lock = await acquireMutationLock(state, args.conversationId);
1835
+ try {
1836
+ return await callback(state, lock);
1837
+ } finally {
1838
+ await state.releaseLock(lock);
1839
+ }
1840
+ }
1841
+ async function readConversation(state, conversationId) {
1842
+ const raw = await state.get(conversationKey(conversationId));
1843
+ if (raw == null) {
1844
+ return void 0;
1845
+ }
1846
+ const conversation = normalizeConversation(conversationId, raw);
1847
+ if (!conversation) {
1848
+ throw new InvalidConversationRecordError(conversationId);
1849
+ }
1850
+ return conversation;
1851
+ }
1852
+ async function writeConversation(state, lock, conversation) {
1853
+ const execution = executionWithPendingMessages(
1854
+ conversation.execution,
1855
+ conversation.execution.pendingMessages
1856
+ );
1857
+ const next = {
1858
+ ...conversation,
1859
+ execution
1860
+ };
1861
+ const fenced = await state.extendLock(
1862
+ lock,
1863
+ CONVERSATION_MUTATION_LOCK_TTL_MS
1864
+ );
1865
+ if (!fenced) {
1866
+ throw new ConversationMutationFencedError(next.conversationId);
1867
+ }
1868
+ await state.set(
1869
+ conversationKey(next.conversationId),
1870
+ next,
1871
+ JUNIOR_THREAD_STATE_TTL_MS
1872
+ );
1873
+ await upsertIndexEntry({
1874
+ state,
1875
+ indexKey: CONVERSATION_BY_ACTIVITY_INDEX_KEY,
1876
+ conversationId: next.conversationId,
1877
+ score: next.lastActivityAtMs
1878
+ });
1879
+ if (!hasRunnableWork(next)) {
1880
+ await removeIndexEntry({
1881
+ state,
1882
+ indexKey: CONVERSATION_ACTIVE_INDEX_KEY,
1883
+ conversationId: next.conversationId
1884
+ });
1885
+ return;
1886
+ }
1887
+ await upsertIndexEntry({
1888
+ state,
1889
+ indexKey: CONVERSATION_ACTIVE_INDEX_KEY,
1890
+ conversationId: next.conversationId,
1891
+ score: next.execution.updatedAtMs ?? next.updatedAtMs
1892
+ });
1893
+ }
1894
+ function assertSameConversationDestination(args) {
1895
+ if (!args.current || sameDestination(args.current, args.next)) {
1896
+ return;
1897
+ }
1898
+ throw new Error(
1899
+ `Conversation destination changed for ${args.conversationId}`
1900
+ );
1901
+ }
1902
+ function conversationWorkState(conversation) {
1903
+ const lease = conversation.execution.lease;
1904
+ return {
1905
+ ...conversation,
1906
+ lastEnqueuedAtMs: conversation.execution.lastEnqueuedAtMs,
1907
+ ...lease ? {
1908
+ lease: {
1909
+ acquiredAtMs: lease.acquiredAtMs,
1910
+ lastCheckInAtMs: lease.lastCheckInAtMs,
1911
+ leaseExpiresAtMs: lease.expiresAtMs,
1912
+ leaseToken: lease.token
1913
+ }
1914
+ } : {},
1915
+ messages: pendingMessages(conversation),
1916
+ needsRun: hasRunnableWork(conversation)
1917
+ };
1918
+ }
1919
+ async function getConversation(args) {
1920
+ const state = await getConnectedState(args.state);
1921
+ return await readConversation(state, args.conversationId);
1922
+ }
1923
+ async function getConversationWorkState(args) {
1924
+ const conversation = await getConversation(args);
1925
+ return conversation ? conversationWorkState(conversation) : void 0;
1926
+ }
1927
+ async function appendInboundMessage(args) {
1928
+ const nowMs = args.nowMs ?? now();
1929
+ return await withConversationMutation(
1930
+ { conversationId: args.message.conversationId, state: args.state },
1931
+ async (state, lock) => {
1932
+ const current = await readConversation(state, args.message.conversationId) ?? emptyConversation({
1933
+ conversationId: args.message.conversationId,
1934
+ destination: args.message.destination,
1935
+ nowMs,
1936
+ source: args.message.source
1937
+ });
1938
+ assertSameConversationDestination({
1939
+ conversationId: args.message.conversationId,
1940
+ current: current.destination,
1941
+ next: args.message.destination
1942
+ });
1943
+ const existingPending = current.execution.pendingMessages.some(
1944
+ (message) => message.inboundMessageId === args.message.inboundMessageId
1945
+ );
1946
+ const existing = current.execution.inboundMessageIds.includes(
1947
+ args.message.inboundMessageId
1948
+ );
1949
+ if (existing) {
1950
+ if (!existingPending) {
1951
+ return { status: "duplicate" };
1952
+ }
1953
+ const nextStatus = current.execution.status === "idle" ? "pending" : current.execution.status;
1954
+ await writeConversation(
1955
+ state,
1956
+ lock,
1957
+ withExecutionUpdate(
1958
+ current,
1959
+ {
1960
+ ...current.execution,
1961
+ status: nextStatus,
1962
+ pendingMessages: current.execution.pendingMessages.map(
1963
+ (message) => message.inboundMessageId === args.message.inboundMessageId ? upgradedPendingMessage(message, args.message) : message
1964
+ )
1965
+ },
1966
+ nowMs
1967
+ )
1968
+ );
1969
+ return { status: "duplicate" };
1970
+ }
1971
+ const status = current.execution.lease && current.execution.status === "running" ? "running" : current.execution.lease ? "awaiting_resume" : "pending";
1972
+ const next = {
1973
+ ...current,
1974
+ destination: current.destination ?? args.message.destination,
1975
+ source: current.source ?? args.message.source,
1976
+ lastActivityAtMs: nowMs
1977
+ };
1978
+ await writeConversation(
1979
+ state,
1980
+ lock,
1981
+ withExecutionUpdate(
1982
+ next,
1983
+ {
1984
+ ...current.execution,
1985
+ status,
1986
+ inboundMessageIds: [
1987
+ ...current.execution.inboundMessageIds,
1988
+ args.message.inboundMessageId
1989
+ ],
1990
+ pendingMessages: [
1991
+ ...current.execution.pendingMessages,
1992
+ args.message
1993
+ ].sort(compareMessages)
1994
+ },
1995
+ nowMs
1996
+ )
1997
+ );
1998
+ return { status: "appended" };
1999
+ }
2000
+ );
2001
+ }
2002
+ async function requestConversationWork(args) {
2003
+ const nowMs = args.nowMs ?? now();
2004
+ return await withConversationMutation(args, async (state, lock) => {
2005
+ const existing = await readConversation(state, args.conversationId);
2006
+ if (existing) {
2007
+ assertSameConversationDestination({
2008
+ conversationId: args.conversationId,
2009
+ current: existing.destination,
2010
+ next: args.destination
2011
+ });
2012
+ }
2013
+ const current = existing ?? emptyConversation({
2014
+ conversationId: args.conversationId,
2015
+ destination: args.destination,
2016
+ nowMs
2017
+ });
2018
+ const status = current.execution.lease ? "awaiting_resume" : "pending";
2019
+ await writeConversation(
2020
+ state,
2021
+ lock,
2022
+ withExecutionUpdate(
2023
+ {
2024
+ ...current,
2025
+ destination: current.destination ?? args.destination
2026
+ },
2027
+ {
2028
+ ...current.execution,
2029
+ status
2030
+ },
2031
+ nowMs
2032
+ )
2033
+ );
2034
+ return { status: existing === void 0 ? "created" : "updated" };
2035
+ });
2036
+ }
2037
+ async function markConversationWorkEnqueued(args) {
2038
+ const nowMs = args.nowMs ?? now();
2039
+ await withConversationMutation(args, async (state, lock) => {
2040
+ const current = await readConversation(state, args.conversationId);
2041
+ if (!current) {
2042
+ return;
2043
+ }
2044
+ await writeConversation(
2045
+ state,
2046
+ lock,
2047
+ withExecutionUpdate(
2048
+ current,
2049
+ {
2050
+ ...current.execution,
2051
+ lastEnqueuedAtMs: nowMs
2052
+ },
2053
+ nowMs
2054
+ )
2055
+ );
2056
+ });
2057
+ }
2058
+ async function clearConsumedConversationWake(args) {
2059
+ const nowMs = args.nowMs ?? now();
2060
+ return await withConversationMutation(args, async (state, lock) => {
2061
+ const current = await readConversation(state, args.conversationId);
2062
+ if (!current || hasRunnableWork(current) || current.execution.lastEnqueuedAtMs === void 0) {
2063
+ return false;
2064
+ }
2065
+ await writeConversation(
2066
+ state,
2067
+ lock,
2068
+ withExecutionUpdate(
2069
+ current,
2070
+ {
2071
+ ...current.execution,
2072
+ lastEnqueuedAtMs: void 0
2073
+ },
2074
+ nowMs
2075
+ )
2076
+ );
2077
+ return true;
2078
+ });
2079
+ }
2080
+ async function startConversationWork(args) {
2081
+ const nowMs = args.nowMs ?? now();
2082
+ return await withConversationMutation(args, async (state, lock) => {
2083
+ const current = await readConversation(state, args.conversationId);
2084
+ if (!current) {
2085
+ return { status: "no_work" };
2086
+ }
2087
+ if (isLeaseActive(current.execution.lease, nowMs)) {
2088
+ return {
2089
+ status: "active",
2090
+ leaseExpiresAtMs: current.execution.lease.expiresAtMs
2091
+ };
2092
+ }
2093
+ if (!hasRunnableWork(current)) {
2094
+ return { status: "no_work" };
2095
+ }
2096
+ const lease = {
2097
+ token: randomUUID(),
2098
+ acquiredAtMs: nowMs,
2099
+ lastCheckInAtMs: nowMs,
2100
+ expiresAtMs: nowMs + CONVERSATION_WORK_LEASE_TTL_MS
2101
+ };
2102
+ await writeConversation(
2103
+ state,
2104
+ lock,
2105
+ withExecutionUpdate(
2106
+ current,
2107
+ {
2108
+ ...current.execution,
2109
+ lease,
2110
+ status: "running",
2111
+ runId: current.execution.runId ?? randomUUID(),
2112
+ lastEnqueuedAtMs: void 0
2113
+ },
2114
+ nowMs
2115
+ )
2116
+ );
2117
+ return {
2118
+ status: "acquired",
2119
+ leaseToken: lease.token,
2120
+ leaseExpiresAtMs: lease.expiresAtMs
2121
+ };
2122
+ });
2123
+ }
2124
+ async function checkInConversationWork(args) {
2125
+ const nowMs = args.nowMs ?? now();
2126
+ return await withConversationMutation(args, async (state, lock) => {
2127
+ const current = await readConversation(state, args.conversationId);
2128
+ if (!current || current.execution.lease?.token !== args.leaseToken) {
2129
+ return false;
2130
+ }
2131
+ await writeConversation(
2132
+ state,
2133
+ lock,
2134
+ withExecutionUpdate(
2135
+ current,
2136
+ {
2137
+ ...current.execution,
2138
+ lease: {
2139
+ ...current.execution.lease,
2140
+ lastCheckInAtMs: nowMs,
2141
+ expiresAtMs: nowMs + CONVERSATION_WORK_LEASE_TTL_MS
2142
+ }
2143
+ },
2144
+ nowMs
2145
+ )
2146
+ );
2147
+ return true;
2148
+ });
2149
+ }
2150
+ async function drainConversationMailbox(args) {
2151
+ const nowMs = args.nowMs ?? now();
2152
+ const pending = await withConversationMutation(args, async (state) => {
2153
+ const current = await readConversation(state, args.conversationId);
2154
+ if (!current || current.execution.lease?.token !== args.leaseToken) {
2155
+ throw new Error(
2156
+ `Conversation lease is not held for ${args.conversationId}`
2157
+ );
2158
+ }
2159
+ return pendingMessages(current);
2160
+ });
2161
+ if (pending.length === 0) {
2162
+ return [];
2163
+ }
2164
+ const acknowledgedIds = await args.handle(pending);
2165
+ const pendingIds = new Set(
2166
+ pending.map((message) => message.inboundMessageId)
2167
+ );
2168
+ for (const inboundMessageId of acknowledgedIds ?? []) {
2169
+ if (!pendingIds.has(inboundMessageId)) {
2170
+ throw new Error(
2171
+ `Conversation mailbox acknowledgement is not pending for ${args.conversationId}`
2172
+ );
2173
+ }
2174
+ }
2175
+ const drainedIds = new Set(
2176
+ acknowledgedIds ?? pending.map((message) => message.inboundMessageId)
2177
+ );
2178
+ await withConversationMutation(args, async (state, lock) => {
2179
+ const current = await readConversation(state, args.conversationId);
2180
+ if (!current || current.execution.lease?.token !== args.leaseToken) {
2181
+ throw new Error(
2182
+ `Conversation lease is not held for ${args.conversationId}`
2183
+ );
2184
+ }
2185
+ const pendingMessages2 = current.execution.pendingMessages.filter(
2186
+ (message) => !drainedIds.has(message.inboundMessageId)
2187
+ );
2188
+ await writeConversation(
2189
+ state,
2190
+ lock,
2191
+ withExecutionUpdate(
2192
+ current,
2193
+ {
2194
+ ...current.execution,
2195
+ status: current.execution.status === "pending" && pendingMessages2.length === 0 ? "running" : current.execution.status,
2196
+ pendingMessages: pendingMessages2
2197
+ },
2198
+ nowMs
2199
+ )
2200
+ );
2201
+ });
2202
+ return pending.filter((message) => drainedIds.has(message.inboundMessageId));
2203
+ }
2204
+ async function ackMessages(args) {
2205
+ const nowMs = args.nowMs ?? now();
2206
+ const inboundMessageIds = new Set(args.inboundMessageIds);
2207
+ return await withConversationMutation(args, async (state, lock) => {
2208
+ const current = await readConversation(state, args.conversationId);
2209
+ if (!current || current.execution.lease?.token !== args.leaseToken) {
2210
+ return false;
2211
+ }
2212
+ if (inboundMessageIds.size === 0) {
2213
+ return true;
2214
+ }
2215
+ const pendingMessages2 = current.execution.pendingMessages.filter(
2216
+ (message) => !inboundMessageIds.has(message.inboundMessageId)
2217
+ );
2218
+ if (pendingMessages2.length === current.execution.pendingMessages.length) {
2219
+ return true;
2220
+ }
2221
+ await writeConversation(
2222
+ state,
2223
+ lock,
2224
+ withExecutionUpdate(
2225
+ current,
2226
+ {
2227
+ ...current.execution,
2228
+ pendingMessages: pendingMessages2
2229
+ },
2230
+ nowMs
2231
+ )
2232
+ );
2233
+ return true;
2234
+ });
2235
+ }
2236
+ async function requestConversationContinuation(args) {
2237
+ const nowMs = args.nowMs ?? now();
2238
+ return await withConversationMutation(args, async (state, lock) => {
2239
+ const current = await readConversation(state, args.conversationId);
2240
+ if (!current || current.execution.lease?.token !== args.leaseToken) {
2241
+ return false;
2242
+ }
2243
+ assertSameConversationDestination({
2244
+ conversationId: args.conversationId,
2245
+ current: current.destination,
2246
+ next: args.destination
2247
+ });
2248
+ await writeConversation(
2249
+ state,
2250
+ lock,
2251
+ withExecutionUpdate(
2252
+ current,
2253
+ {
2254
+ ...current.execution,
2255
+ status: "awaiting_resume"
2256
+ },
2257
+ nowMs
2258
+ )
2259
+ );
2260
+ return true;
2261
+ });
2262
+ }
2263
+ async function releaseConversationWork(args) {
2264
+ const nowMs = args.nowMs ?? now();
2265
+ return await withConversationMutation(args, async (state, lock) => {
2266
+ const current = await readConversation(state, args.conversationId);
2267
+ if (!current || current.execution.lease?.token !== args.leaseToken) {
2268
+ return false;
2269
+ }
2270
+ await writeConversation(
2271
+ state,
2272
+ lock,
2273
+ withExecutionUpdate(
2274
+ current,
2275
+ {
2276
+ ...current.execution,
2277
+ lease: void 0,
2278
+ status: current.execution.status === "running" ? "pending" : current.execution.status
2279
+ },
2280
+ nowMs
2281
+ )
2282
+ );
2283
+ return true;
2284
+ });
2285
+ }
2286
+ async function completeConversationWork(args) {
2287
+ const nowMs = args.nowMs ?? now();
2288
+ return await withConversationMutation(args, async (state, lock) => {
2289
+ const current = await readConversation(state, args.conversationId);
2290
+ if (!current || current.execution.lease?.token !== args.leaseToken) {
2291
+ return "lost_lease";
2292
+ }
2293
+ const hasPending = pendingMessages(current).length > 0;
2294
+ const needsRun = current.execution.status === "awaiting_resume";
2295
+ const runnable = needsRun || hasPending;
2296
+ await writeConversation(
2297
+ state,
2298
+ lock,
2299
+ withExecutionUpdate(
2300
+ current,
2301
+ {
2302
+ ...current.execution,
2303
+ lease: void 0,
2304
+ status: runnable ? "pending" : "idle",
2305
+ runId: runnable ? current.execution.runId : void 0
2306
+ },
2307
+ nowMs
2308
+ )
2309
+ );
2310
+ return runnable ? "pending" : "completed";
2311
+ });
2312
+ }
2313
+ async function recordAttemptFailure(args) {
2314
+ const nowMs = args.nowMs ?? now();
2315
+ return await withConversationMutation(args, async (state, lock) => {
2316
+ const current = await readConversation(state, args.conversationId);
2317
+ if (!current || current.execution.lease?.token !== args.leaseToken) {
2318
+ return {
2319
+ status: "lost_lease",
2320
+ pendingCount: 0,
2321
+ deadLetteredMessages: []
2322
+ };
1233
2323
  }
1234
- if (statePersisted) {
1235
- if (failure) {
1236
- await turnLifecycle.fail({
1237
- conversationId,
1238
- turnId,
1239
- createdAtMs: Date.now(),
1240
- failureCode: "model_execution_failed",
1241
- ...modelFailureEventId ? { eventId: modelFailureEventId } : {}
1242
- });
1243
- } else {
1244
- await turnLifecycle.complete({
1245
- conversationId,
1246
- turnId,
1247
- createdAtMs: Date.now(),
1248
- outcome: assistantMessageDelivered ? "success" : "no_reply"
1249
- });
1250
- }
1251
- lifecycleTerminalized = true;
2324
+ const pendingIds = new Set(
2325
+ current.execution.pendingMessages.map(
2326
+ (message) => message.inboundMessageId
2327
+ )
2328
+ );
2329
+ if (args.inboundMessageIds.length === 0 || args.inboundMessageIds.some((id) => !pendingIds.has(id))) {
2330
+ return {
2331
+ status: "skipped",
2332
+ pendingCount: current.execution.pendingMessages.length,
2333
+ deadLetteredMessages: []
2334
+ };
1252
2335
  }
1253
- dispatch = await markDispatch({
1254
- dispatch,
1255
- status: failure ? "failed" : "completed",
1256
- ...failure ? { errorMessage: failure } : {},
1257
- resultMessageTs
1258
- });
1259
- if (!failure) {
1260
- try {
1261
- await scheduleCompletedTasks({
1262
- conversationId,
1263
- sessionId: turnId
1264
- });
1265
- } catch (error) {
1266
- logException(
1267
- error,
1268
- "plugin_session_completed_task_schedule_failed",
1269
- logContext,
1270
- {},
1271
- "Plugin session.completed task scheduling failed"
1272
- );
2336
+ const attemptedIds = new Set(args.inboundMessageIds);
2337
+ const deadLetteredMessages = [];
2338
+ const pendingMessages2 = [];
2339
+ for (const message of current.execution.pendingMessages) {
2340
+ if (!attemptedIds.has(message.inboundMessageId)) {
2341
+ pendingMessages2.push(message);
2342
+ continue;
1273
2343
  }
1274
- }
1275
- } catch (error) {
1276
- if (error instanceof AuthorizationFlowDisabledError) {
1277
- if (lifecycleStarted && !lifecycleTerminalized) {
1278
- await turnLifecycle.fail({
1279
- conversationId,
1280
- turnId,
1281
- createdAtMs: Date.now(),
1282
- failureCode: "agent_run_failed"
1283
- });
1284
- lifecycleTerminalized = true;
2344
+ const attempted = {
2345
+ ...message,
2346
+ attemptCount: (message.attemptCount ?? 0) + 1
2347
+ };
2348
+ if (attempted.attemptCount >= CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS) {
2349
+ deadLetteredMessages.push(attempted);
2350
+ continue;
1285
2351
  }
1286
- await markDispatch({
1287
- dispatch,
1288
- status: "blocked",
1289
- errorMessage: `Dispatch requires ${error.provider} authorization.`
1290
- });
1291
- return;
2352
+ pendingMessages2.push(attempted);
1292
2353
  }
1293
- if (error instanceof PluginCredentialFailureError) {
1294
- if (lifecycleStarted && !lifecycleTerminalized) {
1295
- await turnLifecycle.fail({
1296
- conversationId,
1297
- turnId,
1298
- createdAtMs: Date.now(),
1299
- failureCode: "agent_run_failed"
1300
- });
1301
- lifecycleTerminalized = true;
1302
- }
1303
- await markDispatch({
1304
- dispatch,
1305
- status: "blocked",
1306
- errorMessage: error.message
1307
- });
1308
- return;
2354
+ await writeConversation(
2355
+ state,
2356
+ lock,
2357
+ withExecutionUpdate(
2358
+ current,
2359
+ {
2360
+ ...current.execution,
2361
+ pendingMessages: pendingMessages2
2362
+ },
2363
+ nowMs
2364
+ )
2365
+ );
2366
+ return {
2367
+ status: "recorded",
2368
+ pendingCount: pendingMessages2.length,
2369
+ deadLetteredMessages
2370
+ };
2371
+ });
2372
+ }
2373
+ async function deadLetterAttempt(args) {
2374
+ const nowMs = args.nowMs ?? now();
2375
+ return await withConversationMutation(args, async (state, lock) => {
2376
+ const current = await readConversation(state, args.conversationId);
2377
+ if (!current || current.execution.lease?.token !== args.leaseToken) {
2378
+ return "lost_lease";
1309
2379
  }
1310
- const eventId = logException(
1311
- error,
1312
- "agent_dispatch_run_failed",
1313
- {
1314
- ...logContext,
1315
- modelId: standardModelId(botConfig)
1316
- },
1317
- {},
1318
- "Agent dispatch failed"
2380
+ const runnable = pendingMessages(current).length > 0;
2381
+ await writeConversation(
2382
+ state,
2383
+ lock,
2384
+ withExecutionUpdate(
2385
+ current,
2386
+ {
2387
+ ...current.execution,
2388
+ lease: void 0,
2389
+ status: runnable ? "pending" : "failed",
2390
+ runId: runnable ? current.execution.runId : void 0
2391
+ },
2392
+ nowMs
2393
+ )
1319
2394
  );
1320
- if (lifecycleStarted && !lifecycleTerminalized) {
1321
- await turnLifecycle.fail({
1322
- conversationId,
1323
- turnId,
1324
- createdAtMs: Date.now(),
1325
- failureCode,
1326
- ...eventId ? { eventId } : {}
1327
- });
1328
- lifecycleTerminalized = true;
2395
+ return runnable ? "pending" : "failed";
2396
+ });
2397
+ }
2398
+ async function clearExpiredConversationLease(args) {
2399
+ const nowMs = args.nowMs ?? now();
2400
+ return await withConversationMutation(args, async (state, lock) => {
2401
+ const current = await readConversation(state, args.conversationId);
2402
+ if (!current?.execution.lease || current.execution.lease.expiresAtMs > nowMs) {
2403
+ return false;
1329
2404
  }
1330
- await markDispatch({
1331
- dispatch,
1332
- status: "failed",
1333
- errorMessage: error instanceof Error ? error.message : String(error)
1334
- });
1335
- } finally {
1336
- await stateAdapter.releaseLock(destinationLock);
1337
- }
2405
+ await writeConversation(
2406
+ state,
2407
+ lock,
2408
+ withExecutionUpdate(
2409
+ current,
2410
+ {
2411
+ ...current.execution,
2412
+ lease: void 0,
2413
+ status: "pending"
2414
+ },
2415
+ nowMs
2416
+ )
2417
+ );
2418
+ return true;
2419
+ });
1338
2420
  }
1339
-
1340
- // src/handlers/agent-dispatch.ts
1341
- async function POST(request, waitUntil, options) {
1342
- const payload = await verifyDispatchCallbackRequest(request);
1343
- if (!payload) {
1344
- return new Response("Unauthorized", { status: 401 });
1345
- }
1346
- waitUntil(
1347
- () => runAgentDispatchSlice(payload, {
1348
- agentRunner: options.agentRunner
1349
- }).catch((error) => {
1350
- logException(
1351
- error,
1352
- "agent_dispatch_handler_failed",
1353
- {},
1354
- { "app.dispatch.id": payload.id },
1355
- "Agent dispatch handler failed"
1356
- );
1357
- })
1358
- );
1359
- return new Response("Accepted", { status: 202 });
2421
+ async function removeActiveConversation(args) {
2422
+ const state = await getConnectedState(args.state);
2423
+ await removeIndexEntry({
2424
+ state,
2425
+ indexKey: CONVERSATION_ACTIVE_INDEX_KEY,
2426
+ conversationId: args.conversationId
2427
+ });
2428
+ }
2429
+ async function listActiveConversationIds(args = {}) {
2430
+ const state = await getConnectedState(args.state);
2431
+ const index = await getConversationIndexStore(state);
2432
+ const entries = await index.list({
2433
+ indexKey: CONVERSATION_ACTIVE_INDEX_KEY,
2434
+ limit: args.limit,
2435
+ order: "asc",
2436
+ scoreMax: args.staleBeforeMs
2437
+ });
2438
+ return entries.map((entry) => entry.conversationId);
1360
2439
  }
1361
-
1362
- // src/handlers/heartbeat.ts
1363
- import { timingSafeEqual as timingSafeEqual2 } from "crypto";
1364
2440
 
1365
2441
  // src/chat/task-execution/store.ts
1366
2442
  function metadataStore(options) {
@@ -1373,7 +2449,7 @@ function hasRecentEnqueueMarker(conversation, nowMs) {
1373
2449
  const lastEnqueuedAtMs = conversation.execution.lastEnqueuedAtMs;
1374
2450
  return typeof lastEnqueuedAtMs === "number" && lastEnqueuedAtMs + CONVERSATION_WORK_STALE_ENQUEUE_MS > nowMs;
1375
2451
  }
1376
- function now() {
2452
+ function now2() {
1377
2453
  return Date.now();
1378
2454
  }
1379
2455
  async function recordExecutionMetadata(args) {
@@ -1414,7 +2490,7 @@ function hasRunnableConversationWork(conversation) {
1414
2490
  return conversation.execution.status !== "idle" || countPendingConversationMessages(conversation) > 0;
1415
2491
  }
1416
2492
  async function ensureConversationWake(args) {
1417
- const nowMs = args.nowMs ?? now();
2493
+ const nowMs = args.nowMs ?? now2();
1418
2494
  const conversation = await getConversation({
1419
2495
  conversationId: args.conversationId,
1420
2496
  state: args.state
@@ -1430,8 +2506,7 @@ async function ensureConversationWake(args) {
1430
2506
  }
1431
2507
  const queueResult = await args.queue.send(
1432
2508
  {
1433
- conversationId: args.conversationId,
1434
- destination: conversation.destination
2509
+ conversationId: args.conversationId
1435
2510
  },
1436
2511
  {
1437
2512
  delayMs: args.delayMs,
@@ -1447,7 +2522,7 @@ async function ensureConversationWake(args) {
1447
2522
  return { status: "enqueued", queueMessageId: queueResult?.messageId };
1448
2523
  }
1449
2524
  async function appendAndEnqueueInboundMessage(args) {
1450
- const nowMs = args.nowMs ?? now();
2525
+ const nowMs = args.nowMs ?? now2();
1451
2526
  const appendResult = await appendInboundMessage({
1452
2527
  message: args.message,
1453
2528
  nowMs,
@@ -2108,7 +3183,47 @@ async function GET2(request, waitUntil, options = {}) {
2108
3183
  import { timingSafeEqual as timingSafeEqual3 } from "crypto";
2109
3184
 
2110
3185
  // src/chat/conversations/sql/purge.ts
2111
- import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
3186
+ import { and, asc, eq as eq2, inArray, isNull, sql } from "drizzle-orm";
3187
+
3188
+ // src/chat/conversations/sql/privacy.ts
3189
+ import { eq } from "drizzle-orm";
3190
+ import { alias } from "drizzle-orm/pg-core";
3191
+ var privacyRoot = alias(juniorConversations, "privacy_root");
3192
+ async function readRootCandidate(executor, conversationId) {
3193
+ const rows = await executor.db().select({
3194
+ requestedConversationId: juniorConversations.conversationId,
3195
+ requestedParentConversationId: juniorConversations.parentConversationId,
3196
+ requestedRootConversationId: juniorConversations.rootConversationId,
3197
+ rootConversationId: privacyRoot.conversationId,
3198
+ rootDestinationId: privacyRoot.destinationId,
3199
+ rootParentConversationId: privacyRoot.parentConversationId,
3200
+ rootRootConversationId: privacyRoot.rootConversationId
3201
+ }).from(juniorConversations).leftJoin(
3202
+ privacyRoot,
3203
+ eq(privacyRoot.conversationId, juniorConversations.rootConversationId)
3204
+ ).where(eq(juniorConversations.conversationId, conversationId));
3205
+ const row = rows[0];
3206
+ return row?.requestedRootConversationId !== null && (row?.requestedParentConversationId !== null || row?.requestedRootConversationId === row?.requestedConversationId) && row?.rootConversationId === row.requestedRootConversationId && row.rootRootConversationId === row.rootConversationId && row.rootParentConversationId === null && row.rootDestinationId !== null ? {
3207
+ destinationId: row.rootDestinationId,
3208
+ rootConversationId: row.rootConversationId
3209
+ } : void 0;
3210
+ }
3211
+ async function readCandidateVisibility(executor, candidate) {
3212
+ const destinations = await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq(juniorDestinations.id, candidate.destinationId)).for("share");
3213
+ return {
3214
+ rootConversationId: candidate.rootConversationId,
3215
+ visibility: destinations[0]?.visibility ?? null
3216
+ };
3217
+ }
3218
+ async function resolveRootVisibility(executor, conversationId) {
3219
+ const candidate = await readRootCandidate(executor, conversationId);
3220
+ if (!candidate) {
3221
+ return { rootConversationId: conversationId, visibility: null };
3222
+ }
3223
+ return readCandidateVisibility(executor, candidate);
3224
+ }
3225
+
3226
+ // src/chat/conversations/sql/purge.ts
2112
3227
  async function discoverConversationTree(executor, root) {
2113
3228
  const all = /* @__PURE__ */ new Map([
2114
3229
  [root.conversationId, root]
@@ -2177,7 +3292,7 @@ async function selectExpiredRoots(executor, args) {
2177
3292
  visibility: juniorDestinations.visibility
2178
3293
  }).from(juniorConversations).leftJoin(
2179
3294
  juniorDestinations,
2180
- eq(juniorDestinations.id, juniorConversations.destinationId)
3295
+ eq2(juniorDestinations.id, juniorConversations.destinationId)
2181
3296
  ).where(
2182
3297
  and(
2183
3298
  isNull(juniorConversations.parentConversationId),
@@ -2198,7 +3313,7 @@ async function purgeConversationTree(executor, args) {
2198
3313
  const initialRoots = await executor.db().select({
2199
3314
  conversationId: juniorConversations.conversationId,
2200
3315
  parentConversationId: juniorConversations.parentConversationId
2201
- }).from(juniorConversations).where(eq(juniorConversations.conversationId, args.rootConversationId));
3316
+ }).from(juniorConversations).where(eq2(juniorConversations.conversationId, args.rootConversationId));
2202
3317
  const initialRoot = initialRoots[0];
2203
3318
  if (!initialRoot || args.retention && initialRoot.parentConversationId !== null) {
2204
3319
  return { purged: false, conversations: 0 };
@@ -2220,14 +3335,14 @@ async function purgeConversationTree(executor, args) {
2220
3335
  destinationId: juniorConversations.destinationId,
2221
3336
  parentConversationId: juniorConversations.parentConversationId
2222
3337
  }).from(juniorConversations).where(
2223
- eq(juniorConversations.conversationId, args.rootConversationId)
3338
+ eq2(juniorConversations.conversationId, args.rootConversationId)
2224
3339
  ).for("update");
2225
3340
  const root = roots[0];
2226
3341
  if (!root || args.retention && root.parentConversationId !== null) {
2227
3342
  return { purged: false, conversations: 0 };
2228
3343
  }
2229
3344
  const resolvedScrubMetadata = args.scrubMetadataFromRootVisibility ? (await resolveRootVisibility(executor, args.rootConversationId)).visibility !== "public" : args.scrubMetadata;
2230
- const destinations = root.destinationId ? await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq(juniorDestinations.id, root.destinationId)).for("share") : [];
3345
+ const destinations = root.destinationId ? await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq2(juniorDestinations.id, root.destinationId)).for("share") : [];
2231
3346
  const isPublic = destinations[0]?.visibility === "public";
2232
3347
  const ids = tree.map((conversation) => conversation.conversationId);
2233
3348
  if (args.retention) {
@@ -2445,7 +3560,7 @@ var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
2445
3560
  var STATUS_MIN_VISIBLE_MS = 1200;
2446
3561
  var STATUS_ROTATION_INTERVAL_MS = 3e4;
2447
3562
  function createAssistantStatusScheduler(args) {
2448
- const now3 = args.now ?? (() => Date.now());
3563
+ const now4 = args.now ?? (() => Date.now());
2449
3564
  const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
2450
3565
  const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
2451
3566
  const random = args.random ?? Math.random;
@@ -2509,7 +3624,7 @@ function createAssistantStatusScheduler(args) {
2509
3624
  }
2510
3625
  currentVisibleStatus = text;
2511
3626
  currentLoadingMessages = nextLoadingMessages;
2512
- lastStatusAt = now3();
3627
+ lastStatusAt = now4();
2513
3628
  scheduleRotation();
2514
3629
  await enqueueStatusUpdate(async () => {
2515
3630
  await args.sendStatus(text, nextLoadingMessages);
@@ -2596,7 +3711,7 @@ function createAssistantStatusScheduler(args) {
2596
3711
  }
2597
3712
  return;
2598
3713
  }
2599
- const elapsed = now3() - lastStatusAt;
3714
+ const elapsed = now4() - lastStatusAt;
2600
3715
  const waitMs = Math.max(
2601
3716
  STATUS_UPDATE_DEBOUNCE_MS - elapsed,
2602
3717
  STATUS_MIN_VISIBLE_MS - elapsed,
@@ -2994,19 +4109,14 @@ function resolveReplyTimeoutMs(explicitTimeoutMs) {
2994
4109
  const parsed = Number.parseInt(raw, 10);
2995
4110
  return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
2996
4111
  }
2997
- async function postSlackMessageBestEffort(channelId, threadTs, text, footer) {
4112
+ async function postSlackMessageBestEffort(channelId, threadTs, text, conversationId) {
2998
4113
  try {
2999
- if (footer) {
3000
- await postSlackApiReplyPosts({
4114
+ if (conversationId) {
4115
+ await sendSlackReply({
3001
4116
  channelId,
3002
- threadTs,
3003
- posts: [
3004
- {
3005
- text,
3006
- stage: "thread_reply"
3007
- }
3008
- ],
3009
- footer
4117
+ conversationId,
4118
+ text,
4119
+ threadTs
3010
4120
  });
3011
4121
  return;
3012
4122
  }
@@ -3054,10 +4164,10 @@ function getResumeLogContext(args, lockKey) {
3054
4164
  const actor = routing?.actor;
3055
4165
  return {
3056
4166
  conversationId: args.conversationId,
3057
- slackThreadId: lockKey,
3058
- slackUserId: isUserActor(actor) ? actor.userId : void 0,
3059
- slackUserName: isUserActor(actor) ? actor.userName : void 0,
3060
- slackChannelId: args.channelId,
4167
+ messageConversationId: lockKey,
4168
+ userId: isUserActor(actor) ? actor.userId : void 0,
4169
+ userName: isUserActor(actor) ? actor.userName : void 0,
4170
+ destinationName: args.channelId,
3061
4171
  assistantUserName: botConfig.userName,
3062
4172
  modelId: standardModelId(botConfig)
3063
4173
  };
@@ -3321,18 +4431,18 @@ async function resumeSlackTurn(args) {
3321
4431
  };
3322
4432
  };
3323
4433
  const deliverAssistantMessage = async (assistantMessage) => {
3324
- const posts = planSlackAssistantMessagePosts(assistantMessage.text);
3325
- if (posts.length === 0) {
4434
+ if (!assistantMessage.text.trim()) {
3326
4435
  return;
3327
4436
  }
3328
4437
  failureCode = "delivery_failed";
3329
4438
  const deliveryState2 = await getDeliveryConversation();
3330
4439
  let messageTs;
3331
4440
  try {
3332
- messageTs = await postSlackApiReplyPosts({
4441
+ messageTs = await sendSlackReply({
3333
4442
  channelId: runArgs.channelId,
3334
- threadTs: runArgs.threadTs,
3335
- posts
4443
+ conversationId: runArgs.conversationId,
4444
+ text: assistantMessage.text,
4445
+ threadTs: runArgs.threadTs
3336
4446
  });
3337
4447
  } catch (error) {
3338
4448
  if (isRetryableSlackPostError(error)) {
@@ -3459,16 +4569,10 @@ async function resumeSlackTurn(args) {
3459
4569
  destination: replyContext.routing.destination,
3460
4570
  source: replyContext.routing.source,
3461
4571
  actor: resumeActor,
3462
- surface: "slack",
3463
- logContext: {
3464
- threadId: runArgs.conversationId,
3465
- actorId: isUserActor(replyContext.routing.actor) ? replyContext.routing.actor.userId : void 0,
3466
- channelId: runArgs.channelId,
3467
- assistantUserName: botConfig.userName
3468
- }
4572
+ surface: "slack"
3469
4573
  });
3470
4574
  }
3471
- await runArgs.onSuccess?.(reply);
4575
+ await runArgs.commitResult?.(reply);
3472
4576
  if (reply.diagnostics.outcome === "success") {
3473
4577
  await turnLifecycle.complete({
3474
4578
  conversationId: runArgs.conversationId,
@@ -3564,9 +4668,6 @@ async function resumeSlackTurn(args) {
3564
4668
  try {
3565
4669
  await deferredPauseHandler();
3566
4670
  if (deferredAuthInfo) {
3567
- const footer = buildSlackReplyFooter({
3568
- conversationId: runArgs.conversationId
3569
- });
3570
4671
  await postSlackMessageBestEffort(
3571
4672
  runArgs.channelId,
3572
4673
  runArgs.threadTs,
@@ -3574,7 +4675,7 @@ async function resumeSlackTurn(args) {
3574
4675
  deferredAuthInfo.actorId,
3575
4676
  deferredAuthInfo.providerDisplayName
3576
4677
  ),
3577
- footer
4678
+ runArgs.conversationId
3578
4679
  );
3579
4680
  }
3580
4681
  return true;
@@ -3977,7 +5078,7 @@ async function resumeAuthorizedMcpTurn(args) {
3977
5078
  }
3978
5079
  }
3979
5080
  },
3980
- onSuccess: async (reply) => {
5081
+ commitResult: async (reply) => {
3981
5082
  await persistCompletedReplyState(
3982
5083
  authSession.channelId,
3983
5084
  authSession.threadTs,
@@ -4575,7 +5676,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
4575
5676
  }
4576
5677
  }
4577
5678
  },
4578
- onSuccess: async (reply) => {
5679
+ commitResult: async (reply) => {
4579
5680
  logInfo(
4580
5681
  "oauth_callback_resume_complete",
4581
5682
  {},
@@ -4862,9 +5963,9 @@ function buildJwksUrl(value) {
4862
5963
  return url;
4863
5964
  }
4864
5965
  async function getJwks(issuer) {
4865
- const now3 = Date.now();
5966
+ const now4 = Date.now();
4866
5967
  const cached = jwksByIssuer.get(issuer);
4867
- if (cached && cached.expiresAtMs > now3) {
5968
+ if (cached && cached.expiresAtMs > now4) {
4868
5969
  return cached.jwks;
4869
5970
  }
4870
5971
  if (cached) {
@@ -4889,7 +5990,7 @@ async function getJwks(issuer) {
4889
5990
  }
4890
5991
  jwksByIssuer.set(issuer, {
4891
5992
  jwks,
4892
- expiresAtMs: now3 + OIDC_DISCOVERY_CACHE_TTL_MS
5993
+ expiresAtMs: now4 + OIDC_DISCOVERY_CACHE_TTL_MS
4893
5994
  });
4894
5995
  return jwks;
4895
5996
  }
@@ -5239,7 +6340,7 @@ function rehydrateAttachmentFetchers(message, downloadPrivateSlackFile2 = downlo
5239
6340
 
5240
6341
  // src/chat/slack/adapter-context.ts
5241
6342
  var initializedAdapters = /* @__PURE__ */ new WeakSet();
5242
- async function getConnectedState(stateAdapter) {
6343
+ async function getConnectedState2(stateAdapter) {
5243
6344
  const state = stateAdapter ?? getStateAdapter();
5244
6345
  await state.connect();
5245
6346
  return state;
@@ -5248,7 +6349,7 @@ async function ensureSlackAdapterInitialized(args) {
5248
6349
  if (initializedAdapters.has(args.adapter)) {
5249
6350
  return;
5250
6351
  }
5251
- const state = await getConnectedState(args.state);
6352
+ const state = await getConnectedState2(args.state);
5252
6353
  await args.adapter.initialize({
5253
6354
  getState: () => state
5254
6355
  });
@@ -5716,6 +6817,7 @@ function slackSerializedResourceEventMessage(input) {
5716
6817
  raw: {
5717
6818
  channel: input.channelId,
5718
6819
  event_type: "resource_event",
6820
+ resource_event_type: input.eventType,
5719
6821
  thread_ts: input.threadTs,
5720
6822
  type: "message",
5721
6823
  user: "UJRNEVENT"
@@ -5741,6 +6843,7 @@ function createSlackResourceEventInboundMessage(input) {
5741
6843
  const timestampIso = new Date(input.event.occurredAtMs).toISOString();
5742
6844
  const message = slackSerializedResourceEventMessage({
5743
6845
  channelId: slack.channelId,
6846
+ eventType: input.event.eventType,
5744
6847
  id: messageId,
5745
6848
  text: input.text,
5746
6849
  threadTs: slack.threadTs,
@@ -5781,7 +6884,7 @@ function createSlackResourceEventInboundMessage(input) {
5781
6884
  }
5782
6885
  };
5783
6886
  }
5784
- function getConnectedState2(stateAdapter) {
6887
+ function getConnectedState3(stateAdapter) {
5785
6888
  return stateAdapter ?? getStateAdapter();
5786
6889
  }
5787
6890
  function parseSlackMetadata(value) {
@@ -5883,7 +6986,7 @@ function createSlackConversationWorker(options) {
5883
6986
  return async (context) => {
5884
6987
  const adapter = options.getSlackAdapter();
5885
6988
  const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
5886
- const state = getConnectedState2(options.state);
6989
+ const state = getConnectedState3(options.state);
5887
6990
  await state.connect();
5888
6991
  const records = getPendingRecords({
5889
6992
  execution: { pendingMessages: [...context.attempt.messages] }
@@ -6235,7 +7338,7 @@ async function handleUnlink(event, actorId, provider) {
6235
7338
  await tokenStore.delete(actorId, provider);
6236
7339
  logInfo(
6237
7340
  "slash_command_unlink",
6238
- { slackUserId: actorId },
7341
+ { userId: actorId },
6239
7342
  { "app.credential.provider": provider },
6240
7343
  `Unlinked ${formatProviderLabel(provider)} account via ${getCommandName()} slash command`
6241
7344
  );
@@ -6457,7 +7560,7 @@ async function handleSlackEvent(args) {
6457
7560
  );
6458
7561
  } catch (error) {
6459
7562
  logException(error, "slack_app_home_publish_failed", {
6460
- slackUserId: userId
7563
+ userId
6461
7564
  });
6462
7565
  }
6463
7566
  }
@@ -6568,7 +7671,7 @@ async function handleSlashCommandForm(args) {
6568
7671
  await withSpan(
6569
7672
  "chat.slash_command",
6570
7673
  "chat.slash_command",
6571
- { slackUserId: userId },
7674
+ { userId },
6572
7675
  async () => {
6573
7676
  await handleSlashCommand({
6574
7677
  adapter: args.adapter,
@@ -6607,7 +7710,7 @@ async function handleInteractivePayload(args) {
6607
7710
  await withSpan(
6608
7711
  "chat.app_home_disconnect",
6609
7712
  "chat.app_home_disconnect",
6610
- { slackUserId: userId },
7713
+ { userId },
6611
7714
  async () => {
6612
7715
  try {
6613
7716
  await unlinkProvider(userId, provider, args.userTokenStore);
@@ -6615,7 +7718,7 @@ async function handleInteractivePayload(args) {
6615
7718
  logException(
6616
7719
  error,
6617
7720
  "app_home_disconnect_unlink_failed",
6618
- { slackUserId: userId },
7721
+ { userId },
6619
7722
  { "app.credential.provider": provider }
6620
7723
  );
6621
7724
  }
@@ -6625,7 +7728,7 @@ async function handleInteractivePayload(args) {
6625
7728
  logException(
6626
7729
  error,
6627
7730
  "app_home_disconnect_publish_failed",
6628
- { slackUserId: userId },
7731
+ { userId },
6629
7732
  { "app.credential.provider": provider }
6630
7733
  );
6631
7734
  }
@@ -6668,7 +7771,7 @@ async function handleSlackForm(args) {
6668
7771
  })
6669
7772
  ).catch((error) => {
6670
7773
  logException(error, "slash_command_failed", {
6671
- slackUserId: params.get("user_id") ?? void 0
7774
+ userId: params.get("user_id") ?? void 0
6672
7775
  });
6673
7776
  })
6674
7777
  );
@@ -6698,7 +7801,7 @@ async function handleSlackForm(args) {
6698
7801
  })
6699
7802
  ).catch((error) => {
6700
7803
  logException(error, "slack_interactive_payload_failed", {
6701
- slackUserId: payload.user?.id?.trim() || void 0
7804
+ userId: payload.user?.id?.trim() || void 0
6702
7805
  });
6703
7806
  })
6704
7807
  );
@@ -6830,25 +7933,10 @@ import {
6830
7933
  registerDevConsumer
6831
7934
  } from "@vercel/queue";
6832
7935
 
6833
- // src/chat/task-execution/queue.ts
6834
- var ConversationQueueMessageRejectedError = class extends Error {
6835
- conversationId;
6836
- reason;
6837
- constructor(reason, message, options = {}) {
6838
- super(message);
6839
- this.name = "ConversationQueueMessageRejectedError";
6840
- this.reason = reason;
6841
- this.conversationId = options.conversationId;
6842
- }
6843
- };
6844
- function isConversationQueueMessageRejectedError(error) {
6845
- return error instanceof ConversationQueueMessageRejectedError;
6846
- }
6847
-
6848
7936
  // src/chat/task-execution/worker.ts
6849
7937
  var CONVERSATION_WORK_DEFER_DELAY_MS = 15e3;
6850
7938
  var CONVERSATION_WORK_SOFT_YIELD_AFTER_MS = 24e4;
6851
- function now2(options) {
7939
+ function now3(options) {
6852
7940
  return options.nowMs?.() ?? Date.now();
6853
7941
  }
6854
7942
  function nudgeIdempotencyKey(reason, conversationId, nowMs) {
@@ -6919,7 +8007,7 @@ function isTerminalFailure(failure) {
6919
8007
  }
6920
8008
  function startLeaseCheckIn(args) {
6921
8009
  const timer = setInterval(() => {
6922
- const nowMs = now2(args.options);
8010
+ const nowMs = now3(args.options);
6923
8011
  void checkInConversationWork2({
6924
8012
  conversationId: args.conversationId,
6925
8013
  leaseToken: args.leaseToken,
@@ -6975,16 +8063,16 @@ async function processConversationWork(message, options) {
6975
8063
  await clearConsumedConversationWake2({
6976
8064
  conversationId,
6977
8065
  conversationStore: options.conversationStore,
6978
- nowMs: now2(options),
8066
+ nowMs: now3(options),
6979
8067
  state: options.state
6980
8068
  });
6981
8069
  }
6982
8070
  return { status: "no_work" };
6983
8071
  }
6984
- if (!initial.destination || !sameDestination(initial.destination, message.destination)) {
8072
+ if (!initial.destination) {
6985
8073
  throw new ConversationQueueMessageRejectedError(
6986
- "destination_mismatch",
6987
- `Conversation work queue destination changed for ${conversationId}`,
8074
+ "invalid_record",
8075
+ `Conversation work is missing destination context for ${conversationId}`,
6988
8076
  { conversationId }
6989
8077
  );
6990
8078
  }
@@ -6992,20 +8080,20 @@ async function processConversationWork(message, options) {
6992
8080
  const lease = await startConversationWork2({
6993
8081
  conversationId,
6994
8082
  conversationStore: options.conversationStore,
6995
- nowMs: now2(options),
8083
+ nowMs: now3(options),
6996
8084
  state: options.state
6997
8085
  });
6998
8086
  if (lease.status === "no_work") {
6999
8087
  await clearConsumedConversationWake2({
7000
8088
  conversationId,
7001
8089
  conversationStore: options.conversationStore,
7002
- nowMs: now2(options),
8090
+ nowMs: now3(options),
7003
8091
  state: options.state
7004
8092
  });
7005
8093
  return { status: "no_work" };
7006
8094
  }
7007
8095
  if (lease.status === "active") {
7008
- const nudgeNowMs = now2(options);
8096
+ const nudgeNowMs = now3(options);
7009
8097
  await ensureConversationWake({
7010
8098
  conversationId,
7011
8099
  conversationStore: options.conversationStore,
@@ -7026,7 +8114,7 @@ async function processConversationWork(message, options) {
7026
8114
  );
7027
8115
  return { status: "active" };
7028
8116
  }
7029
- const startedAtMs = now2(options);
8117
+ const startedAtMs = now3(options);
7030
8118
  const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? CONVERSATION_WORK_SOFT_YIELD_AFTER_MS);
7031
8119
  const leasedWork = await getConversationWorkState2({
7032
8120
  conversationId,
@@ -7060,7 +8148,7 @@ async function processConversationWork(message, options) {
7060
8148
  leaseToken: lease.leaseToken,
7061
8149
  conversationStore: options.conversationStore,
7062
8150
  handle,
7063
- nowMs: now2(options),
8151
+ nowMs: now3(options),
7064
8152
  state: options.state
7065
8153
  });
7066
8154
  const ack = async () => {
@@ -7069,7 +8157,7 @@ async function processConversationWork(message, options) {
7069
8157
  inboundMessageIds: attemptMessageIds,
7070
8158
  leaseToken: lease.leaseToken,
7071
8159
  conversationStore: options.conversationStore,
7072
- nowMs: now2(options),
8160
+ nowMs: now3(options),
7073
8161
  state: options.state
7074
8162
  });
7075
8163
  if (!acknowledged) {
@@ -7092,13 +8180,13 @@ async function processConversationWork(message, options) {
7092
8180
  },
7093
8181
  conversationId,
7094
8182
  destination,
7095
- shouldYield: () => leaseLost || now2(options) >= softYieldDeadlineMs,
8183
+ shouldYield: () => leaseLost || now3(options) >= softYieldDeadlineMs,
7096
8184
  checkIn: async () => {
7097
8185
  const checkedIn = await checkInConversationWork2({
7098
8186
  conversationId,
7099
8187
  leaseToken: lease.leaseToken,
7100
8188
  conversationStore: options.conversationStore,
7101
- nowMs: now2(options),
8189
+ nowMs: now3(options),
7102
8190
  state: options.state
7103
8191
  });
7104
8192
  if (!checkedIn) {
@@ -7114,7 +8202,7 @@ async function processConversationWork(message, options) {
7114
8202
  conversationId,
7115
8203
  destination,
7116
8204
  leaseToken: lease.leaseToken,
7117
- nowMs: now2(options),
8205
+ nowMs: now3(options),
7118
8206
  options
7119
8207
  });
7120
8208
  return { status: "lost_lease" };
@@ -7124,13 +8212,13 @@ async function processConversationWork(message, options) {
7124
8212
  conversationId,
7125
8213
  destination,
7126
8214
  leaseToken: lease.leaseToken,
7127
- nowMs: now2(options),
8215
+ nowMs: now3(options),
7128
8216
  options
7129
8217
  });
7130
8218
  return { status: "lost_lease" };
7131
8219
  }
7132
8220
  if (result.status === "yielded") {
7133
- const yieldNowMs = now2(options);
8221
+ const yieldNowMs = now3(options);
7134
8222
  const continuationMarked = await requestConversationContinuation2({
7135
8223
  conversationId,
7136
8224
  destination,
@@ -7165,7 +8253,7 @@ async function processConversationWork(message, options) {
7165
8253
  "conversation_work_cooperative_yield",
7166
8254
  { conversationId },
7167
8255
  {
7168
- "app.worker.elapsed_ms": now2(options) - startedAtMs,
8256
+ "app.worker.elapsed_ms": now3(options) - startedAtMs,
7169
8257
  "app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
7170
8258
  },
7171
8259
  "Conversation work yielded cooperatively"
@@ -7173,7 +8261,7 @@ async function processConversationWork(message, options) {
7173
8261
  return { status: "yielded" };
7174
8262
  }
7175
8263
  if (result.status === "deferred") {
7176
- const deferredNowMs = now2(options);
8264
+ const deferredNowMs = now3(options);
7177
8265
  const released = await releaseConversationWork2({
7178
8266
  conversationId,
7179
8267
  leaseToken: lease.leaseToken,
@@ -7202,7 +8290,7 @@ async function processConversationWork(message, options) {
7202
8290
  const failure = await recordFailedDeliveryAttempt({
7203
8291
  conversationId,
7204
8292
  leaseToken: lease.leaseToken,
7205
- nowMs: now2(options),
8293
+ nowMs: now3(options),
7206
8294
  messageIds: attemptMessageIds,
7207
8295
  options
7208
8296
  });
@@ -7211,7 +8299,7 @@ async function processConversationWork(message, options) {
7211
8299
  conversationId,
7212
8300
  leaseToken: lease.leaseToken,
7213
8301
  conversationStore: options.conversationStore,
7214
- nowMs: now2(options),
8302
+ nowMs: now3(options),
7215
8303
  state: options.state
7216
8304
  });
7217
8305
  return { status: "failed" };
@@ -7221,14 +8309,14 @@ async function processConversationWork(message, options) {
7221
8309
  conversationId,
7222
8310
  leaseToken: lease.leaseToken,
7223
8311
  conversationStore: options.conversationStore,
7224
- nowMs: now2(options),
8312
+ nowMs: now3(options),
7225
8313
  state: options.state
7226
8314
  });
7227
8315
  if (completion === "lost_lease") {
7228
8316
  return { status: "lost_lease" };
7229
8317
  }
7230
8318
  if (completion === "pending") {
7231
- const nudgeNowMs = now2(options);
8319
+ const nudgeNowMs = now3(options);
7232
8320
  const wake = await ensureConversationWake({
7233
8321
  conversationId,
7234
8322
  conversationStore: options.conversationStore,
@@ -7247,13 +8335,13 @@ async function processConversationWork(message, options) {
7247
8335
  "conversation_work_completed",
7248
8336
  { conversationId },
7249
8337
  {
7250
- "app.worker.elapsed_ms": now2(options) - startedAtMs
8338
+ "app.worker.elapsed_ms": now3(options) - startedAtMs
7251
8339
  },
7252
8340
  "Conversation work completed"
7253
8341
  );
7254
8342
  return { status: "completed" };
7255
8343
  } catch (error) {
7256
- const errorNowMs = now2(options);
8344
+ const errorNowMs = now3(options);
7257
8345
  let recoveryRecorded = false;
7258
8346
  try {
7259
8347
  const failure = attemptMessageIds.length > 0 ? await recordFailedDeliveryAttempt({
@@ -7318,7 +8406,7 @@ async function processConversationWork(message, options) {
7318
8406
  "conversation_work_failed",
7319
8407
  { conversationId },
7320
8408
  {
7321
- "app.worker.elapsed_ms": now2(options) - startedAtMs
8409
+ "app.worker.elapsed_ms": now3(options) - startedAtMs
7322
8410
  },
7323
8411
  "Conversation work failed"
7324
8412
  );
@@ -7336,18 +8424,11 @@ async function processConversationWork(message, options) {
7336
8424
  var CONVERSATION_WORK_VISIBILITY_TIMEOUT_BUFFER_SECONDS = 30;
7337
8425
  var CONVERSATION_WORK_DEV_CONSUMER_GROUP = "junior_conversation_work_dev";
7338
8426
  function parseConversationQueueMessage(message) {
7339
- const destination = parseDestination(
7340
- message?.destination
7341
- );
7342
- if (!message || typeof message !== "object" || typeof message.conversationId !== "string" || !message.conversationId.trim() || !destination) {
7343
- throw new Error(
7344
- "Conversation queue message is missing destination context"
7345
- );
8427
+ const parsed = conversationQueueMessageSchema.safeParse(message);
8428
+ if (!parsed.success) {
8429
+ throw new Error("Conversation queue message is malformed");
7346
8430
  }
7347
- return {
7348
- conversationId: message.conversationId,
7349
- destination
7350
- };
8431
+ return parsed.data;
7351
8432
  }
7352
8433
  function resolveConversationWorkVisibilityTimeoutSeconds(functionMaxDurationSeconds = getChatConfig().functionMaxDurationSeconds) {
7353
8434
  return functionMaxDurationSeconds + CONVERSATION_WORK_VISIBILITY_TIMEOUT_BUFFER_SECONDS;
@@ -7898,10 +8979,10 @@ function createAcceptedSteeringDrain(hooks, options) {
7898
8979
  function buildLogContext(deps, args) {
7899
8980
  return {
7900
8981
  conversationId: args.threadId ?? args.runId,
7901
- slackThreadId: args.threadId,
7902
- slackUserId: args.actorId,
7903
- slackUserName: args.actorUserName,
7904
- slackChannelId: args.channelId,
8982
+ messageConversationId: args.threadId,
8983
+ userId: args.actorId,
8984
+ userName: args.actorUserName,
8985
+ destinationName: args.channelId,
7905
8986
  runId: args.runId,
7906
8987
  assistantUserName: deps.assistantUserName,
7907
8988
  modelId: deps.modelId
@@ -8620,9 +9701,9 @@ function createSlackTurnRuntime(deps) {
8620
9701
  error,
8621
9702
  "assistant_thread_started_handler_failed",
8622
9703
  {
8623
- slackThreadId: event.threadId,
8624
- slackUserId: event.userId,
8625
- slackChannelId: event.channelId,
9704
+ messageConversationId: event.threadId,
9705
+ userId: event.userId,
9706
+ destinationName: event.channelId,
8626
9707
  assistantUserName: deps.assistantUserName,
8627
9708
  modelId: deps.modelId
8628
9709
  },
@@ -8644,9 +9725,9 @@ function createSlackTurnRuntime(deps) {
8644
9725
  error,
8645
9726
  "assistant_context_changed_handler_failed",
8646
9727
  {
8647
- slackThreadId: event.threadId,
8648
- slackUserId: event.userId,
8649
- slackChannelId: event.channelId,
9728
+ messageConversationId: event.threadId,
9729
+ userId: event.userId,
9730
+ destinationName: event.channelId,
8650
9731
  assistantUserName: deps.assistantUserName,
8651
9732
  modelId: deps.modelId
8652
9733
  },
@@ -8670,9 +9751,9 @@ function createSubscribedReplyPolicy(deps) {
8670
9751
  logWarn(
8671
9752
  "subscribed_message_classifier_failed",
8672
9753
  {
8673
- slackThreadId: input.context.threadId,
8674
- slackUserId: input.context.actorId,
8675
- slackChannelId: input.context.channelId,
9754
+ messageConversationId: input.context.threadId,
9755
+ userId: input.context.actorId,
9756
+ destinationName: input.context.channelId,
8676
9757
  runId: input.context.runId,
8677
9758
  assistantUserName: botConfig.userName,
8678
9759
  modelId: botConfig.fastModelId
@@ -8881,9 +9962,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
8881
9962
  logWarn(
8882
9963
  "attachment_skipped_size_limit",
8883
9964
  {
8884
- slackThreadId: context.threadId,
8885
- slackUserId: context.actorId,
8886
- slackChannelId: context.channelId,
9965
+ messageConversationId: context.threadId,
9966
+ userId: context.actorId,
9967
+ destinationName: context.channelId,
8887
9968
  runId: context.runId,
8888
9969
  assistantUserName: botConfig.userName,
8889
9970
  modelId: standardModelId(botConfig)
@@ -8906,9 +9987,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
8906
9987
  logWarn(
8907
9988
  "image_attachment_processing_failed",
8908
9989
  {
8909
- slackThreadId: context.threadId,
8910
- slackUserId: context.actorId,
8911
- slackChannelId: context.channelId,
9990
+ messageConversationId: context.threadId,
9991
+ userId: context.actorId,
9992
+ destinationName: context.channelId,
8912
9993
  runId: context.runId,
8913
9994
  assistantUserName: botConfig.userName,
8914
9995
  modelId: botConfig.visionModelId ?? standardModelId(botConfig)
@@ -8925,9 +10006,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
8925
10006
  logWarn(
8926
10007
  "attachment_resolution_failed",
8927
10008
  {
8928
- slackThreadId: context.threadId,
8929
- slackUserId: context.actorId,
8930
- slackChannelId: context.channelId,
10009
+ messageConversationId: context.threadId,
10010
+ userId: context.actorId,
10011
+ destinationName: context.channelId,
8931
10012
  runId: context.runId,
8932
10013
  assistantUserName: botConfig.userName,
8933
10014
  modelId: standardModelId(botConfig)
@@ -8975,9 +10056,9 @@ async function summarizeConversationImage(args, deps) {
8975
10056
  logWarn(
8976
10057
  "conversation_image_vision_failed",
8977
10058
  {
8978
- slackThreadId: args.context.threadId,
8979
- slackUserId: args.context.actorId,
8980
- slackChannelId: args.context.channelId,
10059
+ messageConversationId: args.context.threadId,
10060
+ userId: args.context.actorId,
10061
+ destinationName: args.context.channelId,
8981
10062
  runId: args.context.runId,
8982
10063
  assistantUserName: botConfig.userName,
8983
10064
  modelId: visionModelId
@@ -9031,9 +10112,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
9031
10112
  logWarn(
9032
10113
  "conversation_image_replies_fetch_failed",
9033
10114
  {
9034
- slackThreadId: context.threadId,
9035
- slackUserId: context.actorId,
9036
- slackChannelId: context.channelId,
10115
+ messageConversationId: context.threadId,
10116
+ userId: context.actorId,
10117
+ destinationName: context.channelId,
9037
10118
  runId: context.runId,
9038
10119
  assistantUserName: botConfig.userName,
9039
10120
  modelId: standardModelId(botConfig)
@@ -9098,9 +10179,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
9098
10179
  logWarn(
9099
10180
  "conversation_image_skipped_size_limit",
9100
10181
  {
9101
- slackThreadId: context.threadId,
9102
- slackUserId: context.actorId,
9103
- slackChannelId: context.channelId,
10182
+ messageConversationId: context.threadId,
10183
+ userId: context.actorId,
10184
+ destinationName: context.channelId,
9104
10185
  runId: context.runId,
9105
10186
  assistantUserName: botConfig.userName,
9106
10187
  modelId: standardModelId(botConfig)
@@ -9125,9 +10206,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
9125
10206
  logWarn(
9126
10207
  "conversation_image_download_failed",
9127
10208
  {
9128
- slackThreadId: context.threadId,
9129
- slackUserId: context.actorId,
9130
- slackChannelId: context.channelId,
10209
+ messageConversationId: context.threadId,
10210
+ userId: context.actorId,
10211
+ destinationName: context.channelId,
9131
10212
  runId: context.runId,
9132
10213
  assistantUserName: botConfig.userName,
9133
10214
  modelId: standardModelId(botConfig)
@@ -9145,9 +10226,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
9145
10226
  logWarn(
9146
10227
  "conversation_image_skipped_size_limit",
9147
10228
  {
9148
- slackThreadId: context.threadId,
9149
- slackUserId: context.actorId,
9150
- slackChannelId: context.channelId,
10229
+ messageConversationId: context.threadId,
10230
+ userId: context.actorId,
10231
+ destinationName: context.channelId,
9151
10232
  runId: context.runId,
9152
10233
  assistantUserName: botConfig.userName,
9153
10234
  modelId: standardModelId(botConfig)
@@ -9188,9 +10269,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
9188
10269
  logInfo(
9189
10270
  "conversation_image_context_hydrated",
9190
10271
  {
9191
- slackThreadId: context.threadId,
9192
- slackUserId: context.actorId,
9193
- slackChannelId: context.channelId,
10272
+ messageConversationId: context.threadId,
10273
+ userId: context.actorId,
10274
+ destinationName: context.channelId,
9194
10275
  runId: context.runId,
9195
10276
  assistantUserName: botConfig.userName,
9196
10277
  modelId: standardModelId(botConfig)
@@ -9313,9 +10394,9 @@ function maybeUpdateAssistantTitle(args) {
9313
10394
  logWarn(
9314
10395
  "thread_title_generation_failed",
9315
10396
  {
9316
- slackThreadId: args.threadId,
9317
- slackUserId: args.actorId,
9318
- slackChannelId: args.channelId,
10397
+ messageConversationId: args.threadId,
10398
+ userId: args.actorId,
10399
+ destinationName: args.channelId,
9319
10400
  runId: args.runId,
9320
10401
  assistantUserName: args.assistantUserName,
9321
10402
  modelId: args.modelId
@@ -9347,9 +10428,9 @@ function maybeUpdateAssistantTitle(args) {
9347
10428
  logError(
9348
10429
  "thread_title_generation_permission_denied",
9349
10430
  {
9350
- slackThreadId: args.threadId,
9351
- slackUserId: args.actorId,
9352
- slackChannelId: args.channelId,
10431
+ messageConversationId: args.threadId,
10432
+ userId: args.actorId,
10433
+ destinationName: args.channelId,
9353
10434
  runId: args.runId,
9354
10435
  assistantUserName: args.assistantUserName,
9355
10436
  modelId: args.modelId
@@ -9361,9 +10442,9 @@ function maybeUpdateAssistantTitle(args) {
9361
10442
  logWarn(
9362
10443
  "thread_title_slack_update_failed",
9363
10444
  {
9364
- slackThreadId: args.threadId,
9365
- slackUserId: args.actorId,
9366
- slackChannelId: args.channelId,
10445
+ messageConversationId: args.threadId,
10446
+ userId: args.actorId,
10447
+ destinationName: args.channelId,
9367
10448
  runId: args.runId,
9368
10449
  assistantUserName: args.assistantUserName,
9369
10450
  modelId: args.modelId
@@ -9595,9 +10676,9 @@ function createReplyToThread(deps) {
9595
10676
  "chat.reply",
9596
10677
  {
9597
10678
  conversationId,
9598
- slackThreadId: threadId,
9599
- slackUserId: message.author.userId,
9600
- slackChannelId: channelId,
10679
+ messageConversationId: threadId,
10680
+ userId: message.author.userId,
10681
+ destinationName: channelId,
9601
10682
  runId,
9602
10683
  assistantUserName: botConfig.userName,
9603
10684
  modelId: standardModelId(botConfig)
@@ -9653,9 +10734,9 @@ function createReplyToThread(deps) {
9653
10734
  const turnId = buildDeterministicTurnId(message.id);
9654
10735
  const turnTraceContext = {
9655
10736
  conversationId,
9656
- slackThreadId: threadId,
9657
- slackUserId: message.author.userId,
9658
- slackChannelId: channelId,
10737
+ messageConversationId: threadId,
10738
+ userId: message.author.userId,
10739
+ destinationName: channelId,
9659
10740
  runId,
9660
10741
  assistantUserName: botConfig.userName,
9661
10742
  modelId: standardModelId(botConfig)
@@ -9676,23 +10757,16 @@ function createReplyToThread(deps) {
9676
10757
  actor.userId,
9677
10758
  providerDisplayName
9678
10759
  );
9679
- const footer = buildSlackReplyFooter({ conversationId });
9680
10760
  try {
10761
+ await beforeFirstResponsePost();
9681
10762
  if (channelId && threadTs) {
9682
- await postSlackApiReplyPosts({
9683
- beforePost: beforeFirstResponsePost,
10763
+ await sendSlackReply({
9684
10764
  channelId,
9685
- threadTs,
9686
- posts: [
9687
- {
9688
- text,
9689
- stage: "thread_reply"
9690
- }
9691
- ],
9692
- footer
10765
+ conversationId,
10766
+ text,
10767
+ threadTs
9693
10768
  });
9694
10769
  } else {
9695
- await beforeFirstResponsePost();
9696
10770
  await thread.post(buildSlackOutputMessage(text));
9697
10771
  }
9698
10772
  } catch (error) {
@@ -9988,7 +11062,7 @@ function createReplyToThread(deps) {
9988
11062
  });
9989
11063
  }
9990
11064
  if (actor?.userName) {
9991
- setTags({ slackUserName: actor.userName });
11065
+ setTags({ userName: actor.userName });
9992
11066
  }
9993
11067
  const turnAttachments = collectTurnAttachments(
9994
11068
  message,
@@ -10014,32 +11088,6 @@ function createReplyToThread(deps) {
10014
11088
  const compactingStatus = {
10015
11089
  text: "Compacting context"
10016
11090
  };
10017
- const postThreadReply = async (payload, stage) => {
10018
- await beforeFirstResponsePost();
10019
- try {
10020
- return await thread.post(payload);
10021
- } catch (error) {
10022
- if (isRetryableSlackPostError(error)) {
10023
- throw new RetryableDeliveryError(error);
10024
- }
10025
- const eventId = logException(
10026
- error,
10027
- "slack_thread_post_failed",
10028
- turnTraceContext,
10029
- {
10030
- "app.slack.reply_stage": stage,
10031
- ...messageTs ? { "messaging.message.id": messageTs } : {},
10032
- ...getSlackErrorObservabilityAttributes(error)
10033
- },
10034
- "Failed to post Slack thread reply"
10035
- );
10036
- throw new ConversationTurnBoundaryError({
10037
- cause: error,
10038
- ...eventId ? { eventId } : {},
10039
- failureCode: "delivery_failed"
10040
- });
10041
- }
10042
- };
10043
11091
  let persistedAtLeastOnce = false;
10044
11092
  let shouldPersistFailureState = true;
10045
11093
  let runResultHandled = false;
@@ -10059,18 +11107,45 @@ function createReplyToThread(deps) {
10059
11107
  turnCompletionNotified = true;
10060
11108
  };
10061
11109
  const deliverAssistantMessage = async (assistantMessage) => {
10062
- const posts = planSlackAssistantMessagePosts(assistantMessage.text);
10063
- if (posts.length === 0) {
11110
+ if (!assistantMessage.text.trim()) {
10064
11111
  return;
10065
11112
  }
10066
11113
  boundaryFailureCode = "delivery_failed";
10067
11114
  let slackTs;
10068
- for (const post of posts) {
10069
- const sentMessage = await postThreadReply(
10070
- buildSlackOutputMessage(post.text),
10071
- post.stage
11115
+ await beforeFirstResponsePost();
11116
+ try {
11117
+ if (channelId && threadTs && thread.adapter.name === "slack") {
11118
+ slackTs = await sendSlackReply({
11119
+ channelId,
11120
+ conversationId,
11121
+ text: assistantMessage.text,
11122
+ threadTs
11123
+ });
11124
+ } else {
11125
+ for (const text of splitSlackReplyText(assistantMessage.text)) {
11126
+ slackTs = (await thread.post(buildSlackOutputMessage(text))).id;
11127
+ }
11128
+ }
11129
+ } catch (error) {
11130
+ if (isRetryableSlackPostError(error)) {
11131
+ throw new RetryableDeliveryError(error);
11132
+ }
11133
+ const eventId = logException(
11134
+ error,
11135
+ "slack_thread_post_failed",
11136
+ turnTraceContext,
11137
+ {
11138
+ "app.slack.reply_stage": "thread_reply",
11139
+ ...messageTs ? { "messaging.message.id": messageTs } : {},
11140
+ ...getSlackErrorObservabilityAttributes(error)
11141
+ },
11142
+ "Failed to post Slack thread reply"
10072
11143
  );
10073
- slackTs = sentMessage.id;
11144
+ throw new ConversationTurnBoundaryError({
11145
+ cause: error,
11146
+ ...eventId ? { eventId } : {},
11147
+ failureCode: "delivery_failed"
11148
+ });
10074
11149
  }
10075
11150
  assistantMessageDelivered = true;
10076
11151
  const recordedMessageId = recordDeliveredAssistantMessage({
@@ -10402,9 +11477,9 @@ function createReplyToThread(deps) {
10402
11477
  }
10403
11478
  let reply = outcome.result;
10404
11479
  const diagnosticsContext = {
10405
- slackThreadId: threadId,
10406
- slackUserId: message.author.userId,
10407
- slackChannelId: channelId,
11480
+ messageConversationId: threadId,
11481
+ userId: message.author.userId,
11482
+ destinationName: channelId,
10408
11483
  runId,
10409
11484
  assistantUserName: botConfig.userName,
10410
11485
  modelId: reply.diagnostics.modelId
@@ -10454,13 +11529,6 @@ function createReplyToThread(deps) {
10454
11529
  sliceId: 1,
10455
11530
  messages: reply.piMessages,
10456
11531
  modelId: reply.diagnostics.modelId,
10457
- logContext: {
10458
- threadId,
10459
- actorId: slackActorId,
10460
- channelId,
10461
- runId,
10462
- assistantUserName: botConfig.userName
10463
- },
10464
11532
  actor: executionActor,
10465
11533
  surface: "slack"
10466
11534
  });
@@ -10747,6 +11815,11 @@ async function refreshAssistantThreadContext(event) {
10747
11815
 
10748
11816
  // src/chat/runtime/conversation-message.ts
10749
11817
  var NON_TEXT_MESSAGE_TEXT = "[non-text message]";
11818
+ function resourceEventType(entry) {
11819
+ if (!entry.raw || typeof entry.raw !== "object") return void 0;
11820
+ const raw = entry.raw;
11821
+ return raw.event_type === "resource_event" && typeof raw.resource_event_type === "string" ? raw.resource_event_type : void 0;
11822
+ }
10750
11823
  function resolveMessageText(args) {
10751
11824
  const text = normalizeConversationText(args.text);
10752
11825
  return text || NON_TEXT_MESSAGE_TEXT;
@@ -10771,6 +11844,7 @@ function toConversationMessage(args) {
10771
11844
  },
10772
11845
  meta: {
10773
11846
  attachmentCount: args.entry.attachments.length,
11847
+ eventType: resourceEventType(args.entry),
10774
11848
  explicitMention: args.explicitMention,
10775
11849
  imageAttachmentCount: imageAttachmentCount > 0 ? imageAttachmentCount : void 0,
10776
11850
  imagesHydrated: !messageHasPotentialImageAttachment,
@@ -11377,7 +12451,7 @@ async function continueSlackAgentRun(payload, options) {
11377
12451
  }
11378
12452
  }
11379
12453
  },
11380
- onSuccess: async (reply) => {
12454
+ commitResult: async (reply) => {
11381
12455
  await persistCompletedReplyState2({
11382
12456
  sessionRecord: activeSessionRecord,
11383
12457
  reply
@@ -11704,6 +12778,9 @@ function renderResourceEventNotificationText(subscription, event) {
11704
12778
  "Handling:",
11705
12779
  "- This is a subscribed conversation update, not a user-authored command.",
11706
12780
  "- Use the subscription intent to decide whether this event warrants action or a visible reply. Otherwise, stay silent.",
12781
+ "- Treat the trusted summary as sufficient evidence for the facts it reports. Do not verify or expand those facts with tools.",
12782
+ "- Use tools only when the subscription intent explicitly requires missing details or an action.",
12783
+ "- When replying, state what changed and the useful next step, if any.",
11707
12784
  "",
11708
12785
  "Subscription:",
11709
12786
  `- resource: ${subscription.label}`,
@@ -12210,6 +13287,7 @@ async function createApp(options) {
12210
13287
  return app;
12211
13288
  }
12212
13289
  export {
13290
+ JUNIOR_VERSION,
12213
13291
  createApp,
12214
13292
  defineJuniorPlugins
12215
13293
  };