@sentry/junior 0.2.0 → 0.4.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/README.md +8 -1
- package/dist/{bot-T73QBC4J.js → bot-Y6A47LEZ.js} +3 -2
- package/dist/{chunk-TQSDLJVE.js → chunk-DGKNXMK4.js} +2 -2
- package/dist/{chunk-QHDDCUTN.js → chunk-OZFXD5IG.js} +552 -261
- package/dist/{chunk-RXNMJQPY.js → chunk-RFUE5VBK.js} +854 -346
- package/dist/{chunk-JDBWDYGR.js → chunk-TEQ3UIS7.js} +1 -1
- package/dist/chunk-Z5E25LRN.js +445 -0
- package/dist/cli/snapshot-warmup.js +2 -1
- package/dist/handlers/queue-callback.js +4 -3
- package/dist/handlers/router.js +90 -39
- package/dist/handlers/webhooks.js +1 -1
- package/dist/next-config.d.ts +2 -2
- package/dist/next-config.js +82 -77
- package/package.json +1 -1
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
setSpanAttributes,
|
|
5
5
|
withSpan
|
|
6
6
|
} from "./chunk-PY4AI2GZ.js";
|
|
7
|
+
import {
|
|
8
|
+
discoverInstalledPluginPackageContent,
|
|
9
|
+
discoverProjectRoots
|
|
10
|
+
} from "./chunk-Z5E25LRN.js";
|
|
7
11
|
|
|
8
12
|
// src/chat/config.ts
|
|
9
13
|
var MIN_AGENT_TURN_TIMEOUT_MS = 10 * 1e3;
|
|
@@ -385,15 +389,15 @@ import { createHash } from "crypto";
|
|
|
385
389
|
import { Sandbox } from "@vercel/sandbox";
|
|
386
390
|
|
|
387
391
|
// src/chat/plugins/registry.ts
|
|
388
|
-
import { readFileSync
|
|
389
|
-
import
|
|
392
|
+
import { readFileSync, readdirSync, statSync } from "fs";
|
|
393
|
+
import path2 from "path";
|
|
390
394
|
import { parse as parseYaml } from "yaml";
|
|
391
395
|
|
|
392
396
|
// src/chat/home.ts
|
|
393
397
|
import fs from "fs";
|
|
394
398
|
import path from "path";
|
|
395
399
|
function homeDir() {
|
|
396
|
-
return
|
|
400
|
+
return resolveHomeDir();
|
|
397
401
|
}
|
|
398
402
|
function unique(values) {
|
|
399
403
|
return [...new Set(values)];
|
|
@@ -409,46 +413,60 @@ function pathExists(targetPath) {
|
|
|
409
413
|
function hasAnyDataMarkers(appDir) {
|
|
410
414
|
return pathExists(path.join(appDir, "SOUL.md"));
|
|
411
415
|
}
|
|
412
|
-
function
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
return directApp;
|
|
416
|
+
function scoreAppCandidate(appDir) {
|
|
417
|
+
let score = 0;
|
|
418
|
+
if (pathExists(path.join(appDir, "SOUL.md"))) {
|
|
419
|
+
score += 4;
|
|
417
420
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const localPackagesDir = path.join(cwd, "packages");
|
|
421
|
-
if (pathExists(localPackagesDir)) {
|
|
422
|
-
for (const entry of fs.readdirSync(localPackagesDir)) {
|
|
423
|
-
packageRoots.push(path.join(localPackagesDir, entry));
|
|
424
|
-
}
|
|
421
|
+
if (pathExists(path.join(appDir, "ABOUT.md"))) {
|
|
422
|
+
score += 2;
|
|
425
423
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const nestedPackages = path.join(child, "packages");
|
|
429
|
-
if (!pathExists(nestedPackages)) {
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
for (const nestedEntry of fs.readdirSync(nestedPackages)) {
|
|
433
|
-
packageRoots.push(path.join(nestedPackages, nestedEntry));
|
|
434
|
-
}
|
|
424
|
+
if (pathExists(path.join(appDir, "skills"))) {
|
|
425
|
+
score += 1;
|
|
435
426
|
}
|
|
436
|
-
|
|
437
|
-
|
|
427
|
+
if (pathExists(path.join(appDir, "plugins"))) {
|
|
428
|
+
score += 1;
|
|
429
|
+
}
|
|
430
|
+
return score;
|
|
431
|
+
}
|
|
432
|
+
function resolveCandidateAppDirs(cwd, projectRoots) {
|
|
433
|
+
const roots = projectRoots ?? discoverProjectRoots(cwd);
|
|
434
|
+
const resolved = [];
|
|
435
|
+
const seen = /* @__PURE__ */ new Set();
|
|
436
|
+
for (const root of roots) {
|
|
437
|
+
const appDir = path.resolve(root, "app");
|
|
438
438
|
if (!pathExists(appDir)) {
|
|
439
439
|
continue;
|
|
440
440
|
}
|
|
441
|
-
|
|
441
|
+
if (seen.has(appDir)) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
seen.add(appDir);
|
|
445
|
+
resolved.push(appDir);
|
|
442
446
|
}
|
|
447
|
+
return resolved;
|
|
448
|
+
}
|
|
449
|
+
function resolveHomeDir(cwd = process.cwd(), options) {
|
|
450
|
+
const resolvedCwd = path.resolve(cwd);
|
|
451
|
+
const directApp = path.resolve(resolvedCwd, "app");
|
|
452
|
+
if (pathExists(directApp) && hasAnyDataMarkers(directApp)) {
|
|
453
|
+
return directApp;
|
|
454
|
+
}
|
|
455
|
+
const candidates = resolveCandidateAppDirs(resolvedCwd, options?.projectRoots);
|
|
443
456
|
if (candidates.length === 0) {
|
|
444
457
|
return directApp;
|
|
445
458
|
}
|
|
446
459
|
candidates.sort((left, right) => {
|
|
447
|
-
const leftScore =
|
|
448
|
-
const rightScore =
|
|
460
|
+
const leftScore = scoreAppCandidate(left);
|
|
461
|
+
const rightScore = scoreAppCandidate(right);
|
|
449
462
|
if (leftScore !== rightScore) {
|
|
450
463
|
return rightScore - leftScore;
|
|
451
464
|
}
|
|
465
|
+
const leftDistance = path.relative(resolvedCwd, left).split(path.sep).length;
|
|
466
|
+
const rightDistance = path.relative(resolvedCwd, right).split(path.sep).length;
|
|
467
|
+
if (leftDistance !== rightDistance) {
|
|
468
|
+
return leftDistance - rightDistance;
|
|
469
|
+
}
|
|
452
470
|
return left.localeCompare(right);
|
|
453
471
|
});
|
|
454
472
|
return candidates[0];
|
|
@@ -536,7 +554,9 @@ function getPrivateKey(envName) {
|
|
|
536
554
|
);
|
|
537
555
|
}
|
|
538
556
|
if (key.asymmetricKeyType !== "rsa") {
|
|
539
|
-
throw new Error(
|
|
557
|
+
throw new Error(
|
|
558
|
+
`Invalid ${envName}: GitHub App signing requires an RSA private key`
|
|
559
|
+
);
|
|
540
560
|
}
|
|
541
561
|
return key;
|
|
542
562
|
}
|
|
@@ -553,8 +573,8 @@ function createAppJwt(appId, privateKeyEnv) {
|
|
|
553
573
|
const signature = signer.sign(getPrivateKey(privateKeyEnv)).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
554
574
|
return `${signingInput}.${signature}`;
|
|
555
575
|
}
|
|
556
|
-
async function githubRequest(apiBase,
|
|
557
|
-
const response = await fetch(`${apiBase}${
|
|
576
|
+
async function githubRequest(apiBase, path3, params) {
|
|
577
|
+
const response = await fetch(`${apiBase}${path3}`, {
|
|
558
578
|
method: params.method ?? "GET",
|
|
559
579
|
headers: {
|
|
560
580
|
Accept: "application/vnd.github+json",
|
|
@@ -591,7 +611,14 @@ function capabilityToPermissions(capability, pluginName) {
|
|
|
591
611
|
function createGitHubAppBroker(manifest, credentials) {
|
|
592
612
|
const tokenCache = /* @__PURE__ */ new Map();
|
|
593
613
|
const provider = manifest.name;
|
|
594
|
-
const {
|
|
614
|
+
const {
|
|
615
|
+
apiDomains,
|
|
616
|
+
apiHeaders,
|
|
617
|
+
authTokenEnv,
|
|
618
|
+
appIdEnv,
|
|
619
|
+
privateKeyEnv,
|
|
620
|
+
installationIdEnv
|
|
621
|
+
} = credentials;
|
|
595
622
|
const apiBase = `https://${apiDomains[0]}`;
|
|
596
623
|
const placeholder = resolveAuthTokenPlaceholder(credentials);
|
|
597
624
|
return {
|
|
@@ -622,6 +649,7 @@ function createGitHubAppBroker(manifest, credentials) {
|
|
|
622
649
|
headerTransforms: apiDomains.map((domain) => ({
|
|
623
650
|
domain,
|
|
624
651
|
headers: {
|
|
652
|
+
...apiHeaders ?? {},
|
|
625
653
|
Authorization: `Bearer ${cached.token}`
|
|
626
654
|
}
|
|
627
655
|
})),
|
|
@@ -641,17 +669,16 @@ function createGitHubAppBroker(manifest, credentials) {
|
|
|
641
669
|
if (repositoryName) {
|
|
642
670
|
tokenRequestBody.repositories = [repositoryName];
|
|
643
671
|
}
|
|
644
|
-
const accessTokenResponse = await githubRequest(
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
token: appJwt,
|
|
650
|
-
body: tokenRequestBody
|
|
651
|
-
}
|
|
652
|
-
);
|
|
672
|
+
const accessTokenResponse = await githubRequest(apiBase, `/app/installations/${installationId}/access_tokens`, {
|
|
673
|
+
method: "POST",
|
|
674
|
+
token: appJwt,
|
|
675
|
+
body: tokenRequestBody
|
|
676
|
+
});
|
|
653
677
|
const providerExpiresAtMs = Date.parse(accessTokenResponse.expires_at);
|
|
654
|
-
const expiresAtMs = Math.min(
|
|
678
|
+
const expiresAtMs = Math.min(
|
|
679
|
+
providerExpiresAtMs,
|
|
680
|
+
Date.now() + MAX_LEASE_MS
|
|
681
|
+
);
|
|
655
682
|
tokenCache.set(cacheKey, {
|
|
656
683
|
installationId,
|
|
657
684
|
token: accessTokenResponse.token,
|
|
@@ -665,6 +692,7 @@ function createGitHubAppBroker(manifest, credentials) {
|
|
|
665
692
|
headerTransforms: apiDomains.map((domain) => ({
|
|
666
693
|
domain,
|
|
667
694
|
headers: {
|
|
695
|
+
...apiHeaders ?? {},
|
|
668
696
|
Authorization: `Bearer ${accessTokenResponse.token}`
|
|
669
697
|
}
|
|
670
698
|
})),
|
|
@@ -692,6 +720,70 @@ var CredentialUnavailableError = class extends Error {
|
|
|
692
720
|
}
|
|
693
721
|
};
|
|
694
722
|
|
|
723
|
+
// src/chat/plugins/oauth-request.ts
|
|
724
|
+
var DEFAULT_TOKEN_CONTENT_TYPE = "application/x-www-form-urlencoded";
|
|
725
|
+
function requireNonEmptyTokenField(data, field) {
|
|
726
|
+
const value = data[field];
|
|
727
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
728
|
+
throw new Error(`OAuth token response missing ${field}`);
|
|
729
|
+
}
|
|
730
|
+
return value;
|
|
731
|
+
}
|
|
732
|
+
function contentTypeToBody(contentType, payload) {
|
|
733
|
+
const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase();
|
|
734
|
+
if (!mediaType || mediaType === DEFAULT_TOKEN_CONTENT_TYPE) {
|
|
735
|
+
return new URLSearchParams(payload);
|
|
736
|
+
}
|
|
737
|
+
if (mediaType === "application/json" || mediaType.endsWith("+json")) {
|
|
738
|
+
return JSON.stringify(payload);
|
|
739
|
+
}
|
|
740
|
+
throw new Error(`Unsupported OAuth token Content-Type: ${contentType}`);
|
|
741
|
+
}
|
|
742
|
+
function buildOAuthTokenRequest(input) {
|
|
743
|
+
const headers = new Headers({ Accept: "application/json" });
|
|
744
|
+
for (const [name, value] of Object.entries(input.tokenExtraHeaders ?? {})) {
|
|
745
|
+
headers.set(name, value);
|
|
746
|
+
}
|
|
747
|
+
if (!headers.has("Content-Type")) {
|
|
748
|
+
headers.set("Content-Type", DEFAULT_TOKEN_CONTENT_TYPE);
|
|
749
|
+
}
|
|
750
|
+
const payload = { ...input.payload };
|
|
751
|
+
if (input.tokenAuthMethod === "basic") {
|
|
752
|
+
headers.set(
|
|
753
|
+
"Authorization",
|
|
754
|
+
`Basic ${Buffer.from(`${input.clientId}:${input.clientSecret}`).toString("base64")}`
|
|
755
|
+
);
|
|
756
|
+
} else {
|
|
757
|
+
payload.client_id = input.clientId;
|
|
758
|
+
payload.client_secret = input.clientSecret;
|
|
759
|
+
}
|
|
760
|
+
const contentType = headers.get("Content-Type") ?? DEFAULT_TOKEN_CONTENT_TYPE;
|
|
761
|
+
const serializedHeaders = {};
|
|
762
|
+
headers.forEach((value, key) => {
|
|
763
|
+
serializedHeaders[key] = value;
|
|
764
|
+
});
|
|
765
|
+
return {
|
|
766
|
+
headers: serializedHeaders,
|
|
767
|
+
body: contentTypeToBody(contentType, payload)
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function parseOAuthTokenResponse(data) {
|
|
771
|
+
const accessToken = requireNonEmptyTokenField(data, "access_token");
|
|
772
|
+
const refreshToken = requireNonEmptyTokenField(data, "refresh_token");
|
|
773
|
+
const expiresIn = data.expires_in;
|
|
774
|
+
if (expiresIn === void 0) {
|
|
775
|
+
return { accessToken, refreshToken };
|
|
776
|
+
}
|
|
777
|
+
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
778
|
+
throw new Error("OAuth token response returned invalid expires_in");
|
|
779
|
+
}
|
|
780
|
+
return {
|
|
781
|
+
accessToken,
|
|
782
|
+
refreshToken,
|
|
783
|
+
expiresAt: Date.now() + expiresIn * 1e3
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
695
787
|
// src/chat/plugins/oauth-bearer-broker.ts
|
|
696
788
|
var MAX_LEASE_MS2 = 60 * 60 * 1e3;
|
|
697
789
|
var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
@@ -699,35 +791,38 @@ async function refreshAccessToken(refreshToken, oauth) {
|
|
|
699
791
|
const clientId = process.env[oauth.clientIdEnv]?.trim();
|
|
700
792
|
const clientSecret = process.env[oauth.clientSecretEnv]?.trim();
|
|
701
793
|
if (!clientId || !clientSecret) {
|
|
702
|
-
throw new Error(
|
|
794
|
+
throw new Error(
|
|
795
|
+
`Missing ${oauth.clientIdEnv} or ${oauth.clientSecretEnv} for token refresh`
|
|
796
|
+
);
|
|
703
797
|
}
|
|
798
|
+
const request = buildOAuthTokenRequest({
|
|
799
|
+
clientId,
|
|
800
|
+
clientSecret,
|
|
801
|
+
payload: {
|
|
802
|
+
grant_type: "refresh_token",
|
|
803
|
+
refresh_token: refreshToken
|
|
804
|
+
},
|
|
805
|
+
tokenAuthMethod: oauth.tokenAuthMethod,
|
|
806
|
+
tokenExtraHeaders: oauth.tokenExtraHeaders
|
|
807
|
+
});
|
|
704
808
|
const response = await fetch(oauth.tokenEndpoint, {
|
|
705
809
|
method: "POST",
|
|
706
|
-
headers:
|
|
707
|
-
body:
|
|
708
|
-
grant_type: "refresh_token",
|
|
709
|
-
refresh_token: refreshToken,
|
|
710
|
-
client_id: clientId,
|
|
711
|
-
client_secret: clientSecret
|
|
712
|
-
})
|
|
810
|
+
headers: request.headers,
|
|
811
|
+
body: request.body
|
|
713
812
|
});
|
|
714
813
|
if (!response.ok) {
|
|
715
814
|
throw new Error(`Token refresh failed: ${response.status}`);
|
|
716
815
|
}
|
|
717
816
|
const data = await response.json();
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
return
|
|
722
|
-
accessToken: data.access_token,
|
|
723
|
-
refreshToken: data.refresh_token,
|
|
724
|
-
expiresIn: data.expires_in
|
|
725
|
-
};
|
|
817
|
+
return parseOAuthTokenResponse(data);
|
|
818
|
+
}
|
|
819
|
+
function getLeaseExpiry(expiresAt) {
|
|
820
|
+
return expiresAt ? Math.min(expiresAt, Date.now() + MAX_LEASE_MS2) : Date.now() + MAX_LEASE_MS2;
|
|
726
821
|
}
|
|
727
822
|
function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
728
823
|
const provider = manifest.name;
|
|
729
824
|
const supportedCapabilities = new Set(manifest.capabilities);
|
|
730
|
-
const { apiDomains, authTokenEnv } = credentials;
|
|
825
|
+
const { apiDomains, apiHeaders, authTokenEnv } = credentials;
|
|
731
826
|
const authTokenPlaceholder = resolveAuthTokenPlaceholder(credentials);
|
|
732
827
|
function buildLease(token, capability, expiresAtMs, reason) {
|
|
733
828
|
return {
|
|
@@ -737,7 +832,7 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
737
832
|
env: { [authTokenEnv]: authTokenPlaceholder },
|
|
738
833
|
headerTransforms: apiDomains.map((domain) => ({
|
|
739
834
|
domain,
|
|
740
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
835
|
+
headers: { ...apiHeaders ?? {}, Authorization: `Bearer ${token}` }
|
|
741
836
|
})),
|
|
742
837
|
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
743
838
|
metadata: { reason }
|
|
@@ -746,27 +841,58 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
746
841
|
return {
|
|
747
842
|
async issue(input) {
|
|
748
843
|
if (!supportedCapabilities.has(input.capability)) {
|
|
749
|
-
throw new Error(
|
|
844
|
+
throw new Error(
|
|
845
|
+
`Unsupported ${provider} capability: ${input.capability}`
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
const envToken = process.env[authTokenEnv]?.trim();
|
|
849
|
+
const oauth = manifest.oauth;
|
|
850
|
+
if (!oauth) {
|
|
851
|
+
if (envToken) {
|
|
852
|
+
return buildLease(
|
|
853
|
+
envToken,
|
|
854
|
+
input.capability,
|
|
855
|
+
Date.now() + MAX_LEASE_MS2,
|
|
856
|
+
input.reason
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
throw new CredentialUnavailableError(
|
|
860
|
+
provider,
|
|
861
|
+
`No ${provider} credentials available.`
|
|
862
|
+
);
|
|
750
863
|
}
|
|
751
|
-
if (input.requesterId
|
|
752
|
-
const stored = await deps.userTokenStore.get(
|
|
864
|
+
if (input.requesterId) {
|
|
865
|
+
const stored = await deps.userTokenStore.get(
|
|
866
|
+
input.requesterId,
|
|
867
|
+
provider
|
|
868
|
+
);
|
|
753
869
|
if (stored) {
|
|
754
870
|
const now = Date.now();
|
|
755
|
-
if (stored.expiresAt
|
|
871
|
+
if (stored.expiresAt !== void 0 && stored.expiresAt - now < REFRESH_BUFFER_MS) {
|
|
756
872
|
try {
|
|
757
|
-
const refreshed = await refreshAccessToken(
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
873
|
+
const refreshed = await refreshAccessToken(
|
|
874
|
+
stored.refreshToken,
|
|
875
|
+
oauth
|
|
876
|
+
);
|
|
877
|
+
await deps.userTokenStore.set(
|
|
878
|
+
input.requesterId,
|
|
879
|
+
provider,
|
|
880
|
+
refreshed
|
|
881
|
+
);
|
|
882
|
+
return buildLease(
|
|
883
|
+
refreshed.accessToken,
|
|
884
|
+
input.capability,
|
|
885
|
+
getLeaseExpiry(refreshed.expiresAt),
|
|
886
|
+
input.reason
|
|
887
|
+
);
|
|
766
888
|
} catch {
|
|
767
889
|
if (stored.expiresAt > Date.now()) {
|
|
768
|
-
|
|
769
|
-
|
|
890
|
+
return buildLease(
|
|
891
|
+
stored.accessToken,
|
|
892
|
+
input.capability,
|
|
893
|
+
getLeaseExpiry(stored.expiresAt),
|
|
894
|
+
input.reason
|
|
895
|
+
);
|
|
770
896
|
}
|
|
771
897
|
throw new CredentialUnavailableError(
|
|
772
898
|
provider,
|
|
@@ -774,9 +900,13 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
774
900
|
);
|
|
775
901
|
}
|
|
776
902
|
}
|
|
777
|
-
if (stored.expiresAt > Date.now()) {
|
|
778
|
-
|
|
779
|
-
|
|
903
|
+
if (stored.expiresAt === void 0 || stored.expiresAt > Date.now()) {
|
|
904
|
+
return buildLease(
|
|
905
|
+
stored.accessToken,
|
|
906
|
+
input.capability,
|
|
907
|
+
getLeaseExpiry(stored.expiresAt),
|
|
908
|
+
input.reason
|
|
909
|
+
);
|
|
780
910
|
}
|
|
781
911
|
throw new CredentialUnavailableError(
|
|
782
912
|
provider,
|
|
@@ -788,10 +918,13 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
788
918
|
`No ${provider} credentials available.`
|
|
789
919
|
);
|
|
790
920
|
}
|
|
791
|
-
const envToken = process.env[authTokenEnv]?.trim();
|
|
792
921
|
if (envToken) {
|
|
793
|
-
|
|
794
|
-
|
|
922
|
+
return buildLease(
|
|
923
|
+
envToken,
|
|
924
|
+
input.capability,
|
|
925
|
+
getLeaseExpiry(),
|
|
926
|
+
input.reason
|
|
927
|
+
);
|
|
795
928
|
}
|
|
796
929
|
throw new CredentialUnavailableError(
|
|
797
930
|
provider,
|
|
@@ -801,98 +934,6 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
801
934
|
};
|
|
802
935
|
}
|
|
803
936
|
|
|
804
|
-
// src/chat/plugins/package-discovery.ts
|
|
805
|
-
import { readFileSync, statSync } from "fs";
|
|
806
|
-
import path2 from "path";
|
|
807
|
-
function isDirectory(targetPath) {
|
|
808
|
-
try {
|
|
809
|
-
return statSync(targetPath).isDirectory();
|
|
810
|
-
} catch {
|
|
811
|
-
return false;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
function isFile(targetPath) {
|
|
815
|
-
try {
|
|
816
|
-
return statSync(targetPath).isFile();
|
|
817
|
-
} catch {
|
|
818
|
-
return false;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
function readRootPackageJson(cwd) {
|
|
822
|
-
const rootPackageJsonPath = path2.join(cwd, "package.json");
|
|
823
|
-
try {
|
|
824
|
-
const raw = readFileSync(rootPackageJsonPath, "utf8");
|
|
825
|
-
return JSON.parse(raw);
|
|
826
|
-
} catch {
|
|
827
|
-
return null;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
function uniqueSorted(values) {
|
|
831
|
-
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
832
|
-
}
|
|
833
|
-
function readInstalledDependencyNames(cwd) {
|
|
834
|
-
const rootPackageJson = readRootPackageJson(cwd);
|
|
835
|
-
if (!rootPackageJson) {
|
|
836
|
-
return [];
|
|
837
|
-
}
|
|
838
|
-
return uniqueSorted([
|
|
839
|
-
...Object.keys(rootPackageJson.dependencies ?? {}),
|
|
840
|
-
...Object.keys(rootPackageJson.optionalDependencies ?? {})
|
|
841
|
-
]);
|
|
842
|
-
}
|
|
843
|
-
function packageInstallDir(cwd, packageName) {
|
|
844
|
-
return path2.join(cwd, "node_modules", ...packageName.split("/"));
|
|
845
|
-
}
|
|
846
|
-
function discoverInstalledJuniorContentPackages(cwd = process.cwd()) {
|
|
847
|
-
const dependencies = readInstalledDependencyNames(cwd);
|
|
848
|
-
const discovered = [];
|
|
849
|
-
for (const dependency of dependencies) {
|
|
850
|
-
const dir = packageInstallDir(cwd, dependency);
|
|
851
|
-
if (!isDirectory(dir)) {
|
|
852
|
-
continue;
|
|
853
|
-
}
|
|
854
|
-
const hasRootPluginManifest = isFile(path2.join(dir, "plugin.yaml"));
|
|
855
|
-
const hasPluginsDir = isDirectory(path2.join(dir, "plugins"));
|
|
856
|
-
const hasSkillsDir = isDirectory(path2.join(dir, "skills"));
|
|
857
|
-
if (!hasRootPluginManifest && !hasPluginsDir && !hasSkillsDir) {
|
|
858
|
-
continue;
|
|
859
|
-
}
|
|
860
|
-
discovered.push({
|
|
861
|
-
name: dependency,
|
|
862
|
-
dir,
|
|
863
|
-
hasRootPluginManifest,
|
|
864
|
-
hasPluginsDir,
|
|
865
|
-
hasSkillsDir
|
|
866
|
-
});
|
|
867
|
-
}
|
|
868
|
-
return discovered;
|
|
869
|
-
}
|
|
870
|
-
function discoverInstalledPluginPackageContent(cwd = process.cwd()) {
|
|
871
|
-
const manifestRoots = [];
|
|
872
|
-
const skillRoots2 = [];
|
|
873
|
-
const tracingIncludes = [];
|
|
874
|
-
for (const pkg of discoverInstalledJuniorContentPackages(cwd)) {
|
|
875
|
-
const base = `./node_modules/${pkg.name}`;
|
|
876
|
-
if (pkg.hasRootPluginManifest) {
|
|
877
|
-
manifestRoots.push(pkg.dir);
|
|
878
|
-
tracingIncludes.push(`${base}/plugin.yaml`);
|
|
879
|
-
}
|
|
880
|
-
if (pkg.hasPluginsDir) {
|
|
881
|
-
manifestRoots.push(path2.join(pkg.dir, "plugins"));
|
|
882
|
-
tracingIncludes.push(`${base}/plugins/**/*`);
|
|
883
|
-
}
|
|
884
|
-
if (pkg.hasSkillsDir) {
|
|
885
|
-
skillRoots2.push(path2.join(pkg.dir, "skills"));
|
|
886
|
-
tracingIncludes.push(`${base}/skills/**/*`);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
return {
|
|
890
|
-
manifestRoots: uniqueSorted(manifestRoots),
|
|
891
|
-
skillRoots: uniqueSorted(skillRoots2),
|
|
892
|
-
tracingIncludes: uniqueSorted(tracingIncludes)
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
|
|
896
937
|
// src/chat/plugins/registry.ts
|
|
897
938
|
var PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
898
939
|
var SHORT_CAPABILITY_RE = /^[a-z0-9]+(\.[a-z0-9-]+)*$/;
|
|
@@ -900,6 +941,15 @@ var SHORT_CONFIG_KEY_RE = /^[a-z0-9]+(\.[a-z0-9-]+)*$/;
|
|
|
900
941
|
var AUTH_TOKEN_ENV_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
901
942
|
var API_DOMAIN_RE = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
902
943
|
var RUNTIME_POSTINSTALL_CMD_RE = /^[A-Za-z0-9._/-]+$/;
|
|
944
|
+
var RESERVED_AUTHORIZE_PARAM_KEYS = /* @__PURE__ */ new Set([
|
|
945
|
+
"client_id",
|
|
946
|
+
"scope",
|
|
947
|
+
"state",
|
|
948
|
+
"redirect_uri",
|
|
949
|
+
"response_type"
|
|
950
|
+
]);
|
|
951
|
+
var FORBIDDEN_API_HEADER_NAMES = /* @__PURE__ */ new Set(["authorization"]);
|
|
952
|
+
var FORBIDDEN_TOKEN_HEADER_NAMES = /* @__PURE__ */ new Set(["authorization"]);
|
|
903
953
|
function toRecord(value, errorMessage) {
|
|
904
954
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
905
955
|
throw new Error(errorMessage);
|
|
@@ -914,14 +964,24 @@ function requireStringField(record, field, errorMessage) {
|
|
|
914
964
|
return value.trim();
|
|
915
965
|
}
|
|
916
966
|
function requireEnvVarField(record, field, pluginName) {
|
|
917
|
-
const value = requireStringField(
|
|
967
|
+
const value = requireStringField(
|
|
968
|
+
record,
|
|
969
|
+
field,
|
|
970
|
+
`Plugin ${pluginName} ${field} must be a non-empty string`
|
|
971
|
+
);
|
|
918
972
|
if (!AUTH_TOKEN_ENV_RE.test(value)) {
|
|
919
|
-
throw new Error(
|
|
973
|
+
throw new Error(
|
|
974
|
+
`Plugin ${pluginName} ${field} must be an uppercase env var name`
|
|
975
|
+
);
|
|
920
976
|
}
|
|
921
977
|
return value;
|
|
922
978
|
}
|
|
923
979
|
function requireHttpsUrlField(record, field, pluginName) {
|
|
924
|
-
const value = requireStringField(
|
|
980
|
+
const value = requireStringField(
|
|
981
|
+
record,
|
|
982
|
+
field,
|
|
983
|
+
`Plugin ${pluginName} oauth.${field} must be a non-empty string`
|
|
984
|
+
);
|
|
925
985
|
let parsed;
|
|
926
986
|
try {
|
|
927
987
|
parsed = new URL(value);
|
|
@@ -936,26 +996,79 @@ function requireHttpsUrlField(record, field, pluginName) {
|
|
|
936
996
|
function normalizeApiDomain(rawDomain, name) {
|
|
937
997
|
const domain = typeof rawDomain === "string" ? rawDomain.trim().toLowerCase() : "";
|
|
938
998
|
if (!domain) {
|
|
939
|
-
throw new Error(
|
|
999
|
+
throw new Error(
|
|
1000
|
+
`Plugin ${name} credentials.api-domains entries must be non-empty strings`
|
|
1001
|
+
);
|
|
940
1002
|
}
|
|
941
1003
|
if (!API_DOMAIN_RE.test(domain)) {
|
|
942
|
-
throw new Error(
|
|
1004
|
+
throw new Error(
|
|
1005
|
+
`Plugin ${name} credentials.api-domains entries must be valid domain names`
|
|
1006
|
+
);
|
|
943
1007
|
}
|
|
944
1008
|
return domain;
|
|
945
1009
|
}
|
|
1010
|
+
function parseStringMap(data, errorLabel, options = {}) {
|
|
1011
|
+
if (data === void 0) {
|
|
1012
|
+
return void 0;
|
|
1013
|
+
}
|
|
1014
|
+
const record = toRecord(
|
|
1015
|
+
data,
|
|
1016
|
+
`${errorLabel} must be an object when provided`
|
|
1017
|
+
);
|
|
1018
|
+
const entries = Object.entries(record);
|
|
1019
|
+
if (entries.length === 0) {
|
|
1020
|
+
return void 0;
|
|
1021
|
+
}
|
|
1022
|
+
const result = {};
|
|
1023
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1024
|
+
for (const [rawKey, rawValue] of entries) {
|
|
1025
|
+
const key = rawKey.trim();
|
|
1026
|
+
if (!key) {
|
|
1027
|
+
throw new Error(`${errorLabel} keys must be non-empty strings`);
|
|
1028
|
+
}
|
|
1029
|
+
if (typeof rawValue !== "string" || !rawValue.trim()) {
|
|
1030
|
+
throw new Error(`${errorLabel}.${key} must be a non-empty string`);
|
|
1031
|
+
}
|
|
1032
|
+
const normalizedKey = key.toLowerCase();
|
|
1033
|
+
if (options.reservedKeys?.has(normalizedKey)) {
|
|
1034
|
+
throw new Error(`${errorLabel}.${key} is reserved by the runtime`);
|
|
1035
|
+
}
|
|
1036
|
+
if (options.forbiddenKeys?.has(normalizedKey)) {
|
|
1037
|
+
throw new Error(`${errorLabel}.${key} is not allowed`);
|
|
1038
|
+
}
|
|
1039
|
+
if (seen.has(normalizedKey)) {
|
|
1040
|
+
throw new Error(`${errorLabel}.${key} is duplicated`);
|
|
1041
|
+
}
|
|
1042
|
+
seen.add(normalizedKey);
|
|
1043
|
+
result[key] = rawValue.trim();
|
|
1044
|
+
}
|
|
1045
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
1046
|
+
}
|
|
946
1047
|
function parseBaseCredentialFields(data, name) {
|
|
947
1048
|
const rawDomains = data["api-domains"];
|
|
948
1049
|
if (!Array.isArray(rawDomains) || rawDomains.length === 0) {
|
|
949
|
-
throw new Error(
|
|
1050
|
+
throw new Error(
|
|
1051
|
+
`Plugin ${name} credentials.api-domains must be a non-empty array of strings`
|
|
1052
|
+
);
|
|
950
1053
|
}
|
|
951
|
-
const apiDomains = rawDomains.map(
|
|
1054
|
+
const apiDomains = rawDomains.map(
|
|
1055
|
+
(rawDomain) => normalizeApiDomain(rawDomain, name)
|
|
1056
|
+
);
|
|
1057
|
+
const apiHeaders = parseStringMap(
|
|
1058
|
+
data["api-headers"],
|
|
1059
|
+
`Plugin ${name} credentials.api-headers`,
|
|
1060
|
+
{ forbiddenKeys: FORBIDDEN_API_HEADER_NAMES }
|
|
1061
|
+
);
|
|
952
1062
|
const authTokenEnv = requireEnvVarField(data, "auth-token-env", name);
|
|
953
1063
|
const authTokenPlaceholderRaw = data["auth-token-placeholder"];
|
|
954
1064
|
if (authTokenPlaceholderRaw !== void 0 && (typeof authTokenPlaceholderRaw !== "string" || !authTokenPlaceholderRaw.trim())) {
|
|
955
|
-
throw new Error(
|
|
1065
|
+
throw new Error(
|
|
1066
|
+
`Plugin ${name} credentials.auth-token-placeholder must be a non-empty string when provided`
|
|
1067
|
+
);
|
|
956
1068
|
}
|
|
957
1069
|
return {
|
|
958
1070
|
apiDomains,
|
|
1071
|
+
...apiHeaders ? { apiHeaders } : {},
|
|
959
1072
|
authTokenEnv,
|
|
960
1073
|
...typeof authTokenPlaceholderRaw === "string" ? { authTokenPlaceholder: authTokenPlaceholderRaw.trim() } : {}
|
|
961
1074
|
};
|
|
@@ -970,8 +1083,18 @@ function parseCredentials(data, name) {
|
|
|
970
1083
|
const base = parseBaseCredentialFields(data, name);
|
|
971
1084
|
const appIdEnv = requireEnvVarField(data, "app-id-env", name);
|
|
972
1085
|
const privateKeyEnv = requireEnvVarField(data, "private-key-env", name);
|
|
973
|
-
const installationIdEnv = requireEnvVarField(
|
|
974
|
-
|
|
1086
|
+
const installationIdEnv = requireEnvVarField(
|
|
1087
|
+
data,
|
|
1088
|
+
"installation-id-env",
|
|
1089
|
+
name
|
|
1090
|
+
);
|
|
1091
|
+
return {
|
|
1092
|
+
type: "github-app",
|
|
1093
|
+
...base,
|
|
1094
|
+
appIdEnv,
|
|
1095
|
+
privateKeyEnv,
|
|
1096
|
+
installationIdEnv
|
|
1097
|
+
};
|
|
975
1098
|
}
|
|
976
1099
|
throw new Error(`Plugin ${name} has unsupported credentials.type: "${type}"`);
|
|
977
1100
|
}
|
|
@@ -986,7 +1109,9 @@ function parseRuntimeDependencies(data, name) {
|
|
|
986
1109
|
const seen = /* @__PURE__ */ new Set();
|
|
987
1110
|
for (const entry of data) {
|
|
988
1111
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
989
|
-
throw new Error(
|
|
1112
|
+
throw new Error(
|
|
1113
|
+
`Plugin ${name} runtime-dependencies entries must be objects`
|
|
1114
|
+
);
|
|
990
1115
|
}
|
|
991
1116
|
const record = entry;
|
|
992
1117
|
const type = record.type;
|
|
@@ -995,20 +1120,28 @@ function parseRuntimeDependencies(data, name) {
|
|
|
995
1120
|
const version = record.version;
|
|
996
1121
|
const sha256 = record.sha256;
|
|
997
1122
|
if (typeof type !== "string" || type !== "npm" && type !== "system") {
|
|
998
|
-
throw new Error(
|
|
1123
|
+
throw new Error(
|
|
1124
|
+
`Plugin ${name} runtime dependency type must be "npm" or "system"`
|
|
1125
|
+
);
|
|
999
1126
|
}
|
|
1000
1127
|
const normalizedPackage = typeof packageName === "string" ? packageName.trim() : "";
|
|
1001
1128
|
const normalizedUrl = typeof packageUrl === "string" ? packageUrl.trim() : "";
|
|
1002
1129
|
if (type === "npm") {
|
|
1003
1130
|
if (!normalizedPackage) {
|
|
1004
|
-
throw new Error(
|
|
1131
|
+
throw new Error(
|
|
1132
|
+
`Plugin ${name} runtime dependency package must be a non-empty string`
|
|
1133
|
+
);
|
|
1005
1134
|
}
|
|
1006
1135
|
if (packageUrl !== void 0 || sha256 !== void 0) {
|
|
1007
|
-
throw new Error(
|
|
1136
|
+
throw new Error(
|
|
1137
|
+
`Plugin ${name} npm runtime dependencies must only include package/version fields`
|
|
1138
|
+
);
|
|
1008
1139
|
}
|
|
1009
1140
|
const normalizedVersion = typeof version === "string" ? version.trim() : "latest";
|
|
1010
1141
|
if (!normalizedVersion) {
|
|
1011
|
-
throw new Error(
|
|
1142
|
+
throw new Error(
|
|
1143
|
+
`Plugin ${name} runtime dependency version must be a non-empty string when provided`
|
|
1144
|
+
);
|
|
1012
1145
|
}
|
|
1013
1146
|
const dedupeKey2 = `${type}:${normalizedPackage}:${normalizedVersion}`;
|
|
1014
1147
|
if (seen.has(dedupeKey2)) {
|
|
@@ -1023,17 +1156,25 @@ function parseRuntimeDependencies(data, name) {
|
|
|
1023
1156
|
continue;
|
|
1024
1157
|
}
|
|
1025
1158
|
if (version !== void 0) {
|
|
1026
|
-
throw new Error(
|
|
1159
|
+
throw new Error(
|
|
1160
|
+
`Plugin ${name} system runtime dependencies must not include a version`
|
|
1161
|
+
);
|
|
1027
1162
|
}
|
|
1028
1163
|
if (normalizedPackage && normalizedUrl) {
|
|
1029
|
-
throw new Error(
|
|
1164
|
+
throw new Error(
|
|
1165
|
+
`Plugin ${name} system runtime dependencies must specify either package or url, not both`
|
|
1166
|
+
);
|
|
1030
1167
|
}
|
|
1031
1168
|
if (!normalizedPackage && !normalizedUrl) {
|
|
1032
|
-
throw new Error(
|
|
1169
|
+
throw new Error(
|
|
1170
|
+
`Plugin ${name} system runtime dependencies must specify package or url`
|
|
1171
|
+
);
|
|
1033
1172
|
}
|
|
1034
1173
|
if (normalizedPackage) {
|
|
1035
1174
|
if (sha256 !== void 0) {
|
|
1036
|
-
throw new Error(
|
|
1175
|
+
throw new Error(
|
|
1176
|
+
`Plugin ${name} system runtime dependency package entries must not include sha256`
|
|
1177
|
+
);
|
|
1037
1178
|
}
|
|
1038
1179
|
const dedupeKey2 = `${type}:package:${normalizedPackage}`;
|
|
1039
1180
|
if (seen.has(dedupeKey2)) {
|
|
@@ -1047,11 +1188,15 @@ function parseRuntimeDependencies(data, name) {
|
|
|
1047
1188
|
continue;
|
|
1048
1189
|
}
|
|
1049
1190
|
if (!/^https:\/\//i.test(normalizedUrl)) {
|
|
1050
|
-
throw new Error(
|
|
1191
|
+
throw new Error(
|
|
1192
|
+
`Plugin ${name} system runtime dependency url must be an https URL`
|
|
1193
|
+
);
|
|
1051
1194
|
}
|
|
1052
1195
|
const normalizedSha256 = typeof sha256 === "string" ? sha256.trim().toLowerCase() : "";
|
|
1053
1196
|
if (!/^[a-f0-9]{64}$/.test(normalizedSha256)) {
|
|
1054
|
-
throw new Error(
|
|
1197
|
+
throw new Error(
|
|
1198
|
+
`Plugin ${name} system runtime dependency url entries must include a valid sha256`
|
|
1199
|
+
);
|
|
1055
1200
|
}
|
|
1056
1201
|
const dedupeKey = `${type}:url:${normalizedUrl}:${normalizedSha256}`;
|
|
1057
1202
|
if (seen.has(dedupeKey)) {
|
|
@@ -1076,12 +1221,16 @@ function parseRuntimePostinstall(data, name) {
|
|
|
1076
1221
|
const parsed = [];
|
|
1077
1222
|
for (const entry of data) {
|
|
1078
1223
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
1079
|
-
throw new Error(
|
|
1224
|
+
throw new Error(
|
|
1225
|
+
`Plugin ${name} runtime-postinstall entries must be objects`
|
|
1226
|
+
);
|
|
1080
1227
|
}
|
|
1081
1228
|
const record = entry;
|
|
1082
1229
|
const cmd = typeof record.cmd === "string" ? record.cmd.trim() : "";
|
|
1083
1230
|
if (!cmd) {
|
|
1084
|
-
throw new Error(
|
|
1231
|
+
throw new Error(
|
|
1232
|
+
`Plugin ${name} runtime-postinstall cmd must be a non-empty string`
|
|
1233
|
+
);
|
|
1085
1234
|
}
|
|
1086
1235
|
if (!RUNTIME_POSTINSTALL_CMD_RE.test(cmd)) {
|
|
1087
1236
|
throw new Error(
|
|
@@ -1090,11 +1239,15 @@ function parseRuntimePostinstall(data, name) {
|
|
|
1090
1239
|
}
|
|
1091
1240
|
const argsRaw = record.args;
|
|
1092
1241
|
if (argsRaw !== void 0 && (!Array.isArray(argsRaw) || !argsRaw.every((arg) => typeof arg === "string"))) {
|
|
1093
|
-
throw new Error(
|
|
1242
|
+
throw new Error(
|
|
1243
|
+
`Plugin ${name} runtime-postinstall args must be an array of strings when provided`
|
|
1244
|
+
);
|
|
1094
1245
|
}
|
|
1095
1246
|
const sudoRaw = record.sudo;
|
|
1096
1247
|
if (sudoRaw !== void 0 && typeof sudoRaw !== "boolean") {
|
|
1097
|
-
throw new Error(
|
|
1248
|
+
throw new Error(
|
|
1249
|
+
`Plugin ${name} runtime-postinstall sudo must be a boolean when provided`
|
|
1250
|
+
);
|
|
1098
1251
|
}
|
|
1099
1252
|
const normalizedArgs = Array.isArray(argsRaw) ? argsRaw.map((arg) => arg.trim()).filter((arg) => arg.length > 0) : void 0;
|
|
1100
1253
|
parsed.push({
|
|
@@ -1106,7 +1259,10 @@ function parseRuntimePostinstall(data, name) {
|
|
|
1106
1259
|
return parsed.length > 0 ? parsed : void 0;
|
|
1107
1260
|
}
|
|
1108
1261
|
function parseManifest(raw, dir) {
|
|
1109
|
-
const data = toRecord(
|
|
1262
|
+
const data = toRecord(
|
|
1263
|
+
parseYaml(raw),
|
|
1264
|
+
`Invalid plugin manifest in ${dir}: expected an object`
|
|
1265
|
+
);
|
|
1110
1266
|
const rawName = data.name;
|
|
1111
1267
|
if (typeof rawName !== "string" || !PLUGIN_NAME_RE.test(rawName)) {
|
|
1112
1268
|
throw new Error(`Invalid plugin name in ${dir}: "${rawName}"`);
|
|
@@ -1119,7 +1275,9 @@ function parseManifest(raw, dir) {
|
|
|
1119
1275
|
const description = rawDescription;
|
|
1120
1276
|
const rawCapabilities = data.capabilities;
|
|
1121
1277
|
if (rawCapabilities !== void 0 && !Array.isArray(rawCapabilities)) {
|
|
1122
|
-
throw new Error(
|
|
1278
|
+
throw new Error(
|
|
1279
|
+
`Plugin ${name} capabilities must be an array when provided`
|
|
1280
|
+
);
|
|
1123
1281
|
}
|
|
1124
1282
|
const capabilities = [];
|
|
1125
1283
|
for (const cap of rawCapabilities ?? []) {
|
|
@@ -1130,7 +1288,9 @@ function parseManifest(raw, dir) {
|
|
|
1130
1288
|
}
|
|
1131
1289
|
const rawConfigKeys = data["config-keys"];
|
|
1132
1290
|
if (rawConfigKeys !== void 0 && !Array.isArray(rawConfigKeys)) {
|
|
1133
|
-
throw new Error(
|
|
1291
|
+
throw new Error(
|
|
1292
|
+
`Plugin ${name} config-keys must be an array when provided`
|
|
1293
|
+
);
|
|
1134
1294
|
}
|
|
1135
1295
|
const configKeys = [];
|
|
1136
1296
|
for (const key of rawConfigKeys ?? []) {
|
|
@@ -1141,11 +1301,20 @@ function parseManifest(raw, dir) {
|
|
|
1141
1301
|
}
|
|
1142
1302
|
const credentialsRaw = data.credentials;
|
|
1143
1303
|
if (credentialsRaw !== void 0) {
|
|
1144
|
-
toRecord(
|
|
1304
|
+
toRecord(
|
|
1305
|
+
credentialsRaw,
|
|
1306
|
+
`Plugin ${name} credentials must be an object when provided`
|
|
1307
|
+
);
|
|
1145
1308
|
}
|
|
1146
1309
|
const credentials = credentialsRaw ? parseCredentials(credentialsRaw, name) : void 0;
|
|
1147
|
-
const runtimeDependencies = parseRuntimeDependencies(
|
|
1148
|
-
|
|
1310
|
+
const runtimeDependencies = parseRuntimeDependencies(
|
|
1311
|
+
data["runtime-dependencies"],
|
|
1312
|
+
name
|
|
1313
|
+
);
|
|
1314
|
+
const runtimePostinstall = parseRuntimePostinstall(
|
|
1315
|
+
data["runtime-postinstall"],
|
|
1316
|
+
name
|
|
1317
|
+
);
|
|
1149
1318
|
const manifest = {
|
|
1150
1319
|
name,
|
|
1151
1320
|
description,
|
|
@@ -1161,14 +1330,54 @@ function parseManifest(raw, dir) {
|
|
|
1161
1330
|
throw new Error(`Plugin ${name} oauth requires credentials`);
|
|
1162
1331
|
}
|
|
1163
1332
|
if (credentials.type !== "oauth-bearer") {
|
|
1164
|
-
throw new Error(
|
|
1333
|
+
throw new Error(
|
|
1334
|
+
`Plugin ${name} oauth requires credentials.type "oauth-bearer"`
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
const authorizeParams = parseStringMap(
|
|
1338
|
+
oauthRaw["authorize-params"],
|
|
1339
|
+
`Plugin ${name} oauth.authorize-params`,
|
|
1340
|
+
{ reservedKeys: RESERVED_AUTHORIZE_PARAM_KEYS }
|
|
1341
|
+
);
|
|
1342
|
+
const tokenExtraHeaders = parseStringMap(
|
|
1343
|
+
oauthRaw["token-extra-headers"],
|
|
1344
|
+
`Plugin ${name} oauth.token-extra-headers`,
|
|
1345
|
+
{ forbiddenKeys: FORBIDDEN_TOKEN_HEADER_NAMES }
|
|
1346
|
+
);
|
|
1347
|
+
const tokenAuthMethodRaw = oauthRaw["token-auth-method"];
|
|
1348
|
+
let tokenAuthMethod;
|
|
1349
|
+
if (tokenAuthMethodRaw !== void 0) {
|
|
1350
|
+
const parsedTokenAuthMethod = requireStringField(
|
|
1351
|
+
oauthRaw,
|
|
1352
|
+
"token-auth-method",
|
|
1353
|
+
`Plugin ${name} oauth.token-auth-method must be a non-empty string`
|
|
1354
|
+
);
|
|
1355
|
+
if (parsedTokenAuthMethod !== "body" && parsedTokenAuthMethod !== "basic") {
|
|
1356
|
+
throw new Error(
|
|
1357
|
+
`Plugin ${name} oauth.token-auth-method must be "body" or "basic"`
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
tokenAuthMethod = parsedTokenAuthMethod;
|
|
1165
1361
|
}
|
|
1166
1362
|
manifest.oauth = {
|
|
1167
1363
|
clientIdEnv: requireEnvVarField(oauthRaw, "client-id-env", name),
|
|
1168
1364
|
clientSecretEnv: requireEnvVarField(oauthRaw, "client-secret-env", name),
|
|
1169
|
-
authorizeEndpoint: requireHttpsUrlField(
|
|
1365
|
+
authorizeEndpoint: requireHttpsUrlField(
|
|
1366
|
+
oauthRaw,
|
|
1367
|
+
"authorize-endpoint",
|
|
1368
|
+
name
|
|
1369
|
+
),
|
|
1170
1370
|
tokenEndpoint: requireHttpsUrlField(oauthRaw, "token-endpoint", name),
|
|
1171
|
-
|
|
1371
|
+
...oauthRaw.scope !== void 0 ? {
|
|
1372
|
+
scope: requireStringField(
|
|
1373
|
+
oauthRaw,
|
|
1374
|
+
"scope",
|
|
1375
|
+
`Plugin ${name} oauth.scope must be a non-empty string`
|
|
1376
|
+
)
|
|
1377
|
+
} : {},
|
|
1378
|
+
...authorizeParams ? { authorizeParams } : {},
|
|
1379
|
+
...tokenAuthMethod ? { tokenAuthMethod } : {},
|
|
1380
|
+
...tokenExtraHeaders ? { tokenExtraHeaders } : {}
|
|
1172
1381
|
};
|
|
1173
1382
|
}
|
|
1174
1383
|
const targetRaw = data.target ? toRecord(data.target, `Plugin ${name} target must be an object`) : void 0;
|
|
@@ -1178,14 +1387,20 @@ function parseManifest(raw, dir) {
|
|
|
1178
1387
|
}
|
|
1179
1388
|
const rawConfigKey = targetRaw["config-key"];
|
|
1180
1389
|
if (typeof rawConfigKey !== "string" || !rawConfigKey.trim()) {
|
|
1181
|
-
throw new Error(
|
|
1390
|
+
throw new Error(
|
|
1391
|
+
`Plugin ${name} target.config-key must be a non-empty string`
|
|
1392
|
+
);
|
|
1182
1393
|
}
|
|
1183
1394
|
if (!SHORT_CONFIG_KEY_RE.test(rawConfigKey)) {
|
|
1184
|
-
throw new Error(
|
|
1395
|
+
throw new Error(
|
|
1396
|
+
`Plugin ${name} target.config-key "${rawConfigKey}" is invalid`
|
|
1397
|
+
);
|
|
1185
1398
|
}
|
|
1186
1399
|
const qualifiedKey = `${name}.${rawConfigKey}`;
|
|
1187
1400
|
if (!configKeys.includes(qualifiedKey)) {
|
|
1188
|
-
throw new Error(
|
|
1401
|
+
throw new Error(
|
|
1402
|
+
`Plugin ${name} target.config-key "${rawConfigKey}" must be listed in config-keys`
|
|
1403
|
+
);
|
|
1189
1404
|
}
|
|
1190
1405
|
manifest.target = { type: "repo", configKey: qualifiedKey };
|
|
1191
1406
|
}
|
|
@@ -1204,13 +1419,15 @@ function registerPluginManifest(raw, pluginDir) {
|
|
|
1204
1419
|
}
|
|
1205
1420
|
for (const cap of manifest.capabilities) {
|
|
1206
1421
|
if (capabilityToPlugin.has(cap)) {
|
|
1207
|
-
throw new Error(
|
|
1422
|
+
throw new Error(
|
|
1423
|
+
`Duplicate capability "${cap}" in plugin "${manifest.name}"`
|
|
1424
|
+
);
|
|
1208
1425
|
}
|
|
1209
1426
|
}
|
|
1210
1427
|
const definition = {
|
|
1211
1428
|
manifest,
|
|
1212
1429
|
dir: pluginDir,
|
|
1213
|
-
skillsDir:
|
|
1430
|
+
skillsDir: path2.join(pluginDir, "skills")
|
|
1214
1431
|
};
|
|
1215
1432
|
pluginDefinitions.push(definition);
|
|
1216
1433
|
pluginsByName.set(manifest.name, definition);
|
|
@@ -1231,24 +1448,29 @@ function loadPlugins() {
|
|
|
1231
1448
|
let entries;
|
|
1232
1449
|
let rootStat;
|
|
1233
1450
|
try {
|
|
1234
|
-
rootStat =
|
|
1451
|
+
rootStat = statSync(pluginsRoot);
|
|
1235
1452
|
} catch (error) {
|
|
1236
|
-
logWarn(
|
|
1237
|
-
"
|
|
1238
|
-
|
|
1239
|
-
|
|
1453
|
+
logWarn(
|
|
1454
|
+
"plugin_root_read_failed",
|
|
1455
|
+
{},
|
|
1456
|
+
{
|
|
1457
|
+
"file.directory": pluginsRoot,
|
|
1458
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
1459
|
+
},
|
|
1460
|
+
"Failed to read plugin root"
|
|
1461
|
+
);
|
|
1240
1462
|
continue;
|
|
1241
1463
|
}
|
|
1242
1464
|
if (rootStat.isDirectory()) {
|
|
1243
|
-
const manifestPath =
|
|
1465
|
+
const manifestPath = path2.join(pluginsRoot, "plugin.yaml");
|
|
1244
1466
|
let hasRootManifest = false;
|
|
1245
1467
|
try {
|
|
1246
|
-
hasRootManifest =
|
|
1468
|
+
hasRootManifest = statSync(manifestPath).isFile();
|
|
1247
1469
|
} catch {
|
|
1248
1470
|
hasRootManifest = false;
|
|
1249
1471
|
}
|
|
1250
1472
|
if (hasRootManifest) {
|
|
1251
|
-
const rawRootManifest =
|
|
1473
|
+
const rawRootManifest = readFileSync(manifestPath, "utf8");
|
|
1252
1474
|
registerPluginManifest(rawRootManifest, pluginsRoot);
|
|
1253
1475
|
continue;
|
|
1254
1476
|
}
|
|
@@ -1256,24 +1478,29 @@ function loadPlugins() {
|
|
|
1256
1478
|
try {
|
|
1257
1479
|
entries = readdirSync(pluginsRoot);
|
|
1258
1480
|
} catch (error) {
|
|
1259
|
-
logWarn(
|
|
1260
|
-
"
|
|
1261
|
-
|
|
1262
|
-
|
|
1481
|
+
logWarn(
|
|
1482
|
+
"plugin_root_read_failed",
|
|
1483
|
+
{},
|
|
1484
|
+
{
|
|
1485
|
+
"file.directory": pluginsRoot,
|
|
1486
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
1487
|
+
},
|
|
1488
|
+
"Failed to read plugin root"
|
|
1489
|
+
);
|
|
1263
1490
|
continue;
|
|
1264
1491
|
}
|
|
1265
1492
|
for (const entry of entries.sort()) {
|
|
1266
|
-
const pluginDir =
|
|
1493
|
+
const pluginDir = path2.join(pluginsRoot, entry);
|
|
1267
1494
|
try {
|
|
1268
|
-
const stat =
|
|
1495
|
+
const stat = statSync(pluginDir);
|
|
1269
1496
|
if (!stat.isDirectory()) continue;
|
|
1270
1497
|
} catch {
|
|
1271
1498
|
continue;
|
|
1272
1499
|
}
|
|
1273
|
-
const manifestPath =
|
|
1500
|
+
const manifestPath = path2.join(pluginDir, "plugin.yaml");
|
|
1274
1501
|
let raw;
|
|
1275
1502
|
try {
|
|
1276
|
-
raw =
|
|
1503
|
+
raw = readFileSync(manifestPath, "utf8");
|
|
1277
1504
|
} catch {
|
|
1278
1505
|
continue;
|
|
1279
1506
|
}
|
|
@@ -1295,8 +1522,12 @@ function loadPlugins() {
|
|
|
1295
1522
|
"Loaded plugins"
|
|
1296
1523
|
);
|
|
1297
1524
|
}
|
|
1525
|
+
function ensurePluginsLoaded() {
|
|
1526
|
+
loadPlugins();
|
|
1527
|
+
}
|
|
1298
1528
|
loadPlugins();
|
|
1299
1529
|
function getPluginCapabilityProviders() {
|
|
1530
|
+
ensurePluginsLoaded();
|
|
1300
1531
|
return pluginDefinitions.map((plugin) => ({
|
|
1301
1532
|
provider: plugin.manifest.name,
|
|
1302
1533
|
capabilities: [...plugin.manifest.capabilities],
|
|
@@ -1305,9 +1536,11 @@ function getPluginCapabilityProviders() {
|
|
|
1305
1536
|
}));
|
|
1306
1537
|
}
|
|
1307
1538
|
function getPluginProviders() {
|
|
1539
|
+
ensurePluginsLoaded();
|
|
1308
1540
|
return [...pluginDefinitions];
|
|
1309
1541
|
}
|
|
1310
1542
|
function getPluginRuntimeDependencies() {
|
|
1543
|
+
ensurePluginsLoaded();
|
|
1311
1544
|
const seen = /* @__PURE__ */ new Set();
|
|
1312
1545
|
const deps = [];
|
|
1313
1546
|
for (const plugin of pluginDefinitions) {
|
|
@@ -1336,6 +1569,7 @@ function getPluginRuntimeDependencies() {
|
|
|
1336
1569
|
});
|
|
1337
1570
|
}
|
|
1338
1571
|
function getPluginRuntimePostinstall() {
|
|
1572
|
+
ensurePluginsLoaded();
|
|
1339
1573
|
const commands = [];
|
|
1340
1574
|
for (const plugin of pluginDefinitions) {
|
|
1341
1575
|
for (const command of plugin.manifest.runtimePostinstall ?? []) {
|
|
@@ -1349,6 +1583,7 @@ function getPluginRuntimePostinstall() {
|
|
|
1349
1583
|
return commands;
|
|
1350
1584
|
}
|
|
1351
1585
|
function getPluginOAuthConfig(provider) {
|
|
1586
|
+
ensurePluginsLoaded();
|
|
1352
1587
|
const plugin = pluginsByName.get(provider);
|
|
1353
1588
|
if (!plugin?.manifest.oauth) return void 0;
|
|
1354
1589
|
const oauth = plugin.manifest.oauth;
|
|
@@ -1357,17 +1592,28 @@ function getPluginOAuthConfig(provider) {
|
|
|
1357
1592
|
clientSecretEnv: oauth.clientSecretEnv,
|
|
1358
1593
|
authorizeEndpoint: oauth.authorizeEndpoint,
|
|
1359
1594
|
tokenEndpoint: oauth.tokenEndpoint,
|
|
1360
|
-
scope: oauth.scope,
|
|
1595
|
+
...oauth.scope ? { scope: oauth.scope } : {},
|
|
1596
|
+
...oauth.authorizeParams ? { authorizeParams: { ...oauth.authorizeParams } } : {},
|
|
1597
|
+
...oauth.tokenAuthMethod ? { tokenAuthMethod: oauth.tokenAuthMethod } : {},
|
|
1598
|
+
...oauth.tokenExtraHeaders ? { tokenExtraHeaders: { ...oauth.tokenExtraHeaders } } : {},
|
|
1361
1599
|
callbackPath: `/api/oauth/callback/${plugin.manifest.name}`
|
|
1362
1600
|
};
|
|
1363
1601
|
}
|
|
1364
1602
|
function getPluginSkillRoots() {
|
|
1365
|
-
|
|
1603
|
+
ensurePluginsLoaded();
|
|
1604
|
+
return [
|
|
1605
|
+
.../* @__PURE__ */ new Set([
|
|
1606
|
+
...pluginDefinitions.map((plugin) => plugin.skillsDir),
|
|
1607
|
+
...packageSkillRoots
|
|
1608
|
+
])
|
|
1609
|
+
];
|
|
1366
1610
|
}
|
|
1367
1611
|
function isPluginProvider(provider) {
|
|
1612
|
+
ensurePluginsLoaded();
|
|
1368
1613
|
return pluginsByName.has(provider);
|
|
1369
1614
|
}
|
|
1370
1615
|
function createPluginBroker(provider, deps) {
|
|
1616
|
+
ensurePluginsLoaded();
|
|
1371
1617
|
const plugin = pluginsByName.get(provider);
|
|
1372
1618
|
if (!plugin) {
|
|
1373
1619
|
throw new Error(`Unknown plugin provider: "${provider}"`);
|
|
@@ -1467,6 +1713,9 @@ function buildDependencyProfile(runtime) {
|
|
|
1467
1713
|
postinstall
|
|
1468
1714
|
};
|
|
1469
1715
|
}
|
|
1716
|
+
function getRuntimeDependencyProfileHash(runtime) {
|
|
1717
|
+
return buildDependencyProfile(runtime)?.profileHash;
|
|
1718
|
+
}
|
|
1470
1719
|
function shouldRebuildCachedSnapshot(profile, cached) {
|
|
1471
1720
|
if (!profile.hasFloatingVersions) {
|
|
1472
1721
|
return false;
|
|
@@ -1497,7 +1746,11 @@ async function getCachedSnapshot(profileHash) {
|
|
|
1497
1746
|
async function setCachedSnapshot(entry) {
|
|
1498
1747
|
const state = getStateAdapter();
|
|
1499
1748
|
await state.connect();
|
|
1500
|
-
await state.set(
|
|
1749
|
+
await state.set(
|
|
1750
|
+
profileCacheKey(entry.profileHash),
|
|
1751
|
+
JSON.stringify(entry),
|
|
1752
|
+
SNAPSHOT_CACHE_TTL_MS
|
|
1753
|
+
);
|
|
1501
1754
|
}
|
|
1502
1755
|
async function withSnapshotSpan(name, op, attributes, callback) {
|
|
1503
1756
|
return await withSpan(name, op, {}, callback, attributes);
|
|
@@ -1532,7 +1785,11 @@ async function installGhCliViaDnf(sandbox) {
|
|
|
1532
1785
|
}
|
|
1533
1786
|
const dnf5Repo = await tryRun(sandbox, {
|
|
1534
1787
|
cmd: "dnf",
|
|
1535
|
-
args: [
|
|
1788
|
+
args: [
|
|
1789
|
+
"config-manager",
|
|
1790
|
+
"addrepo",
|
|
1791
|
+
"--from-repofile=https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
1792
|
+
],
|
|
1536
1793
|
sudo: true
|
|
1537
1794
|
});
|
|
1538
1795
|
if (!dnf5Repo.ok) {
|
|
@@ -1549,7 +1806,11 @@ async function installGhCliViaDnf(sandbox) {
|
|
|
1549
1806
|
sandbox,
|
|
1550
1807
|
{
|
|
1551
1808
|
cmd: "dnf",
|
|
1552
|
-
args: [
|
|
1809
|
+
args: [
|
|
1810
|
+
"config-manager",
|
|
1811
|
+
"--add-repo",
|
|
1812
|
+
"https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
1813
|
+
],
|
|
1553
1814
|
sudo: true
|
|
1554
1815
|
},
|
|
1555
1816
|
"dnf config-manager --add-repo gh-cli.repo"
|
|
@@ -1580,8 +1841,12 @@ function runtimeDependencyFilePath(url, sha256) {
|
|
|
1580
1841
|
return `/tmp/junior-runtime-${sha256.slice(0, 12)}-${sanitizedBasename}`;
|
|
1581
1842
|
}
|
|
1582
1843
|
async function installRuntimeDependencies(sandbox, deps) {
|
|
1583
|
-
const systemDeps = deps.filter(
|
|
1584
|
-
|
|
1844
|
+
const systemDeps = deps.filter(
|
|
1845
|
+
(dep) => dep.type === "system"
|
|
1846
|
+
);
|
|
1847
|
+
const npmPackages = deps.filter(
|
|
1848
|
+
(dep) => dep.type === "npm"
|
|
1849
|
+
).map((dep) => `${dep.package}@${dep.version}`);
|
|
1585
1850
|
if (systemDeps.length > 0) {
|
|
1586
1851
|
await withSnapshotSpan(
|
|
1587
1852
|
"sandbox.snapshot.install_system",
|
|
@@ -1608,7 +1873,9 @@ async function installRuntimeDependencies(sandbox, deps) {
|
|
|
1608
1873
|
const checksumStdout = (await checksumResult.stdout()).trim();
|
|
1609
1874
|
const checksumStderr = (await checksumResult.stderr()).trim();
|
|
1610
1875
|
if (checksumResult.exitCode !== 0) {
|
|
1611
|
-
throw new Error(
|
|
1876
|
+
throw new Error(
|
|
1877
|
+
`sha256sum failed: ${checksumStderr || checksumStdout || "command failed"}`
|
|
1878
|
+
);
|
|
1612
1879
|
}
|
|
1613
1880
|
const actualChecksum = checksumStdout.split(/\s+/)[0]?.toLowerCase();
|
|
1614
1881
|
if (!actualChecksum) {
|
|
@@ -1685,7 +1952,10 @@ async function runRuntimePostinstall(sandbox, commands) {
|
|
|
1685
1952
|
},
|
|
1686
1953
|
async () => {
|
|
1687
1954
|
for (const command of commands) {
|
|
1688
|
-
const invocation = [
|
|
1955
|
+
const invocation = [
|
|
1956
|
+
JSON.stringify(command.cmd),
|
|
1957
|
+
...(command.args ?? []).map((arg) => JSON.stringify(arg))
|
|
1958
|
+
].join(" ");
|
|
1689
1959
|
const pathPrefix = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin:$PATH`;
|
|
1690
1960
|
await runOrThrow(
|
|
1691
1961
|
sandbox,
|
|
@@ -1840,7 +2110,9 @@ async function resolveRuntimeDependencySnapshot(params) {
|
|
|
1840
2110
|
};
|
|
1841
2111
|
}
|
|
1842
2112
|
const cached = await getCachedSnapshot(profile.profileHash);
|
|
1843
|
-
const cachedNeedsRebuild = Boolean(
|
|
2113
|
+
const cachedNeedsRebuild = Boolean(
|
|
2114
|
+
cached?.snapshotId && shouldRebuildCachedSnapshot(profile, cached)
|
|
2115
|
+
);
|
|
1844
2116
|
if (!params.forceRebuild && cached?.snapshotId && !cachedNeedsRebuild) {
|
|
1845
2117
|
await params.onProgress?.("cache_hit");
|
|
1846
2118
|
return {
|
|
@@ -1866,34 +2138,50 @@ async function resolveRuntimeDependencySnapshot(params) {
|
|
|
1866
2138
|
}
|
|
1867
2139
|
return !shouldRebuildCachedSnapshot(profile, candidate);
|
|
1868
2140
|
};
|
|
1869
|
-
const lockResult = await withBuildLock(
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
await
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2141
|
+
const lockResult = await withBuildLock(
|
|
2142
|
+
profile.profileHash,
|
|
2143
|
+
async () => {
|
|
2144
|
+
const latest = await getCachedSnapshot(profile.profileHash);
|
|
2145
|
+
if (latest?.snapshotId && canUseCachedSnapshot(latest)) {
|
|
2146
|
+
await params.onProgress?.("cache_hit");
|
|
2147
|
+
return {
|
|
2148
|
+
snapshotId: latest.snapshotId,
|
|
2149
|
+
source: "callback_cache"
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
await params.onProgress?.("building_snapshot");
|
|
2153
|
+
const nextSnapshotId = await createDependencySnapshot(
|
|
2154
|
+
profile,
|
|
2155
|
+
params.runtime,
|
|
2156
|
+
params.timeoutMs
|
|
2157
|
+
);
|
|
2158
|
+
await setCachedSnapshot({
|
|
2159
|
+
profileHash: profile.profileHash,
|
|
2160
|
+
snapshotId: nextSnapshotId,
|
|
2161
|
+
runtime: params.runtime,
|
|
2162
|
+
createdAtMs: Date.now(),
|
|
2163
|
+
dependencyCount: profile.dependencyCount
|
|
2164
|
+
});
|
|
2165
|
+
await params.onProgress?.("build_complete");
|
|
2166
|
+
return { snapshotId: nextSnapshotId, source: "built" };
|
|
2167
|
+
},
|
|
2168
|
+
canUseCachedSnapshot,
|
|
2169
|
+
{
|
|
2170
|
+
onWaitingForLock: async () => {
|
|
2171
|
+
await params.onProgress?.("waiting_for_lock");
|
|
2172
|
+
}
|
|
1889
2173
|
}
|
|
1890
|
-
|
|
2174
|
+
);
|
|
1891
2175
|
return {
|
|
1892
2176
|
snapshotId: lockResult.snapshotId,
|
|
1893
2177
|
profileHash: profile.profileHash,
|
|
1894
2178
|
dependencyCount: profile.dependencyCount,
|
|
1895
2179
|
cacheHit: lockResult.source !== "built",
|
|
1896
|
-
resolveOutcome: toResolveOutcome(
|
|
2180
|
+
resolveOutcome: toResolveOutcome(
|
|
2181
|
+
Boolean(params.forceRebuild),
|
|
2182
|
+
lockResult.source,
|
|
2183
|
+
lockResult.waitedForLock
|
|
2184
|
+
),
|
|
1897
2185
|
...rebuildReason ? { rebuildReason } : {}
|
|
1898
2186
|
};
|
|
1899
2187
|
}
|
|
@@ -1910,6 +2198,8 @@ export {
|
|
|
1910
2198
|
soulPathCandidates,
|
|
1911
2199
|
resolveAuthTokenPlaceholder,
|
|
1912
2200
|
CredentialUnavailableError,
|
|
2201
|
+
buildOAuthTokenRequest,
|
|
2202
|
+
parseOAuthTokenResponse,
|
|
1913
2203
|
getPluginCapabilityProviders,
|
|
1914
2204
|
getPluginProviders,
|
|
1915
2205
|
getPluginOAuthConfig,
|
|
@@ -1935,6 +2225,7 @@ export {
|
|
|
1935
2225
|
SANDBOX_WORKSPACE_ROOT,
|
|
1936
2226
|
SANDBOX_SKILLS_ROOT,
|
|
1937
2227
|
sandboxSkillDir,
|
|
2228
|
+
getRuntimeDependencyProfileHash,
|
|
1938
2229
|
resolveRuntimeDependencySnapshot,
|
|
1939
2230
|
isSnapshotMissingError
|
|
1940
2231
|
};
|