@agentxm/extension-sources 0.28.4-bootstrap.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/LICENSE +110 -0
- package/README.md +9 -0
- package/dist/src/axm-skill-gate.d.ts +64 -0
- package/dist/src/axm-skill-gate.js +25 -0
- package/dist/src/errors.d.ts +116 -0
- package/dist/src/errors.js +98 -0
- package/dist/src/failure-category.d.ts +10 -0
- package/dist/src/failure-category.js +2 -0
- package/dist/src/file-url.d.ts +9 -0
- package/dist/src/file-url.js +12 -0
- package/dist/src/git/detect.d.ts +7 -0
- package/dist/src/git/detect.js +23 -0
- package/dist/src/git/operations.d.ts +37 -0
- package/dist/src/git/operations.js +101 -0
- package/dist/src/glob.d.ts +39 -0
- package/dist/src/glob.js +79 -0
- package/dist/src/hook-package-discovery.d.ts +19 -0
- package/dist/src/hook-package-discovery.js +14 -0
- package/dist/src/index.d.ts +30 -0
- package/dist/src/index.js +35 -0
- package/dist/src/integrity.d.ts +13 -0
- package/dist/src/integrity.js +17 -0
- package/dist/src/live.d.ts +29 -0
- package/dist/src/live.js +131 -0
- package/dist/src/manifest-package-discovery.d.ts +28 -0
- package/dist/src/manifest-package-discovery.js +96 -0
- package/dist/src/package-discovery.d.ts +19 -0
- package/dist/src/package-discovery.js +137 -0
- package/dist/src/package-sources.d.ts +25 -0
- package/dist/src/package-sources.js +102 -0
- package/dist/src/providers/azurerepos/index.d.ts +11 -0
- package/dist/src/providers/azurerepos/index.js +11 -0
- package/dist/src/providers/azurerepos/repo-exists.d.ts +5 -0
- package/dist/src/providers/azurerepos/repo-exists.js +21 -0
- package/dist/src/providers/azurerepos/scp.d.ts +12 -0
- package/dist/src/providers/azurerepos/scp.js +29 -0
- package/dist/src/providers/azurerepos/shorthand.d.ts +12 -0
- package/dist/src/providers/azurerepos/shorthand.js +45 -0
- package/dist/src/providers/azurerepos/url.d.ts +14 -0
- package/dist/src/providers/azurerepos/url.js +38 -0
- package/dist/src/providers/bitbucket/index.d.ts +11 -0
- package/dist/src/providers/bitbucket/index.js +11 -0
- package/dist/src/providers/bitbucket/resolve-repo.d.ts +11 -0
- package/dist/src/providers/bitbucket/resolve-repo.js +33 -0
- package/dist/src/providers/bitbucket/scp.d.ts +11 -0
- package/dist/src/providers/bitbucket/scp.js +28 -0
- package/dist/src/providers/bitbucket/shorthand.d.ts +10 -0
- package/dist/src/providers/bitbucket/shorthand.js +11 -0
- package/dist/src/providers/bitbucket/url.d.ts +13 -0
- package/dist/src/providers/bitbucket/url.js +42 -0
- package/dist/src/providers/convention-discovery.d.ts +11 -0
- package/dist/src/providers/convention-discovery.js +473 -0
- package/dist/src/providers/git-hosting.d.ts +31 -0
- package/dist/src/providers/git-hosting.js +73 -0
- package/dist/src/providers/git.d.ts +22 -0
- package/dist/src/providers/git.js +46 -0
- package/dist/src/providers/github/index.d.ts +11 -0
- package/dist/src/providers/github/index.js +11 -0
- package/dist/src/providers/github/resolve-repo.d.ts +11 -0
- package/dist/src/providers/github/resolve-repo.js +34 -0
- package/dist/src/providers/github/scp.d.ts +11 -0
- package/dist/src/providers/github/scp.js +28 -0
- package/dist/src/providers/github/shorthand.d.ts +10 -0
- package/dist/src/providers/github/shorthand.js +11 -0
- package/dist/src/providers/github/url.d.ts +13 -0
- package/dist/src/providers/github/url.js +42 -0
- package/dist/src/providers/gitlab/index.d.ts +11 -0
- package/dist/src/providers/gitlab/index.js +11 -0
- package/dist/src/providers/gitlab/resolve-repo.d.ts +11 -0
- package/dist/src/providers/gitlab/resolve-repo.js +33 -0
- package/dist/src/providers/gitlab/scp.d.ts +11 -0
- package/dist/src/providers/gitlab/scp.js +28 -0
- package/dist/src/providers/gitlab/shorthand.d.ts +10 -0
- package/dist/src/providers/gitlab/shorthand.js +11 -0
- package/dist/src/providers/gitlab/url.d.ts +13 -0
- package/dist/src/providers/gitlab/url.js +42 -0
- package/dist/src/providers/local-parser/index.d.ts +8 -0
- package/dist/src/providers/local-parser/index.js +8 -0
- package/dist/src/providers/local-parser/parser.d.ts +13 -0
- package/dist/src/providers/local-parser/parser.js +14 -0
- package/dist/src/providers/local.d.ts +23 -0
- package/dist/src/providers/local.js +39 -0
- package/dist/src/providers/parse-provider-shorthand.d.ts +18 -0
- package/dist/src/providers/parse-provider-shorthand.js +74 -0
- package/dist/src/providers/registry/host-provider.d.ts +47 -0
- package/dist/src/providers/registry/host-provider.js +584 -0
- package/dist/src/resolve-identifier.d.ts +73 -0
- package/dist/src/resolve-identifier.js +273 -0
- package/dist/src/resolve-source-pattern.d.ts +29 -0
- package/dist/src/resolve-source-pattern.js +77 -0
- package/dist/src/resolve-source.d.ts +79 -0
- package/dist/src/resolve-source.js +503 -0
- package/dist/src/rule-package-discovery.d.ts +19 -0
- package/dist/src/rule-package-discovery.js +14 -0
- package/dist/src/service.d.ts +72 -0
- package/dist/src/service.js +109 -0
- package/dist/src/url-fragment.d.ts +5 -0
- package/dist/src/url-fragment.js +19 -0
- package/dist/src/workspace-catalog.d.ts +79 -0
- package/dist/src/workspace-catalog.js +26 -0
- package/package.json +57 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry source host provider implementations.
|
|
3
|
+
*
|
|
4
|
+
* Thin adapters between SourceHostProvider contract and RegistryClient.
|
|
5
|
+
* Type mapping at the boundary keeps registry and source domains separated.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import * as FileSystem from "effect/FileSystem";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import type * as Scope from "effect/Scope";
|
|
14
|
+
import { type RegistryClient } from "@agentxm/registry-client";
|
|
15
|
+
import { AxmSkillCandidateGate } from "../../axm-skill-gate.js";
|
|
16
|
+
import { type SourceResolutionFailure } from "../../errors.js";
|
|
17
|
+
import type { NamedRegistryFindOptions, NamedRegistryResolution, SourceHostProvider } from "@agentxm/extension-model/unstable/sources/source-host-provider";
|
|
18
|
+
import type { RegistrySource, RegistrySourceHost } from "@agentxm/extension-model/unstable/sources/types";
|
|
19
|
+
type RegistrySourceHostProvider<R = never> = SourceHostProvider<RegistrySource, R, SourceResolutionFailure> & {
|
|
20
|
+
readonly resolveNamed: (source: RegistrySource, options: NamedRegistryFindOptions) => Effect.Effect<NamedRegistryResolution, SourceResolutionFailure, R>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Creates a local registry source host provider backed by a RegistryClient.
|
|
24
|
+
*
|
|
25
|
+
* @experimental This API is unstable and may change without notice.
|
|
26
|
+
*/
|
|
27
|
+
export declare const createLocalRegistrySourceHostProvider: (client: RegistryClient) => RegistrySourceHostProvider<FileSystem.FileSystem | Path.Path | AxmSkillCandidateGate | Scope.Scope>;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a remote registry source host provider backed by a RegistryClient.
|
|
30
|
+
*
|
|
31
|
+
* All operations delegate to the underlying RemoteRegistryClient, which
|
|
32
|
+
* returns errors for all operations (remote not yet supported).
|
|
33
|
+
*
|
|
34
|
+
* @experimental This API is unstable and may change without notice.
|
|
35
|
+
*/
|
|
36
|
+
export declare const createRemoteRegistrySourceHostProvider: (client: RegistryClient) => RegistrySourceHostProvider<FileSystem.FileSystem | Path.Path | AxmSkillCandidateGate | Scope.Scope>;
|
|
37
|
+
/**
|
|
38
|
+
* Create a registry source host provider for a given RegistrySourceHost.
|
|
39
|
+
*
|
|
40
|
+
* Creates the appropriate RegistryClient internally based on the host's
|
|
41
|
+
* location protocol, then wraps it in the matching host provider.
|
|
42
|
+
*
|
|
43
|
+
* @experimental This API is unstable and may change without notice.
|
|
44
|
+
*/
|
|
45
|
+
export declare const createRegistrySourceHostProviderFromHost: (host: RegistrySourceHost) => Effect.Effect<RegistrySourceHostProvider<FileSystem.FileSystem | Path.Path | AxmSkillCandidateGate | Scope.Scope>, never, FileSystem.FileSystem | Path.Path | import("effect/unstable/http/HttpClient").HttpClient>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=host-provider.d.ts.map
|
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry source host provider implementations.
|
|
3
|
+
*
|
|
4
|
+
* Thin adapters between SourceHostProvider contract and RegistryClient.
|
|
5
|
+
* Type mapping at the boundary keeps registry and source domains separated.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import * as FileSystem from "effect/FileSystem";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import * as Array from "effect/Array";
|
|
13
|
+
import * as DateTime from "effect/DateTime";
|
|
14
|
+
import * as Duration from "effect/Duration";
|
|
15
|
+
import * as Effect from "effect/Effect";
|
|
16
|
+
import * as Option from "effect/Option";
|
|
17
|
+
import * as semver from "semver";
|
|
18
|
+
import { decodeHandleSync } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
19
|
+
import { createRegistryClient, extractZip, resolveVersionEntryWithReleaseAge, resolveVersionEntryForReleaseAge, extensionLifecycleWarnings, } from "@agentxm/registry-client";
|
|
20
|
+
import { packagesToPackageUrlParts } from "@agentxm/registry-protocol/unstable/registry";
|
|
21
|
+
import { isVersionEntryEligibleAt, releaseAgeEvidence, releaseAgeExemptionForIdentity, } from "@agentxm/registry-protocol/unstable/registry/release-age-policy";
|
|
22
|
+
import { AxmSkillCandidateGate } from "../../axm-skill-gate.js";
|
|
23
|
+
import { SourceNetworkFailure, SourceNotResolvable, sourceResolutionFailureCategory, } from "../../errors.js";
|
|
24
|
+
import { computeIntegrity } from "../../integrity.js";
|
|
25
|
+
import { decodeExtensionNameSync, toExtensionTypePlural, toAuthor, } from "@agentxm/extension-model/unstable/extensions";
|
|
26
|
+
import { installableExtensionTypes, isInstallableExtensionType, } from "@agentxm/extension-model/unstable/extensions/installable-types";
|
|
27
|
+
// -----------------------------------------------------------------------------
|
|
28
|
+
// Type Mapping Helpers
|
|
29
|
+
// -----------------------------------------------------------------------------
|
|
30
|
+
/** Map FindOptions + owner to GetExtensionsByOwnerArgs (no pagination — fetch all). */
|
|
31
|
+
const toSearchOptions = (owner, options) => ({
|
|
32
|
+
owner,
|
|
33
|
+
names: options.names,
|
|
34
|
+
types: options.type === "*" ? [] : [options.type],
|
|
35
|
+
limit: Option.none(),
|
|
36
|
+
offset: 0,
|
|
37
|
+
});
|
|
38
|
+
const toRegistrySearchOptions = (owner, options) => ({
|
|
39
|
+
owner,
|
|
40
|
+
names: options.names,
|
|
41
|
+
types: options.type === "*" ? [] : [options.type],
|
|
42
|
+
limit: Option.none(),
|
|
43
|
+
offset: 0,
|
|
44
|
+
});
|
|
45
|
+
const authorToMetadata = (author) => ({
|
|
46
|
+
name: author.name,
|
|
47
|
+
...(Option.isSome(author.email) && { email: author.email.value }),
|
|
48
|
+
...(Option.isSome(author.url) && { url: author.url.value }),
|
|
49
|
+
});
|
|
50
|
+
const getSupportedExtensionRefs = (entries, source) => Array.getSomes(entries.map((entry) => toExtensionRef(entry, source)));
|
|
51
|
+
const needsIndexBackedResolution = (options) => Option.isSome(options.versionRange) || Option.isSome(options.minimumReleaseAge ?? Option.none());
|
|
52
|
+
const manifestFromIndex = (index, versionRange, minimumReleaseAge) => Effect.gen(function* () {
|
|
53
|
+
const selectedVersion = yield* resolveVersionEntryWithReleaseAge(index.versions, versionRange, minimumReleaseAge ?? Option.none());
|
|
54
|
+
if (Option.isNone(selectedVersion))
|
|
55
|
+
return Option.none();
|
|
56
|
+
const version = selectedVersion.value;
|
|
57
|
+
const lifecycleWarnings = extensionLifecycleWarnings(index, version);
|
|
58
|
+
return Option.some({
|
|
59
|
+
owner: index.owner,
|
|
60
|
+
type: index.type,
|
|
61
|
+
name: index.name,
|
|
62
|
+
publisherBindingId: index.publisherBindingId,
|
|
63
|
+
description: Option.fromUndefinedOr(index.description),
|
|
64
|
+
repository: Option.fromUndefinedOr(index.repository),
|
|
65
|
+
bugs: Option.fromUndefinedOr(index.bugs),
|
|
66
|
+
license: Option.fromUndefinedOr(index.license),
|
|
67
|
+
authors: Option.match(Option.fromUndefinedOr(index.authors), {
|
|
68
|
+
onNone: () => [],
|
|
69
|
+
onSome: (authors) => authors.map((author) => toAuthor(author)),
|
|
70
|
+
}),
|
|
71
|
+
dependencies: version.dependencies ?? {},
|
|
72
|
+
version: version.version,
|
|
73
|
+
integrity: version.integrity,
|
|
74
|
+
packages: packagesToPackageUrlParts(version.packages),
|
|
75
|
+
...(index.deprecation === null ? {} : { deprecation: index.deprecation }),
|
|
76
|
+
...(lifecycleWarnings.length === 0 ? {} : { lifecycleWarnings }),
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
const namedTarget = (options) => `${options.owner}/${toExtensionTypePlural(options.type)}/${options.name}`;
|
|
80
|
+
const isOfficialAxmSkill = (options) => options.type === "skill" && options.owner === "@agentxm" && options.name === "axm";
|
|
81
|
+
const manifestForVersion = (index, version) => {
|
|
82
|
+
const lifecycleWarnings = extensionLifecycleWarnings(index, version);
|
|
83
|
+
return {
|
|
84
|
+
owner: index.owner,
|
|
85
|
+
type: index.type,
|
|
86
|
+
name: index.name,
|
|
87
|
+
publisherBindingId: index.publisherBindingId,
|
|
88
|
+
description: Option.fromUndefinedOr(index.description),
|
|
89
|
+
repository: Option.fromUndefinedOr(index.repository),
|
|
90
|
+
bugs: Option.fromUndefinedOr(index.bugs),
|
|
91
|
+
license: Option.fromUndefinedOr(index.license),
|
|
92
|
+
authors: Option.match(Option.fromUndefinedOr(index.authors), {
|
|
93
|
+
onNone: () => [],
|
|
94
|
+
onSome: (authors) => authors.map((author) => toAuthor(author)),
|
|
95
|
+
}),
|
|
96
|
+
dependencies: version.dependencies ?? {},
|
|
97
|
+
version: version.version,
|
|
98
|
+
integrity: version.integrity,
|
|
99
|
+
packages: packagesToPackageUrlParts(version.packages),
|
|
100
|
+
...(index.deprecation === null ? {} : { deprecation: index.deprecation }),
|
|
101
|
+
...(lifecycleWarnings.length === 0 ? {} : { lifecycleWarnings }),
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
const versionsMatchingNamedOptions = (versions, options, exempt, acceptedVersion) => {
|
|
105
|
+
const requested = Option.getOrElse(options.versionRange, () => "*");
|
|
106
|
+
const exact = semver.valid(requested) === requested;
|
|
107
|
+
return versions
|
|
108
|
+
.filter((entry) => exact
|
|
109
|
+
? entry.version === requested
|
|
110
|
+
: entry.yankedAt === undefined && semver.satisfies(entry.version, requested))
|
|
111
|
+
.filter((entry) => exempt ||
|
|
112
|
+
entry.version === acceptedVersion ||
|
|
113
|
+
isVersionEntryEligibleAt(entry, options.releaseAgeEvaluation))
|
|
114
|
+
.sort((left, right) => semver.compareBuild(right.version, left.version));
|
|
115
|
+
};
|
|
116
|
+
const probeAxmSkillCompatibility = (client, source, index, version) => Effect.gen(function* () {
|
|
117
|
+
const manifest = manifestForVersion(index, version);
|
|
118
|
+
const ref = toExtensionRef(manifest, source);
|
|
119
|
+
if (Option.isNone(ref) || ref.value.type !== "skill") {
|
|
120
|
+
return yield* new SourceNotResolvable({
|
|
121
|
+
category: "internal",
|
|
122
|
+
detail: "Registry returned an invalid official AXM skill candidate",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const { archive } = yield* client.getExtensionPackage({
|
|
126
|
+
owner: index.owner,
|
|
127
|
+
type: index.type,
|
|
128
|
+
name: index.name,
|
|
129
|
+
version: Option.some(version.version),
|
|
130
|
+
usagePurpose: "verification",
|
|
131
|
+
});
|
|
132
|
+
const actualIntegrity = yield* computeIntegrity(archive);
|
|
133
|
+
if (actualIntegrity !== version.integrity) {
|
|
134
|
+
return yield* new SourceNetworkFailure({
|
|
135
|
+
detail: `Integrity mismatch for skill:${index.name}@${version.version}`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const fs = yield* FileSystem.FileSystem;
|
|
139
|
+
const path = yield* Path.Path;
|
|
140
|
+
const gate = yield* AxmSkillCandidateGate;
|
|
141
|
+
const tmpDir = yield* Effect.acquireRelease(fs.makeTempDirectory({ prefix: "axm-registry-compatibility-" }).pipe(Effect.mapError((cause) => new SourceNetworkFailure({
|
|
142
|
+
detail: "Temporary compatibility-probe directory could not be created",
|
|
143
|
+
cause,
|
|
144
|
+
}))), (directory) => fs.remove(directory, { recursive: true }).pipe(Effect.ignore));
|
|
145
|
+
yield* extractZip(archive, tmpDir);
|
|
146
|
+
const result = yield* gate.evaluate({
|
|
147
|
+
ref: ref.value,
|
|
148
|
+
packageRoot: tmpDir,
|
|
149
|
+
skillSourcePath: path.join(tmpDir, "src"),
|
|
150
|
+
});
|
|
151
|
+
if (result === null) {
|
|
152
|
+
return yield* new SourceNotResolvable({
|
|
153
|
+
category: "internal",
|
|
154
|
+
detail: "Official AXM skill compatibility probe returned no result",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return { result, ref: ref.value };
|
|
158
|
+
});
|
|
159
|
+
const resolveNamedFromClient = (client, source, options) => Effect.gen(function* () {
|
|
160
|
+
const indexOption = yield* client.getExtensionIndex({
|
|
161
|
+
owner: options.owner,
|
|
162
|
+
type: options.type,
|
|
163
|
+
name: decodeExtensionNameSync(options.name),
|
|
164
|
+
});
|
|
165
|
+
const target = namedTarget(options);
|
|
166
|
+
if (Option.isNone(indexOption)) {
|
|
167
|
+
return { kind: "not_found", target };
|
|
168
|
+
}
|
|
169
|
+
const exemption = releaseAgeExemptionForIdentity(options.releaseAgeEvaluation, {
|
|
170
|
+
owner: indexOption.value.owner,
|
|
171
|
+
type: indexOption.value.type,
|
|
172
|
+
name: indexOption.value.name,
|
|
173
|
+
});
|
|
174
|
+
const acceptedVersion = options.accepted?.publisherBindingId === indexOption.value.publisherBindingId
|
|
175
|
+
? options.accepted.version
|
|
176
|
+
: undefined;
|
|
177
|
+
const resolution = resolveVersionEntryForReleaseAge(indexOption.value.versions, options.versionRange, options.releaseAgeEvaluation, exemption, acceptedVersion);
|
|
178
|
+
if (resolution.kind === "version_unsatisfied") {
|
|
179
|
+
const requested = Option.getOrUndefined(options.versionRange);
|
|
180
|
+
if (requested !== undefined && semver.valid(requested) === requested) {
|
|
181
|
+
return { kind: "not_found", target };
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
kind: "version_unsatisfied",
|
|
185
|
+
target,
|
|
186
|
+
requestedRange: Option.getOrElse(options.versionRange, () => "*"),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (resolution.kind === "policy_held") {
|
|
190
|
+
return {
|
|
191
|
+
kind: "policy_held",
|
|
192
|
+
target,
|
|
193
|
+
...(Option.isSome(options.versionRange)
|
|
194
|
+
? { requestedRange: options.versionRange.value }
|
|
195
|
+
: {}),
|
|
196
|
+
candidate: resolution.candidate,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (isOfficialAxmSkill(options)) {
|
|
200
|
+
const requested = Option.getOrElse(options.versionRange, () => "*");
|
|
201
|
+
const exactRequest = semver.valid(requested) === requested;
|
|
202
|
+
const probeCandidate = (candidate) => probeAxmSkillCompatibility(client, source, indexOption.value, candidate).pipe(Effect.map(Option.some), Effect.catchIf((error) => !exactRequest && sourceResolutionFailureCategory(error) === "not_found", () => Effect.succeed(Option.none())));
|
|
203
|
+
const candidates = versionsMatchingNamedOptions(indexOption.value.versions, options, exemption !== undefined, acceptedVersion);
|
|
204
|
+
let latestIncompatibility = null;
|
|
205
|
+
let latestRecoveryAction = null;
|
|
206
|
+
let latestRecoveryTarget = null;
|
|
207
|
+
for (const candidate of candidates) {
|
|
208
|
+
const probe = yield* probeCandidate(candidate);
|
|
209
|
+
if (Option.isNone(probe))
|
|
210
|
+
continue;
|
|
211
|
+
const probed = probe.value;
|
|
212
|
+
if (probed.result.status === "incompatible") {
|
|
213
|
+
latestIncompatibility = probed.result.detail;
|
|
214
|
+
latestRecoveryAction = probed.result.recoveryCommand;
|
|
215
|
+
latestRecoveryTarget = probed.result.recoveryTarget;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (exemption !== undefined &&
|
|
219
|
+
!isVersionEntryEligibleAt(candidate, options.releaseAgeEvaluation)) {
|
|
220
|
+
return {
|
|
221
|
+
kind: "exempted",
|
|
222
|
+
target,
|
|
223
|
+
ref: probed.ref,
|
|
224
|
+
bypassed: releaseAgeEvidence(candidate, options.releaseAgeEvaluation),
|
|
225
|
+
exemption,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
kind: "selected",
|
|
230
|
+
target,
|
|
231
|
+
ref: probed.ref,
|
|
232
|
+
...(resolution.kind === "selected" && resolution.newerHeld !== undefined
|
|
233
|
+
? { newerHeld: resolution.newerHeld }
|
|
234
|
+
: {}),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (exemption === undefined) {
|
|
238
|
+
const heldCandidates = versionsMatchingNamedOptions(indexOption.value.versions, options, true).filter((candidate) => !isVersionEntryEligibleAt(candidate, options.releaseAgeEvaluation));
|
|
239
|
+
for (const candidate of heldCandidates) {
|
|
240
|
+
const probe = yield* probeCandidate(candidate);
|
|
241
|
+
if (Option.isNone(probe) || probe.value.result.status === "incompatible")
|
|
242
|
+
continue;
|
|
243
|
+
return {
|
|
244
|
+
kind: "policy_held",
|
|
245
|
+
target,
|
|
246
|
+
...(Option.isSome(options.versionRange)
|
|
247
|
+
? { requestedRange: options.versionRange.value }
|
|
248
|
+
: {}),
|
|
249
|
+
candidate: releaseAgeEvidence(candidate, options.releaseAgeEvaluation),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (exactRequest || latestIncompatibility !== null) {
|
|
254
|
+
return yield* new SourceNotResolvable({
|
|
255
|
+
category: "conflict",
|
|
256
|
+
detail: latestIncompatibility ??
|
|
257
|
+
`The official AXM skill release ${requested} is incompatible with this AXM CLI.`,
|
|
258
|
+
recover: latestRecoveryTarget === null
|
|
259
|
+
? "Follow the compatibility recovery plan for the running CLI and official skill"
|
|
260
|
+
: `Converge to ${latestRecoveryTarget}`,
|
|
261
|
+
...(latestRecoveryAction === null ? {} : { cmd: latestRecoveryAction }),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return { kind: "not_found", target };
|
|
265
|
+
}
|
|
266
|
+
const version = resolution.version;
|
|
267
|
+
const manifest = manifestForVersion(indexOption.value, version);
|
|
268
|
+
const ref = toExtensionRef(manifest, source);
|
|
269
|
+
if (Option.isNone(ref)) {
|
|
270
|
+
return yield* new SourceNotResolvable({
|
|
271
|
+
category: "internal",
|
|
272
|
+
detail: `Registry returned unsupported extension type for ${target}`,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return resolution.kind === "exempted"
|
|
276
|
+
? {
|
|
277
|
+
kind: "exempted",
|
|
278
|
+
target,
|
|
279
|
+
ref: ref.value,
|
|
280
|
+
bypassed: resolution.bypassed,
|
|
281
|
+
exemption: resolution.exemption,
|
|
282
|
+
}
|
|
283
|
+
: {
|
|
284
|
+
kind: "selected",
|
|
285
|
+
target,
|
|
286
|
+
ref: ref.value,
|
|
287
|
+
...(resolution.newerHeld === undefined ? {} : { newerHeld: resolution.newerHeld }),
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
const findOfficialAxmSkill = (client, source, options) => Effect.gen(function* () {
|
|
291
|
+
const owner = Option.getOrNull(Option.isSome(options.owner) ? options.owner : source.owner);
|
|
292
|
+
if (owner !== "@agentxm" ||
|
|
293
|
+
options.type !== "skill" ||
|
|
294
|
+
options.names.length !== 1 ||
|
|
295
|
+
options.names[0] !== "axm") {
|
|
296
|
+
return Option.none();
|
|
297
|
+
}
|
|
298
|
+
const evaluatedAt = yield* DateTime.now;
|
|
299
|
+
const resolution = yield* resolveNamedFromClient(client, source, {
|
|
300
|
+
owner,
|
|
301
|
+
type: "skill",
|
|
302
|
+
name: "axm",
|
|
303
|
+
versionRange: options.versionRange,
|
|
304
|
+
releaseAgeEvaluation: {
|
|
305
|
+
minimumReleaseAge: Option.getOrElse(options.minimumReleaseAge ?? Option.none(), () => Duration.zero),
|
|
306
|
+
evaluatedAt,
|
|
307
|
+
mode: "enforce",
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
return Option.some(resolution.kind === "selected" ? [resolution.ref] : []);
|
|
311
|
+
});
|
|
312
|
+
const findWithVersionRange = (client, source, owners, options) => Effect.forEach(owners, (owner) => Effect.gen(function* () {
|
|
313
|
+
const requestedTypes = options.type === "*" ? installableExtensionTypes : [options.type];
|
|
314
|
+
const requestedNames = options.names.length > 0 ? options.names : [];
|
|
315
|
+
if (requestedNames.length === 0) {
|
|
316
|
+
const result = yield* client.getExtensionsByScope(toRegistrySearchOptions(owner, options));
|
|
317
|
+
const resolved = yield* Effect.forEach(result.extensions, (entry) => client
|
|
318
|
+
.getExtensionIndex({
|
|
319
|
+
owner: entry.owner,
|
|
320
|
+
type: entry.type,
|
|
321
|
+
name: entry.name,
|
|
322
|
+
})
|
|
323
|
+
.pipe(Effect.flatMap((indexOption) => Option.match(indexOption, {
|
|
324
|
+
onNone: () => Effect.succeed(Option.none()),
|
|
325
|
+
onSome: (index) => manifestFromIndex(index, options.versionRange, options.minimumReleaseAge),
|
|
326
|
+
}))), { concurrency: "unbounded" });
|
|
327
|
+
return getSupportedExtensionRefs(Array.getSomes(resolved), source);
|
|
328
|
+
}
|
|
329
|
+
const resolved = yield* Effect.forEach(requestedNames, (name) => Effect.forEach(requestedTypes, (type) => Effect.sync(() => {
|
|
330
|
+
try {
|
|
331
|
+
return decodeExtensionNameSync(name);
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
}).pipe(Effect.flatMap((decodedName) => decodedName === undefined
|
|
337
|
+
? Effect.succeed(Option.none())
|
|
338
|
+
: client.getExtensionIndex({ owner, type, name: decodedName }).pipe(Effect.flatMap((indexOption) => Option.match(indexOption, {
|
|
339
|
+
onNone: () => Effect.succeed(Option.none()),
|
|
340
|
+
onSome: (index) => manifestFromIndex(index, options.versionRange, options.minimumReleaseAge),
|
|
341
|
+
}))))), { concurrency: "unbounded" }), { concurrency: "unbounded" });
|
|
342
|
+
return resolved.flat().flatMap((entry) => Option.match(entry, {
|
|
343
|
+
onNone: () => [],
|
|
344
|
+
onSome: (manifest) => getSupportedExtensionRefs([manifest], source),
|
|
345
|
+
}));
|
|
346
|
+
}), { concurrency: "unbounded" }).pipe(Effect.map((results) => results.flat()));
|
|
347
|
+
/** Map RegistryExtensionManifest to ExtensionRef, stamped with the source. */
|
|
348
|
+
const toExtensionRef = (entry, source) => {
|
|
349
|
+
if (!isInstallableExtensionType(entry.type)) {
|
|
350
|
+
return Option.none();
|
|
351
|
+
}
|
|
352
|
+
const repository = Option.getOrUndefined(entry.repository);
|
|
353
|
+
const license = Option.getOrUndefined(entry.license);
|
|
354
|
+
const authors = entry.authors.map((author) => authorToMetadata(author));
|
|
355
|
+
const dependencies = entry.dependencies;
|
|
356
|
+
const skillMetadata = {
|
|
357
|
+
...(repository !== undefined && { repository }),
|
|
358
|
+
...(license !== undefined && { license }),
|
|
359
|
+
...(authors.length > 0 && { authors }),
|
|
360
|
+
...(Object.keys(dependencies).length > 0 && { dependencies }),
|
|
361
|
+
};
|
|
362
|
+
const details = {
|
|
363
|
+
owner: entry.owner,
|
|
364
|
+
publisherBindingId: entry.publisherBindingId,
|
|
365
|
+
name: entry.name,
|
|
366
|
+
version: entry.version,
|
|
367
|
+
integrity: Option.fromUndefinedOr(entry.integrity || undefined),
|
|
368
|
+
packages: entry.packages,
|
|
369
|
+
...(entry.deprecation === undefined ? {} : { deprecation: entry.deprecation }),
|
|
370
|
+
...(entry.lifecycleWarnings === undefined
|
|
371
|
+
? {}
|
|
372
|
+
: { lifecycleWarnings: entry.lifecycleWarnings }),
|
|
373
|
+
};
|
|
374
|
+
switch (entry.type) {
|
|
375
|
+
case "skill":
|
|
376
|
+
return Option.some({
|
|
377
|
+
type: "skill",
|
|
378
|
+
refType: "registry",
|
|
379
|
+
skill: {
|
|
380
|
+
name: entry.name,
|
|
381
|
+
description: entry.description,
|
|
382
|
+
metadata: Object.keys(skillMetadata).length > 0 ? Option.some(skillMetadata) : Option.none(),
|
|
383
|
+
},
|
|
384
|
+
source,
|
|
385
|
+
...details,
|
|
386
|
+
});
|
|
387
|
+
case "mcp-server":
|
|
388
|
+
return Option.some({
|
|
389
|
+
type: "mcp-server",
|
|
390
|
+
refType: "registry",
|
|
391
|
+
server: { name: entry.name },
|
|
392
|
+
source,
|
|
393
|
+
...details,
|
|
394
|
+
});
|
|
395
|
+
case "subagent":
|
|
396
|
+
return Option.some({
|
|
397
|
+
type: "subagent",
|
|
398
|
+
refType: "registry",
|
|
399
|
+
subagent: { name: entry.name, description: entry.description },
|
|
400
|
+
source,
|
|
401
|
+
...details,
|
|
402
|
+
});
|
|
403
|
+
case "rule":
|
|
404
|
+
return Option.some({
|
|
405
|
+
type: "rule",
|
|
406
|
+
refType: "registry",
|
|
407
|
+
rule: { name: entry.name },
|
|
408
|
+
source,
|
|
409
|
+
...details,
|
|
410
|
+
});
|
|
411
|
+
case "hook":
|
|
412
|
+
return Option.some({
|
|
413
|
+
type: "hook",
|
|
414
|
+
refType: "registry",
|
|
415
|
+
hook: { name: entry.name },
|
|
416
|
+
source,
|
|
417
|
+
...details,
|
|
418
|
+
});
|
|
419
|
+
case "knowledge":
|
|
420
|
+
return Option.some({
|
|
421
|
+
type: "knowledge",
|
|
422
|
+
refType: "registry",
|
|
423
|
+
knowledge: { name: entry.name },
|
|
424
|
+
source,
|
|
425
|
+
...details,
|
|
426
|
+
});
|
|
427
|
+
case "pack":
|
|
428
|
+
return Option.some({
|
|
429
|
+
type: "pack",
|
|
430
|
+
refType: "registry",
|
|
431
|
+
pack: { name: entry.name, dependencies },
|
|
432
|
+
source,
|
|
433
|
+
...details,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
/** Extract extension name from an ExtensionRef. */
|
|
438
|
+
const refName = (ref) => {
|
|
439
|
+
switch (ref.type) {
|
|
440
|
+
case "skill":
|
|
441
|
+
return ref.skill.name;
|
|
442
|
+
case "mcp-server":
|
|
443
|
+
return ref.server.name;
|
|
444
|
+
case "pack":
|
|
445
|
+
return ref.pack.name;
|
|
446
|
+
case "subagent":
|
|
447
|
+
return ref.subagent.name;
|
|
448
|
+
case "rule":
|
|
449
|
+
return ref.rule.name;
|
|
450
|
+
case "hook":
|
|
451
|
+
return ref.hook.name;
|
|
452
|
+
case "knowledge":
|
|
453
|
+
return ref.knowledge.name;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
/** Map ExtensionRef type to ExtensionType. */
|
|
457
|
+
const refRegistryType = (ref) => ref.type;
|
|
458
|
+
const fetchRegistryExtension = (client, ref) => Effect.gen(function* () {
|
|
459
|
+
if (ref.refType !== "registry") {
|
|
460
|
+
return yield* new SourceNetworkFailure({
|
|
461
|
+
detail: "Ref missing registry details (owner, version, integrity)",
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
const { owner, version, integrity: expectedIntegrity } = ref;
|
|
465
|
+
const type = refRegistryType(ref);
|
|
466
|
+
const name = refName(ref);
|
|
467
|
+
const { archive: archiveBytes, warnings } = yield* client.getExtensionPackage({
|
|
468
|
+
owner,
|
|
469
|
+
type,
|
|
470
|
+
name,
|
|
471
|
+
version: Option.some(version),
|
|
472
|
+
});
|
|
473
|
+
if (warnings !== undefined) {
|
|
474
|
+
yield* Effect.forEach(warnings, (warning) => Effect.logWarning(warning), {
|
|
475
|
+
discard: true,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
if (Option.isSome(expectedIntegrity)) {
|
|
479
|
+
const actualIntegrity = yield* computeIntegrity(archiveBytes);
|
|
480
|
+
if (actualIntegrity !== expectedIntegrity.value) {
|
|
481
|
+
return yield* new SourceNetworkFailure({
|
|
482
|
+
detail: `Integrity mismatch for ${type}:${name}@${version}`,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const fs = yield* FileSystem.FileSystem;
|
|
487
|
+
const tmpDir = yield* Effect.acquireRelease(fs.makeTempDirectory({ prefix: "axm-registry-package-" }).pipe(Effect.mapError((e) => new SourceNetworkFailure({
|
|
488
|
+
detail: "Temporary source directory could not be created",
|
|
489
|
+
cause: e,
|
|
490
|
+
}))), (dir) => fs.remove(dir, { recursive: true }).pipe(Effect.ignore));
|
|
491
|
+
yield* extractZip(archiveBytes, tmpDir);
|
|
492
|
+
return { directory: tmpDir };
|
|
493
|
+
});
|
|
494
|
+
// -----------------------------------------------------------------------------
|
|
495
|
+
// LocalRegistrySourceHostProvider
|
|
496
|
+
// -----------------------------------------------------------------------------
|
|
497
|
+
/**
|
|
498
|
+
* Creates a local registry source host provider backed by a RegistryClient.
|
|
499
|
+
*
|
|
500
|
+
* @experimental This API is unstable and may change without notice.
|
|
501
|
+
*/
|
|
502
|
+
export const createLocalRegistrySourceHostProvider = (client) => ({
|
|
503
|
+
type: "registry",
|
|
504
|
+
match: (url) => Effect.succeed(url.protocol === "file:"),
|
|
505
|
+
resolveNamed: (source, options) => resolveNamedFromClient(client, source, options),
|
|
506
|
+
find: (source, options) => Effect.gen(function* () {
|
|
507
|
+
const compatibleAxmSkill = yield* findOfficialAxmSkill(client, source, options);
|
|
508
|
+
if (Option.isSome(compatibleAxmSkill))
|
|
509
|
+
return compatibleAxmSkill.value;
|
|
510
|
+
const fsService = yield* FileSystem.FileSystem;
|
|
511
|
+
const pathService = yield* Path.Path;
|
|
512
|
+
const extensionsDir = pathService.join(source.location.pathname, "extensions");
|
|
513
|
+
const dirExists = yield* fsService
|
|
514
|
+
.exists(extensionsDir)
|
|
515
|
+
.pipe(Effect.orElseSucceed(() => false));
|
|
516
|
+
if (!dirExists)
|
|
517
|
+
return [];
|
|
518
|
+
const entries = yield* fsService
|
|
519
|
+
.readDirectory(extensionsDir)
|
|
520
|
+
.pipe(Effect.orElseSucceed(() => []));
|
|
521
|
+
const namespaces = Option.isSome(options.owner)
|
|
522
|
+
? [options.owner.value]
|
|
523
|
+
: entries.filter((d) => d.startsWith("@")).map((entry) => decodeHandleSync(entry));
|
|
524
|
+
if (needsIndexBackedResolution(options)) {
|
|
525
|
+
return yield* findWithVersionRange(client, source, namespaces, options);
|
|
526
|
+
}
|
|
527
|
+
const results = yield* Effect.forEach(namespaces, (owner) => Effect.gen(function* () {
|
|
528
|
+
const result = yield* client.getExtensionsByScope(toRegistrySearchOptions(owner, options));
|
|
529
|
+
return getSupportedExtensionRefs(result.extensions, source);
|
|
530
|
+
}), { concurrency: "unbounded" });
|
|
531
|
+
return results.flat();
|
|
532
|
+
}),
|
|
533
|
+
fetch: (_source, ref) => fetchRegistryExtension(client, ref),
|
|
534
|
+
});
|
|
535
|
+
// -----------------------------------------------------------------------------
|
|
536
|
+
// RemoteRegistrySourceHostProvider
|
|
537
|
+
// -----------------------------------------------------------------------------
|
|
538
|
+
/**
|
|
539
|
+
* Creates a remote registry source host provider backed by a RegistryClient.
|
|
540
|
+
*
|
|
541
|
+
* All operations delegate to the underlying RemoteRegistryClient, which
|
|
542
|
+
* returns errors for all operations (remote not yet supported).
|
|
543
|
+
*
|
|
544
|
+
* @experimental This API is unstable and may change without notice.
|
|
545
|
+
*/
|
|
546
|
+
export const createRemoteRegistrySourceHostProvider = (client) => ({
|
|
547
|
+
type: "registry",
|
|
548
|
+
match: (url) => Effect.succeed(url.protocol === "https:"),
|
|
549
|
+
resolveNamed: (source, options) => resolveNamedFromClient(client, source, options),
|
|
550
|
+
find: (source, options) => Effect.gen(function* () {
|
|
551
|
+
const compatibleAxmSkill = yield* findOfficialAxmSkill(client, source, options);
|
|
552
|
+
if (Option.isSome(compatibleAxmSkill))
|
|
553
|
+
return compatibleAxmSkill.value;
|
|
554
|
+
const owner = Option.isSome(options.owner) ? options.owner.value : "*";
|
|
555
|
+
if (needsIndexBackedResolution(options) && owner !== "*") {
|
|
556
|
+
return yield* findWithVersionRange(client, source, [owner], options);
|
|
557
|
+
}
|
|
558
|
+
const searchOptions = owner === "*" ? toSearchOptions("*", options) : toRegistrySearchOptions(owner, options);
|
|
559
|
+
const result = yield* client.getExtensionsByScope(searchOptions);
|
|
560
|
+
return getSupportedExtensionRefs(result.extensions, source);
|
|
561
|
+
}),
|
|
562
|
+
fetch: (_source, ref) => fetchRegistryExtension(client, ref),
|
|
563
|
+
});
|
|
564
|
+
// -----------------------------------------------------------------------------
|
|
565
|
+
// Factory
|
|
566
|
+
// -----------------------------------------------------------------------------
|
|
567
|
+
/**
|
|
568
|
+
* Create a registry source host provider for a given RegistrySourceHost.
|
|
569
|
+
*
|
|
570
|
+
* Creates the appropriate RegistryClient internally based on the host's
|
|
571
|
+
* location protocol, then wraps it in the matching host provider.
|
|
572
|
+
*
|
|
573
|
+
* @experimental This API is unstable and may change without notice.
|
|
574
|
+
*/
|
|
575
|
+
export const createRegistrySourceHostProviderFromHost = (host) => Effect.gen(function* () {
|
|
576
|
+
const location = host.location;
|
|
577
|
+
const locationStr = location.protocol === "file:" ? location.pathname : location.href;
|
|
578
|
+
const client = yield* createRegistryClient(locationStr);
|
|
579
|
+
if (location.protocol === "file:" || !location.protocol.startsWith("http")) {
|
|
580
|
+
return createLocalRegistrySourceHostProvider(client);
|
|
581
|
+
}
|
|
582
|
+
return createRemoteRegistrySourceHostProvider(client);
|
|
583
|
+
});
|
|
584
|
+
//# sourceMappingURL=host-provider.js.map
|