@sentry/junior 0.109.0 → 0.111.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 (123) hide show
  1. package/dist/{agent-hooks-FHHVLNPA.js → agent-hooks-QQ2IWKOP.js} +12 -13
  2. package/dist/api/conversations/access.d.ts +9 -0
  3. package/dist/api/conversations/aggregate.d.ts +12 -1
  4. package/dist/api/conversations/archive.d.ts +3 -5
  5. package/dist/api/conversations/detail.d.ts +156 -7
  6. package/dist/api/conversations/events.d.ts +1 -1
  7. package/dist/api/conversations/list.d.ts +57 -6
  8. package/dist/api/conversations/projection.d.ts +6 -7
  9. package/dist/api/conversations/reporting.d.ts +10 -4
  10. package/dist/api/conversations/routes.d.ts +2 -1
  11. package/dist/api/conversations/shared.d.ts +1 -1
  12. package/dist/api/conversations/stats.d.ts +37 -6
  13. package/dist/api/conversations/stats.query.d.ts +1 -1
  14. package/dist/api/conversations/usage.d.ts +9 -0
  15. package/dist/api/http.d.ts +7 -0
  16. package/dist/api/locations/detail.d.ts +102 -6
  17. package/dist/api/locations/list.d.ts +43 -5
  18. package/dist/api/locations/query.d.ts +4 -2
  19. package/dist/api/locations/routes.d.ts +2 -1
  20. package/dist/api/people/list.d.ts +28 -6
  21. package/dist/api/people/list.query.d.ts +1 -1
  22. package/dist/api/people/profile.d.ts +96 -7
  23. package/dist/api/people/profile.query.d.ts +4 -2
  24. package/dist/api/people/routes.d.ts +2 -1
  25. package/dist/api/people/shared.d.ts +1219 -2
  26. package/dist/api/route.d.ts +18 -4
  27. package/dist/api/schema/common.d.ts +6 -0
  28. package/dist/api/{conversations/schema.d.ts → schema/conversation.d.ts} +31 -3
  29. package/dist/api/{locations/schema.d.ts → schema/location.d.ts} +5 -0
  30. package/dist/api/{people/schema.d.ts → schema/person.d.ts} +37 -45
  31. package/dist/api/schema.d.ts +8 -22
  32. package/dist/api/schema.js +10 -2
  33. package/dist/api.d.ts +5 -1
  34. package/dist/api.js +767 -384
  35. package/dist/app.js +591 -447
  36. package/dist/build/virtual-config.d.ts +2 -0
  37. package/dist/catalog-runtime-D22W6QEJ.js +16 -0
  38. package/dist/chat/agent/request.d.ts +3 -6
  39. package/dist/chat/agent/resume.d.ts +11 -12
  40. package/dist/chat/agent/sandbox.d.ts +36 -11
  41. package/dist/chat/agent/tools.d.ts +5 -8
  42. package/dist/chat/config.d.ts +8 -1
  43. package/dist/chat/conversations/history.d.ts +3 -0
  44. package/dist/chat/conversations/sql/privacy.d.ts +3 -15
  45. package/dist/chat/pi/messages.d.ts +2 -2
  46. package/dist/chat/pi/sdk.d.ts +1 -1
  47. package/dist/chat/pi/sql-model-usage.d.ts +4 -1
  48. package/dist/chat/plugins/agent-hooks.d.ts +2 -2
  49. package/dist/chat/runtime/agent-continue-runner.d.ts +7 -3
  50. package/dist/chat/runtime/slack-runtime.d.ts +1 -2
  51. package/dist/chat/runtime/thread-state.d.ts +3 -7
  52. package/dist/chat/runtime/turn-preparation.d.ts +2 -2
  53. package/dist/chat/sandbox/command-result.d.ts +91 -0
  54. package/dist/chat/sandbox/errors.d.ts +3 -1
  55. package/dist/chat/sandbox/ref.d.ts +5 -0
  56. package/dist/chat/sandbox/sandbox.d.ts +19 -46
  57. package/dist/chat/sandbox/session.d.ts +16 -20
  58. package/dist/chat/sandbox/skill-sync.d.ts +2 -2
  59. package/dist/chat/sandbox/workspace.d.ts +14 -12
  60. package/dist/chat/services/auth-pause.d.ts +4 -0
  61. package/dist/chat/services/context-compaction.d.ts +1 -0
  62. package/dist/chat/services/turn-result.d.ts +3 -4
  63. package/dist/chat/task-execution/queue-signing.d.ts +10 -8
  64. package/dist/chat/task-execution/queue.d.ts +9 -7
  65. package/dist/chat/task-execution/slack-work.d.ts +9 -7
  66. package/dist/chat/task-execution/state.d.ts +75 -22
  67. package/dist/chat/task-execution/store.d.ts +34 -3
  68. package/dist/chat/task-execution/worker.d.ts +0 -1
  69. package/dist/chat/tool-support/normalize-result.d.ts +2 -2
  70. package/dist/chat/tool-support/pi-tool-adapter.d.ts +2 -2
  71. package/dist/chat/tool-support/structured-result.d.ts +1 -2
  72. package/dist/chat/tools/sandbox/file-uploads.d.ts +1 -1
  73. package/dist/chat/{runtime → tools/sandbox}/generated-artifacts.d.ts +3 -3
  74. package/dist/chat/tools/types.d.ts +1 -1
  75. package/dist/chat/tools/web/fetch-content.d.ts +5 -0
  76. package/dist/chat/tools/web/image-generate.d.ts +2 -1
  77. package/dist/chat/tools/web/search.d.ts +1 -1
  78. package/dist/{chunk-CEA3A3UA.js → chunk-2ZTTXTSZ.js} +24 -1
  79. package/dist/{chunk-IVVGFRQQ.js → chunk-42WRQLJ6.js} +10 -38
  80. package/dist/{chunk-O3JMSZLI.js → chunk-6Y5L4BOM.js} +3 -5
  81. package/dist/{chunk-HO5NT6OE.js → chunk-B45FPP2A.js} +306 -31
  82. package/dist/{chunk-WHMOBKT7.js → chunk-BTRNMS6A.js} +2 -2
  83. package/dist/{chunk-PLB7MRCQ.js → chunk-C4MZ3GP5.js} +1 -1
  84. package/dist/{chunk-HHVY4SMP.js → chunk-FPK7XY3F.js} +98 -92
  85. package/dist/{chunk-RGX4V75M.js → chunk-IFAHK54X.js} +3 -3
  86. package/dist/{chunk-ISMWX44P.js → chunk-J7APPQIJ.js} +2 -2
  87. package/dist/{chunk-TMBRFULF.js → chunk-JQFJO5WH.js} +7 -1
  88. package/dist/{chunk-FWJRXN4F.js → chunk-L6JPKANW.js} +3 -139
  89. package/dist/{chunk-7Q6WCISF.js → chunk-MIAKQNYX.js} +43 -31
  90. package/dist/chunk-MPVES2IC.js +44 -0
  91. package/dist/{chunk-JNAVF45E.js → chunk-NQLWOTY4.js} +47 -19
  92. package/dist/{chunk-IXKSQGIO.js → chunk-OOLU34J4.js} +8860 -9137
  93. package/dist/{chunk-OUHNWH46.js → chunk-PYKKM4MM.js} +1 -1
  94. package/dist/{chunk-MXW4GL53.js → chunk-PZELBSHC.js} +8 -9
  95. package/dist/{chunk-QY636HZU.js → chunk-QQIVLZW4.js} +96 -17
  96. package/dist/{chunk-72OMGPDS.js → chunk-SL3ZKNLQ.js} +19 -18
  97. package/dist/{chunk-3SXUV423.js → chunk-WSRBFHPX.js} +1 -1
  98. package/dist/{chunk-TT6GMXNA.js → chunk-XQAYFRWT.js} +2 -2
  99. package/dist/{chunk-MGUMR35T.js → chunk-Z2W4SJZ7.js} +1 -1
  100. package/dist/{chunk-OCD3OZRK.js → chunk-Z4G3WCP3.js} +1 -1
  101. package/dist/{chunk-FENLIFJD.js → chunk-ZRZ4SVWG.js} +69 -10
  102. package/dist/cli/chat.js +23 -24
  103. package/dist/cli/check.js +8 -8
  104. package/dist/cli/plugins.js +15 -16
  105. package/dist/cli/snapshot-warmup.js +11 -11
  106. package/dist/cli/upgrade.js +9 -9
  107. package/dist/db/schema/conversations.d.ts +17 -0
  108. package/dist/db/schema.d.ts +17 -0
  109. package/dist/{db-V3NU72O2.js → db-WXQOQESG.js} +8 -9
  110. package/dist/instrumentation.js +4 -4
  111. package/dist/nitro.js +15 -10
  112. package/dist/reporting-schema.d.ts +46 -0
  113. package/dist/{runner-JYBCQBLK.js → runner-4QY4XH4B.js} +23 -33
  114. package/dist/{skills-U6MDAST7.js → skills-P5MU4EBX.js} +8 -8
  115. package/dist/{validation-72N3JVRU.js → validation-PQF5OP6T.js} +8 -8
  116. package/dist/version.js +1 -1
  117. package/migrations/0007_thick_sugar_man.sql +23 -0
  118. package/migrations/meta/0007_snapshot.json +1013 -0
  119. package/migrations/meta/_journal.json +7 -0
  120. package/package.json +6 -6
  121. package/dist/catalog-runtime-DMGJSLV2.js +0 -16
  122. package/dist/chunk-OB42YVAE.js +0 -16
  123. package/dist/chunk-SYAQIR5Z.js +0 -53
