@sentry/junior 0.108.0 → 0.109.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 (62) hide show
  1. package/dist/{agent-hooks-NU5HK3PS.js → agent-hooks-FHHVLNPA.js} +12 -12
  2. package/dist/api/conversations/events.d.ts +1 -1
  3. package/dist/api/conversations/schema.d.ts +57 -6
  4. package/dist/api/schema.js +1 -1
  5. package/dist/api.js +108 -22
  6. package/dist/app.d.ts +1 -0
  7. package/dist/app.js +1481 -420
  8. package/dist/{catalog-runtime-FOFNPR3P.js → catalog-runtime-DMGJSLV2.js} +6 -4
  9. package/dist/chat/conversations/sql/store.d.ts +0 -7
  10. package/dist/chat/log-context.d.ts +40 -0
  11. package/dist/chat/logging.d.ts +3 -26
  12. package/dist/chat/runtime/slack-resume.d.ts +2 -2
  13. package/dist/chat/services/turn-session-record.d.ts +0 -2
  14. package/dist/chat/slack/reply.d.ts +7 -19
  15. package/dist/chat/state/conversation.d.ts +1 -0
  16. package/dist/chat/task-execution/slack-work.d.ts +2 -2
  17. package/dist/chunk-3SXUV423.js +10 -0
  18. package/dist/{chunk-3RGQLX2F.js → chunk-72OMGPDS.js} +1010 -26
  19. package/dist/{chunk-2XXEKR44.js → chunk-7Q6WCISF.js} +1 -1
  20. package/dist/{chunk-TE4QHJH4.js → chunk-FENLIFJD.js} +72 -591
  21. package/dist/{chunk-XKB7LGIW.js → chunk-FWJRXN4F.js} +3 -3
  22. package/dist/{chunk-PUAMO4PI.js → chunk-HHVY4SMP.js} +41 -7
  23. package/dist/{chunk-B5I5LMSP.js → chunk-HO5NT6OE.js} +5 -7
  24. package/dist/{chunk-O24R4DCK.js → chunk-ISMWX44P.js} +93 -55
  25. package/dist/{chunk-7FBGKXPE.js → chunk-IVVGFRQQ.js} +5 -0
  26. package/dist/{chunk-KPMPQ6AA.js → chunk-IXKSQGIO.js} +90 -100
  27. package/dist/{chunk-SS67LUOK.js → chunk-JNAVF45E.js} +6 -7
  28. package/dist/{chunk-DY7TRYB5.js → chunk-MGUMR35T.js} +1 -1
  29. package/dist/{chunk-A5CO2EHL.js → chunk-MXW4GL53.js} +11 -15
  30. package/dist/{chunk-RT5BUHR5.js → chunk-O3JMSZLI.js} +1 -1
  31. package/dist/chunk-OCD3OZRK.js +125 -0
  32. package/dist/{chunk-H3QYZL7K.js → chunk-OUHNWH46.js} +28 -3
  33. package/dist/{chunk-VIXFG2VC.js → chunk-PLB7MRCQ.js} +1 -2
  34. package/dist/{chunk-UD6THJ2I.js → chunk-QY636HZU.js} +114 -12
  35. package/dist/{chunk-VH6KWKG2.js → chunk-RGX4V75M.js} +3 -3
  36. package/dist/{chunk-B7PUBQQM.js → chunk-SYAQIR5Z.js} +1 -1
  37. package/dist/chunk-TMBRFULF.js +471 -0
  38. package/dist/{chunk-YIPD3L6L.js → chunk-TT6GMXNA.js} +8 -10
  39. package/dist/{chunk-YT72TOEK.js → chunk-WHMOBKT7.js} +2 -2
  40. package/dist/cli/chat.js +23 -23
  41. package/dist/cli/check.js +8 -6
  42. package/dist/cli/plugins.js +16 -16
  43. package/dist/cli/snapshot-warmup.js +10 -9
  44. package/dist/cli/upgrade.js +314 -3505
  45. package/dist/db/db.d.ts +1 -1
  46. package/dist/{db-AMRBAT5D.js → db-V3NU72O2.js} +8 -6
  47. package/dist/instrumentation.js +3 -2
  48. package/dist/nitro.js +5 -4
  49. package/dist/{runner-TQH5GAJ4.js → runner-JYBCQBLK.js} +17 -20
  50. package/dist/{skills-ITNHBARB.js → skills-U6MDAST7.js} +7 -5
  51. package/dist/{validation-AGQ46ZGV.js → validation-72N3JVRU.js} +7 -5
  52. package/dist/version.d.ts +2 -0
  53. package/dist/version.js +7 -0
  54. package/migrations/0006_drop_legacy_conversation_messages.sql +1 -0
  55. package/migrations/README.md +12 -9
  56. package/migrations/meta/0006_snapshot.json +948 -0
  57. package/migrations/meta/_journal.json +7 -0
  58. package/package.json +9 -5
  59. package/dist/chunk-AHJR2IFF.js +0 -115
  60. package/dist/chunk-G3E7SCME.js +0 -28
  61. package/dist/chunk-J3B3FPP2.js +0 -1061
  62. package/dist/chunk-TWINAEZQ.js +0 -1283
package/dist/app.js CHANGED
@@ -6,31 +6,7 @@ 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-MXW4GL53.js";
34
10
  import {
35
11
  GET,
36
12
  buildSentryConversationUrl,
@@ -38,13 +14,13 @@ import {
38
14
  resolveRootVisibility,
39
15
  resolveSlackChannelTypeFromMessage,
40
16
  resolveSlackConversationContext
41
- } from "./chunk-XKB7LGIW.js";
17
+ } from "./chunk-FWJRXN4F.js";
42
18
  import "./chunk-CEA3A3UA.js";
