@sentry/junior 0.116.1 → 0.117.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.
- package/dist/{agent-hooks-GUAAS3XH.js → agent-hooks-5IK4LPLP.js} +8 -8
- package/dist/api/conversations/detail.d.ts +7 -0
- package/dist/api/conversations/event-list.d.ts +7 -0
- package/dist/api/conversations/events.d.ts +1 -1
- package/dist/api/schema/conversation.d.ts +28 -0
- package/dist/api/schema.js +1 -1
- package/dist/api.js +25 -12
- package/dist/app.js +32 -30
- package/dist/{catalog-runtime-WMPC4J4U.js → catalog-runtime-MTUYW3AA.js} +5 -5
- package/dist/chat/agent/prompt.d.ts +2 -0
- package/dist/chat/agent/request.d.ts +2 -1
- package/dist/chat/agent/resume.d.ts +2 -0
- package/dist/chat/agent/tools.d.ts +1 -0
- package/dist/chat/conversations/history.d.ts +22 -1
- package/dist/chat/conversations/projection.d.ts +6 -0
- package/dist/chat/mcp/client.d.ts +4 -0
- package/dist/chat/mcp/errors.d.ts +22 -1
- package/dist/chat/oauth-response.d.ts +21 -0
- package/dist/chat/plugins/prompt.d.ts +9 -0
- package/dist/chat/plugins/types.d.ts +2 -21
- package/dist/chat/sandbox/snapshot/install.d.ts +6 -0
- package/dist/chat/sandbox/snapshot/profile.d.ts +14 -0
- package/dist/chat/sandbox/snapshot/resolve.d.ts +22 -0
- package/dist/chat/sandbox/snapshot/span.d.ts +2 -0
- package/dist/chat/services/context-compaction-marker.d.ts +1 -0
- package/dist/chat/services/turn-session-record.d.ts +2 -0
- package/dist/chat/state/turn-session.d.ts +2 -0
- package/dist/chat/tools/query-conversation-events.d.ts +1 -1
- package/dist/chat/tools/sandbox/view-image.d.ts +11 -0
- package/dist/chat/tools/types.d.ts +7 -0
- package/dist/{chunk-RYFAVWP3.js → chunk-27KWTFP6.js} +33 -12
- package/dist/{chunk-N3C4XCRE.js → chunk-3GE3F4LT.js} +1 -1
- package/dist/{chunk-XA3VLFPK.js → chunk-5O3JDQDG.js} +154 -20
- package/dist/{chunk-CGXLCMWI.js → chunk-6G45D3YG.js} +114 -10
- package/dist/{chunk-F3SWGSPG.js → chunk-72Z3HNTQ.js} +1 -1
- package/dist/{chunk-DLLW5ITL.js → chunk-AKG4TZNH.js} +2 -2
- package/dist/{chunk-TC4ZEM2D.js → chunk-BFIRIZPH.js} +9 -0
- package/dist/{chunk-OY2PQQ4P.js → chunk-CU7HTGNI.js} +1 -1
- package/dist/{chunk-CY7NXM32.js → chunk-DS27ZBSP.js} +4 -2
- package/dist/{chunk-VBMMMVVQ.js → chunk-FAWBONPC.js} +404 -443
- package/dist/{chunk-7CSVG54J.js → chunk-KU6ALEJC.js} +1 -1
- package/dist/{chunk-H35BDAUS.js → chunk-LN765Y4N.js} +6 -6
- package/dist/{chunk-KNCELO2M.js → chunk-LUMEBOWC.js} +1 -1
- package/dist/{chunk-GDAXWEIJ.js → chunk-NHZEUHKH.js} +514 -344
- package/dist/{chunk-ZYHLNTC2.js → chunk-STUI6NIL.js} +78 -16
- package/dist/{chunk-2YYPXQNQ.js → chunk-W5JEQQEA.js} +1 -1
- package/dist/{chunk-JPKGW6L6.js → chunk-WEK3V5RI.js} +73 -32
- package/dist/{chunk-WLT72MRP.js → chunk-WUNPRM76.js} +1 -1
- package/dist/{chunk-FAEALXEH.js → chunk-YRWJ3KEU.js} +2 -2
- package/dist/{chunk-BFYVIOTS.js → chunk-ZRQUQJUV.js} +12 -2
- package/dist/cli/chat.js +21 -21
- package/dist/cli/check.js +6 -6
- package/dist/cli/main.js +1 -1
- package/dist/cli/plugins.js +11 -11
- package/dist/cli/{snapshot-warmup.js → snapshot-create.js} +17 -19
- package/dist/cli/upgrade.js +5 -5
- package/dist/{db-FH4R5ZQ4.js → db-J2ITKS63.js} +5 -5
- package/dist/handlers/oauth-html.d.ts +4 -2
- package/dist/instrumentation.js +2 -2
- package/dist/nitro.js +2 -2
- package/dist/{oauth-callback-server-C2SSJ3FQ.js → oauth-callback-server-NQYKBT24.js} +15 -15
- package/dist/{runner-NAALFIXM.js → runner-53V5GIQM.js} +10 -10
- package/dist/{skills-UN2JXYYF.js → skills-BU3ZMLVB.js} +6 -6
- package/dist/{validation-EEF4ROHR.js → validation-74EIKKE6.js} +6 -6
- package/dist/version.js +1 -1
- package/package.json +5 -5
- package/dist/chat/sandbox/runtime-dependency-snapshots.d.ts +0 -20
|
@@ -1,31 +1,138 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SANDBOX_WORKSPACE_ROOT,
|
|
3
3
|
getStateAdapter
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KU6ALEJC.js";
|
|
5
5
|
import {
|
|
6
6
|
sleep
|
|
7
7
|
} from "./chunk-4ZNGQH7C.js";
|
|
8
8
|
import {
|
|
9
9
|
toOptionalTrimmed
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DS27ZBSP.js";
|
|
11
11
|
import {
|
|
12
12
|
pluginCatalogRuntime
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CU7HTGNI.js";
|
|
14
14
|
import {
|
|
15
15
|
setSpanAttributes,
|
|
16
16
|
setSpanStatus,
|
|
17
17
|
withSpan
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-72Z3HNTQ.js";
|
|
19
|
+
|
|
20
|
+
// src/chat/sandbox/snapshot/profile.ts
|
|
21
|
+
import { createHash } from "crypto";
|
|
19
22
|
|
|
20
23
|
// src/chat/sandbox/runtime-dependencies.ts
|
|
21
24
|
var GLOBAL_RUNTIME_DEPENDENCIES = [
|
|
22
25
|
{ type: "system", package: "docker" },
|
|
26
|
+
{ type: "system", package: "spal-release" },
|
|
23
27
|
{ type: "system", package: "ripgrep" }
|
|
24
28
|
];
|
|
25
29
|
|
|
26
|
-
// src/chat/sandbox/
|
|
27
|
-
|
|
30
|
+
// src/chat/sandbox/snapshot/profile.ts
|
|
31
|
+
var VERSION = 1;
|
|
32
|
+
var DEFAULT_FLOATING_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
33
|
+
function isExactNpmVersion(version) {
|
|
34
|
+
return /^\d+\.\d+\.\d+(?:[-+][a-z0-9.]+)?$/i.test(version.trim());
|
|
35
|
+
}
|
|
36
|
+
function isFloating(dep) {
|
|
37
|
+
return dep.type === "npm" && !isExactNpmVersion(dep.version);
|
|
38
|
+
}
|
|
39
|
+
function mergeDependencies(dependencies2) {
|
|
40
|
+
const seen = /* @__PURE__ */ new Set();
|
|
41
|
+
const npmVersions = /* @__PURE__ */ new Map();
|
|
42
|
+
const merged = [];
|
|
43
|
+
for (const dependency of dependencies2) {
|
|
44
|
+
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}`;
|
|
45
|
+
if (seen.has(key)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
seen.add(key);
|
|
49
|
+
if (dependency.type === "npm") {
|
|
50
|
+
const version = npmVersions.get(dependency.package);
|
|
51
|
+
if (version !== void 0 && version !== dependency.version) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Conflicting runtime dependency versions for ${dependency.package}: ${version} and ${dependency.version}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
npmVersions.set(dependency.package, dependency.version);
|
|
57
|
+
}
|
|
58
|
+
merged.push(dependency);
|
|
59
|
+
}
|
|
60
|
+
return merged;
|
|
61
|
+
}
|
|
62
|
+
function floatingMaxAgeMs() {
|
|
63
|
+
const raw = process.env.SANDBOX_SNAPSHOT_FLOATING_MAX_AGE_MS;
|
|
64
|
+
if (!raw?.trim()) {
|
|
65
|
+
return DEFAULT_FLOATING_MAX_AGE_MS;
|
|
66
|
+
}
|
|
67
|
+
const parsed = Number.parseInt(raw, 10);
|
|
68
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_FLOATING_MAX_AGE_MS;
|
|
69
|
+
}
|
|
70
|
+
function create(runtime) {
|
|
71
|
+
const dependencies2 = mergeDependencies([
|
|
72
|
+
...GLOBAL_RUNTIME_DEPENDENCIES,
|
|
73
|
+
...pluginCatalogRuntime.getRuntimeDependencies()
|
|
74
|
+
]);
|
|
75
|
+
const postinstall2 = pluginCatalogRuntime.getRuntimePostinstall();
|
|
76
|
+
if (dependencies2.length === 0 && postinstall2.length === 0) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const rebuildEpoch = process.env.SANDBOX_SNAPSHOT_REBUILD_EPOCH?.trim() ?? "";
|
|
80
|
+
const floating = dependencies2.some((dependency) => isFloating(dependency)) || postinstall2.length > 0;
|
|
81
|
+
const hash2 = createHash("sha256").update(
|
|
82
|
+
JSON.stringify({
|
|
83
|
+
version: VERSION,
|
|
84
|
+
runtime,
|
|
85
|
+
rebuildEpoch,
|
|
86
|
+
dependencies: dependencies2,
|
|
87
|
+
postinstall: postinstall2
|
|
88
|
+
})
|
|
89
|
+
).digest("hex");
|
|
90
|
+
return {
|
|
91
|
+
hash: hash2,
|
|
92
|
+
dependencyCount: dependencies2.length,
|
|
93
|
+
floating,
|
|
94
|
+
dependencies: dependencies2,
|
|
95
|
+
postinstall: postinstall2
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function hash(runtime) {
|
|
99
|
+
return create(runtime)?.hash;
|
|
100
|
+
}
|
|
101
|
+
function isStale(profile, createdAtMs) {
|
|
102
|
+
if (!profile.floating) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const maxAgeMs = floatingMaxAgeMs();
|
|
106
|
+
return maxAgeMs === 0 || Date.now() - createdAtMs > maxAgeMs;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/chat/sandbox/snapshot/resolve.ts
|
|
28
110
|
import { Sandbox } from "@vercel/sandbox";
|
|
111
|
+
import { z } from "zod";
|
|
112
|
+
|
|
113
|
+
// src/chat/sandbox/credentials.ts
|
|
114
|
+
function getVercelSandboxCredentials() {
|
|
115
|
+
const token = toOptionalTrimmed(process.env.VERCEL_TOKEN);
|
|
116
|
+
const teamId = toOptionalTrimmed(process.env.VERCEL_TEAM_ID);
|
|
117
|
+
const projectId = toOptionalTrimmed(process.env.VERCEL_PROJECT_ID);
|
|
118
|
+
if (token && teamId && projectId) {
|
|
119
|
+
return { token, teamId, projectId };
|
|
120
|
+
}
|
|
121
|
+
return void 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/chat/sandbox/resources.ts
|
|
125
|
+
function getSandboxResources() {
|
|
126
|
+
const value = process.env.SANDBOX_VCPUS?.trim();
|
|
127
|
+
if (!value || !/^\d+$/.test(value)) {
|
|
128
|
+
return void 0;
|
|
129
|
+
}
|
|
130
|
+
const vcpus = Number(value);
|
|
131
|
+
if (!Number.isSafeInteger(vcpus) || vcpus <= 0) {
|
|
132
|
+
return void 0;
|
|
133
|
+
}
|
|
134
|
+
return { vcpus };
|
|
135
|
+
}
|
|
29
136
|
|
|
30
137
|
// src/chat/sandbox/noninteractive-command.ts
|
|
31
138
|
var NON_INTERACTIVE_ENV = {
|
|
@@ -96,15 +203,215 @@ async function runNonInteractiveCommand(sandbox, input) {
|
|
|
96
203
|
return await sandbox.runCommand(command);
|
|
97
204
|
}
|
|
98
205
|
|
|
99
|
-
// src/chat/sandbox/
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
206
|
+
// src/chat/sandbox/snapshot/span.ts
|
|
207
|
+
async function trace(name, op, attributes, callback) {
|
|
208
|
+
return await withSpan(name, op, {}, callback, attributes);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/chat/sandbox/snapshot/install.ts
|
|
212
|
+
async function runOrThrow(sandbox, params, label) {
|
|
213
|
+
const result = await runNonInteractiveCommand(sandbox, params);
|
|
214
|
+
if (result.exitCode === 0) {
|
|
215
|
+
return;
|
|
106
216
|
}
|
|
107
|
-
|
|
217
|
+
const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
|
|
218
|
+
throw new Error(`${label} failed: ${detail}`);
|
|
219
|
+
}
|
|
220
|
+
async function tryRun(sandbox, params) {
|
|
221
|
+
const result = await runNonInteractiveCommand(sandbox, params);
|
|
222
|
+
return result.exitCode === 0;
|
|
223
|
+
}
|
|
224
|
+
async function installGh(sandbox) {
|
|
225
|
+
const installed = await tryRun(sandbox, {
|
|
226
|
+
cmd: "dnf",
|
|
227
|
+
args: ["install", "-y", "gh"],
|
|
228
|
+
sudo: true
|
|
229
|
+
});
|
|
230
|
+
if (installed) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const repoAdded = await tryRun(sandbox, {
|
|
234
|
+
cmd: "dnf",
|
|
235
|
+
args: [
|
|
236
|
+
"config-manager",
|
|
237
|
+
"addrepo",
|
|
238
|
+
"--from-repofile=https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
239
|
+
],
|
|
240
|
+
sudo: true
|
|
241
|
+
});
|
|
242
|
+
if (!repoAdded) {
|
|
243
|
+
await runOrThrow(
|
|
244
|
+
sandbox,
|
|
245
|
+
{
|
|
246
|
+
cmd: "dnf",
|
|
247
|
+
args: ["install", "-y", "dnf-command(config-manager)"],
|
|
248
|
+
sudo: true
|
|
249
|
+
},
|
|
250
|
+
"dnf install dnf-command(config-manager)"
|
|
251
|
+
);
|
|
252
|
+
await runOrThrow(
|
|
253
|
+
sandbox,
|
|
254
|
+
{
|
|
255
|
+
cmd: "dnf",
|
|
256
|
+
args: [
|
|
257
|
+
"config-manager",
|
|
258
|
+
"--add-repo",
|
|
259
|
+
"https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
260
|
+
],
|
|
261
|
+
sudo: true
|
|
262
|
+
},
|
|
263
|
+
"dnf config-manager --add-repo gh-cli.repo"
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
await runOrThrow(
|
|
267
|
+
sandbox,
|
|
268
|
+
{
|
|
269
|
+
cmd: "dnf",
|
|
270
|
+
args: ["install", "-y", "gh", "--repo", "gh-cli"],
|
|
271
|
+
sudo: true
|
|
272
|
+
},
|
|
273
|
+
"dnf install gh --repo gh-cli"
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
function filePath(url, sha256) {
|
|
277
|
+
let basename = "package.rpm";
|
|
278
|
+
try {
|
|
279
|
+
const candidate = new URL(url).pathname.split("/").filter(Boolean).at(-1);
|
|
280
|
+
if (candidate) {
|
|
281
|
+
basename = candidate;
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
const sanitized = basename.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
286
|
+
return `/tmp/junior-runtime-${sha256.slice(0, 12)}-${sanitized}`;
|
|
287
|
+
}
|
|
288
|
+
async function installUrl(sandbox, dependency) {
|
|
289
|
+
const rpmPath = filePath(dependency.url, dependency.sha256);
|
|
290
|
+
await runOrThrow(
|
|
291
|
+
sandbox,
|
|
292
|
+
{
|
|
293
|
+
cmd: "curl",
|
|
294
|
+
args: ["-fsSL", dependency.url, "-o", rpmPath]
|
|
295
|
+
},
|
|
296
|
+
`curl download ${dependency.url}`
|
|
297
|
+
);
|
|
298
|
+
const checksum = await runNonInteractiveCommand(sandbox, {
|
|
299
|
+
cmd: "sha256sum",
|
|
300
|
+
args: [rpmPath]
|
|
301
|
+
});
|
|
302
|
+
const expected = dependency.sha256;
|
|
303
|
+
const actual = checksum.stdout.trim().split(/\s+/)[0]?.toLowerCase();
|
|
304
|
+
if (checksum.exitCode !== 0) {
|
|
305
|
+
const detail = checksum.stderr.trim() || checksum.stdout.trim() || "command failed";
|
|
306
|
+
throw new Error(`sha256sum failed: ${detail}`);
|
|
307
|
+
}
|
|
308
|
+
if (!actual) {
|
|
309
|
+
throw new Error("sha256sum produced empty output");
|
|
310
|
+
}
|
|
311
|
+
if (actual !== expected) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`checksum mismatch for ${dependency.url}: expected ${expected}, got ${actual}`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
await runOrThrow(
|
|
317
|
+
sandbox,
|
|
318
|
+
{
|
|
319
|
+
cmd: "dnf",
|
|
320
|
+
args: ["install", "-y", rpmPath],
|
|
321
|
+
sudo: true
|
|
322
|
+
},
|
|
323
|
+
`dnf install ${dependency.url}`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
async function dependencies(sandbox, values) {
|
|
327
|
+
const system = values.filter(
|
|
328
|
+
(dependency) => dependency.type === "system"
|
|
329
|
+
);
|
|
330
|
+
const npm = values.filter(
|
|
331
|
+
(dependency) => dependency.type === "npm"
|
|
332
|
+
).map((dependency) => `${dependency.package}@${dependency.version}`);
|
|
333
|
+
if (system.length > 0) {
|
|
334
|
+
await trace(
|
|
335
|
+
"sandbox.snapshot.install_system",
|
|
336
|
+
"sandbox.snapshot.install.system",
|
|
337
|
+
{
|
|
338
|
+
"app.sandbox.snapshot.install.system_count": system.length
|
|
339
|
+
},
|
|
340
|
+
async () => {
|
|
341
|
+
for (const dependency of system) {
|
|
342
|
+
if ("url" in dependency) {
|
|
343
|
+
await installUrl(sandbox, dependency);
|
|
344
|
+
} else if (dependency.package === "gh") {
|
|
345
|
+
await installGh(sandbox);
|
|
346
|
+
} else {
|
|
347
|
+
await runOrThrow(
|
|
348
|
+
sandbox,
|
|
349
|
+
{
|
|
350
|
+
cmd: "dnf",
|
|
351
|
+
args: ["install", "-y", dependency.package],
|
|
352
|
+
sudo: true
|
|
353
|
+
},
|
|
354
|
+
`dnf install ${dependency.package}`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
if (npm.length > 0) {
|
|
362
|
+
await trace(
|
|
363
|
+
"sandbox.snapshot.install_npm",
|
|
364
|
+
"sandbox.snapshot.install.npm",
|
|
365
|
+
{
|
|
366
|
+
"app.sandbox.snapshot.install.npm_count": npm.length
|
|
367
|
+
},
|
|
368
|
+
async () => {
|
|
369
|
+
await runOrThrow(
|
|
370
|
+
sandbox,
|
|
371
|
+
{
|
|
372
|
+
cmd: "npm",
|
|
373
|
+
args: [
|
|
374
|
+
"install",
|
|
375
|
+
"--global",
|
|
376
|
+
"--prefix",
|
|
377
|
+
`${SANDBOX_WORKSPACE_ROOT}/.junior`,
|
|
378
|
+
...npm
|
|
379
|
+
]
|
|
380
|
+
},
|
|
381
|
+
"npm install"
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
async function postinstall(sandbox, commands) {
|
|
388
|
+
if (commands.length === 0) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
await trace(
|
|
392
|
+
"sandbox.snapshot.runtime_postinstall",
|
|
393
|
+
"sandbox.snapshot.runtime_postinstall",
|
|
394
|
+
{
|
|
395
|
+
"app.sandbox.snapshot.runtime_postinstall.count": commands.length
|
|
396
|
+
},
|
|
397
|
+
async () => {
|
|
398
|
+
for (const command of commands) {
|
|
399
|
+
const result = await runNonInteractiveCommand(sandbox, {
|
|
400
|
+
cmd: command.cmd,
|
|
401
|
+
args: command.args,
|
|
402
|
+
login: true,
|
|
403
|
+
pathPrefix: `${SANDBOX_WORKSPACE_ROOT}/.junior/bin:$PATH`,
|
|
404
|
+
...command.sudo !== void 0 ? { sudo: command.sudo } : {}
|
|
405
|
+
});
|
|
406
|
+
if (result.exitCode !== 0) {
|
|
407
|
+
const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
|
|
408
|
+
throw new Error(
|
|
409
|
+
`runtime-postinstall ${command.cmd} failed: ${detail}`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
);
|
|
108
415
|
}
|
|
109
416
|
|
|
110
417
|
// src/chat/sandbox/workspace.ts
|
|
@@ -350,21 +657,21 @@ function bindSandboxFileSystem(fs, signal) {
|
|
|
350
657
|
return fs;
|
|
351
658
|
}
|
|
352
659
|
return {
|
|
353
|
-
readFile(
|
|
660
|
+
readFile(filePath2, options) {
|
|
354
661
|
signal.throwIfAborted();
|
|
355
|
-
return fs.readFile(
|
|
662
|
+
return fs.readFile(filePath2, { ...options, signal });
|
|
356
663
|
},
|
|
357
|
-
writeFile(
|
|
664
|
+
writeFile(filePath2, content, options) {
|
|
358
665
|
signal.throwIfAborted();
|
|
359
|
-
return fs.writeFile(
|
|
666
|
+
return fs.writeFile(filePath2, content, { ...options, signal });
|
|
360
667
|
},
|
|
361
|
-
readdir(
|
|
668
|
+
readdir(filePath2) {
|
|
362
669
|
signal.throwIfAborted();
|
|
363
|
-
return fs.readdir(
|
|
670
|
+
return fs.readdir(filePath2, { signal });
|
|
364
671
|
},
|
|
365
|
-
stat(
|
|
672
|
+
stat(filePath2) {
|
|
366
673
|
signal.throwIfAborted();
|
|
367
|
-
return fs.stat(
|
|
674
|
+
return fs.stat(filePath2, { signal });
|
|
368
675
|
}
|
|
369
676
|
};
|
|
370
677
|
}
|
|
@@ -382,12 +689,12 @@ function createSandboxSession(sandbox, options) {
|
|
|
382
689
|
}
|
|
383
690
|
};
|
|
384
691
|
const fs = {
|
|
385
|
-
readFile: async (
|
|
386
|
-
writeFile: async (
|
|
387
|
-
async () => await fileSystem.writeFile(
|
|
692
|
+
readFile: async (filePath2, readOptions) => await run(async () => await fileSystem.readFile(filePath2, readOptions)),
|
|
693
|
+
writeFile: async (filePath2, content, writeOptions) => await run(
|
|
694
|
+
async () => await fileSystem.writeFile(filePath2, content, writeOptions)
|
|
388
695
|
),
|
|
389
|
-
readdir: async (
|
|
390
|
-
stat: async (
|
|
696
|
+
readdir: async (filePath2, readOptions) => await run(async () => await fileSystem.readdir(filePath2, readOptions)),
|
|
697
|
+
stat: async (filePath2, statOptions) => await run(async () => await fileSystem.stat(filePath2, statOptions))
|
|
391
698
|
};
|
|
392
699
|
return {
|
|
393
700
|
sandboxId: sandbox.name,
|
|
@@ -432,109 +739,43 @@ function createSandboxSession(sandbox, options) {
|
|
|
432
739
|
};
|
|
433
740
|
}
|
|
434
741
|
|
|
435
|
-
// src/chat/sandbox/
|
|
436
|
-
function getSandboxResources() {
|
|
437
|
-
const value = process.env.SANDBOX_VCPUS?.trim();
|
|
438
|
-
if (!value || !/^\d+$/.test(value)) {
|
|
439
|
-
return void 0;
|
|
440
|
-
}
|
|
441
|
-
const vcpus = Number(value);
|
|
442
|
-
if (!Number.isSafeInteger(vcpus) || vcpus <= 0) {
|
|
443
|
-
return void 0;
|
|
444
|
-
}
|
|
445
|
-
return { vcpus };
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// src/chat/sandbox/runtime-dependency-snapshots.ts
|
|
742
|
+
// src/chat/sandbox/snapshot/resolve.ts
|
|
449
743
|
var SNAPSHOT_CACHE_PREFIX = "junior:sandbox_snapshot_profile";
|
|
450
744
|
var SNAPSHOT_LOCK_PREFIX = "junior:sandbox_snapshot_lock";
|
|
451
|
-
var SNAPSHOT_PROFILE_VERSION = 2;
|
|
452
745
|
var SNAPSHOT_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
453
|
-
var
|
|
454
|
-
var
|
|
455
|
-
var
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
746
|
+
var SNAPSHOT_BUILD_LOCK_BUFFER_MS = 30 * 1e3;
|
|
747
|
+
var SNAPSHOT_WAIT_FOR_LOCK_BUFFER_MS = 30 * 1e3;
|
|
748
|
+
var cachedSnapshotSchema = z.object({
|
|
749
|
+
profileHash: z.string(),
|
|
750
|
+
snapshotId: z.string(),
|
|
751
|
+
runtime: z.string(),
|
|
752
|
+
createdAtMs: z.number(),
|
|
753
|
+
dependencyCount: z.number()
|
|
754
|
+
}).strict();
|
|
459
755
|
function profileCacheKey(profileHash) {
|
|
460
756
|
return `${SNAPSHOT_CACHE_PREFIX}:${profileHash}`;
|
|
461
757
|
}
|
|
462
758
|
function profileLockKey(profileHash) {
|
|
463
759
|
return `${SNAPSHOT_LOCK_PREFIX}:${profileHash}`;
|
|
464
760
|
}
|
|
465
|
-
function
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
function parseFloatingDepMaxAgeMs() {
|
|
472
|
-
const raw = process.env.SANDBOX_SNAPSHOT_FLOATING_MAX_AGE_MS;
|
|
473
|
-
if (!raw?.trim()) {
|
|
474
|
-
return DEFAULT_FLOATING_DEP_MAX_AGE_MS;
|
|
475
|
-
}
|
|
476
|
-
const parsed = Number.parseInt(raw, 10);
|
|
477
|
-
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
478
|
-
return DEFAULT_FLOATING_DEP_MAX_AGE_MS;
|
|
479
|
-
}
|
|
480
|
-
return parsed;
|
|
481
|
-
}
|
|
482
|
-
function buildDependencyProfile(runtime) {
|
|
483
|
-
const dependencies = [
|
|
484
|
-
...GLOBAL_RUNTIME_DEPENDENCIES,
|
|
485
|
-
...pluginCatalogRuntime.getRuntimeDependencies()
|
|
486
|
-
];
|
|
487
|
-
const postinstall = pluginCatalogRuntime.getRuntimePostinstall();
|
|
488
|
-
if (dependencies.length === 0 && postinstall.length === 0) {
|
|
761
|
+
async function getCachedSnapshot(profileHash) {
|
|
762
|
+
const state = getStateAdapter();
|
|
763
|
+
await state.connect();
|
|
764
|
+
const raw = await state.get(profileCacheKey(profileHash));
|
|
765
|
+
if (typeof raw !== "string") {
|
|
489
766
|
return null;
|
|
490
767
|
}
|
|
491
|
-
|
|
492
|
-
const hasFloatingVersions = dependencies.some((dep) => hasFloatingSelector(dep)) || postinstall.length > 0;
|
|
493
|
-
const hashInput = JSON.stringify({
|
|
494
|
-
version: SNAPSHOT_PROFILE_VERSION,
|
|
495
|
-
runtime,
|
|
496
|
-
rebuildEpoch,
|
|
497
|
-
dependencies,
|
|
498
|
-
postinstall
|
|
499
|
-
});
|
|
500
|
-
const profileHash = createHash("sha256").update(hashInput).digest("hex");
|
|
501
|
-
return {
|
|
502
|
-
profileHash,
|
|
503
|
-
dependencyCount: dependencies.length,
|
|
504
|
-
hasFloatingVersions,
|
|
505
|
-
dependencies,
|
|
506
|
-
postinstall
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
function getRuntimeDependencyProfileHash(runtime) {
|
|
510
|
-
return buildDependencyProfile(runtime)?.profileHash;
|
|
511
|
-
}
|
|
512
|
-
function shouldRebuildCachedSnapshot(profile, cached) {
|
|
513
|
-
if (!profile.hasFloatingVersions) {
|
|
514
|
-
return false;
|
|
515
|
-
}
|
|
516
|
-
const maxAgeMs = parseFloatingDepMaxAgeMs();
|
|
517
|
-
if (maxAgeMs === 0) {
|
|
518
|
-
return true;
|
|
519
|
-
}
|
|
520
|
-
return Date.now() - cached.createdAtMs > maxAgeMs;
|
|
521
|
-
}
|
|
522
|
-
async function getCachedSnapshot(profileHash) {
|
|
768
|
+
let value;
|
|
523
769
|
try {
|
|
524
|
-
|
|
525
|
-
await state.connect();
|
|
526
|
-
const raw = await state.get(profileCacheKey(profileHash));
|
|
527
|
-
if (typeof raw !== "string") {
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
const parsed = JSON.parse(raw);
|
|
531
|
-
if (!parsed || typeof parsed !== "object" || typeof parsed.profileHash !== "string" || typeof parsed.snapshotId !== "string" || typeof parsed.runtime !== "string" || typeof parsed.createdAtMs !== "number" || typeof parsed.dependencyCount !== "number") {
|
|
532
|
-
return null;
|
|
533
|
-
}
|
|
534
|
-
return parsed;
|
|
770
|
+
value = JSON.parse(raw);
|
|
535
771
|
} catch {
|
|
536
|
-
|
|
772
|
+
throw new Error(`Invalid cached sandbox snapshot for ${profileHash}`);
|
|
537
773
|
}
|
|
774
|
+
const parsed = cachedSnapshotSchema.safeParse(value);
|
|
775
|
+
if (!parsed.success || parsed.data.profileHash !== profileHash) {
|
|
776
|
+
throw new Error(`Invalid cached sandbox snapshot for ${profileHash}`);
|
|
777
|
+
}
|
|
778
|
+
return parsed.data;
|
|
538
779
|
}
|
|
539
780
|
async function setCachedSnapshot(entry) {
|
|
540
781
|
const state = getStateAdapter();
|
|
@@ -545,280 +786,13 @@ async function setCachedSnapshot(entry) {
|
|
|
545
786
|
SNAPSHOT_CACHE_TTL_MS
|
|
546
787
|
);
|
|
547
788
|
}
|
|
548
|
-
async function
|
|
549
|
-
return await
|
|
550
|
-
}
|
|
551
|
-
async function runOrThrow(sandbox, params, label) {
|
|
552
|
-
const result = await runNonInteractiveCommand(sandbox, params);
|
|
553
|
-
if (result.exitCode === 0) {
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
const stderr = result.stderr.trim();
|
|
557
|
-
const stdout = result.stdout.trim();
|
|
558
|
-
const detail = stderr || stdout || "command failed";
|
|
559
|
-
throw new Error(`${label} failed: ${detail}`);
|
|
560
|
-
}
|
|
561
|
-
async function tryRun(sandbox, params) {
|
|
562
|
-
const result = await runNonInteractiveCommand(sandbox, params);
|
|
563
|
-
if (result.exitCode === 0) {
|
|
564
|
-
return { ok: true };
|
|
565
|
-
}
|
|
566
|
-
const stderr = result.stderr.trim();
|
|
567
|
-
const stdout = result.stdout.trim();
|
|
568
|
-
return { ok: false, detail: stderr || stdout || "command failed" };
|
|
569
|
-
}
|
|
570
|
-
async function installGhCliViaDnf(sandbox) {
|
|
571
|
-
const direct = await tryRun(sandbox, {
|
|
572
|
-
cmd: "dnf",
|
|
573
|
-
args: ["install", "-y", "gh"],
|
|
574
|
-
sudo: true
|
|
575
|
-
});
|
|
576
|
-
if (direct.ok) {
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
const dnf5Repo = await tryRun(sandbox, {
|
|
580
|
-
cmd: "dnf",
|
|
581
|
-
args: [
|
|
582
|
-
"config-manager",
|
|
583
|
-
"addrepo",
|
|
584
|
-
"--from-repofile=https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
585
|
-
],
|
|
586
|
-
sudo: true
|
|
587
|
-
});
|
|
588
|
-
if (!dnf5Repo.ok) {
|
|
589
|
-
await runOrThrow(
|
|
590
|
-
sandbox,
|
|
591
|
-
{
|
|
592
|
-
cmd: "dnf",
|
|
593
|
-
args: ["install", "-y", "dnf-command(config-manager)"],
|
|
594
|
-
sudo: true
|
|
595
|
-
},
|
|
596
|
-
"dnf install dnf-command(config-manager)"
|
|
597
|
-
);
|
|
598
|
-
await runOrThrow(
|
|
599
|
-
sandbox,
|
|
600
|
-
{
|
|
601
|
-
cmd: "dnf",
|
|
602
|
-
args: [
|
|
603
|
-
"config-manager",
|
|
604
|
-
"--add-repo",
|
|
605
|
-
"https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
606
|
-
],
|
|
607
|
-
sudo: true
|
|
608
|
-
},
|
|
609
|
-
"dnf config-manager --add-repo gh-cli.repo"
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
await runOrThrow(
|
|
613
|
-
sandbox,
|
|
614
|
-
{
|
|
615
|
-
cmd: "dnf",
|
|
616
|
-
args: ["install", "-y", "gh", "--repo", "gh-cli"],
|
|
617
|
-
sudo: true
|
|
618
|
-
},
|
|
619
|
-
"dnf install gh --repo gh-cli"
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
function runtimeDependencyFilePath(url, sha256) {
|
|
623
|
-
let urlBasename = "package.rpm";
|
|
624
|
-
try {
|
|
625
|
-
const pathname = new URL(url).pathname;
|
|
626
|
-
const segments = pathname.split("/").filter(Boolean);
|
|
627
|
-
const candidate = segments[segments.length - 1];
|
|
628
|
-
if (candidate) {
|
|
629
|
-
urlBasename = candidate;
|
|
630
|
-
}
|
|
631
|
-
} catch {
|
|
632
|
-
}
|
|
633
|
-
const sanitizedBasename = urlBasename.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
634
|
-
return `/tmp/junior-runtime-${sha256.slice(0, 12)}-${sanitizedBasename}`;
|
|
635
|
-
}
|
|
636
|
-
async function downloadVerifiedRuntimeDependency(sandbox, url, sha256) {
|
|
637
|
-
const filePath = runtimeDependencyFilePath(url, sha256);
|
|
638
|
-
await runOrThrow(
|
|
639
|
-
sandbox,
|
|
640
|
-
{
|
|
641
|
-
cmd: "curl",
|
|
642
|
-
args: ["-fsSL", url, "-o", filePath]
|
|
643
|
-
},
|
|
644
|
-
`curl download ${url}`
|
|
645
|
-
);
|
|
646
|
-
const checksumResult = await runNonInteractiveCommand(sandbox, {
|
|
647
|
-
cmd: "sha256sum",
|
|
648
|
-
args: [filePath]
|
|
649
|
-
});
|
|
650
|
-
const checksumStdout = checksumResult.stdout.trim();
|
|
651
|
-
const checksumStderr = checksumResult.stderr.trim();
|
|
652
|
-
if (checksumResult.exitCode !== 0) {
|
|
653
|
-
throw new Error(
|
|
654
|
-
`sha256sum failed: ${checksumStderr || checksumStdout || "command failed"}`
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
const actualChecksum = checksumStdout.split(/\s+/)[0]?.toLowerCase();
|
|
658
|
-
if (!actualChecksum) {
|
|
659
|
-
throw new Error("sha256sum produced empty output");
|
|
660
|
-
}
|
|
661
|
-
if (actualChecksum !== sha256) {
|
|
662
|
-
throw new Error(
|
|
663
|
-
`checksum mismatch for ${url}: expected ${sha256}, got ${actualChecksum}`
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
|
-
return filePath;
|
|
667
|
-
}
|
|
668
|
-
async function installRipgrep(sandbox) {
|
|
669
|
-
const archivePath = await downloadVerifiedRuntimeDependency(
|
|
670
|
-
sandbox,
|
|
671
|
-
RIPGREP_ARCHIVE_URL,
|
|
672
|
-
RIPGREP_ARCHIVE_SHA256
|
|
673
|
-
);
|
|
674
|
-
const extractedDir = `/tmp/ripgrep-${RIPGREP_VERSION}-x86_64-unknown-linux-musl`;
|
|
675
|
-
await runOrThrow(
|
|
676
|
-
sandbox,
|
|
677
|
-
{
|
|
678
|
-
cmd: "tar",
|
|
679
|
-
args: ["-xzf", archivePath, "-C", "/tmp"]
|
|
680
|
-
},
|
|
681
|
-
"extract ripgrep"
|
|
682
|
-
);
|
|
683
|
-
await runOrThrow(
|
|
684
|
-
sandbox,
|
|
685
|
-
{
|
|
686
|
-
cmd: "mkdir",
|
|
687
|
-
args: ["-p", `${SANDBOX_WORKSPACE_ROOT}/.junior/bin`]
|
|
688
|
-
},
|
|
689
|
-
"create sandbox bin directory"
|
|
690
|
-
);
|
|
691
|
-
await runOrThrow(
|
|
692
|
-
sandbox,
|
|
693
|
-
{
|
|
694
|
-
cmd: "install",
|
|
695
|
-
args: [
|
|
696
|
-
"-m",
|
|
697
|
-
"0755",
|
|
698
|
-
`${extractedDir}/rg`,
|
|
699
|
-
`${SANDBOX_WORKSPACE_ROOT}/.junior/bin/rg`
|
|
700
|
-
]
|
|
701
|
-
},
|
|
702
|
-
"install ripgrep"
|
|
703
|
-
);
|
|
704
|
-
}
|
|
705
|
-
async function installRuntimeDependencies(sandbox, deps) {
|
|
706
|
-
const systemDeps = deps.filter(
|
|
707
|
-
(dep) => dep.type === "system"
|
|
708
|
-
);
|
|
709
|
-
const npmPackages = deps.filter(
|
|
710
|
-
(dep) => dep.type === "npm"
|
|
711
|
-
).map((dep) => `${dep.package}@${dep.version}`);
|
|
712
|
-
if (systemDeps.length > 0) {
|
|
713
|
-
await withSnapshotSpan(
|
|
714
|
-
"sandbox.snapshot.install_system",
|
|
715
|
-
"sandbox.snapshot.install.system",
|
|
716
|
-
{
|
|
717
|
-
"app.sandbox.snapshot.install.system_count": systemDeps.length
|
|
718
|
-
},
|
|
719
|
-
async () => {
|
|
720
|
-
for (const dep of systemDeps) {
|
|
721
|
-
if ("url" in dep) {
|
|
722
|
-
const rpmPath = await downloadVerifiedRuntimeDependency(
|
|
723
|
-
sandbox,
|
|
724
|
-
dep.url,
|
|
725
|
-
dep.sha256
|
|
726
|
-
);
|
|
727
|
-
await runOrThrow(
|
|
728
|
-
sandbox,
|
|
729
|
-
{
|
|
730
|
-
cmd: "dnf",
|
|
731
|
-
args: ["install", "-y", rpmPath],
|
|
732
|
-
sudo: true
|
|
733
|
-
},
|
|
734
|
-
`dnf install ${dep.url}`
|
|
735
|
-
);
|
|
736
|
-
continue;
|
|
737
|
-
}
|
|
738
|
-
if (dep.package === "ripgrep") {
|
|
739
|
-
await installRipgrep(sandbox);
|
|
740
|
-
continue;
|
|
741
|
-
}
|
|
742
|
-
if (dep.package === "gh") {
|
|
743
|
-
await installGhCliViaDnf(sandbox);
|
|
744
|
-
continue;
|
|
745
|
-
}
|
|
746
|
-
await runOrThrow(
|
|
747
|
-
sandbox,
|
|
748
|
-
{
|
|
749
|
-
cmd: "dnf",
|
|
750
|
-
args: ["install", "-y", dep.package],
|
|
751
|
-
sudo: true
|
|
752
|
-
},
|
|
753
|
-
`dnf install ${dep.package}`
|
|
754
|
-
);
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
);
|
|
758
|
-
}
|
|
759
|
-
if (npmPackages.length > 0) {
|
|
760
|
-
await withSnapshotSpan(
|
|
761
|
-
"sandbox.snapshot.install_npm",
|
|
762
|
-
"sandbox.snapshot.install.npm",
|
|
763
|
-
{
|
|
764
|
-
"app.sandbox.snapshot.install.npm_count": npmPackages.length
|
|
765
|
-
},
|
|
766
|
-
async () => {
|
|
767
|
-
await runOrThrow(
|
|
768
|
-
sandbox,
|
|
769
|
-
{
|
|
770
|
-
cmd: "npm",
|
|
771
|
-
args: [
|
|
772
|
-
"install",
|
|
773
|
-
"--global",
|
|
774
|
-
"--prefix",
|
|
775
|
-
`${SANDBOX_WORKSPACE_ROOT}/.junior`,
|
|
776
|
-
...npmPackages
|
|
777
|
-
]
|
|
778
|
-
},
|
|
779
|
-
"npm install"
|
|
780
|
-
);
|
|
781
|
-
}
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
async function runRuntimePostinstall(sandbox, commands) {
|
|
786
|
-
if (commands.length === 0) {
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
await withSnapshotSpan(
|
|
790
|
-
"sandbox.snapshot.runtime_postinstall",
|
|
791
|
-
"sandbox.snapshot.runtime_postinstall",
|
|
792
|
-
{
|
|
793
|
-
"app.sandbox.snapshot.runtime_postinstall.count": commands.length
|
|
794
|
-
},
|
|
795
|
-
async () => {
|
|
796
|
-
for (const command of commands) {
|
|
797
|
-
const result = await runNonInteractiveCommand(sandbox, {
|
|
798
|
-
cmd: command.cmd,
|
|
799
|
-
args: command.args,
|
|
800
|
-
login: true,
|
|
801
|
-
pathPrefix: `${SANDBOX_WORKSPACE_ROOT}/.junior/bin:$PATH`,
|
|
802
|
-
...command.sudo !== void 0 ? { sudo: command.sudo } : {}
|
|
803
|
-
});
|
|
804
|
-
if (result.exitCode === 0) {
|
|
805
|
-
continue;
|
|
806
|
-
}
|
|
807
|
-
const stderr = result.stderr.trim();
|
|
808
|
-
const stdout = result.stdout.trim();
|
|
809
|
-
const detail = stderr || stdout || "command failed";
|
|
810
|
-
throw new Error(`runtime-postinstall ${command.cmd} failed: ${detail}`);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
);
|
|
814
|
-
}
|
|
815
|
-
async function createDependencySnapshot(profile, runtime, timeoutMs) {
|
|
816
|
-
return await withSnapshotSpan(
|
|
789
|
+
async function build(value, runtime, timeoutMs) {
|
|
790
|
+
return await trace(
|
|
817
791
|
"sandbox.snapshot.build",
|
|
818
792
|
"sandbox.snapshot.build",
|
|
819
793
|
{
|
|
820
794
|
"app.sandbox.runtime": runtime,
|
|
821
|
-
"app.sandbox.snapshot.dependency_count":
|
|
795
|
+
"app.sandbox.snapshot.dependency_count": value.dependencyCount
|
|
822
796
|
},
|
|
823
797
|
async () => {
|
|
824
798
|
const sandboxCredentials = getVercelSandboxCredentials();
|
|
@@ -832,13 +806,13 @@ async function createDependencySnapshot(profile, runtime, timeoutMs) {
|
|
|
832
806
|
})
|
|
833
807
|
);
|
|
834
808
|
try {
|
|
835
|
-
await
|
|
836
|
-
await
|
|
837
|
-
return await
|
|
809
|
+
await dependencies(sandbox, value.dependencies);
|
|
810
|
+
await postinstall(sandbox, value.postinstall);
|
|
811
|
+
return await trace(
|
|
838
812
|
"sandbox.snapshot.capture",
|
|
839
813
|
"sandbox.snapshot.capture",
|
|
840
814
|
{
|
|
841
|
-
"app.sandbox.snapshot.dependency_count":
|
|
815
|
+
"app.sandbox.snapshot.dependency_count": value.dependencyCount
|
|
842
816
|
},
|
|
843
817
|
async () => {
|
|
844
818
|
const snapshot = await sandbox.snapshot();
|
|
@@ -854,40 +828,35 @@ async function createDependencySnapshot(profile, runtime, timeoutMs) {
|
|
|
854
828
|
}
|
|
855
829
|
);
|
|
856
830
|
}
|
|
857
|
-
async function withBuildLock(profileHash, callback, canUseCachedSnapshot,
|
|
831
|
+
async function withBuildLock(profileHash, timeoutMs, callback, canUseCachedSnapshot, onWaitingForLock) {
|
|
858
832
|
const state = getStateAdapter();
|
|
859
833
|
await state.connect();
|
|
860
834
|
const lockKey = profileLockKey(profileHash);
|
|
861
|
-
const
|
|
835
|
+
const lockTtlMs = timeoutMs + SNAPSHOT_BUILD_LOCK_BUFFER_MS;
|
|
836
|
+
const tryAcquireLock = async () => await state.acquireLock(lockKey, lockTtlMs);
|
|
862
837
|
let lock = await tryAcquireLock();
|
|
863
838
|
if (lock) {
|
|
864
839
|
try {
|
|
865
|
-
|
|
866
|
-
return {
|
|
867
|
-
snapshotId: result.snapshotId,
|
|
868
|
-
source: result.source,
|
|
869
|
-
waitedForLock: false
|
|
870
|
-
};
|
|
840
|
+
return await callback();
|
|
871
841
|
} finally {
|
|
872
842
|
await state.releaseLock(lock);
|
|
873
843
|
}
|
|
874
844
|
}
|
|
875
|
-
return await
|
|
845
|
+
return await trace(
|
|
876
846
|
"sandbox.snapshot.lock_wait",
|
|
877
847
|
"sandbox.snapshot.lock_wait",
|
|
878
848
|
{
|
|
879
849
|
"app.sandbox.snapshot.profile_hash": profileHash
|
|
880
850
|
},
|
|
881
851
|
async () => {
|
|
882
|
-
await
|
|
883
|
-
const waitUntil = Date.now() +
|
|
852
|
+
await onWaitingForLock?.();
|
|
853
|
+
const waitUntil = Date.now() + lockTtlMs + SNAPSHOT_WAIT_FOR_LOCK_BUFFER_MS;
|
|
884
854
|
while (Date.now() < waitUntil) {
|
|
885
855
|
const cached2 = await getCachedSnapshot(profileHash);
|
|
886
856
|
if (cached2?.snapshotId && canUseCachedSnapshot(cached2)) {
|
|
887
857
|
return {
|
|
888
858
|
snapshotId: cached2.snapshotId,
|
|
889
|
-
source: "
|
|
890
|
-
waitedForLock: true
|
|
859
|
+
source: "cache_hit_after_lock_wait"
|
|
891
860
|
};
|
|
892
861
|
}
|
|
893
862
|
lock = await tryAcquireLock();
|
|
@@ -896,8 +865,7 @@ async function withBuildLock(profileHash, callback, canUseCachedSnapshot, hooks)
|
|
|
896
865
|
const result = await callback();
|
|
897
866
|
return {
|
|
898
867
|
snapshotId: result.snapshotId,
|
|
899
|
-
source: result.source
|
|
900
|
-
waitedForLock: true
|
|
868
|
+
source: result.source === "built" ? "built" : "cache_hit_after_lock_wait"
|
|
901
869
|
};
|
|
902
870
|
} finally {
|
|
903
871
|
await state.releaseLock(lock);
|
|
@@ -909,22 +877,18 @@ async function withBuildLock(profileHash, callback, canUseCachedSnapshot, hooks)
|
|
|
909
877
|
if (cached?.snapshotId && canUseCachedSnapshot(cached)) {
|
|
910
878
|
return {
|
|
911
879
|
snapshotId: cached.snapshotId,
|
|
912
|
-
source: "
|
|
913
|
-
waitedForLock: true
|
|
880
|
+
source: "cache_hit_after_lock_wait"
|
|
914
881
|
};
|
|
915
882
|
}
|
|
916
883
|
throw new Error("Timed out waiting for snapshot build lock");
|
|
917
884
|
}
|
|
918
885
|
);
|
|
919
886
|
}
|
|
920
|
-
function toResolveOutcome(forceRebuild, source
|
|
887
|
+
function toResolveOutcome(forceRebuild, source) {
|
|
921
888
|
if (source === "built") {
|
|
922
889
|
return forceRebuild ? "forced_rebuild" : "rebuilt";
|
|
923
890
|
}
|
|
924
|
-
|
|
925
|
-
return "cache_hit_after_lock_wait";
|
|
926
|
-
}
|
|
927
|
-
return "cache_hit";
|
|
891
|
+
return source;
|
|
928
892
|
}
|
|
929
893
|
function getRebuildReason(params) {
|
|
930
894
|
if (params.forceRebuild) {
|
|
@@ -938,8 +902,8 @@ function getRebuildReason(params) {
|
|
|
938
902
|
}
|
|
939
903
|
return void 0;
|
|
940
904
|
}
|
|
941
|
-
async function
|
|
942
|
-
return await
|
|
905
|
+
async function resolve(params) {
|
|
906
|
+
return await trace(
|
|
943
907
|
"sandbox.snapshot.resolve",
|
|
944
908
|
"sandbox.snapshot.resolve",
|
|
945
909
|
{
|
|
@@ -948,25 +912,24 @@ async function resolveRuntimeDependencySnapshot(params) {
|
|
|
948
912
|
},
|
|
949
913
|
async () => {
|
|
950
914
|
await params.onProgress?.("resolve_start");
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
if (!profile) {
|
|
915
|
+
const currentProfile = create(params.runtime);
|
|
916
|
+
if (!currentProfile) {
|
|
954
917
|
return {
|
|
955
918
|
dependencyCount: 0,
|
|
956
919
|
cacheHit: false,
|
|
957
920
|
resolveOutcome: "no_profile"
|
|
958
921
|
};
|
|
959
922
|
}
|
|
960
|
-
const cached = await getCachedSnapshot(
|
|
923
|
+
const cached = await getCachedSnapshot(currentProfile.hash);
|
|
961
924
|
const cachedNeedsRebuild = Boolean(
|
|
962
|
-
cached?.snapshotId &&
|
|
925
|
+
cached?.snapshotId && isStale(currentProfile, cached.createdAtMs)
|
|
963
926
|
);
|
|
964
927
|
if (!params.forceRebuild && cached?.snapshotId && !cachedNeedsRebuild) {
|
|
965
928
|
await params.onProgress?.("cache_hit");
|
|
966
929
|
return {
|
|
967
930
|
snapshotId: cached.snapshotId,
|
|
968
|
-
profileHash:
|
|
969
|
-
dependencyCount:
|
|
931
|
+
profileHash: currentProfile.hash,
|
|
932
|
+
dependencyCount: currentProfile.dependencyCount,
|
|
970
933
|
cacheHit: true,
|
|
971
934
|
resolveOutcome: "cache_hit"
|
|
972
935
|
};
|
|
@@ -982,60 +945,58 @@ async function resolveRuntimeDependencySnapshot(params) {
|
|
|
982
945
|
if (params.staleSnapshotId) {
|
|
983
946
|
return candidate.snapshotId !== params.staleSnapshotId;
|
|
984
947
|
}
|
|
985
|
-
return candidate.
|
|
948
|
+
return candidate.snapshotId !== cached?.snapshotId;
|
|
986
949
|
}
|
|
987
|
-
return !
|
|
950
|
+
return !isStale(currentProfile, candidate.createdAtMs);
|
|
988
951
|
};
|
|
989
952
|
const lockResult = await withBuildLock(
|
|
990
|
-
|
|
953
|
+
currentProfile.hash,
|
|
954
|
+
params.timeoutMs,
|
|
991
955
|
async () => {
|
|
992
|
-
const latest = await getCachedSnapshot(
|
|
956
|
+
const latest = await getCachedSnapshot(currentProfile.hash);
|
|
993
957
|
if (latest?.snapshotId && canUseCachedSnapshot(latest)) {
|
|
994
958
|
await params.onProgress?.("cache_hit");
|
|
995
959
|
return {
|
|
996
960
|
snapshotId: latest.snapshotId,
|
|
997
|
-
source: "
|
|
961
|
+
source: "cache_hit"
|
|
998
962
|
};
|
|
999
963
|
}
|
|
1000
964
|
await params.onProgress?.("building_snapshot");
|
|
1001
|
-
const nextSnapshotId = await
|
|
1002
|
-
|
|
965
|
+
const nextSnapshotId = await build(
|
|
966
|
+
currentProfile,
|
|
1003
967
|
params.runtime,
|
|
1004
968
|
params.timeoutMs
|
|
1005
969
|
);
|
|
1006
970
|
await setCachedSnapshot({
|
|
1007
|
-
profileHash:
|
|
971
|
+
profileHash: currentProfile.hash,
|
|
1008
972
|
snapshotId: nextSnapshotId,
|
|
1009
973
|
runtime: params.runtime,
|
|
1010
974
|
createdAtMs: Date.now(),
|
|
1011
|
-
dependencyCount:
|
|
975
|
+
dependencyCount: currentProfile.dependencyCount
|
|
1012
976
|
});
|
|
1013
977
|
await params.onProgress?.("build_complete");
|
|
1014
978
|
return { snapshotId: nextSnapshotId, source: "built" };
|
|
1015
979
|
},
|
|
1016
980
|
canUseCachedSnapshot,
|
|
1017
|
-
{
|
|
1018
|
-
|
|
1019
|
-
await params.onProgress?.("waiting_for_lock");
|
|
1020
|
-
}
|
|
981
|
+
async () => {
|
|
982
|
+
await params.onProgress?.("waiting_for_lock");
|
|
1021
983
|
}
|
|
1022
984
|
);
|
|
1023
985
|
return {
|
|
1024
986
|
snapshotId: lockResult.snapshotId,
|
|
1025
|
-
profileHash:
|
|
1026
|
-
dependencyCount:
|
|
987
|
+
profileHash: currentProfile.hash,
|
|
988
|
+
dependencyCount: currentProfile.dependencyCount,
|
|
1027
989
|
cacheHit: lockResult.source !== "built",
|
|
1028
990
|
resolveOutcome: toResolveOutcome(
|
|
1029
991
|
Boolean(params.forceRebuild),
|
|
1030
|
-
lockResult.source
|
|
1031
|
-
lockResult.waitedForLock
|
|
992
|
+
lockResult.source
|
|
1032
993
|
),
|
|
1033
994
|
...rebuildReason ? { rebuildReason } : {}
|
|
1034
995
|
};
|
|
1035
996
|
}
|
|
1036
997
|
);
|
|
1037
998
|
}
|
|
1038
|
-
function
|
|
999
|
+
function isMissingError(error) {
|
|
1039
1000
|
const searchable = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
1040
1001
|
return searchable.includes("snapshot") && (searchable.includes("not found") || searchable.includes("unknown") || searchable.includes("404"));
|
|
1041
1002
|
}
|
|
@@ -1052,10 +1013,10 @@ export {
|
|
|
1052
1013
|
buildNonInteractiveShellScript,
|
|
1053
1014
|
runNonInteractiveCommand,
|
|
1054
1015
|
getSandboxResources,
|
|
1055
|
-
|
|
1016
|
+
create,
|
|
1017
|
+
hash,
|
|
1056
1018
|
bindSandboxFileSystem,
|
|
1057
1019
|
createSandboxSession,
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
isSnapshotMissingError
|
|
1020
|
+
resolve,
|
|
1021
|
+
isMissingError
|
|
1061
1022
|
};
|