@sentry/junior 0.203.0 → 0.204.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 (174) hide show
  1. package/dist/agent-CMyTD4DD.js +17509 -0
  2. package/dist/agent-hooks-Bbcp9e33.js +4040 -0
  3. package/dist/agent-runner-DNb1A49M.js +678 -0
  4. package/dist/api/schema.d.ts +2728 -2639
  5. package/dist/api/schema.js +4 -169
  6. package/dist/api.d.ts +23 -17
  7. package/dist/api.js +3881 -5326
  8. package/dist/app-B1TOqBI8.d.ts +246 -0
  9. package/dist/app.d.ts +4 -520
  10. package/dist/app.js +9522 -11875
  11. package/dist/auth-pause-state-DosEalIG.js +168 -0
  12. package/dist/catalog-runtime-BReos-D4.js +7 -0
  13. package/dist/checkpoint-CNrPyRJN.js +2450 -0
  14. package/dist/cli/chat.d.ts +20 -0
  15. package/dist/cli/chat.js +229 -336
  16. package/dist/cli/check.d.ts +8 -0
  17. package/dist/cli/check.js +513 -806
  18. package/dist/cli/env.d.ts +13 -0
  19. package/dist/cli/env.js +63 -12
  20. package/dist/cli/init.d.ts +3 -0
  21. package/dist/cli/init.js +90 -159
  22. package/dist/cli/main.d.ts +6 -0
  23. package/dist/cli/main.js +43 -64
  24. package/dist/cli/plugins.d.ts +13 -0
  25. package/dist/cli/plugins.js +148 -227
  26. package/dist/cli/run.d.ts +16 -0
  27. package/dist/cli/run.js +52 -8
  28. package/dist/cli/snapshot-create.d.ts +4 -0
  29. package/dist/cli/snapshot-create.js +71 -125
  30. package/dist/cli/upgrade.d.ts +15 -0
  31. package/dist/cli/upgrade.js +232 -335
  32. package/dist/config-nGCW1OTG.js +941 -0
  33. package/dist/context-B45_WqVX.js +298 -0
  34. package/dist/conversation-privacy-B4voFRSm.js +182 -0
  35. package/dist/conversation-work-C6y3T5Hx.js +51 -0
  36. package/dist/credential-sync-kXr1hvO1.js +2660 -0
  37. package/dist/db-BeyXKJOI.d.ts +5 -0
  38. package/dist/destinations-BeBiJeEM.js +29 -0
  39. package/dist/execution-stats-DWfSihcH.js +373 -0
  40. package/dist/executor-DcMSlI5c.js +721 -0
  41. package/dist/experimental-CIXf7Uye.js +46 -0
  42. package/dist/history-ClE4toxe.d.ts +32 -0
  43. package/dist/history-CqTuSAdI.js +441 -0
  44. package/dist/instrumentation.d.ts +3 -3
  45. package/dist/instrumentation.js +130 -176
  46. package/dist/job-queue-Drn_LXlM.js +63 -0
  47. package/dist/logging-BzAk1S_s.js +1210 -0
  48. package/dist/model-profile-CZmVoGdX.js +49 -0
  49. package/dist/nitro.d.ts +31 -36
  50. package/dist/nitro.js +275 -456
  51. package/dist/oauth-callback-server-JiftokvA.js +130 -0
  52. package/dist/oauth-relay-CdCnu80W.js +62 -0
  53. package/dist/package-discovery-7a9YqLhE.js +455 -0
  54. package/dist/pending-auth-CWmSdZVg.js +185 -0
  55. package/dist/plugin-module-BKGOXca2.js +81 -0
  56. package/dist/plugins-BI9A1vKA.d.ts +95 -0
  57. package/dist/plugins-egKWjgue.js +99 -0
  58. package/dist/private-trace-result-BLvBjy_e.js +16 -0
  59. package/dist/profile-CrT5nXbw.js +861 -0
  60. package/dist/registry-DH1xUo2N.js +1419 -0
  61. package/dist/reporting-schema-DosTN__Q.js +105 -0
  62. package/dist/resolve-CZHnfxaS.js +546 -0
  63. package/dist/rolldown-runtime-B4FMCO8f.js +28 -0
  64. package/dist/runner-CZV2sC46.js +392 -0
  65. package/dist/sandbox-egress-signals-bHZfmhbd.js +46 -0
  66. package/dist/schema-Rn8-XEaN.js +1140 -0
  67. package/dist/sentry-CD3SdJl-.js +26 -0
  68. package/dist/skills-CIcDQSgM.js +316 -0
  69. package/dist/spawn-DdkUFRmq.js +29 -0
  70. package/dist/stats-DTUbAb88.js +2634 -0
  71. package/dist/store-C5qxF2ac.d.ts +208 -0
  72. package/dist/task-queue-CJIR4RF7.js +136 -0
  73. package/dist/task-runner-D-O3A-Cp.js +639 -0
  74. package/dist/thread-state-CRc9jTJS.js +431 -0
  75. package/dist/turn-execution-BkinIdJF.js +47 -0
  76. package/dist/turn-lifecycle-DQV19VTW.js +54 -0
  77. package/dist/usage-D3EbSO7w.js +296 -0
  78. package/dist/usage-schema-CKaDkH_j.js +20 -0
  79. package/dist/validation-C2CR-OX3.js +40 -0
  80. package/dist/vercel-queue-DIkVcfK4.js +167 -0
  81. package/dist/vercel.d.ts +6 -6
  82. package/dist/vercel.js +14 -6
  83. package/dist/version-CnXZ614E.d.ts +5 -0
  84. package/dist/version-DxcWdoKa.js +8 -0
  85. package/dist/version.d.ts +2 -4
  86. package/dist/version.js +2 -6
  87. package/dist/web-authorization-RMBxAR_t.js +76 -0
  88. package/dist/web-input-3EPfpf55.js +494 -0
  89. package/dist/work-fGofI7Op.js +509 -0
  90. package/dist/worker-CsOLq-mR.js +625 -0
  91. package/package.json +8 -8
  92. package/dist/agent-hooks-GB2VPTNA.js +0 -51
  93. package/dist/catalog-runtime-UBLIWD3O.js +0 -14
  94. package/dist/chunk-2LYHWMR4.js +0 -46
  95. package/dist/chunk-352OWAZH.js +0 -320
  96. package/dist/chunk-3Y47VMHV.js +0 -6
  97. package/dist/chunk-3YJSKOWF.js +0 -164
  98. package/dist/chunk-4KSSKTQ5.js +0 -229
  99. package/dist/chunk-4UO6FK4G.js +0 -64
  100. package/dist/chunk-5AHX53NQ.js +0 -717
  101. package/dist/chunk-5J3E5FWA.js +0 -700
  102. package/dist/chunk-5PDAG6O3.js +0 -206
  103. package/dist/chunk-6Y5B5PEX.js +0 -80
  104. package/dist/chunk-7GWQ5S5R.js +0 -2086
  105. package/dist/chunk-7H2VZTWN.js +0 -103
  106. package/dist/chunk-7NL7T6VU.js +0 -406
  107. package/dist/chunk-A5L27LE4.js +0 -387
  108. package/dist/chunk-ADIAWL34.js +0 -81
  109. package/dist/chunk-ASTEMBKJ.js +0 -519
  110. package/dist/chunk-CEMMLV5L.js +0 -98
  111. package/dist/chunk-CM5HNV2W.js +0 -131
  112. package/dist/chunk-CRTRBBRI.js +0 -105
  113. package/dist/chunk-DA2KGZZI.js +0 -13
  114. package/dist/chunk-DIB4GX3G.js +0 -8
  115. package/dist/chunk-DKIJ2RP5.js +0 -68
  116. package/dist/chunk-DWPN6SCH.js +0 -172
  117. package/dist/chunk-EDKUFGPL.js +0 -64
  118. package/dist/chunk-ENY6SKIU.js +0 -1228
  119. package/dist/chunk-F4MGCZOX.js +0 -2943
  120. package/dist/chunk-FA6POGLH.js +0 -70
  121. package/dist/chunk-FSUR7GTG.js +0 -20475
  122. package/dist/chunk-GKEOHJL5.js +0 -1856
  123. package/dist/chunk-GMCB6W5Y.js +0 -333
  124. package/dist/chunk-IE2VPG7O.js +0 -1135
  125. package/dist/chunk-IZ5B5IHQ.js +0 -3656
  126. package/dist/chunk-JAL3CH4G.js +0 -27
  127. package/dist/chunk-KIIEUXD3.js +0 -1764
  128. package/dist/chunk-KXU4NIL7.js +0 -692
  129. package/dist/chunk-MTM5IFCY.js +0 -1190
  130. package/dist/chunk-N7TMSOZH.js +0 -235
  131. package/dist/chunk-NCEBK6PH.js +0 -36
  132. package/dist/chunk-NHJU2ZAL.js +0 -56
  133. package/dist/chunk-P6KTRNVC.js +0 -321
  134. package/dist/chunk-PDO5BLNM.js +0 -22
  135. package/dist/chunk-PDRNLLFT.js +0 -3295
  136. package/dist/chunk-PEQXYSSE.js +0 -45
  137. package/dist/chunk-PKEX2AQX.js +0 -198
  138. package/dist/chunk-PSJCGIGO.js +0 -1163
  139. package/dist/chunk-PZUSUL55.js +0 -506
  140. package/dist/chunk-R45M4TUS.js +0 -37
  141. package/dist/chunk-RC745GGZ.js +0 -122
  142. package/dist/chunk-SP3QKZXJ.js +0 -820
  143. package/dist/chunk-TXKUKMVO.js +0 -417
  144. package/dist/chunk-UE4GPWSD.js +0 -931
  145. package/dist/chunk-UIE3R5XU.js +0 -17
  146. package/dist/chunk-UXPG6ZIN.js +0 -84
  147. package/dist/chunk-VFUK3X5B.js +0 -50
  148. package/dist/chunk-VNMJWREK.js +0 -787
  149. package/dist/chunk-VZ7CSOX2.js +0 -706
  150. package/dist/chunk-WCNQJKFT.js +0 -10
  151. package/dist/chunk-WDZR3QQM.js +0 -135
  152. package/dist/chunk-WLYVJCHV.js +0 -1433
  153. package/dist/chunk-WS7NAFTM.js +0 -1520
  154. package/dist/chunk-X6GDDWQH.js +0 -60
  155. package/dist/chunk-XPDEYNQI.js +0 -102
  156. package/dist/chunk-XRW44VGM.js +0 -58
  157. package/dist/chunk-Y2CM7HXH.js +0 -111
  158. package/dist/chunk-Y7X25LFY.js +0 -15
  159. package/dist/conversation-work-VIOQ5ZF3.js +0 -54
  160. package/dist/db-NGDJV7YH.js +0 -26
  161. package/dist/discovery-DWVDM7UK.js +0 -44
  162. package/dist/experimental-P5FZM4CU.js +0 -12
  163. package/dist/oauth-callback-server-YFY7YEGK.js +0 -243
  164. package/dist/oauth-relay-5XPVRPYB.js +0 -8
  165. package/dist/plugins-YTYJMMJF.js +0 -14
  166. package/dist/runner-ORTNSB2Q.js +0 -522
  167. package/dist/sandbox-egress-signals-FIL7EGPR.js +0 -73
  168. package/dist/sentry-MB3NL3IR.js +0 -42
  169. package/dist/skills-H376WQOH.js +0 -27
  170. package/dist/spawn-T43WHLOS.js +0 -42
  171. package/dist/task-runner-LBZWF32V.js +0 -36
  172. package/dist/validation-REJLRKI6.js +0 -17
  173. package/dist/work-3KJ3RE76.js +0 -51
  174. package/dist/worker-QFEBU72E.js +0 -24