43
19
  import {
44
20
  getVercelConversationWorkQueue,
45
21
  resolveConversationWorkQueueTopic,
46
22
  verifyConversationQueueMessage
47
- } from "./chunk-2XXEKR44.js";
23
+ } from "./chunk-7Q6WCISF.js";
48
24
  import {
49
25
  PLUGIN_TASK_QUEUE_TOPIC,
50
26
  createVercelQueueClient,
@@ -53,7 +29,7 @@ import {
53
29
  import {
54
30
  validatePluginEgressCredentialHooks,
55
31
  validatePluginRegistrations
56
- } from "./chunk-DY7TRYB5.js";
32
+ } from "./chunk-MGUMR35T.js";
57
33
  import {
58
34
  defineJuniorPlugins,
59
35
  pluginCatalogConfigFromEnv,
@@ -106,21 +82,28 @@ import {
106
82
  splitSlackReplyText,
107
83
  startOAuthFlow,
108
84
  truncateStatusText
109
- } from "./chunk-KPMPQ6AA.js";
85
+ } from "./chunk-IXKSQGIO.js";
110
86
  import {
111
87
  ConversationTurnBoundaryError,
112
88
  CooperativeTurnYieldError,
89
+ JUNIOR_THREAD_STATE_TTL_MS,
113
90
  TurnInputCommitLostError,
114
91
  TurnInputDeferredError,
115
92
  TurnSliceLimitExceededError,
93
+ abandonAgentTurnSessionRecord,
116
94
  buildConversationContext,
117
95
  buildDeterministicAssistantMessageId,
118
96
  buildDeterministicTurnId,
119
97
  buildTurnLimitResponse,
120
98
  clearPendingAuth,
121
99
  coerceThreadArtifactsState,
100
+ coerceThreadConversationState,
101
+ commitMessages,
122
102
  completeDeliveredTurn,
123
103
  createConversationMemoryService,
104
+ failAgentTurnSessionRecord,
105
+ getAgentTurnSessionRecord,
106
+ getAgentTurnSessionRecordForResume,
124
107
  getChannelConfigurationService,
125
108
  getChannelConfigurationServiceById,
126
109
  getConversationMessageSlackTs,
@@ -135,6 +118,9 @@ import {
135
118
  isPendingAuthLatestRequest,
136
119
  isTurnInputCommitLostError,
137
120
  isTurnInputDeferredError,
121
+ listAgentTurnSessionSummariesForConversation,
122
+ loadConversationProjection,
123
+ loadProjection,
138
124
  markConversationMessage,
139
125
  markTurnClosed,
140
126
  markTurnFailed,
@@ -148,33 +134,23 @@ import {
148
134
  persistThreadStateById,
149
135
  persistWithRetry,
150
136
  persistYieldSessionRecord,
137
+ recordAgentTurnSessionSummary,
138
+ recordAuthorizationCompleted,
151
139
  recordDeliveredAssistantMessage,
152
140
  startActiveTurn,
153
141
  turnHasReply,
154
142
  updateConversationStats,
155
143
  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";
144
+ } from "./chunk-72OMGPDS.js";
145
+ import "./chunk-HO5NT6OE.js";
172
146
  import "./chunk-MU6HHZEN.js";
173
147
  import "./chunk-PDO5BLNM.js";
174
148
  import "./chunk-UIE3R5XU.js";
175
149
  import {
176
150
  bindScheduledTaskCredentialSubject,
177
151
  bindSlackDirectCredentialSubject,
152
+ createPluginLogger,
153
+ createPluginState,
178
154
  getDashboardConversationLink,
179
155
  getPluginApiRoutes,
180
156
  getPluginRoutes,
@@ -185,16 +161,12 @@ import {
185
161
  validatePlugins,
186
162
  verifyScheduledTaskCredentialSubject,
187
163
  verifySlackDirectCredentialSubject
188
- } from "./chunk-UD6THJ2I.js";
189
- import {
190
- createPluginLogger,
191
- createPluginState
192
- } from "./chunk-AHJR2IFF.js";
193
- import "./chunk-G3E7SCME.js";
164
+ } from "./chunk-QY636HZU.js";
194
165
  import {
195
166
  acquireActiveLock,
167
+ getDefaultRedisStateAdapterFor,
196
168
  getStateAdapter
197
- } from "./chunk-H3QYZL7K.js";
169
+ } from "./chunk-OUHNWH46.js";
198
170
  import {
199
171
  SlackActionError,
200
172
  downloadPrivateSlackFile,
@@ -205,7 +177,7 @@ import {
205
177
  normalizeSlackConversationId,
206
178
  runWithSlackInstallationToken,
207
179
  runWithWorkspaceTeamId
208
- } from "./chunk-VH6KWKG2.js";
180
+ } from "./chunk-RGX4V75M.js";
209
181
  import {
210
182
  contextProvenance,
211
183
  getConversationEventStore,
@@ -213,10 +185,12 @@ import {
213
185
  getDb,
214
186
  getSqlExecutor,
215
187
  instructionProvenanceFor,
216
- juniorConversationEvents,
217
- juniorConversations,
218
188
  withConversationEventLock
219
- } from "./chunk-TE4QHJH4.js";
189
+ } from "./chunk-FENLIFJD.js";
190
+ import {
191
+ juniorConversationEvents,
192
+ juniorConversations
193
+ } from "./chunk-TMBRFULF.js";
220
194
  import {
221
195
  sleep
222
196
  } from "./chunk-4ZNGQH7C.js";
@@ -240,7 +214,7 @@ import {
240
214
  standardModelId,
241
215
  stripRuntimeTurnContext,
242
216
  trimTrailingAssistantMessages
243
- } from "./chunk-SS67LUOK.js";
217
+ } from "./chunk-JNAVF45E.js";
244
218
  import {
245
219
  juniorDestinations
246
220
  } from "./chunk-VFUK3X5B.js";
@@ -249,32 +223,35 @@ import {
249
223
  readSlackRawMessageContext,
250
224
  resolveSlackChannelIdFromMessage,
251
225
  resolveSlackChannelIdFromThreadId
252
- } from "./chunk-RT5BUHR5.js";
226
+ } from "./chunk-O3JMSZLI.js";
253
227
  import {
254
228
  createSlackDestination,
255
229
  destinationKey,
256
230
  parseDestination,
257
231
  requireSlackDestination,
258
232
  sameDestination
259
- } from "./chunk-B7PUBQQM.js";
233
+ } from "./chunk-SYAQIR5Z.js";
260
234
  import {
261
235
  discoverSkills
262
- } from "./chunk-YT72TOEK.js";
236
+ } from "./chunk-WHMOBKT7.js";
237
+ import {
238
+ pluginCatalogRuntime
239
+ } from "./chunk-3SXUV423.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-TT6GMXNA.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-PLB7MRCQ.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-ISMWX44P.js";
294
271
  import {
272
+ isRecord,
273
+ toOptionalNumber,
295
274
  toOptionalString
296
275
  } from "./chunk-OB42YVAE.js";
297
276
  import {
298
277
  continueTrace
299
278
  } from "./chunk-6GWA276C.js";
300
279
  import "./chunk-B3IT5END.js";
280
+ import {
281
+ homeDir
282
+ } from "./chunk-3V7OFJZC.js";
301
283
  import {
302
284
  JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE,
303
285
  parseSlackChannelId,
304
286
  parseSlackMessageTs
305
- } from "./chunk-7FBGKXPE.js";
287
+ } from "./chunk-IVVGFRQQ.js";
306
288
  import {
307
- homeDir
308
- } from "./chunk-3V7OFJZC.js";
289
+ JUNIOR_VERSION
290
+ } from "./chunk-OCD3OZRK.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,13 +1177,7 @@ 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"
1232
1181
  });
1233
1182
  }
1234
1183
  if (statePersisted) {
@@ -1250,117 +1199,1244 @@ async function runAgentDispatchSlice(callback, deps) {
1250
1199
  }
1251
1200
  lifecycleTerminalized = true;
1252
1201
  }
1253
- dispatch = await markDispatch({
1254
- dispatch,
1255
- status: failure ? "failed" : "completed",
1256
- ...failure ? { errorMessage: failure } : {},
1257
- resultMessageTs
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 } : {}
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
1258
2247
  });
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
- );
1273
- }
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;
1274
2269
  }
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;
1285
- }
1286
- await markDispatch({
1287
- dispatch,
1288
- status: "blocked",
1289
- errorMessage: `Dispatch requires ${error.provider} authorization.`
1290
- });
1291
- return;
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
+ };
1292
2323
  }
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;
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
+ };
2335
+ }
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;
1302
2343
  }