package/dist/app.js CHANGED
@@ -6,21 +6,23 @@ import {
6
6
  processPluginTask,
7
7
  requireTurnFailureEventId,
8
8
  scheduleSessionCompletedPluginTasks
9
- } from "./chunk-MXW4GL53.js";
9
+ } from "./chunk-PZELBSHC.js";
10
10
  import {
11
11
  GET,
12
12
  buildSentryConversationUrl,
13
13
  conversationVisibilityFromSlackChannelType,
14
- resolveRootVisibility,
15
14
  resolveSlackChannelTypeFromMessage,
16
15
  resolveSlackConversationContext
17
- } from "./chunk-FWJRXN4F.js";
18
- import "./chunk-CEA3A3UA.js";
16
+ } from "./chunk-L6JPKANW.js";
17
+ import "./chunk-2ZTTXTSZ.js";
19
18
  import {
19
+ ConversationQueueMessageRejectedError,
20
+ conversationQueueMessageSchema,
20
21
  getVercelConversationWorkQueue,
22
+ isConversationQueueMessageRejectedError,
21
23
  resolveConversationWorkQueueTopic,
22
24
  verifyConversationQueueMessage
23
- } from "./chunk-7Q6WCISF.js";
25
+ } from "./chunk-MIAKQNYX.js";
24
26
  import {
25
27
  PLUGIN_TASK_QUEUE_TOPIC,
26
28
  createVercelQueueClient,
@@ -29,7 +31,7 @@ import {
29
31
  import {
30
32
  validatePluginEgressCredentialHooks,
31
33
  validatePluginRegistrations
32
- } from "./chunk-MGUMR35T.js";
34
+ } from "./chunk-Z2W4SJZ7.js";
33
35
  import {
34
36
  defineJuniorPlugins,
35
37
  pluginCatalogConfigFromEnv,
@@ -82,7 +84,7 @@ import {
82
84
  splitSlackReplyText,
83
85
  startOAuthFlow,
84
86
  truncateStatusText
85
- } from "./chunk-IXKSQGIO.js";
87
+ } from "./chunk-OOLU34J4.js";
86
88
  import {
87
89
  ConversationTurnBoundaryError,
88
90
  CooperativeTurnYieldError,
@@ -141,8 +143,8 @@ import {
141
143
  turnHasReply,
142
144
  updateConversationStats,
143
145
  upsertConversationMessage
144
- } from "./chunk-72OMGPDS.js";
145
- import "./chunk-HO5NT6OE.js";
146
+ } from "./chunk-SL3ZKNLQ.js";
147
+ import "./chunk-B45FPP2A.js";
146
148
  import "./chunk-MU6HHZEN.js";
147
149
  import "./chunk-PDO5BLNM.js";
148
150
  import "./chunk-UIE3R5XU.js";
@@ -161,12 +163,12 @@ import {
161
163
  validatePlugins,
162
164
  verifyScheduledTaskCredentialSubject,
163
165
  verifySlackDirectCredentialSubject
164
- } from "./chunk-QY636HZU.js";
166
+ } from "./chunk-QQIVLZW4.js";
165
167
  import {
166
168
  acquireActiveLock,
167
169
  getDefaultRedisStateAdapterFor,
168
170
  getStateAdapter
169
- } from "./chunk-OUHNWH46.js";
171
+ } from "./chunk-PYKKM4MM.js";
170
172
  import {
171
173
  SlackActionError,
172
174
  downloadPrivateSlackFile,
@@ -177,20 +179,25 @@ import {
177
179
  normalizeSlackConversationId,
178
180
  runWithSlackInstallationToken,
179
181
  runWithWorkspaceTeamId
180
- } from "./chunk-RGX4V75M.js";
182
+ } from "./chunk-IFAHK54X.js";
181
183
  import {
182
184
  contextProvenance,
185
+ createSlackDestination,
186
+ destinationKey,
183
187
  getConversationEventStore,
184
188
  getConversationStore,
185
189
  getDb,
186
190
  getSqlExecutor,
187
191
  instructionProvenanceFor,
192
+ parseDestination,
193
+ requireSlackDestination,
194
+ sameDestination,
188
195
  withConversationEventLock
189
- } from "./chunk-FENLIFJD.js";
196
+ } from "./chunk-ZRZ4SVWG.js";
190
197
  import {
191
198
  juniorConversationEvents,
192
199
  juniorConversations
193
- } from "./chunk-TMBRFULF.js";
200
+ } from "./chunk-JQFJO5WH.js";
194
201
  import {
195
202
  sleep
196
203
  } from "./chunk-4ZNGQH7C.js";
@@ -199,6 +206,7 @@ import {
199
206
  botConfig,
200
207
  completeObject,
201
208
  completeText,
209
+ configureFunctionMaxDurationSeconds,
202
210
  escapeXml,
203
211
  getChatConfig,
204
212
  getRuntimeMetadata,
@@ -214,7 +222,7 @@ import {
214
222
  standardModelId,
215
223
  stripRuntimeTurnContext,
216
224
  trimTrailingAssistantMessages
217
- } from "./chunk-JNAVF45E.js";
225
+ } from "./chunk-NQLWOTY4.js";
218
226
  import {
219
227
  juniorDestinations
220
228
  } from "./chunk-VFUK3X5B.js";
@@ -223,26 +231,19 @@ import {
223
231
  readSlackRawMessageContext,
224
232
  resolveSlackChannelIdFromMessage,
225
233
  resolveSlackChannelIdFromThreadId
226
- } from "./chunk-O3JMSZLI.js";
227
- import {
228
- createSlackDestination,
229
- destinationKey,
230
- parseDestination,
231
- requireSlackDestination,
232
- sameDestination
233
- } from "./chunk-SYAQIR5Z.js";
234
+ } from "./chunk-6Y5L4BOM.js";
234
235
  import {
235
236
  discoverSkills
236
- } from "./chunk-WHMOBKT7.js";
237
+ } from "./chunk-BTRNMS6A.js";
237
238
  import {
238
239
  pluginCatalogRuntime
239
- } from "./chunk-3SXUV423.js";
240
+ } from "./chunk-WSRBFHPX.js";
240
241
  import {
241
242
  buildOAuthTokenRequest,
242
243
  credentialSubjectSchema,
243
244
  hasRequiredOAuthScope,
244
245
  parseOAuthTokenResponse
245
- } from "./chunk-TT6GMXNA.js";
246
+ } from "./chunk-XQAYFRWT.js";
246
247
  import {
247
248
  createActor,
248
249
  createSlackActor,
@@ -251,7 +252,7 @@ import {
251
252
  isUserActor,
252
253
  parseActorUserId,
253
254
  parseStoredSlackActor
254
- } from "./chunk-PLB7MRCQ.js";
255
+ } from "./chunk-C4MZ3GP5.js";
255
256
  import {
256
257
  buildTurnFailureResponse,
257
258
  createChatSdkLogger,
@@ -267,12 +268,14 @@ import {
267
268
  setTags,
268
269
  withContext,
269
270
  withSpan
270
- } from "./chunk-ISMWX44P.js";
271
+ } from "./chunk-J7APPQIJ.js";
271
272
  import {
272
273
  isRecord,
274
+ parseSlackChannelId,
275
+ parseSlackMessageTs,
273
276
  toOptionalNumber,
274
277
  toOptionalString
275
- } from "./chunk-OB42YVAE.js";
278
+ } from "./chunk-42WRQLJ6.js";
276
279
  import {
277
280
  continueTrace
278
281
  } from "./chunk-6GWA276C.js";
@@ -281,13 +284,11 @@ import {
281
284
  homeDir
282
285
  } from "./chunk-3V7OFJZC.js";
283
286
  import {
284
- JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE,
285
- parseSlackChannelId,
286
- parseSlackMessageTs
287
- } from "./chunk-IVVGFRQQ.js";
287
+ JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE
288
+ } from "./chunk-MPVES2IC.js";
288
289
  import {
289
290
  JUNIOR_VERSION
290
- } from "./chunk-OCD3OZRK.js";
291
+ } from "./chunk-Z4G3WCP3.js";
291
292
  import "./chunk-MLKGABMK.js";
292
293
 
293
294
  // src/app.ts
@@ -823,8 +824,7 @@ async function persistRuntimePatch(args) {
823
824
  await persistThreadStateById(args.threadId, {
824
825
  artifacts: args.artifacts,
825
826
  conversation: args.conversation,
826
- sandboxId: args.sandboxId,
827
- sandboxDependencyProfileHash: args.sandboxDependencyProfileHash
827
+ sandboxRef: args.sandboxRef
828
828
  });
829
829
  }
830
830
  async function markDispatch(args) {
@@ -946,8 +946,7 @@ async function runAgentDispatchSlice(callback, deps) {
946
946
  return;
947
947
  }
948
948
  let artifacts = coerceThreadArtifactsState(persisted);
949
- let sandboxId = typeof persisted.app_sandbox_id === "string" ? persisted.app_sandbox_id : void 0;
950
- let sandboxDependencyProfileHash = typeof persisted.app_sandbox_dependency_profile_hash === "string" ? persisted.app_sandbox_dependency_profile_hash : void 0;
949
+ let sandboxRef = getPersistedSandboxState(persisted);
951
950
  const channelConfiguration = getChannelConfigurationServiceById(
952
951
  dispatch.destination.channelId
953
952
  );
@@ -1052,24 +1051,19 @@ async function runAgentDispatchSlice(callback, deps) {
1052
1051
  },
1053
1052
  state: {
1054
1053
  artifactState: artifacts,
1055
- sandbox: {
1056
- sandboxId,
1057
- sandboxDependencyProfileHash
1058
- }
1054
+ sandboxRef
1059
1055
  },
1060
1056
  delivery: {
1061
1057
  onAssistantMessage: deliverAssistantMessage
1062
1058
  },
1063
1059
  durability: {
1064
- onSandboxAcquired: async (sandbox) => {
1065
- sandboxId = sandbox.sandboxId;
1066
- sandboxDependencyProfileHash = sandbox.sandboxDependencyProfileHash;
1060
+ onSandboxRefChanged: async (nextSandboxRef) => {
1061
+ sandboxRef = nextSandboxRef;
1067
1062
  await persistRuntimePatch({
1068
1063
  threadId: conversationId,
1069
1064
  conversation,
1070
1065
  artifacts,
1071
- sandboxId,
1072
- sandboxDependencyProfileHash
1066
+ sandboxRef
1073
1067
  });
1074
1068
  },
1075
1069
  onArtifactStateUpdated: async (nextArtifacts2) => {
@@ -1078,8 +1072,7 @@ async function runAgentDispatchSlice(callback, deps) {
1078
1072
  threadId: conversationId,
1079
1073
  conversation,
1080
1074
  artifacts,
1081
- sandboxId,
1082
- sandboxDependencyProfileHash
1075
+ sandboxRef
1083
1076
  });
1084
1077
  }
1085
1078
  }
@@ -1141,8 +1134,7 @@ async function runAgentDispatchSlice(callback, deps) {
1141
1134
  threadId: conversationId,
1142
1135
  conversation,
1143
1136
  artifacts: nextArtifacts,
1144
- sandboxId: reply.sandboxId ?? sandboxId,
1145
- sandboxDependencyProfileHash: reply.sandboxDependencyProfileHash ?? sandboxDependencyProfileHash
1137
+ sandboxRef: reply.sandboxRef ?? sandboxRef
1146
1138
  });
1147
1139
  });
1148
1140
  statePersisted = true;
@@ -1313,8 +1305,10 @@ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
1313
1305
 
1314
1306
  // src/chat/task-execution/state.ts
1315
1307
  import { randomUUID } from "crypto";
1308
+ import { destinationSchema as destinationSchema2 } from "@sentry/junior-plugin-api";
1309
+ import { z as z2 } from "zod";
1316
1310
  var CONVERSATION_PREFIX = "junior:conversation";
1317
- var CONVERSATION_SCHEMA_VERSION = 1;
1311
+ var CONVERSATION_SCHEMA_VERSION = 2;
1318
1312
  var CONVERSATION_ACTIVITY_INDEX_MAX_LENGTH = 1e4;
1319
1313
  var CONVERSATION_INDEX_LOCK_TTL_MS = 1e4;
1320
1314
  var CONVERSATION_INDEX_LOCK_WAIT_MS = 2e3;
@@ -1345,6 +1339,36 @@ var CONVERSATION_WORK_LEASE_TTL_MS = 9e4;
1345
1339
  var CONVERSATION_WORK_CHECK_IN_INTERVAL_MS = 15e3;
1346
1340
  var CONVERSATION_WORK_STALE_ENQUEUE_MS = 6e4;
1347
1341
  var CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS = 5;
