@sentry/junior 0.177.0 → 0.178.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 (91) hide show
  1. package/dist/{agent-hooks-IYHCN7CO.js → agent-hooks-PBT4NNBV.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 +245 -166
  13. package/dist/app.js +35 -35
  14. package/dist/{catalog-runtime-2TD4CXSB.js → catalog-runtime-M7QGKPFZ.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/services/workspace-snapshot-wait.d.ts +27 -0
  30. package/dist/chat/workspaces/types.d.ts +1 -0
  31. package/dist/{chunk-3FWXQZJJ.js → chunk-22T3HB44.js} +1 -1
  32. package/dist/{chunk-DLTP6TUJ.js → chunk-3ZZEPLLB.js} +2 -2
  33. package/dist/{chunk-7ZFESKJ2.js → chunk-4K6VNP3S.js} +9 -9
  34. package/dist/{chunk-N4YEVOEC.js → chunk-534BO42N.js} +45 -276
  35. package/dist/{chunk-6NZRKJWU.js → chunk-6OYVH66J.js} +1 -1
  36. package/dist/{chunk-J2EWL6GJ.js → chunk-ABLODWPO.js} +1 -1
  37. package/dist/{chunk-ORMJY4RG.js → chunk-AGJ4OQ2V.js} +1 -1
  38. package/dist/{chunk-Q362TECJ.js → chunk-AQHSXA3J.js} +331 -2
  39. package/dist/{chunk-4SVAFAI2.js → chunk-AVRWMQ4U.js} +1 -1
  40. package/dist/{chunk-I7PKGMKI.js → chunk-BFLMRTPD.js} +6 -6
  41. package/dist/{chunk-ZCYABD7N.js → chunk-BHHWCNXI.js} +5 -5
  42. package/dist/{chunk-LFZODSAY.js → chunk-BUGO42R6.js} +4 -4
  43. package/dist/{chunk-7B6NW4P7.js → chunk-D3UJTX4U.js} +1 -1
  44. package/dist/{chunk-2IMQLZHS.js → chunk-FQ6BMP5P.js} +767 -81
  45. package/dist/{chunk-4TWDRU64.js → chunk-G3CQEUHI.js} +5 -5
  46. package/dist/{chunk-VGDOEKO2.js → chunk-H6ZXBFGX.js} +1 -1
  47. package/dist/{chunk-SNPQGTHJ.js → chunk-IQRPDKPJ.js} +1 -1
  48. package/dist/{chunk-CAWBLVBY.js → chunk-JVXZ3WDH.js} +6 -6
  49. package/dist/{chunk-7PZDAXIK.js → chunk-KEQAA42D.js} +1 -1
  50. package/dist/{chunk-UB5YB2ED.js → chunk-LAOP4EPL.js} +6 -6
  51. package/dist/{chunk-WYO2HXY5.js → chunk-OGRD5PFM.js} +1 -1
  52. package/dist/{chunk-AFOFVEXP.js → chunk-PRDKO3QB.js} +124 -13
  53. package/dist/{chunk-QLLOX7JY.js → chunk-QCCLDJHB.js} +8 -8
  54. package/dist/{chunk-FVVYHML7.js → chunk-RE4XWXBV.js} +6 -6
  55. package/dist/{chunk-4TRHPYTL.js → chunk-RGV6KHDM.js} +4 -4
  56. package/dist/{chunk-DGGIKWYQ.js → chunk-RGXOLPWO.js} +1 -1
  57. package/dist/{chunk-ELEAIRTG.js → chunk-TKB25A3L.js} +2 -2
  58. package/dist/{chunk-6KGMMVHF.js → chunk-U3UDPGZG.js} +2 -2
  59. package/dist/{chunk-7CVYXUOJ.js → chunk-UGWBJAGN.js} +1 -1
  60. package/dist/{chunk-LVL5NG5G.js → chunk-UMG7M32G.js} +19 -19
  61. package/dist/{chunk-LPMLAJYR.js → chunk-XCNS3CXE.js} +11 -17
  62. package/dist/{chunk-PA7QDS7P.js → chunk-XWUJWHFD.js} +204 -100
  63. package/dist/{chunk-WCQUKX74.js → chunk-ZCHHVTMF.js} +14 -5
  64. package/dist/cli/chat.js +31 -30
  65. package/dist/cli/check.js +6 -6
  66. package/dist/cli/plugins.js +13 -13
  67. package/dist/cli/snapshot-create.js +10 -10
  68. package/dist/cli/upgrade.js +7 -7
  69. package/dist/db/schema/conversation-participants.d.ts +17 -0
  70. package/dist/db/schema/conversations.d.ts +629 -1
  71. package/dist/db/schema/snapshots.d.ts +1 -1
  72. package/dist/db/schema.d.ts +18 -1
  73. package/dist/{db-A3CB76US.js → db-QVYOWATH.js} +7 -7
  74. package/dist/instrumentation.js +4 -4
  75. package/dist/nitro.js +2 -2
  76. package/dist/{oauth-callback-server-64QMI3IB.js → oauth-callback-server-PLPAFXO6.js} +23 -23
  77. package/dist/{runner-EIAE2BGD.js → runner-AGNEIOZS.js} +15 -15
  78. package/dist/{skills-ZADWFKDG.js → skills-U5KQ66EM.js} +6 -6
  79. package/dist/spawn-ZRYYZP2B.js +37 -0
  80. package/dist/{validation-4I6AF4CV.js → validation-QE4U45KS.js} +6 -6
  81. package/dist/version.js +1 -1
  82. package/dist/{work-BS6BZ66U.js → work-ATAXAHJJ.js} +18 -18
  83. package/dist/worker-LIL5AOER.js +24 -0
  84. package/migrations/0033_workspace_snapshot_active_build.sql +1 -0
  85. package/migrations/0034_strong_randall.sql +31 -0
  86. package/migrations/meta/0033_snapshot.json +2914 -0
  87. package/migrations/meta/0034_snapshot.json +3038 -0
  88. package/migrations/meta/_journal.json +15 -1
  89. package/package.json +5 -5
  90. package/dist/spawn-DVUOUFFC.js +0 -37
  91. 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-BHHWCNXI.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-QCCLDJHB.js";
36
+ import {
37
+ getTurnRequestDeadline
38
+ } from "./chunk-6OYVH66J.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-JVXZ3WDH.js";
52
55
  import {
53
56
  parseSandboxEgressAuthRequiredSignal,
54
57
  parseSandboxEgressPermissionDeniedSignal,
@@ -87,10 +90,10 @@ import {
87
90
  scheduledTaskAttributes,
88
91
  storeAttachments,
89
92
  writableEventTask
90
- } from "./chunk-4TRHPYTL.js";
93
+ } from "./chunk-RGV6KHDM.js";
91
94
  import {
92
95
  parseSlackThreadId
93
- } from "./chunk-3FWXQZJJ.js";
96
+ } from "./chunk-22T3HB44.js";
94
97
  import {
95
98
  AGENTS_REMOVAL_NOTICE,
96
99
  AGENTS_REPLACEMENT_NOTICE,
@@ -160,23 +163,18 @@ import {
160
163
  stripRuntimeTurnContext,
161
164
  trimTrailingAssistantMessages,
162
165
  truncateText
163
- } from "./chunk-I7PKGMKI.js";
166
+ } from "./chunk-BFLMRTPD.js";
164
167
  import {
165
168
  bindSandboxFileSystem,
166
169
  createSandboxSession,
170
+ dependencies,
167
171
  getSandboxResources,
168
- getVercelSandboxCredentials,
169
- isAlreadyExistsError,
170
172
  isMissingError,
171
- isSandboxCommandStreamInterruptedError,
172
- isSandboxMissingError,
173
- isSandboxUnavailableError,
174
- isSnapshottingError,
173
+ postinstall,
175
174
  resolve,
176
- stopSession,
177
- throwSandboxOperationError,
178
- wrapSandboxSetupError
179
- } from "./chunk-N4YEVOEC.js";
175
+ setSnapshotSpanAttributes,
176
+ stopSession
177
+ } from "./chunk-534BO42N.js";
180
178
  import {
181
179
  privateTraceResultAttributes
182
180
  } from "./chunk-UIE3R5XU.js";
