@sentry/junior 0.110.0 → 0.112.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 (76) hide show
  1. package/dist/{agent-hooks-ELT4ODFF.js → agent-hooks-6MVTPKP4.js} +8 -8
  2. package/dist/api/people/profile.d.ts +1 -0
  3. package/dist/api/schema/person.d.ts +2 -0
  4. package/dist/api/schema.js +1 -1
  5. package/dist/api.js +13 -12
  6. package/dist/app.js +551 -401
  7. package/dist/build/virtual-config.d.ts +2 -0
  8. package/dist/{catalog-runtime-4GI22PXK.js → catalog-runtime-UEO25P2J.js} +5 -5
  9. package/dist/chat/agent/request.d.ts +3 -6
  10. package/dist/chat/agent/sandbox.d.ts +36 -11
  11. package/dist/chat/agent/tools.d.ts +5 -8
  12. package/dist/chat/config.d.ts +8 -1
  13. package/dist/chat/pi/sdk.d.ts +1 -1
  14. package/dist/chat/plugins/agent-hooks.d.ts +2 -2
  15. package/dist/chat/runtime/agent-continue-runner.d.ts +7 -3
  16. package/dist/chat/runtime/agent-run-outcome.d.ts +1 -0
  17. package/dist/chat/runtime/slack-runtime.d.ts +1 -2
  18. package/dist/chat/runtime/thread-state.d.ts +3 -7
  19. package/dist/chat/runtime/turn-preparation.d.ts +2 -2
  20. package/dist/chat/sandbox/command-result.d.ts +91 -0
  21. package/dist/chat/sandbox/errors.d.ts +3 -1
  22. package/dist/chat/sandbox/ref.d.ts +5 -0
  23. package/dist/chat/sandbox/sandbox.d.ts +19 -46
  24. package/dist/chat/sandbox/session.d.ts +16 -20
  25. package/dist/chat/sandbox/skill-sync.d.ts +2 -2
  26. package/dist/chat/sandbox/workspace.d.ts +14 -12
  27. package/dist/chat/services/auth-pause-response.d.ts +1 -1
  28. package/dist/chat/services/auth-pause.d.ts +2 -1
  29. package/dist/chat/services/mcp-auth-orchestration.d.ts +1 -1
  30. package/dist/chat/services/plugin-auth-orchestration.d.ts +2 -1
  31. package/dist/chat/services/turn-result.d.ts +3 -4
  32. package/dist/chat/task-execution/slack-work.d.ts +3 -1
  33. package/dist/chat/task-execution/state.d.ts +75 -22
  34. package/dist/chat/task-execution/store.d.ts +34 -3
  35. package/dist/chat/task-execution/worker.d.ts +0 -1
  36. package/dist/chat/tool-support/normalize-result.d.ts +2 -2
  37. package/dist/chat/tool-support/pi-tool-adapter.d.ts +2 -2
  38. package/dist/chat/tool-support/structured-result.d.ts +1 -2
  39. package/dist/chat/tools/resource-events.d.ts +3 -129
  40. package/dist/chat/tools/sandbox/file-uploads.d.ts +1 -1
  41. package/dist/chat/{runtime → tools/sandbox}/generated-artifacts.d.ts +3 -3
  42. package/dist/chat/tools/types.d.ts +1 -1
  43. package/dist/chat/tools/web/fetch-content.d.ts +5 -0
  44. package/dist/chat/tools/web/image-generate.d.ts +2 -1
  45. package/dist/chat/tools/web/search.d.ts +1 -1
  46. package/dist/{chunk-3S7YYHBI.js → chunk-2FJ6WEYV.js} +1 -1
  47. package/dist/{chunk-Y5FK3CNG.js → chunk-4EJO4HIC.js} +1 -1
  48. package/dist/{chunk-W6RJZG7F.js → chunk-66IP6WED.js} +306 -31
  49. package/dist/{chunk-W75ZPAGG.js → chunk-AU2JVUDF.js} +1 -1
  50. package/dist/{chunk-3UBQI73J.js → chunk-AWNDKI5K.js} +7 -8
  51. package/dist/{chunk-IYKPU7H6.js → chunk-CSS7JHEM.js} +1 -1
  52. package/dist/{chunk-SBOBTS27.js → chunk-GJG67YFS.js} +2 -2
  53. package/dist/{chunk-RHHRKSPI.js → chunk-GR4IB6OQ.js} +8729 -9024
  54. package/dist/{chunk-ZKIOWEFL.js → chunk-IV2O4HQJ.js} +1 -1
  55. package/dist/{chunk-FPK7XY3F.js → chunk-JE23YR3X.js} +1 -0
  56. package/dist/{chunk-L5XQPJ7T.js → chunk-NDQL4ESR.js} +2 -2
  57. package/dist/{chunk-WNOVRQ67.js → chunk-PK4ETCUQ.js} +46 -18
  58. package/dist/{chunk-4QU44P7Q.js → chunk-QIALGHRN.js} +2 -2
  59. package/dist/{chunk-C5XAFTHY.js → chunk-SKEMI4IF.js} +16 -13
  60. package/dist/{chunk-Y3H2DN6F.js → chunk-TZU3R5OC.js} +1 -1
  61. package/dist/{chunk-2T7TIEXI.js → chunk-VBSVNOLA.js} +11 -15
  62. package/dist/{chunk-GK47NBDE.js → chunk-X3SP5RDW.js} +1 -1
  63. package/dist/{chunk-3TV7OEHL.js → chunk-ZHP4KNF7.js} +1 -1
  64. package/dist/cli/chat.js +19 -19
  65. package/dist/cli/check.js +6 -6
  66. package/dist/cli/plugins.js +11 -11
  67. package/dist/cli/snapshot-warmup.js +8 -8
  68. package/dist/cli/upgrade.js +5 -5
  69. package/dist/{db-Y6W6CWWW.js → db-P5H5RVQV.js} +5 -5
  70. package/dist/instrumentation.js +2 -2
  71. package/dist/nitro.js +14 -8
  72. package/dist/{runner-BBFV5GLR.js → runner-B4ZDUO3R.js} +19 -28
  73. package/dist/{skills-AR4AF32L.js → skills-GWTOZ3XV.js} +6 -6
  74. package/dist/{validation-FBIEPTC2.js → validation-QFIC6XH6.js} +6 -6
  75. package/dist/version.js +1 -1
  76. package/package.json +5 -5
package/dist/app.js CHANGED
@@ -6,14 +6,14 @@ import {
6
6
  processPluginTask,
7
7
  requireTurnFailureEventId,
8
8
  scheduleSessionCompletedPluginTasks
9
- } from "./chunk-3UBQI73J.js";
9
+ } from "./chunk-AWNDKI5K.js";
10
10
  import {
11
11
  GET,
12
12
  buildSentryConversationUrl,
13
13
  conversationVisibilityFromSlackChannelType,
14
14
  resolveSlackChannelTypeFromMessage,
15
15
  resolveSlackConversationContext
16
- } from "./chunk-3S7YYHBI.js";
16
+ } from "./chunk-2FJ6WEYV.js";
17
17
  import "./chunk-2ZTTXTSZ.js";
