@sentry/junior 0.177.0 → 0.179.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 (102) hide show
  1. package/dist/{agent-hooks-IYHCN7CO.js → agent-hooks-32IDCLMW.js} +12 -12
  2. package/dist/api/conversations/archive.d.ts +5 -2
  3. package/dist/api/conversations/list.d.ts +0 -1
  4. package/dist/api/conversations/membership.d.ts +14 -0
  5. package/dist/api/conversations/projection.d.ts +2 -1
  6. package/dist/api/locations/detail.d.ts +1 -1
  7. package/dist/api/people/shared.d.ts +2 -2
  8. package/dist/api/schema/conversation.d.ts +4 -4
  9. package/dist/api/schema/location.d.ts +1 -1
  10. package/dist/api/schema/person.d.ts +1 -1
  11. package/dist/api/schema.js +1 -1
  12. package/dist/api.js +399 -287
  13. package/dist/app.js +573 -198
  14. package/dist/{catalog-runtime-2TD4CXSB.js → catalog-runtime-FBVA6WAQ.js} +5 -5
  15. package/dist/chat/agent/sandbox.d.ts +2 -0
  16. package/dist/chat/conversations/sql/participants.d.ts +1 -0
  17. package/dist/chat/conversations/store.d.ts +0 -1
  18. package/dist/chat/sandbox/errors.d.ts +4 -0
  19. package/dist/chat/sandbox/noninteractive-command.d.ts +1 -0
  20. package/dist/chat/sandbox/prepare-workspace.d.ts +27 -3
  21. package/dist/chat/sandbox/sandbox.d.ts +2 -0
  22. package/dist/chat/sandbox/session.d.ts +2 -0
  23. package/dist/chat/sandbox/snapshot/builder-sandbox.d.ts +5 -0
  24. package/dist/chat/sandbox/snapshot/install.d.ts +2 -2
  25. package/dist/chat/sandbox/snapshot/span.d.ts +3 -0
  26. package/dist/chat/sandbox/snapshot/store.d.ts +20 -17
  27. package/dist/chat/sandbox/snapshot/waiting-error.d.ts +15 -0
  28. package/dist/chat/sandbox/snapshot/workspace.d.ts +17 -0
  29. package/dist/chat/scheduled-tasks/personal.d.ts +5 -8
  30. package/dist/chat/scheduled-tasks/runs.d.ts +49 -0
  31. package/dist/chat/scheduled-tasks/schedule-intent.d.ts +1 -1
  32. package/dist/chat/scheduled-tasks/tasks.d.ts +27 -0
  33. package/dist/chat/scheduled-tasks/tool-support.d.ts +0 -4
  34. package/dist/chat/scheduled-tasks/tools/create.d.ts +1 -1
  35. package/dist/chat/scheduled-tasks/tools/update.d.ts +1 -1
  36. package/dist/chat/scheduled-tasks/types.d.ts +127 -78
  37. package/dist/chat/services/workspace-snapshot-wait.d.ts +27 -0
  38. package/dist/chat/workspaces/types.d.ts +1 -0
  39. package/dist/{chunk-CAWBLVBY.js → chunk-2HIPYNQI.js} +6 -6
  40. package/dist/{chunk-ZCYABD7N.js → chunk-3AFVR5UC.js} +5 -5
  41. package/dist/{chunk-PA7QDS7P.js → chunk-3I7XE3PA.js} +204 -100
  42. package/dist/{chunk-VGDOEKO2.js → chunk-3TM3FYLQ.js} +1 -1
  43. package/dist/{chunk-6NZRKJWU.js → chunk-4GWXAILB.js} +1 -1
  44. package/dist/{chunk-LFZODSAY.js → chunk-5IRROPA5.js} +4 -4
  45. package/dist/{chunk-N4YEVOEC.js → chunk-B6ECUSVX.js} +45 -276
  46. package/dist/{chunk-4TWDRU64.js → chunk-CJLBLELR.js} +5 -5
  47. package/dist/{chunk-3FWXQZJJ.js → chunk-FAS6AIMF.js} +1 -1
  48. package/dist/{chunk-I7PKGMKI.js → chunk-FIDMHAO6.js} +6 -6
  49. package/dist/{chunk-4SVAFAI2.js → chunk-FTGD2FBP.js} +1 -1
  50. package/dist/{chunk-LVL5NG5G.js → chunk-G2CAC4CV.js} +19 -19
  51. package/dist/{chunk-DLTP6TUJ.js → chunk-GENV6KF2.js} +2 -2
  52. package/dist/{chunk-FVVYHML7.js → chunk-GPLAT6H7.js} +6 -6
  53. package/dist/{chunk-7CVYXUOJ.js → chunk-HQNE5APD.js} +1 -1
  54. package/dist/{chunk-Q362TECJ.js → chunk-JMJNFEDC.js} +331 -2
  55. package/dist/{chunk-AFOFVEXP.js → chunk-JT4WMD3C.js} +124 -13
  56. package/dist/{chunk-7ZFESKJ2.js → chunk-KJLHIHFF.js} +9 -9
  57. package/dist/{chunk-QLLOX7JY.js → chunk-Q5B3WTGG.js} +8 -8
  58. package/dist/{chunk-DGGIKWYQ.js → chunk-R5NWWYIS.js} +1 -1
  59. package/dist/{chunk-WCQUKX74.js → chunk-R6U7N4FS.js} +14 -5
  60. package/dist/{chunk-WYO2HXY5.js → chunk-RTRBYSF3.js} +1 -1
  61. package/dist/{chunk-2IMQLZHS.js → chunk-RZRYOLCE.js} +1144 -136
  62. package/dist/{chunk-7B6NW4P7.js → chunk-SLGGOASN.js} +1 -1
  63. package/dist/{chunk-SNPQGTHJ.js → chunk-TIEKUH4Q.js} +1 -1
  64. package/dist/{chunk-ELEAIRTG.js → chunk-TKB25A3L.js} +2 -2
  65. package/dist/{chunk-UB5YB2ED.js → chunk-U4TKB6F6.js} +6 -6
  66. package/dist/{chunk-ORMJY4RG.js → chunk-UT6X7XN7.js} +1 -1
  67. package/dist/{chunk-LPMLAJYR.js → chunk-VP4OVRDD.js} +11 -17
  68. package/dist/{chunk-7PZDAXIK.js → chunk-XSPSCWLH.js} +1 -1
  69. package/dist/chunk-XZY6G5YZ.js +1088 -0
  70. package/dist/{chunk-J2EWL6GJ.js → chunk-YFNAMRGE.js} +1 -1
  71. package/dist/{chunk-6KGMMVHF.js → chunk-ZQMG3CL5.js} +2 -2
  72. package/dist/cli/chat.js +31 -30
  73. package/dist/cli/check.js +6 -6
  74. package/dist/cli/plugins.js +13 -13
  75. package/dist/cli/snapshot-create.js +10 -10
  76. package/dist/cli/upgrade.js +7 -7
  77. package/dist/db/schema/conversation-participants.d.ts +17 -0
  78. package/dist/db/schema/conversations.d.ts +629 -1
  79. package/dist/db/schema/scheduled-tasks.d.ts +29 -5
  80. package/dist/db/schema/snapshots.d.ts +1 -1
  81. package/dist/db/schema.d.ts +46 -5
  82. package/dist/{db-A3CB76US.js → db-EJAV7JL6.js} +7 -7
  83. package/dist/instrumentation.js +4 -4
  84. package/dist/nitro.js +2 -2
  85. package/dist/{oauth-callback-server-64QMI3IB.js → oauth-callback-server-GAVSUBTQ.js} +23 -23
  86. package/dist/{runner-EIAE2BGD.js → runner-34JL64JF.js} +15 -15
  87. package/dist/{skills-ZADWFKDG.js → skills-U545HALM.js} +6 -6
  88. package/dist/spawn-I2S4IDUQ.js +37 -0
  89. package/dist/{validation-4I6AF4CV.js → validation-EVXVGA44.js} +6 -6
  90. package/dist/version.js +1 -1
  91. package/dist/{work-BS6BZ66U.js → work-QOAUPDBU.js} +18 -18
  92. package/dist/worker-5GX2IKLZ.js +24 -0
  93. package/migrations/0033_workspace_snapshot_active_build.sql +1 -0
  94. package/migrations/0034_strong_randall.sql +31 -0
  95. package/migrations/meta/0033_snapshot.json +2914 -0
  96. package/migrations/meta/0034_snapshot.json +3038 -0
  97. package/migrations/meta/_journal.json +15 -1
  98. package/package.json +4 -4
  99. package/dist/chat/scheduled-tasks/store.d.ts +0 -78
  100. package/dist/chunk-4TRHPYTL.js +0 -1944
  101. package/dist/spawn-DVUOUFFC.js +0 -37
  102. package/dist/worker-RS5PWTCE.js +0 -24
@@ -7,7 +7,7 @@ import {
7
7
  PluginCredentialFailureError,
8
8
  agentNameSchema,
9
9
  createPluginAuthOrchestration
10
- } from "./chunk-ZCYABD7N.js";
10
+ } from "./chunk-3AFVR5UC.js";
11
11
  import {
12
12
  authorizationId
13
13
  } from "./chunk-DIB4GX3G.js";
@@ -32,7 +32,10 @@ import {
32
32
  searchSlackUsers,
33
33
  selectPluginGrant,
34
34
  toMcpProviderError
35
- } from "./chunk-QLLOX7JY.js";
35
+ } from "./chunk-Q5B3WTGG.js";
36
+ import {
37
+ getTurnRequestDeadline
38
+ } from "./chunk-4GWXAILB.js";
36
39
  import {
37
40
  RetryableDeliveryError,
38
41
  actorFromRun,
@@ -48,7 +51,7 @@ import {
48
51
  getSlackMessagePermalink,
49
52
  resolveBaseUrl,
50
53
  uploadFilesToConversation
51
- } from "./chunk-CAWBLVBY.js";
54
+ } from "./chunk-2HIPYNQI.js";
52
55
  import {
53
56
  parseSandboxEgressAuthRequiredSignal,
54
57
  parseSandboxEgressPermissionDeniedSignal,
@@ -59,7 +62,7 @@ import {
59
62
  SCHEDULED_TASK_SYSTEM_ACTOR,
60
63
  compactEventTask,
61
64
  createEventTask,
62
- createSchedulerSqlStore,
65
+ createScheduledTask,
63
66
  deleteEventTask,
64
67
  eventNamespaceSchema,
65
68
  eventTaskListToolResultSchema,
@@ -67,30 +70,29 @@ import {
67
70
  eventTaskSuccess,
68
71
  eventTaskToolResultSchema,
69
72
  getEventTask,
70
- getFirstRunAtMs,
71
73
  getResourceEventCatalog,
72
- getZonedDateTimeParts,
73
74
  incrementStat,
74
75
  injectContinuationToolName,
75
76
  juniorToolOutputSchema,
76
77
  listEventTasksForTeam,
78
+ listScheduledTasksForTeam,
77
79
  makeStructuredToolOutput,
78
80
  normalizeEventIdentifier,
79
81
  pluginSupportsEvent,
82
+ readScheduledTask,
80
83
  registeredEventTaskTriggerSchema,
81
84
  registeredEventTypeSchema,
82
85
  registeredResourceTypeSchema,
83
86
  requireEventTaskSlackContext,
84
87
  requireSupportedEventTaskTrigger,
85
- resolveLocalScheduleAtMs,
86
88
  saveEventTask,
87
- scheduledTaskAttributes,
89
+ saveScheduledTask,
88
90
  storeAttachments,
89
91
  writableEventTask
90
- } from "./chunk-4TRHPYTL.js";
92
+ } from "./chunk-XZY6G5YZ.js";
91
93
  import {
92
94
  parseSlackThreadId
93
- } from "./chunk-3FWXQZJJ.js";
95
+ } from "./chunk-FAS6AIMF.js";
94
96
  import {
95
97
  AGENTS_REMOVAL_NOTICE,
96
98
  AGENTS_REPLACEMENT_NOTICE,
@@ -160,23 +162,18 @@ import {
160
162
  stripRuntimeTurnContext,
161
163
  trimTrailingAssistantMessages,
162
164
  truncateText
163
- } from "./chunk-I7PKGMKI.js";
165
+ } from "./chunk-FIDMHAO6.js";
164
166
  import {
165
167
  bindSandboxFileSystem,
166
168
  createSandboxSession,
169
+ dependencies,
167
170
  getSandboxResources,
168
- getVercelSandboxCredentials,
169
- isAlreadyExistsError,
170
171
  isMissingError,
171
- isSandboxCommandStreamInterruptedError,
172
- isSandboxMissingError,
173
- isSandboxUnavailableError,
174
- isSnapshottingError,
172
+ postinstall,
175
173
  resolve,
176
- stopSession,
177
- throwSandboxOperationError,
178
- wrapSandboxSetupError
179
- } from "./chunk-N4YEVOEC.js";
174
+ setSnapshotSpanAttributes,
175
+ stopSession
176
+ } from "./chunk-B6ECUSVX.js";
180
177
  import {
181
178
  privateTraceResultAttributes
182
179
  } from "./chunk-UIE3R5XU.js";