@@ -196,6 +194,7 @@ import {
196
194
  createResourceEventSubscription,
197
195
  createWorkspace,
198
196
  deleteWorkspace,
197
+ deleteWorkspaceSnapshotBuilders,
199
198
  downloadPrivateSlackFile,
200
199
  getDashboardTaskLink,
201
200
  getFilePermalink,
@@ -207,19 +206,23 @@ import {
207
206
  getSlackToolContext,
208
207
  getWorkspace,
209
208
  getWorkspaceByName,
209
+ getWorkspaceSnapshotBuilder,
210
+ invalidateReadySnapshot,
210
211
  isConversationScopedChannel,
211
212
  listResourceEventSubscriptions,
212
213
  listWorkspaces,
214
+ loadSnapshotsForProfile,
213
215
  normalizeSlackConversationId,
214
216
  readActorIdentity,
215
- recordResolvedWorkspaceSnapshot,
216
217
  requireResourceWatchConversation,
217
218
  resolveChannelCapabilities,
219
+ setWorkspaceSnapshot,
220
+ setWorkspaceSnapshotBuild,
218
221
  tryWorkspaceRepoCheckoutPath,
219
222
  updateWorkspace,
220
223
  withSlackRetries,
221
224
  workspaceRepoCheckoutPath
222
- } from "./chunk-PA7QDS7P.js";
225
+ } from "./chunk-XWUJWHFD.js";
223
226
  import {
224
227
  KNOWN_CONVERSATION_EVENT_TYPES,
225
228
  contextProvenance,
@@ -231,29 +234,47 @@ import {
231
234
  instructionActors,
232
235
  instructionProvenanceFor,
233
236
  sameActorIdentity
234
- } from "./chunk-LPMLAJYR.js";
237
+ } from "./chunk-XCNS3CXE.js";
235
238
  import {
236
239
  juniorArtifacts,
237
240
  juniorIdentities
238
- } from "./chunk-WCQUKX74.js";
241
+ } from "./chunk-ZCHHVTMF.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-AQHSXA3J.js";
254
273
  import {
255
- getStateAdapter
256
- } from "./chunk-VGDOEKO2.js";
274
+ fenceLock,
275
+ getStateAdapter,
276
+ withLock
277
+ } from "./chunk-H6ZXBFGX.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-3ZZEPLLB.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-KEQAA42D.js";
300
321
  import {
301
322
  discoverSkills,
302
323
  findSkillByName,
303
324
  loadSkillsByName,
304
325
  parseSkillInvocation
305
- } from "./chunk-6KGMMVHF.js";
326
+ } from "./chunk-U3UDPGZG.js";
306
327
  import {
307
328
  pluginCatalogRuntime
308
- } from "./chunk-J2EWL6GJ.js";
329
+ } from "./chunk-ABLODWPO.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-D3UJTX4U.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-UGWBJAGN.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) => {
@@ -11997,7 +12633,7 @@ function getTimeZoneOffsetMinutes(timestampMs, timezone) {
11997
12633
  import { z as z55 } from "zod";
11998
12634
 
11999
12635
  // src/chat/artifacts/store.ts
12000
- import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
12636
+ import { createHash as createHash4, randomUUID as randomUUID5 } from "crypto";
12001
12637
  import { and as and2, eq as eq2, isNull } from "drizzle-orm";
12002
12638
  var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
12003
12639
  var IMAGE_TYPES = {
@@ -12090,7 +12726,7 @@ async function publishImage(args) {
12090
12726
  eq2(juniorArtifacts.sha256, sha256)
12091
12727
  )
12092
12728
  );
12093
- const id = existing?.id ?? randomUUID3();
12729
+ const id = existing?.id ?? randomUUID5();
12094
12730
  const filename = `${id}.${imageType.extension}`;
12095
12731
  const storageKey = artifactKey(filename);
12096
12732
  await args.storage.put({
@@ -13443,7 +14079,18 @@ function createWorkspaceTools(context) {
13443
14079
  const workspace = await getWorkspaceByName(getDb(), name);
13444
14080
  if (!workspace)
13445
14081
  throw new ToolInputError(`Workspace not found: ${name}`);
13446
- await context.workspaces.switch(workspace, options.signal);
14082
+ try {
14083
+ await context.workspaces.switch(workspace, options.signal);
14084
+ } catch (error) {
14085
+ if (isWorkspaceSnapshotWaitingError(error)) {
14086
+ return {
14087
+ workspace: view(workspace),
14088
+ waiting: "workspace_snapshot",
14089
+ timed_out: true
14090
+ };
14091
+ }
14092
+ throw error;
14093
+ }
13447
14094
  await tryRecordWorkspaceSwitchStat(workspace);
13448
14095
  return { workspace: view(workspace) };
13449
14096
  }
@@ -14963,7 +15610,7 @@ function createMcpAuthOrchestration(input) {
14963
15610
  }
14964
15611
 
14965
15612
  // src/chat/egress/plugin.ts
14966
- import { randomUUID as randomUUID4 } from "crypto";
15613
+ import { randomUUID as randomUUID6 } from "crypto";
14967
15614
 
14968
15615
  // src/chat/sandbox/egress/credentials.ts
14969
15616
  var HTTP_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
@@ -15717,8 +16364,8 @@ async function executeCredentialedEgressRequest(input) {
15717
16364
  function credentialContextForPluginEgress(credentialContext) {
15718
16365
  return {
15719
16366
  credentials: credentialContext,
15720
- contextId: randomUUID4(),
15721
- egressId: `plugin-egress:${randomUUID4()}`,
16367
+ contextId: randomUUID6(),
16368
+ egressId: `plugin-egress:${randomUUID6()}`,
15722
16369
  expiresAtMs: Date.now() + 30 * 60 * 1e3
15723
16370
  };
15724
16371
  }
@@ -16151,6 +16798,7 @@ async function wireAgentTools(args) {
16151
16798
  actor: args.currentActor,
16152
16799
  locationConfiguration: args.run.environment?.locationConfiguration,
16153
16800
  configurationValues: args.configurationValues,
16801
+ shouldYield: args.durability.shouldYield,
16154
16802
  getActiveSkill: () => args.skillSandbox.getActiveSkill(),
16155
16803
  prepareSandbox: pluginHooks.prepareSandbox,
16156
16804
  prepareWorkspace: async (sandbox, recipe, signal) => await pluginHooks.prepareWorkspace(sandbox, recipe.repos, signal),
@@ -17470,7 +18118,10 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
17470
18118
  history: run.history
17471
18119
  });
17472
18120
  const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [...input.piMessages ?? []];
17473
- const handoffEnabled = !isAgentRunFeatureDisabled(policy.disabledFeatures, "handoff");
18121
+ const handoffEnabled = !isAgentRunFeatureDisabled(
18122
+ policy.disabledFeatures,
18123
+ "handoff"
18124
+ );
17474
18125
  const storedTurnRoute = await loadTurnRoute({ conversationId, turnId });
17475
18126
  if (storedTurnRoute) {
17476
18127
  const resumedAfterHandoff = handoffEnabled && activeModelProfile !== STANDARD_MODEL_PROFILE && activeModelProfile !== storedTurnRoute.modelProfile;
@@ -17943,10 +18594,14 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
17943
18594
  const containsHandoff = toolCalls2.some(
17944
18595
  (call) => call.name === HANDOFF_TOOL_NAME
17945
18596
  );
17946
- if (containsHandoff && toolCalls2.length !== 1) {
18597
+ const containsWorkspaceSwitch = toolCalls2.some(
18598
+ (call) => call.name === "switchWorkspace"
18599
+ );
18600
+ const exclusiveTool = containsHandoff ? HANDOFF_TOOL_NAME : containsWorkspaceSwitch ? "switchWorkspace" : void 0;
18601
+ if (exclusiveTool && toolCalls2.length !== 1) {
17947
18602
  return {
17948
18603
  block: true,
17949
- reason: "handoff must be the only tool call in its assistant message; reissue it alone"
18604
+ reason: `${exclusiveTool} must be the only tool call in its assistant message; reissue it alone`
17950
18605
  };
17951
18606
  }
17952
18607
  return void 0;
@@ -17962,15 +18617,46 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
17962
18617
  },
17963
18618
  prepareNextTurnWithContext: async (nextTurn, hookSignal) => {
17964
18619
  try {
18620
+ let continuedSnapshotWait = false;
18621
+ for (; ; ) {
18622
+ const snapshotWait = await continueWorkspaceSnapshotWait({
18623
+ messages: currentAgentMessages(),
18624
+ tools: toolsForActiveProfile(),
18625
+ signal: hookSignal
18626
+ });
18627
+ if (snapshotWait.kind === "none") break;
18628
+ if (snapshotWait.kind === "failed") {
18629
+ agent.state.messages = snapshotWait.messages;
18630
+ await runResume.persistSafeBoundary(snapshotWait.messages);
18631
+ throw snapshotWait.error;
18632
+ }
18633
+ continuedSnapshotWait = true;
18634
+ agent.state.messages = snapshotWait.messages;
18635
+ await runResume.persistSafeBoundary(snapshotWait.messages);
18636
+ if (snapshotWait.kind === "ready") break;
18637
+ const yieldError2 = runResume.prepareYieldIfDue(
18638
+ snapshotWait.messages
18639
+ );
18640
+ if (yieldError2) throw yieldError2;
18641
+ await sleep(5e3, hookSignal);
18642
+ }
17965
18643
  const handoffUpdate = applyPendingHandoff();
18644
+ const snapshotWaitUpdate = continuedSnapshotWait ? {
18645
+ context: {
18646
+ ...nextTurn.context,
18647
+ messages: currentAgentMessages(),
18648
+ tools: agent.state.tools
18649
+ }
18650
+ } : void 0;
17966
18651
  const pendingMessages = await drainSteeringMessages();
17967
18652
  const capacityUpdate = await applyActiveContextCompaction(
17968
- handoffUpdate ? currentAgentMessages() : nextTurn.context.messages,
18653
+ handoffUpdate || continuedSnapshotWait ? currentAgentMessages() : nextTurn.context.messages,
17969
18654
  hookSignal,
17970
18655
  pendingMessages,
17971
18656
  true
17972
18657
  );
17973
- const combinedUpdate = capacityUpdate && handoffUpdate ? { ...handoffUpdate, ...capacityUpdate } : capacityUpdate ?? handoffUpdate;
18658
+ const handoffOrWaitUpdate = handoffUpdate ? { ...snapshotWaitUpdate, ...handoffUpdate } : snapshotWaitUpdate;
18659
+ const combinedUpdate = capacityUpdate ? { ...handoffOrWaitUpdate, ...capacityUpdate } : handoffOrWaitUpdate;
17974
18660
  const repositoryInstructionsUpdate = await repositoryInstructionsContext.applyUpdate(
17975
18661
  combinedUpdate,
17976
18662
  nextTurn.context