18
18
  import {
19
19
  ConversationQueueMessageRejectedError,
@@ -31,7 +31,7 @@ import {
31
31
  import {
32
32
  validatePluginEgressCredentialHooks,
33
33
  validatePluginRegistrations
34
- } from "./chunk-Y5FK3CNG.js";
34
+ } from "./chunk-4EJO4HIC.js";
35
35
  import {
36
36
  defineJuniorPlugins,
37
37
  pluginCatalogConfigFromEnv,
@@ -84,7 +84,7 @@ import {
84
84
  splitSlackReplyText,
85
85
  startOAuthFlow,
86
86
  truncateStatusText
87
- } from "./chunk-RHHRKSPI.js";
87
+ } from "./chunk-GR4IB6OQ.js";
88
88
  import {
89
89
  ConversationTurnBoundaryError,
90
90
  CooperativeTurnYieldError,
@@ -143,8 +143,8 @@ import {
143
143
  turnHasReply,
144
144
  updateConversationStats,
145
145
  upsertConversationMessage
146
- } from "./chunk-C5XAFTHY.js";
147
- import "./chunk-W6RJZG7F.js";
146
+ } from "./chunk-SKEMI4IF.js";
147
+ import "./chunk-66IP6WED.js";
148
148
  import "./chunk-MU6HHZEN.js";
149
149
  import "./chunk-PDO5BLNM.js";
150
150
  import "./chunk-UIE3R5XU.js";
@@ -163,12 +163,12 @@ import {
163
163
  validatePlugins,
164
164
  verifyScheduledTaskCredentialSubject,
165
165
  verifySlackDirectCredentialSubject
166
- } from "./chunk-2T7TIEXI.js";
166
+ } from "./chunk-VBSVNOLA.js";
167
167
  import {
168
168
  acquireActiveLock,
169
169
  getDefaultRedisStateAdapterFor,
170
170
  getStateAdapter
171
- } from "./chunk-GK47NBDE.js";
171
+ } from "./chunk-X3SP5RDW.js";
172
172
  import {
173
173
  SlackActionError,
174
174
  downloadPrivateSlackFile,
@@ -179,7 +179,7 @@ import {
179
179
  normalizeSlackConversationId,
180
180
  runWithSlackInstallationToken,
181
181
  runWithWorkspaceTeamId
182
- } from "./chunk-L5XQPJ7T.js";
182
+ } from "./chunk-NDQL4ESR.js";
183
183
  import {
184
184
  contextProvenance,
185
185
  createSlackDestination,
@@ -193,7 +193,7 @@ import {
193
193
  requireSlackDestination,
194
194
  sameDestination,
195
195
  withConversationEventLock
196
- } from "./chunk-4QU44P7Q.js";
196
+ } from "./chunk-QIALGHRN.js";
197
197
  import {
198
198
  juniorConversationEvents,
199
199
  juniorConversations
@@ -206,6 +206,7 @@ import {
206
206
  botConfig,
207
207
  completeObject,
208
208
  completeText,
209
+ configureFunctionMaxDurationSeconds,
209
210
  escapeXml,
210
211
  getChatConfig,
211
212
  getRuntimeMetadata,
@@ -221,7 +222,7 @@ import {
221
222
  standardModelId,
222
223
  stripRuntimeTurnContext,
223
224
  trimTrailingAssistantMessages
224
- } from "./chunk-WNOVRQ67.js";
225
+ } from "./chunk-PK4ETCUQ.js";
225
226
  import {
226
227
  juniorDestinations
227
228
  } from "./chunk-VFUK3X5B.js";
@@ -233,16 +234,16 @@ import {
233
234
  } from "./chunk-6Y5L4BOM.js";
234
235
  import {
235
236
  discoverSkills
236
- } from "./chunk-SBOBTS27.js";
237
+ } from "./chunk-GJG67YFS.js";
237
238
  import {
238
239
  pluginCatalogRuntime
239
- } from "./chunk-3TV7OEHL.js";
240
+ } from "./chunk-ZHP4KNF7.js";
240
241
  import {
241
242
  buildOAuthTokenRequest,
242
243
  credentialSubjectSchema,
243
244
  hasRequiredOAuthScope,
244
245
  parseOAuthTokenResponse
245
- } from "./chunk-Y3H2DN6F.js";
246
+ } from "./chunk-TZU3R5OC.js";
246
247
  import {
247
248
  createActor,
248
249
  createSlackActor,
@@ -267,7 +268,7 @@ import {
267
268
  setTags,
268
269
  withContext,
269
270
  withSpan
270
- } from "./chunk-IYKPU7H6.js";
271
+ } from "./chunk-CSS7JHEM.js";
271
272
  import {
272
273
  isRecord,
273
274
  parseSlackChannelId,
@@ -284,10 +285,10 @@ import {
284
285
  } from "./chunk-3V7OFJZC.js";
285
286
  import {
286
287
  JUNIOR_PLUGIN_TASK_CALLBACK_ROUTE
287
- } from "./chunk-W75ZPAGG.js";
288
+ } from "./chunk-AU2JVUDF.js";
288
289
  import {
289
290
  JUNIOR_VERSION
290
- } from "./chunk-ZKIOWEFL.js";
291
+ } from "./chunk-IV2O4HQJ.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,6 +2537,9 @@ 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
  }
@@ -2612,10 +2651,10 @@ async function checkInConversationWork2(args) {
2612
2651
  }