@@ -196,6 +193,7 @@ import {
196
193
  createResourceEventSubscription,
197
194
  createWorkspace,
198
195
  deleteWorkspace,
196
+ deleteWorkspaceSnapshotBuilders,
199
197
  downloadPrivateSlackFile,
200
198
  getDashboardTaskLink,
201
199
  getFilePermalink,
@@ -207,19 +205,23 @@ import {
207
205
  getSlackToolContext,
208
206
  getWorkspace,
209
207
  getWorkspaceByName,
208
+ getWorkspaceSnapshotBuilder,
209
+ invalidateReadySnapshot,
210
210
  isConversationScopedChannel,
211
211
  listResourceEventSubscriptions,
212
212
  listWorkspaces,
213
+ loadSnapshotsForProfile,
213
214
  normalizeSlackConversationId,
214
215
  readActorIdentity,
215
- recordResolvedWorkspaceSnapshot,
216
216
  requireResourceWatchConversation,
217
217
  resolveChannelCapabilities,
218
+ setWorkspaceSnapshot,
219
+ setWorkspaceSnapshotBuild,
218
220
  tryWorkspaceRepoCheckoutPath,
219
221
  updateWorkspace,
220
222
  withSlackRetries,
221
223
  workspaceRepoCheckoutPath
222
- } from "./chunk-PA7QDS7P.js";
224
+ } from "./chunk-3I7XE3PA.js";
223
225
  import {
224
226
  KNOWN_CONVERSATION_EVENT_TYPES,
225
227
  contextProvenance,
@@ -231,29 +233,48 @@ import {
231
233
  instructionActors,
232
234
  instructionProvenanceFor,
233
235
  sameActorIdentity
234
- } from "./chunk-LPMLAJYR.js";
236
+ } from "./chunk-VP4OVRDD.js";
235
237
  import {
236
238
  juniorArtifacts,
237
- juniorIdentities
238
- } from "./chunk-WCQUKX74.js";
239
+ juniorIdentities,
240
+ juniorSchedulerRuns
241
+ } from "./chunk-R6U7N4FS.js";
239
242
  import {
240
243
  SANDBOX_DATA_ROOT,
241
244
  SANDBOX_REPOS_ROOT,
242
245
  SANDBOX_SKILLS_ROOT,
243
246
  SANDBOX_WORKSPACE_ROOT,
247
+ WorkspaceSnapshotWaitingError,
244
248
  buildCommandScript,
245
249
  buildNonInteractiveShellScript,
250
+ create,
246
251
  ensureDockerDaemon,
252
+ getVercelSandboxCredentials,
247
253
  hash,
254
+ isAbortError,
255
+ isAlreadyExistsError,
256
+ isSandboxApiTransientError,
257
+ isSandboxCommandStreamInterruptedError,
258
+ isSandboxMissingError,
259
+ isSandboxUnavailableError,
260
+ isSnapshottingError,
261
+ isStale,
262
+ isWorkspaceSnapshotWaitingError,
263
+ isWorkspaceSnapshotWaitingResult,
248
264
  runNonInteractiveCommand,
249
265
  sandboxSkillDir,
250
266
  sandboxSkillFile,
251
267
  sandboxSkillPathResolution,
252
- sleep
253
- } from "./chunk-Q362TECJ.js";
268
+ sleep,
269
+ throwSandboxOperationError,
270
+ workspaceNameFromWaitingResult,
271
+ wrapSandboxSetupError
272
+ } from "./chunk-JMJNFEDC.js";
254
273
  import {
255
- getStateAdapter
256
- } from "./chunk-VGDOEKO2.js";
274
+ fenceLock,
275
+ getStateAdapter,
276
+ withLock
277
+ } from "./chunk-3TM3FYLQ.js";
257
278
  import {
258
279
  ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
259
280
  COMPACTION_SUMMARY_PREFIX,
@@ -286,7 +307,7 @@ import {
286
307
  resolveGatewayCredential,
287
308
  resolveGatewayModel,
288
309
  unwrapCurrentInstruction
289
- } from "./chunk-DLTP6TUJ.js";
310
+ } from "./chunk-GENV6KF2.js";
290
311
  import {
291
312
  canExposeConversationPayload,
292
313
  resolveConversationPrivacy,
@@ -296,16 +317,16 @@ import {
296
317
  toGenAiMessagesTraceAttributes,
297
318
  toGenAiPayloadMetadata,
298
319
  toGenAiPayloadTraceAttributes
299
- } from "./chunk-7PZDAXIK.js";
320
+ } from "./chunk-XSPSCWLH.js";
300
321
  import {
301
322
  discoverSkills,
302
323
  findSkillByName,
303
324
  loadSkillsByName,
304
325
  parseSkillInvocation
305
- } from "./chunk-6KGMMVHF.js";
326
+ } from "./chunk-ZQMG3CL5.js";
306
327
  import {
307
328
  pluginCatalogRuntime
308
- } from "./chunk-J2EWL6GJ.js";
329
+ } from "./chunk-YFNAMRGE.js";
309
330
  import {
310
331
  CredentialUnavailableError,
311
332
  OAuthProviderError,
@@ -313,7 +334,7 @@ import {
313
334
  getOAuthProviderErrorAttributes,
314
335
  resolveAuthTokenPlaceholder,
315
336
  resolvePluginCommandEnv
316
- } from "./chunk-7B6NW4P7.js";
337
+ } from "./chunk-SLGGOASN.js";
317
338
  import {
318
339
  credentialUserSubjectId
319
340
  } from "./chunk-CGA4ERJ5.js";
@@ -343,7 +364,7 @@ import {
343
364
  summarizeMessageText,
344
365
  withLogContext,
345
366
  withSpan
346
- } from "./chunk-7CVYXUOJ.js";
367
+ } from "./chunk-HQNE5APD.js";
347
368
  import {
348
369
  startInactiveSpan,
349
370
  withActiveSpan
@@ -987,6 +1008,97 @@ function nextEmptyOutputContinuation(args) {
987
1008
  return isContinuablePiBoundary(messages) ? { kind: "retry", messages } : { kind: "exhausted" };
988
1009
  }
989
1010
 
1011
+ // src/chat/services/workspace-snapshot-wait.ts
1012
+ import { randomUUID } from "crypto";
1013
+ var SWITCH_WORKSPACE_TOOL = "switchWorkspace";
1014
+ function pendingWorkspaceSnapshotWait(messages) {
1015
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
1016
+ const message = messages[index];
1017
+ if (!isToolResultMessage(message)) break;
1018
+ if (message.toolName !== SWITCH_WORKSPACE_TOOL) continue;
1019
+ const name = workspaceNameFromWaitingResult(message.details);
1020
+ return name ? { name } : null;
1021
+ }
1022
+ return null;
1023
+ }
1024
+ async function continueWorkspaceSnapshotWait(args) {
1025
+ if (!isContinuablePiBoundary(args.messages)) return { kind: "none" };
1026
+ const pending = pendingWorkspaceSnapshotWait(args.messages);
1027
+ if (!pending) return { kind: "none" };
1028
+ const tool = args.tools.find((entry) => entry.name === SWITCH_WORKSPACE_TOOL);
1029
+ if (!tool) return { kind: "none" };
1030
+ const toolCallId = randomUUID();
1031
+ const assistantMessage = {
1032
+ role: "assistant",
1033
+ api: "openai-responses",
1034
+ provider: "openai",
1035
+ model: "host-continue",
1036
+ content: [
1037
+ {
1038
+ type: "toolCall",
1039
+ id: toolCallId,
1040
+ name: SWITCH_WORKSPACE_TOOL,
1041
+ arguments: { name: pending.name }
1042
+ }
1043
+ ],
1044
+ usage: {
1045
+ input: 0,
1046
+ output: 0,
1047
+ cacheRead: 0,
1048
+ cacheWrite: 0,
1049
+ totalTokens: 0,
1050
+ cost: {
1051
+ input: 0,
1052
+ output: 0,
1053
+ cacheRead: 0,
1054
+ cacheWrite: 0,
1055
+ total: 0
1056
+ }
1057
+ },
1058
+ timestamp: Date.now(),
1059
+ stopReason: "toolUse"
1060
+ };
1061
+ let result;
1062
+ try {
1063
+ result = await tool.execute(
1064
+ toolCallId,
1065
+ { name: pending.name },
1066
+ args.signal
1067
+ );
1068
+ } catch (error) {
1069
+ const message = error instanceof Error ? error.message : String(error);
1070
+ const toolResultMessage2 = {
1071
+ role: "toolResult",
1072
+ toolCallId,
1073
+ toolName: SWITCH_WORKSPACE_TOOL,
1074
+ content: [{ type: "text", text: message }],
1075
+ details: { error: message },
1076
+ isError: true,
1077
+ timestamp: Date.now()
1078
+ };
1079
+ return {
1080
+ kind: "failed",
1081
+ error,
1082
+ messages: [...args.messages, assistantMessage, toolResultMessage2]
1083
+ };
1084
+ }
1085
+ const toolResultMessage = {
1086
+ role: "toolResult",
1087
+ toolCallId,
1088
+ toolName: SWITCH_WORKSPACE_TOOL,
1089
+ content: result.content,
1090
+ details: result.details,
1091
+ isError: false,
1092
+ timestamp: Date.now()
1093
+ };
1094
+ const messages = [
1095
+ ...args.messages,
1096
+ assistantMessage,
1097
+ toolResultMessage
1098
+ ];
1099
+ return isWorkspaceSnapshotWaitingResult(result.details) ? { kind: "waiting", messages } : { kind: "ready", messages };
1100
+ }
1101
+
990
1102
  // src/chat/agent/retry-usage.ts
