@sentry/junior 0.116.0 → 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-67NFITDH.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-CWZY6GTH.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-OF5FODBL.js → chunk-27KWTFP6.js} +33 -12
- package/dist/{chunk-MGH5ADQS.js → chunk-3GE3F4LT.js} +1 -1
- package/dist/{chunk-JZPQ5K4V.js → chunk-5O3JDQDG.js} +154 -20
- package/dist/{chunk-PGKWXHYJ.js → chunk-6G45D3YG.js} +114 -10
- package/dist/{chunk-3JOGUT46.js → chunk-72Z3HNTQ.js} +1 -1
- package/dist/{chunk-6RQ5GCWH.js → chunk-AKG4TZNH.js} +2 -2
- package/dist/{chunk-TC4ZEM2D.js → chunk-BFIRIZPH.js} +9 -0
- package/dist/{chunk-25GU5B5E.js → chunk-CU7HTGNI.js} +1 -1
- package/dist/{chunk-PZGPCOSD.js → chunk-DS27ZBSP.js} +4 -2
- package/dist/{chunk-3H45VOBZ.js → chunk-FAWBONPC.js} +404 -391
- package/dist/{chunk-53ZKTQO6.js → chunk-KU6ALEJC.js} +1 -1
- package/dist/{chunk-UEVLIYK5.js → chunk-LN765Y4N.js} +6 -6
- package/dist/{chunk-4DD6AOCG.js → chunk-LUMEBOWC.js} +1 -1
- package/dist/{chunk-5UNB623T.js → chunk-NHZEUHKH.js} +514 -344
- package/dist/{chunk-VZCSLYCE.js → chunk-STUI6NIL.js} +78 -16
- package/dist/{chunk-BXLS3I4W.js → chunk-W5JEQQEA.js} +1 -1
- package/dist/{chunk-VKZ6WVAY.js → chunk-WEK3V5RI.js} +73 -32
- package/dist/{chunk-N7TMYLF2.js → chunk-WUNPRM76.js} +1 -1
- package/dist/{chunk-CI3VK7X5.js → chunk-YRWJ3KEU.js} +2 -2
- package/dist/{chunk-KBNDFU6J.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-57FHVGGQ.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-PAQRPXZX.js → oauth-callback-server-NQYKBT24.js} +15 -15
- package/dist/{runner-4ISZYJEX.js → runner-53V5GIQM.js} +10 -10
- package/dist/{skills-UE2KXQMB.js → skills-BU3ZMLVB.js} +6 -6
- package/dist/{validation-PO5LHTJT.js → validation-74EIKKE6.js} +6 -6
- package/dist/version.js +1 -1
- package/package.json +6 -6
- 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,106 +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 = 1;
|
|
452
745
|
var SNAPSHOT_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
453
|
-
var
|
|
454
|
-
var
|
|
455
|
-
var
|
|
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();
|
|
456
755
|
function profileCacheKey(profileHash) {
|
|
457
756
|
return `${SNAPSHOT_CACHE_PREFIX}:${profileHash}`;
|
|
458
757
|
}
|
|
459
758
|
function profileLockKey(profileHash) {
|
|
460
759
|
return `${SNAPSHOT_LOCK_PREFIX}:${profileHash}`;
|
|
461
760
|
}
|
|
462
|
-
function
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
function parseFloatingDepMaxAgeMs() {
|
|
469
|
-
const raw = process.env.SANDBOX_SNAPSHOT_FLOATING_MAX_AGE_MS;
|
|
470
|
-
if (!raw?.trim()) {
|
|
471
|
-
return DEFAULT_FLOATING_DEP_MAX_AGE_MS;
|
|
472
|
-
}
|
|
473
|
-
const parsed = Number.parseInt(raw, 10);
|
|
474
|
-
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
475
|
-
return DEFAULT_FLOATING_DEP_MAX_AGE_MS;
|
|
476
|
-
}
|
|
477
|
-
return parsed;
|
|
478
|
-
}
|
|
479
|
-
function buildDependencyProfile(runtime) {
|
|
480
|
-
const dependencies = [
|
|
481
|
-
...GLOBAL_RUNTIME_DEPENDENCIES,
|
|
482
|
-
...pluginCatalogRuntime.getRuntimeDependencies()
|
|
483
|
-
];
|
|
484
|
-
const postinstall = pluginCatalogRuntime.getRuntimePostinstall();
|
|
485
|
-
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") {
|
|
486
766
|
return null;
|
|
487
767
|
}
|
|
488
|
-
|
|
489
|
-
const hasFloatingVersions = dependencies.some((dep) => hasFloatingSelector(dep)) || postinstall.length > 0;
|
|
490
|
-
const hashInput = JSON.stringify({
|
|
491
|
-
version: SNAPSHOT_PROFILE_VERSION,
|
|
492
|
-
runtime,
|
|
493
|
-
rebuildEpoch,
|
|
494
|
-
dependencies,
|
|
495
|
-
postinstall
|
|
496
|
-
});
|
|
497
|
-
const profileHash = createHash("sha256").update(hashInput).digest("hex");
|
|
498
|
-
return {
|
|
499
|
-
profileHash,
|
|
500
|
-
dependencyCount: dependencies.length,
|
|
501
|
-
hasFloatingVersions,
|
|
502
|
-
dependencies,
|
|
503
|
-
postinstall
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
function getRuntimeDependencyProfileHash(runtime) {
|
|
507
|
-
return buildDependencyProfile(runtime)?.profileHash;
|
|
508
|
-
}
|
|
509
|
-
function shouldRebuildCachedSnapshot(profile, cached) {
|
|
510
|
-
if (!profile.hasFloatingVersions) {
|
|
511
|
-
return false;
|
|
512
|
-
}
|
|
513
|
-
const maxAgeMs = parseFloatingDepMaxAgeMs();
|
|
514
|
-
if (maxAgeMs === 0) {
|
|
515
|
-
return true;
|
|
516
|
-
}
|
|
517
|
-
return Date.now() - cached.createdAtMs > maxAgeMs;
|
|
518
|
-
}
|
|
519
|
-
async function getCachedSnapshot(profileHash) {
|
|
768
|
+
let value;
|
|
520
769
|
try {
|
|
521
|
-
|
|
522
|
-
await state.connect();
|
|
523
|
-
const raw = await state.get(profileCacheKey(profileHash));
|
|
524
|
-
if (typeof raw !== "string") {
|
|
525
|
-
return null;
|
|
526
|
-
}
|
|
527
|
-
const parsed = JSON.parse(raw);
|
|
528
|
-
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") {
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
return parsed;
|
|
770
|
+
value = JSON.parse(raw);
|
|
532
771
|
} catch {
|
|
533
|
-
|
|
772
|
+
throw new Error(`Invalid cached sandbox snapshot for ${profileHash}`);
|
|
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}`);
|
|
534
777
|
}
|
|
778
|
+
return parsed.data;
|
|
535
779
|
}
|
|
536
780
|
async function setCachedSnapshot(entry) {
|
|
537
781
|
const state = getStateAdapter();
|
|
@@ -542,231 +786,13 @@ async function setCachedSnapshot(entry) {
|
|
|
542
786
|
SNAPSHOT_CACHE_TTL_MS
|
|
543
787
|
);
|
|
544
788
|
}
|
|
545
|
-
async function
|
|
546
|
-
return await
|
|
547
|
-
}
|
|
548
|
-
async function runOrThrow(sandbox, params, label) {
|
|
549
|
-
const result = await runNonInteractiveCommand(sandbox, params);
|
|
550
|
-
if (result.exitCode === 0) {
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
const stderr = result.stderr.trim();
|
|
554
|
-
const stdout = result.stdout.trim();
|
|
555
|
-
const detail = stderr || stdout || "command failed";
|
|
556
|
-
throw new Error(`${label} failed: ${detail}`);
|
|
557
|
-
}
|
|
558
|
-
async function tryRun(sandbox, params) {
|
|
559
|
-
const result = await runNonInteractiveCommand(sandbox, params);
|
|
560
|
-
if (result.exitCode === 0) {
|
|
561
|
-
return { ok: true };
|
|
562
|
-
}
|
|
563
|
-
const stderr = result.stderr.trim();
|
|
564
|
-
const stdout = result.stdout.trim();
|
|
565
|
-
return { ok: false, detail: stderr || stdout || "command failed" };
|
|
566
|
-
}
|
|
567
|
-
async function installGhCliViaDnf(sandbox) {
|
|
568
|
-
const direct = await tryRun(sandbox, {
|
|
569
|
-
cmd: "dnf",
|
|
570
|
-
args: ["install", "-y", "gh"],
|
|
571
|
-
sudo: true
|
|
572
|
-
});
|
|
573
|
-
if (direct.ok) {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
const dnf5Repo = await tryRun(sandbox, {
|
|
577
|
-
cmd: "dnf",
|
|
578
|
-
args: [
|
|
579
|
-
"config-manager",
|
|
580
|
-
"addrepo",
|
|
581
|
-
"--from-repofile=https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
582
|
-
],
|
|
583
|
-
sudo: true
|
|
584
|
-
});
|
|
585
|
-
if (!dnf5Repo.ok) {
|
|
586
|
-
await runOrThrow(
|
|
587
|
-
sandbox,
|
|
588
|
-
{
|
|
589
|
-
cmd: "dnf",
|
|
590
|
-
args: ["install", "-y", "dnf-command(config-manager)"],
|
|
591
|
-
sudo: true
|
|
592
|
-
},
|
|
593
|
-
"dnf install dnf-command(config-manager)"
|
|
594
|
-
);
|
|
595
|
-
await runOrThrow(
|
|
596
|
-
sandbox,
|
|
597
|
-
{
|
|
598
|
-
cmd: "dnf",
|
|
599
|
-
args: [
|
|
600
|
-
"config-manager",
|
|
601
|
-
"--add-repo",
|
|
602
|
-
"https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
603
|
-
],
|
|
604
|
-
sudo: true
|
|
605
|
-
},
|
|
606
|
-
"dnf config-manager --add-repo gh-cli.repo"
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
await runOrThrow(
|
|
610
|
-
sandbox,
|
|
611
|
-
{
|
|
612
|
-
cmd: "dnf",
|
|
613
|
-
args: ["install", "-y", "gh", "--repo", "gh-cli"],
|
|
614
|
-
sudo: true
|
|
615
|
-
},
|
|
616
|
-
"dnf install gh --repo gh-cli"
|
|
617
|
-
);
|
|
618
|
-
}
|
|
619
|
-
function runtimeDependencyFilePath(url, sha256) {
|
|
620
|
-
let urlBasename = "package.rpm";
|
|
621
|
-
try {
|
|
622
|
-
const pathname = new URL(url).pathname;
|
|
623
|
-
const segments = pathname.split("/").filter(Boolean);
|
|
624
|
-
const candidate = segments[segments.length - 1];
|
|
625
|
-
if (candidate) {
|
|
626
|
-
urlBasename = candidate;
|
|
627
|
-
}
|
|
628
|
-
} catch {
|
|
629
|
-
}
|
|
630
|
-
const sanitizedBasename = urlBasename.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
631
|
-
return `/tmp/junior-runtime-${sha256.slice(0, 12)}-${sanitizedBasename}`;
|
|
632
|
-
}
|
|
633
|
-
async function installRuntimeDependencies(sandbox, deps) {
|
|
634
|
-
const systemDeps = deps.filter(
|
|
635
|
-
(dep) => dep.type === "system"
|
|
636
|
-
);
|
|
637
|
-
const npmPackages = deps.filter(
|
|
638
|
-
(dep) => dep.type === "npm"
|
|
639
|
-
).map((dep) => `${dep.package}@${dep.version}`);
|
|
640
|
-
if (systemDeps.length > 0) {
|
|
641
|
-
await withSnapshotSpan(
|
|
642
|
-
"sandbox.snapshot.install_system",
|
|
643
|
-
"sandbox.snapshot.install.system",
|
|
644
|
-
{
|
|
645
|
-
"app.sandbox.snapshot.install.system_count": systemDeps.length
|
|
646
|
-
},
|
|
647
|
-
async () => {
|
|
648
|
-
for (const dep of systemDeps) {
|
|
649
|
-
if ("url" in dep) {
|
|
650
|
-
const rpmPath = runtimeDependencyFilePath(dep.url, dep.sha256);
|
|
651
|
-
await runOrThrow(
|
|
652
|
-
sandbox,
|
|
653
|
-
{
|
|
654
|
-
cmd: "curl",
|
|
655
|
-
args: ["-fsSL", dep.url, "-o", rpmPath]
|
|
656
|
-
},
|
|
657
|
-
`curl download ${dep.url}`
|
|
658
|
-
);
|
|
659
|
-
const checksumResult = await runNonInteractiveCommand(sandbox, {
|
|
660
|
-
cmd: "sha256sum",
|
|
661
|
-
args: [rpmPath]
|
|
662
|
-
});
|
|
663
|
-
const checksumStdout = checksumResult.stdout.trim();
|
|
664
|
-
const checksumStderr = checksumResult.stderr.trim();
|
|
665
|
-
if (checksumResult.exitCode !== 0) {
|
|
666
|
-
throw new Error(
|
|
667
|
-
`sha256sum failed: ${checksumStderr || checksumStdout || "command failed"}`
|
|
668
|
-
);
|
|
669
|
-
}
|
|
670
|
-
const actualChecksum = checksumStdout.split(/\s+/)[0]?.toLowerCase();
|
|
671
|
-
if (!actualChecksum) {
|
|
672
|
-
throw new Error("sha256sum produced empty output");
|
|
673
|
-
}
|
|
674
|
-
if (actualChecksum !== dep.sha256) {
|
|
675
|
-
throw new Error(
|
|
676
|
-
`checksum mismatch for ${dep.url}: expected ${dep.sha256}, got ${actualChecksum}`
|
|
677
|
-
);
|
|
678
|
-
}
|
|
679
|
-
await runOrThrow(
|
|
680
|
-
sandbox,
|
|
681
|
-
{
|
|
682
|
-
cmd: "dnf",
|
|
683
|
-
args: ["install", "-y", rpmPath],
|
|
684
|
-
sudo: true
|
|
685
|
-
},
|
|
686
|
-
`dnf install ${dep.url}`
|
|
687
|
-
);
|
|
688
|
-
continue;
|
|
689
|
-
}
|
|
690
|
-
if (dep.package === "gh") {
|
|
691
|
-
await installGhCliViaDnf(sandbox);
|
|
692
|
-
continue;
|
|
693
|
-
}
|
|
694
|
-
await runOrThrow(
|
|
695
|
-
sandbox,
|
|
696
|
-
{
|
|
697
|
-
cmd: "dnf",
|
|
698
|
-
args: ["install", "-y", dep.package],
|
|
699
|
-
sudo: true
|
|
700
|
-
},
|
|
701
|
-
`dnf install ${dep.package}`
|
|
702
|
-
);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
);
|
|
706
|
-
}
|
|
707
|
-
if (npmPackages.length > 0) {
|
|
708
|
-
await withSnapshotSpan(
|
|
709
|
-
"sandbox.snapshot.install_npm",
|
|
710
|
-
"sandbox.snapshot.install.npm",
|
|
711
|
-
{
|
|
712
|
-
"app.sandbox.snapshot.install.npm_count": npmPackages.length
|
|
713
|
-
},
|
|
714
|
-
async () => {
|
|
715
|
-
await runOrThrow(
|
|
716
|
-
sandbox,
|
|
717
|
-
{
|
|
718
|
-
cmd: "npm",
|
|
719
|
-
args: [
|
|
720
|
-
"install",
|
|
721
|
-
"--global",
|
|
722
|
-
"--prefix",
|
|
723
|
-
`${SANDBOX_WORKSPACE_ROOT}/.junior`,
|
|
724
|
-
...npmPackages
|
|
725
|
-
]
|
|
726
|
-
},
|
|
727
|
-
"npm install"
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
async function runRuntimePostinstall(sandbox, commands) {
|
|
734
|
-
if (commands.length === 0) {
|
|
735
|
-
return;
|
|
736
|
-
}
|
|
737
|
-
await withSnapshotSpan(
|
|
738
|
-
"sandbox.snapshot.runtime_postinstall",
|
|
739
|
-
"sandbox.snapshot.runtime_postinstall",
|
|
740
|
-
{
|
|
741
|
-
"app.sandbox.snapshot.runtime_postinstall.count": commands.length
|
|
742
|
-
},
|
|
743
|
-
async () => {
|
|
744
|
-
for (const command of commands) {
|
|
745
|
-
const result = await runNonInteractiveCommand(sandbox, {
|
|
746
|
-
cmd: command.cmd,
|
|
747
|
-
args: command.args,
|
|
748
|
-
login: true,
|
|
749
|
-
pathPrefix: `${SANDBOX_WORKSPACE_ROOT}/.junior/bin:$PATH`,
|
|
750
|
-
...command.sudo !== void 0 ? { sudo: command.sudo } : {}
|
|
751
|
-
});
|
|
752
|
-
if (result.exitCode === 0) {
|
|
753
|
-
continue;
|
|
754
|
-
}
|
|
755
|
-
const stderr = result.stderr.trim();
|
|
756
|
-
const stdout = result.stdout.trim();
|
|
757
|
-
const detail = stderr || stdout || "command failed";
|
|
758
|
-
throw new Error(`runtime-postinstall ${command.cmd} failed: ${detail}`);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
);
|
|
762
|
-
}
|
|
763
|
-
async function createDependencySnapshot(profile, runtime, timeoutMs) {
|
|
764
|
-
return await withSnapshotSpan(
|
|
789
|
+
async function build(value, runtime, timeoutMs) {
|
|
790
|
+
return await trace(
|
|
765
791
|
"sandbox.snapshot.build",
|
|
766
792
|
"sandbox.snapshot.build",
|
|
767
793
|
{
|
|
768
794
|
"app.sandbox.runtime": runtime,
|
|
769
|
-
"app.sandbox.snapshot.dependency_count":
|
|
795
|
+
"app.sandbox.snapshot.dependency_count": value.dependencyCount
|
|
770
796
|
},
|
|
771
797
|
async () => {
|
|
772
798
|
const sandboxCredentials = getVercelSandboxCredentials();
|
|
@@ -780,13 +806,13 @@ async function createDependencySnapshot(profile, runtime, timeoutMs) {
|
|
|
780
806
|
})
|
|
781
807
|
);
|
|
782
808
|
try {
|
|
783
|
-
await
|
|
784
|
-
await
|
|
785
|
-
return await
|
|
809
|
+
await dependencies(sandbox, value.dependencies);
|
|
810
|
+
await postinstall(sandbox, value.postinstall);
|
|
811
|
+
return await trace(
|
|
786
812
|
"sandbox.snapshot.capture",
|
|
787
813
|
"sandbox.snapshot.capture",
|
|
788
814
|
{
|
|
789
|
-
"app.sandbox.snapshot.dependency_count":
|
|
815
|
+
"app.sandbox.snapshot.dependency_count": value.dependencyCount
|
|
790
816
|
},
|
|
791
817
|
async () => {
|
|
792
818
|
const snapshot = await sandbox.snapshot();
|
|
@@ -802,40 +828,35 @@ async function createDependencySnapshot(profile, runtime, timeoutMs) {
|
|
|
802
828
|
}
|
|
803
829
|
);
|
|
804
830
|
}
|
|
805
|
-
async function withBuildLock(profileHash, callback, canUseCachedSnapshot,
|
|
831
|
+
async function withBuildLock(profileHash, timeoutMs, callback, canUseCachedSnapshot, onWaitingForLock) {
|
|
806
832
|
const state = getStateAdapter();
|
|
807
833
|
await state.connect();
|
|
808
834
|
const lockKey = profileLockKey(profileHash);
|
|
809
|
-
const
|
|
835
|
+
const lockTtlMs = timeoutMs + SNAPSHOT_BUILD_LOCK_BUFFER_MS;
|
|
836
|
+
const tryAcquireLock = async () => await state.acquireLock(lockKey, lockTtlMs);
|
|
810
837
|
let lock = await tryAcquireLock();
|
|
811
838
|
if (lock) {
|
|
812
839
|
try {
|
|
813
|
-
|
|
814
|
-
return {
|
|
815
|
-
snapshotId: result.snapshotId,
|
|
816
|
-
source: result.source,
|
|
817
|
-
waitedForLock: false
|
|
818
|
-
};
|
|
840
|
+
return await callback();
|
|
819
841
|
} finally {
|
|
820
842
|
await state.releaseLock(lock);
|
|
821
843
|
}
|
|
822
844
|
}
|
|
823
|
-
return await
|
|
845
|
+
return await trace(
|
|
824
846
|
"sandbox.snapshot.lock_wait",
|
|
825
847
|
"sandbox.snapshot.lock_wait",
|
|
826
848
|
{
|
|
827
849
|
"app.sandbox.snapshot.profile_hash": profileHash
|
|
828
850
|
},
|
|
829
851
|
async () => {
|
|
830
|
-
await
|
|
831
|
-
const waitUntil = Date.now() +
|
|
852
|
+
await onWaitingForLock?.();
|
|
853
|
+
const waitUntil = Date.now() + lockTtlMs + SNAPSHOT_WAIT_FOR_LOCK_BUFFER_MS;
|
|
832
854
|
while (Date.now() < waitUntil) {
|
|
833
855
|
const cached2 = await getCachedSnapshot(profileHash);
|
|
834
856
|
if (cached2?.snapshotId && canUseCachedSnapshot(cached2)) {
|
|
835
857
|
return {
|
|
836
858
|
snapshotId: cached2.snapshotId,
|
|
837
|
-
source: "
|
|
838
|
-
waitedForLock: true
|
|
859
|
+
source: "cache_hit_after_lock_wait"
|
|
839
860
|
};
|
|
840
861
|
}
|
|
841
862
|
lock = await tryAcquireLock();
|
|
@@ -844,8 +865,7 @@ async function withBuildLock(profileHash, callback, canUseCachedSnapshot, hooks)
|
|
|
844
865
|
const result = await callback();
|
|
845
866
|
return {
|
|
846
867
|
snapshotId: result.snapshotId,
|
|
847
|
-
source: result.source
|
|
848
|
-
waitedForLock: true
|
|
868
|
+
source: result.source === "built" ? "built" : "cache_hit_after_lock_wait"
|
|
849
869
|
};
|
|
850
870
|
} finally {
|
|
851
871
|
await state.releaseLock(lock);
|
|
@@ -857,22 +877,18 @@ async function withBuildLock(profileHash, callback, canUseCachedSnapshot, hooks)
|
|
|
857
877
|
if (cached?.snapshotId && canUseCachedSnapshot(cached)) {
|
|
858
878
|
return {
|
|
859
879
|
snapshotId: cached.snapshotId,
|
|
860
|
-
source: "
|
|
861
|
-
waitedForLock: true
|
|
880
|
+
source: "cache_hit_after_lock_wait"
|
|
862
881
|
};
|
|
863
882
|
}
|
|
864
883
|
throw new Error("Timed out waiting for snapshot build lock");
|
|
865
884
|
}
|
|
866
885
|
);
|
|
867
886
|
}
|
|
868
|
-
function toResolveOutcome(forceRebuild, source
|
|
887
|
+
function toResolveOutcome(forceRebuild, source) {
|
|
869
888
|
if (source === "built") {
|
|
870
889
|
return forceRebuild ? "forced_rebuild" : "rebuilt";
|
|
871
890
|
}
|
|
872
|
-
|
|
873
|
-
return "cache_hit_after_lock_wait";
|
|
874
|
-
}
|
|
875
|
-
return "cache_hit";
|
|
891
|
+
return source;
|
|
876
892
|
}
|
|
877
893
|
function getRebuildReason(params) {
|
|
878
894
|
if (params.forceRebuild) {
|
|
@@ -886,8 +902,8 @@ function getRebuildReason(params) {
|
|
|
886
902
|
}
|
|
887
903
|
return void 0;
|
|
888
904
|
}
|
|
889
|
-
async function
|
|
890
|
-
return await
|
|
905
|
+
async function resolve(params) {
|
|
906
|
+
return await trace(
|
|
891
907
|
"sandbox.snapshot.resolve",
|
|
892
908
|
"sandbox.snapshot.resolve",
|
|
893
909
|
{
|
|
@@ -896,25 +912,24 @@ async function resolveRuntimeDependencySnapshot(params) {
|
|
|
896
912
|
},
|
|
897
913
|
async () => {
|
|
898
914
|
await params.onProgress?.("resolve_start");
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
if (!profile) {
|
|
915
|
+
const currentProfile = create(params.runtime);
|
|
916
|
+
if (!currentProfile) {
|
|
902
917
|
return {
|
|
903
918
|
dependencyCount: 0,
|
|
904
919
|
cacheHit: false,
|
|
905
920
|
resolveOutcome: "no_profile"
|
|
906
921
|
};
|
|
907
922
|
}
|
|
908
|
-
const cached = await getCachedSnapshot(
|
|
923
|
+
const cached = await getCachedSnapshot(currentProfile.hash);
|
|
909
924
|
const cachedNeedsRebuild = Boolean(
|
|
910
|
-
cached?.snapshotId &&
|
|
925
|
+
cached?.snapshotId && isStale(currentProfile, cached.createdAtMs)
|
|
911
926
|
);
|
|
912
927
|
if (!params.forceRebuild && cached?.snapshotId && !cachedNeedsRebuild) {
|
|
913
928
|
await params.onProgress?.("cache_hit");
|
|
914
929
|
return {
|
|
915
930
|
snapshotId: cached.snapshotId,
|
|
916
|
-
profileHash:
|
|
917
|
-
dependencyCount:
|
|
931
|
+
profileHash: currentProfile.hash,
|
|
932
|
+
dependencyCount: currentProfile.dependencyCount,
|
|
918
933
|
cacheHit: true,
|
|
919
934
|
resolveOutcome: "cache_hit"
|
|
920
935
|
};
|
|
@@ -930,60 +945,58 @@ async function resolveRuntimeDependencySnapshot(params) {
|
|
|
930
945
|
if (params.staleSnapshotId) {
|
|
931
946
|
return candidate.snapshotId !== params.staleSnapshotId;
|
|
932
947
|
}
|
|
933
|
-
return candidate.
|
|
948
|
+
return candidate.snapshotId !== cached?.snapshotId;
|
|
934
949
|
}
|
|
935
|
-
return !
|
|
950
|
+
return !isStale(currentProfile, candidate.createdAtMs);
|
|
936
951
|
};
|
|
937
952
|
const lockResult = await withBuildLock(
|
|
938
|
-
|
|
953
|
+
currentProfile.hash,
|
|
954
|
+
params.timeoutMs,
|
|
939
955
|
async () => {
|
|
940
|
-
const latest = await getCachedSnapshot(
|
|
956
|
+
const latest = await getCachedSnapshot(currentProfile.hash);
|
|
941
957
|
if (latest?.snapshotId && canUseCachedSnapshot(latest)) {
|
|
942
958
|
await params.onProgress?.("cache_hit");
|
|
943
959
|
return {
|
|
944
960
|
snapshotId: latest.snapshotId,
|
|
945
|
-
source: "
|
|
961
|
+
source: "cache_hit"
|
|
946
962
|
};
|
|
947
963
|
}
|
|
948
964
|
await params.onProgress?.("building_snapshot");
|
|
949
|
-
const nextSnapshotId = await
|
|
950
|
-
|
|
965
|
+
const nextSnapshotId = await build(
|
|
966
|
+
currentProfile,
|
|
951
967
|
params.runtime,
|
|
952
968
|
params.timeoutMs
|
|
953
969
|
);
|
|
954
970
|
await setCachedSnapshot({
|
|
955
|
-
profileHash:
|
|
971
|
+
profileHash: currentProfile.hash,
|
|
956
972
|
snapshotId: nextSnapshotId,
|
|
957
973
|
runtime: params.runtime,
|
|
958
974
|
createdAtMs: Date.now(),
|
|
959
|
-
dependencyCount:
|
|
975
|
+
dependencyCount: currentProfile.dependencyCount
|
|
960
976
|
});
|
|
961
977
|
await params.onProgress?.("build_complete");
|
|
962
978
|
return { snapshotId: nextSnapshotId, source: "built" };
|
|
963
979
|
},
|
|
964
980
|
canUseCachedSnapshot,
|
|
965
|
-
{
|
|
966
|
-
|
|
967
|
-
await params.onProgress?.("waiting_for_lock");
|
|
968
|
-
}
|
|
981
|
+
async () => {
|
|
982
|
+
await params.onProgress?.("waiting_for_lock");
|
|
969
983
|
}
|
|
970
984
|
);
|
|
971
985
|
return {
|
|
972
986
|
snapshotId: lockResult.snapshotId,
|
|
973
|
-
profileHash:
|
|
974
|
-
dependencyCount:
|
|
987
|
+
profileHash: currentProfile.hash,
|
|
988
|
+
dependencyCount: currentProfile.dependencyCount,
|
|
975
989
|
cacheHit: lockResult.source !== "built",
|
|
976
990
|
resolveOutcome: toResolveOutcome(
|
|
977
991
|
Boolean(params.forceRebuild),
|
|
978
|
-
lockResult.source
|
|
979
|
-
lockResult.waitedForLock
|
|
992
|
+
lockResult.source
|
|
980
993
|
),
|
|
981
994
|
...rebuildReason ? { rebuildReason } : {}
|
|
982
995
|
};
|
|
983
996
|
}
|
|
984
997
|
);
|
|
985
998
|
}
|
|
986
|
-
function
|
|
999
|
+
function isMissingError(error) {
|
|
987
1000
|
const searchable = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
988
1001
|
return searchable.includes("snapshot") && (searchable.includes("not found") || searchable.includes("unknown") || searchable.includes("404"));
|
|
989
1002
|
}
|
|
@@ -1000,10 +1013,10 @@ export {
|
|
|
1000
1013
|
buildNonInteractiveShellScript,
|
|
1001
1014
|
runNonInteractiveCommand,
|
|
1002
1015
|
getSandboxResources,
|
|
1003
|
-
|
|
1016
|
+
create,
|
|
1017
|
+
hash,
|
|
1004
1018
|
bindSandboxFileSystem,
|
|
1005
1019
|
createSandboxSession,
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
isSnapshotMissingError
|
|
1020
|
+
resolve,
|
|
1021
|
+
isMissingError
|
|
1009
1022
|
};
|