@@ -0,0 +1,861 @@
1
+ import { R as toOptionalTrimmed, i as getChatConfig } from "./config-nGCW1OTG.js";
2
+ import { _ as setSpanAttributes, v as setSpanStatus } from "./logging-BzAk1S_s.js";
3
+ import { n as pluginCatalogRuntime } from "./catalog-runtime-BReos-D4.js";
4
+ import { createHash } from "node:crypto";
5
+ import { createMemoryState } from "@chat-adapter/state-memory";
6
+ import { createRedisState } from "@chat-adapter/state-redis";
7
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
8
+ //#region src/chat/sandbox/paths.ts
9
+ function normalizeWorkspaceRoot(input) {
10
+ const candidate = (input ?? "").trim();
11
+ if (!candidate) return "/vercel/sandbox";
12
+ const normalized = candidate.replace(/\/+$/, "");
13
+ return normalized.startsWith("/") ? normalized : `/${normalized}`;
14
+ }
15
+ const SANDBOX_WORKSPACE_ROOT = normalizeWorkspaceRoot(process.env.VERCEL_SANDBOX_WORKSPACE_DIR);
16
+ const SANDBOX_REPOS_ROOT = `${SANDBOX_WORKSPACE_ROOT}/repos`;
17
+ const SANDBOX_SKILLS_ROOT = `${SANDBOX_WORKSPACE_ROOT}/skills`;
18
+ const SANDBOX_DATA_ROOT = `${SANDBOX_WORKSPACE_ROOT}/data`;
19
+ function sandboxSkillDir(skillName) {
20
+ return `${SANDBOX_SKILLS_ROOT}/${skillName}`;
21
+ }
22
+ function sandboxSkillFile(skillName) {
23
+ return `${sandboxSkillDir(skillName)}/SKILL.md`;
24
+ }
25
+ /** Explain how the agent should resolve files and commands owned by a skill. */
26
+ function sandboxSkillPathResolution(skillName) {
27
+ const skillDir = sandboxSkillDir(skillName);
28
+ return `Resolve relative paths in this skill against ${skillDir}. For bash commands from this skill, cd to ${skillDir} first or use absolute paths.`;
29
+ }
30
+ //#endregion
31
+ //#region src/chat/sleep.ts
32
+ /** Wait for a duration, rejecting with the owning signal reason when cancelled. */
33
+ function sleep(ms, signal) {
34
+ if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));
35
+ return new Promise((resolve, reject) => {
36
+ if (signal.aborted) {
37
+ reject(signal.reason);
38
+ return;
39
+ }
40
+ const timeoutId = setTimeout(() => {
41
+ signal.removeEventListener("abort", onAbort);
42
+ resolve();
43
+ }, ms);
44
+ const onAbort = () => {
45
+ clearTimeout(timeoutId);
46
+ reject(signal.reason);
47
+ };
48
+ signal.addEventListener("abort", onAbort, { once: true });
49
+ });
50
+ }
51
+ //#endregion
52
+ //#region src/chat/state/locks.ts
53
+ const ACTIVE_LOCK_TTL_MS = 9e4;
54
+ const MUTATION_LOCK_TTL_MS = 1e4;
55
+ /**
56
+ * Acquire a lock for long-running work that the queued state adapter should
57
+ * keep alive while the owning invocation is still making progress.
58
+ */
59
+ async function acquireActiveLock(state, threadId) {
60
+ return await state.acquireLock(threadId, ACTIVE_LOCK_TTL_MS);
61
+ }
62
+ /**
63
+ * Run work under one lock, optionally waiting for the current owner.
64
+ * `keepAlive` is for multi-store mutations that must retain ownership until all
65
+ * side effects finish; callers should still fence each blind write.
66
+ */
67
+ async function withLock(state, key, run, options = {}) {
68
+ const startedAtMs = Date.now();
69
+ let lock;
70
+ while (true) {
71
+ lock = await state.acquireLock(key, options.ttlMs ?? 9e4);
72
+ if (lock) break;
73
+ if (Date.now() - startedAtMs >= (options.waitMs ?? 0)) return { acquired: false };
74
+ await setTimeout$1(options.retryMs ?? 25, void 0, { ref: false });
75
+ }
76
+ const ttlMs = options.ttlMs ?? 9e4;
77
+ const heartbeat = options.keepAlive ? setInterval(() => {
78
+ state.extendLock(lock, ttlMs);
79
+ }, Math.max(1, Math.floor(ttlMs / 3))) : void 0;
80
+ heartbeat?.unref();
81
+ try {
82
+ return {
83
+ acquired: true,
84
+ value: await run(lock)
85
+ };
86
+ } finally {
87
+ if (heartbeat) clearInterval(heartbeat);
88
+ await state.releaseLock(lock);
89
+ }
90
+ }
91
+ /**
92
+ * Prove lock ownership immediately before a blind write.
93
+ *
94
+ * Whole-record mutations must stop if their lock expired rather than overwrite
95
+ * state committed by a newer owner.
96
+ */
97
+ async function fenceLock(state, lock, ttlMs = MUTATION_LOCK_TTL_MS) {
98
+ if (!await state.extendLock(lock, ttlMs)) throw new Error(`Lock ownership was lost before write for ${lock.threadId}`);
99
+ }
100
+ /** Run work under the active lock, reporting contention without ambiguity. */
101
+ async function withActiveLock(state, key, run) {
102
+ return await withLock(state, key, run);
103
+ }
104
+ //#endregion
105
+ //#region src/chat/state/adapter.ts
106
+ const ACTIVE_LOCK_HEARTBEAT_MS = 3e4;
107
+ let stateAdapter;
108
+ let redisStateAdapter;
109
+ /** Lazily connect state operations, including queued-adapter lock heartbeats. */
110
+ function createConnectingStateAdapter(base) {
111
+ const connected = async (operation) => {
112
+ await base.connect();
113
+ return await operation();
114
+ };
115
+ return {
116
+ appendToList: (key, value, options) => connected(() => base.appendToList(key, value, options)),
117
+ connect: () => base.connect(),
118
+ disconnect: () => base.disconnect(),
119
+ subscribe: (threadId) => connected(() => base.subscribe(threadId)),
120
+ unsubscribe: (threadId) => connected(() => base.unsubscribe(threadId)),
121
+ isSubscribed: (threadId) => connected(() => base.isSubscribed(threadId)),
122
+ acquireLock: (threadId, ttlMs) => connected(() => base.acquireLock(threadId, ttlMs)),
123
+ releaseLock: (lock) => connected(() => base.releaseLock(lock)),
124
+ extendLock: (lock, ttlMs) => connected(() => base.extendLock(lock, ttlMs)),
125
+ forceReleaseLock: (threadId) => connected(() => base.forceReleaseLock(threadId)),
126
+ enqueue: (threadId, entry, maxSize) => connected(() => base.enqueue(threadId, entry, maxSize)),
127
+ dequeue: (threadId) => connected(() => base.dequeue(threadId)),
128
+ queueDepth: (threadId) => connected(() => base.queueDepth(threadId)),
129
+ get: (key) => connected(() => base.get(key)),
130
+ getList: (key) => connected(() => base.getList(key)),
131
+ set: (key, value, ttlMs) => connected(() => base.set(key, value, ttlMs)),
132
+ setIfNotExists: (key, value, ttlMs) => connected(() => base.setIfNotExists(key, value, ttlMs)),
133
+ delete: (key) => connected(() => base.delete(key))
134
+ };
135
+ }
136
+ function createPrefixedStateAdapter(base, prefix) {
137
+ const prefixed = (value) => `${prefix}:${value}`;
138
+ const unprefixed = (value) => value.startsWith(`${prefix}:`) ? value.slice(prefix.length + 1) : value;
139
+ const prefixLock = (lock) => ({
140
+ ...lock,
141
+ threadId: prefixed(lock.threadId)
142
+ });
143
+ const unprefixLock = (lock) => ({
144
+ ...lock,
145
+ threadId: unprefixed(lock.threadId)
146
+ });
147
+ return {
148
+ appendToList: (key, value, options) => base.appendToList(prefixed(key), value, options),
149
+ connect: () => base.connect(),
150
+ disconnect: () => base.disconnect(),
151
+ subscribe: (threadId) => base.subscribe(prefixed(threadId)),
152
+ unsubscribe: (threadId) => base.unsubscribe(prefixed(threadId)),
153
+ isSubscribed: (threadId) => base.isSubscribed(prefixed(threadId)),
154
+ acquireLock: async (threadId, ttlMs) => {
155
+ const lock = await base.acquireLock(prefixed(threadId), ttlMs);
156
+ return lock ? unprefixLock(lock) : null;
157
+ },
158
+ releaseLock: (lock) => base.releaseLock(prefixLock(lock)),
159
+ extendLock: async (lock, ttlMs) => {
160
+ const prefixedLock = prefixLock(lock);
161
+ const extended = await base.extendLock(prefixedLock, ttlMs);
162
+ if (extended) lock.expiresAt = prefixedLock.expiresAt;
163
+ return extended;
164
+ },
165
+ forceReleaseLock: (threadId) => base.forceReleaseLock(prefixed(threadId)),
166
+ enqueue: (threadId, entry, maxSize) => base.enqueue(prefixed(threadId), entry, maxSize),
167
+ dequeue: (threadId) => base.dequeue(prefixed(threadId)),
168
+ queueDepth: (threadId) => base.queueDepth(prefixed(threadId)),
169
+ get: (key) => base.get(prefixed(key)),
170
+ getList: (key) => base.getList(prefixed(key)),
171
+ set: (key, value, ttlMs) => base.set(prefixed(key), value, ttlMs),
172
+ setIfNotExists: (key, value, ttlMs) => base.setIfNotExists(prefixed(key), value, ttlMs),
173
+ delete: (key) => base.delete(prefixed(key))
174
+ };
175
+ }
176
+ function createQueuedStateAdapter(base, options) {
177
+ const heartbeats = /* @__PURE__ */ new Map();
178
+ const shouldHeartbeatLock = (ttlMs) => ttlMs === ACTIVE_LOCK_TTL_MS;
179
+ const heartbeatKey = (lock) => `${lock.threadId}:${lock.token}`;
180
+ const stopHeartbeatByKey = (key) => {
181
+ const heartbeat = heartbeats.get(key);
182
+ if (!heartbeat) return;
183
+ clearInterval(heartbeat.timer);
184
+ heartbeats.delete(key);
185
+ };
186
+ const stopHeartbeat = (lock) => {
187
+ stopHeartbeatByKey(heartbeatKey(lock));
188
+ };
189
+ const stopHeartbeatsForThread = (threadId) => {
190
+ for (const [key, heartbeat] of heartbeats) if (heartbeat.lock.threadId === threadId) stopHeartbeatByKey(key);
191
+ };
192
+ const stopAllHeartbeats = () => {
193
+ for (const key of heartbeats.keys()) stopHeartbeatByKey(key);
194
+ };
195
+ const runHeartbeat = async (key) => {
196
+ const heartbeat = heartbeats.get(key);
197
+ if (!heartbeat || heartbeat.inFlight) return;
198
+ heartbeat.inFlight = true;
199
+ try {
200
+ if (Date.now() - heartbeat.startedAtMs >= options.activeLockMaxAgeMs) {
201
+ stopHeartbeatByKey(key);
202
+ return;
203
+ }
204
+ if (!await base.extendLock(heartbeat.lock, heartbeat.ttlMs)) {
205
+ stopHeartbeatByKey(key);
206
+ return;
207
+ }
208
+ heartbeat.lock.expiresAt = Date.now() + heartbeat.ttlMs;
209
+ } catch {} finally {
210
+ const current = heartbeats.get(key);
211
+ if (current === heartbeat) current.inFlight = false;
212
+ }
213
+ };
214
+ const startOrUpdateHeartbeat = (lock, ttlMs) => {
215
+ const key = heartbeatKey(lock);
216
+ const existing = heartbeats.get(key);
217
+ if (existing) {
218
+ existing.ttlMs = ttlMs;
219
+ return;
220
+ }
221
+ const timer = setInterval(() => {
222
+ runHeartbeat(key);
223
+ }, ACTIVE_LOCK_HEARTBEAT_MS);
224
+ timer.unref?.();
225
+ heartbeats.set(key, {
226
+ inFlight: false,
227
+ lock,
228
+ startedAtMs: Date.now(),
229
+ timer,
230
+ ttlMs
231
+ });
232
+ };
233
+ const acquireLock = async (threadId, ttlMs) => {
234
+ const lock = await base.acquireLock(threadId, ttlMs);
235
+ if (lock && shouldHeartbeatLock(ttlMs)) startOrUpdateHeartbeat(lock, ttlMs);
236
+ return lock;
237
+ };
238
+ return {
239
+ appendToList: (key, value, options) => base.appendToList(key, value, options),
240
+ connect: () => base.connect(),
241
+ disconnect: async () => {
242
+ stopAllHeartbeats();
243
+ await base.disconnect();
244
+ },
245
+ subscribe: (threadId) => base.subscribe(threadId),
246
+ unsubscribe: (threadId) => base.unsubscribe(threadId),
247
+ isSubscribed: (threadId) => base.isSubscribed(threadId),
248
+ acquireLock,
249
+ releaseLock: async (lock) => {
250
+ stopHeartbeat(lock);
251
+ await base.releaseLock(lock);
252
+ },
253
+ extendLock: async (lock, ttlMs) => {
254
+ const extended = await base.extendLock(lock, ttlMs);
255
+ if (extended) {
256
+ lock.expiresAt = Date.now() + ttlMs;
257
+ if (shouldHeartbeatLock(ttlMs)) startOrUpdateHeartbeat(lock, ttlMs);
258
+ else stopHeartbeat(lock);
259
+ } else stopHeartbeat(lock);
260
+ return extended;
261
+ },
262
+ forceReleaseLock: async (threadId) => {
263
+ stopHeartbeatsForThread(threadId);
264
+ await base.forceReleaseLock(threadId);
265
+ },
266
+ enqueue: (threadId, entry, maxSize) => base.enqueue(threadId, entry, maxSize),
267
+ dequeue: (threadId) => base.dequeue(threadId),
268
+ queueDepth: (threadId) => base.queueDepth(threadId),
269
+ get: (key) => base.get(key),
270
+ getList: (key) => base.getList(key),
271
+ set: (key, value, ttlMs) => base.set(key, value, ttlMs),
272
+ setIfNotExists: (key, value, ttlMs) => base.setIfNotExists(key, value, ttlMs),
273
+ delete: (key) => base.delete(key)
274
+ };
275
+ }
276
+ function withOptionalPrefix(base, prefix) {
277
+ return prefix ? createPrefixedStateAdapter(base, prefix) : base;
278
+ }
279
+ function createStateAdapter() {
280
+ const config = getChatConfig();
281
+ const activeLockMaxAgeMs = config.bot.turnTimeoutMs + ACTIVE_LOCK_TTL_MS;
282
+ if (config.state.adapter === "memory") {
283
+ redisStateAdapter = void 0;
284
+ return createQueuedStateAdapter(createConnectingStateAdapter(withOptionalPrefix(createMemoryState(), config.state.keyPrefix)), { activeLockMaxAgeMs });
285
+ }
286
+ if (!config.state.redisUrl) throw new Error("REDIS_URL is required for durable Slack thread state");
287
+ const redisState = createRedisState({ url: config.state.redisUrl });
288
+ redisStateAdapter = redisState;
289
+ return createQueuedStateAdapter(createConnectingStateAdapter(withOptionalPrefix(redisState, config.state.keyPrefix)), { activeLockMaxAgeMs });
290
+ }
291
+ function getOptionalRedisStateAdapter() {
292
+ getStateAdapter();
293
+ return redisStateAdapter;
294
+ }
295
+ async function getConnectedStateContext() {
296
+ const adapter = getStateAdapter();
297
+ await adapter.connect();
298
+ return {
299
+ redisStateAdapter: getOptionalRedisStateAdapter(),
300
+ stateAdapter: adapter
301
+ };
302
+ }
303
+ /** Return the Redis adapter only when the caller is using the default state adapter. */
304
+ async function getDefaultRedisStateAdapterFor(adapter) {
305
+ if (adapter !== stateAdapter) return;
306
+ const context = await getConnectedStateContext();
307
+ return context.stateAdapter === adapter ? context.redisStateAdapter : void 0;
308
+ }
309
+ function getStateAdapter() {
310
+ if (!stateAdapter) stateAdapter = createStateAdapter();
311
+ return stateAdapter;
312
+ }
313
+ async function disconnectStateAdapter() {
314
+ if (!stateAdapter) return;
315
+ try {
316
+ await stateAdapter.disconnect();
317
+ } finally {
318
+ stateAdapter = void 0;
319
+ redisStateAdapter = void 0;
320
+ }
321
+ }
322
+ //#endregion
323
+ //#region src/chat/sandbox/noninteractive-command.ts
324
+ const NON_INTERACTIVE_ENV = {
325
+ CI: "1",
326
+ TERM: "dumb",
327
+ NO_COLOR: "1",
328
+ PAGER: "cat",
329
+ GIT_PAGER: "cat",
330
+ GH_PROMPT_DISABLED: "1",
331
+ GH_NO_UPDATE_NOTIFIER: "1",
332
+ GH_NO_EXTENSION_UPDATE_NOTIFIER: "1",
333
+ GH_SPINNER_DISABLED: "1",
334
+ GIT_TERMINAL_PROMPT: "0",
335
+ GCM_INTERACTIVE: "never",
336
+ DEBIAN_FRONTEND: "noninteractive",
337
+ GIT_ASKPASS: "/bin/true",
338
+ GIT_CONFIG_NOSYSTEM: "1",
339
+ GIT_CONFIG_COUNT: "2",
340
+ GIT_CONFIG_KEY_0: "credential.helper",
341
+ GIT_CONFIG_VALUE_0: "",
342
+ GIT_CONFIG_KEY_1: "http.emptyAuth",
343
+ GIT_CONFIG_VALUE_1: "true"
344
+ };
345
+ function shellQuote(value) {
346
+ return `'${value.replace(/'/g, "'\\''")}'`;
347
+ }
348
+ function buildEnvExports(options) {
349
+ const lines = [];
350
+ if (options.pathPrefix) lines.push(`export PATH="${options.pathPrefix}"`);
351
+ for (const [key, value] of Object.entries(NON_INTERACTIVE_ENV)) lines.push(`export ${key}=${shellQuote(value)}`);
352
+ for (const [key, value] of Object.entries(options.env ?? {})) lines.push(`export ${key}=${shellQuote(value)}`);
353
+ return lines;
354
+ }
355
+ /** Render one argv command as a safely quoted shell script. */
356
+ function buildCommandScript(input) {
357
+ return [shellQuote(input.cmd), ...(input.args ?? []).map(shellQuote)].join(" ");
358
+ }
359
+ /** Build one shell entrypoint that never waits on terminal input. */
360
+ function buildNonInteractiveShellScript(script, options = {}) {
361
+ return [
362
+ ...buildEnvExports(options),
363
+ "exec </dev/null",
364
+ script
365
+ ].join(" && ");
366
+ }
367
+ /** Wrap argv-style commands so every sandbox subprocess runs in non-interactive mode. */
368
+ function buildNonInteractiveCommand(input) {
369
+ return {
370
+ cmd: "bash",
371
+ args: [input.login ? "-lc" : "-c", buildNonInteractiveShellScript(buildCommandScript(input), {
372
+ env: input.env,
373
+ pathPrefix: input.pathPrefix
374
+ })]
375
+ };
376
+ }
377
+ /** Run a subprocess through one enforced non-interactive entrypoint. */
378
+ async function runNonInteractiveCommand(sandbox, input) {
379
+ const command = {
380
+ ...buildNonInteractiveCommand(input),
381
+ ...input.cwd ? { cwd: input.cwd } : void 0,
382
+ ...input.signal ? { signal: input.signal } : void 0,
383
+ ...input.sudo !== void 0 ? { sudo: input.sudo } : void 0,
384
+ ...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : void 0
385
+ };
386
+ return await sandbox.runCommand(command);
387
+ }
388
+ //#endregion
389
+ //#region src/chat/sandbox/credentials.ts
390
+ /**
391
+ * Resolve explicit Vercel Sandbox credentials, or return undefined to let
392
+ * the SDK read ambient Vercel auth from its own environment detection.
393
+ */
394
+ function getVercelSandboxCredentials() {
395
+ const token = toOptionalTrimmed(process.env.VERCEL_TOKEN);
396
+ const teamId = toOptionalTrimmed(process.env.VERCEL_TEAM_ID);
397
+ const projectId = toOptionalTrimmed(process.env.VERCEL_PROJECT_ID);
398
+ if (token && teamId && projectId) return {
399
+ token,
400
+ teamId,
401
+ projectId
402
+ };
403
+ }
404
+ //#endregion
405
+ //#region src/chat/sandbox/http-error-details.ts
406
+ const DEFAULT_PREVIEW_LIMIT = 512;
407
+ function toTrimmedString(value, maxChars) {
408
+ if (typeof value !== "string") return;
409
+ const normalized = value.trim();
410
+ if (!normalized) return;
411
+ return normalized.length > maxChars ? `${normalized.slice(0, maxChars)}…` : normalized;
412
+ }
413
+ /** Extract structured HTTP error metadata (status, headers, body preview) for logging. */
414
+ function extractHttpErrorDetails(error, options = {}) {
415
+ const prefix = options.attributePrefix ?? "app.http_error";
416
+ const previewLimit = options.previewLimit ?? DEFAULT_PREVIEW_LIMIT;
417
+ const normalizedError = error instanceof Error ? error : new Error(String(error));
418
+ const err = error ?? {};
419
+ const attributes = {
420
+ "error.type": normalizedError.name || "Error",
421
+ "exception.message": toTrimmedString(normalizedError.message, previewLimit) ?? "HTTP error"
422
+ };
423
+ const response = err.response;
424
+ const statusCode = typeof response?.status === "number" ? response.status : void 0;
425
+ const statusText = toTrimmedString(response?.statusText, previewLimit);
426
+ const responseUrl = toTrimmedString(response?.url, previewLimit);
427
+ const responseText = toTrimmedString(err.text, previewLimit);
428
+ const responseJson = toTrimmedString(err.json && typeof err.json === "object" ? JSON.stringify(err.json) : void 0, previewLimit);
429
+ const contentType = toTrimmedString(response?.headers?.get?.("content-type"), previewLimit);
430
+ const requestIdHeader = toTrimmedString(response?.headers?.get?.("x-request-id"), previewLimit);
431
+ const vercelIdHeader = toTrimmedString(response?.headers?.get?.("x-vercel-id"), previewLimit);
432
+ const requestId = requestIdHeader ?? vercelIdHeader;
433
+ if (statusCode !== void 0) attributes["http.response.status_code"] = statusCode;
434
+ if (statusText) attributes[`${prefix}.status_text`] = statusText;
435
+ if (responseUrl) attributes["url.full"] = responseUrl;
436
+ if (contentType) attributes["http.response.header.content-type"] = [contentType];
437
+ if (requestIdHeader) attributes["http.response.header.x-request-id"] = [requestIdHeader];
438
+ if (vercelIdHeader) attributes["http.response.header.x-vercel-id"] = [vercelIdHeader];
439
+ if (responseText) {
440
+ attributes[`${prefix}.response_text_preview`] = responseText;
441
+ attributes[`${prefix}.response_text_length`] = responseText.length;
442
+ }
443
+ if (responseJson) attributes[`${prefix}.response_json_preview`] = responseJson;
444
+ const summaryParts = [];
445
+ if (statusCode !== void 0) summaryParts.push(`status=${statusCode}`);
446
+ if (statusText) summaryParts.push(`statusText=${statusText}`);
447
+ if (responseUrl) summaryParts.push(`url=${responseUrl}`);
448
+ if (requestId) summaryParts.push(`requestId=${requestId}`);
449
+ for (const field of options.extraFields ?? []) {
450
+ const value = toTrimmedString(err[field.sourceKey], previewLimit);
451
+ if (!value) continue;
452
+ attributes[`${prefix}.${field.attributeKey}`] = value;
453
+ summaryParts.push(`${field.summaryKey ?? field.attributeKey}=${value}`);
454
+ }
455
+ if (responseJson) summaryParts.push(`json=${responseJson}`);
456
+ else if (responseText) summaryParts.push(`text=${responseText}`);
457
+ const searchableText = `${normalizedError.message} ${responseText ?? ""} ${responseJson ?? ""}`.toLowerCase();
458
+ return {
459
+ attributes,
460
+ summary: summaryParts.join(", "),
461
+ searchableText
462
+ };
463
+ }
464
+ //#endregion
465
+ //#region src/chat/sandbox/snapshot/not-ready-error.ts
466
+ /** Report that a Workspace snapshot is not ready to use. */
467
+ var WorkspaceSnapshotNotReadyError = class extends Error {
468
+ workspaceName;
469
+ constructor(workspaceName) {
470
+ super(`Workspace ${workspaceName} snapshot is not ready`);
471
+ this.name = "WorkspaceSnapshotNotReadyError";
472
+ this.workspaceName = workspaceName;
473
+ }
474
+ };
475
+ /** Return WorkspaceSnapshotNotReadyError from this error or its causes. */
476
+ function getWorkspaceSnapshotNotReadyError(error) {
477
+ const seen = /* @__PURE__ */ new Set();
478
+ let current = error;
479
+ while (current instanceof Error && !seen.has(current)) {
480
+ if (current instanceof WorkspaceSnapshotNotReadyError) return current;
481
+ seen.add(current);
482
+ current = current.cause;
483
+ }
484
+ }
485
+ /** True when this error or a cause is WorkspaceSnapshotNotReadyError. */
486
+ function isWorkspaceSnapshotNotReadyError(error) {
487
+ return getWorkspaceSnapshotNotReadyError(error) !== void 0;
488
+ }
489
+ /** User message when a Workspace sandbox is still preparing. */
490
+ function workspaceSnapshotNotReadyUserMessage(error) {
491
+ return `The ${error.workspaceName} workspace is still preparing its sandbox. Wait for that preparation to finish, then try again.`;
492
+ }
493
+ //#endregion
494
+ //#region src/chat/sandbox/errors.ts
495
+ const SANDBOX_ERROR_FIELDS = [{
496
+ sourceKey: "sandboxId",
497
+ attributeKey: "sandbox_id",
498
+ summaryKey: "sandboxId"
499
+ }];
500
+ function getSandboxErrorDetails(error) {
501
+ return extractHttpErrorDetails(error, {
502
+ attributePrefix: "app.sandbox.api_error",
503
+ extraFields: [...SANDBOX_ERROR_FIELDS]
504
+ });
505
+ }
506
+ function findInErrorChain(error, predicate) {
507
+ const seen = /* @__PURE__ */ new Set();
508
+ let current = error;
509
+ while (current && !seen.has(current)) {
510
+ if (predicate(current)) return true;
511
+ seen.add(current);
512
+ current = typeof current === "object" ? current.cause : void 0;
513
+ }
514
+ return false;
515
+ }
516
+ function isRecord(value) {
517
+ return typeof value === "object" && value !== null;
518
+ }
519
+ function isInvalidSandboxSessionError(error) {
520
+ if (!isRecord(error)) return false;
521
+ if (isRecord(error.json) && error.json.invalidToken === true) return true;
522
+ const response = error.response;
523
+ return isRecord(response) && response.status === 403 && typeof response.url === "string" && response.url.includes("/sandboxes/sessions");
524
+ }
525
+ function getFirstErrorMessage(error) {
526
+ const seen = /* @__PURE__ */ new Set();
527
+ let current = error;
528
+ while (current && !seen.has(current)) {
529
+ if (current instanceof Error) {
530
+ const message = current.message.trim();
531
+ if (message) return message;
532
+ }
533
+ seen.add(current);
534
+ current = typeof current === "object" ? current.cause : void 0;
535
+ }
536
+ }
537
+ /** Detect sandbox filesystem operations that can safely ignore existing directories. */
538
+ function isAlreadyExistsError(error) {
539
+ const details = getSandboxErrorDetails(error);
540
+ return details.searchableText.includes("already exists") || details.searchableText.includes("file exists") || details.searchableText.includes("eexist");
541
+ }
542
+ /** Detect when the current sandbox session is unavailable and must be reacquired. */
543
+ function isSandboxUnavailableError(error) {
544
+ return findInErrorChain(error, (candidate) => {
545
+ if (isInvalidSandboxSessionError(candidate)) return true;
546
+ const details = getSandboxErrorDetails(candidate);
547
+ const searchable = `${details.searchableText} ${details.summary}`.toLowerCase();
548
+ return searchable.includes("sandbox_stopped") || searchable.includes("status=410") || searchable.includes("status code 410") || searchable.includes("no longer available") || searchable.includes("stream was closed and is not accepting commands");
549
+ });
550
+ }
551
+ /** Detect a durable sandbox reference that can no longer be restored. */
552
+ function isSandboxMissingError(error) {
553
+ return findInErrorChain(error, (candidate) => {
554
+ const details = getSandboxErrorDetails(candidate);
555
+ const searchable = `${details.searchableText} ${details.summary}`.toLowerCase();
556
+ return searchable.includes("status=404") || searchable.includes("status code 404") || searchable.includes("snapshot_not_found");
557
+ });
558
+ }
559
+ /** Detect transient snapshot boot races so sandbox creation can retry. */
560
+ function isSnapshottingError(error) {
561
+ return findInErrorChain(error, (candidate) => {
562
+ const details = getSandboxErrorDetails(candidate);
563
+ const searchable = `${details.searchableText} ${details.summary}`.toLowerCase();
564
+ return searchable.includes("sandbox_snapshotting") || searchable.includes("creating a snapshot") || searchable.includes("stopped shortly");
565
+ });
566
+ }
567
+ /** Detect cancellation so a durable builder stays available for check-in. */
568
+ function isAbortError(error) {
569
+ return findInErrorChain(error, (candidate) => {
570
+ if (typeof DOMException !== "undefined" && candidate instanceof DOMException) return candidate.name === "AbortError";
571
+ if (!(candidate instanceof Error)) return false;
572
+ if (candidate.name === "AbortError") return true;
573
+ return candidate.message.toLowerCase().startsWith("executeagentrun timed out after");
574
+ });
575
+ }
576
+ /** Detect transient Vercel Sandbox API failures that keep a job resumable. */
577
+ function isSandboxApiTransientError(error) {
578
+ return findInErrorChain(error, (candidate) => {
579
+ const details = getSandboxErrorDetails(candidate);
580
+ const searchable = `${details.searchableText} ${details.summary}`.toLowerCase();
581
+ if (searchable.includes("internal_server_error") || searchable.includes("status=500") || searchable.includes("status code 500") || searchable.includes("status=502") || searchable.includes("status code 502") || searchable.includes("status=503") || searchable.includes("status code 503") || searchable.includes("status=504") || searchable.includes("status code 504")) return true;
582
+ if (!isRecord(candidate)) return false;
583
+ const response = candidate.response;
584
+ return isRecord(response) && typeof response.status === "number" && response.status >= 500 && response.status < 600;
585
+ });
586
+ }
587
+ /** Detect interrupted command streams where no reliable exit status is available. */
588
+ function isSandboxCommandStreamInterruptedError(error) {
589
+ return findInErrorChain(error, (candidate) => {
590
+ if (!(candidate instanceof Error)) return false;
591
+ return candidate.name === "StreamError" && candidate.message.toLowerCase().includes("stream ended before command finished");
592
+ });
593
+ }
594
+ /** Wrap setup failures. Keep WorkspaceSnapshotNotReadyError as-is. */
595
+ function wrapSandboxSetupError(error) {
596
+ const notReady = getWorkspaceSnapshotNotReadyError(error);
597
+ if (notReady) return notReady;
598
+ try {
599
+ const details = getSandboxErrorDetails(error);
600
+ if (details.summary) return new Error(`sandbox setup failed (${details.summary})`, { cause: error });
601
+ } catch {}
602
+ let causeMessage;
603
+ try {
604
+ causeMessage = getFirstErrorMessage(error);
605
+ } catch (cause) {
606
+ causeMessage = cause instanceof Error ? cause.message : void 0;
607
+ }
608
+ if (causeMessage && causeMessage.trim() && causeMessage !== "sandbox setup failed") {
609
+ const oneLine = causeMessage.replace(/\s+/g, " ").trim();
610
+ return new Error(`sandbox setup failed (${oneLine})`, { cause: error });
611
+ }
612
+ return new Error("sandbox setup failed", { cause: error });
613
+ }
614
+ /** Record span data and throw one stable sandbox operation error. */
615
+ function throwSandboxOperationError(action, error, includeMissingPath = false) {
616
+ const details = getSandboxErrorDetails(error);
617
+ setSpanAttributes({
618
+ ...details.attributes,
619
+ ...includeMissingPath ? { "app.sandbox.api_error.missing_path": details.searchableText.includes("no such file") || details.searchableText.includes("enoent") } : void 0,
620
+ "app.sandbox.success": false
621
+ });
622
+ setSpanStatus("error");
623
+ throw new Error(details.summary ? `${action} failed (${details.summary})` : `${action} failed`, { cause: error });
624
+ }
625
+ //#endregion
626
+ //#region src/chat/sandbox/docker.ts
627
+ /** Docker daemon config for Vercel sandbox guests (nested containers). */
628
+ const DOCKER_DAEMON_JSON = `{
629
+ "storage-driver": "overlay2",
630
+ "live-restore": false
631
+ }
632
+ `;
633
+ /**
634
+ * Start dockerd when the socket is missing and wait until `docker info` works.
635
+ * Re-execs through sudo so the sandbox user can call it without root.
636
+ */
637
+ const DOCKER_ENSURE_SCRIPT = `#!/bin/sh
638
+ set -eu
639
+
640
+ if [ "$(id -u)" -ne 0 ]; then
641
+ exec sudo -n "$0" "$@"
642
+ fi
643
+
644
+ SOCK=/var/run/docker.sock
645
+ PIDFILE=/var/run/docker.pid
646
+ LOG=/var/log/junior-dockerd.log
647
+
648
+ is_ready() {
649
+ [ -S "$SOCK" ] && /usr/bin/docker info >/dev/null 2>&1
650
+ }
651
+
652
+ if is_ready; then
653
+ chmod 666 "$SOCK" 2>/dev/null || true
654
+ exit 0
655
+ fi
656
+
657
+ mkdir -p /etc/docker /var/lib/docker /var/run /var/log
658
+ if [ ! -f /etc/docker/daemon.json ]; then
659
+ cat > /etc/docker/daemon.json <<'EOF'
660
+ {
661
+ "storage-driver": "overlay2",
662
+ "live-restore": false
663
+ }
664
+ EOF
665
+ fi
666
+
667
+ if [ -f "$PIDFILE" ]; then
668
+ oldpid="$(cat "$PIDFILE" 2>/dev/null || true)"
669
+ if [ -n "\${oldpid:-}" ] && ! kill -0 "$oldpid" 2>/dev/null; then
670
+ rm -f "$PIDFILE"
671
+ fi
672
+ fi
673
+
674
+ if [ ! -f "$PIDFILE" ] || ! kill -0 "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null; then
675
+ # dockerd must outlive this helper; nohup + background is intentional.
676
+ nohup dockerd --host="unix://$SOCK" --pidfile="$PIDFILE" >"$LOG" 2>&1 &
677
+ fi
678
+
679
+ i=0
680
+ while [ "$i" -lt 50 ]; do
681
+ if is_ready; then
682
+ chmod 666 "$SOCK" 2>/dev/null || true
683
+ exit 0
684
+ fi
685
+ i=$((i + 1))
686
+ sleep 0.1
687
+ done
688
+
689
+ echo "junior-ensure-docker: dockerd failed to become ready" >&2
690
+ if [ -f "$LOG" ]; then
691
+ tail -n 50 "$LOG" >&2 || true
692
+ fi
693
+ exit 1
694
+ `;
695
+ /** PATH wrapper so plain `docker` starts the daemon on first use. */
696
+ const DOCKER_CLI_WRAPPER_SCRIPT = `#!/bin/sh
697
+ set -eu
698
+ if command -v junior-ensure-docker >/dev/null 2>&1; then
699
+ junior-ensure-docker
700
+ fi
701
+ exec /usr/bin/docker "$@"
702
+ `;
703
+ /** PATH wrapper so plain `docker-compose` starts the daemon on first use. */
704
+ const DOCKER_COMPOSE_CLI_WRAPPER_SCRIPT = `#!/bin/sh
705
+ set -eu
706
+ if command -v junior-ensure-docker >/dev/null 2>&1; then
707
+ junior-ensure-docker
708
+ fi
709
+ exec /usr/local/libexec/junior-docker-compose "$@"
710
+ `;
711
+ /**
712
+ * Best-effort dockerd start during sandbox prepare.
713
+ * Missing helper is a no-op so tests and base sandboxes stay green.
714
+ */
715
+ async function ensureDockerDaemon(sandbox, signal) {
716
+ await runNonInteractiveCommand(sandbox, {
717
+ cmd: "sh",
718
+ args: ["-c", "command -v junior-ensure-docker >/dev/null 2>&1 && junior-ensure-docker || true"],
719
+ signal
720
+ });
721
+ }
722
+ //#endregion
723
+ //#region src/chat/sandbox/runtime-dependencies.ts
724
+ const GLOBAL_RUNTIME_DEPENDENCIES = [
725
+ {
726
+ type: "system",
727
+ package: "docker"
728
+ },
729
+ {
730
+ type: "system",
731
+ package: "spal-release"
732
+ },
733
+ {
734
+ type: "system",
735
+ package: "ripgrep"
736
+ }
737
+ ];
738
+ function writeFileCommand(path, contents, mode = "0644") {
739
+ return [`printf '%s' '${Buffer.from(contents, "utf8").toString("base64")}' | base64 -d > ${path}`, `chmod ${mode} ${path}`].join("\n");
740
+ }
741
+ function writeExecutableCommand(path, contents) {
742
+ return writeFileCommand(path, contents, "0755");
743
+ }
744
+ const GLOBAL_RUNTIME_POSTINSTALL = [{
745
+ cmd: "sh",
746
+ args: ["-c", [
747
+ "set -eu",
748
+ "case \"$(uname -m)\" in",
749
+ " x86_64) asset=\"x86_64\"; sha256=\"7af95166a730b87e172d4fc9aefea8725d3c6c7327d59149267b452114ddb7d4\" ;;",
750
+ " aarch64) asset=\"aarch64\"; sha256=\"49082844b87f03cdcd5f5bbef1ba8c9c897b7a2dfb80cea18d61ec8ca6117e0c\" ;;",
751
+ " *) echo \"unsupported Docker Compose architecture: $(uname -m)\" >&2; exit 1 ;;",
752
+ "esac",
753
+ "tmp=\"$(mktemp)\"",
754
+ "trap 'rm -f \"$tmp\"' EXIT",
755
+ "curl --fail --location --silent --show-error \"https://github.com/docker/compose/releases/download/v2.39.4/docker-compose-linux-${asset}\" --output \"$tmp\"",
756
+ "echo \"${sha256} ${tmp}\" | sha256sum --check --status",
757
+ "install -D -m 0755 \"$tmp\" /usr/local/libexec/junior-docker-compose",
758
+ "mkdir -p /usr/local/lib/docker/cli-plugins /usr/local/libexec /etc/docker /usr/local/bin",
759
+ `mkdir -p ${SANDBOX_WORKSPACE_ROOT}/.junior/bin`,
760
+ writeFileCommand("/etc/docker/daemon.json", DOCKER_DAEMON_JSON),
761
+ writeExecutableCommand("/usr/local/bin/junior-ensure-docker", DOCKER_ENSURE_SCRIPT),
762
+ "ln -sfn /usr/local/libexec/junior-docker-compose /usr/local/lib/docker/cli-plugins/docker-compose",
763
+ writeExecutableCommand("/usr/local/bin/docker-compose", DOCKER_COMPOSE_CLI_WRAPPER_SCRIPT),
764
+ writeExecutableCommand(`${SANDBOX_WORKSPACE_ROOT}/.junior/bin/docker`, DOCKER_CLI_WRAPPER_SCRIPT),
765
+ writeExecutableCommand(`${SANDBOX_WORKSPACE_ROOT}/.junior/bin/docker-compose`, DOCKER_COMPOSE_CLI_WRAPPER_SCRIPT),
766
+ writeExecutableCommand("/usr/local/bin/docker", DOCKER_CLI_WRAPPER_SCRIPT)
767
+ ].join("\n")],
768
+ sudo: true
769
+ }];
770
+ //#endregion
771
+ //#region src/chat/sandbox/snapshot/profile.ts
772
+ const VERSION = 1;
773
+ const DEFAULT_FLOATING_MAX_AGE_MS = 6048e5;
774
+ function isExactNpmVersion(version) {
775
+ return /^\d+\.\d+\.\d+(?:[-+][a-z0-9.]+)?$/i.test(version.trim());
776
+ }
777
+ function isFloating(dep) {
778
+ return dep.type === "npm" && !isExactNpmVersion(dep.version);
779
+ }
780
+ /**
781
+ * Merge dependencies for one global install, rejecting competing npm versions.
782
+ */
783
+ function mergeDependencies(dependencies) {
784
+ const seen = /* @__PURE__ */ new Set();
785
+ const npmVersions = /* @__PURE__ */ new Map();
786
+ const merged = [];
787
+ for (const dependency of dependencies) {
788
+ const key = dependency.type === "npm" ? `${dependency.type}:${dependency.package}:${dependency.version}` : "package" in dependency ? `${dependency.type}:package:${dependency.package}` : `${dependency.type}:url:${dependency.url}:${dependency.sha256}`;
789
+ if (seen.has(key)) continue;
790
+ seen.add(key);
791
+ if (dependency.type === "npm") {
792
+ const version = npmVersions.get(dependency.package);
793
+ if (version !== void 0 && version !== dependency.version) throw new Error(`Conflicting runtime dependency versions for ${dependency.package}: ${version} and ${dependency.version}`);
794
+ npmVersions.set(dependency.package, dependency.version);
795
+ }
796
+ merged.push(dependency);
797
+ }
798
+ return merged;
799
+ }
800
+ function floatingMaxAgeMs() {
801
+ const raw = process.env.SANDBOX_SNAPSHOT_FLOATING_MAX_AGE_MS;
802
+ if (!raw?.trim()) return DEFAULT_FLOATING_MAX_AGE_MS;
803
+ const parsed = Number.parseInt(raw, 10);
804
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_FLOATING_MAX_AGE_MS;
805
+ }
806
+ /** Locale-independent string order for stable profile hashes. */
807
+ function compareCodePoints(left, right) {
808
+ if (left < right) return -1;
809
+ if (left > right) return 1;
810
+ return 0;
811
+ }
812
+ function workspaceRecipe(workspace) {
813
+ const repos = [...workspace.repos].map(({ provider, repo }) => ({
814
+ provider,
815
+ repo
816
+ })).sort((left, right) => {
817
+ const provider = compareCodePoints(left.provider, right.provider);
818
+ if (provider !== 0) return provider;
819
+ return compareCodePoints(left.repo, right.repo);
820
+ });
821
+ return {
822
+ id: workspace.id,
823
+ repos,
824
+ setupScript: workspace.setupScript
825
+ };
826
+ }
827
+ /** Build the complete profile that selects one reusable sandbox snapshot. */
828
+ function create(runtime, workspace) {
829
+ const dependencies = mergeDependencies([...GLOBAL_RUNTIME_DEPENDENCIES, ...pluginCatalogRuntime.getRuntimeDependencies()]);
830
+ const pluginPostinstall = pluginCatalogRuntime.getRuntimePostinstall();
831
+ const postinstall = [...GLOBAL_RUNTIME_POSTINSTALL, ...pluginPostinstall];
832
+ if (dependencies.length === 0 && postinstall.length === 0 && !workspace) return null;
833
+ const rebuildEpoch = process.env.SANDBOX_SNAPSHOT_REBUILD_EPOCH?.trim() ?? "";
834
+ const floating = dependencies.some((dependency) => isFloating(dependency)) || pluginPostinstall.length > 0 || Boolean(workspace);
835
+ return {
836
+ hash: createHash("sha256").update(JSON.stringify({
837
+ version: VERSION,
838
+ runtime,
839
+ rebuildEpoch,
840
+ dependencies,
841
+ postinstall,
842
+ ...workspace ? { workspace: workspaceRecipe(workspace) } : void 0
843
+ })).digest("hex"),
844
+ dependencyCount: dependencies.length,
845
+ floating,
846
+ dependencies,
847
+ postinstall
848
+ };
849
+ }
850
+ /** Return the current dependency profile hash without building its snapshot. */
851
+ function hash(runtime, workspace) {
852
+ return create(runtime, workspace)?.hash;
853
+ }
854
+ /** Decide whether a cached snapshot has outlived a floating profile. */
855
+ function isStale(profile, createdAtMs) {
856
+ if (!profile.floating) return false;
857
+ const maxAgeMs = floatingMaxAgeMs();
858
+ return maxAgeMs === 0 || Date.now() - createdAtMs > maxAgeMs;
859
+ }
860
+ //#endregion
861
+ export { sleep as A, getDefaultRedisStateAdapterFor as C, fenceLock as D, acquireActiveLock as E, sandboxSkillDir as F, sandboxSkillFile as I, sandboxSkillPathResolution as L, SANDBOX_REPOS_ROOT as M, SANDBOX_SKILLS_ROOT as N, withActiveLock as O, SANDBOX_WORKSPACE_ROOT as P, disconnectStateAdapter as S, MUTATION_LOCK_TTL_MS as T, workspaceSnapshotNotReadyUserMessage as _, isAbortError as a, buildNonInteractiveShellScript as b, isSandboxCommandStreamInterruptedError as c, isSnapshottingError as d, throwSandboxOperationError as f, isWorkspaceSnapshotNotReadyError as g, getWorkspaceSnapshotNotReadyError as h, ensureDockerDaemon as i, SANDBOX_DATA_ROOT as j, withLock as k, isSandboxMissingError as l, WorkspaceSnapshotNotReadyError as m, hash as n, isAlreadyExistsError as o, wrapSandboxSetupError as p, isStale as r, isSandboxApiTransientError as s, create as t, isSandboxUnavailableError as u, getVercelSandboxCredentials as v, getStateAdapter as w, runNonInteractiveCommand as x, buildCommandScript as y };