@sentry/junior 0.8.0 → 0.9.1
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/chunk-7GLBFSWP.js +839 -0
- package/dist/{chunk-OGLG4WAL.js → chunk-JNHYSCAO.js} +11346 -9421
- package/dist/{chunk-Z5E25LRN.js → chunk-KCLEEKYX.js} +124 -17
- package/dist/{chunk-NNYZHUWR.js → chunk-T7BIFBYH.js} +15 -6
- package/dist/chunk-UPCIISWE.js +678 -0
- package/dist/chunk-VM3CPAZF.js +448 -0
- package/dist/chunk-ZBWWHP6Q.js +1436 -0
- package/dist/{chunk-PY4AI2GZ.js → chunk-ZW4OVKF5.js} +376 -79
- package/dist/cli/check.js +4 -2
- package/dist/cli/snapshot-warmup.js +7 -6
- package/dist/handlers/queue-callback.js +7 -7
- package/dist/handlers/router.d.ts +1 -0
- package/dist/handlers/router.js +523 -85
- package/dist/handlers/webhooks.js +2 -2
- package/dist/next-config.js +1 -1
- package/dist/production-PPQEQOPO.js +15 -0
- package/package.json +12 -14
- package/dist/bot-7SE3TX37.js +0 -19
- package/dist/chunk-H3ZG43WE.js +0 -330
- package/dist/chunk-KT5HARSN.js +0 -164
- package/dist/chunk-RKOO42TW.js +0 -1797
- package/dist/chunk-VW26MOSO.js +0 -522
package/dist/chunk-RKOO42TW.js
DELETED
|
@@ -1,1797 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
discoverInstalledPluginPackageContent,
|
|
3
|
-
discoverProjectRoots
|
|
4
|
-
} from "./chunk-Z5E25LRN.js";
|
|
5
|
-
import {
|
|
6
|
-
logInfo,
|
|
7
|
-
logWarn,
|
|
8
|
-
setSpanAttributes,
|
|
9
|
-
withSpan
|
|
10
|
-
} from "./chunk-PY4AI2GZ.js";
|
|
11
|
-
import {
|
|
12
|
-
parsePluginManifest
|
|
13
|
-
} from "./chunk-VW26MOSO.js";
|
|
14
|
-
|
|
15
|
-
// src/chat/plugins/registry.ts
|
|
16
|
-
import { readFileSync, readdirSync, statSync } from "fs";
|
|
17
|
-
import path2 from "path";
|
|
18
|
-
|
|
19
|
-
// src/chat/home.ts
|
|
20
|
-
import fs from "fs";
|
|
21
|
-
import path from "path";
|
|
22
|
-
function homeDir() {
|
|
23
|
-
return resolveHomeDir();
|
|
24
|
-
}
|
|
25
|
-
function unique(values) {
|
|
26
|
-
return [...new Set(values)];
|
|
27
|
-
}
|
|
28
|
-
function pathExists(targetPath) {
|
|
29
|
-
try {
|
|
30
|
-
fs.accessSync(targetPath);
|
|
31
|
-
return true;
|
|
32
|
-
} catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function hasAnyDataMarkers(appDir) {
|
|
37
|
-
return pathExists(path.join(appDir, "SOUL.md")) || pathExists(path.join(appDir, "ABOUT.md"));
|
|
38
|
-
}
|
|
39
|
-
function scoreAppCandidate(appDir) {
|
|
40
|
-
let score = 0;
|
|
41
|
-
if (pathExists(path.join(appDir, "SOUL.md"))) {
|
|
42
|
-
score += 4;
|
|
43
|
-
}
|
|
44
|
-
if (pathExists(path.join(appDir, "ABOUT.md"))) {
|
|
45
|
-
score += 2;
|
|
46
|
-
}
|
|
47
|
-
if (pathExists(path.join(appDir, "skills"))) {
|
|
48
|
-
score += 1;
|
|
49
|
-
}
|
|
50
|
-
if (pathExists(path.join(appDir, "plugins"))) {
|
|
51
|
-
score += 1;
|
|
52
|
-
}
|
|
53
|
-
return score;
|
|
54
|
-
}
|
|
55
|
-
function resolveCandidateAppDirs(cwd, projectRoots) {
|
|
56
|
-
const roots = projectRoots ?? discoverProjectRoots(cwd);
|
|
57
|
-
const resolved = [];
|
|
58
|
-
const seen = /* @__PURE__ */ new Set();
|
|
59
|
-
for (const root of roots) {
|
|
60
|
-
const appDir = path.resolve(root, "app");
|
|
61
|
-
if (!pathExists(appDir)) {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (seen.has(appDir)) {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
seen.add(appDir);
|
|
68
|
-
resolved.push(appDir);
|
|
69
|
-
}
|
|
70
|
-
return resolved;
|
|
71
|
-
}
|
|
72
|
-
function resolveHomeDir(cwd = process.cwd(), options) {
|
|
73
|
-
const resolvedCwd = path.resolve(cwd);
|
|
74
|
-
const directApp = path.resolve(resolvedCwd, "app");
|
|
75
|
-
if (pathExists(directApp) && hasAnyDataMarkers(directApp)) {
|
|
76
|
-
return directApp;
|
|
77
|
-
}
|
|
78
|
-
const candidates = resolveCandidateAppDirs(
|
|
79
|
-
resolvedCwd,
|
|
80
|
-
options?.projectRoots
|
|
81
|
-
);
|
|
82
|
-
if (candidates.length === 0) {
|
|
83
|
-
return directApp;
|
|
84
|
-
}
|
|
85
|
-
candidates.sort((left, right) => {
|
|
86
|
-
const leftScore = scoreAppCandidate(left);
|
|
87
|
-
const rightScore = scoreAppCandidate(right);
|
|
88
|
-
if (leftScore !== rightScore) {
|
|
89
|
-
return rightScore - leftScore;
|
|
90
|
-
}
|
|
91
|
-
const leftDistance = path.relative(resolvedCwd, left).split(path.sep).length;
|
|
92
|
-
const rightDistance = path.relative(resolvedCwd, right).split(path.sep).length;
|
|
93
|
-
if (leftDistance !== rightDistance) {
|
|
94
|
-
return leftDistance - rightDistance;
|
|
95
|
-
}
|
|
96
|
-
return left.localeCompare(right);
|
|
97
|
-
});
|
|
98
|
-
return candidates[0];
|
|
99
|
-
}
|
|
100
|
-
function resolveContentRoots(subdir) {
|
|
101
|
-
if (subdir === "data") {
|
|
102
|
-
return [homeDir()];
|
|
103
|
-
}
|
|
104
|
-
return [path.join(homeDir(), subdir)];
|
|
105
|
-
}
|
|
106
|
-
function dataRoots() {
|
|
107
|
-
return unique(resolveContentRoots("data"));
|
|
108
|
-
}
|
|
109
|
-
function skillRoots() {
|
|
110
|
-
return unique(resolveContentRoots("skills"));
|
|
111
|
-
}
|
|
112
|
-
function pluginRoots() {
|
|
113
|
-
return unique(resolveContentRoots("plugins"));
|
|
114
|
-
}
|
|
115
|
-
function soulPathCandidates() {
|
|
116
|
-
const candidates = dataRoots().map((root) => path.join(root, "SOUL.md"));
|
|
117
|
-
return unique(candidates);
|
|
118
|
-
}
|
|
119
|
-
function aboutPathCandidates() {
|
|
120
|
-
const candidates = dataRoots().map((root) => path.join(root, "ABOUT.md"));
|
|
121
|
-
return unique(candidates);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// src/chat/plugins/github-app-broker.ts
|
|
125
|
-
import { createPrivateKey, createSign, randomUUID } from "crypto";
|
|
126
|
-
|
|
127
|
-
// src/chat/plugins/auth-token-placeholder.ts
|
|
128
|
-
var DEFAULT_PLACEHOLDERS = {
|
|
129
|
-
"oauth-bearer": "host_managed_credential",
|
|
130
|
-
"github-app": "ghp_host_managed_credential"
|
|
131
|
-
};
|
|
132
|
-
function resolveAuthTokenPlaceholder(credentials) {
|
|
133
|
-
return credentials.authTokenPlaceholder?.trim() || DEFAULT_PLACEHOLDERS[credentials.type];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// src/chat/plugins/github-app-broker.ts
|
|
137
|
-
var MAX_LEASE_MS = 60 * 60 * 1e3;
|
|
138
|
-
function normalizeTargetScope(target) {
|
|
139
|
-
const owner = target?.owner?.trim().toLowerCase();
|
|
140
|
-
const repo = target?.repo?.trim().toLowerCase();
|
|
141
|
-
if (!owner || !repo) {
|
|
142
|
-
return "all";
|
|
143
|
-
}
|
|
144
|
-
return `${owner}/${repo}`;
|
|
145
|
-
}
|
|
146
|
-
function base64Url(input) {
|
|
147
|
-
return Buffer.from(input).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
148
|
-
}
|
|
149
|
-
function normalizePrivateKey(raw) {
|
|
150
|
-
let normalized = raw.trim();
|
|
151
|
-
if (normalized.startsWith('"') && normalized.endsWith('"') || normalized.startsWith("'") && normalized.endsWith("'")) {
|
|
152
|
-
normalized = normalized.slice(1, -1);
|
|
153
|
-
}
|
|
154
|
-
normalized = normalized.replace(/\r\n/g, "\n");
|
|
155
|
-
if (normalized.includes("\\n")) {
|
|
156
|
-
normalized = normalized.replace(/\\n/g, "\n");
|
|
157
|
-
}
|
|
158
|
-
if (!normalized.includes("-----BEGIN")) {
|
|
159
|
-
try {
|
|
160
|
-
const decoded = Buffer.from(normalized, "base64").toString("utf8").trim();
|
|
161
|
-
if (decoded.includes("-----BEGIN")) {
|
|
162
|
-
normalized = decoded;
|
|
163
|
-
}
|
|
164
|
-
} catch {
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return normalized;
|
|
168
|
-
}
|
|
169
|
-
function getPrivateKey(envName) {
|
|
170
|
-
const raw = process.env[envName];
|
|
171
|
-
if (!raw) {
|
|
172
|
-
throw new Error(`Missing ${envName}`);
|
|
173
|
-
}
|
|
174
|
-
const normalized = normalizePrivateKey(raw);
|
|
175
|
-
let key;
|
|
176
|
-
try {
|
|
177
|
-
key = createPrivateKey({ key: normalized, format: "pem" });
|
|
178
|
-
} catch {
|
|
179
|
-
throw new Error(
|
|
180
|
-
`Invalid ${envName}: expected a PEM-encoded RSA private key (raw PEM, escaped newlines, or base64-encoded PEM)`
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
if (key.asymmetricKeyType !== "rsa") {
|
|
184
|
-
throw new Error(
|
|
185
|
-
`Invalid ${envName}: GitHub App signing requires an RSA private key`
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
return key;
|
|
189
|
-
}
|
|
190
|
-
function createAppJwt(appId, privateKeyEnv) {
|
|
191
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
192
|
-
const header = { alg: "RS256", typ: "JWT" };
|
|
193
|
-
const payload = { iat: now - 60, exp: now + 9 * 60, iss: appId };
|
|
194
|
-
const encodedHeader = base64Url(JSON.stringify(header));
|
|
195
|
-
const encodedPayload = base64Url(JSON.stringify(payload));
|
|
196
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
197
|
-
const signer = createSign("RSA-SHA256");
|
|
198
|
-
signer.update(signingInput);
|
|
199
|
-
signer.end();
|
|
200
|
-
const signature = signer.sign(getPrivateKey(privateKeyEnv)).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
201
|
-
return `${signingInput}.${signature}`;
|
|
202
|
-
}
|
|
203
|
-
async function githubRequest(apiBase, path3, params) {
|
|
204
|
-
const response = await fetch(`${apiBase}${path3}`, {
|
|
205
|
-
method: params.method ?? "GET",
|
|
206
|
-
headers: {
|
|
207
|
-
Accept: "application/vnd.github+json",
|
|
208
|
-
Authorization: `Bearer ${params.token}`,
|
|
209
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
210
|
-
...params.body ? { "Content-Type": "application/json" } : {}
|
|
211
|
-
},
|
|
212
|
-
...params.body ? { body: JSON.stringify(params.body) } : {}
|
|
213
|
-
});
|
|
214
|
-
const text = await response.text();
|
|
215
|
-
let parsed = void 0;
|
|
216
|
-
if (text) {
|
|
217
|
-
try {
|
|
218
|
-
parsed = JSON.parse(text);
|
|
219
|
-
} catch {
|
|
220
|
-
parsed = void 0;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if (!response.ok) {
|
|
224
|
-
const message = parsed && typeof parsed === "object" && "message" in parsed && typeof parsed.message === "string" ? parsed.message : `GitHub API error ${response.status}`;
|
|
225
|
-
throw new Error(message);
|
|
226
|
-
}
|
|
227
|
-
return parsed;
|
|
228
|
-
}
|
|
229
|
-
function capabilityToPermissions(capability, pluginName) {
|
|
230
|
-
if (capability === `${pluginName}.issues.read`) {
|
|
231
|
-
return { issues: "read" };
|
|
232
|
-
}
|
|
233
|
-
if (capability === `${pluginName}.issues.write` || capability === `${pluginName}.issues.comment` || capability === `${pluginName}.labels.write`) {
|
|
234
|
-
return { issues: "write" };
|
|
235
|
-
}
|
|
236
|
-
throw new Error(`Unsupported GitHub capability: ${capability}`);
|
|
237
|
-
}
|
|
238
|
-
function createGitHubAppBroker(manifest, credentials) {
|
|
239
|
-
const tokenCache = /* @__PURE__ */ new Map();
|
|
240
|
-
const provider = manifest.name;
|
|
241
|
-
const {
|
|
242
|
-
apiDomains,
|
|
243
|
-
apiHeaders,
|
|
244
|
-
authTokenEnv,
|
|
245
|
-
appIdEnv,
|
|
246
|
-
privateKeyEnv,
|
|
247
|
-
installationIdEnv
|
|
248
|
-
} = credentials;
|
|
249
|
-
const apiBase = `https://${apiDomains[0]}`;
|
|
250
|
-
const placeholder = resolveAuthTokenPlaceholder(credentials);
|
|
251
|
-
return {
|
|
252
|
-
async issue(input) {
|
|
253
|
-
const permissions = capabilityToPermissions(input.capability, provider);
|
|
254
|
-
const appId = process.env[appIdEnv];
|
|
255
|
-
if (!appId) {
|
|
256
|
-
throw new Error(`Missing ${appIdEnv}`);
|
|
257
|
-
}
|
|
258
|
-
const installationIdRaw = process.env[installationIdEnv]?.trim();
|
|
259
|
-
if (!installationIdRaw) {
|
|
260
|
-
throw new Error(`Missing ${installationIdEnv}`);
|
|
261
|
-
}
|
|
262
|
-
const installationId = Number(installationIdRaw);
|
|
263
|
-
if (!Number.isFinite(installationId)) {
|
|
264
|
-
throw new Error(`Invalid ${installationIdEnv}`);
|
|
265
|
-
}
|
|
266
|
-
const targetScope = normalizeTargetScope(input.target);
|
|
267
|
-
const cacheKey = `${installationId}:${input.capability}:${targetScope}`;
|
|
268
|
-
const cached = tokenCache.get(cacheKey);
|
|
269
|
-
const now = Date.now();
|
|
270
|
-
if (cached && cached.expiresAt - now > 2 * 60 * 1e3) {
|
|
271
|
-
return {
|
|
272
|
-
id: randomUUID(),
|
|
273
|
-
provider,
|
|
274
|
-
capability: input.capability,
|
|
275
|
-
env: { [authTokenEnv]: placeholder },
|
|
276
|
-
headerTransforms: apiDomains.map((domain) => ({
|
|
277
|
-
domain,
|
|
278
|
-
headers: {
|
|
279
|
-
...apiHeaders ?? {},
|
|
280
|
-
Authorization: `Bearer ${cached.token}`
|
|
281
|
-
}
|
|
282
|
-
})),
|
|
283
|
-
expiresAt: new Date(cached.expiresAt).toISOString(),
|
|
284
|
-
metadata: {
|
|
285
|
-
installationId: String(cached.installationId),
|
|
286
|
-
targetScope,
|
|
287
|
-
reason: input.reason
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
const appJwt = createAppJwt(appId, privateKeyEnv);
|
|
292
|
-
const repositoryName = input.target?.repo?.trim().toLowerCase();
|
|
293
|
-
const tokenRequestBody = {
|
|
294
|
-
permissions
|
|
295
|
-
};
|
|
296
|
-
if (repositoryName) {
|
|
297
|
-
tokenRequestBody.repositories = [repositoryName];
|
|
298
|
-
}
|
|
299
|
-
const accessTokenResponse = await githubRequest(apiBase, `/app/installations/${installationId}/access_tokens`, {
|
|
300
|
-
method: "POST",
|
|
301
|
-
token: appJwt,
|
|
302
|
-
body: tokenRequestBody
|
|
303
|
-
});
|
|
304
|
-
const providerExpiresAtMs = Date.parse(accessTokenResponse.expires_at);
|
|
305
|
-
const expiresAtMs = Math.min(
|
|
306
|
-
providerExpiresAtMs,
|
|
307
|
-
Date.now() + MAX_LEASE_MS
|
|
308
|
-
);
|
|
309
|
-
tokenCache.set(cacheKey, {
|
|
310
|
-
installationId,
|
|
311
|
-
token: accessTokenResponse.token,
|
|
312
|
-
expiresAt: expiresAtMs
|
|
313
|
-
});
|
|
314
|
-
return {
|
|
315
|
-
id: randomUUID(),
|
|
316
|
-
provider,
|
|
317
|
-
capability: input.capability,
|
|
318
|
-
env: { [authTokenEnv]: placeholder },
|
|
319
|
-
headerTransforms: apiDomains.map((domain) => ({
|
|
320
|
-
domain,
|
|
321
|
-
headers: {
|
|
322
|
-
...apiHeaders ?? {},
|
|
323
|
-
Authorization: `Bearer ${accessTokenResponse.token}`
|
|
324
|
-
}
|
|
325
|
-
})),
|
|
326
|
-
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
327
|
-
metadata: {
|
|
328
|
-
installationId: String(installationId),
|
|
329
|
-
targetScope,
|
|
330
|
-
reason: input.reason
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// src/chat/plugins/oauth-bearer-broker.ts
|
|
338
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
339
|
-
|
|
340
|
-
// src/chat/credentials/broker.ts
|
|
341
|
-
var CredentialUnavailableError = class extends Error {
|
|
342
|
-
provider;
|
|
343
|
-
constructor(provider, message) {
|
|
344
|
-
super(message);
|
|
345
|
-
this.name = "CredentialUnavailableError";
|
|
346
|
-
this.provider = provider;
|
|
347
|
-
}
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
// src/chat/plugins/oauth-request.ts
|
|
351
|
-
var DEFAULT_TOKEN_CONTENT_TYPE = "application/x-www-form-urlencoded";
|
|
352
|
-
function requireNonEmptyTokenField(data, field) {
|
|
353
|
-
const value = data[field];
|
|
354
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
355
|
-
throw new Error(`OAuth token response missing ${field}`);
|
|
356
|
-
}
|
|
357
|
-
return value;
|
|
358
|
-
}
|
|
359
|
-
function contentTypeToBody(contentType, payload) {
|
|
360
|
-
const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase();
|
|
361
|
-
if (!mediaType || mediaType === DEFAULT_TOKEN_CONTENT_TYPE) {
|
|
362
|
-
return new URLSearchParams(payload);
|
|
363
|
-
}
|
|
364
|
-
if (mediaType === "application/json" || mediaType.endsWith("+json")) {
|
|
365
|
-
return JSON.stringify(payload);
|
|
366
|
-
}
|
|
367
|
-
throw new Error(`Unsupported OAuth token Content-Type: ${contentType}`);
|
|
368
|
-
}
|
|
369
|
-
function buildOAuthTokenRequest(input) {
|
|
370
|
-
const headers = new Headers({ Accept: "application/json" });
|
|
371
|
-
for (const [name, value] of Object.entries(input.tokenExtraHeaders ?? {})) {
|
|
372
|
-
headers.set(name, value);
|
|
373
|
-
}
|
|
374
|
-
if (!headers.has("Content-Type")) {
|
|
375
|
-
headers.set("Content-Type", DEFAULT_TOKEN_CONTENT_TYPE);
|
|
376
|
-
}
|
|
377
|
-
const payload = { ...input.payload };
|
|
378
|
-
if (input.tokenAuthMethod === "basic") {
|
|
379
|
-
headers.set(
|
|
380
|
-
"Authorization",
|
|
381
|
-
`Basic ${Buffer.from(`${input.clientId}:${input.clientSecret}`).toString("base64")}`
|
|
382
|
-
);
|
|
383
|
-
} else {
|
|
384
|
-
payload.client_id = input.clientId;
|
|
385
|
-
payload.client_secret = input.clientSecret;
|
|
386
|
-
}
|
|
387
|
-
const contentType = headers.get("Content-Type") ?? DEFAULT_TOKEN_CONTENT_TYPE;
|
|
388
|
-
const serializedHeaders = {};
|
|
389
|
-
headers.forEach((value, key) => {
|
|
390
|
-
serializedHeaders[key] = value;
|
|
391
|
-
});
|
|
392
|
-
return {
|
|
393
|
-
headers: serializedHeaders,
|
|
394
|
-
body: contentTypeToBody(contentType, payload)
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
function parseOAuthTokenResponse(data) {
|
|
398
|
-
const accessToken = requireNonEmptyTokenField(data, "access_token");
|
|
399
|
-
const refreshToken = requireNonEmptyTokenField(data, "refresh_token");
|
|
400
|
-
const expiresIn = data.expires_in;
|
|
401
|
-
if (expiresIn === void 0) {
|
|
402
|
-
return { accessToken, refreshToken };
|
|
403
|
-
}
|
|
404
|
-
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
405
|
-
throw new Error("OAuth token response returned invalid expires_in");
|
|
406
|
-
}
|
|
407
|
-
return {
|
|
408
|
-
accessToken,
|
|
409
|
-
refreshToken,
|
|
410
|
-
expiresAt: Date.now() + expiresIn * 1e3
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// src/chat/plugins/oauth-bearer-broker.ts
|
|
415
|
-
var MAX_LEASE_MS2 = 60 * 60 * 1e3;
|
|
416
|
-
var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
417
|
-
async function refreshAccessToken(refreshToken, oauth) {
|
|
418
|
-
const clientId = process.env[oauth.clientIdEnv]?.trim();
|
|
419
|
-
const clientSecret = process.env[oauth.clientSecretEnv]?.trim();
|
|
420
|
-
if (!clientId || !clientSecret) {
|
|
421
|
-
throw new Error(
|
|
422
|
-
`Missing ${oauth.clientIdEnv} or ${oauth.clientSecretEnv} for token refresh`
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
const request = buildOAuthTokenRequest({
|
|
426
|
-
clientId,
|
|
427
|
-
clientSecret,
|
|
428
|
-
payload: {
|
|
429
|
-
grant_type: "refresh_token",
|
|
430
|
-
refresh_token: refreshToken
|
|
431
|
-
},
|
|
432
|
-
tokenAuthMethod: oauth.tokenAuthMethod,
|
|
433
|
-
tokenExtraHeaders: oauth.tokenExtraHeaders
|
|
434
|
-
});
|
|
435
|
-
const response = await fetch(oauth.tokenEndpoint, {
|
|
436
|
-
method: "POST",
|
|
437
|
-
headers: request.headers,
|
|
438
|
-
body: request.body
|
|
439
|
-
});
|
|
440
|
-
if (!response.ok) {
|
|
441
|
-
throw new Error(`Token refresh failed: ${response.status}`);
|
|
442
|
-
}
|
|
443
|
-
const data = await response.json();
|
|
444
|
-
return parseOAuthTokenResponse(data);
|
|
445
|
-
}
|
|
446
|
-
function getLeaseExpiry(expiresAt) {
|
|
447
|
-
return expiresAt ? Math.min(expiresAt, Date.now() + MAX_LEASE_MS2) : Date.now() + MAX_LEASE_MS2;
|
|
448
|
-
}
|
|
449
|
-
function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
450
|
-
const provider = manifest.name;
|
|
451
|
-
const supportedCapabilities = new Set(manifest.capabilities);
|
|
452
|
-
const { apiDomains, apiHeaders, authTokenEnv } = credentials;
|
|
453
|
-
const authTokenPlaceholder = resolveAuthTokenPlaceholder(credentials);
|
|
454
|
-
function buildLease(token, capability, expiresAtMs, reason) {
|
|
455
|
-
return {
|
|
456
|
-
id: randomUUID2(),
|
|
457
|
-
provider,
|
|
458
|
-
capability,
|
|
459
|
-
env: { [authTokenEnv]: authTokenPlaceholder },
|
|
460
|
-
headerTransforms: apiDomains.map((domain) => ({
|
|
461
|
-
domain,
|
|
462
|
-
headers: { ...apiHeaders ?? {}, Authorization: `Bearer ${token}` }
|
|
463
|
-
})),
|
|
464
|
-
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
465
|
-
metadata: { reason }
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
return {
|
|
469
|
-
async issue(input) {
|
|
470
|
-
if (!supportedCapabilities.has(input.capability)) {
|
|
471
|
-
throw new Error(
|
|
472
|
-
`Unsupported ${provider} capability: ${input.capability}`
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
const envToken = process.env[authTokenEnv]?.trim();
|
|
476
|
-
const oauth = manifest.oauth;
|
|
477
|
-
if (!oauth) {
|
|
478
|
-
if (envToken) {
|
|
479
|
-
return buildLease(
|
|
480
|
-
envToken,
|
|
481
|
-
input.capability,
|
|
482
|
-
Date.now() + MAX_LEASE_MS2,
|
|
483
|
-
input.reason
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
throw new CredentialUnavailableError(
|
|
487
|
-
provider,
|
|
488
|
-
`No ${provider} credentials available.`
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
if (input.requesterId) {
|
|
492
|
-
const stored = await deps.userTokenStore.get(
|
|
493
|
-
input.requesterId,
|
|
494
|
-
provider
|
|
495
|
-
);
|
|
496
|
-
if (stored) {
|
|
497
|
-
const now = Date.now();
|
|
498
|
-
if (stored.expiresAt !== void 0 && stored.expiresAt - now < REFRESH_BUFFER_MS) {
|
|
499
|
-
try {
|
|
500
|
-
const refreshed = await refreshAccessToken(
|
|
501
|
-
stored.refreshToken,
|
|
502
|
-
oauth
|
|
503
|
-
);
|
|
504
|
-
await deps.userTokenStore.set(
|
|
505
|
-
input.requesterId,
|
|
506
|
-
provider,
|
|
507
|
-
refreshed
|
|
508
|
-
);
|
|
509
|
-
return buildLease(
|
|
510
|
-
refreshed.accessToken,
|
|
511
|
-
input.capability,
|
|
512
|
-
getLeaseExpiry(refreshed.expiresAt),
|
|
513
|
-
input.reason
|
|
514
|
-
);
|
|
515
|
-
} catch {
|
|
516
|
-
if (stored.expiresAt > Date.now()) {
|
|
517
|
-
return buildLease(
|
|
518
|
-
stored.accessToken,
|
|
519
|
-
input.capability,
|
|
520
|
-
getLeaseExpiry(stored.expiresAt),
|
|
521
|
-
input.reason
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
throw new CredentialUnavailableError(
|
|
525
|
-
provider,
|
|
526
|
-
`Your ${provider} connection has expired.`
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
if (stored.expiresAt === void 0 || stored.expiresAt > Date.now()) {
|
|
531
|
-
return buildLease(
|
|
532
|
-
stored.accessToken,
|
|
533
|
-
input.capability,
|
|
534
|
-
getLeaseExpiry(stored.expiresAt),
|
|
535
|
-
input.reason
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
throw new CredentialUnavailableError(
|
|
539
|
-
provider,
|
|
540
|
-
`Your ${provider} connection has expired.`
|
|
541
|
-
);
|
|
542
|
-
}
|
|
543
|
-
throw new CredentialUnavailableError(
|
|
544
|
-
provider,
|
|
545
|
-
`No ${provider} credentials available.`
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
if (envToken) {
|
|
549
|
-
return buildLease(
|
|
550
|
-
envToken,
|
|
551
|
-
input.capability,
|
|
552
|
-
getLeaseExpiry(),
|
|
553
|
-
input.reason
|
|
554
|
-
);
|
|
555
|
-
}
|
|
556
|
-
throw new CredentialUnavailableError(
|
|
557
|
-
provider,
|
|
558
|
-
`No ${provider} credentials available.`
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// src/chat/plugins/registry.ts
|
|
565
|
-
var pluginDefinitions = [];
|
|
566
|
-
var capabilityToPlugin = /* @__PURE__ */ new Map();
|
|
567
|
-
var pluginConfigKeys = /* @__PURE__ */ new Set();
|
|
568
|
-
var pluginsByName = /* @__PURE__ */ new Map();
|
|
569
|
-
var packageSkillRoots = /* @__PURE__ */ new Set();
|
|
570
|
-
var pluginsLoaded = false;
|
|
571
|
-
function registerPluginManifest(raw, pluginDir) {
|
|
572
|
-
const manifest = parsePluginManifest(raw, pluginDir);
|
|
573
|
-
if (pluginsByName.has(manifest.name)) {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
for (const cap of manifest.capabilities) {
|
|
577
|
-
if (capabilityToPlugin.has(cap)) {
|
|
578
|
-
throw new Error(
|
|
579
|
-
`Duplicate capability "${cap}" in plugin "${manifest.name}"`
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
const definition = {
|
|
584
|
-
manifest,
|
|
585
|
-
dir: pluginDir,
|
|
586
|
-
skillsDir: path2.join(pluginDir, "skills")
|
|
587
|
-
};
|
|
588
|
-
pluginDefinitions.push(definition);
|
|
589
|
-
pluginsByName.set(manifest.name, definition);
|
|
590
|
-
for (const cap of manifest.capabilities) {
|
|
591
|
-
capabilityToPlugin.set(cap, definition);
|
|
592
|
-
}
|
|
593
|
-
for (const key of manifest.configKeys) {
|
|
594
|
-
pluginConfigKeys.add(key);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
function loadPlugins() {
|
|
598
|
-
if (pluginsLoaded) return;
|
|
599
|
-
pluginsLoaded = true;
|
|
600
|
-
const packagedContent = discoverInstalledPluginPackageContent();
|
|
601
|
-
const localRoots = pluginRoots();
|
|
602
|
-
const roots = [...localRoots, ...packagedContent.manifestRoots];
|
|
603
|
-
for (const pluginsRoot of roots) {
|
|
604
|
-
let entries;
|
|
605
|
-
let rootStat;
|
|
606
|
-
try {
|
|
607
|
-
rootStat = statSync(pluginsRoot);
|
|
608
|
-
} catch (error) {
|
|
609
|
-
logWarn(
|
|
610
|
-
"plugin_root_read_failed",
|
|
611
|
-
{},
|
|
612
|
-
{
|
|
613
|
-
"file.directory": pluginsRoot,
|
|
614
|
-
"error.message": error instanceof Error ? error.message : String(error)
|
|
615
|
-
},
|
|
616
|
-
"Failed to read plugin root"
|
|
617
|
-
);
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
if (rootStat.isDirectory()) {
|
|
621
|
-
const manifestPath = path2.join(pluginsRoot, "plugin.yaml");
|
|
622
|
-
let hasRootManifest = false;
|
|
623
|
-
try {
|
|
624
|
-
hasRootManifest = statSync(manifestPath).isFile();
|
|
625
|
-
} catch {
|
|
626
|
-
hasRootManifest = false;
|
|
627
|
-
}
|
|
628
|
-
if (hasRootManifest) {
|
|
629
|
-
const rawRootManifest = readFileSync(manifestPath, "utf8");
|
|
630
|
-
registerPluginManifest(rawRootManifest, pluginsRoot);
|
|
631
|
-
continue;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
try {
|
|
635
|
-
entries = readdirSync(pluginsRoot);
|
|
636
|
-
} catch (error) {
|
|
637
|
-
logWarn(
|
|
638
|
-
"plugin_root_read_failed",
|
|
639
|
-
{},
|
|
640
|
-
{
|
|
641
|
-
"file.directory": pluginsRoot,
|
|
642
|
-
"error.message": error instanceof Error ? error.message : String(error)
|
|
643
|
-
},
|
|
644
|
-
"Failed to read plugin root"
|
|
645
|
-
);
|
|
646
|
-
continue;
|
|
647
|
-
}
|
|
648
|
-
for (const entry of entries.sort()) {
|
|
649
|
-
const pluginDir = path2.join(pluginsRoot, entry);
|
|
650
|
-
try {
|
|
651
|
-
const stat = statSync(pluginDir);
|
|
652
|
-
if (!stat.isDirectory()) continue;
|
|
653
|
-
} catch {
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
const manifestPath = path2.join(pluginDir, "plugin.yaml");
|
|
657
|
-
let raw;
|
|
658
|
-
try {
|
|
659
|
-
raw = readFileSync(manifestPath, "utf8");
|
|
660
|
-
} catch {
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
registerPluginManifest(raw, pluginDir);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
for (const skillRoot of packagedContent.skillRoots) {
|
|
667
|
-
packageSkillRoots.add(skillRoot);
|
|
668
|
-
}
|
|
669
|
-
logInfo(
|
|
670
|
-
"plugins_loaded",
|
|
671
|
-
{},
|
|
672
|
-
{
|
|
673
|
-
"file.directories": [...localRoots, ...packagedContent.manifestRoots],
|
|
674
|
-
"app.plugin.count": pluginDefinitions.length,
|
|
675
|
-
"app.plugin.names": pluginDefinitions.map((plugin) => plugin.manifest.name).sort(),
|
|
676
|
-
"app.plugin.package_skill_roots": [...packageSkillRoots].sort()
|
|
677
|
-
},
|
|
678
|
-
"Loaded plugins"
|
|
679
|
-
);
|
|
680
|
-
}
|
|
681
|
-
function ensurePluginsLoaded() {
|
|
682
|
-
loadPlugins();
|
|
683
|
-
}
|
|
684
|
-
loadPlugins();
|
|
685
|
-
function getPluginCapabilityProviders() {
|
|
686
|
-
ensurePluginsLoaded();
|
|
687
|
-
return pluginDefinitions.map((plugin) => ({
|
|
688
|
-
provider: plugin.manifest.name,
|
|
689
|
-
capabilities: [...plugin.manifest.capabilities],
|
|
690
|
-
configKeys: [...plugin.manifest.configKeys],
|
|
691
|
-
...plugin.manifest.target ? { target: { ...plugin.manifest.target } } : {}
|
|
692
|
-
}));
|
|
693
|
-
}
|
|
694
|
-
function getPluginProviders() {
|
|
695
|
-
ensurePluginsLoaded();
|
|
696
|
-
return [...pluginDefinitions];
|
|
697
|
-
}
|
|
698
|
-
function getPluginRuntimeDependencies() {
|
|
699
|
-
ensurePluginsLoaded();
|
|
700
|
-
const seen = /* @__PURE__ */ new Set();
|
|
701
|
-
const deps = [];
|
|
702
|
-
for (const plugin of pluginDefinitions) {
|
|
703
|
-
for (const dep of plugin.manifest.runtimeDependencies ?? []) {
|
|
704
|
-
const key = dep.type === "npm" ? `${dep.type}:${dep.package}:${dep.version}` : "package" in dep ? `${dep.type}:package:${dep.package}` : `${dep.type}:url:${dep.url}:${dep.sha256}`;
|
|
705
|
-
if (seen.has(key)) {
|
|
706
|
-
continue;
|
|
707
|
-
}
|
|
708
|
-
seen.add(key);
|
|
709
|
-
deps.push(dep);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
return deps.sort((left, right) => {
|
|
713
|
-
if (left.type !== right.type) {
|
|
714
|
-
return left.type.localeCompare(right.type);
|
|
715
|
-
}
|
|
716
|
-
const leftIdentity = "package" in left ? `package:${left.package}` : `url:${left.url}:${left.sha256}`;
|
|
717
|
-
const rightIdentity = "package" in right ? `package:${right.package}` : `url:${right.url}:${right.sha256}`;
|
|
718
|
-
if (leftIdentity !== rightIdentity) {
|
|
719
|
-
return leftIdentity.localeCompare(rightIdentity);
|
|
720
|
-
}
|
|
721
|
-
if (left.type === "npm" && right.type === "npm") {
|
|
722
|
-
return left.version.localeCompare(right.version);
|
|
723
|
-
}
|
|
724
|
-
return 0;
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
function getPluginRuntimePostinstall() {
|
|
728
|
-
ensurePluginsLoaded();
|
|
729
|
-
const commands = [];
|
|
730
|
-
for (const plugin of pluginDefinitions) {
|
|
731
|
-
for (const command of plugin.manifest.runtimePostinstall ?? []) {
|
|
732
|
-
commands.push({
|
|
733
|
-
cmd: command.cmd,
|
|
734
|
-
...command.args ? { args: [...command.args] } : {},
|
|
735
|
-
...command.sudo !== void 0 ? { sudo: command.sudo } : {}
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
return commands;
|
|
740
|
-
}
|
|
741
|
-
function getPluginOAuthConfig(provider) {
|
|
742
|
-
ensurePluginsLoaded();
|
|
743
|
-
const plugin = pluginsByName.get(provider);
|
|
744
|
-
if (!plugin?.manifest.oauth) return void 0;
|
|
745
|
-
const oauth = plugin.manifest.oauth;
|
|
746
|
-
return {
|
|
747
|
-
clientIdEnv: oauth.clientIdEnv,
|
|
748
|
-
clientSecretEnv: oauth.clientSecretEnv,
|
|
749
|
-
authorizeEndpoint: oauth.authorizeEndpoint,
|
|
750
|
-
tokenEndpoint: oauth.tokenEndpoint,
|
|
751
|
-
...oauth.scope ? { scope: oauth.scope } : {},
|
|
752
|
-
...oauth.authorizeParams ? { authorizeParams: { ...oauth.authorizeParams } } : {},
|
|
753
|
-
...oauth.tokenAuthMethod ? { tokenAuthMethod: oauth.tokenAuthMethod } : {},
|
|
754
|
-
...oauth.tokenExtraHeaders ? { tokenExtraHeaders: { ...oauth.tokenExtraHeaders } } : {},
|
|
755
|
-
callbackPath: `/api/oauth/callback/${plugin.manifest.name}`
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
function getPluginSkillRoots() {
|
|
759
|
-
ensurePluginsLoaded();
|
|
760
|
-
return [
|
|
761
|
-
.../* @__PURE__ */ new Set([
|
|
762
|
-
...pluginDefinitions.map((plugin) => plugin.skillsDir),
|
|
763
|
-
...packageSkillRoots
|
|
764
|
-
])
|
|
765
|
-
];
|
|
766
|
-
}
|
|
767
|
-
function isPluginProvider(provider) {
|
|
768
|
-
ensurePluginsLoaded();
|
|
769
|
-
return pluginsByName.has(provider);
|
|
770
|
-
}
|
|
771
|
-
function createPluginBroker(provider, deps) {
|
|
772
|
-
ensurePluginsLoaded();
|
|
773
|
-
const plugin = pluginsByName.get(provider);
|
|
774
|
-
if (!plugin) {
|
|
775
|
-
throw new Error(`Unknown plugin provider: "${provider}"`);
|
|
776
|
-
}
|
|
777
|
-
const { credentials, name } = plugin.manifest;
|
|
778
|
-
if (!credentials) {
|
|
779
|
-
throw new Error(`Provider "${name}" has no credentials configured`);
|
|
780
|
-
}
|
|
781
|
-
let broker;
|
|
782
|
-
if (credentials.type === "oauth-bearer") {
|
|
783
|
-
broker = createOAuthBearerBroker(plugin.manifest, credentials, deps);
|
|
784
|
-
} else if (credentials.type === "github-app") {
|
|
785
|
-
broker = createGitHubAppBroker(plugin.manifest, credentials);
|
|
786
|
-
} else {
|
|
787
|
-
throw new Error(`Unsupported credentials type for plugin "${name}"`);
|
|
788
|
-
}
|
|
789
|
-
setSpanAttributes({
|
|
790
|
-
"app.plugin.name": name,
|
|
791
|
-
"app.plugin.capabilities": plugin.manifest.capabilities,
|
|
792
|
-
"app.plugin.has_oauth": Boolean(plugin.manifest.oauth)
|
|
793
|
-
});
|
|
794
|
-
return broker;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// src/chat/config.ts
|
|
798
|
-
var MIN_AGENT_TURN_TIMEOUT_MS = 10 * 1e3;
|
|
799
|
-
var DEFAULT_AGENT_TURN_TIMEOUT_MS = 12 * 60 * 1e3;
|
|
800
|
-
var DEFAULT_QUEUE_CALLBACK_MAX_DURATION_SECONDS = 800;
|
|
801
|
-
var TURN_TIMEOUT_BUFFER_SECONDS = 20;
|
|
802
|
-
function parseAgentTurnTimeoutMs(rawValue, maxTimeoutMs) {
|
|
803
|
-
const value = Number.parseInt(rawValue ?? "", 10);
|
|
804
|
-
if (Number.isNaN(value)) {
|
|
805
|
-
return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, Math.min(DEFAULT_AGENT_TURN_TIMEOUT_MS, maxTimeoutMs));
|
|
806
|
-
}
|
|
807
|
-
return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, Math.min(value, maxTimeoutMs));
|
|
808
|
-
}
|
|
809
|
-
function resolveQueueCallbackMaxDurationSeconds() {
|
|
810
|
-
const value = Number.parseInt(process.env.QUEUE_CALLBACK_MAX_DURATION_SECONDS ?? "", 10);
|
|
811
|
-
if (Number.isNaN(value) || value <= 0) {
|
|
812
|
-
return DEFAULT_QUEUE_CALLBACK_MAX_DURATION_SECONDS;
|
|
813
|
-
}
|
|
814
|
-
return value;
|
|
815
|
-
}
|
|
816
|
-
function resolveMaxTurnTimeoutMs(queueCallbackMaxDurationSeconds) {
|
|
817
|
-
const budgetSeconds = queueCallbackMaxDurationSeconds - TURN_TIMEOUT_BUFFER_SECONDS;
|
|
818
|
-
return Math.max(MIN_AGENT_TURN_TIMEOUT_MS, budgetSeconds * 1e3);
|
|
819
|
-
}
|
|
820
|
-
function buildBotConfig() {
|
|
821
|
-
const queueCallbackMaxDurationSeconds = resolveQueueCallbackMaxDurationSeconds();
|
|
822
|
-
const maxTurnTimeoutMs = resolveMaxTurnTimeoutMs(queueCallbackMaxDurationSeconds);
|
|
823
|
-
return {
|
|
824
|
-
userName: process.env.JUNIOR_BOT_NAME ?? "junior",
|
|
825
|
-
modelId: process.env.AI_MODEL ?? "anthropic/claude-sonnet-4.6",
|
|
826
|
-
fastModelId: process.env.AI_FAST_MODEL ?? process.env.AI_MODEL ?? "anthropic/claude-haiku-4.5",
|
|
827
|
-
turnTimeoutMs: parseAgentTurnTimeoutMs(process.env.AGENT_TURN_TIMEOUT_MS, maxTurnTimeoutMs)
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
var botConfig = buildBotConfig();
|
|
831
|
-
function toOptionalTrimmed(value) {
|
|
832
|
-
if (!value) {
|
|
833
|
-
return void 0;
|
|
834
|
-
}
|
|
835
|
-
const trimmed = value.trim();
|
|
836
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
837
|
-
}
|
|
838
|
-
function getSlackBotToken() {
|
|
839
|
-
return toOptionalTrimmed(process.env.SLACK_BOT_TOKEN) ?? toOptionalTrimmed(process.env.SLACK_BOT_USER_TOKEN);
|
|
840
|
-
}
|
|
841
|
-
function getSlackSigningSecret() {
|
|
842
|
-
return toOptionalTrimmed(process.env.SLACK_SIGNING_SECRET);
|
|
843
|
-
}
|
|
844
|
-
function getSlackClientId() {
|
|
845
|
-
return toOptionalTrimmed(process.env.SLACK_CLIENT_ID);
|
|
846
|
-
}
|
|
847
|
-
function getSlackClientSecret() {
|
|
848
|
-
return toOptionalTrimmed(process.env.SLACK_CLIENT_SECRET);
|
|
849
|
-
}
|
|
850
|
-
function hasRedisConfig() {
|
|
851
|
-
return Boolean(process.env.REDIS_URL);
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
// src/chat/state.ts
|
|
855
|
-
import { createRedisState } from "@chat-adapter/state-redis";
|
|
856
|
-
import { createMemoryState } from "@chat-adapter/state-memory";
|
|
857
|
-
var MIN_LOCK_TTL_MS = 1e3 * 60 * 5;
|
|
858
|
-
var QUEUE_INGRESS_DEDUP_PREFIX = "junior:queue_ingress";
|
|
859
|
-
var QUEUE_MESSAGE_PROCESSING_PREFIX = "junior:queue_message";
|
|
860
|
-
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
861
|
-
var QUEUE_MESSAGE_PROCESSING_TTL_MS = 30 * 60 * 1e3;
|
|
862
|
-
var QUEUE_MESSAGE_COMPLETED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
863
|
-
var QUEUE_MESSAGE_FAILED_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
864
|
-
var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
865
|
-
var CLAIM_OR_RECLAIM_PROCESSING_SCRIPT = `
|
|
866
|
-
local key = KEYS[1]
|
|
867
|
-
local nowMs = tonumber(ARGV[1])
|
|
868
|
-
local ttlMs = tonumber(ARGV[2])
|
|
869
|
-
local payload = ARGV[3]
|
|
870
|
-
local current = redis.call("get", key)
|
|
871
|
-
|
|
872
|
-
if not current then
|
|
873
|
-
redis.call("set", key, payload, "PX", ttlMs)
|
|
874
|
-
return 1
|
|
875
|
-
end
|
|
876
|
-
|
|
877
|
-
local ok, parsed = pcall(cjson.decode, current)
|
|
878
|
-
if not ok or type(parsed) ~= "table" then
|
|
879
|
-
return 0
|
|
880
|
-
end
|
|
881
|
-
|
|
882
|
-
local status = parsed["status"]
|
|
883
|
-
if status == "failed" then
|
|
884
|
-
redis.call("set", key, payload, "PX", ttlMs)
|
|
885
|
-
return 3
|
|
886
|
-
end
|
|
887
|
-
if status ~= "processing" then
|
|
888
|
-
return 0
|
|
889
|
-
end
|
|
890
|
-
|
|
891
|
-
local updatedAtMs = tonumber(parsed["updatedAtMs"])
|
|
892
|
-
if not updatedAtMs then
|
|
893
|
-
return 0
|
|
894
|
-
end
|
|
895
|
-
|
|
896
|
-
if updatedAtMs + ttlMs < nowMs then
|
|
897
|
-
redis.call("set", key, payload, "PX", ttlMs)
|
|
898
|
-
return 2
|
|
899
|
-
end
|
|
900
|
-
|
|
901
|
-
return 0
|
|
902
|
-
`;
|
|
903
|
-
var UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT = `
|
|
904
|
-
local key = KEYS[1]
|
|
905
|
-
local ownerToken = ARGV[1]
|
|
906
|
-
local ttlMs = tonumber(ARGV[2])
|
|
907
|
-
local payload = ARGV[3]
|
|
908
|
-
local current = redis.call("get", key)
|
|
909
|
-
|
|
910
|
-
if not current then
|
|
911
|
-
return 0
|
|
912
|
-
end
|
|
913
|
-
|
|
914
|
-
local ok, parsed = pcall(cjson.decode, current)
|
|
915
|
-
if not ok or type(parsed) ~= "table" then
|
|
916
|
-
return 0
|
|
917
|
-
end
|
|
918
|
-
|
|
919
|
-
local currentOwner = parsed["ownerToken"]
|
|
920
|
-
local status = parsed["status"]
|
|
921
|
-
if currentOwner ~= ownerToken then
|
|
922
|
-
return 0
|
|
923
|
-
end
|
|
924
|
-
if status ~= "processing" then
|
|
925
|
-
return 0
|
|
926
|
-
end
|
|
927
|
-
|
|
928
|
-
redis.call("set", key, payload, "PX", ttlMs)
|
|
929
|
-
return 1
|
|
930
|
-
`;
|
|
931
|
-
function createQueuedStateAdapter(base) {
|
|
932
|
-
const acquireLock = async (threadId, ttlMs) => {
|
|
933
|
-
const effectiveTtlMs = Math.max(ttlMs, MIN_LOCK_TTL_MS);
|
|
934
|
-
const lock = await base.acquireLock(threadId, effectiveTtlMs);
|
|
935
|
-
return lock;
|
|
936
|
-
};
|
|
937
|
-
return {
|
|
938
|
-
appendToList: (key, value, options) => base.appendToList(key, value, options),
|
|
939
|
-
connect: () => base.connect(),
|
|
940
|
-
disconnect: () => base.disconnect(),
|
|
941
|
-
subscribe: (threadId) => base.subscribe(threadId),
|
|
942
|
-
unsubscribe: (threadId) => base.unsubscribe(threadId),
|
|
943
|
-
isSubscribed: (threadId) => base.isSubscribed(threadId),
|
|
944
|
-
acquireLock,
|
|
945
|
-
releaseLock: (lock) => base.releaseLock(lock),
|
|
946
|
-
extendLock: (lock, ttlMs) => base.extendLock(lock, Math.max(ttlMs, MIN_LOCK_TTL_MS)),
|
|
947
|
-
forceReleaseLock: (threadId) => base.forceReleaseLock(threadId),
|
|
948
|
-
get: (key) => base.get(key),
|
|
949
|
-
getList: (key) => base.getList(key),
|
|
950
|
-
set: (key, value, ttlMs) => base.set(key, value, ttlMs),
|
|
951
|
-
setIfNotExists: (key, value, ttlMs) => base.setIfNotExists(key, value, ttlMs),
|
|
952
|
-
delete: (key) => base.delete(key)
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
function createStateAdapter() {
|
|
956
|
-
if (process.env.JUNIOR_STATE_ADAPTER?.trim().toLowerCase() === "memory") {
|
|
957
|
-
_redisStateAdapter = void 0;
|
|
958
|
-
return createQueuedStateAdapter(createMemoryState());
|
|
959
|
-
}
|
|
960
|
-
if (!hasRedisConfig()) {
|
|
961
|
-
throw new Error("REDIS_URL is required for durable Slack thread state");
|
|
962
|
-
}
|
|
963
|
-
const redisState = createRedisState({
|
|
964
|
-
url: process.env.REDIS_URL
|
|
965
|
-
});
|
|
966
|
-
_redisStateAdapter = redisState;
|
|
967
|
-
return createQueuedStateAdapter(redisState);
|
|
968
|
-
}
|
|
969
|
-
var _stateAdapter;
|
|
970
|
-
var _redisStateAdapter;
|
|
971
|
-
function getRedisStateAdapter() {
|
|
972
|
-
if (!_redisStateAdapter) {
|
|
973
|
-
getStateAdapter();
|
|
974
|
-
}
|
|
975
|
-
if (!_redisStateAdapter) {
|
|
976
|
-
throw new Error("Redis state adapter is unavailable for this runtime");
|
|
977
|
-
}
|
|
978
|
-
return _redisStateAdapter;
|
|
979
|
-
}
|
|
980
|
-
function queueMessageKey(rawKey) {
|
|
981
|
-
return `${QUEUE_MESSAGE_PROCESSING_PREFIX}:${rawKey}`;
|
|
982
|
-
}
|
|
983
|
-
function parseQueueMessageState(value) {
|
|
984
|
-
if (typeof value !== "string") {
|
|
985
|
-
return void 0;
|
|
986
|
-
}
|
|
987
|
-
try {
|
|
988
|
-
const parsed = JSON.parse(value);
|
|
989
|
-
if (!parsed || parsed.status !== "processing" && parsed.status !== "completed" && parsed.status !== "failed" || typeof parsed.updatedAtMs !== "number") {
|
|
990
|
-
return void 0;
|
|
991
|
-
}
|
|
992
|
-
return {
|
|
993
|
-
status: parsed.status,
|
|
994
|
-
updatedAtMs: parsed.updatedAtMs,
|
|
995
|
-
...typeof parsed.ownerToken === "string" ? { ownerToken: parsed.ownerToken } : {},
|
|
996
|
-
...typeof parsed.queueMessageId === "string" ? { queueMessageId: parsed.queueMessageId } : {},
|
|
997
|
-
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {}
|
|
998
|
-
};
|
|
999
|
-
} catch {
|
|
1000
|
-
return void 0;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
function agentTurnSessionKey(conversationId, sessionId) {
|
|
1004
|
-
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
1005
|
-
}
|
|
1006
|
-
function isRecord(value) {
|
|
1007
|
-
return typeof value === "object" && value !== null;
|
|
1008
|
-
}
|
|
1009
|
-
function parseAgentTurnSessionCheckpoint(value) {
|
|
1010
|
-
if (typeof value !== "string") {
|
|
1011
|
-
return void 0;
|
|
1012
|
-
}
|
|
1013
|
-
try {
|
|
1014
|
-
const parsed = JSON.parse(value);
|
|
1015
|
-
if (!isRecord(parsed)) {
|
|
1016
|
-
return void 0;
|
|
1017
|
-
}
|
|
1018
|
-
const status = parsed.state;
|
|
1019
|
-
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed") {
|
|
1020
|
-
return void 0;
|
|
1021
|
-
}
|
|
1022
|
-
const conversationId = parsed.conversationId;
|
|
1023
|
-
const sessionId = parsed.sessionId;
|
|
1024
|
-
const sliceId = parsed.sliceId;
|
|
1025
|
-
const checkpointVersion = parsed.checkpointVersion;
|
|
1026
|
-
const updatedAtMs = parsed.updatedAtMs;
|
|
1027
|
-
if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
|
|
1028
|
-
return void 0;
|
|
1029
|
-
}
|
|
1030
|
-
return {
|
|
1031
|
-
checkpointVersion,
|
|
1032
|
-
conversationId,
|
|
1033
|
-
sessionId,
|
|
1034
|
-
sliceId,
|
|
1035
|
-
state: status,
|
|
1036
|
-
updatedAtMs,
|
|
1037
|
-
piMessages: Array.isArray(parsed.piMessages) ? parsed.piMessages : [],
|
|
1038
|
-
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
1039
|
-
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
|
|
1040
|
-
};
|
|
1041
|
-
} catch {
|
|
1042
|
-
return void 0;
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
function getStateAdapter() {
|
|
1046
|
-
if (!_stateAdapter) {
|
|
1047
|
-
_stateAdapter = createStateAdapter();
|
|
1048
|
-
}
|
|
1049
|
-
return _stateAdapter;
|
|
1050
|
-
}
|
|
1051
|
-
async function disconnectStateAdapter() {
|
|
1052
|
-
if (!_stateAdapter) {
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
try {
|
|
1056
|
-
await _stateAdapter.disconnect();
|
|
1057
|
-
} finally {
|
|
1058
|
-
_stateAdapter = void 0;
|
|
1059
|
-
_redisStateAdapter = void 0;
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
async function claimQueueIngressDedup(rawKey, ttlMs) {
|
|
1063
|
-
await getStateAdapter().connect();
|
|
1064
|
-
const key = `${QUEUE_INGRESS_DEDUP_PREFIX}:${rawKey}`;
|
|
1065
|
-
const result = await getRedisStateAdapter().getClient().set(key, "1", {
|
|
1066
|
-
NX: true,
|
|
1067
|
-
PX: ttlMs
|
|
1068
|
-
});
|
|
1069
|
-
return result === "OK";
|
|
1070
|
-
}
|
|
1071
|
-
async function hasQueueIngressDedup(rawKey) {
|
|
1072
|
-
await getStateAdapter().connect();
|
|
1073
|
-
const key = `${QUEUE_INGRESS_DEDUP_PREFIX}:${rawKey}`;
|
|
1074
|
-
const value = await getRedisStateAdapter().getClient().get(key);
|
|
1075
|
-
return typeof value === "string" && value.length > 0;
|
|
1076
|
-
}
|
|
1077
|
-
async function getQueueMessageProcessingState(rawKey) {
|
|
1078
|
-
await getStateAdapter().connect();
|
|
1079
|
-
const state = await getStateAdapter().get(queueMessageKey(rawKey));
|
|
1080
|
-
return parseQueueMessageState(state);
|
|
1081
|
-
}
|
|
1082
|
-
async function acquireQueueMessageProcessingOwnership(args) {
|
|
1083
|
-
await getStateAdapter().connect();
|
|
1084
|
-
const key = queueMessageKey(args.rawKey);
|
|
1085
|
-
const nowMs = Date.now();
|
|
1086
|
-
const payload = JSON.stringify({
|
|
1087
|
-
status: "processing",
|
|
1088
|
-
updatedAtMs: nowMs,
|
|
1089
|
-
ownerToken: args.ownerToken,
|
|
1090
|
-
...args.queueMessageId ? { queueMessageId: args.queueMessageId } : {}
|
|
1091
|
-
});
|
|
1092
|
-
const result = await getRedisStateAdapter().getClient().eval(CLAIM_OR_RECLAIM_PROCESSING_SCRIPT, {
|
|
1093
|
-
keys: [key],
|
|
1094
|
-
arguments: [
|
|
1095
|
-
String(nowMs),
|
|
1096
|
-
String(QUEUE_MESSAGE_PROCESSING_TTL_MS),
|
|
1097
|
-
payload
|
|
1098
|
-
]
|
|
1099
|
-
});
|
|
1100
|
-
if (result === 1) {
|
|
1101
|
-
return "acquired";
|
|
1102
|
-
}
|
|
1103
|
-
if (result === 2) {
|
|
1104
|
-
return "reclaimed";
|
|
1105
|
-
}
|
|
1106
|
-
if (result === 3) {
|
|
1107
|
-
return "recovered";
|
|
1108
|
-
}
|
|
1109
|
-
return "blocked";
|
|
1110
|
-
}
|
|
1111
|
-
async function refreshQueueMessageProcessingOwnership(args) {
|
|
1112
|
-
await getStateAdapter().connect();
|
|
1113
|
-
const nowMs = Date.now();
|
|
1114
|
-
const payload = JSON.stringify({
|
|
1115
|
-
status: "processing",
|
|
1116
|
-
updatedAtMs: nowMs,
|
|
1117
|
-
ownerToken: args.ownerToken,
|
|
1118
|
-
...args.queueMessageId ? { queueMessageId: args.queueMessageId } : {}
|
|
1119
|
-
});
|
|
1120
|
-
const result = await getRedisStateAdapter().getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
|
|
1121
|
-
keys: [queueMessageKey(args.rawKey)],
|
|
1122
|
-
arguments: [
|
|
1123
|
-
args.ownerToken,
|
|
1124
|
-
String(QUEUE_MESSAGE_PROCESSING_TTL_MS),
|
|
1125
|
-
payload
|
|
1126
|
-
]
|
|
1127
|
-
});
|
|
1128
|
-
return result === 1;
|
|
1129
|
-
}
|
|
1130
|
-
async function completeQueueMessageProcessingOwnership(args) {
|
|
1131
|
-
await getStateAdapter().connect();
|
|
1132
|
-
const payload = JSON.stringify({
|
|
1133
|
-
status: "completed",
|
|
1134
|
-
updatedAtMs: Date.now(),
|
|
1135
|
-
ownerToken: args.ownerToken,
|
|
1136
|
-
...args.queueMessageId ? { queueMessageId: args.queueMessageId } : {}
|
|
1137
|
-
});
|
|
1138
|
-
const result = await getRedisStateAdapter().getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
|
|
1139
|
-
keys: [queueMessageKey(args.rawKey)],
|
|
1140
|
-
arguments: [
|
|
1141
|
-
args.ownerToken,
|
|
1142
|
-
String(QUEUE_MESSAGE_COMPLETED_TTL_MS),
|
|
1143
|
-
payload
|
|
1144
|
-
]
|
|
1145
|
-
});
|
|
1146
|
-
return result === 1;
|
|
1147
|
-
}
|
|
1148
|
-
async function failQueueMessageProcessingOwnership(args) {
|
|
1149
|
-
await getStateAdapter().connect();
|
|
1150
|
-
const payload = JSON.stringify({
|
|
1151
|
-
status: "failed",
|
|
1152
|
-
updatedAtMs: Date.now(),
|
|
1153
|
-
ownerToken: args.ownerToken,
|
|
1154
|
-
errorMessage: args.errorMessage,
|
|
1155
|
-
...args.queueMessageId ? { queueMessageId: args.queueMessageId } : {}
|
|
1156
|
-
});
|
|
1157
|
-
const result = await getRedisStateAdapter().getClient().eval(UPDATE_PROCESSING_STATE_IF_OWNER_SCRIPT, {
|
|
1158
|
-
keys: [queueMessageKey(args.rawKey)],
|
|
1159
|
-
arguments: [
|
|
1160
|
-
args.ownerToken,
|
|
1161
|
-
String(QUEUE_MESSAGE_FAILED_TTL_MS),
|
|
1162
|
-
payload
|
|
1163
|
-
]
|
|
1164
|
-
});
|
|
1165
|
-
return result === 1;
|
|
1166
|
-
}
|
|
1167
|
-
async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
|
|
1168
|
-
await getStateAdapter().connect();
|
|
1169
|
-
const value = await getStateAdapter().get(
|
|
1170
|
-
agentTurnSessionKey(conversationId, sessionId)
|
|
1171
|
-
);
|
|
1172
|
-
return parseAgentTurnSessionCheckpoint(value);
|
|
1173
|
-
}
|
|
1174
|
-
async function upsertAgentTurnSessionCheckpoint(args) {
|
|
1175
|
-
await getStateAdapter().connect();
|
|
1176
|
-
const existing = await getAgentTurnSessionCheckpoint(
|
|
1177
|
-
args.conversationId,
|
|
1178
|
-
args.sessionId
|
|
1179
|
-
);
|
|
1180
|
-
const checkpoint = {
|
|
1181
|
-
checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
|
|
1182
|
-
conversationId: args.conversationId,
|
|
1183
|
-
sessionId: args.sessionId,
|
|
1184
|
-
sliceId: args.sliceId,
|
|
1185
|
-
state: args.state,
|
|
1186
|
-
updatedAtMs: Date.now(),
|
|
1187
|
-
piMessages: Array.isArray(args.piMessages) ? args.piMessages : [],
|
|
1188
|
-
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
1189
|
-
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
|
|
1190
|
-
};
|
|
1191
|
-
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
1192
|
-
await getStateAdapter().set(
|
|
1193
|
-
agentTurnSessionKey(args.conversationId, args.sessionId),
|
|
1194
|
-
JSON.stringify(checkpoint),
|
|
1195
|
-
ttlMs
|
|
1196
|
-
);
|
|
1197
|
-
return checkpoint;
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
// src/chat/sandbox/runtime-dependency-snapshots.ts
|
|
1201
|
-
import { createHash } from "crypto";
|
|
1202
|
-
import { Sandbox } from "@vercel/sandbox";
|
|
1203
|
-
|
|
1204
|
-
// src/chat/sandbox/paths.ts
|
|
1205
|
-
function normalizeWorkspaceRoot(input) {
|
|
1206
|
-
const candidate = (input ?? "").trim();
|
|
1207
|
-
if (!candidate) {
|
|
1208
|
-
return "/vercel/sandbox";
|
|
1209
|
-
}
|
|
1210
|
-
const normalized = candidate.replace(/\/+$/, "");
|
|
1211
|
-
return normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
1212
|
-
}
|
|
1213
|
-
var SANDBOX_WORKSPACE_ROOT = normalizeWorkspaceRoot(process.env.VERCEL_SANDBOX_WORKSPACE_DIR);
|
|
1214
|
-
var SANDBOX_SKILLS_ROOT = `${SANDBOX_WORKSPACE_ROOT}/skills`;
|
|
1215
|
-
function sandboxSkillDir(skillName) {
|
|
1216
|
-
return `${SANDBOX_SKILLS_ROOT}/${skillName}`;
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
// src/chat/sandbox/runtime-dependency-snapshots.ts
|
|
1220
|
-
var SNAPSHOT_CACHE_PREFIX = "junior:sandbox_snapshot_profile";
|
|
1221
|
-
var SNAPSHOT_LOCK_PREFIX = "junior:sandbox_snapshot_lock";
|
|
1222
|
-
var SNAPSHOT_PROFILE_VERSION = 1;
|
|
1223
|
-
var SNAPSHOT_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1224
|
-
var SNAPSHOT_BUILD_LOCK_TTL_MS = 10 * 60 * 1e3;
|
|
1225
|
-
var SNAPSHOT_WAIT_FOR_LOCK_MS = SNAPSHOT_BUILD_LOCK_TTL_MS + 30 * 1e3;
|
|
1226
|
-
var DEFAULT_FLOATING_DEP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1227
|
-
function sleep(ms) {
|
|
1228
|
-
return new Promise((resolve) => {
|
|
1229
|
-
setTimeout(resolve, ms);
|
|
1230
|
-
});
|
|
1231
|
-
}
|
|
1232
|
-
function profileCacheKey(profileHash) {
|
|
1233
|
-
return `${SNAPSHOT_CACHE_PREFIX}:${profileHash}`;
|
|
1234
|
-
}
|
|
1235
|
-
function profileLockKey(profileHash) {
|
|
1236
|
-
return `${SNAPSHOT_LOCK_PREFIX}:${profileHash}`;
|
|
1237
|
-
}
|
|
1238
|
-
function isExactNpmVersion(version) {
|
|
1239
|
-
return /^\d+\.\d+\.\d+(?:[-+][a-z0-9.]+)?$/i.test(version.trim());
|
|
1240
|
-
}
|
|
1241
|
-
function hasFloatingSelector(dep) {
|
|
1242
|
-
return dep.type === "npm" && !isExactNpmVersion(dep.version);
|
|
1243
|
-
}
|
|
1244
|
-
function parseFloatingDepMaxAgeMs() {
|
|
1245
|
-
const raw = process.env.SANDBOX_SNAPSHOT_FLOATING_MAX_AGE_MS;
|
|
1246
|
-
if (!raw?.trim()) {
|
|
1247
|
-
return DEFAULT_FLOATING_DEP_MAX_AGE_MS;
|
|
1248
|
-
}
|
|
1249
|
-
const parsed = Number.parseInt(raw, 10);
|
|
1250
|
-
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
1251
|
-
return DEFAULT_FLOATING_DEP_MAX_AGE_MS;
|
|
1252
|
-
}
|
|
1253
|
-
return parsed;
|
|
1254
|
-
}
|
|
1255
|
-
function buildDependencyProfile(runtime) {
|
|
1256
|
-
const dependencies = getPluginRuntimeDependencies();
|
|
1257
|
-
const postinstall = getPluginRuntimePostinstall();
|
|
1258
|
-
if (dependencies.length === 0 && postinstall.length === 0) {
|
|
1259
|
-
return null;
|
|
1260
|
-
}
|
|
1261
|
-
const rebuildEpoch = process.env.SANDBOX_SNAPSHOT_REBUILD_EPOCH?.trim() ?? "";
|
|
1262
|
-
const hasFloatingVersions = dependencies.some((dep) => hasFloatingSelector(dep)) || postinstall.length > 0;
|
|
1263
|
-
const hashInput = JSON.stringify({
|
|
1264
|
-
version: SNAPSHOT_PROFILE_VERSION,
|
|
1265
|
-
runtime,
|
|
1266
|
-
rebuildEpoch,
|
|
1267
|
-
dependencies,
|
|
1268
|
-
postinstall
|
|
1269
|
-
});
|
|
1270
|
-
const profileHash = createHash("sha256").update(hashInput).digest("hex");
|
|
1271
|
-
return {
|
|
1272
|
-
profileHash,
|
|
1273
|
-
dependencyCount: dependencies.length,
|
|
1274
|
-
hasFloatingVersions,
|
|
1275
|
-
dependencies,
|
|
1276
|
-
postinstall
|
|
1277
|
-
};
|
|
1278
|
-
}
|
|
1279
|
-
function getRuntimeDependencyProfileHash(runtime) {
|
|
1280
|
-
return buildDependencyProfile(runtime)?.profileHash;
|
|
1281
|
-
}
|
|
1282
|
-
function shouldRebuildCachedSnapshot(profile, cached) {
|
|
1283
|
-
if (!profile.hasFloatingVersions) {
|
|
1284
|
-
return false;
|
|
1285
|
-
}
|
|
1286
|
-
const maxAgeMs = parseFloatingDepMaxAgeMs();
|
|
1287
|
-
if (maxAgeMs === 0) {
|
|
1288
|
-
return true;
|
|
1289
|
-
}
|
|
1290
|
-
return Date.now() - cached.createdAtMs > maxAgeMs;
|
|
1291
|
-
}
|
|
1292
|
-
async function getCachedSnapshot(profileHash) {
|
|
1293
|
-
try {
|
|
1294
|
-
const state = getStateAdapter();
|
|
1295
|
-
await state.connect();
|
|
1296
|
-
const raw = await state.get(profileCacheKey(profileHash));
|
|
1297
|
-
if (typeof raw !== "string") {
|
|
1298
|
-
return null;
|
|
1299
|
-
}
|
|
1300
|
-
const parsed = JSON.parse(raw);
|
|
1301
|
-
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") {
|
|
1302
|
-
return null;
|
|
1303
|
-
}
|
|
1304
|
-
return parsed;
|
|
1305
|
-
} catch {
|
|
1306
|
-
return null;
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
async function setCachedSnapshot(entry) {
|
|
1310
|
-
const state = getStateAdapter();
|
|
1311
|
-
await state.connect();
|
|
1312
|
-
await state.set(
|
|
1313
|
-
profileCacheKey(entry.profileHash),
|
|
1314
|
-
JSON.stringify(entry),
|
|
1315
|
-
SNAPSHOT_CACHE_TTL_MS
|
|
1316
|
-
);
|
|
1317
|
-
}
|
|
1318
|
-
async function withSnapshotSpan(name, op, attributes, callback) {
|
|
1319
|
-
return await withSpan(name, op, {}, callback, attributes);
|
|
1320
|
-
}
|
|
1321
|
-
async function runOrThrow(sandbox, params, label) {
|
|
1322
|
-
const result = await sandbox.runCommand(params);
|
|
1323
|
-
if (result.exitCode === 0) {
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
const stderr = (await result.stderr()).trim();
|
|
1327
|
-
const stdout = (await result.stdout()).trim();
|
|
1328
|
-
const detail = stderr || stdout || "command failed";
|
|
1329
|
-
throw new Error(`${label} failed: ${detail}`);
|
|
1330
|
-
}
|
|
1331
|
-
async function tryRun(sandbox, params) {
|
|
1332
|
-
const result = await sandbox.runCommand(params);
|
|
1333
|
-
if (result.exitCode === 0) {
|
|
1334
|
-
return { ok: true };
|
|
1335
|
-
}
|
|
1336
|
-
const stderr = (await result.stderr()).trim();
|
|
1337
|
-
const stdout = (await result.stdout()).trim();
|
|
1338
|
-
return { ok: false, detail: stderr || stdout || "command failed" };
|
|
1339
|
-
}
|
|
1340
|
-
async function installGhCliViaDnf(sandbox) {
|
|
1341
|
-
const direct = await tryRun(sandbox, {
|
|
1342
|
-
cmd: "dnf",
|
|
1343
|
-
args: ["install", "-y", "gh"],
|
|
1344
|
-
sudo: true
|
|
1345
|
-
});
|
|
1346
|
-
if (direct.ok) {
|
|
1347
|
-
return;
|
|
1348
|
-
}
|
|
1349
|
-
const dnf5Repo = await tryRun(sandbox, {
|
|
1350
|
-
cmd: "dnf",
|
|
1351
|
-
args: [
|
|
1352
|
-
"config-manager",
|
|
1353
|
-
"addrepo",
|
|
1354
|
-
"--from-repofile=https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
1355
|
-
],
|
|
1356
|
-
sudo: true
|
|
1357
|
-
});
|
|
1358
|
-
if (!dnf5Repo.ok) {
|
|
1359
|
-
await runOrThrow(
|
|
1360
|
-
sandbox,
|
|
1361
|
-
{
|
|
1362
|
-
cmd: "dnf",
|
|
1363
|
-
args: ["install", "-y", "dnf-command(config-manager)"],
|
|
1364
|
-
sudo: true
|
|
1365
|
-
},
|
|
1366
|
-
"dnf install dnf-command(config-manager)"
|
|
1367
|
-
);
|
|
1368
|
-
await runOrThrow(
|
|
1369
|
-
sandbox,
|
|
1370
|
-
{
|
|
1371
|
-
cmd: "dnf",
|
|
1372
|
-
args: [
|
|
1373
|
-
"config-manager",
|
|
1374
|
-
"--add-repo",
|
|
1375
|
-
"https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
1376
|
-
],
|
|
1377
|
-
sudo: true
|
|
1378
|
-
},
|
|
1379
|
-
"dnf config-manager --add-repo gh-cli.repo"
|
|
1380
|
-
);
|
|
1381
|
-
}
|
|
1382
|
-
await runOrThrow(
|
|
1383
|
-
sandbox,
|
|
1384
|
-
{
|
|
1385
|
-
cmd: "dnf",
|
|
1386
|
-
args: ["install", "-y", "gh", "--repo", "gh-cli"],
|
|
1387
|
-
sudo: true
|
|
1388
|
-
},
|
|
1389
|
-
"dnf install gh --repo gh-cli"
|
|
1390
|
-
);
|
|
1391
|
-
}
|
|
1392
|
-
function runtimeDependencyFilePath(url, sha256) {
|
|
1393
|
-
let urlBasename = "package.rpm";
|
|
1394
|
-
try {
|
|
1395
|
-
const pathname = new URL(url).pathname;
|
|
1396
|
-
const segments = pathname.split("/").filter(Boolean);
|
|
1397
|
-
const candidate = segments[segments.length - 1];
|
|
1398
|
-
if (candidate) {
|
|
1399
|
-
urlBasename = candidate;
|
|
1400
|
-
}
|
|
1401
|
-
} catch {
|
|
1402
|
-
}
|
|
1403
|
-
const sanitizedBasename = urlBasename.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1404
|
-
return `/tmp/junior-runtime-${sha256.slice(0, 12)}-${sanitizedBasename}`;
|
|
1405
|
-
}
|
|
1406
|
-
async function installRuntimeDependencies(sandbox, deps) {
|
|
1407
|
-
const systemDeps = deps.filter(
|
|
1408
|
-
(dep) => dep.type === "system"
|
|
1409
|
-
);
|
|
1410
|
-
const npmPackages = deps.filter(
|
|
1411
|
-
(dep) => dep.type === "npm"
|
|
1412
|
-
).map((dep) => `${dep.package}@${dep.version}`);
|
|
1413
|
-
if (systemDeps.length > 0) {
|
|
1414
|
-
await withSnapshotSpan(
|
|
1415
|
-
"sandbox.snapshot.install_system",
|
|
1416
|
-
"sandbox.snapshot.install.system",
|
|
1417
|
-
{
|
|
1418
|
-
"app.sandbox.snapshot.install.system_count": systemDeps.length
|
|
1419
|
-
},
|
|
1420
|
-
async () => {
|
|
1421
|
-
for (const dep of systemDeps) {
|
|
1422
|
-
if ("url" in dep) {
|
|
1423
|
-
const rpmPath = runtimeDependencyFilePath(dep.url, dep.sha256);
|
|
1424
|
-
await runOrThrow(
|
|
1425
|
-
sandbox,
|
|
1426
|
-
{
|
|
1427
|
-
cmd: "curl",
|
|
1428
|
-
args: ["-fsSL", dep.url, "-o", rpmPath]
|
|
1429
|
-
},
|
|
1430
|
-
`curl download ${dep.url}`
|
|
1431
|
-
);
|
|
1432
|
-
const checksumResult = await sandbox.runCommand({
|
|
1433
|
-
cmd: "sha256sum",
|
|
1434
|
-
args: [rpmPath]
|
|
1435
|
-
});
|
|
1436
|
-
const checksumStdout = (await checksumResult.stdout()).trim();
|
|
1437
|
-
const checksumStderr = (await checksumResult.stderr()).trim();
|
|
1438
|
-
if (checksumResult.exitCode !== 0) {
|
|
1439
|
-
throw new Error(
|
|
1440
|
-
`sha256sum failed: ${checksumStderr || checksumStdout || "command failed"}`
|
|
1441
|
-
);
|
|
1442
|
-
}
|
|
1443
|
-
const actualChecksum = checksumStdout.split(/\s+/)[0]?.toLowerCase();
|
|
1444
|
-
if (!actualChecksum) {
|
|
1445
|
-
throw new Error("sha256sum produced empty output");
|
|
1446
|
-
}
|
|
1447
|
-
if (actualChecksum !== dep.sha256) {
|
|
1448
|
-
throw new Error(
|
|
1449
|
-
`checksum mismatch for ${dep.url}: expected ${dep.sha256}, got ${actualChecksum}`
|
|
1450
|
-
);
|
|
1451
|
-
}
|
|
1452
|
-
await runOrThrow(
|
|
1453
|
-
sandbox,
|
|
1454
|
-
{
|
|
1455
|
-
cmd: "dnf",
|
|
1456
|
-
args: ["install", "-y", rpmPath],
|
|
1457
|
-
sudo: true
|
|
1458
|
-
},
|
|
1459
|
-
`dnf install ${dep.url}`
|
|
1460
|
-
);
|
|
1461
|
-
continue;
|
|
1462
|
-
}
|
|
1463
|
-
if (dep.package === "gh") {
|
|
1464
|
-
await installGhCliViaDnf(sandbox);
|
|
1465
|
-
continue;
|
|
1466
|
-
}
|
|
1467
|
-
await runOrThrow(
|
|
1468
|
-
sandbox,
|
|
1469
|
-
{
|
|
1470
|
-
cmd: "dnf",
|
|
1471
|
-
args: ["install", "-y", dep.package],
|
|
1472
|
-
sudo: true
|
|
1473
|
-
},
|
|
1474
|
-
`dnf install ${dep.package}`
|
|
1475
|
-
);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
);
|
|
1479
|
-
}
|
|
1480
|
-
if (npmPackages.length > 0) {
|
|
1481
|
-
await withSnapshotSpan(
|
|
1482
|
-
"sandbox.snapshot.install_npm",
|
|
1483
|
-
"sandbox.snapshot.install.npm",
|
|
1484
|
-
{
|
|
1485
|
-
"app.sandbox.snapshot.install.npm_count": npmPackages.length
|
|
1486
|
-
},
|
|
1487
|
-
async () => {
|
|
1488
|
-
await runOrThrow(
|
|
1489
|
-
sandbox,
|
|
1490
|
-
{
|
|
1491
|
-
cmd: "npm",
|
|
1492
|
-
args: [
|
|
1493
|
-
"install",
|
|
1494
|
-
"--global",
|
|
1495
|
-
"--prefix",
|
|
1496
|
-
`${SANDBOX_WORKSPACE_ROOT}/.junior`,
|
|
1497
|
-
...npmPackages
|
|
1498
|
-
]
|
|
1499
|
-
},
|
|
1500
|
-
"npm install"
|
|
1501
|
-
);
|
|
1502
|
-
}
|
|
1503
|
-
);
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
async function runRuntimePostinstall(sandbox, commands) {
|
|
1507
|
-
if (commands.length === 0) {
|
|
1508
|
-
return;
|
|
1509
|
-
}
|
|
1510
|
-
await withSnapshotSpan(
|
|
1511
|
-
"sandbox.snapshot.runtime_postinstall",
|
|
1512
|
-
"sandbox.snapshot.runtime_postinstall",
|
|
1513
|
-
{
|
|
1514
|
-
"app.sandbox.snapshot.runtime_postinstall.count": commands.length
|
|
1515
|
-
},
|
|
1516
|
-
async () => {
|
|
1517
|
-
for (const command of commands) {
|
|
1518
|
-
const invocation = [
|
|
1519
|
-
JSON.stringify(command.cmd),
|
|
1520
|
-
...(command.args ?? []).map((arg) => JSON.stringify(arg))
|
|
1521
|
-
].join(" ");
|
|
1522
|
-
const pathPrefix = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin:$PATH`;
|
|
1523
|
-
await runOrThrow(
|
|
1524
|
-
sandbox,
|
|
1525
|
-
{
|
|
1526
|
-
cmd: "bash",
|
|
1527
|
-
args: ["-lc", `export PATH="${pathPrefix}" && ${invocation}`],
|
|
1528
|
-
...command.sudo !== void 0 ? { sudo: command.sudo } : {}
|
|
1529
|
-
},
|
|
1530
|
-
`runtime-postinstall ${command.cmd}`
|
|
1531
|
-
);
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
);
|
|
1535
|
-
}
|
|
1536
|
-
async function createDependencySnapshot(profile, runtime, timeoutMs) {
|
|
1537
|
-
return await withSnapshotSpan(
|
|
1538
|
-
"sandbox.snapshot.build",
|
|
1539
|
-
"sandbox.snapshot.build",
|
|
1540
|
-
{
|
|
1541
|
-
"app.sandbox.runtime": runtime,
|
|
1542
|
-
"app.sandbox.snapshot.dependency_count": profile.dependencyCount
|
|
1543
|
-
},
|
|
1544
|
-
async () => {
|
|
1545
|
-
const sandbox = await Sandbox.create({
|
|
1546
|
-
timeout: timeoutMs,
|
|
1547
|
-
runtime
|
|
1548
|
-
});
|
|
1549
|
-
try {
|
|
1550
|
-
await installRuntimeDependencies(sandbox, profile.dependencies);
|
|
1551
|
-
await runRuntimePostinstall(sandbox, profile.postinstall);
|
|
1552
|
-
return await withSnapshotSpan(
|
|
1553
|
-
"sandbox.snapshot.capture",
|
|
1554
|
-
"sandbox.snapshot.capture",
|
|
1555
|
-
{
|
|
1556
|
-
"app.sandbox.snapshot.dependency_count": profile.dependencyCount
|
|
1557
|
-
},
|
|
1558
|
-
async () => {
|
|
1559
|
-
const snapshot = await sandbox.snapshot();
|
|
1560
|
-
return snapshot.snapshotId;
|
|
1561
|
-
}
|
|
1562
|
-
);
|
|
1563
|
-
} finally {
|
|
1564
|
-
try {
|
|
1565
|
-
await sandbox.stop({ blocking: true });
|
|
1566
|
-
} catch {
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
);
|
|
1571
|
-
}
|
|
1572
|
-
async function withBuildLock(profileHash, callback, canUseCachedSnapshot, hooks) {
|
|
1573
|
-
const state = getStateAdapter();
|
|
1574
|
-
await state.connect();
|
|
1575
|
-
const lockKey = profileLockKey(profileHash);
|
|
1576
|
-
const tryAcquireLock = async () => await state.acquireLock(lockKey, SNAPSHOT_BUILD_LOCK_TTL_MS);
|
|
1577
|
-
let lock = await tryAcquireLock();
|
|
1578
|
-
if (lock) {
|
|
1579
|
-
try {
|
|
1580
|
-
const result = await callback();
|
|
1581
|
-
return {
|
|
1582
|
-
snapshotId: result.snapshotId,
|
|
1583
|
-
source: result.source,
|
|
1584
|
-
waitedForLock: false
|
|
1585
|
-
};
|
|
1586
|
-
} finally {
|
|
1587
|
-
await state.releaseLock(lock);
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
return await withSnapshotSpan(
|
|
1591
|
-
"sandbox.snapshot.lock_wait",
|
|
1592
|
-
"sandbox.snapshot.lock_wait",
|
|
1593
|
-
{
|
|
1594
|
-
"app.sandbox.snapshot.profile_hash": profileHash
|
|
1595
|
-
},
|
|
1596
|
-
async () => {
|
|
1597
|
-
await hooks?.onWaitingForLock?.();
|
|
1598
|
-
const waitUntil = Date.now() + SNAPSHOT_WAIT_FOR_LOCK_MS;
|
|
1599
|
-
while (Date.now() < waitUntil) {
|
|
1600
|
-
const cached2 = await getCachedSnapshot(profileHash);
|
|
1601
|
-
if (cached2?.snapshotId && canUseCachedSnapshot(cached2)) {
|
|
1602
|
-
return {
|
|
1603
|
-
snapshotId: cached2.snapshotId,
|
|
1604
|
-
source: "wait_cache",
|
|
1605
|
-
waitedForLock: true
|
|
1606
|
-
};
|
|
1607
|
-
}
|
|
1608
|
-
lock = await tryAcquireLock();
|
|
1609
|
-
if (lock) {
|
|
1610
|
-
try {
|
|
1611
|
-
const result = await callback();
|
|
1612
|
-
return {
|
|
1613
|
-
snapshotId: result.snapshotId,
|
|
1614
|
-
source: result.source,
|
|
1615
|
-
waitedForLock: true
|
|
1616
|
-
};
|
|
1617
|
-
} finally {
|
|
1618
|
-
await state.releaseLock(lock);
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
await sleep(500);
|
|
1622
|
-
}
|
|
1623
|
-
const cached = await getCachedSnapshot(profileHash);
|
|
1624
|
-
if (cached?.snapshotId && canUseCachedSnapshot(cached)) {
|
|
1625
|
-
return {
|
|
1626
|
-
snapshotId: cached.snapshotId,
|
|
1627
|
-
source: "wait_cache",
|
|
1628
|
-
waitedForLock: true
|
|
1629
|
-
};
|
|
1630
|
-
}
|
|
1631
|
-
throw new Error("Timed out waiting for snapshot build lock");
|
|
1632
|
-
}
|
|
1633
|
-
);
|
|
1634
|
-
}
|
|
1635
|
-
function toResolveOutcome(forceRebuild, source, waitedForLock) {
|
|
1636
|
-
if (source === "built") {
|
|
1637
|
-
return forceRebuild ? "forced_rebuild" : "rebuilt";
|
|
1638
|
-
}
|
|
1639
|
-
if (waitedForLock || source === "wait_cache") {
|
|
1640
|
-
return "cache_hit_after_lock_wait";
|
|
1641
|
-
}
|
|
1642
|
-
return "cache_hit";
|
|
1643
|
-
}
|
|
1644
|
-
function getRebuildReason(params) {
|
|
1645
|
-
if (params.forceRebuild) {
|
|
1646
|
-
return params.staleSnapshotId ? "snapshot_missing" : "force_rebuild";
|
|
1647
|
-
}
|
|
1648
|
-
if (params.cached?.snapshotId && params.shouldRebuildCached) {
|
|
1649
|
-
return "floating_stale";
|
|
1650
|
-
}
|
|
1651
|
-
if (!params.cached?.snapshotId) {
|
|
1652
|
-
return "cache_miss";
|
|
1653
|
-
}
|
|
1654
|
-
return void 0;
|
|
1655
|
-
}
|
|
1656
|
-
async function resolveRuntimeDependencySnapshot(params) {
|
|
1657
|
-
return await withSnapshotSpan(
|
|
1658
|
-
"sandbox.snapshot.resolve",
|
|
1659
|
-
"sandbox.snapshot.resolve",
|
|
1660
|
-
{
|
|
1661
|
-
"app.sandbox.runtime": params.runtime,
|
|
1662
|
-
"app.sandbox.snapshot.force_rebuild": Boolean(params.forceRebuild)
|
|
1663
|
-
},
|
|
1664
|
-
async () => {
|
|
1665
|
-
await params.onProgress?.("resolve_start");
|
|
1666
|
-
const resolveStartedAtMs = Date.now();
|
|
1667
|
-
const profile = buildDependencyProfile(params.runtime);
|
|
1668
|
-
if (!profile) {
|
|
1669
|
-
return {
|
|
1670
|
-
dependencyCount: 0,
|
|
1671
|
-
cacheHit: false,
|
|
1672
|
-
resolveOutcome: "no_profile"
|
|
1673
|
-
};
|
|
1674
|
-
}
|
|
1675
|
-
const cached = await getCachedSnapshot(profile.profileHash);
|
|
1676
|
-
const cachedNeedsRebuild = Boolean(
|
|
1677
|
-
cached?.snapshotId && shouldRebuildCachedSnapshot(profile, cached)
|
|
1678
|
-
);
|
|
1679
|
-
if (!params.forceRebuild && cached?.snapshotId && !cachedNeedsRebuild) {
|
|
1680
|
-
await params.onProgress?.("cache_hit");
|
|
1681
|
-
return {
|
|
1682
|
-
snapshotId: cached.snapshotId,
|
|
1683
|
-
profileHash: profile.profileHash,
|
|
1684
|
-
dependencyCount: profile.dependencyCount,
|
|
1685
|
-
cacheHit: true,
|
|
1686
|
-
resolveOutcome: "cache_hit"
|
|
1687
|
-
};
|
|
1688
|
-
}
|
|
1689
|
-
const rebuildReason = getRebuildReason({
|
|
1690
|
-
forceRebuild: params.forceRebuild,
|
|
1691
|
-
staleSnapshotId: params.staleSnapshotId,
|
|
1692
|
-
cached,
|
|
1693
|
-
shouldRebuildCached: cachedNeedsRebuild
|
|
1694
|
-
});
|
|
1695
|
-
const canUseCachedSnapshot = (candidate) => {
|
|
1696
|
-
if (params.forceRebuild) {
|
|
1697
|
-
if (params.staleSnapshotId) {
|
|
1698
|
-
return candidate.snapshotId !== params.staleSnapshotId;
|
|
1699
|
-
}
|
|
1700
|
-
return candidate.createdAtMs > resolveStartedAtMs;
|
|
1701
|
-
}
|
|
1702
|
-
return !shouldRebuildCachedSnapshot(profile, candidate);
|
|
1703
|
-
};
|
|
1704
|
-
const lockResult = await withBuildLock(
|
|
1705
|
-
profile.profileHash,
|
|
1706
|
-
async () => {
|
|
1707
|
-
const latest = await getCachedSnapshot(profile.profileHash);
|
|
1708
|
-
if (latest?.snapshotId && canUseCachedSnapshot(latest)) {
|
|
1709
|
-
await params.onProgress?.("cache_hit");
|
|
1710
|
-
return {
|
|
1711
|
-
snapshotId: latest.snapshotId,
|
|
1712
|
-
source: "callback_cache"
|
|
1713
|
-
};
|
|
1714
|
-
}
|
|
1715
|
-
await params.onProgress?.("building_snapshot");
|
|
1716
|
-
const nextSnapshotId = await createDependencySnapshot(
|
|
1717
|
-
profile,
|
|
1718
|
-
params.runtime,
|
|
1719
|
-
params.timeoutMs
|
|
1720
|
-
);
|
|
1721
|
-
await setCachedSnapshot({
|
|
1722
|
-
profileHash: profile.profileHash,
|
|
1723
|
-
snapshotId: nextSnapshotId,
|
|
1724
|
-
runtime: params.runtime,
|
|
1725
|
-
createdAtMs: Date.now(),
|
|
1726
|
-
dependencyCount: profile.dependencyCount
|
|
1727
|
-
});
|
|
1728
|
-
await params.onProgress?.("build_complete");
|
|
1729
|
-
return { snapshotId: nextSnapshotId, source: "built" };
|
|
1730
|
-
},
|
|
1731
|
-
canUseCachedSnapshot,
|
|
1732
|
-
{
|
|
1733
|
-
onWaitingForLock: async () => {
|
|
1734
|
-
await params.onProgress?.("waiting_for_lock");
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
);
|
|
1738
|
-
return {
|
|
1739
|
-
snapshotId: lockResult.snapshotId,
|
|
1740
|
-
profileHash: profile.profileHash,
|
|
1741
|
-
dependencyCount: profile.dependencyCount,
|
|
1742
|
-
cacheHit: lockResult.source !== "built",
|
|
1743
|
-
resolveOutcome: toResolveOutcome(
|
|
1744
|
-
Boolean(params.forceRebuild),
|
|
1745
|
-
lockResult.source,
|
|
1746
|
-
lockResult.waitedForLock
|
|
1747
|
-
),
|
|
1748
|
-
...rebuildReason ? { rebuildReason } : {}
|
|
1749
|
-
};
|
|
1750
|
-
}
|
|
1751
|
-
);
|
|
1752
|
-
}
|
|
1753
|
-
function isSnapshotMissingError(error) {
|
|
1754
|
-
const searchable = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
1755
|
-
return searchable.includes("snapshot") && (searchable.includes("not found") || searchable.includes("unknown") || searchable.includes("404"));
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
export {
|
|
1759
|
-
homeDir,
|
|
1760
|
-
skillRoots,
|
|
1761
|
-
soulPathCandidates,
|
|
1762
|
-
aboutPathCandidates,
|
|
1763
|
-
resolveAuthTokenPlaceholder,
|
|
1764
|
-
CredentialUnavailableError,
|
|
1765
|
-
buildOAuthTokenRequest,
|
|
1766
|
-
parseOAuthTokenResponse,
|
|
1767
|
-
getPluginCapabilityProviders,
|
|
1768
|
-
getPluginProviders,
|
|
1769
|
-
getPluginRuntimeDependencies,
|
|
1770
|
-
getPluginRuntimePostinstall,
|
|
1771
|
-
getPluginOAuthConfig,
|
|
1772
|
-
getPluginSkillRoots,
|
|
1773
|
-
isPluginProvider,
|
|
1774
|
-
createPluginBroker,
|
|
1775
|
-
botConfig,
|
|
1776
|
-
getSlackBotToken,
|
|
1777
|
-
getSlackSigningSecret,
|
|
1778
|
-
getSlackClientId,
|
|
1779
|
-
getSlackClientSecret,
|
|
1780
|
-
getStateAdapter,
|
|
1781
|
-
disconnectStateAdapter,
|
|
1782
|
-
claimQueueIngressDedup,
|
|
1783
|
-
hasQueueIngressDedup,
|
|
1784
|
-
getQueueMessageProcessingState,
|
|
1785
|
-
acquireQueueMessageProcessingOwnership,
|
|
1786
|
-
refreshQueueMessageProcessingOwnership,
|
|
1787
|
-
completeQueueMessageProcessingOwnership,
|
|
1788
|
-
failQueueMessageProcessingOwnership,
|
|
1789
|
-
getAgentTurnSessionCheckpoint,
|
|
1790
|
-
upsertAgentTurnSessionCheckpoint,
|
|
1791
|
-
SANDBOX_WORKSPACE_ROOT,
|
|
1792
|
-
SANDBOX_SKILLS_ROOT,
|
|
1793
|
-
sandboxSkillDir,
|
|
1794
|
-
getRuntimeDependencyProfileHash,
|
|
1795
|
-
resolveRuntimeDependencySnapshot,
|
|
1796
|
-
isSnapshotMissingError
|
|
1797
|
-
};
|