@sentry/junior 0.116.1 → 0.118.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-J2FB7XD6.js} +8 -10
- 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/stats.d.ts +22 -0
- package/dist/api/schema.d.ts +2 -0
- package/dist/api/schema.js +7 -3
- package/dist/api/stats.d.ts +16 -0
- package/dist/api/user-pages/routes.d.ts +4 -0
- package/dist/api.js +192 -18
- package/dist/app.js +53 -46
- package/dist/{catalog-runtime-WMPC4J4U.js → catalog-runtime-UHHLU53I.js} +5 -5
- package/dist/chat/agent/prompt.d.ts +6 -2
- 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/plugins/user-pages.d.ts +15 -0
- package/dist/chat/plugins/viewer-actors.d.ts +6 -0
- package/dist/chat/prompt.d.ts +1 -2
- package/dist/chat/sandbox/paths.d.ts +2 -0
- package/dist/chat/sandbox/sandbox.d.ts +3 -1
- 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/sandbox/workspace.d.ts +1 -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/skills.d.ts +1 -2
- package/dist/chat/state/turn-session.d.ts +2 -0
- package/dist/chat/tools/index.d.ts +4 -1
- package/dist/chat/tools/query-conversation-events.d.ts +1 -1
- package/dist/chat/tools/sandbox/file-utils.d.ts +11 -0
- package/dist/chat/tools/sandbox/find-files.d.ts +2 -1
- package/dist/chat/tools/sandbox/grep.d.ts +2 -1
- package/dist/chat/tools/sandbox/view-image.d.ts +11 -0
- package/dist/chat/tools/types.d.ts +7 -0
- package/dist/{chunk-XA3VLFPK.js → chunk-3LGCVRBQ.js} +154 -20
- package/dist/{chunk-JPKGW6L6.js → chunk-3MUSSFMP.js} +78 -39
- package/dist/{chunk-7CSVG54J.js → chunk-3N3FMCKN.js} +30 -2
- package/dist/{chunk-VBMMMVVQ.js → chunk-4LPPH7Y6.js} +405 -446
- package/dist/{chunk-2YYPXQNQ.js → chunk-4SOQPUF4.js} +1 -1
- package/dist/{chunk-H35BDAUS.js → chunk-6BGUAOYP.js} +8 -8
- package/dist/{chunk-GDAXWEIJ.js → chunk-6K2QE2UI.js} +731 -424
- package/dist/{chunk-DLLW5ITL.js → chunk-7GADTCNS.js} +30 -9
- package/dist/{chunk-TC4ZEM2D.js → chunk-7SM3P4XR.js} +28 -1
- package/dist/{chunk-WKU7TY22.js → chunk-FIANEE7Z.js} +3 -1
- package/dist/{chunk-RXRQJF7Y.js → chunk-GCGJGSQL.js} +21 -0
- package/dist/{chunk-ZYHLNTC2.js → chunk-GT2ZUALJ.js} +584 -25
- package/dist/{chunk-KNCELO2M.js → chunk-HN6LFYG3.js} +1 -1
- package/dist/{chunk-N3C4XCRE.js → chunk-HWUBBHLK.js} +1 -1
- package/dist/{chunk-BFYVIOTS.js → chunk-KC5EE27Y.js} +13 -3
- package/dist/chunk-L72GDKSN.js +37 -0
- package/dist/{chunk-F3SWGSPG.js → chunk-MLZFK4KP.js} +1 -1
- package/dist/{chunk-RYFAVWP3.js → chunk-PJPKHGLG.js} +34 -15
- package/dist/{chunk-CY7NXM32.js → chunk-PWRFTDTY.js} +4 -2
- package/dist/{chunk-WLT72MRP.js → chunk-QWCF4RAC.js} +1 -1
- package/dist/{chunk-CGXLCMWI.js → chunk-S6R23MFB.js} +117 -17
- package/dist/{chunk-OY2PQQ4P.js → chunk-UXLYAVYL.js} +1 -1
- package/dist/cli/chat.js +23 -24
- package/dist/cli/check.js +6 -6
- package/dist/cli/main.js +1 -1
- package/dist/cli/plugins.js +12 -14
- package/dist/cli/{snapshot-warmup.js → snapshot-create.js} +17 -20
- package/dist/cli/upgrade.js +7 -7
- package/dist/db/schema/stats.d.ts +93 -0
- package/dist/db/schema.d.ts +94 -1
- package/dist/{db-FH4R5ZQ4.js → db-PMRHFSYL.js} +6 -6
- package/dist/handlers/oauth-html.d.ts +4 -2
- package/dist/instrumentation.js +2 -2
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-C2SSJ3FQ.js → oauth-callback-server-QJUC7S4X.js} +15 -17
- package/dist/{plugins-IUJA4VBU.js → plugins-HW4VWW6G.js} +1 -1
- package/dist/{runner-NAALFIXM.js → runner-PZMV6JQW.js} +10 -12
- package/dist/{skills-UN2JXYYF.js → skills-W4JETYSV.js} +6 -6
- package/dist/stats.d.ts +18 -0
- package/dist/{validation-EEF4ROHR.js → validation-IJLNHZSU.js} +6 -6
- package/dist/version.js +1 -1
- package/migrations/0011_sour_golden_guardian.sql +8 -0
- package/migrations/meta/0011_snapshot.json +1195 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +5 -5
- package/dist/chat/sandbox/runtime-dependency-snapshots.d.ts +0 -20
- package/dist/chunk-4ZNGQH7C.js +0 -25
- package/dist/chunk-FAEALXEH.js +0 -517
|
@@ -1,31 +1,136 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SANDBOX_WORKSPACE_ROOT,
|
|
3
|
-
getStateAdapter
|
|
4
|
-
} from "./chunk-7CSVG54J.js";
|
|
5
|
-
import {
|
|
3
|
+
getStateAdapter,
|
|
6
4
|
sleep
|
|
7
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-3N3FMCKN.js";
|
|
8
6
|
import {
|
|
9
7
|
toOptionalTrimmed
|
|
10
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PWRFTDTY.js";
|
|
11
9
|
import {
|
|
12
10
|
pluginCatalogRuntime
|
|
13
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-UXLYAVYL.js";
|
|
14
12
|
import {
|
|
15
13
|
setSpanAttributes,
|
|
16
14
|
setSpanStatus,
|
|
17
15
|
withSpan
|
|
18
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-MLZFK4KP.js";
|
|
17
|
+
|
|
18
|
+
// src/chat/sandbox/snapshot/profile.ts
|
|
19
|
+
import { createHash } from "crypto";
|
|
19
20
|
|
|
20
21
|
// src/chat/sandbox/runtime-dependencies.ts
|
|
21
22
|
var GLOBAL_RUNTIME_DEPENDENCIES = [
|
|
22
23
|
{ type: "system", package: "docker" },
|
|
24
|
+
{ type: "system", package: "spal-release" },
|
|
23
25
|
{ type: "system", package: "ripgrep" }
|
|
24
26
|
];
|
|
25
27
|
|
|
26
|
-
// src/chat/sandbox/
|
|
27
|
-
|
|
28
|
+
// src/chat/sandbox/snapshot/profile.ts
|
|
29
|
+
var VERSION = 1;
|
|
30
|
+
var DEFAULT_FLOATING_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
31
|
+
function isExactNpmVersion(version) {
|
|
32
|
+
return /^\d+\.\d+\.\d+(?:[-+][a-z0-9.]+)?$/i.test(version.trim());
|
|
33
|
+
}
|
|
34
|
+
function isFloating(dep) {
|
|
35
|
+
return dep.type === "npm" && !isExactNpmVersion(dep.version);
|
|
36
|
+
}
|
|
37
|
+
function mergeDependencies(dependencies2) {
|
|
38
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39
|
+
const npmVersions = /* @__PURE__ */ new Map();
|
|
40
|
+
const merged = [];
|
|
41
|
+
for (const dependency of dependencies2) {
|
|
42
|
+
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}`;
|
|
43
|
+
if (seen.has(key)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
seen.add(key);
|
|
47
|
+
if (dependency.type === "npm") {
|
|
48
|
+
const version = npmVersions.get(dependency.package);
|
|
49
|
+
if (version !== void 0 && version !== dependency.version) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Conflicting runtime dependency versions for ${dependency.package}: ${version} and ${dependency.version}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
npmVersions.set(dependency.package, dependency.version);
|
|
55
|
+
}
|
|
56
|
+
merged.push(dependency);
|
|
57
|
+
}
|
|
58
|
+
return merged;
|
|
59
|
+
}
|
|
60
|
+
function floatingMaxAgeMs() {
|
|
61
|
+
const raw = process.env.SANDBOX_SNAPSHOT_FLOATING_MAX_AGE_MS;
|
|
62
|
+
if (!raw?.trim()) {
|
|
63
|
+
return DEFAULT_FLOATING_MAX_AGE_MS;
|
|
64
|
+
}
|
|
65
|
+
const parsed = Number.parseInt(raw, 10);
|
|
66
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_FLOATING_MAX_AGE_MS;
|
|
67
|
+
}
|
|
68
|
+
function create(runtime) {
|
|
69
|
+
const dependencies2 = mergeDependencies([
|
|
70
|
+
...GLOBAL_RUNTIME_DEPENDENCIES,
|
|
71
|
+
...pluginCatalogRuntime.getRuntimeDependencies()
|
|
72
|
+
]);
|
|
73
|
+
const postinstall2 = pluginCatalogRuntime.getRuntimePostinstall();
|
|
74
|
+
if (dependencies2.length === 0 && postinstall2.length === 0) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const rebuildEpoch = process.env.SANDBOX_SNAPSHOT_REBUILD_EPOCH?.trim() ?? "";
|
|
78
|
+
const floating = dependencies2.some((dependency) => isFloating(dependency)) || postinstall2.length > 0;
|
|
79
|
+
const hash2 = createHash("sha256").update(
|
|
80
|
+
JSON.stringify({
|
|
81
|
+
version: VERSION,
|
|
82
|
+
runtime,
|
|
83
|
+
rebuildEpoch,
|
|
84
|
+
dependencies: dependencies2,
|
|
85
|
+
postinstall: postinstall2
|
|
86
|
+
})
|
|
87
|
+
).digest("hex");
|
|
88
|
+
return {
|
|
89
|
+
hash: hash2,
|
|
90
|
+
dependencyCount: dependencies2.length,
|
|
91
|
+
floating,
|
|
92
|
+
dependencies: dependencies2,
|
|
93
|
+
postinstall: postinstall2
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function hash(runtime) {
|
|
97
|
+
return create(runtime)?.hash;
|
|
98
|
+
}
|
|
99
|
+
function isStale(profile, createdAtMs) {
|
|
100
|
+
if (!profile.floating) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const maxAgeMs = floatingMaxAgeMs();
|
|
104
|
+
return maxAgeMs === 0 || Date.now() - createdAtMs > maxAgeMs;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/chat/sandbox/snapshot/resolve.ts
|
|
28
108
|
import { Sandbox } from "@vercel/sandbox";
|
|
109
|
+
import { z } from "zod";
|
|
110
|
+
|
|
111
|
+
// src/chat/sandbox/credentials.ts
|
|
112
|
+
function getVercelSandboxCredentials() {
|
|
113
|
+
const token = toOptionalTrimmed(process.env.VERCEL_TOKEN);
|
|
114
|
+
const teamId = toOptionalTrimmed(process.env.VERCEL_TEAM_ID);
|
|
115
|
+
const projectId = toOptionalTrimmed(process.env.VERCEL_PROJECT_ID);
|
|
116
|
+
if (token && teamId && projectId) {
|
|
117
|
+
return { token, teamId, projectId };
|
|
118
|
+
}
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/chat/sandbox/resources.ts
|
|
123
|
+
function getSandboxResources() {
|
|
124
|
+
const value = process.env.SANDBOX_VCPUS?.trim();
|
|
125
|
+
if (!value || !/^\d+$/.test(value)) {
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
128
|
+
const vcpus = Number(value);
|
|
129
|
+
if (!Number.isSafeInteger(vcpus) || vcpus <= 0) {
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
return { vcpus };
|
|
133
|
+
}
|
|
29
134
|
|
|
30
135
|
// src/chat/sandbox/noninteractive-command.ts
|
|
31
136
|
var NON_INTERACTIVE_ENV = {
|
|
@@ -96,15 +201,215 @@ async function runNonInteractiveCommand(sandbox, input) {
|
|
|
96
201
|
return await sandbox.runCommand(command);
|
|
97
202
|
}
|
|
98
203
|
|
|
99
|
-
// src/chat/sandbox/
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
204
|
+
// src/chat/sandbox/snapshot/span.ts
|
|
205
|
+
async function trace(name, op, attributes, callback) {
|
|
206
|
+
return await withSpan(name, op, {}, callback, attributes);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/chat/sandbox/snapshot/install.ts
|
|
210
|
+
async function runOrThrow(sandbox, params, label) {
|
|
211
|
+
const result = await runNonInteractiveCommand(sandbox, params);
|
|
212
|
+
if (result.exitCode === 0) {
|
|
213
|
+
return;
|
|
106
214
|
}
|
|
107
|
-
|
|
215
|
+
const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
|
|
216
|
+
throw new Error(`${label} failed: ${detail}`);
|
|
217
|
+
}
|
|
218
|
+
async function tryRun(sandbox, params) {
|
|
219
|
+
const result = await runNonInteractiveCommand(sandbox, params);
|
|
220
|
+
return result.exitCode === 0;
|
|
221
|
+
}
|
|
222
|
+
async function installGh(sandbox) {
|
|
223
|
+
const installed = await tryRun(sandbox, {
|
|
224
|
+
cmd: "dnf",
|
|
225
|
+
args: ["install", "-y", "gh"],
|
|
226
|
+
sudo: true
|
|
227
|
+
});
|
|
228
|
+
if (installed) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const repoAdded = await tryRun(sandbox, {
|
|
232
|
+
cmd: "dnf",
|
|
233
|
+
args: [
|
|
234
|
+
"config-manager",
|
|
235
|
+
"addrepo",
|
|
236
|
+
"--from-repofile=https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
237
|
+
],
|
|
238
|
+
sudo: true
|
|
239
|
+
});
|
|
240
|
+
if (!repoAdded) {
|
|
241
|
+
await runOrThrow(
|
|
242
|
+
sandbox,
|
|
243
|
+
{
|
|
244
|
+
cmd: "dnf",
|
|
245
|
+
args: ["install", "-y", "dnf-command(config-manager)"],
|
|
246
|
+
sudo: true
|
|
247
|
+
},
|
|
248
|
+
"dnf install dnf-command(config-manager)"
|
|
249
|
+
);
|
|
250
|
+
await runOrThrow(
|
|
251
|
+
sandbox,
|
|
252
|
+
{
|
|
253
|
+
cmd: "dnf",
|
|
254
|
+
args: [
|
|
255
|
+
"config-manager",
|
|
256
|
+
"--add-repo",
|
|
257
|
+
"https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
258
|
+
],
|
|
259
|
+
sudo: true
|
|
260
|
+
},
|
|
261
|
+
"dnf config-manager --add-repo gh-cli.repo"
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
await runOrThrow(
|
|
265
|
+
sandbox,
|
|
266
|
+
{
|
|
267
|
+
cmd: "dnf",
|
|
268
|
+
args: ["install", "-y", "gh", "--repo", "gh-cli"],
|
|
269
|
+
sudo: true
|
|
270
|
+
},
|
|
271
|
+
"dnf install gh --repo gh-cli"
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
function filePath(url, sha256) {
|
|
275
|
+
let basename = "package.rpm";
|
|
276
|
+
try {
|
|
277
|
+
const candidate = new URL(url).pathname.split("/").filter(Boolean).at(-1);
|
|
278
|
+
if (candidate) {
|
|
279
|
+
basename = candidate;
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
const sanitized = basename.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
284
|
+
return `/tmp/junior-runtime-${sha256.slice(0, 12)}-${sanitized}`;
|
|
285
|
+
}
|
|
286
|
+
async function installUrl(sandbox, dependency) {
|
|
287
|
+
const rpmPath = filePath(dependency.url, dependency.sha256);
|
|
288
|
+
await runOrThrow(
|
|
289
|
+
sandbox,
|
|
290
|
+
{
|
|
291
|
+
cmd: "curl",
|
|
292
|
+
args: ["-fsSL", dependency.url, "-o", rpmPath]
|
|
293
|
+
},
|
|
294
|
+
`curl download ${dependency.url}`
|
|
295
|
+
);
|
|
296
|
+
const checksum = await runNonInteractiveCommand(sandbox, {
|
|
297
|
+
cmd: "sha256sum",
|
|
298
|
+
args: [rpmPath]
|
|
299
|
+
});
|
|
300
|
+
const expected = dependency.sha256;
|
|
301
|
+
const actual = checksum.stdout.trim().split(/\s+/)[0]?.toLowerCase();
|
|
302
|
+
if (checksum.exitCode !== 0) {
|
|
303
|
+
const detail = checksum.stderr.trim() || checksum.stdout.trim() || "command failed";
|
|
304
|
+
throw new Error(`sha256sum failed: ${detail}`);
|
|
305
|
+
}
|
|
306
|
+
if (!actual) {
|
|
307
|
+
throw new Error("sha256sum produced empty output");
|
|
308
|
+
}
|
|
309
|
+
if (actual !== expected) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`checksum mismatch for ${dependency.url}: expected ${expected}, got ${actual}`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
await runOrThrow(
|
|
315
|
+
sandbox,
|
|
316
|
+
{
|
|
317
|
+
cmd: "dnf",
|
|
318
|
+
args: ["install", "-y", rpmPath],
|
|
319
|
+
sudo: true
|
|
320
|
+
},
|
|
321
|
+
`dnf install ${dependency.url}`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
async function dependencies(sandbox, values) {
|
|
325
|
+
const system = values.filter(
|
|
326
|
+
(dependency) => dependency.type === "system"
|
|
327
|
+
);
|
|
328
|
+
const npm = values.filter(
|
|
329
|
+
(dependency) => dependency.type === "npm"
|
|
330
|
+
).map((dependency) => `${dependency.package}@${dependency.version}`);
|
|
331
|
+
if (system.length > 0) {
|
|
332
|
+
await trace(
|
|
333
|
+
"sandbox.snapshot.install_system",
|
|
334
|
+
"sandbox.snapshot.install.system",
|
|
335
|
+
{
|
|
336
|
+
"app.sandbox.snapshot.install.system_count": system.length
|
|
337
|
+
},
|
|
338
|
+
async () => {
|
|
339
|
+
for (const dependency of system) {
|
|
340
|
+
if ("url" in dependency) {
|
|
341
|
+
await installUrl(sandbox, dependency);
|
|
342
|
+
} else if (dependency.package === "gh") {
|
|
343
|
+
await installGh(sandbox);
|
|
344
|
+
} else {
|
|
345
|
+
await runOrThrow(
|
|
346
|
+
sandbox,
|
|
347
|
+
{
|
|
348
|
+
cmd: "dnf",
|
|
349
|
+
args: ["install", "-y", dependency.package],
|
|
350
|
+
sudo: true
|
|
351
|
+
},
|
|
352
|
+
`dnf install ${dependency.package}`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (npm.length > 0) {
|
|
360
|
+
await trace(
|
|
361
|
+
"sandbox.snapshot.install_npm",
|
|
362
|
+
"sandbox.snapshot.install.npm",
|
|
363
|
+
{
|
|
364
|
+
"app.sandbox.snapshot.install.npm_count": npm.length
|
|
365
|
+
},
|
|
366
|
+
async () => {
|
|
367
|
+
await runOrThrow(
|
|
368
|
+
sandbox,
|
|
369
|
+
{
|
|
370
|
+
cmd: "npm",
|
|
371
|
+
args: [
|
|
372
|
+
"install",
|
|
373
|
+
"--global",
|
|
374
|
+
"--prefix",
|
|
375
|
+
`${SANDBOX_WORKSPACE_ROOT}/.junior`,
|
|
376
|
+
...npm
|
|
377
|
+
]
|
|
378
|
+
},
|
|
379
|
+
"npm install"
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async function postinstall(sandbox, commands) {
|
|
386
|
+
if (commands.length === 0) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
await trace(
|
|
390
|
+
"sandbox.snapshot.runtime_postinstall",
|
|
391
|
+
"sandbox.snapshot.runtime_postinstall",
|
|
392
|
+
{
|
|
393
|
+
"app.sandbox.snapshot.runtime_postinstall.count": commands.length
|
|
394
|
+
},
|
|
395
|
+
async () => {
|
|
396
|
+
for (const command of commands) {
|
|
397
|
+
const result = await runNonInteractiveCommand(sandbox, {
|
|
398
|
+
cmd: command.cmd,
|
|
399
|
+
args: command.args,
|
|
400
|
+
login: true,
|
|
401
|
+
pathPrefix: `${SANDBOX_WORKSPACE_ROOT}/.junior/bin:$PATH`,
|
|
402
|
+
...command.sudo !== void 0 ? { sudo: command.sudo } : {}
|
|
403
|
+
});
|
|
404
|
+
if (result.exitCode !== 0) {
|
|
405
|
+
const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
|
|
406
|
+
throw new Error(
|
|
407
|
+
`runtime-postinstall ${command.cmd} failed: ${detail}`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
);
|
|
108
413
|
}
|
|
109
414
|
|
|
110
415
|
// src/chat/sandbox/workspace.ts
|
|
@@ -350,21 +655,21 @@ function bindSandboxFileSystem(fs, signal) {
|
|
|
350
655
|
return fs;
|
|
351
656
|
}
|
|
352
657
|
return {
|
|
353
|
-
readFile(
|
|
658
|
+
readFile(filePath2, options) {
|
|
354
659
|
signal.throwIfAborted();
|
|
355
|
-
return fs.readFile(
|
|
660
|
+
return fs.readFile(filePath2, { ...options, signal });
|
|
356
661
|
},
|
|
357
|
-
writeFile(
|
|
662
|
+
writeFile(filePath2, content, options) {
|
|
358
663
|
signal.throwIfAborted();
|
|
359
|
-
return fs.writeFile(
|
|
664
|
+
return fs.writeFile(filePath2, content, { ...options, signal });
|
|
360
665
|
},
|
|
361
|
-
readdir(
|
|
666
|
+
readdir(filePath2) {
|
|
362
667
|
signal.throwIfAborted();
|
|
363
|
-
return fs.readdir(
|
|
668
|
+
return fs.readdir(filePath2, { signal });
|
|
364
669
|
},
|
|
365
|
-
stat(
|
|
670
|
+
stat(filePath2) {
|
|
366
671
|
signal.throwIfAborted();
|
|
367
|
-
return fs.stat(
|
|
672
|
+
return fs.stat(filePath2, { signal });
|
|
368
673
|
}
|
|
369
674
|
};
|
|
370
675
|
}
|
|
@@ -382,12 +687,12 @@ function createSandboxSession(sandbox, options) {
|
|
|
382
687
|
}
|
|
383
688
|
};
|
|
384
689
|
const fs = {
|
|
385
|
-
readFile: async (
|
|
386
|
-
writeFile: async (
|
|
387
|
-
async () => await fileSystem.writeFile(
|
|
690
|
+
readFile: async (filePath2, readOptions) => await run(async () => await fileSystem.readFile(filePath2, readOptions)),
|
|
691
|
+
writeFile: async (filePath2, content, writeOptions) => await run(
|
|
692
|
+
async () => await fileSystem.writeFile(filePath2, content, writeOptions)
|
|
388
693
|
),
|
|
389
|
-
readdir: async (
|
|
390
|
-
stat: async (
|
|
694
|
+
readdir: async (filePath2, readOptions) => await run(async () => await fileSystem.readdir(filePath2, readOptions)),
|
|
695
|
+
stat: async (filePath2, statOptions) => await run(async () => await fileSystem.stat(filePath2, statOptions))
|
|
391
696
|
};
|
|
392
697
|
return {
|
|
393
698
|
sandboxId: sandbox.name,
|
|
@@ -432,109 +737,43 @@ function createSandboxSession(sandbox, options) {
|
|
|
432
737
|
};
|
|
433
738
|
}
|
|
434
739
|
|
|
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
|
|
740
|
+
// src/chat/sandbox/snapshot/resolve.ts
|
|
449
741
|
var SNAPSHOT_CACHE_PREFIX = "junior:sandbox_snapshot_profile";
|
|
450
742
|
var SNAPSHOT_LOCK_PREFIX = "junior:sandbox_snapshot_lock";
|
|
451
|
-
var SNAPSHOT_PROFILE_VERSION = 2;
|
|
452
743
|
var SNAPSHOT_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
453
|
-
var
|
|
454
|
-
var
|
|
455
|
-
var
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
744
|
+
var SNAPSHOT_BUILD_LOCK_BUFFER_MS = 30 * 1e3;
|
|
745
|
+
var SNAPSHOT_WAIT_FOR_LOCK_BUFFER_MS = 30 * 1e3;
|
|
746
|
+
var cachedSnapshotSchema = z.object({
|
|
747
|
+
profileHash: z.string(),
|
|
748
|
+
snapshotId: z.string(),
|
|
749
|
+
runtime: z.string(),
|
|
750
|
+
createdAtMs: z.number(),
|
|
751
|
+
dependencyCount: z.number()
|
|
752
|
+
}).strict();
|
|
459
753
|
function profileCacheKey(profileHash) {
|
|
460
754
|
return `${SNAPSHOT_CACHE_PREFIX}:${profileHash}`;
|
|
461
755
|
}
|
|
462
756
|
function profileLockKey(profileHash) {
|
|
463
757
|
return `${SNAPSHOT_LOCK_PREFIX}:${profileHash}`;
|
|
464
758
|
}
|
|
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) {
|
|
759
|
+
async function getCachedSnapshot(profileHash) {
|
|
760
|
+
const state = getStateAdapter();
|
|
761
|
+
await state.connect();
|
|
762
|
+
const raw = await state.get(profileCacheKey(profileHash));
|
|
763
|
+
if (typeof raw !== "string") {
|
|
489
764
|
return null;
|
|
490
765
|
}
|
|
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) {
|
|
766
|
+
let value;
|
|
523
767
|
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;
|
|
768
|
+
value = JSON.parse(raw);
|
|
535
769
|
} catch {
|
|
536
|
-
|
|
770
|
+
throw new Error(`Invalid cached sandbox snapshot for ${profileHash}`);
|
|
537
771
|
}
|
|
772
|
+
const parsed = cachedSnapshotSchema.safeParse(value);
|
|
773
|
+
if (!parsed.success || parsed.data.profileHash !== profileHash) {
|
|
774
|
+
throw new Error(`Invalid cached sandbox snapshot for ${profileHash}`);
|
|
775
|
+
}
|
|
776
|
+
return parsed.data;
|
|
538
777
|
}
|
|
539
778
|
async function setCachedSnapshot(entry) {
|
|
540
779
|
const state = getStateAdapter();
|
|
@@ -545,280 +784,13 @@ async function setCachedSnapshot(entry) {
|
|
|
545
784
|
SNAPSHOT_CACHE_TTL_MS
|
|
546
785
|
);
|
|
547
786
|
}
|
|
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(
|
|
787
|
+
async function build(value, runtime, timeoutMs) {
|
|
788
|
+
return await trace(
|
|
817
789
|
"sandbox.snapshot.build",
|
|
818
790
|
"sandbox.snapshot.build",
|
|
819
791
|
{
|
|
820
792
|
"app.sandbox.runtime": runtime,
|
|
821
|
-
"app.sandbox.snapshot.dependency_count":
|
|
793
|
+
"app.sandbox.snapshot.dependency_count": value.dependencyCount
|
|
822
794
|
},
|
|
823
795
|
async () => {
|
|
824
796
|
const sandboxCredentials = getVercelSandboxCredentials();
|
|
@@ -832,13 +804,13 @@ async function createDependencySnapshot(profile, runtime, timeoutMs) {
|
|
|
832
804
|
})
|
|
833
805
|
);
|
|
834
806
|
try {
|
|
835
|
-
await
|
|
836
|
-
await
|
|
837
|
-
return await
|
|
807
|
+
await dependencies(sandbox, value.dependencies);
|
|
808
|
+
await postinstall(sandbox, value.postinstall);
|
|
809
|
+
return await trace(
|
|
838
810
|
"sandbox.snapshot.capture",
|
|
839
811
|
"sandbox.snapshot.capture",
|
|
840
812
|
{
|
|
841
|
-
"app.sandbox.snapshot.dependency_count":
|
|
813
|
+
"app.sandbox.snapshot.dependency_count": value.dependencyCount
|
|
842
814
|
},
|
|
843
815
|
async () => {
|
|
844
816
|
const snapshot = await sandbox.snapshot();
|
|
@@ -854,40 +826,35 @@ async function createDependencySnapshot(profile, runtime, timeoutMs) {
|
|
|
854
826
|
}
|
|
855
827
|
);
|
|
856
828
|
}
|
|
857
|
-
async function withBuildLock(profileHash, callback, canUseCachedSnapshot,
|
|
829
|
+
async function withBuildLock(profileHash, timeoutMs, callback, canUseCachedSnapshot, onWaitingForLock) {
|
|
858
830
|
const state = getStateAdapter();
|
|
859
831
|
await state.connect();
|
|
860
832
|
const lockKey = profileLockKey(profileHash);
|
|
861
|
-
const
|
|
833
|
+
const lockTtlMs = timeoutMs + SNAPSHOT_BUILD_LOCK_BUFFER_MS;
|
|
834
|
+
const tryAcquireLock = async () => await state.acquireLock(lockKey, lockTtlMs);
|
|
862
835
|
let lock = await tryAcquireLock();
|
|
863
836
|
if (lock) {
|
|
864
837
|
try {
|
|
865
|
-
|
|
866
|
-
return {
|
|
867
|
-
snapshotId: result.snapshotId,
|
|
868
|
-
source: result.source,
|
|
869
|
-
waitedForLock: false
|
|
870
|
-
};
|
|
838
|
+
return await callback();
|
|
871
839
|
} finally {
|
|
872
840
|
await state.releaseLock(lock);
|
|
873
841
|
}
|
|
874
842
|
}
|
|
875
|
-
return await
|
|
843
|
+
return await trace(
|
|
876
844
|
"sandbox.snapshot.lock_wait",
|
|
877
845
|
"sandbox.snapshot.lock_wait",
|
|
878
846
|
{
|
|
879
847
|
"app.sandbox.snapshot.profile_hash": profileHash
|
|
880
848
|
},
|
|
881
849
|
async () => {
|
|
882
|
-
await
|
|
883
|
-
const waitUntil = Date.now() +
|
|
850
|
+
await onWaitingForLock?.();
|
|
851
|
+
const waitUntil = Date.now() + lockTtlMs + SNAPSHOT_WAIT_FOR_LOCK_BUFFER_MS;
|
|
884
852
|
while (Date.now() < waitUntil) {
|
|
885
853
|
const cached2 = await getCachedSnapshot(profileHash);
|
|
886
854
|
if (cached2?.snapshotId && canUseCachedSnapshot(cached2)) {
|
|
887
855
|
return {
|
|
888
856
|
snapshotId: cached2.snapshotId,
|
|
889
|
-
source: "
|
|
890
|
-
waitedForLock: true
|
|
857
|
+
source: "cache_hit_after_lock_wait"
|
|
891
858
|
};
|
|
892
859
|
}
|
|
893
860
|
lock = await tryAcquireLock();
|
|
@@ -896,8 +863,7 @@ async function withBuildLock(profileHash, callback, canUseCachedSnapshot, hooks)
|
|
|
896
863
|
const result = await callback();
|
|
897
864
|
return {
|
|
898
865
|
snapshotId: result.snapshotId,
|
|
899
|
-
source: result.source
|
|
900
|
-
waitedForLock: true
|
|
866
|
+
source: result.source === "built" ? "built" : "cache_hit_after_lock_wait"
|
|
901
867
|
};
|
|
902
868
|
} finally {
|
|
903
869
|
await state.releaseLock(lock);
|
|
@@ -909,22 +875,18 @@ async function withBuildLock(profileHash, callback, canUseCachedSnapshot, hooks)
|
|
|
909
875
|
if (cached?.snapshotId && canUseCachedSnapshot(cached)) {
|
|
910
876
|
return {
|
|
911
877
|
snapshotId: cached.snapshotId,
|
|
912
|
-
source: "
|
|
913
|
-
waitedForLock: true
|
|
878
|
+
source: "cache_hit_after_lock_wait"
|
|
914
879
|
};
|
|
915
880
|
}
|
|
916
881
|
throw new Error("Timed out waiting for snapshot build lock");
|
|
917
882
|
}
|
|
918
883
|
);
|
|
919
884
|
}
|
|
920
|
-
function toResolveOutcome(forceRebuild, source
|
|
885
|
+
function toResolveOutcome(forceRebuild, source) {
|
|
921
886
|
if (source === "built") {
|
|
922
887
|
return forceRebuild ? "forced_rebuild" : "rebuilt";
|
|
923
888
|
}
|
|
924
|
-
|
|
925
|
-
return "cache_hit_after_lock_wait";
|
|
926
|
-
}
|
|
927
|
-
return "cache_hit";
|
|
889
|
+
return source;
|
|
928
890
|
}
|
|
929
891
|
function getRebuildReason(params) {
|
|
930
892
|
if (params.forceRebuild) {
|
|
@@ -938,8 +900,8 @@ function getRebuildReason(params) {
|
|
|
938
900
|
}
|
|
939
901
|
return void 0;
|
|
940
902
|
}
|
|
941
|
-
async function
|
|
942
|
-
return await
|
|
903
|
+
async function resolve(params) {
|
|
904
|
+
return await trace(
|
|
943
905
|
"sandbox.snapshot.resolve",
|
|
944
906
|
"sandbox.snapshot.resolve",
|
|
945
907
|
{
|
|
@@ -948,25 +910,24 @@ async function resolveRuntimeDependencySnapshot(params) {
|
|
|
948
910
|
},
|
|
949
911
|
async () => {
|
|
950
912
|
await params.onProgress?.("resolve_start");
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
if (!profile) {
|
|
913
|
+
const currentProfile = create(params.runtime);
|
|
914
|
+
if (!currentProfile) {
|
|
954
915
|
return {
|
|
955
916
|
dependencyCount: 0,
|
|
956
917
|
cacheHit: false,
|
|
957
918
|
resolveOutcome: "no_profile"
|
|
958
919
|
};
|
|
959
920
|
}
|
|
960
|
-
const cached = await getCachedSnapshot(
|
|
921
|
+
const cached = await getCachedSnapshot(currentProfile.hash);
|
|
961
922
|
const cachedNeedsRebuild = Boolean(
|
|
962
|
-
cached?.snapshotId &&
|
|
923
|
+
cached?.snapshotId && isStale(currentProfile, cached.createdAtMs)
|
|
963
924
|
);
|
|
964
925
|
if (!params.forceRebuild && cached?.snapshotId && !cachedNeedsRebuild) {
|
|
965
926
|
await params.onProgress?.("cache_hit");
|
|
966
927
|
return {
|
|
967
928
|
snapshotId: cached.snapshotId,
|
|
968
|
-
profileHash:
|
|
969
|
-
dependencyCount:
|
|
929
|
+
profileHash: currentProfile.hash,
|
|
930
|
+
dependencyCount: currentProfile.dependencyCount,
|
|
970
931
|
cacheHit: true,
|
|
971
932
|
resolveOutcome: "cache_hit"
|
|
972
933
|
};
|
|
@@ -982,60 +943,58 @@ async function resolveRuntimeDependencySnapshot(params) {
|
|
|
982
943
|
if (params.staleSnapshotId) {
|
|
983
944
|
return candidate.snapshotId !== params.staleSnapshotId;
|
|
984
945
|
}
|
|
985
|
-
return candidate.
|
|
946
|
+
return candidate.snapshotId !== cached?.snapshotId;
|
|
986
947
|
}
|
|
987
|
-
return !
|
|
948
|
+
return !isStale(currentProfile, candidate.createdAtMs);
|
|
988
949
|
};
|
|
989
950
|
const lockResult = await withBuildLock(
|
|
990
|
-
|
|
951
|
+
currentProfile.hash,
|
|
952
|
+
params.timeoutMs,
|
|
991
953
|
async () => {
|
|
992
|
-
const latest = await getCachedSnapshot(
|
|
954
|
+
const latest = await getCachedSnapshot(currentProfile.hash);
|
|
993
955
|
if (latest?.snapshotId && canUseCachedSnapshot(latest)) {
|
|
994
956
|
await params.onProgress?.("cache_hit");
|
|
995
957
|
return {
|
|
996
958
|
snapshotId: latest.snapshotId,
|
|
997
|
-
source: "
|
|
959
|
+
source: "cache_hit"
|
|
998
960
|
};
|
|
999
961
|
}
|
|
1000
962
|
await params.onProgress?.("building_snapshot");
|
|
1001
|
-
const nextSnapshotId = await
|
|
1002
|
-
|
|
963
|
+
const nextSnapshotId = await build(
|
|
964
|
+
currentProfile,
|
|
1003
965
|
params.runtime,
|
|
1004
966
|
params.timeoutMs
|
|
1005
967
|
);
|
|
1006
968
|
await setCachedSnapshot({
|
|
1007
|
-
profileHash:
|
|
969
|
+
profileHash: currentProfile.hash,
|
|
1008
970
|
snapshotId: nextSnapshotId,
|
|
1009
971
|
runtime: params.runtime,
|
|
1010
972
|
createdAtMs: Date.now(),
|
|
1011
|
-
dependencyCount:
|
|
973
|
+
dependencyCount: currentProfile.dependencyCount
|
|
1012
974
|
});
|
|
1013
975
|
await params.onProgress?.("build_complete");
|
|
1014
976
|
return { snapshotId: nextSnapshotId, source: "built" };
|
|
1015
977
|
},
|
|
1016
978
|
canUseCachedSnapshot,
|
|
1017
|
-
{
|
|
1018
|
-
|
|
1019
|
-
await params.onProgress?.("waiting_for_lock");
|
|
1020
|
-
}
|
|
979
|
+
async () => {
|
|
980
|
+
await params.onProgress?.("waiting_for_lock");
|
|
1021
981
|
}
|
|
1022
982
|
);
|
|
1023
983
|
return {
|
|
1024
984
|
snapshotId: lockResult.snapshotId,
|
|
1025
|
-
profileHash:
|
|
1026
|
-
dependencyCount:
|
|
985
|
+
profileHash: currentProfile.hash,
|
|
986
|
+
dependencyCount: currentProfile.dependencyCount,
|
|
1027
987
|
cacheHit: lockResult.source !== "built",
|
|
1028
988
|
resolveOutcome: toResolveOutcome(
|
|
1029
989
|
Boolean(params.forceRebuild),
|
|
1030
|
-
lockResult.source
|
|
1031
|
-
lockResult.waitedForLock
|
|
990
|
+
lockResult.source
|
|
1032
991
|
),
|
|
1033
992
|
...rebuildReason ? { rebuildReason } : {}
|
|
1034
993
|
};
|
|
1035
994
|
}
|
|
1036
995
|
);
|
|
1037
996
|
}
|
|
1038
|
-
function
|
|
997
|
+
function isMissingError(error) {
|
|
1039
998
|
const searchable = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
1040
999
|
return searchable.includes("snapshot") && (searchable.includes("not found") || searchable.includes("unknown") || searchable.includes("404"));
|
|
1041
1000
|
}
|
|
@@ -1052,10 +1011,10 @@ export {
|
|
|
1052
1011
|
buildNonInteractiveShellScript,
|
|
1053
1012
|
runNonInteractiveCommand,
|
|
1054
1013
|
getSandboxResources,
|
|
1055
|
-
|
|
1014
|
+
create,
|
|
1015
|
+
hash,
|
|
1056
1016
|
bindSandboxFileSystem,
|
|
1057
1017
|
createSandboxSession,
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
isSnapshotMissingError
|
|
1018
|
+
resolve,
|
|
1019
|
+
isMissingError
|
|
1061
1020
|
};
|