@agentxm/extension-lifecycle 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/dist/src/configured-entry-resolution.d.ts +110 -0
- package/dist/src/configured-entry-resolution.js +723 -0
- package/dist/src/errors.d.ts +39 -0
- package/dist/src/errors.js +45 -0
- package/dist/src/failure-adapter.d.ts +35 -0
- package/dist/src/failure-adapter.js +23 -0
- package/dist/src/hooks/configured-agent-outcomes-provider.d.ts +18 -0
- package/dist/src/hooks/configured-agent-outcomes-provider.js +39 -0
- package/dist/src/hooks/manager.d.ts +15 -0
- package/dist/src/hooks/manager.js +751 -0
- package/dist/src/index.d.ts +38 -0
- package/dist/src/index.js +41 -0
- package/dist/src/internal/fs-helpers.d.ts +12 -0
- package/dist/src/internal/fs-helpers.js +13 -0
- package/dist/src/internal/integrity.d.ts +13 -0
- package/dist/src/internal/integrity.js +17 -0
- package/dist/src/knowledge/manager.d.ts +11 -0
- package/dist/src/knowledge/manager.js +655 -0
- package/dist/src/live.d.ts +18 -0
- package/dist/src/live.js +18 -0
- package/dist/src/mcps/manager.d.ts +16 -0
- package/dist/src/mcps/manager.js +213 -0
- package/dist/src/mcps/operations/artifact.d.ts +24 -0
- package/dist/src/mcps/operations/artifact.js +74 -0
- package/dist/src/mcps/operations/disable.d.ts +18 -0
- package/dist/src/mcps/operations/disable.js +113 -0
- package/dist/src/mcps/operations/enable.d.ts +18 -0
- package/dist/src/mcps/operations/enable.js +218 -0
- package/dist/src/mcps/operations/install.d.ts +59 -0
- package/dist/src/mcps/operations/install.js +566 -0
- package/dist/src/mcps/operations/sync-outcome.d.ts +10 -0
- package/dist/src/mcps/operations/sync-outcome.js +27 -0
- package/dist/src/mcps/operations/uninstall.d.ts +41 -0
- package/dist/src/mcps/operations/uninstall.js +189 -0
- package/dist/src/packs/dependency-resolution.d.ts +65 -0
- package/dist/src/packs/dependency-resolution.js +414 -0
- package/dist/src/packs/expansion.d.ts +59 -0
- package/dist/src/packs/expansion.js +44 -0
- package/dist/src/packs/manager.d.ts +16 -0
- package/dist/src/packs/manager.js +181 -0
- package/dist/src/packs/operations/install.d.ts +67 -0
- package/dist/src/packs/operations/install.js +156 -0
- package/dist/src/packs/resolved-dependency.d.ts +29 -0
- package/dist/src/packs/resolved-dependency.js +28 -0
- package/dist/src/registry-materialization.d.ts +52 -0
- package/dist/src/registry-materialization.js +67 -0
- package/dist/src/resolution-progress.d.ts +18 -0
- package/dist/src/resolution-progress.js +11 -0
- package/dist/src/resolution-timeout.d.ts +5 -0
- package/dist/src/resolution-timeout.js +11 -0
- package/dist/src/rules/manager.d.ts +20 -0
- package/dist/src/rules/manager.js +475 -0
- package/dist/src/skills/manager.d.ts +18 -0
- package/dist/src/skills/manager.js +346 -0
- package/dist/src/skills/materialization.d.ts +54 -0
- package/dist/src/skills/materialization.js +158 -0
- package/dist/src/skills/operations/disable.d.ts +31 -0
- package/dist/src/skills/operations/disable.js +110 -0
- package/dist/src/skills/operations/enable.d.ts +31 -0
- package/dist/src/skills/operations/enable.js +113 -0
- package/dist/src/skills/operations/install-result.d.ts +33 -0
- package/dist/src/skills/operations/install-result.js +10 -0
- package/dist/src/skills/operations/install.d.ts +67 -0
- package/dist/src/skills/operations/install.js +590 -0
- package/dist/src/skills/operations/source-hash.d.ts +2 -0
- package/dist/src/skills/operations/source-hash.js +6 -0
- package/dist/src/skills/operations/uninstall.d.ts +40 -0
- package/dist/src/skills/operations/uninstall.js +157 -0
- package/dist/src/skills/utils.d.ts +11 -0
- package/dist/src/skills/utils.js +29 -0
- package/dist/src/subagents/manager.d.ts +18 -0
- package/dist/src/subagents/manager.js +677 -0
- package/dist/src/subagents/operations/artifact.d.ts +18 -0
- package/dist/src/subagents/operations/artifact.js +43 -0
- package/dist/src/subagents/operations/disable.d.ts +32 -0
- package/dist/src/subagents/operations/disable.js +116 -0
- package/dist/src/subagents/operations/enable.d.ts +30 -0
- package/dist/src/subagents/operations/enable.js +160 -0
- package/dist/src/workflows/install-command/workflow.d.ts +57 -0
- package/dist/src/workflows/install-command/workflow.js +41 -0
- package/dist/src/workflows/uninstall-command/workflow.d.ts +42 -0
- package/dist/src/workflows/uninstall-command/workflow.js +29 -0
- package/package.json +62 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill extension manager service.
|
|
3
|
+
*
|
|
4
|
+
* Implements ExtensionManager<SkillExtensionRef> with native/non-native
|
|
5
|
+
* branching in materializeInstall and agent symlink creation for all
|
|
6
|
+
* configured agents.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
import * as FileSystem from "effect/FileSystem";
|
|
11
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
12
|
+
import * as Path from "effect/Path";
|
|
13
|
+
import * as Array from "effect/Array";
|
|
14
|
+
import * as Effect from "effect/Effect";
|
|
15
|
+
import * as Layer from "effect/Layer";
|
|
16
|
+
import * as Option from "effect/Option";
|
|
17
|
+
import { sourceToLockEntry } from "@agentxm/workspace-state";
|
|
18
|
+
import { configuredSkillsToDiskRefs } from "@agentxm/extension-workspace";
|
|
19
|
+
import { enabledConfiguredEntries } from "@agentxm/extension-workspace";
|
|
20
|
+
import { SourceHostProviders } from "@agentxm/extension-sources";
|
|
21
|
+
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
22
|
+
import { sanitizeName } from "@agentxm/workspace-state";
|
|
23
|
+
import { computePackageContentHash } from "@agentxm/workspace-state";
|
|
24
|
+
import { stripFileProtocol } from "../internal/fs-helpers.js";
|
|
25
|
+
import { makeWorkspaceRelativeSourcePath } from "@agentxm/extension-model/unstable/path-types";
|
|
26
|
+
import { removeIfExists } from "@agentxm/workspace-state";
|
|
27
|
+
import { CodingAgentRepository, SkillManager, applyProjectionPlans, planSingletonProjection, SkillDefinitionInvalid, SkillInstallStateMissing, } from "@agentxm/extension-workspace";
|
|
28
|
+
import {} from "@agentxm/extension-model/unstable/agents/types";
|
|
29
|
+
import { acceptedRegistryVersionForRef, validateExactResolvedVersion, } from "@agentxm/workspace-state";
|
|
30
|
+
import { computeSkillSourceHash } from "./operations/source-hash.js";
|
|
31
|
+
import { ensureSkillAgentArtifact, materializeSkillCanonical, removeSkillAgentArtifact, } from "./materialization.js";
|
|
32
|
+
import { configuredRowsByName } from "@agentxm/workspace-state";
|
|
33
|
+
import { acceptedCanonicalObservation, prepareAcceptedCanonicalTransition, removableAcceptedCanonicalPath, usableAcceptedCanonicalRef, } from "@agentxm/workspace-state";
|
|
34
|
+
import { isObservedInstalled } from "@agentxm/workspace-state";
|
|
35
|
+
// -----------------------------------------------------------------------------
|
|
36
|
+
// Helpers
|
|
37
|
+
// -----------------------------------------------------------------------------
|
|
38
|
+
// Build skill lock entry from ref
|
|
39
|
+
const buildSkillLockEntry = (ref, workspaceRelativeLocalSourcePath, contentIdentity, treeIntegrity) => sourceToLockEntry({
|
|
40
|
+
ref,
|
|
41
|
+
sourceName: Option.none(),
|
|
42
|
+
contentIdentity,
|
|
43
|
+
treeIntegrity,
|
|
44
|
+
workspaceRelativeLocalSourcePath,
|
|
45
|
+
});
|
|
46
|
+
// -----------------------------------------------------------------------------
|
|
47
|
+
// Live Layer
|
|
48
|
+
// -----------------------------------------------------------------------------
|
|
49
|
+
export const SkillManagerLive = Layer.effect(SkillManager, Effect.gen(function* () {
|
|
50
|
+
const ws = yield* WorkspaceMutations;
|
|
51
|
+
const fs = yield* FileSystem.FileSystem;
|
|
52
|
+
const httpClient = yield* HttpClient.HttpClient;
|
|
53
|
+
const path = yield* Path.Path;
|
|
54
|
+
const sources = yield* SourceHostProviders;
|
|
55
|
+
const agentRepo = yield* CodingAgentRepository;
|
|
56
|
+
const baseDir = ws.baseDir;
|
|
57
|
+
// Build a layer to provide FileSystem + Path to inner effects
|
|
58
|
+
const fsPathLayer = Layer.mergeAll(Layer.succeed(FileSystem.FileSystem, fs), Layer.succeed(Path.Path, path));
|
|
59
|
+
const registryMaterializationLayer = Layer.merge(fsPathLayer, Layer.succeed(HttpClient.HttpClient, httpClient));
|
|
60
|
+
// Provide FileSystem + Path to an effect that needs them
|
|
61
|
+
const provide = (effect) => Effect.provide(effect, fsPathLayer);
|
|
62
|
+
const provideRegistry = (effect) => Effect.provide(effect, registryMaterializationLayer);
|
|
63
|
+
const lastSourceHashes = new Map();
|
|
64
|
+
const lastTreeIntegrities = new Map();
|
|
65
|
+
const lastMaterializations = new Map();
|
|
66
|
+
const materializeInstall = Effect.fn("SkillManager.materializeInstall")(function* ({ ref, force }) {
|
|
67
|
+
const sanitized = sanitizeName(ref.skill.name);
|
|
68
|
+
const lockedEntry = yield* ws.getLockedSkill(ref.skill.name);
|
|
69
|
+
const lockedVersion = ref.refType === "registry" ? acceptedRegistryVersionForRef(lockedEntry, ref) : undefined;
|
|
70
|
+
const materialized = yield* materializeSkillCanonical({
|
|
71
|
+
ref,
|
|
72
|
+
sanitizedName: sanitized,
|
|
73
|
+
fs,
|
|
74
|
+
pathService: path,
|
|
75
|
+
baseDir,
|
|
76
|
+
layout: ws.layout,
|
|
77
|
+
sources,
|
|
78
|
+
provide,
|
|
79
|
+
provideRegistry,
|
|
80
|
+
reuse: {
|
|
81
|
+
force: force === true,
|
|
82
|
+
lockedVersion,
|
|
83
|
+
lockedTreeIntegrity: Option.isSome(lockedEntry)
|
|
84
|
+
? lockedEntry.value.treeIntegrity
|
|
85
|
+
: undefined,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const skillSrcPath = materialized.skillSrcPath;
|
|
89
|
+
const configuredAgents = yield* agentRepo
|
|
90
|
+
.getMaterializationAgents()
|
|
91
|
+
.pipe(Effect.provideService(WorkspaceMutations, ws));
|
|
92
|
+
const resolved = yield* Effect.forEach(configuredAgents, (agent) => agent.resolveEffectiveSkillsDir({ workspaceRoot: baseDir }).pipe(Effect.provide(fsPathLayer), Effect.map((outcome) => ({ agent, outcome }))), { concurrency: "unbounded" });
|
|
93
|
+
const misconfigured = Array.filter(resolved, ({ outcome }) => outcome._tag === "misconfigured");
|
|
94
|
+
if (misconfigured.length > 0) {
|
|
95
|
+
return yield* new SkillDefinitionInvalid({
|
|
96
|
+
detail: "One or more configured agents have invalid skills directory settings",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const installTargets = [];
|
|
100
|
+
for (const { agent, outcome } of resolved) {
|
|
101
|
+
if (outcome._tag === "supported") {
|
|
102
|
+
installTargets.push({ agentId: agent.id, dir: path.normalize(outcome.dir) });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const locations = new Map();
|
|
106
|
+
for (const target of installTargets) {
|
|
107
|
+
const existing = locations.get(target.dir);
|
|
108
|
+
if (existing === undefined) {
|
|
109
|
+
locations.set(target.dir, { dir: target.dir, agentIds: [target.agentId] });
|
|
110
|
+
}
|
|
111
|
+
else if (!existing.agentIds.includes(target.agentId)) {
|
|
112
|
+
existing.agentIds.push(target.agentId);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
yield* applyProjectionPlans([...locations.values()].map((location) => {
|
|
116
|
+
const targetFile = path.join(location.dir, sanitized);
|
|
117
|
+
return planSingletonProjection({
|
|
118
|
+
unitId: "skill:agent-skill-directory",
|
|
119
|
+
targetFile,
|
|
120
|
+
contributor: ref,
|
|
121
|
+
adapter: {
|
|
122
|
+
observe: () => Effect.succeed({
|
|
123
|
+
unitId: "skill:agent-skill-directory",
|
|
124
|
+
path: path.relative(baseDir, targetFile),
|
|
125
|
+
present: false,
|
|
126
|
+
current: false,
|
|
127
|
+
expectedContributors: [ref.skill.name],
|
|
128
|
+
observedContributors: [],
|
|
129
|
+
}),
|
|
130
|
+
apply: () => ensureSkillAgentArtifact({
|
|
131
|
+
canonicalSkillSrcPath: skillSrcPath,
|
|
132
|
+
targetDir: location.dir,
|
|
133
|
+
sanitizedName: sanitized,
|
|
134
|
+
pathService: path,
|
|
135
|
+
baseDir,
|
|
136
|
+
provide,
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}));
|
|
141
|
+
const sourceHash = ref.refType === "workspace"
|
|
142
|
+
? ref.sourceHash
|
|
143
|
+
: ref.refType === "registry"
|
|
144
|
+
? yield* provide(computePackageContentHash(path.dirname(skillSrcPath)))
|
|
145
|
+
: yield* provide(computeSkillSourceHash(skillSrcPath));
|
|
146
|
+
lastSourceHashes.set(ref.skill.name, sourceHash);
|
|
147
|
+
if (ref.refType !== "workspace") {
|
|
148
|
+
if (materialized.treeIntegrity === undefined) {
|
|
149
|
+
return yield* new SkillInstallStateMissing({
|
|
150
|
+
name: ref.skill.name,
|
|
151
|
+
kind: "tree-integrity",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
lastTreeIntegrities.set(ref.skill.name, materialized.treeIntegrity);
|
|
155
|
+
}
|
|
156
|
+
lastMaterializations.set(ref.skill.name, {
|
|
157
|
+
agents: Array.dedupe(installTargets
|
|
158
|
+
.map((target) => target.agentId)
|
|
159
|
+
.filter((agentId) => agentId !== "universal")),
|
|
160
|
+
targets: [...locations.values()].map((location) => {
|
|
161
|
+
const agentIds = location.agentIds.filter((agentId) => agentId !== "universal");
|
|
162
|
+
return {
|
|
163
|
+
path: path.relative(baseDir, path.join(location.dir, sanitized)),
|
|
164
|
+
...(agentIds.length === 0 ? {} : { agentIds }),
|
|
165
|
+
};
|
|
166
|
+
}),
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
const makeMaterializeRemoval = (retainCanonical) => Effect.fn("SkillManager.materializeRemoval")(function* ({ target }) {
|
|
170
|
+
const sanitized = sanitizeName(target.name);
|
|
171
|
+
const configuredAgents = yield* agentRepo
|
|
172
|
+
.getMaterializationAgents()
|
|
173
|
+
.pipe(Effect.provideService(WorkspaceMutations, ws));
|
|
174
|
+
const resolved = yield* Effect.forEach(configuredAgents, (agent) => agent.resolveEffectiveSkillsDir({ workspaceRoot: baseDir }).pipe(Effect.provide(fsPathLayer), Effect.map((outcome) => ({ agent, outcome }))), { concurrency: "unbounded" });
|
|
175
|
+
const uninstallTargets = [];
|
|
176
|
+
for (const { outcome } of resolved) {
|
|
177
|
+
if (outcome._tag === "supported") {
|
|
178
|
+
uninstallTargets.push(path.normalize(outcome.dir));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const distinctDirs = Array.dedupe(uninstallTargets);
|
|
182
|
+
yield* applyProjectionPlans(distinctDirs.map((dir) => {
|
|
183
|
+
const targetFile = path.join(dir, sanitized);
|
|
184
|
+
return planSingletonProjection({
|
|
185
|
+
unitId: "skill:agent-skill-directory",
|
|
186
|
+
targetFile,
|
|
187
|
+
contributor: target,
|
|
188
|
+
adapter: {
|
|
189
|
+
observe: () => Effect.succeed({
|
|
190
|
+
unitId: "skill:agent-skill-directory",
|
|
191
|
+
path: path.relative(baseDir, targetFile),
|
|
192
|
+
present: true,
|
|
193
|
+
current: false,
|
|
194
|
+
expectedContributors: [],
|
|
195
|
+
observedContributors: [target.name],
|
|
196
|
+
}),
|
|
197
|
+
apply: () => removeSkillAgentArtifact({
|
|
198
|
+
fs,
|
|
199
|
+
pathService: path,
|
|
200
|
+
targetDir: dir,
|
|
201
|
+
sanitizedName: sanitized,
|
|
202
|
+
}),
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}));
|
|
206
|
+
if (!retainCanonical) {
|
|
207
|
+
const canonical = yield* provide(acceptedCanonicalObservation({ workspace: ws, type: "skill", name: target.name }));
|
|
208
|
+
const packageRoot = removableAcceptedCanonicalPath(canonical);
|
|
209
|
+
if (Option.isSome(packageRoot))
|
|
210
|
+
yield* removeIfExists(fs, packageRoot.value);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const materializeUninstall = makeMaterializeRemoval(false);
|
|
214
|
+
const materializeDeactivate = makeMaterializeRemoval(true);
|
|
215
|
+
return {
|
|
216
|
+
type: "skill",
|
|
217
|
+
runTransaction: ws.runTransaction,
|
|
218
|
+
isInstalled: Effect.fn("SkillManager.isInstalled")(function* ({ target, }) {
|
|
219
|
+
return yield* isObservedInstalled(ws, "skill", target.name);
|
|
220
|
+
}),
|
|
221
|
+
materializeInstall,
|
|
222
|
+
prepareSourceTransition: ({ ref }) => provide(prepareAcceptedCanonicalTransition({
|
|
223
|
+
workspace: ws,
|
|
224
|
+
type: "skill",
|
|
225
|
+
name: ref.skill.name,
|
|
226
|
+
ref,
|
|
227
|
+
})),
|
|
228
|
+
getLastMaterialization: ({ target }) => Effect.succeed(lastMaterializations.get(target.name) ?? {
|
|
229
|
+
agents: [],
|
|
230
|
+
targets: [],
|
|
231
|
+
}),
|
|
232
|
+
getConfiguredSource: Effect.fn("SkillManager.getConfiguredSource")(function* ({ target }) {
|
|
233
|
+
const configured = yield* ws.getConfiguredSkillEntries();
|
|
234
|
+
const entry = configured[target.name];
|
|
235
|
+
if (entry?.origin === "bundled") {
|
|
236
|
+
return Option.some(`bundled:@agentxm/skills/${target.name}`);
|
|
237
|
+
}
|
|
238
|
+
return Option.fromUndefinedOr(entry?.source);
|
|
239
|
+
}),
|
|
240
|
+
listMaterializable: Effect.fn("SkillManager.listMaterializable")(function* () {
|
|
241
|
+
const configured = yield* ws.records
|
|
242
|
+
.rows("skill")
|
|
243
|
+
.pipe(Effect.map(configuredRowsByName));
|
|
244
|
+
const configuredEntries = yield* ws.getConfiguredSkillEntries();
|
|
245
|
+
const configuredWithoutBundled = Object.fromEntries(Object.entries(configured).filter(([name]) => configuredEntries[name]?.origin !== "bundled"));
|
|
246
|
+
const workspaceRefs = yield* configuredSkillsToDiskRefs({ fs, path, baseDir, scope: ws.scope, layout: ws.layout }, configuredWithoutBundled);
|
|
247
|
+
const trustedRefs = yield* Effect.forEach(enabledConfiguredEntries(configured), ([name]) => configuredEntries[name]?.origin === "bundled"
|
|
248
|
+
? Effect.succeed(Option.none())
|
|
249
|
+
: provide(usableAcceptedCanonicalRef({ workspace: ws, type: "skill", name }).pipe(Effect.map(Option.filter((ref) => ref.type === "skill")))), { concurrency: "unbounded" });
|
|
250
|
+
const refsByName = new Map(workspaceRefs.map((ref) => [ref.skill.name, ref]));
|
|
251
|
+
for (const ref of trustedRefs) {
|
|
252
|
+
if (Option.isSome(ref))
|
|
253
|
+
refsByName.set(ref.value.skill.name, ref.value);
|
|
254
|
+
}
|
|
255
|
+
return [...refsByName.values()];
|
|
256
|
+
}),
|
|
257
|
+
materializeUninstall,
|
|
258
|
+
materializeDeactivate,
|
|
259
|
+
upsertSettingsEntry: Effect.fn("SkillManager.upsertSettingsEntry")(function* ({ ref, versionRange, }) {
|
|
260
|
+
const workspaceRelativeLocalSourcePath = ref.refType === "local"
|
|
261
|
+
? makeWorkspaceRelativeSourcePath(path, baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
|
|
262
|
+
: Option.none();
|
|
263
|
+
if (ref.refType === "local" && Option.isNone(workspaceRelativeLocalSourcePath)) {
|
|
264
|
+
return yield* new SkillDefinitionInvalid({
|
|
265
|
+
detail: `Local skill source path must stay within the workspace root: ${ref.source.path}`,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
const sourceHash = lastSourceHashes.get(ref.skill.name);
|
|
269
|
+
const treeIntegrity = lastTreeIntegrities.get(ref.skill.name);
|
|
270
|
+
if (ref.refType === "workspace") {
|
|
271
|
+
return yield* ws.setSkillEntry(ref.skill.name, {
|
|
272
|
+
source: "workspace",
|
|
273
|
+
enabled: true,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (sourceHash === undefined || treeIntegrity === undefined) {
|
|
277
|
+
return yield* new SkillInstallStateMissing({
|
|
278
|
+
name: ref.skill.name,
|
|
279
|
+
kind: "content-identity",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
const lockEntry = buildSkillLockEntry(ref, workspaceRelativeLocalSourcePath, sourceHash, treeIntegrity);
|
|
283
|
+
if (lockEntry === undefined) {
|
|
284
|
+
return yield* new SkillInstallStateMissing({
|
|
285
|
+
name: ref.skill.name,
|
|
286
|
+
kind: "external-resolution",
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (lockEntry.type === "registry") {
|
|
290
|
+
yield* validateExactResolvedVersion(`skills.${ref.skill.name}.resolvedVersion`, lockEntry.resolvedVersion);
|
|
291
|
+
}
|
|
292
|
+
return yield* ws.setSkill({
|
|
293
|
+
name: ref.skill.name,
|
|
294
|
+
lockEntry,
|
|
295
|
+
versionRange,
|
|
296
|
+
});
|
|
297
|
+
}),
|
|
298
|
+
removeSettingsEntry: ({ target }) => ws
|
|
299
|
+
.removeSkillFromSettings(target.name)
|
|
300
|
+
.pipe(Effect.withSpan("SkillManager.removeSettingsEntry")),
|
|
301
|
+
upsertLockfileEntry: Effect.fn("SkillManager.upsertLockfileEntry")(function* ({ ref, }) {
|
|
302
|
+
const workspaceRelativeLocalSourcePath = ref.refType === "local"
|
|
303
|
+
? makeWorkspaceRelativeSourcePath(path, baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
|
|
304
|
+
: Option.none();
|
|
305
|
+
if (ref.refType === "local" && Option.isNone(workspaceRelativeLocalSourcePath)) {
|
|
306
|
+
return yield* new SkillDefinitionInvalid({
|
|
307
|
+
detail: `Local skill source path must stay within the workspace root: ${ref.source.path}`,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (ref.refType === "workspace") {
|
|
311
|
+
yield* ws.removeSkillLock(ref.skill.name);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const sourceHash = lastSourceHashes.get(ref.skill.name);
|
|
315
|
+
const treeIntegrity = lastTreeIntegrities.get(ref.skill.name);
|
|
316
|
+
if (sourceHash === undefined || treeIntegrity === undefined) {
|
|
317
|
+
return yield* new SkillInstallStateMissing({
|
|
318
|
+
name: ref.skill.name,
|
|
319
|
+
kind: "content-identity",
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
const lockEntry = buildSkillLockEntry(ref, workspaceRelativeLocalSourcePath, sourceHash, treeIntegrity);
|
|
323
|
+
if (lockEntry === undefined) {
|
|
324
|
+
return yield* new SkillInstallStateMissing({
|
|
325
|
+
name: ref.skill.name,
|
|
326
|
+
kind: "external-resolution",
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
if (lockEntry.type === "registry") {
|
|
330
|
+
yield* validateExactResolvedVersion(`skills.${ref.skill.name}.resolvedVersion`, lockEntry.resolvedVersion);
|
|
331
|
+
}
|
|
332
|
+
return yield* ws.setSkillLock({
|
|
333
|
+
name: ref.skill.name,
|
|
334
|
+
lockEntry,
|
|
335
|
+
versionRange: Option.none(),
|
|
336
|
+
});
|
|
337
|
+
}),
|
|
338
|
+
removeLockfileEntry: ({ target }) => ws
|
|
339
|
+
.removeSkillLock(target.name)
|
|
340
|
+
.pipe(Effect.withSpan("SkillManager.removeLockfileEntry")),
|
|
341
|
+
};
|
|
342
|
+
}));
|
|
343
|
+
// -----------------------------------------------------------------------------
|
|
344
|
+
// Internal materialization helpers
|
|
345
|
+
// -----------------------------------------------------------------------------
|
|
346
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as FileSystem from "effect/FileSystem";
|
|
2
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
3
|
+
import * as Path from "effect/Path";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import { SkillMaterializationFailed } from "@agentxm/extension-workspace";
|
|
6
|
+
import { type TreeIntegrity } from "@agentxm/workspace-state";
|
|
7
|
+
import type { SourceHostProvidersService } from "@agentxm/extension-sources";
|
|
8
|
+
import type { SkillExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/skill";
|
|
9
|
+
import type { WorkspaceLayout } from "@agentxm/workspace-state";
|
|
10
|
+
export type ProvideFs = <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem | Path.Path>>;
|
|
11
|
+
export type ProvideRegistryMaterialization = <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>>;
|
|
12
|
+
/** Reuse inputs sourced from the caller's operation context and lockfile. */
|
|
13
|
+
export type CanonicalReuseContext = {
|
|
14
|
+
readonly force: boolean;
|
|
15
|
+
readonly lockedVersion: string | undefined;
|
|
16
|
+
readonly lockedTreeIntegrity: TreeIntegrity | undefined;
|
|
17
|
+
};
|
|
18
|
+
export interface MaterializedSkillCanonical {
|
|
19
|
+
readonly skillSrcPath: string;
|
|
20
|
+
readonly treeIntegrity?: TreeIntegrity;
|
|
21
|
+
}
|
|
22
|
+
export declare const materializeSkillCanonical: (args: {
|
|
23
|
+
readonly ref: SkillExtensionRef;
|
|
24
|
+
readonly sanitizedName: string;
|
|
25
|
+
readonly fs: FileSystem.FileSystem;
|
|
26
|
+
readonly pathService: Path.Path;
|
|
27
|
+
readonly baseDir: string;
|
|
28
|
+
readonly layout: WorkspaceLayout;
|
|
29
|
+
readonly sources: SourceHostProvidersService;
|
|
30
|
+
readonly provide: ProvideFs;
|
|
31
|
+
readonly provideRegistry: ProvideRegistryMaterialization;
|
|
32
|
+
readonly reuse?: CanonicalReuseContext;
|
|
33
|
+
}) => Effect.Effect<{
|
|
34
|
+
skillSrcPath: string & import("effect/Brand").Brand<"AbsolutePath">;
|
|
35
|
+
treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
|
|
36
|
+
}, import("@agentxm/extension-workspace").CanonicalDirectoryReplacementError | import("@agentxm/workspace-state").MaterializedTreeInvalid | import("@agentxm/extension-workspace").PackageCopyFailed | import("@agentxm/extension-workspace").CanonicalPackageProbeFailed | import("@agentxm/extension-workspace").AxmSkillCompatibilityUnavailable | import("@agentxm/extension-workspace").AxmSkillIncompatible, never> | Effect.Effect<{
|
|
37
|
+
skillSrcPath: string & import("effect/Brand").Brand<"AbsolutePath">;
|
|
38
|
+
treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
|
|
39
|
+
}, import("@agentxm/extension-workspace").CoupledDependencyFailure | import("@agentxm/extension-workspace").ArchiveIntegrityMismatch | import("@agentxm/extension-workspace").CanonicalDirectoryReplacementError | import("@agentxm/workspace-state").MaterializedTreeInvalid | import("@agentxm/extension-workspace").CanonicalPackageProbeFailed | import("@agentxm/extension-workspace").AxmSkillCompatibilityUnavailable | import("@agentxm/extension-workspace").AxmSkillIncompatible, never> | Effect.Effect<MaterializedSkillCanonical, import("@agentxm/workspace-state").PathTraversalDetected | import("@agentxm/extension-workspace").AxmSkillCompatibilityUnavailable | import("@agentxm/extension-workspace").AxmSkillIncompatible, never>;
|
|
40
|
+
export declare const ensureSkillAgentArtifact: (args: {
|
|
41
|
+
readonly canonicalSkillSrcPath: string;
|
|
42
|
+
readonly targetDir: string;
|
|
43
|
+
readonly sanitizedName: string;
|
|
44
|
+
readonly pathService: Path.Path;
|
|
45
|
+
readonly baseDir: string;
|
|
46
|
+
readonly provide: ProvideFs;
|
|
47
|
+
}) => Effect.Effect<void, SkillMaterializationFailed, never>;
|
|
48
|
+
export declare const removeSkillAgentArtifact: (args: {
|
|
49
|
+
readonly fs: FileSystem.FileSystem;
|
|
50
|
+
readonly pathService: Path.Path;
|
|
51
|
+
readonly targetDir: string;
|
|
52
|
+
readonly sanitizedName: string;
|
|
53
|
+
}) => Effect.Effect<void, SkillMaterializationFailed, never>;
|
|
54
|
+
//# sourceMappingURL=materialization.d.ts.map
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import * as FileSystem from "effect/FileSystem";
|
|
2
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
3
|
+
import * as Path from "effect/Path";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
import { SkillMaterializationFailed, validateAxmSkillCandidate, } from "@agentxm/extension-workspace";
|
|
7
|
+
import { canReuseExternalPackage, canReuseInstalledPackage, materializeExternalPackageWithTreeIntegrity, } from "@agentxm/extension-workspace";
|
|
8
|
+
import { materializeRegistryPackageWithTreeIntegrity } from "../registry-materialization.js";
|
|
9
|
+
import { validatePathSafety } from "@agentxm/workspace-state";
|
|
10
|
+
import { computeMaterializedTreeIntegrity } from "@agentxm/workspace-state";
|
|
11
|
+
import { copyExtensionDirectory } from "@agentxm/extension-workspace";
|
|
12
|
+
import { computeSkillPathsForLayout } from "@agentxm/workspace-state";
|
|
13
|
+
import { stripFileProtocol } from "../internal/fs-helpers.js";
|
|
14
|
+
import { isPathSafe } from "@agentxm/workspace-state";
|
|
15
|
+
import { createSymlink } from "@agentxm/workspace-state";
|
|
16
|
+
import { protectWorkspacePath } from "@agentxm/workspace-state";
|
|
17
|
+
const replaceExternalCanonical = (fs, pathService, baseDir, sanitizedName, sourcePath, copyTarget, reuse, provide) => Effect.gen(function* () {
|
|
18
|
+
const useExisting = yield* provide(canReuseExternalPackage({
|
|
19
|
+
installedPath: copyTarget,
|
|
20
|
+
force: reuse.force,
|
|
21
|
+
existsFailureDetail: (target) => `Failed to check if canonical path exists: ${target}`,
|
|
22
|
+
}));
|
|
23
|
+
if (useExisting && reuse.lockedTreeIntegrity !== undefined) {
|
|
24
|
+
const observedTree = yield* provide(computeMaterializedTreeIntegrity(copyTarget));
|
|
25
|
+
if (observedTree === reuse.lockedTreeIntegrity)
|
|
26
|
+
return reuse.lockedTreeIntegrity;
|
|
27
|
+
}
|
|
28
|
+
const materialized = yield* provide(materializeExternalPackageWithTreeIntegrity({
|
|
29
|
+
baseDir,
|
|
30
|
+
canonicalPath: copyTarget,
|
|
31
|
+
sourceLocation: sourcePath,
|
|
32
|
+
copyFailureCode: "internal",
|
|
33
|
+
copyFailureDetail: (target) => `Failed to copy skill files to ${target}`,
|
|
34
|
+
}));
|
|
35
|
+
return materialized.treeIntegrity;
|
|
36
|
+
});
|
|
37
|
+
const materializeGitHosted = (ref, sanitizedName, fs, pathService, baseDir, layout, provide, reuse) => Effect.gen(function* () {
|
|
38
|
+
const { canonicalPath, skillSrcPath } = computeSkillPathsForLayout(pathService.join, layout, ref, sanitizedName);
|
|
39
|
+
yield* validatePathSafety(pathService, baseDir, canonicalPath);
|
|
40
|
+
const packageRoot = stripFileProtocol(ref.location);
|
|
41
|
+
const sourceSkillPath = ref.portable === true ? packageRoot : pathService.join(packageRoot, "src");
|
|
42
|
+
yield* provide(validateAxmSkillCandidate({
|
|
43
|
+
ref,
|
|
44
|
+
packageRoot,
|
|
45
|
+
skillSourcePath: sourceSkillPath,
|
|
46
|
+
}));
|
|
47
|
+
const isSelfCopy = pathService.resolve(packageRoot) === pathService.resolve(canonicalPath);
|
|
48
|
+
const treeIntegrity = isSelfCopy
|
|
49
|
+
? yield* provide(computeMaterializedTreeIntegrity(canonicalPath))
|
|
50
|
+
: yield* replaceExternalCanonical(fs, pathService, baseDir, sanitizedName, packageRoot, canonicalPath, reuse, provide);
|
|
51
|
+
return { skillSrcPath, treeIntegrity };
|
|
52
|
+
});
|
|
53
|
+
const materializeLocal = (ref, sanitizedName, fs, pathService, baseDir, layout, provide, reuse) => Effect.gen(function* () {
|
|
54
|
+
const { canonicalPath, skillSrcPath } = computeSkillPathsForLayout(pathService.join, layout, ref, sanitizedName);
|
|
55
|
+
yield* validatePathSafety(pathService, baseDir, canonicalPath);
|
|
56
|
+
const packageRoot = stripFileProtocol(ref.location);
|
|
57
|
+
const sourceSkillPath = ref.portable === true ? packageRoot : pathService.join(packageRoot, "src");
|
|
58
|
+
yield* provide(validateAxmSkillCandidate({
|
|
59
|
+
ref,
|
|
60
|
+
packageRoot,
|
|
61
|
+
skillSourcePath: sourceSkillPath,
|
|
62
|
+
}));
|
|
63
|
+
const isSelfCopy = pathService.resolve(packageRoot) === pathService.resolve(canonicalPath);
|
|
64
|
+
const treeIntegrity = isSelfCopy
|
|
65
|
+
? yield* provide(computeMaterializedTreeIntegrity(canonicalPath))
|
|
66
|
+
: yield* replaceExternalCanonical(fs, pathService, baseDir, sanitizedName, packageRoot, canonicalPath, reuse, provide);
|
|
67
|
+
return { skillSrcPath, treeIntegrity };
|
|
68
|
+
});
|
|
69
|
+
const materializeRegistry = (ref, sanitizedName, fs, pathService, baseDir, layout, provide, provideRegistry, reuse) => Effect.scoped(Effect.gen(function* () {
|
|
70
|
+
const source = ref;
|
|
71
|
+
const { canonicalPath, skillSrcPath } = computeSkillPathsForLayout(pathService.join, layout, source, sanitizedName);
|
|
72
|
+
yield* validatePathSafety(pathService, baseDir, canonicalPath);
|
|
73
|
+
const useExisting = yield* provide(canReuseInstalledPackage({
|
|
74
|
+
installedPath: canonicalPath,
|
|
75
|
+
force: reuse.force,
|
|
76
|
+
refVersion: ref.version,
|
|
77
|
+
hasIntegrity: Option.isSome(ref.integrity),
|
|
78
|
+
...(reuse.lockedVersion === undefined ? {} : { lockedVersion: reuse.lockedVersion }),
|
|
79
|
+
existsFailureDetail: (target) => `Failed to check if canonical path exists: ${target}`,
|
|
80
|
+
}));
|
|
81
|
+
if (useExisting && reuse.lockedTreeIntegrity !== undefined) {
|
|
82
|
+
const observedTree = yield* provide(computeMaterializedTreeIntegrity(canonicalPath));
|
|
83
|
+
if (observedTree === reuse.lockedTreeIntegrity) {
|
|
84
|
+
yield* provide(validateAxmSkillCandidate({
|
|
85
|
+
ref,
|
|
86
|
+
packageRoot: canonicalPath,
|
|
87
|
+
skillSourcePath: skillSrcPath,
|
|
88
|
+
}));
|
|
89
|
+
return { skillSrcPath, treeIntegrity: reuse.lockedTreeIntegrity };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const materialized = yield* provideRegistry(materializeRegistryPackageWithTreeIntegrity({
|
|
93
|
+
baseDir,
|
|
94
|
+
destinationPath: canonicalPath,
|
|
95
|
+
sourceLocation: ref.source.location,
|
|
96
|
+
owner: ref.owner,
|
|
97
|
+
type: "skill",
|
|
98
|
+
name: ref.name,
|
|
99
|
+
version: ref.version,
|
|
100
|
+
integrity: ref.integrity,
|
|
101
|
+
messages: {
|
|
102
|
+
integrityMismatchDetail: `Integrity mismatch for ${ref.name}@${ref.version}`,
|
|
103
|
+
},
|
|
104
|
+
validate: (stagingPath) => provide(validateAxmSkillCandidate({
|
|
105
|
+
ref,
|
|
106
|
+
packageRoot: stagingPath,
|
|
107
|
+
skillSourcePath: pathService.join(stagingPath, "src"),
|
|
108
|
+
}).pipe(Effect.asVoid)),
|
|
109
|
+
}));
|
|
110
|
+
return { skillSrcPath, treeIntegrity: materialized.treeIntegrity };
|
|
111
|
+
}));
|
|
112
|
+
const materializeWorkspace = (ref, pathService, baseDir, provide) => Effect.gen(function* () {
|
|
113
|
+
yield* validatePathSafety(pathService, baseDir, ref.location);
|
|
114
|
+
const skillSourcePath = pathService.join(ref.location, "src");
|
|
115
|
+
yield* provide(validateAxmSkillCandidate({
|
|
116
|
+
ref,
|
|
117
|
+
packageRoot: ref.location,
|
|
118
|
+
skillSourcePath,
|
|
119
|
+
}));
|
|
120
|
+
const materialized = { skillSrcPath: skillSourcePath };
|
|
121
|
+
return materialized;
|
|
122
|
+
});
|
|
123
|
+
export const materializeSkillCanonical = (args) => {
|
|
124
|
+
switch (args.ref.refType) {
|
|
125
|
+
case "git-hosted":
|
|
126
|
+
return materializeGitHosted(args.ref, args.sanitizedName, args.fs, args.pathService, args.baseDir, args.layout, args.provide, args.reuse ?? { force: false, lockedVersion: undefined, lockedTreeIntegrity: undefined });
|
|
127
|
+
case "local":
|
|
128
|
+
return materializeLocal(args.ref, args.sanitizedName, args.fs, args.pathService, args.baseDir, args.layout, args.provide, args.reuse ?? { force: false, lockedVersion: undefined, lockedTreeIntegrity: undefined });
|
|
129
|
+
case "registry":
|
|
130
|
+
return materializeRegistry(args.ref, args.sanitizedName, args.fs, args.pathService, args.baseDir, args.layout, args.provide, args.provideRegistry, args.reuse ?? { force: false, lockedVersion: undefined, lockedTreeIntegrity: undefined });
|
|
131
|
+
case "workspace":
|
|
132
|
+
return materializeWorkspace(args.ref, args.pathService, args.baseDir, args.provide);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
export const ensureSkillAgentArtifact = (args) => Effect.gen(function* () {
|
|
136
|
+
const agentSkillPath = args.pathService.join(args.targetDir, args.sanitizedName);
|
|
137
|
+
if (!isPathSafe(args.pathService, args.baseDir, agentSkillPath)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
yield* args.provide(createSymlink({
|
|
141
|
+
target: args.canonicalSkillSrcPath,
|
|
142
|
+
link: agentSkillPath,
|
|
143
|
+
}).pipe(Effect.catch(() => copyExtensionDirectory(args.canonicalSkillSrcPath, agentSkillPath).pipe(
|
|
144
|
+
// If the copy fallback also fails, surface it — otherwise sync
|
|
145
|
+
// reports success with no materialized skill artifact.
|
|
146
|
+
Effect.mapError((cause) => new SkillMaterializationFailed({
|
|
147
|
+
detail: `Failed to materialize skill artifact at ${agentSkillPath}`,
|
|
148
|
+
cause,
|
|
149
|
+
}))))));
|
|
150
|
+
});
|
|
151
|
+
export const removeSkillAgentArtifact = (args) => {
|
|
152
|
+
const target = args.pathService.join(args.targetDir, args.sanitizedName);
|
|
153
|
+
return protectWorkspacePath(target).pipe(Effect.andThen(args.fs.remove(target, { recursive: true, force: true })), Effect.mapError((cause) => new SkillMaterializationFailed({
|
|
154
|
+
detail: `Failed to remove skill artifact at ${target}`,
|
|
155
|
+
cause,
|
|
156
|
+
})));
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=materialization.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disable skill executor — removes agent symlinks but preserves canonical files.
|
|
3
|
+
*
|
|
4
|
+
* Materialized artifacts are observed directly. Accepted-resolution rows identify
|
|
5
|
+
* source content but do not prove that canonical or projected files exist.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
*/
|
|
9
|
+
import * as FileSystem from "effect/FileSystem";
|
|
10
|
+
import * as Path from "effect/Path";
|
|
11
|
+
import { LifecycleFailureAdapter } from "../../failure-adapter.js";
|
|
12
|
+
import type { OperationHandler } from "@agentxm/workspace-operations";
|
|
13
|
+
import type { Operation } from "@agentxm/workspace-operations";
|
|
14
|
+
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
15
|
+
/**
|
|
16
|
+
* Disable a skill (remove files but keep settings/lockfile entry).
|
|
17
|
+
*
|
|
18
|
+
* @experimental This API is unstable and may change without notice.
|
|
19
|
+
*/
|
|
20
|
+
export type DisableSkillOperation = Operation<"disable-skill", {
|
|
21
|
+
readonly skillName: string;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Disable-skill operation handler.
|
|
25
|
+
*
|
|
26
|
+
* Determines lifecycle from the workspace read model, removes observable
|
|
27
|
+
* materialized targets, and promotes implicit pack members to a direct disabled
|
|
28
|
+
* preference. Canonical files are preserved for later re-enablement.
|
|
29
|
+
*/
|
|
30
|
+
export declare const disableSkill: OperationHandler<DisableSkillOperation, FileSystem.FileSystem | Path.Path | WorkspaceMutations | LifecycleFailureAdapter>;
|
|
31
|
+
//# sourceMappingURL=disable.d.ts.map
|