991
1103
  function getDiscardedRetryUsage(messages, retryMessages) {
992
1104
  const keepsExactPrefix = retryMessages.length <= messages.length && retryMessages.every((message, index) => message === messages[index]);
@@ -2671,7 +2783,7 @@ function formatSandboxCommandResult(params) {
2671
2783
  import fs4 from "fs/promises";
2672
2784
 
2673
2785
  // src/chat/sandbox/egress/session.ts
2674
- import { createHmac, randomUUID, timingSafeEqual } from "crypto";
2786
+ import { createHmac, randomUUID as randomUUID2, timingSafeEqual } from "crypto";
2675
2787
  var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
2676
2788
  var SANDBOX_EGRESS_TOKEN_VERSION = "v1";
2677
2789
  var SANDBOX_EGRESS_HMAC_CONTEXT = "junior.sandbox_egress.v1";
@@ -2743,7 +2855,7 @@ function createSandboxEgressCredentialToken(input) {
2743
2855
  credentials: input.credentials,
2744
2856
  egressId: input.egressId,
2745
2857
  expiresAtMs: now + ttlMs2,
2746
- contextId: randomUUID()
2858
+ contextId: randomUUID2()
2747
2859
  };
2748
2860
  const payload = `${SANDBOX_EGRESS_TOKEN_VERSION}.${base64Url(
2749
2861
  JSON.stringify(context)
@@ -2960,11 +3072,11 @@ async function resolveSandboxCommandEnvironment() {
2960
3072
  }
2961
3073
 
2962
3074
  // src/chat/sandbox/session.ts
2963
- import { randomUUID as randomUUID2 } from "crypto";
2964
- import { Sandbox } from "@vercel/sandbox";
3075
+ import { randomUUID as randomUUID4 } from "crypto";
3076
+ import { Sandbox as Sandbox2 } from "@vercel/sandbox";
2965
3077
 
2966
3078
  // src/chat/sandbox/prepare-workspace.ts
2967
- async function prepareWorkspaceSnapshot(params) {
3079
+ async function prepareWorkspaceRepositories(params) {
2968
3080
  const { sandbox, workspace, signal } = params;
2969
3081
  signal?.throwIfAborted();
2970
3082
  await params.applyNetworkPolicy(sandbox);
@@ -2972,15 +3084,54 @@ async function prepareWorkspaceSnapshot(params) {
2972
3084
  if (params.removeCredentialRoute) {
2973
3085
  await sandbox.update({ networkPolicy: "allow-all" });
2974
3086
  }
2975
- if (!workspace.setupScript.trim()) return;
2976
- const result = await sandbox.runCommand({
3087
+ }
3088
+ function workspaceSetupCommand(workspace) {
3089
+ return {
2977
3090
  cmd: "bash",
2978
3091
  args: ["-euo", "pipefail", "-c", workspace.setupScript],
2979
3092
  cwd: SANDBOX_WORKSPACE_ROOT,
2980
3093
  env: {
2981
3094
  JUNIOR_REPOS_ROOT: SANDBOX_REPOS_ROOT,
2982
3095
  JUNIOR_WORKSPACE_ROOT: SANDBOX_WORKSPACE_ROOT
2983
- },
3096
+ }
3097
+ };
3098
+ }
3099
+ function workspaceSnapshotSetupCommand(workspace, buildId) {
3100
+ const stateRoot = `/tmp/junior-snapshot-build-${buildId}`;
3101
+ const wrapper = [
3102
+ `mkdir -p "${stateRoot}"`,
3103
+ `exec 9>"${stateRoot}/lock"`,
3104
+ "flock 9",
3105
+ `if test -f "${stateRoot}/exit"; then exit "$(cat "${stateRoot}/exit")"; fi`,
3106
+ "set +e",
3107
+ 'bash -euo pipefail -c "$1"',
3108
+ "status=$?",
3109
+ `printf '%s\\n' "$status" > "${stateRoot}/exit"`,
3110
+ 'exit "$status"'
3111
+ ].join("\n");
3112
+ return {
3113
+ cmd: "bash",
3114
+ args: [
3115
+ "-euo",
3116
+ "pipefail",
3117
+ "-c",
3118
+ wrapper,
3119
+ "junior-setup",
3120
+ workspace.setupScript
3121
+ ],
3122
+ cwd: SANDBOX_WORKSPACE_ROOT,
3123
+ env: {
3124
+ JUNIOR_REPOS_ROOT: SANDBOX_REPOS_ROOT,
3125
+ JUNIOR_WORKSPACE_ROOT: SANDBOX_WORKSPACE_ROOT
3126
+ }
3127
+ };
3128
+ }
3129
+ async function prepareWorkspaceSnapshot(params) {
3130
+ const { sandbox, workspace, signal } = params;
3131
+ await prepareWorkspaceRepositories(params);
3132
+ if (!workspace.setupScript.trim()) return;
3133
+ const result = await sandbox.runCommand({
3134
+ ...workspaceSetupCommand(workspace),
2984
3135
  signal
2985
3136
  });
2986
3137
  if (result.exitCode !== 0) {
@@ -2990,6 +3141,486 @@ async function prepareWorkspaceSnapshot(params) {
2990
3141
  }
2991
3142
  }
2992
3143
 
3144
+ // src/chat/sandbox/snapshot/workspace.ts
3145
+ import { randomUUID as randomUUID3 } from "crypto";
3146
+ import { Sandbox } from "@vercel/sandbox";
3147
+ var BUILD_TIMEOUT_MS = 60 * 60 * 1e3;
3148
+ var BUILD_TIMEOUT_ERROR = "Workspace snapshot build timed out after 1 hour";
3149
+ var BUILD_LOCK_TTL_MS = 2 * 60 * 1e3;
3150
+ var BUILD_LOCK_PREFIX = "junior:workspace_snapshot_start";
3151
+ var WAIT_POLL_MS = 5e3;
3152
+ var TRANSIENT_API_RETRY_MS = 2e3;
3153
+ var BUILD_COMMAND_TIMEOUT_MS = 10 * 60 * 1e3;
3154
+ var BUILD_COMMAND_TIMEOUT_ERROR = "Workspace snapshot build phase timed out after 10 minutes";
3155
+ var COMMAND_STATUS_POLL_TIMEOUT_MS = 1e3;
3156
+ var WAIT_YIELD_BUFFER_MS = 4e4;
3157
+ function isCancelOrTransient(error) {
3158
+ return isAbortError(error) || isSandboxApiTransientError(error);
3159
+ }
3160
+ function buildLockKey(profileHash) {
3161
+ return `${BUILD_LOCK_PREFIX}:${profileHash}`;
3162
+ }
3163
+ function builderName() {
3164
+ return `junior-ws-${randomUUID3()}`;
3165
+ }
3166
+ async function snapshotFromSql(workspaceId, value, staleSnapshotId) {
3167
+ const { ready } = await loadSnapshotsForProfile(
3168
+ getDb(),
3169
+ workspaceId,
3170
+ value.hash
3171
+ );
3172
+ if (!ready) return null;
3173
+ if (ready.id === staleSnapshotId || isStale(value, ready.generatedAt.getTime())) {
3174
+ const ownerName = await invalidateReadySnapshot({
3175
+ workspaceId,
3176
+ profileHash: value.hash,
3177
+ snapshotId: ready.id
3178
+ });
3179
+ try {
3180
+ await deleteBuilder(ownerName);
3181
+ } catch (error) {
3182
+ logException(error, "sandbox.workspace_snapshot.builder.delete_failed", {
3183
+ "app.workspace.id": workspaceId
3184
+ });
3185
+ }
3186
+ return null;
3187
+ }
3188
+ return {
3189
+ snapshotId: ready.id,
3190
+ profileHash: ready.profileHash,
3191
+ dependencyCount: value.dependencyCount,
3192
+ cacheHit: true,
3193
+ resolveOutcome: "cache_hit",
3194
+ createdAtMs: ready.generatedAt.getTime(),
3195
+ buildDurationMs: ready.buildDurationMs
3196
+ };
3197
+ }
3198
+ function activeBuild(build) {
3199
+ if (!build || build.status !== "building" || !build.sandboxName) return null;
3200
+ return build;
3201
+ }
3202
+ async function deleteBuilder(sandboxName) {
3203
+ if (!sandboxName) return;
3204
+ await deleteWorkspaceSnapshotBuilders([sandboxName]);
3205
+ }
3206
+ function workspaceDeletedError(workspace) {
3207
+ return new Error(
3208
+ `Workspace ${workspace.name} was deleted while its snapshot was building`
3209
+ );
3210
+ }
3211
+ function requireBuildWrite(written, workspace) {
3212
+ if (!written) {
3213
+ throw new Error(
3214
+ `Workspace ${workspace.name} snapshot build lost SQL ownership`
3215
+ );
3216
+ }
3217
+ }
3218
+ async function markFailed(workspace, build, error, beforeWrite) {
3219
+ try {
3220
+ await beforeWrite();
3221
+ const written = await setWorkspaceSnapshotBuild(workspace.id, {
3222
+ ...build,
3223
+ status: "failed",
3224
+ error: error instanceof Error ? error.message : String(error)
3225
+ });
3226
+ requireBuildWrite(written, workspace);
3227
+ } catch (persistError) {
3228
+ logException(
3229
+ persistError,
3230
+ "sandbox.workspace_snapshot.failure_persist_failed",
3231
+ { "app.workspace.id": workspace.id }
3232
+ );
3233
+ return;
3234
+ }
3235
+ try {
3236
+ await deleteBuilder(build.sandboxName);
3237
+ } catch (cleanupError) {
3238
+ logException(
3239
+ cleanupError,
3240
+ "sandbox.workspace_snapshot.builder.delete_failed",
3241
+ { "app.workspace.id": workspace.id }
3242
+ );
3243
+ }
3244
+ }
3245
+ async function requireRemainingBuildTime(workspace, build, beforeWrite) {
3246
+ const remainingMs = build.startedAt.getTime() + BUILD_TIMEOUT_MS - Date.now();
3247
+ if (remainingMs > 0) return remainingMs;
3248
+ await markFailed(workspace, build, BUILD_TIMEOUT_ERROR, beforeWrite);
3249
+ throw new Error(BUILD_TIMEOUT_ERROR);
3250
+ }
3251
+ async function finishBuild(workspace, value, signal, beforeWrite) {
3252
+ const { build: loaded } = await loadSnapshotsForProfile(
3253
+ getDb(),
3254
+ workspace.id,
3255
+ value.hash
3256
+ );
3257
+ const build = activeBuild(loaded);
3258
+ if (!build?.commandId || !build.sandboxName) return null;
3259
+ try {
3260
+ await requireRemainingBuildTime(workspace, build, beforeWrite);
3261
+ const sandbox = await getWorkspaceSnapshotBuilder(
3262
+ build.sandboxName,
3263
+ signal
3264
+ );
3265
+ const command = await sandbox.getCommand(build.commandId, { signal });
3266
+ const pollSignal = AbortSignal.timeout(COMMAND_STATUS_POLL_TIMEOUT_MS);
3267
+ const waitSignal = signal ? AbortSignal.any([signal, pollSignal]) : pollSignal;
3268
+ let finished;
3269
+ try {
3270
+ finished = await command.wait({ signal: waitSignal });
3271
+ } catch (error) {
3272
+ if (pollSignal.aborted && !signal?.aborted) return null;
3273
+ throw error;
3274
+ }
3275
+ if (finished.exitCode !== 0) {
3276
+ const error = (await finished.stderr({ signal })).trim() || `exit ${finished.exitCode}`;
3277
+ throw new Error(
3278
+ `Workspace ${workspace.name} snapshot setup failed: ${error}`
3279
+ );
3280
+ }
3281
+ await requireRemainingBuildTime(workspace, build, beforeWrite);
3282
+ const snapshot = await sandbox.snapshot({ signal });
3283
+ await requireRemainingBuildTime(workspace, build, beforeWrite);
3284
+ const createdAtMs = Date.now();
3285
+ const buildDurationMs = Math.max(
3286
+ 0,
3287
+ createdAtMs - build.startedAt.getTime()
3288
+ );
3289
+ await beforeWrite();
3290
+ const result = await setWorkspaceSnapshot(
3291
+ workspace.id,
3292
+ {
3293
+ id: snapshot.snapshotId,
3294
+ generatedAt: new Date(createdAtMs),
3295
+ buildDurationMs,
3296
+ profileHash: value.hash
3297
+ },
3298
+ { buildId: build.id }
3299
+ );
3300
+ requireBuildWrite(result.written, workspace);
3301
+ try {
3302
+ await deleteWorkspaceSnapshotBuilders(result.replacedBuilderNames);
3303
+ } catch (error) {
3304
+ logException(error, "sandbox.workspace_snapshot.builder.delete_failed", {
3305
+ "app.workspace.id": workspace.id,
3306
+ "app.sandbox.snapshot.builder_count": result.replacedBuilderNames.length
3307
+ });
3308
+ }
3309
+ return {
3310
+ snapshotId: snapshot.snapshotId,
3311
+ profileHash: value.hash,
3312
+ dependencyCount: value.dependencyCount,
3313
+ cacheHit: false,
3314
+ resolveOutcome: "rebuilt",
3315
+ rebuildReason: "cache_miss",
3316
+ createdAtMs,
3317
+ buildDurationMs
3318
+ };
3319
+ } catch (error) {
3320
+ if (error instanceof Error && error.message === BUILD_TIMEOUT_ERROR) {
3321
+ throw error;
3322
+ }
3323
+ if (isCancelOrTransient(error)) throw error;
3324
+ await markFailed(workspace, build, error, beforeWrite);
3325
+ throw error;
3326
+ }
3327
+ }
3328
+ async function startBuild(params) {
3329
+ const { workspace, value, signal } = params;
3330
+ const credentials = getVercelSandboxCredentials();
3331
+ const resources = getSandboxResources();
3332
+ const name = builderName();
3333
+ const buildId = randomUUID3();
3334
+ const startedAt = /* @__PURE__ */ new Date();
3335
+ try {
3336
+ await Sandbox.create({
3337
+ name,
3338
+ persistent: true,
3339
+ timeout: BUILD_TIMEOUT_MS,
3340
+ runtime: params.runtime,
3341
+ signal,
3342
+ ...credentials ?? {},
3343
+ ...resources ? { resources } : {}
3344
+ });
3345
+ await params.beforeWrite();
3346
+ const written = await setWorkspaceSnapshotBuild(
3347
+ workspace.id,
3348
+ {
3349
+ id: buildId,
3350
+ status: "building",
3351
+ phase: "created",
3352
+ profileHash: value.hash,
3353
+ startedAt,
3354
+ sandboxName: name,
3355
+ commandId: null,
3356
+ error: null
3357
+ },
3358
+ { insertIfMissing: true, expectedRecipe: workspace }
3359
+ );
3360
+ if (!written) {
3361
+ try {
3362
+ await deleteBuilder(name);
3363
+ } catch (cleanupError) {
3364
+ logException(
3365
+ cleanupError,
3366
+ "sandbox.workspace_snapshot.builder.delete_failed",
3367
+ { "app.workspace.id": workspace.id }
3368
+ );
3369
+ }
3370
+ return;
3371
+ }
3372
+ } catch (error) {
3373
+ try {
3374
+ if (!isCancelOrTransient(error)) {
3375
+ try {
3376
+ await params.beforeWrite();
3377
+ await setWorkspaceSnapshotBuild(
3378
+ workspace.id,
3379
+ {
3380
+ id: buildId,
3381
+ status: "failed",
3382
+ phase: "created",
3383
+ profileHash: value.hash,
3384
+ startedAt,
3385
+ sandboxName: name,
3386
+ commandId: null,
3387
+ error: error instanceof Error ? error.message : String(error)
3388
+ },
3389
+ { insertIfMissing: true, expectedRecipe: workspace }
3390
+ );
3391
+ } catch {
3392
+ }
3393
+ }
3394
+ } finally {
3395
+ try {
3396
+ await deleteBuilder(name);
3397
+ } catch (cleanupError) {
3398
+ logException(
3399
+ cleanupError,
3400
+ "sandbox.workspace_snapshot.builder.delete_failed",
3401
+ { "app.workspace.id": workspace.id }
3402
+ );
3403
+ }
3404
+ }
3405
+ throw error;
3406
+ }
3407
+ }
3408
+ async function continueBuild(params) {
3409
+ const { build: loaded } = await loadSnapshotsForProfile(
3410
+ getDb(),
3411
+ params.workspace.id,
3412
+ params.value.hash
3413
+ );
3414
+ const build = activeBuild(loaded);
3415
+ if (!build?.sandboxName || build.commandId) return;
3416
+ const remainingMs = await requireRemainingBuildTime(
3417
+ params.workspace,
3418
+ build,
3419
+ params.beforeWrite
3420
+ );
3421
+ const commandTimeoutMs = Math.min(remainingMs, BUILD_COMMAND_TIMEOUT_MS);
3422
+ const phaseTimeoutSignal = AbortSignal.timeout(commandTimeoutMs);
3423
+ const phaseSignal = params.signal ? AbortSignal.any([params.signal, phaseTimeoutSignal]) : phaseTimeoutSignal;
3424
+ try {
3425
+ const sandbox = await getWorkspaceSnapshotBuilder(
3426
+ build.sandboxName,
3427
+ phaseSignal
3428
+ );
3429
+ const session = createSandboxSession(sandbox);
3430
+ if (build.phase === "created") {
3431
+ await dependencies(
3432
+ session,
3433
+ params.value.dependencies,
3434
+ phaseSignal,
3435
+ commandTimeoutMs
3436
+ );
3437
+ await postinstall(
3438
+ session,
3439
+ params.value.postinstall,
3440
+ phaseSignal,
3441
+ commandTimeoutMs
3442
+ );
3443
+ await requireRemainingBuildTime(
3444
+ params.workspace,
3445
+ build,
3446
+ params.beforeWrite
3447
+ );
3448
+ await params.beforeWrite();
3449
+ const written2 = await setWorkspaceSnapshotBuild(params.workspace.id, {
3450
+ ...build,
3451
+ phase: "dependencies_installed"
3452
+ });
3453
+ requireBuildWrite(written2, params.workspace);
3454
+ return;
3455
+ }
3456
+ if (build.phase === "dependencies_installed") {
3457
+ await prepareWorkspaceRepositories({
3458
+ sandbox: session,
3459
+ workspace: params.workspace,
3460
+ signal: phaseSignal,
3461
+ applyNetworkPolicy: params.applyNetworkPolicy,
3462
+ prepareRepositories: params.prepareRepositories,
3463
+ removeCredentialRoute: params.removeCredentialRoute
3464
+ });
3465
+ await requireRemainingBuildTime(
3466
+ params.workspace,
3467
+ build,
3468
+ params.beforeWrite
3469
+ );
3470
+ await params.beforeWrite();
3471
+ const written2 = await setWorkspaceSnapshotBuild(params.workspace.id, {
3472
+ ...build,
3473
+ phase: "repositories_prepared"
3474
+ });
3475
+ requireBuildWrite(written2, params.workspace);
3476
+ return;
3477
+ }
3478
+ await params.beforeWrite();
3479
+ const command = await sandbox.runCommand({
3480
+ ...workspaceSnapshotSetupCommand(params.workspace, build.id),
3481
+ detached: true,
3482
+ timeoutMs: remainingMs,
3483
+ signal: phaseSignal
3484
+ });
3485
+ await params.beforeWrite();
3486
+ const written = await setWorkspaceSnapshotBuild(params.workspace.id, {
3487
+ ...build,
3488
+ commandId: command.cmdId,
3489
+ error: null
3490
+ });
3491
+ requireBuildWrite(written, params.workspace);
3492
+ } catch (error) {
3493
+ if (phaseTimeoutSignal.aborted && !params.signal?.aborted) {
3494
+ const timeoutError = new Error(BUILD_COMMAND_TIMEOUT_ERROR, {
3495
+ cause: error
3496
+ });
3497
+ await markFailed(
3498
+ params.workspace,
3499
+ build,
3500
+ timeoutError,
3501
+ params.beforeWrite
3502
+ );
3503
+ throw timeoutError;
3504
+ }
3505
+ if (error instanceof Error && error.message === BUILD_TIMEOUT_ERROR) {
3506
+ throw error;
3507
+ }
3508
+ if (isCancelOrTransient(error)) throw error;
3509
+ await markFailed(params.workspace, build, error, params.beforeWrite);
3510
+ throw error;
3511
+ }
3512
+ }
3513
+ async function advanceWorkspaceSnapshot(params) {
3514
+ const value = create(params.runtime, params.workspace);
3515
+ if (!value) {
3516
+ throw new Error(
3517
+ `Workspace ${params.workspace.name} has no snapshot profile`
3518
+ );
3519
+ }
3520
+ const sqlCached = await snapshotFromSql(
3521
+ params.workspace.id,
3522
+ value,
3523
+ params.staleSnapshotId
3524
+ );
3525
+ if (sqlCached) return sqlCached;
3526
+ const state = getStateAdapter();
3527
+ await state.connect();
3528
+ const locked = await withLock(
3529
+ state,
3530
+ buildLockKey(value.hash),
3531
+ async (lock) => {
3532
+ const workspace = await getWorkspace(getDb(), params.workspace.id);
3533
+ if (!workspace) throw workspaceDeletedError(params.workspace);
3534
+ const lockedValue = create(params.runtime, workspace) ?? value;
3535
+ if (lockedValue.hash !== value.hash) return null;
3536
+ const beforeWrite = async () => {
3537
+ await fenceLock(state, lock, BUILD_LOCK_TTL_MS);
3538
+ };
3539
+ const sqlInsideLock = await snapshotFromSql(
3540
+ workspace.id,
3541
+ lockedValue,
3542
+ params.staleSnapshotId
3543
+ );
3544
+ if (sqlInsideLock) return sqlInsideLock;
3545
+ const finished = await finishBuild(
3546
+ workspace,
3547
+ lockedValue,
3548
+ params.signal,
3549
+ beforeWrite
3550
+ );
3551
+ if (finished) return finished;
3552
+ const { build: loaded } = await loadSnapshotsForProfile(
3553
+ getDb(),
3554
+ workspace.id,
3555
+ lockedValue.hash
3556
+ );
3557
+ const build = activeBuild(loaded);
3558
+ if (build && !build.commandId) {
3559
+ await continueBuild({
3560
+ ...params,
3561
+ workspace,
3562
+ value: lockedValue,
3563
+ beforeWrite
3564
+ });
3565
+ } else if (!build) {
3566
+ if (loaded?.sandboxName) {
3567
+ try {
3568
+ await deleteBuilder(loaded.sandboxName);
3569
+ } catch (cleanupError) {
3570
+ logException(
3571
+ cleanupError,
3572
+ "sandbox.workspace_snapshot.builder.delete_failed",
3573
+ { "app.workspace.id": workspace.id }
3574
+ );
3575
+ }
3576
+ }
3577
+ await startBuild({
3578
+ workspace,
3579
+ value: lockedValue,
3580
+ runtime: params.runtime,
3581
+ signal: params.signal,
3582
+ beforeWrite
3583
+ });
3584
+ }
3585
+ return null;
3586
+ },
3587
+ { ttlMs: BUILD_LOCK_TTL_MS, keepAlive: true }
3588
+ );
3589
+ return locked.acquired ? locked.value ?? null : null;
3590
+ }
3591
+ function shouldYieldWait(shouldYield) {
3592
+ if (shouldYield?.()) return true;
3593
+ const deadlineAtMs = getTurnRequestDeadline()?.deadlineAtMs;
3594
+ return deadlineAtMs !== void 0 && Date.now() >= deadlineAtMs - WAIT_YIELD_BUFFER_MS;
3595
+ }
3596
+ async function resolveWorkspaceSnapshot(params) {
3597
+ for (; ; ) {
3598
+ params.signal?.throwIfAborted();
3599
+ const workspace = await getWorkspace(getDb(), params.workspace.id);
3600
+ if (!workspace) throw workspaceDeletedError(params.workspace);
3601
+ const slice = { ...params, workspace };
3602
+ if (shouldYieldWait(slice.shouldYield)) {
3603
+ throw new WorkspaceSnapshotWaitingError(workspace.name);
3604
+ }
3605
+ let snapshot;
3606
+ try {
3607
+ snapshot = await advanceWorkspaceSnapshot(slice);
3608
+ } catch (error) {
3609
+ if (!isSandboxApiTransientError(error)) throw error;
3610
+ if (shouldYieldWait(slice.shouldYield)) {
3611
+ throw new WorkspaceSnapshotWaitingError(workspace.name);
3612
+ }
3613
+ await sleep(TRANSIENT_API_RETRY_MS, params.signal);
3614
+ continue;
3615
+ }
3616
+ if (shouldYieldWait(slice.shouldYield)) {
3617
+ throw new WorkspaceSnapshotWaitingError(workspace.name);
3618
+ }
3619
+ if (snapshot) return snapshot;
3620
+ await sleep(WAIT_POLL_MS, params.signal);
3621
+ }
3622
+ }
3623
+
2993
3624
  // src/chat/sandbox/skill-sync.ts
2994
3625
  import fs3 from "fs/promises";
2995
3626
  import path3 from "path";
@@ -3247,7 +3878,7 @@ function createSandboxRuntime(options) {
3247
3878
  const adaptSandbox = (vercelSandbox) => createSandboxSession(vercelSandbox, {
3248
3879
  onUnavailable: invalidateSession
3249
3880
  });
3250
- const createSandboxName = () => `${SANDBOX_NAME_PREFIX}${randomUUID2()}`;
3881
+ const createSandboxName = () => `${SANDBOX_NAME_PREFIX}${randomUUID4()}`;
3251
3882
  const preflightNetworkPolicy = (sandboxName) => options.createNetworkPolicy?.(sandboxName);
3252
3883
  const persistSandboxRef = async (nextRef) => {
3253
3884
  if (reportedSandboxRef?.id === nextRef.id && reportedSandboxRef.profileHash === nextRef.profileHash && reportedSandboxRef.workspaceId === nextRef.workspaceId) {
@@ -3339,7 +3970,7 @@ function createSandboxRuntime(options) {
3339
3970
  const networkPolicy = preflightNetworkPolicy(sandboxName);
3340
3971
  try {
3341
3972
  return adaptSandbox(
3342
- await Sandbox.create({
3973
+ await Sandbox2.create({
3343
3974
  timeout: timeoutMs,
3344
3975
  ...networkPolicy ? { name: sandboxName, persistent: false, networkPolicy } : {},
3345
3976
  source: {
@@ -3360,20 +3991,6 @@ function createSandboxRuntime(options) {
3360
3991
  }
3361
3992
  throw new Error(`Failed to boot sandbox from snapshot ${snapshotId}`);
3362
3993
  };
3363
- const setSnapshotAttributes = (snapshot) => {
3364
- setSpanAttributes({
3365
- "app.sandbox.source": snapshot.snapshotId ? "snapshot" : "created",
3366
- "app.sandbox.snapshot.cache_hit": snapshot.cacheHit,
3367
- "app.sandbox.snapshot.resolve_outcome": snapshot.resolveOutcome,
3368
- ...snapshot.profileHash ? {
3369
- "app.sandbox.snapshot.profile_hash": snapshot.profileHash
3370
- } : {},
3371
- "app.sandbox.snapshot.dependency_count": snapshot.dependencyCount,
3372
- ...snapshot.rebuildReason ? {
3373
- "app.sandbox.snapshot.rebuild_reason": snapshot.rebuildReason
3374
- } : {}
3375
- });
3376
- };
3377
3994
  const createSandboxFromResolvedSnapshot = async (params) => {
3378
3995
  const { runtime, snapshot, sandboxCredentials, sandboxName, signal } = params;
3379
3996
  signal?.throwIfAborted();
@@ -3381,7 +3998,7 @@ function createSandboxRuntime(options) {
3381
3998
  const networkPolicy = preflightNetworkPolicy(sandboxName);
3382
3999
  const resources = getSandboxResources();
3383
4000
  const session = adaptSandbox(
3384
- await Sandbox.create({
4001
+ await Sandbox2.create({
3385
4002
  timeout: timeoutMs,
3386
4003
  runtime,
3387
4004
  ...networkPolicy ? { name: sandboxName, persistent: false, networkPolicy } : {},
@@ -3403,13 +4020,21 @@ function createSandboxRuntime(options) {
3403
4020
  } catch (error) {
3404
4021
  if (!isMissingError(error)) throw error;
3405
4022
  setSpanAttributes({ "app.sandbox.snapshot.rebuild_after_missing": true });
3406
- const rebuilt = await resolve({
4023
+ const rebuilt = params.workspace ? await resolveWorkspaceSnapshot({
4024
+ workspace: params.workspace,
4025
+ runtime,
4026
+ staleSnapshotId: snapshot.snapshotId,
4027
+ signal,
4028
+ shouldYield: options.shouldYield,
4029
+ applyNetworkPolicy,
4030
+ prepareRepositories: options.onWorkspacePrepare,
4031
+ removeCredentialRoute: Boolean(options.createNetworkPolicy)
4032
+ }) : await resolve({
3407
4033
  runtime,
3408
4034
  timeoutMs,
3409
4035
  forceRebuild: true,
3410
4036
  staleSnapshotId: snapshot.snapshotId,
3411
4037
  signal,
3412
- workspace: params.workspace,
3413
4038
  prepareWorkspace: params.prepareWorkspace
3414
4039
  });
3415
4040
  if (!rebuilt.snapshotId) throw error;
@@ -3446,15 +4071,22 @@ function createSandboxRuntime(options) {
3446
4071
  "app.sandbox.runtime": runtime
3447
4072
  },
3448
4073
  async () => {
3449
- const snapshot = await resolve({
4074
+ const snapshot = workspace ? await resolveWorkspaceSnapshot({
4075
+ workspace,
4076
+ runtime,
4077
+ signal,
4078
+ shouldYield: options.shouldYield,
4079
+ applyNetworkPolicy,
4080
+ prepareRepositories: options.onWorkspacePrepare,
4081
+ removeCredentialRoute: Boolean(options.createNetworkPolicy)
4082
+ }) : await resolve({
3450
4083
  runtime,
3451
4084
  timeoutMs,
3452
4085
  signal,
3453
- workspace,
3454
4086
  prepareWorkspace
3455
4087
  });
3456
4088
  signal?.throwIfAborted();
3457
- setSnapshotAttributes(snapshot);
4089
+ setSnapshotSpanAttributes(snapshot);
3458
4090
  const created = await createSandboxFromResolvedSnapshot({
3459
4091
  runtime,
3460
4092
  snapshot,
@@ -3464,12 +4096,6 @@ function createSandboxRuntime(options) {
3464
4096
  workspace,
3465
4097
  prepareWorkspace
3466
4098
  });
3467
- if (workspace) {
3468
- await recordResolvedWorkspaceSnapshot(
3469
- workspace.id,
3470
- created.snapshot
3471
- );
3472
- }
3473
4099
  return created.session;
3474
4100
  }
3475
4101
  );
@@ -3488,6 +4114,9 @@ function createSandboxRuntime(options) {
3488
4114
  let networkPolicyKey;
3489
4115
  try {
3490
4116
  networkPolicyKey = await applyNetworkPolicy(createdSandbox);
4117
+ if (workspace) {
4118
+ await options.onWorkspacePrepare?.(createdSandbox, workspace, signal);
4119
+ }
3491
4120
  await prepareSandbox(createdSandbox);
3492
4121
  } catch (error) {
3493
4122
  if (workspace) await stopSession(createdSandbox);
@@ -3551,7 +4180,7 @@ function createSandboxRuntime(options) {
3551
4180
  "app.sandbox.source": "id_hint"
3552
4181
  },
3553
4182
  async () => adaptSandbox(
3554
- await Sandbox.get({
4183
+ await Sandbox2.get({
3555
4184
  name: ref.id,
3556
4185
  resume: true,
3557
4186
  ...sandboxCredentials ?? {},
@@ -4636,6 +5265,11 @@ async function grepFilesWithRipgrep(params) {
4636
5265
  });
4637
5266
  if (result.exitCode !== 0 && result.exitCode !== 1) {
4638
5267
  const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
5268
+ if (/error parsing glob/i.test(detail)) {
5269
+ throw new ToolInputError(`Invalid glob: ${params.glob ?? ""}`, {
5270
+ cause: new Error(detail)
5271
+ });
5272
+ }
4639
5273
  if (/regex parse error|error parsing regex/i.test(detail)) {
4640
5274
  throw new ToolInputError(`Invalid regex pattern: ${params.pattern}`, {
4641
5275
  cause: new Error(detail)
@@ -5090,6 +5724,7 @@ function createSandbox(options) {
5090
5724
  skills: options.skills,
5091
5725
  referenceFiles: options.referenceFiles,
5092
5726
  timeoutMs: options.timeoutMs,
5727
+ shouldYield: options.shouldYield,
5093
5728
  traceContext,
5094
5729
  commandEnv: credentialEgress ? resolveSandboxCommandEnvironment : void 0,
5095
5730
  createNetworkPolicy: credentialEgress || hasTracePropagationDomains ? (sessionId, traceHeaders) => buildSandboxEgressNetworkPolicy({
@@ -5543,9 +6178,9 @@ function createSandbox(options) {
5543
6178
  return void 0;
5544
6179
  }
5545
6180
  const { fs: fs5 } = await runtime.tools();
5546
- const workspaceDirectories = activeWorkspace?.repos.map((repo) => tryWorkspaceRepoCheckoutPath(repo.repo)).filter((checkoutPath) => Boolean(checkoutPath)).map(
5547
- (checkoutPath) => `${SANDBOX_WORKSPACE_ROOT}/${checkoutPath}`
5548
- ) ?? [];
6181
+ const workspaceDirectories = activeWorkspace?.repos.map((repo) => tryWorkspaceRepoCheckoutPath(repo.repo)).filter(
6182
+ (checkoutPath) => Boolean(checkoutPath)
6183
+ ).map((checkoutPath) => `${SANDBOX_WORKSPACE_ROOT}/${checkoutPath}`) ?? [];
5549
6184
  const directories = workspaceDirectories.length > 0 ? workspaceDirectories : await listRepositoryDirectories(fs5);
5550
6185
  return await resolveRepositoryInstructionsForDirectories({
5551
6186
  directories,
@@ -5985,6 +6620,7 @@ function createAgentSandbox(options) {
5985
6620
  tracePropagation: options.tracePropagation,
5986
6621
  egressSignals: options.egressSignals,
5987
6622
  credentialEgress: options.credentialEgress,
6623
+ shouldYield: options.shouldYield,
5988
6624
  prepare: options.prepareSandbox,
5989
6625
  prepareWorkspace: options.prepareWorkspace,
5990
6626
  onSandboxRefChanged: async (sandboxRef) => {
@@ -8173,6 +8809,296 @@ import { z as z28 } from "zod";
8173
8809
 
8174
8810
  // src/chat/scheduled-tasks/schedule-intent.ts
8175
8811
  import { z as z26 } from "zod";
8812
+
8813
+ // src/chat/scheduled-tasks/cadence.ts
8814
+ var FORMATTERS = /* @__PURE__ */ new Map();
8815
+ function getFormatter(timezone) {
8816
+ const existing = FORMATTERS.get(timezone);
8817
+ if (existing) {
8818
+ return existing;
8819
+ }
8820
+ const formatter = new Intl.DateTimeFormat("en-US", {
8821
+ timeZone: timezone,
8822
+ hour12: false,
8823
+ year: "numeric",
8824
+ month: "2-digit",
8825
+ day: "2-digit",
8826
+ hour: "2-digit",
8827
+ minute: "2-digit",
8828
+ second: "2-digit"
8829
+ });
8830
+ FORMATTERS.set(timezone, formatter);
8831
+ return formatter;
8832
+ }
8833
+ function normalizeHour(hour) {
8834
+ return hour === 24 ? 0 : hour;
8835
+ }
8836
+ function getLocalDateWeekday(date) {
8837
+ return new Date(Date.UTC(date.year, date.month - 1, date.day)).getUTCDay();
8838
+ }
8839
+ function getWeekStart(date) {
8840
+ return addDays(date, -((getLocalDateWeekday(date) + 6) % 7));
8841
+ }
8842
+ function getZonedDateTimeParts(timestampMs, timezone) {
8843
+ const parts = getFormatter(timezone).formatToParts(new Date(timestampMs));
8844
+ const values = new Map(parts.map((part) => [part.type, part.value]));
8845
+ const year = Number(values.get("year"));
8846
+ const month = Number(values.get("month"));
8847
+ const day = Number(values.get("day"));
8848
+ const hour = normalizeHour(Number(values.get("hour")));
8849
+ const minute = Number(values.get("minute"));
8850
+ const second = Number(values.get("second"));
8851
+ return {
8852
+ year,
8853
+ month,
8854
+ day,
8855
+ hour,
8856
+ minute,
8857
+ second,
8858
+ weekday: getLocalDateWeekday({ year, month, day })
8859
+ };
8860
+ }
8861
+ function getTimeZoneOffsetMs(timestampMs, timezone) {
8862
+ const parts = getZonedDateTimeParts(timestampMs, timezone);
8863
+ return Date.UTC(
8864
+ parts.year,
8865
+ parts.month - 1,
8866
+ parts.day,
8867
+ parts.hour,
8868
+ parts.minute,
8869
+ parts.second
8870
+ ) - timestampMs;
8871
+ }
8872
+ function localDateTimeToTimestampMs(args) {
8873
+ const localAsUtcMs = Date.UTC(
8874
+ args.date.year,
8875
+ args.date.month - 1,
8876
+ args.date.day,
8877
+ args.time.hour,
8878
+ args.time.minute,
8879
+ 0
8880
+ );
8881
+ const offsets = /* @__PURE__ */ new Set();
8882
+ for (const probeDeltaMs of [
8883
+ -36 * 60 * 60 * 1e3,
8884
+ -12 * 60 * 60 * 1e3,
8885
+ 0,
8886
+ 12 * 60 * 60 * 1e3,
8887
+ 36 * 60 * 60 * 1e3
8888
+ ]) {
8889
+ offsets.add(
8890
+ getTimeZoneOffsetMs(localAsUtcMs + probeDeltaMs, args.timezone)
8891
+ );
8892
+ }
8893
+ const matches = [...offsets].map((offsetMs) => localAsUtcMs - offsetMs).filter((timestampMs) => {
8894
+ const parts = getZonedDateTimeParts(timestampMs, args.timezone);
8895
+ return parts.year === args.date.year && parts.month === args.date.month && parts.day === args.date.day && parts.hour === args.time.hour && parts.minute === args.time.minute;
8896
+ });
8897
+ if (matches.length === 0) {
8898
+ return void 0;
8899
+ }
8900
+ return Math.min(...matches);
8901
+ }
8902
+ function resolveLocalScheduleAtMs(args) {
8903
+ const date = parseLocalDate(args.date);
8904
+ if (!date) {
8905
+ return void 0;
8906
+ }
8907
+ return localDateTimeToTimestampMs({
8908
+ date,
8909
+ time: args.time,
8910
+ timezone: args.timezone
8911
+ });
8912
+ }
8913
+ function daysBetween(left, right) {
8914
+ return Math.floor(
8915
+ (Date.UTC(right.year, right.month - 1, right.day) - Date.UTC(left.year, left.month - 1, left.day)) / (24 * 60 * 60 * 1e3)
8916
+ );
8917
+ }
8918
+ function weeklyRecurrenceMatchesDate(date, start, recurrence) {
8919
+ if (compareDate(date, start) < 0) {
8920
+ return false;
8921
+ }
8922
+ return normalizeWeekdays(recurrence.weekdays).includes(
8923
+ getLocalDateWeekday(date)
8924
+ ) && Math.floor(daysBetween(getWeekStart(start), getWeekStart(date)) / 7) % recurrence.interval === 0;
8925
+ }
8926
+ function greatestCommonDivisor(left, right) {
8927
+ let a = Math.abs(left);
8928
+ let b = Math.abs(right);
8929
+ while (b !== 0) {
8930
+ [a, b] = [b, a % b];
8931
+ }
8932
+ return a;
8933
+ }
8934
+ function findNextRunAtMs(args) {
8935
+ const start = parseLocalDate(args.recurrence.startDate);
8936
+ const interval = args.recurrence.interval;
8937
+ if (!start || !Number.isInteger(interval) || interval <= 0) {
8938
+ return void 0;
8939
+ }
8940
+ const searchFrom = compareDate(args.searchFrom, start) < 0 ? start : args.searchFrom;
8941
+ if (args.recurrence.frequency === "daily") {
8942
+ const offsetDays = daysBetween(start, searchFrom);
8943
+ let candidateDate = addDays(
8944
+ start,
8945
+ Math.ceil(offsetDays / interval) * interval
8946
+ );
8947
+ const gregorianCycleDays = 146097;
8948
+ const candidateCount2 = gregorianCycleDays / greatestCommonDivisor(gregorianCycleDays, interval);
8949
+ for (let attempts = 0; attempts < candidateCount2; attempts += 1) {
8950
+ const candidate = buildCandidate({
8951
+ date: candidateDate,
8952
+ recurrence: args.recurrence,
8953
+ timezone: args.timezone
8954
+ });
8955
+ if (candidate !== void 0 && candidate > args.afterMs) {
8956
+ return candidate;
8957
+ }
8958
+ candidateDate = addDays(candidateDate, interval);
8959
+ }
8960
+ return void 0;
8961
+ }
8962
+ if (args.recurrence.frequency === "weekly") {
8963
+ if (normalizeWeekdays(args.recurrence.weekdays).length === 0) {
8964
+ return void 0;
8965
+ }
8966
+ let candidateDate = searchFrom;
8967
+ const gregorianCycleDays = 146097;
8968
+ const cadenceDays = interval * 7;
8969
+ const searchDays = gregorianCycleDays * cadenceDays / greatestCommonDivisor(gregorianCycleDays, cadenceDays);
8970
+ for (let attempts = 0; attempts < searchDays; attempts += 1) {
8971
+ if (weeklyRecurrenceMatchesDate(candidateDate, start, args.recurrence)) {
8972
+ const candidate = buildCandidate({
8973
+ date: candidateDate,
8974
+ recurrence: args.recurrence,
8975
+ timezone: args.timezone
8976
+ });
8977
+ if (candidate !== void 0 && candidate > args.afterMs) {
8978
+ return candidate;
8979
+ }
8980
+ }
8981
+ candidateDate = addDays(candidateDate, 1);
8982
+ }
8983
+ return void 0;
8984
+ }
8985
+ if (args.recurrence.frequency === "monthly") {
8986
+ const startMonth = start.year * 12 + start.month - 1;
8987
+ const searchMonth = searchFrom.year * 12 + searchFrom.month - 1;
8988
+ let candidateMonth = startMonth + Math.ceil((searchMonth - startMonth) / interval) * interval;
8989
+ const gregorianCycleMonths = 4800;
8990
+ const candidateCount2 = gregorianCycleMonths / greatestCommonDivisor(gregorianCycleMonths, interval);
8991
+ for (let attempts = 0; attempts < candidateCount2; attempts += 1) {
8992
+ const candidateDate = {
8993
+ year: Math.floor(candidateMonth / 12),
8994
+ month: candidateMonth % 12 + 1,
8995
+ day: args.recurrence.dayOfMonth ?? 0
8996
+ };
8997
+ if (compareDate(candidateDate, searchFrom) >= 0) {
8998
+ const candidate = buildCandidate({
8999
+ date: candidateDate,
9000
+ recurrence: args.recurrence,
9001
+ timezone: args.timezone
9002
+ });
9003
+ if (candidate !== void 0 && candidate > args.afterMs) {
9004
+ return candidate;
9005
+ }
9006
+ }
9007
+ candidateMonth += interval;
9008
+ }
9009
+ return void 0;
9010
+ }
9011
+ let candidateYear = start.year + Math.ceil((searchFrom.year - start.year) / interval) * interval;
9012
+ const candidateCount = 400 / greatestCommonDivisor(400, interval);
9013
+ for (let attempts = 0; attempts < candidateCount; attempts += 1) {
9014
+ const candidateDate = {
9015
+ year: candidateYear,
9016
+ month: args.recurrence.month ?? 0,
9017
+ day: args.recurrence.dayOfMonth ?? 0
9018
+ };
9019
+ if (compareDate(candidateDate, searchFrom) >= 0) {
9020
+ const candidate = buildCandidate({
9021
+ date: candidateDate,
9022
+ recurrence: args.recurrence,
9023
+ timezone: args.timezone
9024
+ });
9025
+ if (candidate !== void 0 && candidate > args.afterMs) {
9026
+ return candidate;
9027
+ }
9028
+ }
9029
+ candidateYear += interval;
9030
+ }
9031
+ return void 0;
9032
+ }
9033
+ function getFirstRunAtMs(args) {
9034
+ return findNextRunAtMs({
9035
+ ...args,
9036
+ searchFrom: getLocalDate(args.afterMs, args.timezone)
9037
+ });
9038
+ }
9039
+ function compareDate(left, right) {
9040
+ return Date.UTC(left.year, left.month - 1, left.day) - Date.UTC(right.year, right.month - 1, right.day);
9041
+ }
9042
+ function addDays(date, days) {
9043
+ const next = new Date(Date.UTC(date.year, date.month - 1, date.day + days));
9044
+ return {
9045
+ year: next.getUTCFullYear(),
9046
+ month: next.getUTCMonth() + 1,
9047
+ day: next.getUTCDate()
9048
+ };
9049
+ }
9050
+ function daysInMonth(year, month) {
9051
+ return new Date(Date.UTC(year, month, 0)).getUTCDate();
9052
+ }
9053
+ function parseLocalDate(value) {
9054
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
9055
+ if (!match) {
9056
+ return void 0;
9057
+ }
9058
+ const year = Number(match[1]);
9059
+ const month = Number(match[2]);
9060
+ const day = Number(match[3]);
9061
+ if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day) || month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) {
9062
+ return void 0;
9063
+ }
9064
+ return { year, month, day };
9065
+ }
9066
+ function getLocalDate(timestampMs, timezone) {
9067
+ const parts = getZonedDateTimeParts(timestampMs, timezone);
9068
+ return { year: parts.year, month: parts.month, day: parts.day };
9069
+ }
9070
+ function normalizeWeekdays(values) {
9071
+ return [
9072
+ ...new Set((values ?? []).filter((value) => value >= 0 && value <= 6))
9073
+ ].sort((a, b) => a - b);
9074
+ }
9075
+ function buildCandidate(args) {
9076
+ return localDateTimeToTimestampMs({
9077
+ date: args.date,
9078
+ time: args.recurrence.time,
9079
+ timezone: args.timezone
9080
+ });
9081
+ }
9082
+ function getNextRunAtMs(task, scheduledForMs, afterMs = scheduledForMs) {
9083
+ if (task.schedule.kind !== "recurring") {
9084
+ return void 0;
9085
+ }
9086
+ const recurrence = task.schedule.recurrence;
9087
+ if (!recurrence || !Number.isFinite(recurrence.interval) || recurrence.interval <= 0) {
9088
+ return void 0;
9089
+ }
9090
+ const timezone = task.schedule.timezone;
9091
+ const afterDate = getLocalDate(afterMs, timezone);
9092
+ const nextScheduledDate = addDays(getLocalDate(scheduledForMs, timezone), 1);
9093
+ return findNextRunAtMs({
9094
+ afterMs,
9095
+ recurrence,
9096
+ searchFrom: compareDate(afterDate, nextScheduledDate) < 0 ? nextScheduledDate : afterDate,
9097
+ timezone
9098
+ });
9099
+ }
9100
+
9101
+ // src/chat/scheduled-tasks/schedule-intent.ts
8176
9102
  var localDateSchema = z26.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Use a local date in YYYY-MM-DD format.");
8177
9103
  var localTimeSchema = z26.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "Use local time in HH:MM format.");
8178
9104
  var timezoneSchema = z26.string().min(1).max(80).describe(
@@ -8420,6 +9346,41 @@ function compileScheduleIntent(args) {
8420
9346
  };
8421
9347
  }
8422
9348
 
9349
+ // src/chat/scheduled-tasks/telemetry.ts
9350
+ function scheduledTaskAttributes(task, extras = {}) {
9351
+ return {
9352
+ "app.task.id": task.id,
9353
+ "app.task.type": "scheduled",
9354
+ "app.task.status": task.status,
9355
+ "app.task.schedule.kind": task.schedule.kind,
9356
+ "app.task.schedule.timezone": task.schedule.timezone,
9357
+ "app.task.credential_mode": task.credentialMode,
9358
+ "app.task.destination.channel_id": task.destination.channelId,
9359
+ "app.task.destination.team_id": task.destination.teamId,
9360
+ "app.task.destination.visibility": task.conversationAccess.visibility,
9361
+ "app.task.destination.audience": task.conversationAccess.audience,
9362
+ "messaging.destination.name": task.destination.channelId,
9363
+ ...typeof task.nextRunAtMs === "number" ? { "app.task.next_run_at": new Date(task.nextRunAtMs).toISOString() } : {},
9364
+ ...extras
9365
+ };
9366
+ }
9367
+ function scheduledTaskRunAttributes(task, run, extras = {}) {
9368
+ return scheduledTaskAttributes(task, {
9369
+ "app.task.run.id": run.id,
9370
+ "app.task.run.status": run.status,
9371
+ "app.task.run.scheduled_for": new Date(run.scheduledForMs).toISOString(),
9372
+ ...run.dispatchId ? { "app.dispatch.id": run.dispatchId } : {},
9373
+ ...run.resultMessageTs ? { "app.task.result_message_ts": run.resultMessageTs } : {},
9374
+ ...extras
9375
+ });
9376
+ }
9377
+ function logScheduledTaskRunSkipped(task, run, extras = {}) {
9378
+ logInfo(
9379
+ "scheduled_task.run.skipped",
9380
+ scheduledTaskRunAttributes(task, run, extras)
9381
+ );
9382
+ }
9383
+
8423
9384
  // src/chat/scheduled-tasks/tool-support.ts
8424
9385
  import { createHash as createHash2 } from "crypto";
8425
9386
  import {
@@ -8561,7 +9522,7 @@ function sameDestination(task, destination) {
8561
9522
  }
8562
9523
  async function getWritableTask(args) {
8563
9524
  const destination = requireActiveConversation(args.context);
8564
- const task = await schedulerStore(args.context).getTask(args.taskId);
9525
+ const task = await readScheduledTask(getDb(), args.taskId);
8565
9526
  if (!task || task.status === "deleted") {
8566
9527
  throwToolInputError(
8567
9528
  "Scheduled task was not found in the active Slack conversation."
@@ -8632,9 +9593,6 @@ function buildTaskId(args) {
8632
9593
  ).digest("hex").slice(0, 32);
8633
9594
  return `${TASK_ID_PREFIX}_${digest}`;
8634
9595
  }
8635
- function schedulerStore(context) {
8636
- return context.store;
8637
- }
8638
9596
  function normalizeStatus(value) {
8639
9597
  if (value === "active" || value === "blocked") {
8640
9598
  return value;
@@ -8684,13 +9642,13 @@ function createSlackScheduleCreateTaskTool(context) {
8684
9642
  execute: async (input, options) => {
8685
9643
  const destination = requireActiveConversation(context);
8686
9644
  const actor = requireActor(context, destination);
8687
- const store = schedulerStore(context);
8688
9645
  const id = buildTaskId({
8689
9646
  actor,
8690
9647
  destination,
8691
9648
  toolCallId: options.toolCallId
8692
9649
  });
8693
- const existing = await store.getTask(id);
9650
+ const db = getDb();
9651
+ const existing = await readScheduledTask(db, id);
8694
9652
  if (existing) {
8695
9653
  if (!sameDestination(existing, destination) || existing.createdBy.slackUserId !== actor.slackUserId) {
8696
9654
  throwToolInputError("Scheduled task operation identity is invalid.");
@@ -8747,7 +9705,7 @@ function createSlackScheduleCreateTaskTool(context) {
8747
9705
  },
8748
9706
  ...title ? { title } : {}
8749
9707
  };
8750
- const committed = await store.createTask(task);
9708
+ const committed = await createScheduledTask(db, task);
8751
9709
  if (!sameDestination(committed, destination) || committed.createdBy.slackUserId !== actor.slackUserId) {
8752
9710
  throwToolInputError("Scheduled task operation identity is invalid.");
8753
9711
  }
@@ -8791,7 +9749,7 @@ function createSlackScheduleDeleteTaskTool(context) {
8791
9749
  nextRunAtMs: void 0,
8792
9750
  runNowAtMs: void 0
8793
9751
  };
8794
- await schedulerStore(context).saveTask(next);
9752
+ await saveScheduledTask(getDb(), next);
8795
9753
  return scheduleTaskToolResult(
8796
9754
  "slackScheduleDeleteTask",
8797
9755
  compactTask(next)
@@ -8873,7 +9831,7 @@ function createSlackScheduleListTasksTool(context) {
8873
9831
  }
8874
9832
  const query = input.query?.trim() || void 0;
8875
9833
  const crossConversation = Boolean(query) || channelId !== void 0 && channelId !== destination.channelId;
8876
- const matching = (await schedulerStore(context).listTasksForTeam(destination.teamId)).filter((task) => {
9834
+ const matching = (await listScheduledTasksForTeam(getDb(), destination.teamId)).filter((task) => {
8877
9835
  if (task.destination.platform !== "slack") return false;
8878
9836
  if (task.destination.teamId !== destination.teamId) return false;
8879
9837
  if (task.status === "completed") return false;
@@ -8933,7 +9891,7 @@ function createSlackScheduleRunTaskNowTool(context) {
8933
9891
  updatedAtMs: nowMs,
8934
9892
  runNowAtMs: nowMs
8935
9893
  };
8936
- await schedulerStore(context).saveTask(next);
9894
+ await saveScheduledTask(getDb(), next);
8937
9895
  return scheduleTaskToolResult(
8938
9896
  "slackScheduleRunTaskNow",
8939
9897
  compactTask(next)
@@ -8944,6 +9902,7 @@ function createSlackScheduleRunTaskNowTool(context) {
8944
9902
 
8945
9903
  // src/chat/scheduled-tasks/tools/update.ts
8946
9904
  import { z as z33 } from "zod";
9905
+ import { and, eq, inArray } from "drizzle-orm";
8947
9906
  function createSlackScheduleUpdateTaskTool(context) {
8948
9907
  return zodTool({
8949
9908
  approvalMode: "review",
@@ -8973,8 +9932,8 @@ function createSlackScheduleUpdateTaskTool(context) {
8973
9932
  execute: async (input) => {
8974
9933
  const activeDestination = requireActiveConversation(context);
8975
9934
  const actor = requireActor(context, activeDestination);
8976
- const store = schedulerStore(context);
8977
- const lookup = await store.getTask(input.task_id);
9935
+ const db = getDb();
9936
+ const lookup = await readScheduledTask(db, input.task_id);
8978
9937
  if (!lookup || lookup.status === "deleted") {
8979
9938
  throwToolInputError("Scheduled task was not found.");
8980
9939
  }
@@ -9011,7 +9970,12 @@ function createSlackScheduleUpdateTaskTool(context) {
9011
9970
  }
9012
9971
  const changingDestination = moveHere && !alreadyHere;
9013
9972
  if (changingDestination) {
9014
- const incompleteRuns = await store.listIncompleteRunsForTasks([lookup]);
9973
+ const incompleteRuns = await db.select({ id: juniorSchedulerRuns.id }).from(juniorSchedulerRuns).where(
9974
+ and(
9975
+ eq(juniorSchedulerRuns.taskId, lookup.id),
9976
+ inArray(juniorSchedulerRuns.status, ["pending", "running"])
9977
+ )
9978
+ ).limit(1);
9015
9979
  if (incompleteRuns.length > 0) {
9016
9980
  throwToolInputError(
9017
9981
  "Scheduled task cannot be moved while an occurrence is already running. Try again after the current run finishes."
@@ -9077,15 +10041,8 @@ function createSlackScheduleUpdateTaskTool(context) {
9077
10041
  compactTask(lookup)
9078
10042
  );
9079
10043
  }
9080
- await store.saveTask(next);
9081
- const committed = await store.getTask(input.task_id);
9082
- if (!committed) {
9083
- throwToolInputError("Scheduled task update did not complete.");
9084
- }
10044
+ const committed = await saveScheduledTask(db, next);
9085
10045
  if (changingDestination) {
9086
- if (!sameDestination(committed, activeDestination)) {
9087
- throwToolInputError("Scheduled task move did not complete.");
9088
- }
9089
10046
  logInfo(
9090
10047
  "scheduled_task.move.completed",
9091
10048
  scheduledTaskAttributes(committed)
@@ -9107,7 +10064,6 @@ function scheduledTaskToolContext(context) {
9107
10064
  return {
9108
10065
  actor: context.actor,
9109
10066
  source: context.source,
9110
- store: createSchedulerSqlStore(getDb()),
9111
10067
  users: { resolveActor: context.resolveActorIdentity },
9112
10068
  ...context.userText ? { userText: context.userText } : {}
9113
10069
  };
@@ -11730,7 +12686,7 @@ function createSlackThreadReadTool(context) {
11730
12686
  }
11731
12687
 
11732
12688
  // src/chat/tools/user-lookup.ts
11733
- import { and, eq, or, sql } from "drizzle-orm";
12689
+ import { and as and2, eq as eq2, or, sql } from "drizzle-orm";
11734
12690
  import { alias } from "drizzle-orm/pg-core";
11735
12691
  import { z as z53 } from "zod";
11736
12692
  var linkedSlackIdentity = alias(juniorIdentities, "user_lookup_slack_identity");
@@ -11796,12 +12752,12 @@ async function storedSlackUserByEmail(teamId, email) {
11796
12752
  const emailNormalized = normalizeIdentityEmail(email);
11797
12753
  if (!emailNormalized) return void 0;
11798
12754
  const identities = await getSqlExecutor().db().select().from(juniorIdentities).where(
11799
- and(
11800
- eq(juniorIdentities.provider, "slack"),
11801
- eq(juniorIdentities.providerTenantId, teamId),
11802
- eq(juniorIdentities.kind, "user"),
11803
- eq(juniorIdentities.emailVerified, true),
11804
- eq(juniorIdentities.emailNormalized, emailNormalized)
12755
+ and2(
12756
+ eq2(juniorIdentities.provider, "slack"),
12757
+ eq2(juniorIdentities.providerTenantId, teamId),
12758
+ eq2(juniorIdentities.kind, "user"),
12759
+ eq2(juniorIdentities.emailVerified, true),
12760
+ eq2(juniorIdentities.emailNormalized, emailNormalized)
11805
12761
  )
11806
12762
  ).limit(2);
11807
12763
  if (identities.length > 1) {
@@ -11853,18 +12809,18 @@ async function resolveSlack(teamId, query) {
11853
12809
  async function resolveStoredProvider(teamId, provider, query) {
11854
12810
  const rows = await getSqlExecutor().db().select({ slack: linkedSlackIdentity }).from(juniorIdentities).innerJoin(
11855
12811
  linkedSlackIdentity,
11856
- and(
11857
- eq(linkedSlackIdentity.userId, juniorIdentities.userId),
11858
- eq(linkedSlackIdentity.kind, "user"),
11859
- eq(linkedSlackIdentity.provider, "slack"),
11860
- eq(linkedSlackIdentity.providerTenantId, teamId)
12812
+ and2(
12813
+ eq2(linkedSlackIdentity.userId, juniorIdentities.userId),
12814
+ eq2(linkedSlackIdentity.kind, "user"),
12815
+ eq2(linkedSlackIdentity.provider, "slack"),
12816
+ eq2(linkedSlackIdentity.providerTenantId, teamId)
11861
12817
  )
11862
12818
  ).where(
11863
- and(
11864
- eq(juniorIdentities.kind, "user"),
11865
- eq(juniorIdentities.provider, provider),
12819
+ and2(
12820
+ eq2(juniorIdentities.kind, "user"),
12821
+ eq2(juniorIdentities.provider, provider),
11866
12822
  or(
11867
- eq(juniorIdentities.providerSubjectId, query),
12823
+ eq2(juniorIdentities.providerSubjectId, query),
11868
12824
  sql`lower(${juniorIdentities.handle}) = lower(${query})`
11869
12825
  )
11870
12826
  )
@@ -11997,8 +12953,8 @@ function getTimeZoneOffsetMinutes(timestampMs, timezone) {
11997
12953
  import { z as z55 } from "zod";
11998
12954
 
11999
12955
  // src/chat/artifacts/store.ts
12000
- import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
12001
- import { and as and2, eq as eq2, isNull } from "drizzle-orm";
12956
+ import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
12957
+ import { and as and3, eq as eq3, isNull } from "drizzle-orm";
12002
12958
  var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
12003
12959
  var IMAGE_TYPES = {
12004
12960
  gif: "image/gif",
@@ -12085,12 +13041,12 @@ async function publishImage(args) {
12085
13041
  const [existing] = await args.db.db().select({
12086
13042
  id: juniorArtifacts.id
12087
13043
  }).from(juniorArtifacts).where(
12088
- and2(
12089
- eq2(juniorArtifacts.conversationId, args.conversationId),
12090
- eq2(juniorArtifacts.sha256, sha256)
13044
+ and3(
13045
+ eq3(juniorArtifacts.conversationId, args.conversationId),
13046
+ eq3(juniorArtifacts.sha256, sha256)
12091
13047
  )
12092
13048
  );
12093
- const id = existing?.id ?? randomUUID3();
13049
+ const id = existing?.id ?? randomUUID5();
12094
13050
  const filename = `${id}.${imageType.extension}`;
12095
13051
  const storageKey = artifactKey(filename);
12096
13052
  await args.storage.put({
@@ -12148,9 +13104,9 @@ async function unpublishArtifact(args) {
12148
13104
  }
12149
13105
  const now = args.now ?? /* @__PURE__ */ new Date();
12150
13106
  const updated = await args.db.db().update(juniorArtifacts).set({ deleteRequestedAt: now }).where(
12151
- and2(
12152
- eq2(juniorArtifacts.id, parsed.id),
12153
- eq2(juniorArtifacts.conversationId, args.conversationId)
13107
+ and3(
13108
+ eq3(juniorArtifacts.id, parsed.id),
13109
+ eq3(juniorArtifacts.conversationId, args.conversationId)
12154
13110
  )
12155
13111
  ).returning({ id: juniorArtifacts.id });
12156
13112
  if (updated.length === 0) {
@@ -12167,9 +13123,9 @@ async function readPublicArtifact(args) {
12167
13123
  contentType: juniorArtifacts.contentType,
12168
13124
  storageKey: juniorArtifacts.storageKey
12169
13125
  }).from(juniorArtifacts).where(
12170
- and2(
12171
- eq2(juniorArtifacts.id, parsed.id),
12172
- eq2(juniorArtifacts.public, true),
13126
+ and3(
13127
+ eq3(juniorArtifacts.id, parsed.id),
13128
+ eq3(juniorArtifacts.public, true),
12173
13129
  isNull(juniorArtifacts.deleteRequestedAt)
12174
13130
  )
12175
13131
  );
@@ -13443,7 +14399,18 @@ function createWorkspaceTools(context) {
13443
14399
  const workspace = await getWorkspaceByName(getDb(), name);
13444
14400
  if (!workspace)
13445
14401
  throw new ToolInputError(`Workspace not found: ${name}`);
13446
- await context.workspaces.switch(workspace, options.signal);
14402
+ try {
14403
+ await context.workspaces.switch(workspace, options.signal);
14404
+ } catch (error) {
14405
+ if (isWorkspaceSnapshotWaitingError(error)) {
14406
+ return {
14407
+ workspace: view(workspace),
14408
+ waiting: "workspace_snapshot",
14409
+ timed_out: true
14410
+ };
14411
+ }
14412
+ throw error;
14413
+ }
13447
14414
  await tryRecordWorkspaceSwitchStat(workspace);
13448
14415
  return { workspace: view(workspace) };
13449
14416
  }
@@ -14963,7 +15930,7 @@ function createMcpAuthOrchestration(input) {
14963
15930
  }
14964
15931
 
14965
15932
  // src/chat/egress/plugin.ts
14966
- import { randomUUID as randomUUID4 } from "crypto";
15933
+ import { randomUUID as randomUUID6 } from "crypto";
14967
15934
 
14968
15935
  // src/chat/sandbox/egress/credentials.ts
14969
15936
  var HTTP_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
@@ -15717,8 +16684,8 @@ async function executeCredentialedEgressRequest(input) {
15717
16684
  function credentialContextForPluginEgress(credentialContext) {
15718
16685
  return {
15719
16686
  credentials: credentialContext,
15720
- contextId: randomUUID4(),
15721
- egressId: `plugin-egress:${randomUUID4()}`,
16687
+ contextId: randomUUID6(),
16688
+ egressId: `plugin-egress:${randomUUID6()}`,
15722
16689
  expiresAtMs: Date.now() + 30 * 60 * 1e3
15723
16690
  };
15724
16691
  }
@@ -16151,6 +17118,7 @@ async function wireAgentTools(args) {
16151
17118
  actor: args.currentActor,
16152
17119
  locationConfiguration: args.run.environment?.locationConfiguration,
16153
17120
  configurationValues: args.configurationValues,
17121
+ shouldYield: args.durability.shouldYield,
16154
17122
  getActiveSkill: () => args.skillSandbox.getActiveSkill(),
16155
17123
  prepareSandbox: pluginHooks.prepareSandbox,
16156
17124
  prepareWorkspace: async (sandbox, recipe, signal) => await pluginHooks.prepareWorkspace(sandbox, recipe.repos, signal),
@@ -17470,7 +18438,10 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
17470
18438
  history: run.history
17471
18439
  });
17472
18440
  const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [...input.piMessages ?? []];
17473
- const handoffEnabled = !isAgentRunFeatureDisabled(policy.disabledFeatures, "handoff");
18441
+ const handoffEnabled = !isAgentRunFeatureDisabled(
18442
+ policy.disabledFeatures,
18443
+ "handoff"
18444
+ );
17474
18445
  const storedTurnRoute = await loadTurnRoute({ conversationId, turnId });
17475
18446
  if (storedTurnRoute) {
17476
18447
  const resumedAfterHandoff = handoffEnabled && activeModelProfile !== STANDARD_MODEL_PROFILE && activeModelProfile !== storedTurnRoute.modelProfile;
@@ -17943,10 +18914,14 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
17943
18914
  const containsHandoff = toolCalls2.some(
17944
18915
  (call) => call.name === HANDOFF_TOOL_NAME
17945
18916
  );
17946
- if (containsHandoff && toolCalls2.length !== 1) {
18917
+ const containsWorkspaceSwitch = toolCalls2.some(
18918
+ (call) => call.name === "switchWorkspace"
18919
+ );
18920
+ const exclusiveTool = containsHandoff ? HANDOFF_TOOL_NAME : containsWorkspaceSwitch ? "switchWorkspace" : void 0;
18921
+ if (exclusiveTool && toolCalls2.length !== 1) {
17947
18922
  return {
17948
18923
  block: true,
17949
- reason: "handoff must be the only tool call in its assistant message; reissue it alone"
18924
+ reason: `${exclusiveTool} must be the only tool call in its assistant message; reissue it alone`
17950
18925
  };
17951
18926
  }
17952
18927
  return void 0;
@@ -17962,15 +18937,46 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
17962
18937
  },
17963
18938
  prepareNextTurnWithContext: async (nextTurn, hookSignal) => {
17964
18939
  try {
18940
+ let continuedSnapshotWait = false;
18941
+ for (; ; ) {
18942
+ const snapshotWait = await continueWorkspaceSnapshotWait({
18943
+ messages: currentAgentMessages(),
18944
+ tools: toolsForActiveProfile(),
18945
+ signal: hookSignal
18946
+ });
18947
+ if (snapshotWait.kind === "none") break;
18948
+ if (snapshotWait.kind === "failed") {
18949
+ agent.state.messages = snapshotWait.messages;
18950
+ await runResume.persistSafeBoundary(snapshotWait.messages);
18951
+ throw snapshotWait.error;
18952
+ }
18953
+ continuedSnapshotWait = true;
18954
+ agent.state.messages = snapshotWait.messages;
18955
+ await runResume.persistSafeBoundary(snapshotWait.messages);
18956
+ if (snapshotWait.kind === "ready") break;
18957
+ const yieldError2 = runResume.prepareYieldIfDue(
18958
+ snapshotWait.messages
18959
+ );
18960
+ if (yieldError2) throw yieldError2;
18961
+ await sleep(5e3, hookSignal);
18962
+ }
17965
18963
  const handoffUpdate = applyPendingHandoff();
18964
+ const snapshotWaitUpdate = continuedSnapshotWait ? {
18965
+ context: {
18966
+ ...nextTurn.context,
18967
+ messages: currentAgentMessages(),
18968
+ tools: agent.state.tools
18969
+ }
18970
+ } : void 0;
17966
18971
  const pendingMessages = await drainSteeringMessages();
17967
18972
  const capacityUpdate = await applyActiveContextCompaction(
17968
- handoffUpdate ? currentAgentMessages() : nextTurn.context.messages,
18973
+ handoffUpdate || continuedSnapshotWait ? currentAgentMessages() : nextTurn.context.messages,
17969
18974
  hookSignal,
17970
18975
  pendingMessages,
17971
18976
  true
17972
18977
  );
17973
- const combinedUpdate = capacityUpdate && handoffUpdate ? { ...handoffUpdate, ...capacityUpdate } : capacityUpdate ?? handoffUpdate;
18978
+ const handoffOrWaitUpdate = handoffUpdate ? { ...snapshotWaitUpdate, ...handoffUpdate } : snapshotWaitUpdate;
18979
+ const combinedUpdate = capacityUpdate ? { ...handoffOrWaitUpdate, ...capacityUpdate } : handoffOrWaitUpdate;
17974
18980
  const repositoryInstructionsUpdate = await repositoryInstructionsContext.applyUpdate(
17975
18981
  combinedUpdate,
17976
18982
  nextTurn.context
@@ -18450,7 +19456,9 @@ export {
18450
19456
  normalizeSandboxEgressTracePropagationDomains,
18451
19457
  shouldPropagateSandboxEgressTrace,
18452
19458
  resolveSandboxEgressProviderForHost,
18453
- sanitizeScheduledTaskPrincipal,
19459
+ getNextRunAtMs,
19460
+ scheduledTaskRunAttributes,
19461
+ logScheduledTaskRunSkipped,
18454
19462
  listThreadReplies,
18455
19463
  renderBlockText,
18456
19464
  renderAttachmentText,