1342
+ var inboundMessageSourceSchema = z2.enum([
1343
+ "api",
1344
+ "internal",
1345
+ "local",
1346
+ "plugin",
1347
+ "resource_event",
1348
+ "scheduler",
1349
+ "slack"
1350
+ ]);
1351
+ var agentInputSchema = z2.object({
1352
+ attachments: z2.array(z2.unknown()).optional(),
1353
+ authorId: z2.string().min(1).optional(),
1354
+ metadata: z2.record(z2.string(), z2.unknown()).optional(),
1355
+ text: z2.string()
1356
+ }).strict().refine((input) => input.text.trim() || input.attachments?.length, {
1357
+ message: "agent input requires text or attachments"
1358
+ });
1359
+ var inboundMessageDeliverySchema = z2.enum(["defer", "interrupt"]);
1360
+ var inboundMessageSchema = z2.object({
1361
+ attemptCount: z2.number().finite().optional(),
1362
+ conversationId: z2.string().refine((value) => value.trim().length > 0),
1363
+ createdAtMs: z2.number().finite(),
1364
+ delivery: inboundMessageDeliverySchema,
1365
+ destination: destinationSchema2,
1366
+ inboundMessageId: z2.string().refine((value) => value.trim().length > 0),
1367
+ injectedAtMs: z2.number().finite().optional(),
1368
+ input: agentInputSchema,
1369
+ receivedAtMs: z2.number().finite(),
1370
+ source: inboundMessageSourceSchema
1371
+ }).strict();
1348
1372
  function conversationKey(conversationId) {
1349
1373
  return `${CONVERSATION_PREFIX}:${conversationId}`;
1350
1374
  }
@@ -1364,10 +1388,16 @@ function inputHasAttachments(input) {
1364
1388
  return Array.isArray(input.attachments) && input.attachments.length > 0;
1365
1389
  }