2613
2652
  async function drainConversationMailbox2(args) {
2614
2653
  const result = await drainConversationMailbox(args);
2615
- if (result.length > 0) {
2654
+ if (result.changed) {
2616
2655
  await recordExecutionMetadata(args);
2617
2656
  }
2618
- return result;
2657
+ return result.messages;
2619
2658
  }
2620
2659
  async function ackMessages2(args) {
2621
2660
  const result = await ackMessages(args);
@@ -2627,6 +2666,13 @@ async function requestConversationContinuation2(args) {
2627
2666
  await recordExecutionMetadata(args);
2628
2667
  return result;
2629
2668
  }
2669
+ async function beginConversationResume2(args) {
2670
+ const result = await beginConversationResume(args);
2671
+ if (result) {
2672
+ await recordExecutionMetadata(args);
2673
+ }
2674
+ return result;
2675
+ }
2630
2676
  async function releaseConversationWork2(args) {
2631
2677
  const result = await releaseConversationWork(args);
2632
2678
  await recordExecutionMetadata(args);
@@ -4065,9 +4111,26 @@ async function startSlackProcessingReactionForMessage(args) {
4065
4111
  }
4066
4112
 
4067
4113
  // src/chat/services/auth-pause-response.ts
4068
- function buildAuthPauseResponse(slackUserId, providerDisplayName) {
4114
+ var MAX_AUTH_REQUEST_LENGTH = 200;
4115
+ function formatAuthRequest(requestText) {
4116
+ const normalized = requestText.replace(/\s+/g, " ").trim();
4117
+ if (!normalized) {
4118
+ return void 0;
4119
+ }
4120
+ const bounded = normalized.length > MAX_AUTH_REQUEST_LENGTH ? `${normalized.slice(0, MAX_AUTH_REQUEST_LENGTH - 1).trimEnd()}\u2026` : normalized;
4121
+ return bounded.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
4122
+ }
4123
+ function buildAuthPauseResponse(slackUserId, providerDisplayName, requestText) {
4069
4124
  const mention = slackUserId ? `<@${slackUserId}> ` : "";
4070
- return `${mention}I'll need you to authorize ${providerDisplayName}. I sent you a link.`;
4125
+ const request = requestText ? formatAuthRequest(requestText) : void 0;
4126
+ if (!request) {
4127
+ return `${mention}I'll need you to authorize ${providerDisplayName}. I sent you a link.`;
4128
+ }
4129
+ return `${mention}I need access to ${providerDisplayName} to continue.
4130
+
4131
+ *Why:* ${request}
4132
+
4133
+ I sent you a link.`;
4071
4134
  }
4072
4135
 
4073
4136
  // src/chat/runtime/request-deadline.ts
@@ -4313,12 +4376,11 @@ function createResumeReplyContext(args, statusSession, delivery) {
4313
4376
  delivery,
4314
4377
  durability: {
4315
4378
  ...replyContext.durability,
4316
- onSandboxAcquired: async (sandbox) => {
4379
+ onSandboxRefChanged: async (sandboxRef) => {
4317
4380
  await persistThreadStateById(threadId, {
4318
- sandboxId: sandbox.sandboxId,
4319
- sandboxDependencyProfileHash: sandbox.sandboxDependencyProfileHash
4381
+ sandboxRef
4320
4382
  });
4321
- await replyContext.durability?.onSandboxAcquired?.(sandbox);
4383
+ await replyContext.durability?.onSandboxRefChanged?.(sandboxRef);
4322
4384
  },
4323
4385
  onArtifactStateUpdated: async (artifacts) => {
4324
4386
  await persistThreadStateById(threadId, { artifacts });
@@ -4519,7 +4581,8 @@ async function resumeSlackTurn(args) {
4519
4581
  if (outcome.status === "awaiting_auth" && onAuthPause) {
4520
4582
  deferredAuthInfo = {
4521
4583
  providerDisplayName: outcome.providerDisplayName,
4522
- actorId: isUserActor(resumeActor) ? resumeActor.userId : void 0
4584
+ actorId: isUserActor(resumeActor) ? resumeActor.userId : void 0,
4585
+ ...outcome.requestText ? { requestText: outcome.requestText } : {}
4523
4586
  };
4524
4587
  deferredPauseHandler = async () => {
4525
4588
  await onAuthPause({
@@ -4673,7 +4736,8 @@ async function resumeSlackTurn(args) {
4673
4736
  runArgs.threadTs,
4674
4737
  buildAuthPauseResponse(
4675
4738
  deferredAuthInfo.actorId,
4676
- deferredAuthInfo.providerDisplayName
4739
+ deferredAuthInfo.providerDisplayName,
4740
+ deferredAuthInfo.requestText
4677
4741
  ),
4678
4742
  runArgs.conversationId
4679
4743
  );
@@ -5067,7 +5131,7 @@ async function resumeAuthorizedMcpTurn(args) {
5067
5131
  state: {
5068
5132
  artifactState: lockedArtifacts,
5069
5133
  pendingAuth: lockedPendingAuth,
5070
- sandbox: getPersistedSandboxState(lockedState)
5134
+ sandboxRef: getPersistedSandboxState(lockedState)
5071
5135
  },
5072
5136
  durability: {
5073
5137
  recordPendingAuth: async (nextPendingAuth) => {
@@ -5665,7 +5729,7 @@ async function resumeOAuthSessionRecordTurn(stored, options) {
5665
5729
  state: {
5666
5730
  artifactState: lockedArtifacts,
5667
5731
  pendingAuth: lockedPendingAuth,
5668
- sandbox: getPersistedSandboxState(lockedState)
5732
+ sandboxRef: getPersistedSandboxState(lockedState)
5669
5733
  },
5670
5734
  durability: {
5671
5735
  recordPendingAuth: async (nextPendingAuth) => {
@@ -6302,7 +6366,7 @@ import {
6302
6366
  Message,
6303
6367
  ThreadImpl
6304
6368
  } from "chat";
6305
- import { z as z3 } from "zod";
6369
+ import { z as z4 } from "zod";
6306
6370
 
6307
6371
  // src/chat/ingress/message-router.ts
6308
6372
  function normalizeIncomingSlackThreadId(threadId, message) {
@@ -6473,14 +6537,14 @@ async function ensureSlackMessageActorIdentity(message, teamId, lookupSlackUser2
6473
6537
  }
6474
6538
 
6475
6539
  // src/chat/slack/user.ts
6476
- import { z as z2 } from "zod";
6477
- var slackUserInfoSchema = z2.object({
6478
- ok: z2.literal(true),
6479
- user: z2.object({
6480
- name: z2.string().nullish(),
6481
- profile: z2.object({
6482
- email: z2.string().nullish(),
6483
- real_name: z2.string().nullish()
6540
+ import { z as z3 } from "zod";
6541
+ var slackUserInfoSchema = z3.object({
6542
+ ok: z3.literal(true),
6543
+ user: z3.object({
6544
+ name: z3.string().nullish(),
6545
+ profile: z3.object({
6546
+ email: z3.string().nullish(),
6547
+ real_name: z3.string().nullish()
6484
6548
  }).optional()
6485
6549
  })
6486
6550
  });
@@ -6568,14 +6632,14 @@ async function lookupSlackUser(teamId, userId) {
6568
6632
  }
6569
6633
 
6570
6634
  // src/chat/task-execution/slack-work.ts
6571
- var slackConversationRouteSchema = z3.enum(["mention", "subscribed"]);
6572
- var serializedDateSchema = z3.iso.datetime();
6573
- var mdastPointSchema = z3.object({
6574
- column: z3.number().int().positive(),
6575
- line: z3.number().int().positive(),
6576
- offset: z3.number().int().nonnegative().optional()
6635
+ var slackConversationRouteSchema = z4.enum(["mention", "subscribed"]);
6636
+ var serializedDateSchema = z4.iso.datetime();
6637
+ var mdastPointSchema = z4.object({
6638
+ column: z4.number().int().positive(),
6639
+ line: z4.number().int().positive(),
6640
+ offset: z4.number().int().nonnegative().optional()
6577
6641
  }).strict();
6578
- var mdastPositionSchema = z3.object({
6642
+ var mdastPositionSchema = z4.object({
6579
6643
  end: mdastPointSchema,
6580
6644
  start: mdastPointSchema
6581
6645
  }).strict();
@@ -6687,7 +6751,7 @@ function isFormattedContentNode(value) {
6687
6751
  return false;
6688
6752
  }
6689
6753
  }
6690
- var formattedContentSchema = z3.custom(
6754
+ var formattedContentSchema = z4.custom(
6691
6755
  (value) => {
6692
6756
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
6693
6757
  return false;
@@ -6697,79 +6761,79 @@ var formattedContentSchema = z3.custom(
6697
6761
  },
6698
6762
  "must be a valid formatted-content root"
6699
6763
  );
6700
- var serializedMessageSchema = z3.object({
6701
- _type: z3.literal("chat:Message"),
6702
- attachments: z3.array(
6703
- z3.object({
6704
- type: z3.enum(["image", "file", "video", "audio"]),
6705
- url: z3.string().optional(),
6706
- name: z3.string().optional(),
6707
- mimeType: z3.string().optional(),
6708
- size: z3.number().finite().optional(),
6709
- width: z3.number().finite().optional(),
6710
- height: z3.number().finite().optional(),
6711
- fetchMetadata: z3.record(z3.string(), z3.string()).optional()
6764
+ var serializedMessageSchema = z4.object({
6765
+ _type: z4.literal("chat:Message"),
6766
+ attachments: z4.array(
6767
+ z4.object({
6768
+ type: z4.enum(["image", "file", "video", "audio"]),
6769
+ url: z4.string().optional(),
6770
+ name: z4.string().optional(),
6771
+ mimeType: z4.string().optional(),
6772
+ size: z4.number().finite().optional(),
6773
+ width: z4.number().finite().optional(),
6774
+ height: z4.number().finite().optional(),
6775
+ fetchMetadata: z4.record(z4.string(), z4.string()).optional()
6712
6776
  }).strict()
6713
6777
  ),
6714
- author: z3.object({
6715
- userId: z3.string(),
6716
- userName: z3.string(),
6717
- fullName: z3.string(),
6718
- isBot: z3.union([z3.boolean(), z3.literal("unknown")]),
6719
- isMe: z3.boolean()
6778
+ author: z4.object({
6779
+ userId: z4.string(),
6780
+ userName: z4.string(),
6781
+ fullName: z4.string(),
6782
+ isBot: z4.union([z4.boolean(), z4.literal("unknown")]),
6783
+ isMe: z4.boolean()
6720
6784
  }).strict(),
6721
6785
  formatted: formattedContentSchema,
6722
- id: z3.string().min(1),
6723
- isMention: z3.boolean().optional(),
6724
- links: z3.array(
6725
- z3.object({
6726
- url: z3.string(),
6727
- title: z3.string().optional(),
6728
- description: z3.string().optional(),
6729
- imageUrl: z3.string().optional(),
6730
- siteName: z3.string().optional()
6786
+ id: z4.string().min(1),
6787
+ isMention: z4.boolean().optional(),
6788
+ links: z4.array(
6789
+ z4.object({
6790
+ url: z4.string(),
6791
+ title: z4.string().optional(),
6792
+ description: z4.string().optional(),
6793
+ imageUrl: z4.string().optional(),
6794
+ siteName: z4.string().optional()
6731
6795
  }).strict()
6732
6796
  ).optional(),
6733
- metadata: z3.object({
6797
+ metadata: z4.object({
6734
6798
  dateSent: serializedDateSchema,
6735
- edited: z3.boolean(),
6799
+ edited: z4.boolean(),
6736
6800
  editedAt: serializedDateSchema.optional()
6737
6801
  }).strict(),
6738
- raw: z3.unknown(),
6739
- text: z3.string(),
6740
- threadId: z3.string().min(1)
6802
+ raw: z4.unknown(),
6803
+ text: z4.string(),
6804
+ threadId: z4.string().min(1)
6741
6805
  }).strict();
6742
- var serializedThreadSchema = z3.object({
6743
- _type: z3.literal("chat:Thread"),
6744
- adapterName: z3.literal("slack"),
6745
- channelId: z3.string().min(1),
6746
- channelVisibility: z3.enum(["private", "workspace", "external", "unknown"]).optional(),
6806
+ var serializedThreadSchema = z4.object({
6807
+ _type: z4.literal("chat:Thread"),
6808
+ adapterName: z4.literal("slack"),
6809
+ channelId: z4.string().min(1),
6810
+ channelVisibility: z4.enum(["private", "workspace", "external", "unknown"]).optional(),
6747
6811
  currentMessage: serializedMessageSchema.optional(),
6748
- id: z3.string().min(1),
6749
- isDM: z3.boolean()
6812
+ id: z4.string().min(1),
6813
+ isDM: z4.boolean()
6750
6814
  }).strict();
6751
- var slackInstallationSchema = z3.object({
6752
- enterpriseId: z3.string().optional(),
6753
- isEnterpriseInstall: z3.boolean().optional(),
6754
- teamId: z3.string().optional()
6815
+ var slackInstallationSchema = z4.object({
6816
+ enterpriseId: z4.string().optional(),
6817
+ isEnterpriseInstall: z4.boolean().optional(),
6818
+ teamId: z4.string().optional()
6755
6819
  }).strict();
6756
- var slackConversationMessageMetadataBaseSchema = z3.object({
6820
+ var slackConversationMessageMetadataBaseSchema = z4.object({
6757
6821
  installation: slackInstallationSchema.optional(),
6758
6822
  message: serializedMessageSchema,
6759
- platform: z3.literal("slack"),
6823
+ platform: z4.literal("slack"),
6760
6824
  route: slackConversationRouteSchema,
6761
6825
  thread: serializedThreadSchema
6762
6826
  });
6763
- var slackConversationMessageMetadataSchema = z3.union([
6827
+ var slackConversationMessageMetadataSchema = z4.union([
6764
6828
  slackConversationMessageMetadataBaseSchema.strict(),
6765
6829
  slackConversationMessageMetadataBaseSchema.extend({
6766
- kind: z3.literal("resource_event"),
6767
- resourceEvent: z3.object({
6768
- eventKey: z3.string(),
6769
- eventType: z3.string(),
6770
- provider: z3.string(),
6771
- resourceRef: z3.string(),
6772
- subscriptionId: z3.string()
6830
+ kind: z4.literal("resource_event"),
6831
+ resourceEvent: z4.object({
6832
+ eventKey: z4.string(),
6833
+ eventType: z4.string(),
6834
+ provider: z4.string(),
6835
+ resourceRef: z4.string(),
6836
+ subscriptionId: z4.string()
6773
6837
  }).strict()
6774
6838
  }).strict()
6775
6839
  ]);
@@ -6859,6 +6923,7 @@ function createSlackResourceEventInboundMessage(input) {
6859
6923
  createdAtMs: input.event.occurredAtMs,
6860
6924
  destination,
6861
6925
  inboundMessageId: `resource-event:${input.subscription.id}:${input.event.eventKey}`,
6926
+ delivery: "defer",
6862
6927
  source: "resource_event",
6863
6928
  receivedAtMs: Date.now(),
6864
6929
  input: {
@@ -6903,10 +6968,6 @@ function routeForRecords(records) {
6903
6968
  return metadata.route === "mention";
6904
6969
  }) ? "mention" : "subscribed";
6905
6970
  }
6906
- function isSlackAssistantThreadUserMessage(message) {
6907
- const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
6908
- return raw?.channel_type === "im" && typeof raw.thread_ts === "string" && raw.thread_ts.trim().length > 0;
6909
- }
6910
6971
  function isResourceEventNotificationMessage(message) {
6911
6972
  const raw = message.raw && typeof message.raw === "object" ? message.raw : void 0;
6912
6973
  return raw?.event_type === "resource_event";
@@ -7001,7 +7062,9 @@ function createSlackConversationWorker(options) {
7001
7062
  installation: { teamId: destination.teamId },
7002
7063
  state,
7003
7064
  task: async () => {
7004
- await options.resumeAwaitingContinuation(context.conversationId);
7065
+ await options.resumeAwaitingContinuation(context.conversationId, {
7066
+ shouldYield: context.shouldYield
7067
+ });
7005
7068
  }
7006
7069
  });
7007
7070
  return { status: "completed" };
@@ -7069,21 +7132,12 @@ function createSlackConversationWorker(options) {
7069
7132
  };
7070
7133
  const drainSteeringMessages = async (accept) => {
7071
7134
  await context.attempt.drain(async (pendingRecords) => {
7072
- const messages2 = pendingRecords.map((record) => {
7073
- const metadata = parseSlackMetadata(record.input.metadata);
7074
- if (!metadata) {
7075
- throw new Error(
7076
- "Conversation mailbox record is not Slack metadata"
7077
- );
7078
- }
7079
- const message = restoreMessage({ adapter, record });
7080
- return {
7081
- activeRequest: metadata.route === "mention" || isSlackAssistantThreadUserMessage(message),
7135
+ return await accept(
7136
+ pendingRecords.map((record) => ({
7082
7137
  inboundMessageId: record.inboundMessageId,
7083
- message
7084
- };
7085
- });
7086
- return await accept(messages2);
7138
+ message: restoreMessage({ adapter, record })
7139
+ }))
7140
+ );
7087
7141
  });
7088
7142
  };
7089
7143
  try {
@@ -7096,16 +7150,20 @@ function createSlackConversationWorker(options) {
7096
7150
  isFinalAttempt: context.attempt.isFinalAttempt,
7097
7151
  shouldYield: context.shouldYield
7098
7152
  });
7099
- return;
7153
+ } else {
7154
+ await options.runtime.handleSubscribedMessage(
7155
+ thread,
7156
+ latestMessage,
7157
+ {
7158
+ destination: context.destination,
7159
+ messageContext,
7160
+ drainSteeringMessages,
7161
+ ack,
7162
+ isFinalAttempt: context.attempt.isFinalAttempt,
7163
+ shouldYield: context.shouldYield
7164
+ }
7165
+ );
7100
7166
  }
7101
- await options.runtime.handleSubscribedMessage(thread, latestMessage, {
7102
- destination: context.destination,
7103
- messageContext,
7104
- drainSteeringMessages,
7105
- ack,
7106
- isFinalAttempt: context.attempt.isFinalAttempt,
7107
- shouldYield: context.shouldYield
7108
- });
7109
7167
  } catch (error) {
7110
7168
  if (isTurnInputDeferredError(error)) {
7111
7169
  return { status: "deferred" };
@@ -7144,6 +7202,7 @@ function buildSlackInboundMessage(args) {
7144
7202
  args.conversationId,
7145
7203
  args.message.id
7146
7204
  ].join(":"),
7205
+ delivery: args.message.isMention ? "interrupt" : "defer",
7147
7206
  source: "slack",
7148
7207
  createdAtMs: args.message.metadata.dateSent.getTime(),
7149
7208
  receivedAtMs: args.receivedAtMs,
@@ -7935,15 +7994,24 @@ import {
7935
7994
 
7936
7995
  // src/chat/task-execution/worker.ts
7937
7996
  var CONVERSATION_WORK_DEFER_DELAY_MS = 15e3;
7938
- var CONVERSATION_WORK_SOFT_YIELD_AFTER_MS = 24e4;
7939
7997
  function now3(options) {
7940
7998
  return options.nowMs?.() ?? Date.now();
7941
7999
  }
8000
+ function selectAttemptMessages(work) {
8001
+ const messages = work.messages;
8002
+ const interrupts = messages.filter(
8003
+ (message) => message.delivery === "interrupt"
8004
+ );
8005
+ if (interrupts.length > 0) {
8006
+ return interrupts;
8007
+ }
8008
+ return work.execution.status === "awaiting_resume" ? [] : messages;
8009
+ }
7942
8010
  function nudgeIdempotencyKey(reason, conversationId, nowMs) {
7943
8011
  return `${reason}:${conversationId}:${nowMs}`;
7944
8012
  }
7945
8013
  async function requestLostLeaseRecovery(args) {
7946
- const continuationMarked = await requestConversationContinuation2({
8014
+ const resumeRequested = await requestConversationContinuation2({
7947
8015
  conversationId: args.conversationId,
7948
8016
  destination: args.destination,
7949
8017
  leaseToken: args.leaseToken,
@@ -7951,7 +8019,7 @@ async function requestLostLeaseRecovery(args) {
7951
8019
  nowMs: args.nowMs,
7952
8020
  state: args.options.state
7953
8021
  });
7954
- if (!continuationMarked) {
8022
+ if (!resumeRequested) {
7955
8023
  return;
7956
8024
  }
7957
8025
  const released = await releaseConversationWork2({
@@ -8115,15 +8183,8 @@ async function processConversationWork(message, options) {
8115
8183
  return { status: "active" };
8116
8184
  }
8117
8185
  const startedAtMs = now3(options);
8118
- const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? CONVERSATION_WORK_SOFT_YIELD_AFTER_MS);
8119
- const leasedWork = await getConversationWorkState2({
8120
- conversationId,
8121
- state: options.state
8122
- });
8123
- const attemptMessages = leasedWork?.messages ?? initial.messages;
8124
- const attemptMessageIds = attemptMessages.map(
8125
- (message2) => message2.inboundMessageId
8126
- );
8186
+ const softYieldDeadlineMs = startedAtMs + (options.softYieldAfterMs ?? getChatConfig().conversationWorkSoftYieldAfterMs);
8187
+ let attemptMessageIds = [];
8127
8188
  let leaseLost = false;
8128
8189
  const markLeaseLost = () => {
8129
8190
  leaseLost = true;
@@ -8143,166 +8204,246 @@ async function processConversationWork(message, options) {
8143
8204
  },
8144
8205
  "Conversation work lease acquired"
8145
8206
  );
8146
- const drainInbox = (handle) => drainConversationMailbox2({
8207
+ const drain = (handle) => drainConversationMailbox2({
8147
8208
  conversationId,
8148
8209
  leaseToken: lease.leaseToken,
8149
8210
  conversationStore: options.conversationStore,
8150
- handle,
8211
+ handle: async (messages) => {
8212
+ const candidates = messages.filter(
8213
+ (message2) => message2.delivery === "interrupt"
8214
+ );
8215
+ if (candidates.length === 0) {
8216
+ return [];
8217
+ }
8218
+ return await handle(candidates) ?? candidates.map((message2) => message2.inboundMessageId);
8219
+ },
8151
8220
  nowMs: now3(options),
8152
8221
  state: options.state
8153
8222
  });
8154
- const ack = async () => {
8155
- const acknowledged = await ackMessages2({
8223
+ const shouldYield = () => leaseLost || now3(options) >= softYieldDeadlineMs;
8224
+ const checkIn = async () => {
8225
+ const checkedIn = await checkInConversationWork2({
8156
8226
  conversationId,
8157
- inboundMessageIds: attemptMessageIds,
8158
8227
  leaseToken: lease.leaseToken,
8159
8228
  conversationStore: options.conversationStore,
8160
8229
  nowMs: now3(options),
8161
8230
  state: options.state
8162
8231
  });
8163
- if (!acknowledged) {
8232
+ if (!checkedIn) {
8164
8233
  markLeaseLost();
8165
- throw new Error(
8166
- `Conversation work lease lost before inbox ack for ${conversationId}`
8167
- );
8168
8234
  }
8235
+ return checkedIn;
8169
8236
  };
8170
- const workerContext = {
8171
- attempt: {
8172
- ack,
8237
+ const yieldWork = async () => {
8238
+ const yieldNowMs = now3(options);
8239
+ await ensureConversationWake({
8173
8240
  conversationId,
8174
- destination,
8175
- drain: drainInbox,
8176
- isFinalAttempt: attemptMessages.some(
8177
- (message2) => isFinalAttempt(message2)
8178
- ),
8179
- messages: attemptMessages
8180
- },
8181
- conversationId,
8182
- destination,
8183
- shouldYield: () => leaseLost || now3(options) >= softYieldDeadlineMs,
8184
- checkIn: async () => {
8185
- const checkedIn = await checkInConversationWork2({
8186
- conversationId,
8187
- leaseToken: lease.leaseToken,
8188
- conversationStore: options.conversationStore,
8189
- nowMs: now3(options),
8190
- state: options.state
8191
- });
8192
- if (!checkedIn) {
8193
- markLeaseLost();
8194
- }
8195
- return checkedIn;
8241
+ conversationStore: options.conversationStore,
8242
+ idempotencyKey: nudgeIdempotencyKey("yield", conversationId, yieldNowMs),
8243
+ nowMs: yieldNowMs,
8244
+ queue: options.queue,
8245
+ replaceExistingWake: true,
8246
+ state: options.state
8247
+ });
8248
+ const released = await releaseConversationWork2({
8249
+ conversationId,
8250
+ leaseToken: lease.leaseToken,
8251
+ conversationStore: options.conversationStore,
8252
+ nowMs: yieldNowMs,
8253
+ state: options.state
8254
+ });
8255
+ if (!released) {
8256
+ return { status: "lost_lease" };
8196
8257
  }
8258
+ logInfo(
8259
+ "conversation_work_cooperative_yield",
8260
+ { conversationId },
8261
+ {
8262
+ "app.worker.elapsed_ms": now3(options) - startedAtMs,
8263
+ "app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
8264
+ },
8265
+ "Conversation work yielded cooperatively"
8266
+ );
8267
+ return { status: "yielded" };
8197
8268
  };
8198
8269
  try {
8199
- const result = await options.run(workerContext);
8200
- if (result.status === "lost_lease") {
8201
- await requestLostLeaseRecovery({
8202
- conversationId,
8203
- destination,
8204
- leaseToken: lease.leaseToken,
8205
- nowMs: now3(options),
8206
- options
8207
- });
8208
- return { status: "lost_lease" };
8209
- }
8210
- if (leaseLost) {
8211
- await requestLostLeaseRecovery({
8270
+ let hasRun = false;
8271
+ while (true) {
8272
+ attemptMessageIds = [];
8273
+ const leasedWork = await getConversationWorkState2({
8212
8274
  conversationId,
8213
- destination,
8214
- leaseToken: lease.leaseToken,
8215
- nowMs: now3(options),
8216
- options
8217
- });
8218
- return { status: "lost_lease" };
8219
- }
8220
- if (result.status === "yielded") {
8221
- const yieldNowMs = now3(options);
8222
- const continuationMarked = await requestConversationContinuation2({
8223
- conversationId,
8224
- destination,
8225
- leaseToken: lease.leaseToken,
8226
- conversationStore: options.conversationStore,
8227
- nowMs: yieldNowMs,
8228
8275
  state: options.state
8229
8276
  });
8230
- if (!continuationMarked) {
8277
+ if (!leasedWork || leasedWork.lease?.leaseToken !== lease.leaseToken || leaseLost) {
8278
+ markLeaseLost();
8279
+ await requestLostLeaseRecovery({
8280
+ conversationId,
8281
+ destination,
8282
+ leaseToken: lease.leaseToken,
8283
+ nowMs: now3(options),
8284
+ options
8285
+ });
8231
8286
  return { status: "lost_lease" };
8232
8287
  }
8233
- await ensureConversationWake({
8234
- conversationId,
8235
- conversationStore: options.conversationStore,
8236
- idempotencyKey: nudgeIdempotencyKey(
8237
- "yield",
8288
+ const resumePending = leasedWork.execution.status === "awaiting_resume";
8289
+ const attemptMessages = selectAttemptMessages(leasedWork);
8290
+ if (hasRun && shouldYield()) {
8291
+ if (resumePending) {
8292
+ return await yieldWork();
8293
+ }
8294
+ break;
8295
+ }
8296
+ if (resumePending && attemptMessages.length === 0) {
8297
+ const resumeStarted = await beginConversationResume2({
8238
8298
  conversationId,
8239
- yieldNowMs
8240
- ),
8241
- nowMs: yieldNowMs,
8242
- queue: options.queue,
8243
- state: options.state
8244
- });
8245
- await releaseConversationWork2({
8246
- conversationId,
8247
- leaseToken: lease.leaseToken,
8248
- conversationStore: options.conversationStore,
8249
- nowMs: yieldNowMs,
8250
- state: options.state
8251
- });
8252
- logInfo(
8253
- "conversation_work_cooperative_yield",
8254
- { conversationId },
8255
- {
8256
- "app.worker.elapsed_ms": now3(options) - startedAtMs,
8257
- "app.worker.soft_yield_deadline_ms": softYieldDeadlineMs
8258
- },
8259
- "Conversation work yielded cooperatively"
8299
+ leaseToken: lease.leaseToken,
8300
+ conversationStore: options.conversationStore,
8301
+ nowMs: now3(options),
8302
+ state: options.state
8303
+ });
8304
+ if (!resumeStarted) {
8305
+ markLeaseLost();
8306
+ await requestLostLeaseRecovery({
8307
+ conversationId,
8308
+ destination,
8309
+ leaseToken: lease.leaseToken,
8310
+ nowMs: now3(options),
8311
+ options
8312
+ });
8313
+ return { status: "lost_lease" };
8314
+ }
8315
+ }
8316
+ attemptMessageIds = attemptMessages.map(
8317
+ (message2) => message2.inboundMessageId
8260
8318
  );
8261
- return { status: "yielded" };
8262
- }
8263
- if (result.status === "deferred") {
8264
- const deferredNowMs = now3(options);
8265
- const released = await releaseConversationWork2({
8319
+ const ack = async () => {
8320
+ const acknowledged = await ackMessages2({
8321
+ conversationId,
8322
+ inboundMessageIds: attemptMessageIds,
8323
+ leaseToken: lease.leaseToken,
8324
+ conversationStore: options.conversationStore,
8325
+ nowMs: now3(options),
8326
+ state: options.state
8327
+ });
8328
+ if (!acknowledged) {
8329
+ markLeaseLost();
8330
+ throw new Error(
8331
+ `Conversation work lease lost before inbox ack for ${conversationId}`
8332
+ );
8333
+ }
8334
+ };
8335
+ const workerContext = {
8336
+ attempt: {
8337
+ ack,
8338
+ conversationId,
8339
+ destination,
8340
+ drain,
8341
+ isFinalAttempt: attemptMessages.some(
8342
+ (message2) => isFinalAttempt(message2)
8343
+ ),
8344
+ messages: attemptMessages
8345
+ },
8266
8346
  conversationId,
8267
- leaseToken: lease.leaseToken,
8268
- conversationStore: options.conversationStore,
8269
- nowMs: deferredNowMs,
8270
- state: options.state
8271
- });
8272
- if (!released) {
8347
+ destination,
8348
+ shouldYield,
8349
+ checkIn
8350
+ };
8351
+ const result = await options.run(workerContext);
8352
+ hasRun = true;
8353
+ if (result.status === "lost_lease") {
8354
+ await requestLostLeaseRecovery({
8355
+ conversationId,
8356
+ destination,
8357
+ leaseToken: lease.leaseToken,
8358
+ nowMs: now3(options),
8359
+ options
8360
+ });
8273
8361
  return { status: "lost_lease" };
8274
8362
  }
8275
- const wake = await ensureConversationWake({
8276
- conversationId,
8277
- conversationStore: options.conversationStore,
8278
- idempotencyKey: nudgeIdempotencyKey(
8279
- "deferred",
8363
+ if (leaseLost) {
8364
+ await requestLostLeaseRecovery({
8280
8365
  conversationId,
8281
- deferredNowMs
8282
- ),
8283
- nowMs: deferredNowMs,
8284
- queue: options.queue,
8285
- state: options.state
8286
- });
8287
- return wake.status === "enqueued" ? { status: "pending_requeued" } : { status: "completed" };
8288
- }
8289
- if (attemptMessageIds.length > 0) {
8290
- const failure = await recordFailedDeliveryAttempt({
8291
- conversationId,
8292
- leaseToken: lease.leaseToken,
8293
- nowMs: now3(options),
8294
- messageIds: attemptMessageIds,
8295
- options
8296
- });
8297
- if (isTerminalFailure(failure)) {
8298
- await deadLetterAttempt2({
8366
+ destination,
8367
+ leaseToken: lease.leaseToken,
8368
+ nowMs: now3(options),
8369
+ options
8370
+ });
8371
+ return { status: "lost_lease" };
8372
+ }
8373
+ if (result.status === "yielded") {
8374
+ const resumeRequested = await requestConversationContinuation2({
8299
8375
  conversationId,
8376
+ destination,
8300
8377
  leaseToken: lease.leaseToken,
8301
8378
  conversationStore: options.conversationStore,
8302
8379
  nowMs: now3(options),
8303
8380
  state: options.state
8304
8381
  });
8305
- return { status: "failed" };
8382
+ if (!resumeRequested) {
8383
+ return { status: "lost_lease" };
8384
+ }
8385
+ return await yieldWork();
8386
+ }
8387
+ if (result.status === "deferred") {
8388
+ const deferredNowMs = now3(options);
8389
+ const released = await releaseConversationWork2({
8390
+ conversationId,
8391
+ leaseToken: lease.leaseToken,
8392
+ conversationStore: options.conversationStore,
8393
+ nowMs: deferredNowMs,
8394
+ state: options.state
8395
+ });
8396
+ if (!released) {
8397
+ return { status: "lost_lease" };
8398
+ }
8399
+ const wake = await ensureConversationWake({
8400
+ conversationId,
8401
+ conversationStore: options.conversationStore,
8402
+ idempotencyKey: nudgeIdempotencyKey(
8403
+ "deferred",
8404
+ conversationId,
8405
+ deferredNowMs
8406
+ ),
8407
+ nowMs: deferredNowMs,
8408
+ queue: options.queue,
8409
+ state: options.state
8410
+ });
8411
+ return wake.status === "enqueued" ? { status: "pending_requeued" } : { status: "completed" };
8412
+ }
8413
+ if (attemptMessageIds.length > 0) {
8414
+ const failure = await recordFailedDeliveryAttempt({
8415
+ conversationId,
8416
+ leaseToken: lease.leaseToken,
8417
+ nowMs: now3(options),
8418
+ messageIds: attemptMessageIds,
8419
+ options
8420
+ });
8421
+ if (isTerminalFailure(failure)) {
8422
+ await deadLetterAttempt2({
8423
+ conversationId,
8424
+ leaseToken: lease.leaseToken,
8425
+ conversationStore: options.conversationStore,
8426
+ nowMs: now3(options),
8427
+ state: options.state
8428
+ });
8429
+ return { status: "failed" };
8430
+ }
8431
+ if (failure.status === "lost_lease") {
8432
+ return { status: "lost_lease" };
8433
+ }
8434
+ if (failure.status === "recorded") {
8435
+ break;
8436
+ }
8437
+ }
8438
+ const next = await getConversationWorkState2({
8439
+ conversationId,
8440
+ state: options.state
8441
+ });
8442
+ if (!next || next.lease?.leaseToken !== lease.leaseToken) {
8443
+ return { status: "lost_lease" };
8444
+ }
8445
+ if (next.execution.status !== "awaiting_resume" && countPendingConversationMessages(next) === 0) {
8446
+ break;
8306
8447
  }
8307
8448
  }
8308
8449
  const completion = await completeConversationWork2({
@@ -8360,7 +8501,7 @@ async function processConversationWork(message, options) {
8360
8501
  state: options.state
8361
8502
  });
8362
8503
  } else {
8363
- const continuationMarked = await requestConversationContinuation2({
8504
+ const resumeRequested = await requestConversationContinuation2({
8364
8505
  conversationId,
8365
8506
  destination,
8366
8507
  leaseToken: lease.leaseToken,
@@ -8368,7 +8509,7 @@ async function processConversationWork(message, options) {
8368
8509
  nowMs: errorNowMs,
8369
8510
  state: options.state
8370
8511
  });
8371
- if (continuationMarked) {
8512
+ if (resumeRequested) {
8372
8513
  await ensureConversationWake({
8373
8514
  conversationId,
8374
8515
  conversationStore: options.conversationStore,
@@ -8379,6 +8520,7 @@ async function processConversationWork(message, options) {
8379
8520
  ),
8380
8521
  nowMs: errorNowMs,
8381
8522
  queue: options.queue,
8523
+ replaceExistingWake: true,
8382
8524
  state: options.state
8383
8525
  });
8384
8526
  }
@@ -8580,14 +8722,14 @@ function registerVercelPluginTaskDevConsumer() {
8580
8722
  }
8581
8723
 
8582
8724
  // src/chat/services/subscribed-decision.ts
8583
- import { z as z4 } from "zod";
8584
- var replyDecisionSchema = z4.object({
8585
- should_reply: z4.boolean().describe("Whether Junior should respond to this thread message."),
8586
- should_unsubscribe: z4.boolean().describe(
8725
+ import { z as z5 } from "zod";
8726
+ var replyDecisionSchema = z5.object({
8727
+ should_reply: z5.boolean().describe("Whether Junior should respond to this thread message."),
8728
+ should_unsubscribe: z5.boolean().describe(
8587
8729
  "Whether Junior should unsubscribe from this thread because the user clearly asked it to stop participating."
8588
8730
  ),
8589
- confidence: z4.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
8590
- reason: z4.string().describe("Short reason for the decision; use an empty string if none.")
8731
+ confidence: z5.number().min(0).max(1).describe("Classifier confidence from 0 to 1."),
8732
+ reason: z5.string().describe("Short reason for the decision; use an empty string if none.")
8591
8733
  }).strict();
8592
8734
  var ROUTER_CONFIDENCE_THRESHOLD = 0.8;
8593
8735
  var ROUTER_CLASSIFIER_MAX_TOKENS = 240;
@@ -8961,12 +9103,14 @@ function createAcceptedSteeringDrain(hooks, options) {
8961
9103
  await hooks.drainSteeringMessages(async (messages) => {
8962
9104
  const selection = await options.selectMessages(messages, context);
8963
9105
  await options.onSkipped?.(selection.skipped);
8964
- const interrupted = selection.accepted.filter((accepted) => accepted.mode === "interrupt").map((accepted) => accepted.message);
9106
+ const interrupted = selection.accepted.map(
9107
+ (accepted) => accepted.message
9108
+ );
8965
9109
  await accept(getQueuedMessagesFromSlackMessages(interrupted, options));
8966
9110
  interruptedMessages = interrupted;
8967
9111
  await options.onAcceptedForProcessing?.(interrupted);
8968
9112
  return [
8969
- ...selection.accepted.filter((accepted) => accepted.mode === "interrupt").map((accepted) => accepted.inboundMessageId),
9113
+ ...selection.accepted.map((accepted) => accepted.inboundMessageId),
8970
9114
  ...selection.skipped.map((skipped) => skipped.inboundMessageId)
8971
9115
  ];
8972
9116
  });
@@ -9100,19 +9244,17 @@ function createSlackTurnRuntime(deps) {
9100
9244
  rawText: appendSlackLegacyAttachmentText(message.text, message.raw),
9101
9245
  userText: appendSlackLegacyAttachmentText(strippedUserText, message.raw)
9102
9246
  };
9103
- const isActiveRequest = candidate.activeRequest || Boolean(message.isMention);
9104
9247
  const decision = await deps.decideSubscribedReply({
9105
9248
  rawText: text.rawText,
9106
9249
  text: text.userText,
9107
9250
  conversationContext,
9108
9251
  hasAttachments: message.attachments.length > 0 || legacyAttachmentText !== "",
9109
- isExplicitMention: isActiveRequest,
9252
+ isExplicitMention: true,
9110
9253
  context
9111
9254
  });
9112
9255
  return {
9113
9256
  context,
9114
9257
  decision,
9115
- mode: isActiveRequest ? "interrupt" : "defer",
9116
9258
  text
9117
9259
  };
9118
9260
  };
@@ -9166,7 +9308,6 @@ function createSlackTurnRuntime(deps) {
9166
9308
  context: decision.context,
9167
9309
  decision: decision.decision,
9168
9310
  inboundMessageId: candidate.inboundMessageId,
9169
- mode: decision.mode,
9170
9311
  message: candidate.message,
9171
9312
  text: decision.text
9172
9313
  });
@@ -9188,8 +9329,7 @@ function createSlackTurnRuntime(deps) {
9188
9329
  return {
9189
9330
  accepted: selected.filter((message) => message.decision.shouldReply).map((message) => ({
9190
9331
  inboundMessageId: message.inboundMessageId,
9191
- message: message.message,
9192
- mode: message.mode
9332
+ message: message.message
9193
9333
  })),
9194
9334
  skipped: selected.filter((message) => !message.decision.shouldReply)
9195
9335
  };
@@ -10354,11 +10494,11 @@ function getSlackMessageTs(message) {
10354
10494
  }
10355
10495
 
10356
10496
  // src/chat/slack/action-token.ts
10357
- import { z as z5 } from "zod";
10358
- var slackActionTokenSchema = z5.string().trim().min(1).brand();
10359
- var slackMessageEnvelopeSchema = z5.object({
10360
- raw: z5.object({
10361
- action_token: z5.unknown().optional()
10497
+ import { z as z6 } from "zod";
10498
+ var slackActionTokenSchema = z6.string().trim().min(1).brand();
10499
+ var slackMessageEnvelopeSchema = z6.object({
10500
+ raw: z6.object({
10501
+ action_token: z6.unknown().optional()
10362
10502
  }).optional()
10363
10503
  });
10364
10504
  function readSlackActionToken(message) {
@@ -10749,13 +10889,14 @@ function createReplyToThread(deps) {
10749
10889
  beforeFirstResponsePostCalled = true;
10750
10890
  await options.beforeFirstResponsePost?.();
10751
10891
  };
10752
- const postAuthPauseNotice = async (providerDisplayName) => {
10892
+ const postAuthPauseNotice = async (providerDisplayName, requestText) => {
10753
10893
  if (!actor) {
10754
10894
  throw new Error("Slack auth pause notice requires an actor");
10755
10895
  }
10756
10896
  const text = buildAuthPauseResponse(
10757
10897
  actor.userId,
10758
- providerDisplayName
10898
+ providerDisplayName,
10899
+ requestText
10759
10900
  );
10760
10901
  try {
10761
10902
  await beforeFirstResponsePost();
@@ -11353,10 +11494,7 @@ function createReplyToThread(deps) {
11353
11494
  state: {
11354
11495
  artifactState: preparedState.artifacts,
11355
11496
  pendingAuth: preparedState.conversation.processing.pendingAuth,
11356
- sandbox: {
11357
- sandboxId: preparedState.sandboxId,
11358
- sandboxDependencyProfileHash: preparedState.sandboxDependencyProfileHash
11359
- }
11497
+ sandboxRef: preparedState.sandboxRef
11360
11498
  },
11361
11499
  observers: {
11362
11500
  onStatus: (nextStatus) => status.update(nextStatus),
@@ -11369,10 +11507,9 @@ function createReplyToThread(deps) {
11369
11507
  onInputCommitted: options.ack,
11370
11508
  drainSteeringMessages,
11371
11509
  shouldYield: options.shouldYield,
11372
- onSandboxAcquired: async (sandbox) => {
11510
+ onSandboxRefChanged: async (sandboxRef) => {
11373
11511
  await persistThreadState(thread, {
11374
- sandboxId: sandbox.sandboxId,
11375
- sandboxDependencyProfileHash: sandbox.sandboxDependencyProfileHash
11512
+ sandboxRef
11376
11513
  });
11377
11514
  },
11378
11515
  onArtifactStateUpdated: async (artifacts) => {
@@ -11428,7 +11565,10 @@ function createReplyToThread(deps) {
11428
11565
  shouldPersistFailureState = false;
11429
11566
  return;
11430
11567
  }
11431
- await postAuthPauseNotice(outcome.providerDisplayName);
11568
+ await postAuthPauseNotice(
11569
+ outcome.providerDisplayName,
11570
+ outcome.requestText
11571
+ );
11432
11572
  completeAuthPauseTurn({
11433
11573
  conversation: preparedState.conversation,
11434
11574
  sessionId: turnId
@@ -11932,12 +12072,7 @@ async function seedConversationBackfill(thread, conversation, currentTurn) {
11932
12072
  function createPrepareTurnState(deps) {
11933
12073
  return async function prepareTurnState(args) {
11934
12074
  const existingState = await args.thread.state;
11935
- const existingSandboxId = existingState ? toOptionalString(
11936
- existingState.app_sandbox_id
11937
- ) : void 0;
11938
- const existingSandboxDependencyProfileHash = existingState ? toOptionalString(
11939
- existingState.app_sandbox_dependency_profile_hash
11940
- ) : void 0;
12075
+ const sandboxRef = getPersistedSandboxState(existingState ?? {});
11941
12076
  const artifacts = coerceThreadArtifactsState(existingState);
11942
12077
  const conversation = coerceThreadConversationState(existingState);
11943
12078
  const conversationId = args.context.threadId ?? args.context.runId;
@@ -12006,8 +12141,7 @@ function createPrepareTurnState(deps) {
12006
12141
  configuration,
12007
12142
  channelConfiguration,
12008
12143
  conversation,
12009
- sandboxId: existingSandboxId,
12010
- sandboxDependencyProfileHash: existingSandboxDependencyProfileHash,
12144
+ sandboxRef,
12011
12145
  conversationContext,
12012
12146
  userMessageAlreadyReplied,
12013
12147
  userMessageId
@@ -12315,7 +12449,7 @@ async function failUnresumableContinuation(args) {
12315
12449
  errorMessage: args.errorMessage
12316
12450
  });
12317
12451
  }
12318
- async function continueSlackAgentRun(payload, options) {
12452
+ async function continueSlackAgentRun(payload, options, runOptions = {}) {
12319
12453
  const thread = parseSlackThreadId(payload.conversationId);
12320
12454
  if (!thread) {
12321
12455
  throw new Error(
@@ -12368,7 +12502,7 @@ async function continueSlackAgentRun(payload, options) {
12368
12502
  const conversationContext = buildConversationContext(conversation, {
12369
12503
  excludeMessageId: userMessage.id
12370
12504
  });
12371
- const sandbox = getPersistedSandboxState(currentState);
12505
+ const sandboxRef = getPersistedSandboxState(currentState);
12372
12506
  const destination = requireSlackDestination(
12373
12507
  payload.destination,
12374
12508
  "Slack continuation"
@@ -12440,9 +12574,10 @@ async function continueSlackAgentRun(payload, options) {
12440
12574
  state: {
12441
12575
  artifactState: artifacts,
12442
12576
  pendingAuth: conversation.processing.pendingAuth,
12443
- sandbox
12577
+ sandboxRef
12444
12578
  },
12445
12579
  durability: {
12580
+ shouldYield: runOptions.shouldYield,
12446
12581
  recordPendingAuth: async (nextPendingAuth) => {
12447
12582
  conversation.processing.pendingAuth = nextPendingAuth;
12448
12583
  await persistThreadStateById(payload.conversationId, {
@@ -12605,7 +12740,11 @@ async function recoverStrandedRunningSession(args) {
12605
12740
  });
12606
12741
  return false;
12607
12742
  }
12608
- if (await continueSlackAgentRunWithLockRetry(request, args.options)) {
12743
+ if (await continueSlackAgentRunWithLockRetry(
12744
+ request,
12745
+ args.options,
12746
+ args.runOptions
12747
+ )) {
12609
12748
  return true;
12610
12749
  }
12611
12750
  await failUnresumableContinuation({
@@ -12616,13 +12755,14 @@ async function recoverStrandedRunningSession(args) {
12616
12755
  });
12617
12756
  return false;
12618
12757
  }
12619
- async function resumeAwaitingSlackContinuation(conversationId, options) {
12758
+ async function resumeAwaitingSlackContinuation(conversationId, options, runOptions = {}) {
12620
12759
  const summaries = await listAgentTurnSessionSummariesForConversation(conversationId);
12621
12760
  const newest = summaries[0];
12622
12761
  if (newest?.state === "running") {
12623
12762
  return await recoverStrandedRunningSession({
12624
12763
  conversationId,
12625
12764
  options,
12765
+ runOptions,
12626
12766
  summary: newest
12627
12767
  });
12628
12768
  }
@@ -12642,7 +12782,7 @@ async function resumeAwaitingSlackContinuation(conversationId, options) {
12642
12782
  });
12643
12783
  continue;
12644
12784
  }
12645
- if (await continueSlackAgentRunWithLockRetry(request, options)) {
12785
+ if (await continueSlackAgentRunWithLockRetry(request, options, runOptions)) {
12646
12786
  return true;
12647
12787
  }
12648
12788
  await failUnresumableContinuation({
@@ -12654,14 +12794,14 @@ async function resumeAwaitingSlackContinuation(conversationId, options) {
12654
12794
  }
12655
12795
  return false;
12656
12796
  }
12657
- async function continueSlackAgentRunWithLockRetry(payload, options) {
12797
+ async function continueSlackAgentRunWithLockRetry(payload, options, runOptions = {}) {
12658
12798
  const scheduleAgentContinue2 = options.scheduleAgentContinue ?? scheduleAgentContinue;
12659
12799
  for (const [attempt, delayMs] of [
12660
12800
  ...AGENT_CONTINUE_LOCK_RETRY_DELAYS_MS,
12661
12801
  void 0
12662
12802
  ].entries()) {
12663
12803
  try {
12664
- return await continueSlackAgentRun(payload, options);
12804
+ return await continueSlackAgentRun(payload, options, runOptions);
12665
12805
  } catch (error) {
12666
12806
  if (!(error instanceof ResumeTurnBusyError)) {
12667
12807
  throw error;
@@ -12756,10 +12896,14 @@ function createProductionConversationWorkOptions(options) {
12756
12896
  run: createSlackConversationWorker({
12757
12897
  getSlackAdapter: getProductionSlackAdapter,
12758
12898
  conversationStore,
12759
- resumeAwaitingContinuation: async (conversationId) => await resumeAwaitingSlackContinuation(conversationId, {
12760
- agentRunner,
12761
- scheduleSessionCompletedPluginTasks: services.replyExecutor?.scheduleSessionCompletedPluginTasks
12762
- }),
12899
+ resumeAwaitingContinuation: async (conversationId, runOptions) => await resumeAwaitingSlackContinuation(
12900
+ conversationId,
12901
+ {
12902
+ agentRunner,
12903
+ scheduleSessionCompletedPluginTasks: services.replyExecutor?.scheduleSessionCompletedPluginTasks
12904
+ },
12905
+ runOptions
12906
+ ),
12763
12907
  runtime
12764
12908
  })
12765
12909
  };
@@ -12889,6 +13033,7 @@ async function resolveVirtualConfig() {
12889
13033
  const mod = await import("#junior/config");
12890
13034
  return {
12891
13035
  createDashboardApp: mod.createDashboardApp,
13036
+ functionMaxDurationSeconds: mod.functionMaxDurationSeconds,
12892
13037
  dashboard: mod.dashboard,
12893
13038
  pluginSet: mod.pluginSet,
12894
13039
  plugins: mod.plugins,
@@ -13142,6 +13287,11 @@ function mountRoutes(app, routes) {
13142
13287
  }
13143
13288
  async function createApp(options) {
13144
13289
  const virtualConfig = await resolveVirtualConfig();
13290
+ if (virtualConfig?.functionMaxDurationSeconds !== void 0) {
13291
+ configureFunctionMaxDurationSeconds(
13292
+ virtualConfig.functionMaxDurationSeconds
13293
+ );
13294
+ }
13145
13295
  const dashboard = options?.dashboard ?? virtualConfig?.dashboard;
13146
13296
  const configuredPlugins = options?.plugins ?? virtualConfig?.pluginSet;
13147
13297
  const plugins = pluginRuntimeRegistrationsFromPluginSet(configuredPlugins);