1303
- await markDispatch({
1304
- dispatch,
1305
- status: "blocked",
1306
- errorMessage: error.message
1307
- });
1308
- return;
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;
2351
+ }
2352
+ pendingMessages2.push(attempted);
1309
2353
  }
1310
- const eventId = logException(
1311
- error,
1312
- "agent_dispatch_run_failed",
1313
- {
1314
- ...logContext,
1315
- modelId: standardModelId(botConfig)
1316
- },
1317
- {},
1318
- "Agent dispatch failed"
2354
+ await writeConversation(
2355
+ state,
2356
+ lock,
2357
+ withExecutionUpdate(
2358
+ current,
2359
+ {
2360
+ ...current.execution,
2361
+ pendingMessages: pendingMessages2
2362
+ },
2363
+ nowMs
2364
+ )
1319
2365
  );
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;
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";
1329
2379
  }
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
- }
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
+ )
2394
+ );
2395
+ return runnable ? "pending" : "failed";
2396
+ });
1338
2397
  }
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 });
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;
2404
+ }
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
+ });
2420
+ }
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
@@ -1447,7 +2523,7 @@ async function ensureConversationWake(args) {
1447
2523
  return { status: "enqueued", queueMessageId: queueResult?.messageId };
1448
2524
  }
1449
2525
  async function appendAndEnqueueInboundMessage(args) {
1450
- const nowMs = args.nowMs ?? now();
2526
+ const nowMs = args.nowMs ?? now2();
1451
2527
  const appendResult = await appendInboundMessage({
1452
2528
  message: args.message,
1453
2529
  nowMs,
@@ -2445,7 +3521,7 @@ var STATUS_UPDATE_DEBOUNCE_MS = 1e3;
2445
3521
  var STATUS_MIN_VISIBLE_MS = 1200;
2446
3522
  var STATUS_ROTATION_INTERVAL_MS = 3e4;
2447
3523
  function createAssistantStatusScheduler(args) {
2448
- const now3 = args.now ?? (() => Date.now());
3524
+ const now4 = args.now ?? (() => Date.now());
2449
3525
  const setTimer = args.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
2450
3526
  const clearTimer = args.clearTimer ?? ((timer) => clearTimeout(timer));
2451
3527
  const random = args.random ?? Math.random;
@@ -2509,7 +3585,7 @@ function createAssistantStatusScheduler(args) {
2509
3585
  }
2510
3586
  currentVisibleStatus = text;
2511
3587
  currentLoadingMessages = nextLoadingMessages;
2512
- lastStatusAt = now3();
3588
+ lastStatusAt = now4();
2513
3589
  scheduleRotation();
2514
3590
  await enqueueStatusUpdate(async () => {
2515
3591
  await args.sendStatus(text, nextLoadingMessages);
@@ -2596,7 +3672,7 @@ function createAssistantStatusScheduler(args) {
2596
3672
  }
2597
3673
  return;
2598
3674
  }
2599
- const elapsed = now3() - lastStatusAt;
3675
+ const elapsed = now4() - lastStatusAt;
2600
3676
  const waitMs = Math.max(
2601
3677
  STATUS_UPDATE_DEBOUNCE_MS - elapsed,
2602
3678
  STATUS_MIN_VISIBLE_MS - elapsed,
@@ -2994,19 +4070,14 @@ function resolveReplyTimeoutMs(explicitTimeoutMs) {
2994
4070
  const parsed = Number.parseInt(raw, 10);
2995
4071
  return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
2996
4072
  }
2997
- async function postSlackMessageBestEffort(channelId, threadTs, text, footer) {
4073
+ async function postSlackMessageBestEffort(channelId, threadTs, text, conversationId) {
2998
4074
  try {
2999
- if (footer) {
3000
- await postSlackApiReplyPosts({
4075
+ if (conversationId) {
4076
+ await sendSlackReply({
3001
4077
  channelId,
3002
- threadTs,
3003
- posts: [
3004
- {
3005
- text,
3006
- stage: "thread_reply"
3007
- }
3008
- ],
3009
- footer
4078
+ conversationId,
4079
+ text,
4080
+ threadTs
3010
4081
  });
3011
4082
  return;
3012
4083
  }
@@ -3054,10 +4125,10 @@ function getResumeLogContext(args, lockKey) {
3054
4125
  const actor = routing?.actor;
3055
4126
  return {
3056
4127
  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,
4128
+ messageConversationId: lockKey,
4129
+ userId: isUserActor(actor) ? actor.userId : void 0,
4130
+ userName: isUserActor(actor) ? actor.userName : void 0,
4131
+ destinationName: args.channelId,
3061
4132
  assistantUserName: botConfig.userName,
3062
4133
  modelId: standardModelId(botConfig)
3063
4134
  };
@@ -3321,18 +4392,18 @@ async function resumeSlackTurn(args) {
3321
4392
  };
3322
4393
  };
3323
4394
  const deliverAssistantMessage = async (assistantMessage) => {
3324
- const posts = planSlackAssistantMessagePosts(assistantMessage.text);
3325
- if (posts.length === 0) {
4395
+ if (!assistantMessage.text.trim()) {
3326
4396
  return;
3327
4397
  }
3328
4398
  failureCode = "delivery_failed";
3329
4399
  const deliveryState2 = await getDeliveryConversation();
3330
4400
  let messageTs;
3331
4401
  try {
3332
- messageTs = await postSlackApiReplyPosts({
4402
+ messageTs = await sendSlackReply({
3333
4403
  channelId: runArgs.channelId,
3334
- threadTs: runArgs.threadTs,
3335
- posts
4404
+ conversationId: runArgs.conversationId,
4405
+ text: assistantMessage.text,
4406
+ threadTs: runArgs.threadTs
3336
4407
  });
3337
4408
  } catch (error) {
3338
4409
  if (isRetryableSlackPostError(error)) {
@@ -3459,16 +4530,10 @@ async function resumeSlackTurn(args) {
3459
4530
  destination: replyContext.routing.destination,
3460
4531
  source: replyContext.routing.source,
3461
4532
  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
- }
4533
+ surface: "slack"
3469
4534
  });
3470
4535
  }
3471
- await runArgs.onSuccess?.(reply);
4536
+ await runArgs.commitResult?.(reply);
3472
4537
  if (reply.diagnostics.outcome === "success") {
3473
4538
  await turnLifecycle.complete({
3474
4539
  conversationId: runArgs.conversationId,
@@ -3564,9 +4629,6 @@ async function resumeSlackTurn(args) {
3564
4629
  try {
3565
4630
  await deferredPauseHandler();
3566
4631
  if (deferredAuthInfo) {
3567
- const footer = buildSlackReplyFooter({
3568
- conversationId: runArgs.conversationId
3569
- });
3570
4632
  await postSlackMessageBestEffort(
3571
4633
  runArgs.channelId,
3572
4634
  runArgs.threadTs,
@@ -3574,7 +4636,7 @@ async function resumeSlackTurn(args) {
3574
4636
  deferredAuthInfo.actorId,
3575
4637
  deferredAuthInfo.providerDisplayName
3576
4638
  ),
3577
- footer
4639
+ runArgs.conversationId
3578
4640
  );
3579
4641
  }
3580
4642
  return true;
@@ -3977,7 +5039,7 @@ async function resumeAuthorizedMcpTurn(args) {
3977
5039
  }
3978
5040
  }
3979
5041
  },
3980
- onSuccess: async (reply) => {
5042
+ commitResult: async (reply) => {
3981
5043
  await persistCompletedReplyState(
3982
5044
  authSession.channelId,
3983
5045
  authSession.threadTs,
@@ -4575,7 +5637,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
4575
5637
  }
4576
5638
  }
4577
5639
  },
4578
- onSuccess: async (reply) => {
5640
+ commitResult: async (reply) => {
4579
5641
  logInfo(
4580
5642
  "oauth_callback_resume_complete",
4581
5643
  {},
@@ -4862,9 +5924,9 @@ function buildJwksUrl(value) {
4862
5924
  return url;
4863
5925
  }
4864
5926
  async function getJwks(issuer) {
4865
- const now3 = Date.now();
5927
+ const now4 = Date.now();
4866
5928
  const cached = jwksByIssuer.get(issuer);
4867
- if (cached && cached.expiresAtMs > now3) {
5929
+ if (cached && cached.expiresAtMs > now4) {
4868
5930
  return cached.jwks;
4869
5931
  }
4870
5932
  if (cached) {
@@ -4889,7 +5951,7 @@ async function getJwks(issuer) {
4889
5951
  }
4890
5952
  jwksByIssuer.set(issuer, {
4891
5953
  jwks,
4892
- expiresAtMs: now3 + OIDC_DISCOVERY_CACHE_TTL_MS
5954
+ expiresAtMs: now4 + OIDC_DISCOVERY_CACHE_TTL_MS
4893
5955
  });
4894
5956
  return jwks;
4895
5957
  }
@@ -5239,7 +6301,7 @@ function rehydrateAttachmentFetchers(message, downloadPrivateSlackFile2 = downlo
5239
6301
 
5240
6302
  // src/chat/slack/adapter-context.ts
5241
6303
  var initializedAdapters = /* @__PURE__ */ new WeakSet();
5242
- async function getConnectedState(stateAdapter) {
6304
+ async function getConnectedState2(stateAdapter) {
5243
6305
  const state = stateAdapter ?? getStateAdapter();
5244
6306
  await state.connect();
5245
6307
  return state;
@@ -5248,7 +6310,7 @@ async function ensureSlackAdapterInitialized(args) {
5248
6310
  if (initializedAdapters.has(args.adapter)) {
5249
6311
  return;
5250
6312
  }
5251
- const state = await getConnectedState(args.state);
6313
+ const state = await getConnectedState2(args.state);
5252
6314
  await args.adapter.initialize({
5253
6315
  getState: () => state
5254
6316
  });
@@ -5716,6 +6778,7 @@ function slackSerializedResourceEventMessage(input) {
5716
6778
  raw: {
5717
6779
  channel: input.channelId,
5718
6780
  event_type: "resource_event",
6781
+ resource_event_type: input.eventType,
5719
6782
  thread_ts: input.threadTs,
5720
6783
  type: "message",
5721
6784
  user: "UJRNEVENT"
@@ -5741,6 +6804,7 @@ function createSlackResourceEventInboundMessage(input) {
5741
6804
  const timestampIso = new Date(input.event.occurredAtMs).toISOString();
5742
6805
  const message = slackSerializedResourceEventMessage({
5743
6806
  channelId: slack.channelId,
6807
+ eventType: input.event.eventType,
5744
6808
  id: messageId,
5745
6809
  text: input.text,
5746
6810
  threadTs: slack.threadTs,
@@ -5781,7 +6845,7 @@ function createSlackResourceEventInboundMessage(input) {
5781
6845
  }
5782
6846
  };
5783
6847
  }
5784
- function getConnectedState2(stateAdapter) {
6848
+ function getConnectedState3(stateAdapter) {
5785
6849
  return stateAdapter ?? getStateAdapter();
5786
6850
  }
5787
6851
  function parseSlackMetadata(value) {
@@ -5883,7 +6947,7 @@ function createSlackConversationWorker(options) {
5883
6947
  return async (context) => {
5884
6948
  const adapter = options.getSlackAdapter();
5885
6949
  const actorLookup = options.lookupSlackUser ?? lookupSlackUser;
5886
- const state = getConnectedState2(options.state);
6950
+ const state = getConnectedState3(options.state);
5887
6951
  await state.connect();
5888
6952
  const records = getPendingRecords({
5889
6953
  execution: { pendingMessages: [...context.attempt.messages] }
@@ -6235,7 +7299,7 @@ async function handleUnlink(event, actorId, provider) {
6235
7299
  await tokenStore.delete(actorId, provider);
6236
7300
  logInfo(
6237
7301
  "slash_command_unlink",
6238
- { slackUserId: actorId },
7302
+ { userId: actorId },
6239
7303
  { "app.credential.provider": provider },
6240
7304
  `Unlinked ${formatProviderLabel(provider)} account via ${getCommandName()} slash command`
6241
7305
  );
@@ -6457,7 +7521,7 @@ async function handleSlackEvent(args) {
6457
7521
  );
6458
7522
  } catch (error) {
6459
7523
  logException(error, "slack_app_home_publish_failed", {
6460
- slackUserId: userId
7524
+ userId
6461
7525
  });
6462
7526
  }
6463
7527
  }
@@ -6568,7 +7632,7 @@ async function handleSlashCommandForm(args) {
6568
7632
  await withSpan(
6569
7633
  "chat.slash_command",
6570
7634
  "chat.slash_command",
6571
- { slackUserId: userId },
7635
+ { userId },
6572
7636
  async () => {
6573
7637
  await handleSlashCommand({
6574
7638
  adapter: args.adapter,
@@ -6607,7 +7671,7 @@ async function handleInteractivePayload(args) {
6607
7671
  await withSpan(
6608
7672
  "chat.app_home_disconnect",
6609
7673
  "chat.app_home_disconnect",
6610
- { slackUserId: userId },
7674
+ { userId },
6611
7675
  async () => {
6612
7676
  try {
6613
7677
  await unlinkProvider(userId, provider, args.userTokenStore);
@@ -6615,7 +7679,7 @@ async function handleInteractivePayload(args) {
6615
7679
  logException(
6616
7680
  error,
6617
7681
  "app_home_disconnect_unlink_failed",
6618
- { slackUserId: userId },
7682
+ { userId },
6619
7683
  { "app.credential.provider": provider }
6620
7684
  );
6621
7685
  }
@@ -6625,7 +7689,7 @@ async function handleInteractivePayload(args) {
6625
7689
  logException(
6626
7690
  error,
6627
7691
  "app_home_disconnect_publish_failed",
6628
- { slackUserId: userId },
7692
+ { userId },
6629
7693
  { "app.credential.provider": provider }
6630
7694
  );
6631
7695
  }
@@ -6668,7 +7732,7 @@ async function handleSlackForm(args) {
6668
7732
  })
6669
7733
  ).catch((error) => {
6670
7734
  logException(error, "slash_command_failed", {
6671
- slackUserId: params.get("user_id") ?? void 0
7735
+ userId: params.get("user_id") ?? void 0
6672
7736
  });
6673
7737
  })
6674
7738
  );
@@ -6698,7 +7762,7 @@ async function handleSlackForm(args) {
6698
7762
  })
6699
7763
  ).catch((error) => {
6700
7764
  logException(error, "slack_interactive_payload_failed", {
6701
- slackUserId: payload.user?.id?.trim() || void 0
7765
+ userId: payload.user?.id?.trim() || void 0
6702
7766
  });
6703
7767
  })
6704
7768
  );
@@ -6848,7 +7912,7 @@ function isConversationQueueMessageRejectedError(error) {
6848
7912
  // src/chat/task-execution/worker.ts
6849
7913
  var CONVERSATION_WORK_DEFER_DELAY_MS = 15e3;
6850
7914
  var CONVERSATION_WORK_SOFT_YIELD_AFTER_MS = 24e4;
6851
- function now2(options) {
7915
+ function now3(options) {
6852
7916
  return options.nowMs?.() ?? Date.now();
6853
7917
  }
6854
7918
  function nudgeIdempotencyKey(reason, conversationId, nowMs) {
@@ -6919,7 +7983,7 @@ function isTerminalFailure(failure) {
6919
7983
  }
6920
7984
  function startLeaseCheckIn(args) {
6921
7985
  const timer = setInterval(() => {
6922
- const nowMs = now2(args.options);
7986
+ const nowMs = now3(args.options);
6923
7987
  void checkInConversationWork2({
6924
7988
  conversationId: args.conversationId,
6925
7989
  leaseToken: args.leaseToken,
@@ -6975,7 +8039,7 @@ async function processConversationWork(message, options) {
6975
8039
  await clearConsumedConversationWake2({
6976
8040
  conversationId,
6977
8041
  conversationStore: options.conversationStore,
6978
- nowMs: now2(options),
8042
+ nowMs: now3(options),
6979
8043
  state: options.state
6980
8044
  });
6981
8045
  }
@@ -6992,20 +8056,20 @@ async function processConversationWork(message, options) {
6992
8056
  const lease = await startConversationWork2({
6993
8057
  conversationId,
6994
8058
  conversationStore: options.conversationStore,
6995
- nowMs: now2(options),
8059
+ nowMs: now3(options),
6996
8060
  state: options.state
6997
8061
  });
6998
8062
  if (lease.status === "no_work") {
6999
8063
  await clearConsumedConversationWake2({
7000
8064
  conversationId,
7001
8065
  conversationStore: options.conversationStore,
7002
- nowMs: now2(options),
8066
+ nowMs: now3(options),
7003
8067
  state: options.state
7004
8068
  });
7005
8069
  return { status: "no_work" };
7006
8070
  }
7007
8071
  if (lease.status === "active") {
7008
- const nudgeNowMs = now2(options);
8072
+ const nudgeNowMs = now3(options);
7009
8073
  await ensureConversationWake({
7010
8074
  conversationId,
7011
8075
  conversationStore: options.conversationStore,
@@ -7026,7 +8090,7 @@ async function processConversationWork(message, options) {
7026
8090
  );
7027
8091
  return { status: "active" };
7028
8092
  }
7029
- const startedAtMs = now2(options);
8093
+ const startedAtMs = now3(options);
7030
8094
  const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? CONVERSATION_WORK_SOFT_YIELD_AFTER_MS);
7031
8095
  const leasedWork = await getConversationWorkState2({
7032
8096
  conversationId,
@@ -7060,7 +8124,7 @@ async function processConversationWork(message, options) {
7060
8124
  leaseToken: lease.leaseToken,
7061
8125
  conversationStore: options.conversationStore,
7062
8126
  handle,
7063
- nowMs: now2(options),
8127
+ nowMs: now3(options),
7064
8128
  state: options.state
7065
8129
  });
7066
8130
  const ack = async () => {
@@ -7069,7 +8133,7 @@ async function processConversationWork(message, options) {
7069
8133
  inboundMessageIds: attemptMessageIds,
7070
8134
  leaseToken: lease.leaseToken,
7071
8135
  conversationStore: options.conversationStore,
7072
- nowMs: now2(options),
8136
+ nowMs: now3(options),
7073
8137
  state: options.state
7074
8138
  });
7075
8139
  if (!acknowledged) {
@@ -7092,13 +8156,13 @@ async function processConversationWork(message, options) {
7092
8156
  },
7093
8157
  conversationId,
7094
8158
  destination,
7095
- shouldYield: () => leaseLost || now2(options) >= softYieldDeadlineMs,
8159
+ shouldYield: () => leaseLost || now3(options) >= softYieldDeadlineMs,
7096
8160
  checkIn: async () => {
7097
8161
  const checkedIn = await checkInConversationWork2({
7098
8162
  conversationId,
7099
8163
  leaseToken: lease.leaseToken,
7100
8164
  conversationStore: options.conversationStore,
7101
- nowMs: now2(options),
8165
+ nowMs: now3(options),
7102
8166
  state: options.state
7103
8167
  });
7104
8168
  if (!checkedIn) {
@@ -7114,7 +8178,7 @@ async function processConversationWork(message, options) {
7114
8178
  conversationId,
7115
8179
  destination,
7116
8180
  leaseToken: lease.leaseToken,
7117
- nowMs: now2(options),
8181
+ nowMs: now3(options),
7118
8182
  options
7119
8183
  });
7120
8184
  return { status: "lost_lease" };
@@ -7124,13 +8188,13 @@ async function processConversationWork(message, options) {
7124
8188
  conversationId,
7125
8189
  destination,
7126
8190
  leaseToken: lease.leaseToken,
7127
- nowMs: now2(options),
8191
+ nowMs: now3(options),
7128
8192
  options
7129
8193
  });
7130
8194
  return { status: "lost_lease" };
7131
8195
  }
7132
8196
  if (result.status === "yielded") {
7133
- const yieldNowMs = now2(options);
8197
+ const yieldNowMs = now3(options);
7134
8198
  const continuationMarked = await requestConversationContinuation2({
7135
8199
  conversationId,
7136
8200
  destination,
@@ -7165,7 +8229,7 @@ async function processConversationWork(message, options) {
7165
8229
  "conversation_work_cooperative_yield",
7166
8230
  { conversationId },
7167
8231
  {
7168
- "app.worker.elapsed_ms": now2(options) - startedAtMs,
8232
+ "app.worker.elapsed_ms": now3(options) - startedAtMs,
7169
8233
  "app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
7170
8234
  },
7171
8235
  "Conversation work yielded cooperatively"
@@ -7173,7 +8237,7 @@ async function processConversationWork(message, options) {
7173
8237
  return { status: "yielded" };
7174
8238
  }
7175
8239
  if (result.status === "deferred") {
7176
- const deferredNowMs = now2(options);
8240
+ const deferredNowMs = now3(options);
7177
8241
  const released = await releaseConversationWork2({
7178
8242
  conversationId,
7179
8243
  leaseToken: lease.leaseToken,
@@ -7202,7 +8266,7 @@ async function processConversationWork(message, options) {
7202
8266
  const failure = await recordFailedDeliveryAttempt({
7203
8267
  conversationId,
7204
8268
  leaseToken: lease.leaseToken,
7205
- nowMs: now2(options),
8269
+ nowMs: now3(options),
7206
8270
  messageIds: attemptMessageIds,
7207
8271
  options
7208
8272
  });
@@ -7211,7 +8275,7 @@ async function processConversationWork(message, options) {
7211
8275
  conversationId,
7212
8276
  leaseToken: lease.leaseToken,
7213
8277
  conversationStore: options.conversationStore,
7214
- nowMs: now2(options),
8278
+ nowMs: now3(options),
7215
8279
  state: options.state
7216
8280
  });
7217
8281
  return { status: "failed" };
@@ -7221,14 +8285,14 @@ async function processConversationWork(message, options) {
7221
8285
  conversationId,
7222
8286
  leaseToken: lease.leaseToken,
7223
8287
  conversationStore: options.conversationStore,
7224
- nowMs: now2(options),
8288
+ nowMs: now3(options),
7225
8289
  state: options.state
7226
8290
  });
7227
8291
  if (completion === "lost_lease") {
7228
8292
  return { status: "lost_lease" };
7229
8293
  }
7230
8294
  if (completion === "pending") {
7231
- const nudgeNowMs = now2(options);
8295
+ const nudgeNowMs = now3(options);
7232
8296
  const wake = await ensureConversationWake({
7233
8297
  conversationId,
7234
8298
  conversationStore: options.conversationStore,
@@ -7247,13 +8311,13 @@ async function processConversationWork(message, options) {
7247
8311
  "conversation_work_completed",
7248
8312
  { conversationId },
7249
8313
  {
7250
- "app.worker.elapsed_ms": now2(options) - startedAtMs
8314
+ "app.worker.elapsed_ms": now3(options) - startedAtMs
7251
8315
  },
7252
8316
  "Conversation work completed"
7253
8317
  );
7254
8318
  return { status: "completed" };
7255
8319
  } catch (error) {
7256
- const errorNowMs = now2(options);
8320
+ const errorNowMs = now3(options);
7257
8321
  let recoveryRecorded = false;
7258
8322
  try {
7259
8323
  const failure = attemptMessageIds.length > 0 ? await recordFailedDeliveryAttempt({
@@ -7318,7 +8382,7 @@ async function processConversationWork(message, options) {
7318
8382
  "conversation_work_failed",
7319
8383
  { conversationId },
7320
8384
  {
7321
- "app.worker.elapsed_ms": now2(options) - startedAtMs
8385
+ "app.worker.elapsed_ms": now3(options) - startedAtMs
7322
8386
  },
7323
8387
  "Conversation work failed"
7324
8388
  );
@@ -7898,10 +8962,10 @@ function createAcceptedSteeringDrain(hooks, options) {
7898
8962
  function buildLogContext(deps, args) {
7899
8963
  return {
7900
8964
  conversationId: args.threadId ?? args.runId,
7901
- slackThreadId: args.threadId,
7902
- slackUserId: args.actorId,
7903
- slackUserName: args.actorUserName,
7904
- slackChannelId: args.channelId,
8965
+ messageConversationId: args.threadId,
8966
+ userId: args.actorId,
8967
+ userName: args.actorUserName,
8968
+ destinationName: args.channelId,
7905
8969
  runId: args.runId,
7906
8970
  assistantUserName: deps.assistantUserName,
7907
8971
  modelId: deps.modelId
@@ -8620,9 +9684,9 @@ function createSlackTurnRuntime(deps) {
8620
9684
  error,
8621
9685
  "assistant_thread_started_handler_failed",
8622
9686
  {
8623
- slackThreadId: event.threadId,
8624
- slackUserId: event.userId,
8625
- slackChannelId: event.channelId,
9687
+ messageConversationId: event.threadId,
9688
+ userId: event.userId,
9689
+ destinationName: event.channelId,
8626
9690
  assistantUserName: deps.assistantUserName,
8627
9691
  modelId: deps.modelId
8628
9692
  },
@@ -8644,9 +9708,9 @@ function createSlackTurnRuntime(deps) {
8644
9708
  error,
8645
9709
  "assistant_context_changed_handler_failed",
8646
9710
  {
8647
- slackThreadId: event.threadId,
8648
- slackUserId: event.userId,
8649
- slackChannelId: event.channelId,
9711
+ messageConversationId: event.threadId,
9712
+ userId: event.userId,
9713
+ destinationName: event.channelId,
8650
9714
  assistantUserName: deps.assistantUserName,
8651
9715
  modelId: deps.modelId
8652
9716
  },
@@ -8670,9 +9734,9 @@ function createSubscribedReplyPolicy(deps) {
8670
9734
  logWarn(
8671
9735
  "subscribed_message_classifier_failed",
8672
9736
  {
8673
- slackThreadId: input.context.threadId,
8674
- slackUserId: input.context.actorId,
8675
- slackChannelId: input.context.channelId,
9737
+ messageConversationId: input.context.threadId,
9738
+ userId: input.context.actorId,
9739
+ destinationName: input.context.channelId,
8676
9740
  runId: input.context.runId,
8677
9741
  assistantUserName: botConfig.userName,
8678
9742
  modelId: botConfig.fastModelId
@@ -8881,9 +9945,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
8881
9945
  logWarn(
8882
9946
  "attachment_skipped_size_limit",
8883
9947
  {
8884
- slackThreadId: context.threadId,
8885
- slackUserId: context.actorId,
8886
- slackChannelId: context.channelId,
9948
+ messageConversationId: context.threadId,
9949
+ userId: context.actorId,
9950
+ destinationName: context.channelId,
8887
9951
  runId: context.runId,
8888
9952
  assistantUserName: botConfig.userName,
8889
9953
  modelId: standardModelId(botConfig)
@@ -8906,9 +9970,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
8906
9970
  logWarn(
8907
9971
  "image_attachment_processing_failed",
8908
9972
  {
8909
- slackThreadId: context.threadId,
8910
- slackUserId: context.actorId,
8911
- slackChannelId: context.channelId,
9973
+ messageConversationId: context.threadId,
9974
+ userId: context.actorId,
9975
+ destinationName: context.channelId,
8912
9976
  runId: context.runId,
8913
9977
  assistantUserName: botConfig.userName,
8914
9978
  modelId: botConfig.visionModelId ?? standardModelId(botConfig)
@@ -8925,9 +9989,9 @@ async function resolveUserAttachmentsWithDeps(attachments, context, deps) {
8925
9989
  logWarn(
8926
9990
  "attachment_resolution_failed",
8927
9991
  {
8928
- slackThreadId: context.threadId,
8929
- slackUserId: context.actorId,
8930
- slackChannelId: context.channelId,
9992
+ messageConversationId: context.threadId,
9993
+ userId: context.actorId,
9994
+ destinationName: context.channelId,
8931
9995
  runId: context.runId,
8932
9996
  assistantUserName: botConfig.userName,
8933
9997
  modelId: standardModelId(botConfig)
@@ -8975,9 +10039,9 @@ async function summarizeConversationImage(args, deps) {
8975
10039
  logWarn(
8976
10040
  "conversation_image_vision_failed",
8977
10041
  {
8978
- slackThreadId: args.context.threadId,
8979
- slackUserId: args.context.actorId,
8980
- slackChannelId: args.context.channelId,
10042
+ messageConversationId: args.context.threadId,
10043
+ userId: args.context.actorId,
10044
+ destinationName: args.context.channelId,
8981
10045
  runId: args.context.runId,
8982
10046
  assistantUserName: botConfig.userName,
8983
10047
  modelId: visionModelId
@@ -9031,9 +10095,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
9031
10095
  logWarn(
9032
10096
  "conversation_image_replies_fetch_failed",
9033
10097
  {
9034
- slackThreadId: context.threadId,
9035
- slackUserId: context.actorId,
9036
- slackChannelId: context.channelId,
10098
+ messageConversationId: context.threadId,
10099
+ userId: context.actorId,
10100
+ destinationName: context.channelId,
9037
10101
  runId: context.runId,
9038
10102
  assistantUserName: botConfig.userName,
9039
10103
  modelId: standardModelId(botConfig)
@@ -9098,9 +10162,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
9098
10162
  logWarn(
9099
10163
  "conversation_image_skipped_size_limit",
9100
10164
  {
9101
- slackThreadId: context.threadId,
9102
- slackUserId: context.actorId,
9103
- slackChannelId: context.channelId,
10165
+ messageConversationId: context.threadId,
10166
+ userId: context.actorId,
10167
+ destinationName: context.channelId,
9104
10168
  runId: context.runId,
9105
10169
  assistantUserName: botConfig.userName,
9106
10170
  modelId: standardModelId(botConfig)
@@ -9125,9 +10189,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
9125
10189
  logWarn(
9126
10190
  "conversation_image_download_failed",
9127
10191
  {
9128
- slackThreadId: context.threadId,
9129
- slackUserId: context.actorId,
9130
- slackChannelId: context.channelId,
10192
+ messageConversationId: context.threadId,
10193
+ userId: context.actorId,
10194
+ destinationName: context.channelId,
9131
10195
  runId: context.runId,
9132
10196
  assistantUserName: botConfig.userName,
9133
10197
  modelId: standardModelId(botConfig)
@@ -9145,9 +10209,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
9145
10209
  logWarn(
9146
10210
  "conversation_image_skipped_size_limit",
9147
10211
  {
9148
- slackThreadId: context.threadId,
9149
- slackUserId: context.actorId,
9150
- slackChannelId: context.channelId,
10212
+ messageConversationId: context.threadId,
10213
+ userId: context.actorId,
10214
+ destinationName: context.channelId,
9151
10215
  runId: context.runId,
9152
10216
  assistantUserName: botConfig.userName,
9153
10217
  modelId: standardModelId(botConfig)
@@ -9188,9 +10252,9 @@ async function hydrateConversationVisionContextWithDeps(conversation, context, d
9188
10252
  logInfo(
9189
10253
  "conversation_image_context_hydrated",
9190
10254
  {
9191
- slackThreadId: context.threadId,
9192
- slackUserId: context.actorId,
9193
- slackChannelId: context.channelId,
10255
+ messageConversationId: context.threadId,
10256
+ userId: context.actorId,
10257
+ destinationName: context.channelId,
9194
10258
  runId: context.runId,
9195
10259
  assistantUserName: botConfig.userName,
9196
10260
  modelId: standardModelId(botConfig)
@@ -9313,9 +10377,9 @@ function maybeUpdateAssistantTitle(args) {
9313
10377
  logWarn(
9314
10378
  "thread_title_generation_failed",
9315
10379
  {
9316
- slackThreadId: args.threadId,
9317
- slackUserId: args.actorId,
9318
- slackChannelId: args.channelId,
10380
+ messageConversationId: args.threadId,
10381
+ userId: args.actorId,
10382
+ destinationName: args.channelId,
9319
10383
  runId: args.runId,
9320
10384
  assistantUserName: args.assistantUserName,
9321
10385
  modelId: args.modelId
@@ -9347,9 +10411,9 @@ function maybeUpdateAssistantTitle(args) {
9347
10411
  logError(
9348
10412
  "thread_title_generation_permission_denied",
9349
10413
  {
9350
- slackThreadId: args.threadId,
9351
- slackUserId: args.actorId,
9352
- slackChannelId: args.channelId,
10414
+ messageConversationId: args.threadId,
10415
+ userId: args.actorId,
10416
+ destinationName: args.channelId,
9353
10417
  runId: args.runId,
9354
10418
  assistantUserName: args.assistantUserName,
9355
10419
  modelId: args.modelId
@@ -9361,9 +10425,9 @@ function maybeUpdateAssistantTitle(args) {
9361
10425
  logWarn(
9362
10426
  "thread_title_slack_update_failed",
9363
10427
  {
9364
- slackThreadId: args.threadId,
9365
- slackUserId: args.actorId,
9366
- slackChannelId: args.channelId,
10428
+ messageConversationId: args.threadId,
10429
+ userId: args.actorId,
10430
+ destinationName: args.channelId,
9367
10431
  runId: args.runId,
9368
10432
  assistantUserName: args.assistantUserName,
9369
10433
  modelId: args.modelId
@@ -9595,9 +10659,9 @@ function createReplyToThread(deps) {
9595
10659
  "chat.reply",
9596
10660
  {
9597
10661
  conversationId,
9598
- slackThreadId: threadId,
9599
- slackUserId: message.author.userId,
9600
- slackChannelId: channelId,
10662
+ messageConversationId: threadId,
10663
+ userId: message.author.userId,
10664
+ destinationName: channelId,
9601
10665
  runId,
9602
10666
  assistantUserName: botConfig.userName,
9603
10667
  modelId: standardModelId(botConfig)
@@ -9653,9 +10717,9 @@ function createReplyToThread(deps) {
9653
10717
  const turnId = buildDeterministicTurnId(message.id);
9654
10718
  const turnTraceContext = {
9655
10719
  conversationId,
9656
- slackThreadId: threadId,
9657
- slackUserId: message.author.userId,
9658
- slackChannelId: channelId,
10720
+ messageConversationId: threadId,
10721
+ userId: message.author.userId,
10722
+ destinationName: channelId,
9659
10723
  runId,
9660
10724
  assistantUserName: botConfig.userName,
9661
10725
  modelId: standardModelId(botConfig)
@@ -9676,23 +10740,16 @@ function createReplyToThread(deps) {
9676
10740
  actor.userId,
9677
10741
  providerDisplayName
9678
10742
  );
9679
- const footer = buildSlackReplyFooter({ conversationId });
9680
10743
  try {
10744
+ await beforeFirstResponsePost();
9681
10745
  if (channelId && threadTs) {
9682
- await postSlackApiReplyPosts({
9683
- beforePost: beforeFirstResponsePost,
10746
+ await sendSlackReply({
9684
10747
  channelId,
9685
- threadTs,
9686
- posts: [
9687
- {
9688
- text,
9689
- stage: "thread_reply"
9690
- }
9691
- ],
9692
- footer
10748
+ conversationId,
10749
+ text,
10750
+ threadTs
9693
10751
  });
9694
10752
  } else {
9695
- await beforeFirstResponsePost();
9696
10753
  await thread.post(buildSlackOutputMessage(text));
9697
10754
  }
9698
10755
  } catch (error) {
@@ -9988,7 +11045,7 @@ function createReplyToThread(deps) {
9988
11045
  });
9989
11046
  }
9990
11047
  if (actor?.userName) {
9991
- setTags({ slackUserName: actor.userName });
11048
+ setTags({ userName: actor.userName });
9992
11049
  }
9993
11050
  const turnAttachments = collectTurnAttachments(
9994
11051
  message,
@@ -10014,32 +11071,6 @@ function createReplyToThread(deps) {
10014
11071
  const compactingStatus = {
10015
11072
  text: "Compacting context"
10016
11073
  };
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
11074
  let persistedAtLeastOnce = false;
10044
11075
  let shouldPersistFailureState = true;
10045
11076
  let runResultHandled = false;
@@ -10059,18 +11090,45 @@ function createReplyToThread(deps) {
10059
11090
  turnCompletionNotified = true;
10060
11091
  };
10061
11092
  const deliverAssistantMessage = async (assistantMessage) => {
10062
- const posts = planSlackAssistantMessagePosts(assistantMessage.text);
10063
- if (posts.length === 0) {
11093
+ if (!assistantMessage.text.trim()) {
10064
11094
  return;
10065
11095
  }
10066
11096
  boundaryFailureCode = "delivery_failed";
10067
11097
  let slackTs;
10068
- for (const post of posts) {
10069
- const sentMessage = await postThreadReply(
10070
- buildSlackOutputMessage(post.text),
10071
- post.stage
11098
+ await beforeFirstResponsePost();
11099
+ try {
11100
+ if (channelId && threadTs && thread.adapter.name === "slack") {
11101
+ slackTs = await sendSlackReply({
11102
+ channelId,
11103
+ conversationId,
11104
+ text: assistantMessage.text,
11105
+ threadTs
11106
+ });
11107
+ } else {
11108
+ for (const text of splitSlackReplyText(assistantMessage.text)) {
11109
+ slackTs = (await thread.post(buildSlackOutputMessage(text))).id;
11110
+ }
11111
+ }
11112
+ } catch (error) {
11113
+ if (isRetryableSlackPostError(error)) {
11114
+ throw new RetryableDeliveryError(error);
11115
+ }
11116
+ const eventId = logException(
11117
+ error,
11118
+ "slack_thread_post_failed",
11119
+ turnTraceContext,
11120
+ {
11121
+ "app.slack.reply_stage": "thread_reply",
11122
+ ...messageTs ? { "messaging.message.id": messageTs } : {},
11123
+ ...getSlackErrorObservabilityAttributes(error)
11124
+ },
11125
+ "Failed to post Slack thread reply"
10072
11126
  );
10073
- slackTs = sentMessage.id;
11127
+ throw new ConversationTurnBoundaryError({
11128
+ cause: error,
11129
+ ...eventId ? { eventId } : {},
11130
+ failureCode: "delivery_failed"
11131
+ });
10074
11132
  }
10075
11133
  assistantMessageDelivered = true;
10076
11134
  const recordedMessageId = recordDeliveredAssistantMessage({
@@ -10402,9 +11460,9 @@ function createReplyToThread(deps) {
10402
11460
  }
10403
11461
  let reply = outcome.result;
10404
11462
  const diagnosticsContext = {
10405
- slackThreadId: threadId,
10406
- slackUserId: message.author.userId,
10407
- slackChannelId: channelId,
11463
+ messageConversationId: threadId,
11464
+ userId: message.author.userId,
11465
+ destinationName: channelId,
10408
11466
  runId,
10409
11467
  assistantUserName: botConfig.userName,
10410
11468
  modelId: reply.diagnostics.modelId
@@ -10454,13 +11512,6 @@ function createReplyToThread(deps) {
10454
11512
  sliceId: 1,
10455
11513
  messages: reply.piMessages,
10456
11514
  modelId: reply.diagnostics.modelId,
10457
- logContext: {
10458
- threadId,
10459
- actorId: slackActorId,
10460
- channelId,
10461
- runId,
10462
- assistantUserName: botConfig.userName
10463
- },
10464
11515
  actor: executionActor,
10465
11516
  surface: "slack"
10466
11517
  });
@@ -10747,6 +11798,11 @@ async function refreshAssistantThreadContext(event) {
10747
11798
 
10748
11799
  // src/chat/runtime/conversation-message.ts
10749
11800
  var NON_TEXT_MESSAGE_TEXT = "[non-text message]";
11801
+ function resourceEventType(entry) {
11802
+ if (!entry.raw || typeof entry.raw !== "object") return void 0;
11803
+ const raw = entry.raw;
11804
+ return raw.event_type === "resource_event" && typeof raw.resource_event_type === "string" ? raw.resource_event_type : void 0;
11805
+ }
10750
11806
  function resolveMessageText(args) {
10751
11807
  const text = normalizeConversationText(args.text);
10752
11808
  return text || NON_TEXT_MESSAGE_TEXT;
@@ -10771,6 +11827,7 @@ function toConversationMessage(args) {
10771
11827
  },
10772
11828
  meta: {
10773
11829
  attachmentCount: args.entry.attachments.length,
11830
+ eventType: resourceEventType(args.entry),
10774
11831
  explicitMention: args.explicitMention,
10775
11832
  imageAttachmentCount: imageAttachmentCount > 0 ? imageAttachmentCount : void 0,
10776
11833
  imagesHydrated: !messageHasPotentialImageAttachment,
@@ -11377,7 +12434,7 @@ async function continueSlackAgentRun(payload, options) {
11377
12434
  }
11378
12435
  }
11379
12436
  },
11380
- onSuccess: async (reply) => {
12437
+ commitResult: async (reply) => {
11381
12438
  await persistCompletedReplyState2({
11382
12439
  sessionRecord: activeSessionRecord,
11383
12440
  reply
@@ -11704,6 +12761,9 @@ function renderResourceEventNotificationText(subscription, event) {
11704
12761
  "Handling:",
11705
12762
  "- This is a subscribed conversation update, not a user-authored command.",
11706
12763
  "- Use the subscription intent to decide whether this event warrants action or a visible reply. Otherwise, stay silent.",
12764
+ "- Treat the trusted summary as sufficient evidence for the facts it reports. Do not verify or expand those facts with tools.",
12765
+ "- Use tools only when the subscription intent explicitly requires missing details or an action.",
12766
+ "- When replying, state what changed and the useful next step, if any.",
11707
12767
  "",
11708
12768
  "Subscription:",
11709
12769
  `- resource: ${subscription.label}`,
@@ -12210,6 +13270,7 @@ async function createApp(options) {
12210
13270
  return app;
12211
13271
  }
12212
13272
  export {
13273
+ JUNIOR_VERSION,
12213
13274
  createApp,
12214
13275
  defineJuniorPlugins
12215
13276
  };