1366
1390
  function upgradedPendingMessage(stored, duplicate) {
1367
- if (!inputHasAttachments(duplicate.input) || inputHasAttachments(stored.input)) {
1391
+ const promotesInterrupt = stored.delivery === "defer" && duplicate.delivery === "interrupt";
1392
+ const delivery = promotesInterrupt ? "interrupt" : stored.delivery;
1393
+ const input = promotesInterrupt ? {
1394
+ ...duplicate.input,
1395
+ ...inputHasAttachments(duplicate.input) || !inputHasAttachments(stored.input) ? {} : { attachments: stored.input.attachments }
1396
+ } : inputHasAttachments(duplicate.input) && !inputHasAttachments(stored.input) ? duplicate.input : stored.input;
1397
+ if (input === stored.input && delivery === stored.delivery) {
1368
1398
  return stored;
1369
1399
  }
1370
- return { ...stored, input: duplicate.input };
1400
+ return { ...stored, delivery, input };
1371
1401
  }
1372
1402
  function compareIndexDescending(left, right) {
1373
1403
  return right.score - left.score || right.conversationId.localeCompare(left.conversationId);
@@ -1379,10 +1409,8 @@ function uniqueStrings(values) {
1379
1409
  return [...new Set(values)];
1380
1410
  }
1381
1411
  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;
1412
+ const parsed = inboundMessageSourceSchema.safeParse(value);
1413
+ return parsed.success ? parsed.data : void 0;
1386
1414
  }
1387
1415
  function normalizeExecutionStatus(value) {
1388
1416
  if (value === "awaiting_resume" || value === "failed" || value === "idle" || value === "pending" || value === "running") {
@@ -1390,53 +1418,13 @@ function normalizeExecutionStatus(value) {
1390
1418
  }
1391
1419
  return void 0;
1392
1420
  }
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
- };
1421
+ function normalizeMessage(value, schemaVersion) {
1422
+ const candidate = schemaVersion === 1 && isRecord(value) && value.delivery === void 0 ? {
1423
+ ...value,
1424
+ delivery: "defer"
1425
+ } : value;
1426
+ const parsed = inboundMessageSchema.safeParse(candidate);
1427
+ return parsed.success ? parsed.data : void 0;
1440
1428
  }
1441
1429
  function isFinalAttempt(message) {
1442
1430
  return (message.attemptCount ?? 0) >= CONVERSATION_WORK_MAX_DELIVERY_ATTEMPTS - 1;
@@ -1462,7 +1450,7 @@ function normalizeLease(value) {
1462
1450
  expiresAtMs
1463
1451
  };
1464
1452
  }
1465
- function normalizeExecution(conversationId, value) {
1453
+ function normalizeExecution(conversationId, value, schemaVersion) {
1466
1454
  if (!isRecord(value)) {
1467
1455
  return void 0;
1468
1456
  }
@@ -1470,7 +1458,20 @@ function normalizeExecution(conversationId, value) {
1470
1458
  if (!status) {
1471
1459
  return void 0;
1472
1460
  }
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) : [];
1461
+ const pendingMessages2 = [];
1462
+ if (Array.isArray(value.pendingMessages)) {
1463
+ for (const rawMessage of value.pendingMessages) {
1464
+ const message = normalizeMessage(rawMessage, schemaVersion);
1465
+ if (!message || message.conversationId !== conversationId) {
1466
+ return void 0;
1467
+ }
1468
+ if (message.injectedAtMs !== void 0) {
1469
+ continue;
1470
+ }
1471
+ pendingMessages2.push(message);
1472
+ }
1473
+ pendingMessages2.sort(compareMessages);
1474
+ }
1474
1475
  const inboundMessageIds = Array.isArray(value.inboundMessageIds) ? uniqueStrings(
1475
1476
  value.inboundMessageIds.map((id) => typeof id === "string" ? id : void 0).filter((id) => Boolean(id))
1476
1477
  ) : [];
@@ -1492,14 +1493,19 @@ function normalizeExecution(conversationId, value) {
1492
1493
  };
1493
1494
  }
1494
1495
  function normalizeConversation(conversationId, value) {
1495
- if (!isRecord(value) || value.schemaVersion !== CONVERSATION_SCHEMA_VERSION) {
1496
+ if (!isRecord(value) || value.schemaVersion !== 1 && value.schemaVersion !== CONVERSATION_SCHEMA_VERSION) {
1496
1497
  return void 0;
1497
1498
  }
1499
+ const schemaVersion = value.schemaVersion;
1498
1500
  const storedConversationId = toOptionalString(value.conversationId);
1499
1501
  const createdAtMs = toOptionalNumber(value.createdAtMs);
1500
1502
  const lastActivityAtMs = toOptionalNumber(value.lastActivityAtMs);
1501
1503
  const updatedAtMs = toOptionalNumber(value.updatedAtMs);
1502
- const execution = normalizeExecution(conversationId, value.execution);
1504
+ const execution = normalizeExecution(
1505
+ conversationId,
1506
+ value.execution,
1507
+ schemaVersion
1508
+ );
1503
1509
  const destination = value.destination === void 0 ? void 0 : parseDestination(value.destination);
1504
1510
  if (storedConversationId !== conversationId || typeof createdAtMs !== "number" || typeof lastActivityAtMs !== "number" || typeof updatedAtMs !== "number" || !execution || value.destination !== void 0 && !destination) {
1505
1511
  return void 0;
@@ -2107,7 +2113,7 @@ async function startConversationWork(args) {
2107
2113
  {
2108
2114
  ...current.execution,
2109
2115
  lease,
2110
- status: "running",
2116
+ status: current.execution.status === "awaiting_resume" ? "awaiting_resume" : "running",
2111
2117
  runId: current.execution.runId ?? randomUUID(),
2112
2118
  lastEnqueuedAtMs: void 0
2113
2119
  },
@@ -2159,22 +2165,25 @@ async function drainConversationMailbox(args) {
2159
2165
  return pendingMessages(current);
2160
2166
  });
2161
2167
  if (pending.length === 0) {
2162
- return [];
2168
+ return { changed: false, messages: [] };
2163
2169
  }
2164
- const acknowledgedIds = await args.handle(pending);
2170
+ const result = await args.handle(pending);
2165
2171
  const pendingIds = new Set(
2166
2172
  pending.map((message) => message.inboundMessageId)
2167
2173
  );
2168
- for (const inboundMessageId of acknowledgedIds ?? []) {
2174
+ const acknowledgedIds = new Set(
2175
+ result ?? pending.map((message) => message.inboundMessageId)
2176
+ );
2177
+ for (const inboundMessageId of acknowledgedIds) {
2169
2178
  if (!pendingIds.has(inboundMessageId)) {
2170
2179
  throw new Error(
2171
- `Conversation mailbox acknowledgement is not pending for ${args.conversationId}`
2180
+ `Conversation mailbox drain result is not pending for ${args.conversationId}`
2172
2181
  );
2173
2182
  }
2174
2183
  }
2175
- const drainedIds = new Set(
2176
- acknowledgedIds ?? pending.map((message) => message.inboundMessageId)
2177
- );
2184
+ if (acknowledgedIds.size === 0) {
2185
+ return { changed: false, messages: [] };
2186
+ }
2178
2187
  await withConversationMutation(args, async (state, lock) => {
2179
2188
  const current = await readConversation(state, args.conversationId);
2180
2189
  if (!current || current.execution.lease?.token !== args.leaseToken) {
@@ -2183,7 +2192,7 @@ async function drainConversationMailbox(args) {
2183
2192
  );
2184
2193
  }
2185
2194
  const pendingMessages2 = current.execution.pendingMessages.filter(
2186
- (message) => !drainedIds.has(message.inboundMessageId)
2195
+ (message) => !acknowledgedIds.has(message.inboundMessageId)
2187
2196
  );
2188
2197
  await writeConversation(
2189
2198
  state,
@@ -2199,7 +2208,12 @@ async function drainConversationMailbox(args) {
2199
2208
  )
2200
2209
  );
2201
2210
  });
2202
- return pending.filter((message) => drainedIds.has(message.inboundMessageId));
2211
+ return {
2212
+ changed: true,
2213
+ messages: pending.filter(
2214
+ (message) => acknowledgedIds.has(message.inboundMessageId)
2215
+ )
2216
+ };
2203
2217
  }
2204
2218
  async function ackMessages(args) {
2205
2219
  const nowMs = args.nowMs ?? now();
@@ -2260,6 +2274,28 @@ async function requestConversationContinuation(args) {
2260
2274
  return true;
2261
2275
  });
2262
2276
  }
2277
+ async function beginConversationResume(args) {
2278
+ const nowMs = args.nowMs ?? now();
2279
+ return await withConversationMutation(args, async (state, lock) => {
2280
+ const current = await readConversation(state, args.conversationId);
2281
+ if (!current || current.execution.lease?.token !== args.leaseToken || current.execution.status !== "awaiting_resume") {
2282
+ return false;
2283
+ }
2284
+ await writeConversation(
2285
+ state,
2286
+ lock,
2287
+ withExecutionUpdate(
2288
+ current,
2289
+ {
2290
+ ...current.execution,
2291
+ status: "running"
2292
+ },
2293
+ nowMs
2294
+ )
2295
+ );
2296
+ return true;
2297
+ });
2298
+ }
2263
2299
  async function releaseConversationWork(args) {
2264
2300
  const nowMs = args.nowMs ?? now();
2265
2301
  return await withConversationMutation(args, async (state, lock) => {
@@ -2301,7 +2337,7 @@ async function completeConversationWork(args) {
2301
2337
  {
2302
2338
  ...current.execution,
2303
2339
  lease: void 0,
2304
- status: runnable ? "pending" : "idle",
2340
+ status: needsRun ? "awaiting_resume" : hasPending ? "pending" : "idle",
2305
2341
  runId: runnable ? current.execution.runId : void 0
2306
2342
  },
2307
2343
  nowMs
@@ -2410,7 +2446,7 @@ async function clearExpiredConversationLease(args) {
2410
2446
  {
2411
2447
  ...current.execution,
2412
2448
  lease: void 0,
2413
- status: "pending"
2449
+ status: "awaiting_resume"
2414
2450
  },
2415
2451
  nowMs
2416
2452
  )
@@ -2501,13 +2537,15 @@ async function ensureConversationWake(args) {
2501
2537
  if (!conversation.destination) {
2502
2538
  return { status: "no_work" };
2503
2539
  }
2540
+ if (args.replaceExistingWake !== true && conversation.execution.lease && conversation.execution.lease.expiresAtMs > nowMs) {
2541
+ return { status: "lease_active" };
2542
+ }
2504
2543
  if (args.replaceExistingWake !== true && hasRecentEnqueueMarker(conversation, nowMs)) {
2505
2544
  return { status: "already_enqueued" };
2506
2545
  }
2507
2546
  const queueResult = await args.queue.send(
2508
2547
  {
2509
- conversationId: args.conversationId,
2510
- destination: conversation.destination
2548
+ conversationId: args.conversationId
2511
2549
  },
2512
2550
  {
2513
2551
  delayMs: args.delayMs,
@@ -2613,10 +2651,10 @@ async function checkInConversationWork2(args) {
2613
2651
  }
2614
2652
  async function drainConversationMailbox2(args) {
2615
2653
  const result = await drainConversationMailbox(args);
2616
- if (result.length > 0) {
2654
+ if (result.changed) {
2617
2655
  await recordExecutionMetadata(args);
2618
2656
  }
2619
- return result;
2657
+ return result.messages;
2620
2658
  }
2621
2659
  async function ackMessages2(args) {
2622
2660
  const result = await ackMessages(args);
@@ -2628,6 +2666,13 @@ async function requestConversationContinuation2(args) {
2628
2666
  await recordExecutionMetadata(args);
2629
2667
  return result;
2630
2668
  }
2669
+ async function beginConversationResume2(args) {
2670
+ const result = await beginConversationResume(args);
2671
+ if (result) {
2672
+ await recordExecutionMetadata(args);
2673
+ }
2674
+ return result;
2675
+ }
2631
2676
  async function releaseConversationWork2(args) {
2632
2677
  const result = await releaseConversationWork(args);
2633
2678
  await recordExecutionMetadata(args);
@@ -3184,7 +3229,47 @@ async function GET2(request, waitUntil, options = {}) {
3184
3229
  import { timingSafeEqual as timingSafeEqual3 } from "crypto";
3185
3230
 
3186
3231
  // src/chat/conversations/sql/purge.ts
3187
- import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
3232
+ import { and, asc, eq as eq2, inArray, isNull, sql } from "drizzle-orm";
3233
+
3234
+ // src/chat/conversations/sql/privacy.ts
3235
+ import { eq } from "drizzle-orm";
3236
+ import { alias } from "drizzle-orm/pg-core";
3237
+ var privacyRoot = alias(juniorConversations, "privacy_root");
3238
+ async function readRootCandidate(executor, conversationId) {
3239
+ const rows = await executor.db().select({
3240
+ requestedConversationId: juniorConversations.conversationId,
3241
+ requestedParentConversationId: juniorConversations.parentConversationId,
3242
+ requestedRootConversationId: juniorConversations.rootConversationId,
3243
+ rootConversationId: privacyRoot.conversationId,
3244
+ rootDestinationId: privacyRoot.destinationId,
3245
+ rootParentConversationId: privacyRoot.parentConversationId,
3246
+ rootRootConversationId: privacyRoot.rootConversationId
3247
+ }).from(juniorConversations).leftJoin(
3248
+ privacyRoot,
3249
+ eq(privacyRoot.conversationId, juniorConversations.rootConversationId)
3250
+ ).where(eq(juniorConversations.conversationId, conversationId));
3251
+ const row = rows[0];
3252
+ return row?.requestedRootConversationId !== null && (row?.requestedParentConversationId !== null || row?.requestedRootConversationId === row?.requestedConversationId) && row?.rootConversationId === row.requestedRootConversationId && row.rootRootConversationId === row.rootConversationId && row.rootParentConversationId === null && row.rootDestinationId !== null ? {
3253
+ destinationId: row.rootDestinationId,
3254
+ rootConversationId: row.rootConversationId
3255
+ } : void 0;
3256
+ }
3257
+ async function readCandidateVisibility(executor, candidate) {
3258
+ const destinations = await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq(juniorDestinations.id, candidate.destinationId)).for("share");
3259
+ return {
3260
+ rootConversationId: candidate.rootConversationId,
3261
+ visibility: destinations[0]?.visibility ?? null
3262
+ };
3263
+ }
3264
+ async function resolveRootVisibility(executor, conversationId) {
3265
+ const candidate = await readRootCandidate(executor, conversationId);
3266
+ if (!candidate) {
3267
+ return { rootConversationId: conversationId, visibility: null };
3268
+ }
3269
+ return readCandidateVisibility(executor, candidate);
3270
+ }
3271
+
3272
+ // src/chat/conversations/sql/purge.ts
3188
3273
  async function discoverConversationTree(executor, root) {
3189
3274
  const all = /* @__PURE__ */ new Map([
3190
3275
  [root.conversationId, root]
@@ -3253,7 +3338,7 @@ async function selectExpiredRoots(executor, args) {
3253
3338
  visibility: juniorDestinations.visibility
3254
3339
  }).from(juniorConversations).leftJoin(
3255
3340
  juniorDestinations,
3256
- eq(juniorDestinations.id, juniorConversations.destinationId)
3341
+ eq2(juniorDestinations.id, juniorConversations.destinationId)
3257
3342
  ).where(
3258
3343
  and(
3259
3344
  isNull(juniorConversations.parentConversationId),
@@ -3274,7 +3359,7 @@ async function purgeConversationTree(executor, args) {
3274
3359
  const initialRoots = await executor.db().select({
3275
3360
  conversationId: juniorConversations.conversationId,
3276
3361
  parentConversationId: juniorConversations.parentConversationId
3277
- }).from(juniorConversations).where(eq(juniorConversations.conversationId, args.rootConversationId));
3362
+ }).from(juniorConversations).where(eq2(juniorConversations.conversationId, args.rootConversationId));
3278
3363
  const initialRoot = initialRoots[0];
3279
3364
  if (!initialRoot || args.retention && initialRoot.parentConversationId !== null) {
3280
3365
  return { purged: false, conversations: 0 };
@@ -3296,14 +3381,14 @@ async function purgeConversationTree(executor, args) {
3296
3381
  destinationId: juniorConversations.destinationId,
3297
3382
  parentConversationId: juniorConversations.parentConversationId
3298
3383
  }).from(juniorConversations).where(
3299
- eq(juniorConversations.conversationId, args.rootConversationId)
3384
+ eq2(juniorConversations.conversationId, args.rootConversationId)
3300
3385
  ).for("update");
3301
3386
  const root = roots[0];
3302
3387
  if (!root || args.retention && root.parentConversationId !== null) {
3303
3388
  return { purged: false, conversations: 0 };
3304
3389
  }
3305
3390
  const resolvedScrubMetadata = args.scrubMetadataFromRootVisibility ? (await resolveRootVisibility(executor, args.rootConversationId)).visibility !== "public" : args.scrubMetadata;
3306
- const destinations = root.destinationId ? await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq(juniorDestinations.id, root.destinationId)).for("share") : [];
3391
+ const destinations = root.destinationId ? await executor.db().select({ visibility: juniorDestinations.visibility }).from(juniorDestinations).where(eq2(juniorDestinations.id, root.destinationId)).for("share") : [];
3307
3392
  const isPublic = destinations[0]?.visibility === "public";
3308
3393
  const ids = tree.map((conversation) => conversation.conversationId);
3309
3394
  if (args.retention) {
@@ -4274,12 +4359,11 @@ function createResumeReplyContext(args, statusSession, delivery) {
4274
4359
  delivery,
4275
4360
  durability: {
4276
4361
  ...replyContext.durability,
4277
- onSandboxAcquired: async (sandbox) => {
4362
+ onSandboxRefChanged: async (sandboxRef) => {
4278
4363
  await persistThreadStateById(threadId, {
4279
- sandboxId: sandbox.sandboxId,
4280
- sandboxDependencyProfileHash: sandbox.sandboxDependencyProfileHash
4364
+ sandboxRef
4281
4365
  });
4282
- await replyContext.durability?.onSandboxAcquired?.(sandbox);
4366
+ await replyContext.durability?.onSandboxRefChanged?.(sandboxRef);
4283
4367
  },
4284
4368
  onArtifactStateUpdated: async (artifacts) => {
4285
4369
  await persistThreadStateById(threadId, { artifacts });
@@ -5028,7 +5112,7 @@ async function resumeAuthorizedMcpTurn(args) {
5028
5112
  state: {
5029
5113
  artifactState: lockedArtifacts,
5030
5114
  pendingAuth: lockedPendingAuth,
5031
- sandbox: getPersistedSandboxState(lockedState)
5115
+ sandboxRef: getPersistedSandboxState(lockedState)
5032
5116
  },
5033
5117
  durability: {
5034
5118
  recordPendingAuth: async (nextPendingAuth) => {
@@ -5626,7 +5710,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
5626
5710
  state: {
5627
5711
  artifactState: lockedArtifacts,
5628
5712
  pendingAuth: lockedPendingAuth,
5629
- sandbox: getPersistedSandboxState(lockedState)
5713
+ sandboxRef: getPersistedSandboxState(lockedState)
5630
5714
  },
5631
5715
  durability: {
5632
5716
  recordPendingAuth: async (nextPendingAuth) => {
@@ -6263,7 +6347,7 @@ import {
6263
6347
  Message,
6264
6348
  ThreadImpl
6265
6349
  } from "chat";
6266
- import { z as z3 } from "zod";
6350
+ import { z as z4 } from "zod";
6267
6351
 
6268
6352
  // src/chat/ingress/message-router.ts
6269
6353
  function normalizeIncomingSlackThreadId(threadId, message) {
@@ -6434,14 +6518,14 @@ async function ensureSlackMessageActorIdentity(message, teamId, lookupSlackUser2
6434
6518
  }
6435
6519
 
6436
6520
  // src/chat/slack/user.ts
6437
- import { z as z2 } from "zod";
6438
- var slackUserInfoSchema = z2.object({
6439
- ok: z2.literal(true),
6440
- user: z2.object({
6441
- name: z2.string().nullish(),
6442
- profile: z2.object({
6443
- email: z2.string().nullish(),
6444
- real_name: z2.string().nullish()
6521
+ import { z as z3 } from "zod";
6522
+ var slackUserInfoSchema = z3.object({
6523
+ ok: z3.literal(true),
6524
+ user: z3.object({
6525
+ name: z3.string().nullish(),
6526
+ profile: z3.object({
6527
+ email: z3.string().nullish(),
6528
+ real_name: z3.string().nullish()
6445
6529
  }).optional()
6446
6530
  })
6447
6531
  });
@@ -6529,14 +6613,14 @@ async function lookupSlackUser(teamId, userId) {
6529
6613
  }
6530
6614
 
6531
6615
  // src/chat/task-execution/slack-work.ts
6532
- var slackConversationRouteSchema = z3.enum(["mention", "subscribed"]);
6533
- var serializedDateSchema = z3.iso.datetime();
6534
- var mdastPointSchema = z3.object({
6535
- column: z3.number().int().positive(),
6536
- line: z3.number().int().positive(),
6537
- offset: z3.number().int().nonnegative().optional()
6616
+ var slackConversationRouteSchema = z4.enum(["mention", "subscribed"]);
6617
+ var serializedDateSchema = z4.iso.datetime();
6618
+ var mdastPointSchema = z4.object({
6619
+ column: z4.number().int().positive(),
6620
+ line: z4.number().int().positive(),
6621
+ offset: z4.number().int().nonnegative().optional()
6538
6622
  }).strict();
6539
- var mdastPositionSchema = z3.object({
6623
+ var mdastPositionSchema = z4.object({
6540
6624
  end: mdastPointSchema,
6541
6625
  start: mdastPointSchema
6542
6626
  }).strict();
@@ -6648,7 +6732,7 @@ function isFormattedContentNode(value) {
6648
6732
  return false;
6649
6733
  }
6650
6734
  }
6651
- var formattedContentSchema = z3.custom(
6735
+ var formattedContentSchema = z4.custom(
6652
6736
  (value) => {
6653
6737
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
6654
6738
  return false;
@@ -6658,79 +6742,79 @@ var formattedContentSchema = z3.custom(
6658
6742
  },
6659
6743
  "must be a valid formatted-content root"
6660
6744
  );
6661
- var serializedMessageSchema = z3.object({
6662
- _type: z3.literal("chat:Message"),
6663
- attachments: z3.array(
6664
- z3.object({
6665
- type: z3.enum(["image", "file", "video", "audio"]),
6666
- url: z3.string().optional(),
6667
- name: z3.string().optional(),
6668
- mimeType: z3.string().optional(),
6669
- size: z3.number().finite().optional(),
6670
- width: z3.number().finite().optional(),
6671
- height: z3.number().finite().optional(),
6672
- fetchMetadata: z3.record(z3.string(), z3.string()).optional()
6745
+ var serializedMessageSchema = z4.object({
6746
+ _type: z4.literal("chat:Message"),
6747
+ attachments: z4.array(
6748
+ z4.object({
6749
+ type: z4.enum(["image", "file", "video", "audio"]),
6750
+ url: z4.string().optional(),
6751
+ name: z4.string().optional(),
6752
+ mimeType: z4.string().optional(),
6753
+ size: z4.number().finite().optional(),
6754
+ width: z4.number().finite().optional(),
6755
+ height: z4.number().finite().optional(),
6756
+ fetchMetadata: z4.record(z4.string(), z4.string()).optional()
6673
6757
  }).strict()
6674
6758
  ),
6675
- author: z3.object({
6676
- userId: z3.string(),
6677
- userName: z3.string(),
6678
- fullName: z3.string(),
6679
- isBot: z3.union([z3.boolean(), z3.literal("unknown")]),
6680
- isMe: z3.boolean()
6759
+ author: z4.object({
6760
+ userId: z4.string(),
6761
+ userName: z4.string(),
6762
+ fullName: z4.string(),
6763
+ isBot: z4.union([z4.boolean(), z4.literal("unknown")]),
6764
+ isMe: z4.boolean()
6681
6765
  }).strict(),
6682
6766
  formatted: formattedContentSchema,
6683
- id: z3.string().min(1),
6684
- isMention: z3.boolean().optional(),
6685
- links: z3.array(
6686
- z3.object({
6687
- url: z3.string(),
6688
- title: z3.string().optional(),
6689
- description: z3.string().optional(),
6690
- imageUrl: z3.string().optional(),
6691
- siteName: z3.string().optional()
6767
+ id: z4.string().min(1),
6768
+ isMention: z4.boolean().optional(),
6769
+ links: z4.array(
6770
+ z4.object({
6771
+ url: z4.string(),
6772
+ title: z4.string().optional(),
6773
+ description: z4.string().optional(),
6774
+ imageUrl: z4.string().optional(),
6775
+ siteName: z4.string().optional()
6692
6776
  }).strict()
6693
6777
  ).optional(),
6694
- metadata: z3.object({
6778
+ metadata: z4.object({
6695
6779
  dateSent: serializedDateSchema,
6696
- edited: z3.boolean(),
6780
+ edited: z4.boolean(),
6697
6781
  editedAt: serializedDateSchema.optional()
6698
6782
  }).strict(),
6699
- raw: z3.unknown(),
6700
- text: z3.string(),
6701
- threadId: z3.string().min(1)
6783
+ raw: z4.unknown(),
6784
+ text: z4.string(),
6785
+ threadId: z4.string().min(1)
6702
6786
  }).strict();
6703
- var serializedThreadSchema = z3.object({
6704
- _type: z3.literal("chat:Thread"),
6705
- adapterName: z3.literal("slack"),
6706
- channelId: z3.string().min(1),
6707
- channelVisibility: z3.enum(["private", "workspace", "external", "unknown"]).optional(),
6787
+ var serializedThreadSchema = z4.object({
6788
+ _type: z4.literal("chat:Thread"),
6789
+ adapterName: z4.literal("slack"),
6790
+ channelId: z4.string().min(1),
6791
+ channelVisibility: z4.enum(["private", "workspace", "external", "unknown"]).optional(),
6708
6792
  currentMessage: serializedMessageSchema.optional(),
6709
- id: z3.string().min(1),
6710
- isDM: z3.boolean()
6793
+ id: z4.string().min(1),
6794
+ isDM: z4.boolean()
6711
6795
  }).strict();
6712
- var slackInstallationSchema = z3.object({
6713
- enterpriseId: z3.string().optional(),
6714
- isEnterpriseInstall: z3.boolean().optional(),
6715
- teamId: z3.string().optional()
6796
+ var slackInstallationSchema = z4.object({
6797
+ enterpriseId: z4.string().optional(),
6798
+ isEnterpriseInstall: z4.boolean().optional(),
6799
+ teamId: z4.string().optional()
6716
6800
  }).strict();
6717
- var slackConversationMessageMetadataBaseSchema = z3.object({
6801
+ var slackConversationMessageMetadataBaseSchema = z4.object({
6718
6802
  installation: slackInstallationSchema.optional(),
6719
6803
  message: serializedMessageSchema,
6720
- platform: z3.literal("slack"),
6804
+ platform: z4.literal("slack"),
6721
6805
  route: slackConversationRouteSchema,
6722
6806
  thread: serializedThreadSchema
6723
6807
  });
6724
- var slackConversationMessageMetadataSchema = z3.union([
6808
+ var slackConversationMessageMetadataSchema = z4.union([
6725
6809
  slackConversationMessageMetadataBaseSchema.strict(),
6726
6810
  slackConversationMessageMetadataBaseSchema.extend({
6727
- kind: z3.literal("resource_event"),
6728
- resourceEvent: z3.object({
6729
- eventKey: z3.string(),
6730
- eventType: z3.string(),
6731
- provider: z3.string(),
6732
- resourceRef: z3.string(),
6733
- subscriptionId: z3.string()
6811
+ kind: z4.literal("resource_event"),
6812
+ resourceEvent: z4.object({
6813
+ eventKey: z4.string(),
6814
+ eventType: z4.string(),
6815
+ provider: z4.string(),
6816
+ resourceRef: z4.string(),
6817
+ subscriptionId: z4.string()
6734
6818
  }).strict()
6735
6819
  }).strict()
6736
6820
  ]);
@@ -6820,6 +6904,7 @@ function createSlackResourceEventInboundMessage(input) {
6820
6904
  createdAtMs: input.event.occurredAtMs,
6821
6905
  destination,
6822
6906
  inboundMessageId: `resource-event:${input.subscription.id}:${input.event.eventKey}`,
6907
+ delivery: "defer",
6823
6908
  source: "resource_event",
6824
6909
  receivedAtMs: Date.now(),
6825
6910
  input: {
@@ -6864,10 +6949,6 @@ function routeForRecords(records) {
6864
6949
  return metadata.route === "mention";
6865
6950
  }) ? "mention" : "subscribed";
6866
6951
  }
6867
- function isSlackAssistantThreadUserMessage(message) {
6868
- const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
6869
- return raw?.channel_type === "im" && typeof raw.thread_ts === "string" && raw.thread_ts.trim().length > 0;
6870
- }
6871
6952
  function isResourceEventNotificationMessage(message) {
6872
6953
  const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
6873
6954
  return raw?.event_type === "resource_event";
@@ -6962,7 +7043,9 @@ function createSlackConversationWorker(options) {
6962
7043
  installation: { teamId: destination.teamId },
6963
7044
  state,
6964
7045
  task: async () => {
6965
- await options.resumeAwaitingContinuation(context.conversationId);
7046
+ await options.resumeAwaitingContinuation(context.conversationId, {
7047
+ shouldYield: context.shouldYield
7048
+ });
6966
7049
  }
6967
7050
  });
6968
7051
  return { status: "completed" };
@@ -7030,21 +7113,12 @@ function createSlackConversationWorker(options) {
7030
7113
  };
7031
7114
  const drainSteeringMessages = async (accept) => {
7032
7115
  await context.attempt.drain(async (pendingRecords) => {
7033
- const messages2 = pendingRecords.map((record) => {
7034
- const metadata = parseSlackMetadata(record.input.metadata);
7035
- if (!metadata) {
7036
- throw new Error(
7037
- "Conversation mailbox record is not Slack metadata"
7038
- );
7039
- }
7040
- const message = restoreMessage({ adapter, record });
7041
- return {
7042
- activeRequest: metadata.route === "mention" || isSlackAssistantThreadUserMessage(message),
7116
+ return await accept(
7117
+ pendingRecords.map((record) => ({
7043
7118
  inboundMessageId: record.inboundMessageId,
7044
- message
7045
- };
7046
- });
7047
- return await accept(messages2);
7119
+ message: restoreMessage({ adapter, record })
7120
+ }))
7121
+ );
7048
7122
  });
7049
7123
  };
7050
7124
  try {
@@ -7057,16 +7131,20 @@ function createSlackConversationWorker(options) {
7057
7131
  isFinalAttempt: context.attempt.isFinalAttempt,
7058
7132
  shouldYield: context.shouldYield
7059
7133
  });
7060
- return;
7134
+ } else {
7135
+ await options.runtime.handleSubscribedMessage(
7136
+ thread,
7137
+ latestMessage,
7138
+ {
7139
+ destination: context.destination,
7140
+ messageContext,
7141
+ drainSteeringMessages,
7142
+ ack,
7143
+ isFinalAttempt: context.attempt.isFinalAttempt,
7144
+ shouldYield: context.shouldYield
7145
+ }
7146
+ );
7061
7147
  }
7062
- await options.runtime.handleSubscribedMessage(thread, latestMessage, {
7063
- destination: context.destination,
7064
- messageContext,
7065
- drainSteeringMessages,
7066
- ack,
7067
- isFinalAttempt: context.attempt.isFinalAttempt,
7068
- shouldYield: context.shouldYield
7069
- });
7070
7148
  } catch (error) {
7071
7149
  if (isTurnInputDeferredError(error)) {
7072
7150
  return { status: "deferred" };
@@ -7105,6 +7183,7 @@ function buildSlackInboundMessage(args) {
7105
7183
  args.conversationId,
7106
7184
  args.message.id
7107
7185
  ].join(":"),
7186
+ delivery: args.message.isMention ? "interrupt" : "defer",
7108
7187
  source: "slack",
7109
7188
  createdAtMs: args.message.metadata.dateSent.getTime(),
7110
7189
  receivedAtMs: args.receivedAtMs,
@@ -7894,32 +7973,26 @@ import {
7894
7973
  registerDevConsumer
7895
7974
  } from "@vercel/queue";
7896
7975
 
7897
- // src/chat/task-execution/queue.ts
7898
- var ConversationQueueMessageRejectedError = class extends Error {
7899
- conversationId;
7900
- reason;
7901
- constructor(reason, message, options = {}) {
7902
- super(message);
7903
- this.name = "ConversationQueueMessageRejectedError";
7904
- this.reason = reason;
7905
- this.conversationId = options.conversationId;
7906
- }
7907
- };
7908
- function isConversationQueueMessageRejectedError(error) {
7909
- return error instanceof ConversationQueueMessageRejectedError;
7910
- }
7911
-
7912
7976
  // src/chat/task-execution/worker.ts
7913
7977
  var CONVERSATION_WORK_DEFER_DELAY_MS = 15e3;
7914
- var CONVERSATION_WORK_SOFT_YIELD_AFTER_MS = 24e4;
7915
7978
  function now3(options) {
7916
7979
  return options.nowMs?.() ?? Date.now();
7917
7980
  }
7981
+ function selectAttemptMessages(work) {
7982
+ const messages = work.messages;
7983
+ const interrupts = messages.filter(
7984
+ (message) => message.delivery === "interrupt"
7985
+ );
7986
+ if (interrupts.length > 0) {
7987
+ return interrupts;
7988
+ }
7989
+ return work.execution.status === "awaiting_resume" ? [] : messages;
7990
+ }
7918
7991
  function nudgeIdempotencyKey(reason, conversationId, nowMs) {
7919
7992
  return `${reason}:${conversationId}:${nowMs}`;
7920
7993
  }
7921
7994
  async function requestLostLeaseRecovery(args) {
7922
- const continuationMarked = await requestConversationContinuation2({
7995
+ const resumeRequested = await requestConversationContinuation2({
7923
7996
  conversationId: args.conversationId,
7924
7997
  destination: args.destination,
7925
7998
  leaseToken: args.leaseToken,
@@ -7927,7 +8000,7 @@ async function requestLostLeaseRecovery(args) {
7927
8000
  nowMs: args.nowMs,
7928
8001
  state: args.options.state
7929
8002
  });
7930
- if (!continuationMarked) {
8003
+ if (!resumeRequested) {
7931
8004
  return;
7932
8005
  }
7933
8006
  const released = await releaseConversationWork2({
@@ -8045,10 +8118,10 @@ async function processConversationWork(message, options) {
8045
8118
  }
8046
8119
  return { status: "no_work" };
8047
8120
  }
8048
- if (!initial.destination || !sameDestination(initial.destination, message.destination)) {
8121
+ if (!initial.destination) {
8049
8122
  throw new ConversationQueueMessageRejectedError(
8050
- "destination_mismatch",
8051
- `Conversation work queue destination changed for ${conversationId}`,
8123
+ "invalid_record",
8124
+ `Conversation work is missing destination context for ${conversationId}`,
8052
8125
  { conversationId }
8053
8126
  );
8054
8127
  }
@@ -8091,15 +8164,8 @@ async function processConversationWork(message, options) {
8091
8164
  return { status: "active" };
8092
8165
  }
8093
8166
  const startedAtMs = now3(options);
8094
- const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? CONVERSATION_WORK_SOFT_YIELD_AFTER_MS);
8095
- const leasedWork = await getConversationWorkState2({
8096
- conversationId,
8097
- state: options.state
8098
- });
8099
- const attemptMessages = leasedWork?.messages ?? initial.messages;
8100
- const attemptMessageIds = attemptMessages.map(
8101
- (message2) => message2.inboundMessageId
8102
- );
8167
+ const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? getChatConfig().conversationWorkSoftYieldAfterMs);
8168
+ let attemptMessageIds = [];
8103
8169
  let leaseLost = false;
8104
8170
  const markLeaseLost = () => {
8105
8171
  leaseLost = true;
@@ -8119,166 +8185,246 @@ async function processConversationWork(message, options) {
8119
8185
  },
8120
8186
  "Conversation work lease acquired"
8121
8187
  );
8122
- const drainInbox = (handle) => drainConversationMailbox2({
8188
+ const drain = (handle) => drainConversationMailbox2({
8123
8189
  conversationId,
8124
8190
  leaseToken: lease.leaseToken,
8125
8191
  conversationStore: options.conversationStore,
8126
- handle,
8192
+ handle: async (messages) => {
8193
+ const candidates = messages.filter(
8194
+ (message2) => message2.delivery === "interrupt"
8195
+ );
8196
+ if (candidates.length === 0) {
8197
+ return [];
8198
+ }
8199
+ return await handle(candidates) ?? candidates.map((message2) => message2.inboundMessageId);
8200
+ },
8127
8201
  nowMs: now3(options),
8128
8202
  state: options.state
8129
8203
  });
8130
- const ack = async () => {
8131
- const acknowledged = await ackMessages2({
8204
+ const shouldYield = () => leaseLost || now3(options) >= softYieldDeadlineMs;
8205
+ const checkIn = async () => {
8206
+ const checkedIn = await checkInConversationWork2({
8132
8207
  conversationId,
8133
- inboundMessageIds: attemptMessageIds,
8134
8208
  leaseToken: lease.leaseToken,
8135
8209
  conversationStore: options.conversationStore,
8136
8210
  nowMs: now3(options),
8137
8211
  state: options.state
8138
8212
  });
8139
- if (!acknowledged) {
8213
+ if (!checkedIn) {
8140
8214
  markLeaseLost();
8141
- throw new Error(
8142
- `Conversation work lease lost before inbox ack for ${conversationId}`
8143
- );
8144
8215
  }
8216
+ return checkedIn;
8145
8217
  };
8146
- const workerContext = {
8147
- attempt: {
8148
- ack,
8218
+ const yieldWork = async () => {
8219
+ const yieldNowMs = now3(options);
8220
+ await ensureConversationWake({
8149
8221
  conversationId,
8150
- destination,
8151
- drain: drainInbox,
8152
- isFinalAttempt: attemptMessages.some(
8153
- (message2) => isFinalAttempt(message2)
8154
- ),
8155
- messages: attemptMessages
8156
- },
8157
- conversationId,
8158
- destination,
8159
- shouldYield: () => leaseLost || now3(options) >= softYieldDeadlineMs,
8160
- checkIn: async () => {
8161
- const checkedIn = await checkInConversationWork2({
8162
- conversationId,
8163
- leaseToken: lease.leaseToken,
8164
- conversationStore: options.conversationStore,
8165
- nowMs: now3(options),
8166
- state: options.state
8167
- });
8168
- if (!checkedIn) {
8169
- markLeaseLost();
8170
- }
8171
- return checkedIn;
8222
+ conversationStore: options.conversationStore,
8223
+ idempotencyKey: nudgeIdempotencyKey("yield", conversationId, yieldNowMs),
8224
+ nowMs: yieldNowMs,
8225
+ queue: options.queue,
8226
+ replaceExistingWake: true,
8227
+ state: options.state
8228
+ });
8229
+ const released = await releaseConversationWork2({
8230
+ conversationId,
8231
+ leaseToken: lease.leaseToken,
8232
+ conversationStore: options.conversationStore,
8233
+ nowMs: yieldNowMs,
8234
+ state: options.state
8235
+ });
8236
+ if (!released) {
8237
+ return { status: "lost_lease" };
8172
8238
  }
8239
+ logInfo(
8240
+ "conversation_work_cooperative_yield",
8241
+ { conversationId },
8242
+ {
8243
+ "app.worker.elapsed_ms": now3(options) - startedAtMs,
8244
+ "app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
8245
+ },
8246
+ "Conversation work yielded cooperatively"
8247
+ );
8248
+ return { status: "yielded" };
8173
8249
  };
8174
8250
  try {
8175
- const result = await options.run(workerContext);
8176
- if (result.status === "lost_lease") {
8177
- await requestLostLeaseRecovery({
8251
+ let hasRun = false;
8252
+ while (true) {
8253
+ attemptMessageIds = [];
8254
+ const leasedWork = await getConversationWorkState2({
8178
8255
  conversationId,
8179
- destination,
8180
- leaseToken: lease.leaseToken,
8181
- nowMs: now3(options),
8182
- options
8183
- });
8184
- return { status: "lost_lease" };
8185
- }
8186
- if (leaseLost) {
8187
- await requestLostLeaseRecovery({
8188
- conversationId,
8189
- destination,
8190
- leaseToken: lease.leaseToken,
8191
- nowMs: now3(options),
8192
- options
8193
- });
8194
- return { status: "lost_lease" };
8195
- }
8196
- if (result.status === "yielded") {
8197
- const yieldNowMs = now3(options);
8198
- const continuationMarked = await requestConversationContinuation2({
8199
- conversationId,
8200
- destination,
8201
- leaseToken: lease.leaseToken,
8202
- conversationStore: options.conversationStore,
8203
- nowMs: yieldNowMs,
8204
8256
  state: options.state
8205
8257
  });
8206
- if (!continuationMarked) {
8258
+ if (!leasedWork || leasedWork.lease?.leaseToken !== lease.leaseToken || leaseLost) {
8259
+ markLeaseLost();
8260
+ await requestLostLeaseRecovery({
8261
+ conversationId,
8262
+ destination,
8263
+ leaseToken: lease.leaseToken,
8264
+ nowMs: now3(options),
8265
+ options
8266
+ });
8207
8267
  return { status: "lost_lease" };
8208
8268
  }
8209
- await ensureConversationWake({
8210
- conversationId,
8211
- conversationStore: options.conversationStore,
8212
- idempotencyKey: nudgeIdempotencyKey(
8213
- "yield",
8269
+ const resumePending = leasedWork.execution.status === "awaiting_resume";
8270
+ const attemptMessages = selectAttemptMessages(leasedWork);
8271
+ if (hasRun && shouldYield()) {
8272
+ if (resumePending) {
8273
+ return await yieldWork();
8274
+ }
8275
+ break;
8276
+ }
8277
+ if (resumePending && attemptMessages.length === 0) {
8278
+ const resumeStarted = await beginConversationResume2({
8214
8279
  conversationId,
8215
- yieldNowMs
8216
- ),
8217
- nowMs: yieldNowMs,
8218
- queue: options.queue,
8219
- state: options.state
8220
- });
8221
- await releaseConversationWork2({
8222
- conversationId,
8223
- leaseToken: lease.leaseToken,
8224
- conversationStore: options.conversationStore,
8225
- nowMs: yieldNowMs,
8226
- state: options.state
8227
- });
8228
- logInfo(
8229
- "conversation_work_cooperative_yield",
8230
- { conversationId },
8231
- {
8232
- "app.worker.elapsed_ms": now3(options) - startedAtMs,
8233
- "app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
8234
- },
8235
- "Conversation work yielded cooperatively"
8280
+ leaseToken: lease.leaseToken,
8281
+ conversationStore: options.conversationStore,
8282
+ nowMs: now3(options),
8283
+ state: options.state
8284
+ });
8285
+ if (!resumeStarted) {
8286
+ markLeaseLost();
8287
+ await requestLostLeaseRecovery({
8288
+ conversationId,
8289
+ destination,
8290
+ leaseToken: lease.leaseToken,
8291
+ nowMs: now3(options),
8292
+ options
8293
+ });
8294
+ return { status: "lost_lease" };
8295
+ }
8296
+ }
8297
+ attemptMessageIds = attemptMessages.map(
8298
+ (message2) => message2.inboundMessageId
8236
8299
  );
8237
- return { status: "yielded" };
8238
- }
8239
- if (result.status === "deferred") {
8240
- const deferredNowMs = now3(options);
8241
- const released = await releaseConversationWork2({
8300
+ const ack = async () => {
8301
+ const acknowledged = await ackMessages2({
8302
+ conversationId,
8303
+ inboundMessageIds: attemptMessageIds,
8304
+ leaseToken: lease.leaseToken,
8305
+ conversationStore: options.conversationStore,
8306
+ nowMs: now3(options),
8307
+ state: options.state
8308
+ });
8309
+ if (!acknowledged) {
8310
+ markLeaseLost();
8311
+ throw new Error(
8312
+ `Conversation work lease lost before inbox ack for ${conversationId}`
8313
+ );
8314
+ }
8315
+ };
8316
+ const workerContext = {
8317
+ attempt: {
8318
+ ack,
8319
+ conversationId,
8320
+ destination,
8321
+ drain,
8322
+ isFinalAttempt: attemptMessages.some(
8323
+ (message2) => isFinalAttempt(message2)
8324
+ ),
8325
+ messages: attemptMessages
8326
+ },
8242
8327
  conversationId,
8243
- leaseToken: lease.leaseToken,
8244
- conversationStore: options.conversationStore,
8245
- nowMs: deferredNowMs,
8246
- state: options.state
8247
- });
8248
- if (!released) {
8328
+ destination,
8329
+ shouldYield,
8330
+ checkIn
8331
+ };
8332
+ const result = await options.run(workerContext);
8333
+ hasRun = true;
8334
+ if (result.status === "lost_lease") {
8335
+ await requestLostLeaseRecovery({
8336
+ conversationId,
8337
+ destination,
8338
+ leaseToken: lease.leaseToken,
8339
+ nowMs: now3(options),
8340
+ options
8341
+ });
8249
8342
  return { status: "lost_lease" };
8250
8343
  }
8251
- const wake = await ensureConversationWake({
8252
- conversationId,
8253
- conversationStore: options.conversationStore,
8254
- idempotencyKey: nudgeIdempotencyKey(
8255
- "deferred",
8344
+ if (leaseLost) {
8345
+ await requestLostLeaseRecovery({
8256
8346
  conversationId,
8257
- deferredNowMs
8258
- ),
8259
- nowMs: deferredNowMs,
8260
- queue: options.queue,
8261
- state: options.state
8262
- });
8263
- return wake.status === "enqueued" ? { status: "pending_requeued" } : { status: "completed" };
8264
- }
8265
- if (attemptMessageIds.length > 0) {
8266
- const failure = await recordFailedDeliveryAttempt({
8267
- conversationId,
8268
- leaseToken: lease.leaseToken,
8269
- nowMs: now3(options),
8270
- messageIds: attemptMessageIds,
8271
- options
8272
- });
8273
- if (isTerminalFailure(failure)) {
8274
- await deadLetterAttempt2({
8347
+ destination,
8348
+ leaseToken: lease.leaseToken,
8349
+ nowMs: now3(options),
8350
+ options
8351
+ });
8352
+ return { status: "lost_lease" };
8353
+ }
8354
+ if (result.status === "yielded") {
8355
+ const resumeRequested = await requestConversationContinuation2({
8275
8356
  conversationId,
8357
+ destination,
8276
8358
  leaseToken: lease.leaseToken,
8277
8359
  conversationStore: options.conversationStore,
8278
8360
  nowMs: now3(options),
8279
8361
  state: options.state
8280
8362
  });
8281
- return { status: "failed" };
8363
+ if (!resumeRequested) {
8364
+ return { status: "lost_lease" };
8365
+ }
8366
+ return await yieldWork();
8367
+ }
8368
+ if (result.status === "deferred") {
8369
+ const deferredNowMs = now3(options);
8370
+ const released = await releaseConversationWork2({
8371
+ conversationId,
8372
+ leaseToken: lease.leaseToken,
8373
+ conversationStore: options.conversationStore,
8374
+ nowMs: deferredNowMs,
8375
+ state: options.state
8376
+ });
8377
+ if (!released) {
8378
+ return { status: "lost_lease" };
8379
+ }
8380
+ const wake = await ensureConversationWake({
8381
+ conversationId,
8382
+ conversationStore: options.conversationStore,
8383
+ idempotencyKey: nudgeIdempotencyKey(
8384
+ "deferred",
8385
+ conversationId,
8386
+ deferredNowMs
8387
+ ),
8388
+ nowMs: deferredNowMs,
8389
+ queue: options.queue,
8390
+ state: options.state
8391
+ });
8392
+ return wake.status === "enqueued" ? { status: "pending_requeued" } : { status: "completed" };
8393
+ }
8394
+ if (attemptMessageIds.length > 0) {
8395
+ const failure = await recordFailedDeliveryAttempt({
8396
+ conversationId,
8397
+ leaseToken: lease.leaseToken,
8398
+ nowMs: now3(options),
8399
+ messageIds: attemptMessageIds,
8400
+ options
8401
+ });
8402
+ if (isTerminalFailure(failure)) {
8403
+ await deadLetterAttempt2({
8404
+ conversationId,
8405
+ leaseToken: lease.leaseToken,
8406
+ conversationStore: options.conversationStore,
8407
+ nowMs: now3(options),
8408
+ state: options.state
8409
+ });
8410
+ return { status: "failed" };
8411
+ }
8412
+ if (failure.status === "lost_lease") {
8413
+ return { status: "lost_lease" };
8414
+ }
8415
+ if (failure.status === "recorded") {
8416
+ break;
8417
+ }
8418
+ }
8419
+ const next = await getConversationWorkState2({
8420
+ conversationId,
8421
+ state: options.state
8422
+ });
8423
+ if (!next || next.lease?.leaseToken !== lease.leaseToken) {
8424
+ return { status: "lost_lease" };
8425
+ }
8426
+ if (next.execution.status !== "awaiting_resume" && countPendingConversationMessages(next) === 0) {
8427
+ break;
8282
8428
  }
8283
8429
  }
8284
8430
  const completion = await completeConversationWork2({
@@ -8336,7 +8482,7 @@ async function processConversationWork(message, options) {
8336
8482
  state: options.state
8337
8483
  });
8338
8484
  } else {
8339
- const continuationMarked = await requestConversationContinuation2({
8485
+ const resumeRequested = await requestConversationContinuation2({
8340
8486
  conversationId,
8341
8487
  destination,
8342
8488
  leaseToken: lease.leaseToken,
@@ -8344,7 +8490,7 @@ async function processConversationWork(message, options) {
8344
8490
  nowMs: errorNowMs,
8345
8491
  state: options.state
8346
8492
  });
8347
- if (continuationMarked) {
8493
+ if (resumeRequested) {
8348
8494
  await ensureConversationWake({
8349
8495
  conversationId,
8350
8496
  conversationStore: options.conversationStore,
@@ -8355,6 +8501,7 @@ async function processConversationWork(message, options) {
8355
8501
  ),
8356
8502
  nowMs: errorNowMs,
8357
8503
  queue: options.queue,
8504
+ replaceExistingWake: true,
8358
8505
  state: options.state
8359
8506
  });
8360
8507
  }
@@ -8400,18 +8547,11 @@ async function processConversationWork(message, options) {
8400
8547
  var CONVERSATION_WORK_VISIBILITY_TIMEOUT_BUFFER_SECONDS = 30;
8401
8548
  var CONVERSATION_WORK_DEV_CONSUMER_GROUP = "junior_conversation_work_dev";
8402
8549
  function parseConversationQueueMessage(message) {
8403
- const destination = parseDestination(
8404
- message?.destination
8405
- );
8406
- if (!message || typeof message !== "object" || typeof message.conversationId !== "string" || !message.conversationId.trim() || !destination) {
8407
- throw new Error(
8408
- "Conversation queue message is missing destination context"
8409
- );
8550
+ const parsed = conversationQueueMessageSchema.safeParse(message);
8551
+ if (!parsed.success) {
8552
+ throw new Error("Conversation queue message is malformed");
8410
8553
  }
8411
- return {
8412
- conversationId: message.conversationId,
8413
- destination
8414
- };
8554
+ return parsed.data;
8415
8555
  }
8416
8556
  function resolveConversationWorkVisibilityTimeoutSeconds(functionMaxDurationSeconds = getChatConfig().functionMaxDurationSeconds) {
8417
8557
  return functionMaxDurationSeconds + CONVERSATION_WORK_VISIBILITY_TIMEOUT_BUFFER_SECONDS;
@@ -8563,14 +8703,14 @@ function registerVercelPluginTaskDevConsumer() {
8563
8703
  }
8564
8704
 
8565
8705
  // src/chat/services/subscribed-decision.ts
8566
- import { z as z4 } from "zod";
8567
- var replyDecisionSchema = z4.object({
8568
- should_reply: z4.boolean().describe("Whether Junior should respond to this thread message."),
8569
- should_unsubscribe: z4.boolean().describe(
8706
+ import { z as z5 } from "zod";
8707
+ var replyDecisionSchema = z5.object({
8708
+ should_reply: z5.boolean().describe("Whether Junior should respond to this thread message."),
8709
+ should_unsubscribe: z5.boolean().describe(
8570
8710
  "Whether Junior should unsubscribe from this thread because the user clearly asked it to stop participating."
8571
8711
  ),
8572
- confidence: z4.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
8573
- reason: z4.string().describe("Short reason for the decision; use an empty string if none.")
8712
+ confidence: z5.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
8713
+ reason: z5.string().describe("Short reason for the decision; use an empty string if none.")
8574
8714
  }).strict();
8575
8715
  var ROUTER_CONFIDENCE_THRESHOLD = 0.8;
8576
8716
  var ROUTER_CLASSIFIER_MAX_TOKENS = 240;
@@ -8944,12 +9084,14 @@ function createAcceptedSteeringDrain(hooks, options) {
8944
9084
  await hooks.drainSteeringMessages(async (messages) => {
8945
9085
  const selection = await options.selectMessages(messages, context);
8946
9086
  await options.onSkipped?.(selection.skipped);
8947
- const interrupted = selection.accepted.filter((accepted) => accepted.mode === "interrupt").map((accepted) => accepted.message);
9087
+ const interrupted = selection.accepted.map(
9088
+ (accepted) => accepted.message
9089
+ );
8948
9090
  await accept(getQueuedMessagesFromSlackMessages(interrupted, options));
8949
9091
  interruptedMessages = interrupted;
8950
9092
  await options.onAcceptedForProcessing?.(interrupted);
8951
9093
  return [
8952
- ...selection.accepted.filter((accepted) => accepted.mode === "interrupt").map((accepted) => accepted.inboundMessageId),
9094
+ ...selection.accepted.map((accepted) => accepted.inboundMessageId),
8953
9095
  ...selection.skipped.map((skipped) => skipped.inboundMessageId)
8954
9096
  ];
8955
9097
  });
@@ -9083,19 +9225,17 @@ function createSlackTurnRuntime(deps) {
9083
9225
  rawText: appendSlackLegacyAttachmentText(message.text, message.raw),
9084
9226
  userText: appendSlackLegacyAttachmentText(strippedUserText, message.raw)
9085
9227
  };
9086
- const isActiveRequest = candidate.activeRequest || Boolean(message.isMention);
9087
9228
  const decision = await deps.decideSubscribedReply({
9088
9229
  rawText: text.rawText,
9089
9230
  text: text.userText,
9090
9231
  conversationContext,
9091
9232
  hasAttachments: message.attachments.length > 0 || legacyAttachmentText !== "",
9092
- isExplicitMention: isActiveRequest,
9233
+ isExplicitMention: true,
9093
9234
  context
9094
9235
  });
9095
9236
  return {
9096
9237
  context,
9097
9238
  decision,
9098
- mode: isActiveRequest ? "interrupt" : "defer",
9099
9239
  text
9100
9240
  };
9101
9241
  };
@@ -9149,7 +9289,6 @@ function createSlackTurnRuntime(deps) {
9149
9289
  context: decision.context,
9150
9290
  decision: decision.decision,
9151
9291
  inboundMessageId: candidate.inboundMessageId,
9152
- mode: decision.mode,
9153
9292
  message: candidate.message,
9154
9293
  text: decision.text
9155
9294
  });
@@ -9171,8 +9310,7 @@ function createSlackTurnRuntime(deps) {
9171
9310
  return {
9172
9311
  accepted: selected.filter((message) => message.decision.shouldReply).map((message) => ({
9173
9312
  inboundMessageId: message.inboundMessageId,
9174
- message: message.message,
9175
- mode: message.mode
9313
+ message: message.message
9176
9314
  })),
9177
9315
  skipped: selected.filter((message) => !message.decision.shouldReply)
9178
9316
  };
@@ -10337,11 +10475,11 @@ function getSlackMessageTs(message) {
10337
10475
  }
10338
10476
 
10339
10477
  // src/chat/slack/action-token.ts
10340
- import { z as z5 } from "zod";
10341
- var slackActionTokenSchema = z5.string().trim().min(1).brand();
10342
- var slackMessageEnvelopeSchema = z5.object({
10343
- raw: z5.object({
10344
- action_token: z5.unknown().optional()
10478
+ import { z as z6 } from "zod";
10479
+ var slackActionTokenSchema = z6.string().trim().min(1).brand();
10480
+ var slackMessageEnvelopeSchema = z6.object({
10481
+ raw: z6.object({
10482
+ action_token: z6.unknown().optional()
10345
10483
  }).optional()
10346
10484
  });
10347
10485
  function readSlackActionToken(message) {
@@ -11336,10 +11474,7 @@ function createReplyToThread(deps) {
11336
11474
  state: {
11337
11475
  artifactState: preparedState.artifacts,
11338
11476
  pendingAuth: preparedState.conversation.processing.pendingAuth,
11339
- sandbox: {
11340
- sandboxId: preparedState.sandboxId,
11341
- sandboxDependencyProfileHash: preparedState.sandboxDependencyProfileHash
11342
- }
11477
+ sandboxRef: preparedState.sandboxRef
11343
11478
  },
11344
11479
  observers: {
11345
11480
  onStatus: (nextStatus) => status.update(nextStatus),
@@ -11352,10 +11487,9 @@ function createReplyToThread(deps) {
11352
11487
  onInputCommitted: options.ack,
11353
11488
  drainSteeringMessages,
11354
11489
  shouldYield: options.shouldYield,
11355
- onSandboxAcquired: async (sandbox) => {
11490
+ onSandboxRefChanged: async (sandboxRef) => {
11356
11491
  await persistThreadState(thread, {
11357
- sandboxId: sandbox.sandboxId,
11358
- sandboxDependencyProfileHash: sandbox.sandboxDependencyProfileHash
11492
+ sandboxRef
11359
11493
  });
11360
11494
  },
11361
11495
  onArtifactStateUpdated: async (artifacts) => {
@@ -11915,12 +12049,7 @@ async function seedConversationBackfill(thread, conversation, currentTurn) {
11915
12049
  function createPrepareTurnState(deps) {
11916
12050
  return async function prepareTurnState(args) {
11917
12051
  const existingState = await args.thread.state;
11918
- const existingSandboxId = existingState ? toOptionalString(
11919
- existingState.app_sandbox_id
11920
- ) : void 0;
11921
- const existingSandboxDependencyProfileHash = existingState ? toOptionalString(
11922
- existingState.app_sandbox_dependency_profile_hash
11923
- ) : void 0;
12052
+ const sandboxRef = getPersistedSandboxState(existingState ?? {});
11924
12053
  const artifacts = coerceThreadArtifactsState(existingState);
11925
12054
  const conversation = coerceThreadConversationState(existingState);
11926
12055
  const conversationId = args.context.threadId ?? args.context.runId;
@@ -11989,8 +12118,7 @@ function createPrepareTurnState(deps) {
11989
12118
  configuration,
11990
12119
  channelConfiguration,
11991
12120
  conversation,
11992
- sandboxId: existingSandboxId,
11993
- sandboxDependencyProfileHash: existingSandboxDependencyProfileHash,
12121
+ sandboxRef,
11994
12122
  conversationContext,
11995
12123
  userMessageAlreadyReplied,
11996
12124
  userMessageId
@@ -12298,7 +12426,7 @@ async function failUnresumableContinuation(args) {
12298
12426
  errorMessage: args.errorMessage
12299
12427
  });
12300
12428
  }
12301
- async function continueSlackAgentRun(payload, options) {
12429
+ async function continueSlackAgentRun(payload, options, runOptions = {}) {
12302
12430
  const thread = parseSlackThreadId(payload.conversationId);
12303
12431
  if (!thread) {
12304
12432
  throw new Error(
@@ -12351,7 +12479,7 @@ async function continueSlackAgentRun(payload, options) {
12351
12479
  const conversationContext = buildConversationContext(conversation, {
12352
12480
  excludeMessageId: userMessage.id
12353
12481
  });
12354
- const sandbox = getPersistedSandboxState(currentState);
12482
+ const sandboxRef = getPersistedSandboxState(currentState);
12355
12483
  const destination = requireSlackDestination(
12356
12484
  payload.destination,
12357
12485
  "Slack continuation"
@@ -12423,9 +12551,10 @@ async function continueSlackAgentRun(payload, options) {
12423
12551
  state: {
12424
12552
  artifactState: artifacts,
12425
12553
  pendingAuth: conversation.processing.pendingAuth,
12426
- sandbox
12554
+ sandboxRef
12427
12555
  },
12428
12556
  durability: {
12557
+ shouldYield: runOptions.shouldYield,
12429
12558
  recordPendingAuth: async (nextPendingAuth) => {
12430
12559
  conversation.processing.pendingAuth = nextPendingAuth;
12431
12560
  await persistThreadStateById(payload.conversationId, {
@@ -12588,7 +12717,11 @@ async function recoverStrandedRunningSession(args) {
12588
12717
  });
12589
12718
  return false;
12590
12719
  }
12591
- if (await continueSlackAgentRunWithLockRetry(request, args.options)) {
12720
+ if (await continueSlackAgentRunWithLockRetry(
12721
+ request,
12722
+ args.options,
12723
+ args.runOptions
12724
+ )) {
12592
12725
  return true;
12593
12726
  }
12594
12727
  await failUnresumableContinuation({
@@ -12599,13 +12732,14 @@ async function recoverStrandedRunningSession(args) {
12599
12732
  });
12600
12733
  return false;
12601
12734
  }
12602
- async function resumeAwaitingSlackContinuation(conversationId, options) {
12735
+ async function resumeAwaitingSlackContinuation(conversationId, options, runOptions = {}) {
12603
12736
  const summaries = await listAgentTurnSessionSummariesForConversation(conversationId);
12604
12737
  const newest = summaries[0];
12605
12738
  if (newest?.state === "running") {
12606
12739
  return await recoverStrandedRunningSession({
12607
12740
  conversationId,
12608
12741
  options,
12742
+ runOptions,
12609
12743
  summary: newest
12610
12744
  });
12611
12745
  }
@@ -12625,7 +12759,7 @@ async function resumeAwaitingSlackContinuation(conversationId, options) {
12625
12759
  });
12626
12760
  continue;
12627
12761
  }
12628
- if (await continueSlackAgentRunWithLockRetry(request, options)) {
12762
+ if (await continueSlackAgentRunWithLockRetry(request, options, runOptions)) {
12629
12763
  return true;
12630
12764
  }
12631
12765
  await failUnresumableContinuation({
@@ -12637,14 +12771,14 @@ async function resumeAwaitingSlackContinuation(conversationId, options) {
12637
12771
  }
12638
12772
  return false;
12639
12773
  }
12640
- async function continueSlackAgentRunWithLockRetry(payload, options) {
12774
+ async function continueSlackAgentRunWithLockRetry(payload, options, runOptions = {}) {
12641
12775
  const scheduleAgentContinue2 = options.scheduleAgentContinue ?? scheduleAgentContinue;
12642
12776
  for (const [attempt, delayMs] of [
12643
12777
  ...AGENT_CONTINUE_LOCK_RETRY_DELAYS_MS,
12644
12778
  void 0
12645
12779
  ].entries()) {
12646
12780
  try {
12647
- return await continueSlackAgentRun(payload, options);
12781
+ return await continueSlackAgentRun(payload, options, runOptions);
12648
12782
  } catch (error) {
12649
12783
  if (!(error instanceof ResumeTurnBusyError)) {
12650
12784
  throw error;
@@ -12739,10 +12873,14 @@ function createProductionConversationWorkOptions(options) {
12739
12873
  run: createSlackConversationWorker({
12740
12874
  getSlackAdapter: getProductionSlackAdapter,
12741
12875
  conversationStore,
12742
- resumeAwaitingContinuation: async (conversationId) => await resumeAwaitingSlackContinuation(conversationId, {
12743
- agentRunner,
12744
- scheduleSessionCompletedPluginTasks: services.replyExecutor?.scheduleSessionCompletedPluginTasks
12745
- }),
12876
+ resumeAwaitingContinuation: async (conversationId, runOptions) => await resumeAwaitingSlackContinuation(
12877
+ conversationId,
12878
+ {
12879
+ agentRunner,
12880
+ scheduleSessionCompletedPluginTasks: services.replyExecutor?.scheduleSessionCompletedPluginTasks
12881
+ },
12882
+ runOptions
12883
+ ),
12746
12884
  runtime
12747
12885
  })
12748
12886
  };
@@ -12872,6 +13010,7 @@ async function resolveVirtualConfig() {
12872
13010
  const mod = await import("#junior/config");
12873
13011
  return {
12874
13012
  createDashboardApp: mod.createDashboardApp,
13013
+ functionMaxDurationSeconds: mod.functionMaxDurationSeconds,
12875
13014
  dashboard: mod.dashboard,
12876
13015
  pluginSet: mod.pluginSet,
12877
13016
  plugins: mod.plugins,
@@ -13125,6 +13264,11 @@ function mountRoutes(app, routes) {
13125
13264
  }
13126
13265
  async function createApp(options) {
13127
13266
  const virtualConfig = await resolveVirtualConfig();
13267
+ if (virtualConfig?.functionMaxDurationSeconds !== void 0) {
13268
+ configureFunctionMaxDurationSeconds(
13269
+ virtualConfig.functionMaxDurationSeconds
13270
+ );
13271
+ }
13128
13272
  const dashboard = options?.dashboard ?? virtualConfig?.dashboard;
13129
13273
  const configuredPlugins = options?.plugins ?? virtualConfig?.pluginSet;
13130
13274
  const plugins = pluginRuntimeRegistrationsFromPluginSet(configuredPlugins);