@agentxm/extension-materialization 0.28.14-preview.1789139351.5cd4595aa
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 +16 -0
- package/dist/src/desired-state/errors.d.ts +22 -0
- package/dist/src/desired-state/errors.js +22 -0
- package/dist/src/desired-state/retained-materialization.d.ts +58 -0
- package/dist/src/desired-state/retained-materialization.js +119 -0
- package/dist/src/errors.d.ts +42 -0
- package/dist/src/errors.js +14 -0
- package/dist/src/extensions/canonical-directory.d.ts +106 -0
- package/dist/src/extensions/canonical-directory.js +210 -0
- package/dist/src/extensions/canonical-reuse.d.ts +43 -0
- package/dist/src/extensions/canonical-reuse.js +38 -0
- package/dist/src/extensions/copy-directory.d.ts +50 -0
- package/dist/src/extensions/copy-directory.js +78 -0
- package/dist/src/extensions/errors.d.ts +97 -0
- package/dist/src/extensions/errors.js +42 -0
- package/dist/src/extensions/materializable-from-disk.d.ts +37 -0
- package/dist/src/extensions/materializable-from-disk.js +89 -0
- package/dist/src/extensions/operations.d.ts +252 -0
- package/dist/src/extensions/operations.js +631 -0
- package/dist/src/hooks/errors.d.ts +33 -0
- package/dist/src/hooks/errors.js +18 -0
- package/dist/src/hooks/manager.d.ts +13 -0
- package/dist/src/hooks/manager.js +778 -0
- package/dist/src/index.d.ts +44 -0
- package/dist/src/index.js +56 -0
- package/dist/src/knowledge/errors.d.ts +75 -0
- package/dist/src/knowledge/errors.js +36 -0
- package/dist/src/knowledge/manager.d.ts +9 -0
- package/dist/src/knowledge/manager.js +709 -0
- package/dist/src/live.d.ts +20 -0
- package/dist/src/live.js +20 -0
- package/dist/src/manager-contract.d.ts +136 -0
- package/dist/src/manager-contract.js +19 -0
- package/dist/src/manager-registry-live.d.ts +11 -0
- package/dist/src/manager-registry-live.js +23 -0
- package/dist/src/manager-registry.d.ts +37 -0
- package/dist/src/manager-registry.js +22 -0
- package/dist/src/managers.d.ts +147 -0
- package/dist/src/managers.js +32 -0
- package/dist/src/mcps/artifact.d.ts +24 -0
- package/dist/src/mcps/artifact.js +74 -0
- package/dist/src/mcps/authored-materialization.d.ts +28 -0
- package/dist/src/mcps/authored-materialization.js +34 -0
- package/dist/src/mcps/errors.d.ts +105 -0
- package/dist/src/mcps/errors.js +40 -0
- package/dist/src/mcps/install-operation.d.ts +92 -0
- package/dist/src/mcps/install-operation.js +557 -0
- package/dist/src/mcps/manager.d.ts +15 -0
- package/dist/src/mcps/manager.js +253 -0
- package/dist/src/mcps/native-entry.d.ts +51 -0
- package/dist/src/mcps/native-entry.js +100 -0
- package/dist/src/mcps/secret-store-live.d.ts +14 -0
- package/dist/src/mcps/secret-store-live.js +58 -0
- package/dist/src/mcps/secret-store.d.ts +55 -0
- package/dist/src/mcps/secret-store.js +33 -0
- package/dist/src/packs/errors.d.ts +50 -0
- package/dist/src/packs/errors.js +24 -0
- package/dist/src/packs/manager.d.ts +16 -0
- package/dist/src/packs/manager.js +189 -0
- package/dist/src/projection-participants-live.d.ts +17 -0
- package/dist/src/projection-participants-live.js +66 -0
- package/dist/src/projection-step-failure.d.ts +19 -0
- package/dist/src/projection-step-failure.js +76 -0
- package/dist/src/registry-materialization.d.ts +52 -0
- package/dist/src/registry-materialization.js +67 -0
- package/dist/src/rules/errors.d.ts +31 -0
- package/dist/src/rules/errors.js +17 -0
- package/dist/src/rules/manager.d.ts +18 -0
- package/dist/src/rules/manager.js +492 -0
- package/dist/src/skills/errors.d.ts +42 -0
- package/dist/src/skills/errors.js +21 -0
- package/dist/src/skills/manager.d.ts +17 -0
- package/dist/src/skills/manager.js +339 -0
- package/dist/src/skills/materialization.d.ts +52 -0
- package/dist/src/skills/materialization.js +168 -0
- package/dist/src/skills/skill-artifact.d.ts +38 -0
- package/dist/src/skills/skill-artifact.js +70 -0
- package/dist/src/skills/source-hash.d.ts +2 -0
- package/dist/src/skills/source-hash.js +6 -0
- package/dist/src/subagents/errors.d.ts +43 -0
- package/dist/src/subagents/errors.js +20 -0
- package/dist/src/subagents/lock-entry-builder.d.ts +20 -0
- package/dist/src/subagents/lock-entry-builder.js +63 -0
- package/dist/src/subagents/manager.d.ts +17 -0
- package/dist/src/subagents/manager.js +736 -0
- package/dist/src/testing.d.ts +87 -0
- package/dist/src/testing.js +116 -0
- package/package.json +70 -0
|
@@ -0,0 +1,339 @@
|
|
|
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 { stripFileProtocol } from "@agentxm/registry-client";
|
|
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 "../extensions/materializable-from-disk.js";
|
|
19
|
+
import { enabledConfiguredEntries } from "@agentxm/workspace-state";
|
|
20
|
+
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
21
|
+
import { sanitizeName } from "@agentxm/workspace-state";
|
|
22
|
+
import { computePackageContentHash } from "@agentxm/workspace-state";
|
|
23
|
+
import { makeWorkspaceRelativeSourcePath } from "@agentxm/extension-model/unstable/path-types";
|
|
24
|
+
import { removeIfExists } from "@agentxm/workspace-state";
|
|
25
|
+
import { SkillManager } from "../managers.js";
|
|
26
|
+
import { SkillDefinitionInvalid, SkillInstallStateMissing } from "./errors.js";
|
|
27
|
+
import { CodingAgentRepository, applyProjectionPlans, planSingletonProjection, } from "@agentxm/workspace-projection";
|
|
28
|
+
import {} from "@agentxm/extension-model/unstable/agents/types";
|
|
29
|
+
import { acceptedRegistryVersionForRef, validateExactResolvedVersion, } from "@agentxm/workspace-state";
|
|
30
|
+
import { computeSkillSourceHash } from "./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 path = yield* Path.Path;
|
|
53
|
+
const agentRepo = yield* CodingAgentRepository;
|
|
54
|
+
const baseDir = ws.baseDir;
|
|
55
|
+
const materializeInstall = Effect.fn("SkillManager.materializeInstall")(function* ({ ref, force, }) {
|
|
56
|
+
const sanitized = sanitizeName(ref.skill.name);
|
|
57
|
+
const lockedEntry = yield* ws.getLockedSkill(ref.skill.name);
|
|
58
|
+
const lockedVersion = ref.refType === "registry" ? acceptedRegistryVersionForRef(lockedEntry, ref) : undefined;
|
|
59
|
+
const materialized = yield* materializeSkillCanonical({
|
|
60
|
+
ref,
|
|
61
|
+
sanitizedName: sanitized,
|
|
62
|
+
baseDir,
|
|
63
|
+
layout: ws.layout,
|
|
64
|
+
reuse: {
|
|
65
|
+
force: force === true,
|
|
66
|
+
lockedVersion,
|
|
67
|
+
lockedTreeIntegrity: Option.isSome(lockedEntry)
|
|
68
|
+
? lockedEntry.value.treeIntegrity
|
|
69
|
+
: undefined,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
const skillSrcPath = materialized.skillSrcPath;
|
|
73
|
+
const configuredAgents = yield* agentRepo
|
|
74
|
+
.getMaterializationAgents()
|
|
75
|
+
.pipe(Effect.provideService(WorkspaceMutations, ws));
|
|
76
|
+
const resolved = yield* Effect.forEach(configuredAgents, (agent) => agent
|
|
77
|
+
.resolveEffectiveSkillsDir({ workspaceRoot: baseDir })
|
|
78
|
+
.pipe(Effect.map((outcome) => ({ agent, outcome }))), { concurrency: "unbounded" });
|
|
79
|
+
const misconfigured = Array.filter(resolved, ({ outcome }) => outcome._tag === "misconfigured");
|
|
80
|
+
if (misconfigured.length > 0) {
|
|
81
|
+
return yield* new SkillDefinitionInvalid({
|
|
82
|
+
detail: "One or more configured agents have invalid skills directory settings",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
const installTargets = [];
|
|
86
|
+
for (const { agent, outcome } of resolved) {
|
|
87
|
+
if (outcome._tag === "supported") {
|
|
88
|
+
installTargets.push({ agentId: agent.id, dir: path.normalize(outcome.dir) });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const locations = new Map();
|
|
92
|
+
for (const target of installTargets) {
|
|
93
|
+
const existing = locations.get(target.dir);
|
|
94
|
+
if (existing === undefined) {
|
|
95
|
+
locations.set(target.dir, { dir: target.dir, agentIds: [target.agentId] });
|
|
96
|
+
}
|
|
97
|
+
else if (!existing.agentIds.includes(target.agentId)) {
|
|
98
|
+
existing.agentIds.push(target.agentId);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
yield* applyProjectionPlans([...locations.values()].map((location) => {
|
|
102
|
+
const targetFile = path.join(location.dir, sanitized);
|
|
103
|
+
return planSingletonProjection({
|
|
104
|
+
unitId: "skill:agent-skill-directory",
|
|
105
|
+
targetFile,
|
|
106
|
+
contributor: ref,
|
|
107
|
+
adapter: {
|
|
108
|
+
observe: () => Effect.succeed({
|
|
109
|
+
unitId: "skill:agent-skill-directory",
|
|
110
|
+
path: path.relative(baseDir, targetFile),
|
|
111
|
+
present: false,
|
|
112
|
+
current: false,
|
|
113
|
+
expectedContributors: [ref.skill.name],
|
|
114
|
+
observedContributors: [],
|
|
115
|
+
}),
|
|
116
|
+
apply: () => ensureSkillAgentArtifact({
|
|
117
|
+
canonicalSkillSrcPath: skillSrcPath,
|
|
118
|
+
targetDir: location.dir,
|
|
119
|
+
sanitizedName: sanitized,
|
|
120
|
+
baseDir,
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}));
|
|
125
|
+
const sourceHash = ref.refType === "workspace"
|
|
126
|
+
? ref.sourceHash
|
|
127
|
+
: ref.refType === "registry"
|
|
128
|
+
? yield* computePackageContentHash(path.dirname(skillSrcPath))
|
|
129
|
+
: yield* computeSkillSourceHash(skillSrcPath);
|
|
130
|
+
if (ref.refType !== "workspace" && materialized.treeIntegrity === undefined) {
|
|
131
|
+
return yield* new SkillInstallStateMissing({
|
|
132
|
+
name: ref.skill.name,
|
|
133
|
+
kind: "tree-integrity",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
sourceHash: Option.some(sourceHash),
|
|
138
|
+
treeIntegrity: Option.fromUndefinedOr(ref.refType === "workspace" ? undefined : materialized.treeIntegrity),
|
|
139
|
+
observation: {
|
|
140
|
+
agents: Array.dedupe(installTargets
|
|
141
|
+
.map((target) => target.agentId)
|
|
142
|
+
.filter((agentId) => agentId !== "universal")),
|
|
143
|
+
targets: [...locations.values()].map((location) => {
|
|
144
|
+
const agentIds = location.agentIds.filter((agentId) => agentId !== "universal");
|
|
145
|
+
return {
|
|
146
|
+
path: path.relative(baseDir, path.join(location.dir, sanitized)),
|
|
147
|
+
...(agentIds.length === 0 ? {} : { agentIds }),
|
|
148
|
+
};
|
|
149
|
+
}),
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
const makeMaterializeRemoval = (retainCanonical) => Effect.fn("SkillManager.materializeRemoval")(function* ({ target }) {
|
|
154
|
+
const sanitized = sanitizeName(target.name);
|
|
155
|
+
const configuredAgents = yield* agentRepo
|
|
156
|
+
.getMaterializationAgents()
|
|
157
|
+
.pipe(Effect.provideService(WorkspaceMutations, ws));
|
|
158
|
+
const resolved = yield* Effect.forEach(configuredAgents, (agent) => agent
|
|
159
|
+
.resolveEffectiveSkillsDir({ workspaceRoot: baseDir })
|
|
160
|
+
.pipe(Effect.map((outcome) => ({ agent, outcome }))), { concurrency: "unbounded" });
|
|
161
|
+
const uninstallTargets = [];
|
|
162
|
+
for (const { outcome } of resolved) {
|
|
163
|
+
if (outcome._tag === "supported") {
|
|
164
|
+
uninstallTargets.push(path.normalize(outcome.dir));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const distinctDirs = Array.dedupe(uninstallTargets);
|
|
168
|
+
yield* applyProjectionPlans(distinctDirs.map((dir) => {
|
|
169
|
+
const targetFile = path.join(dir, sanitized);
|
|
170
|
+
return planSingletonProjection({
|
|
171
|
+
unitId: "skill:agent-skill-directory",
|
|
172
|
+
targetFile,
|
|
173
|
+
contributor: target,
|
|
174
|
+
adapter: {
|
|
175
|
+
observe: () => Effect.succeed({
|
|
176
|
+
unitId: "skill:agent-skill-directory",
|
|
177
|
+
path: path.relative(baseDir, targetFile),
|
|
178
|
+
present: true,
|
|
179
|
+
current: false,
|
|
180
|
+
expectedContributors: [],
|
|
181
|
+
observedContributors: [target.name],
|
|
182
|
+
}),
|
|
183
|
+
apply: () => removeSkillAgentArtifact({
|
|
184
|
+
targetDir: dir,
|
|
185
|
+
sanitizedName: sanitized,
|
|
186
|
+
}),
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
}));
|
|
190
|
+
if (!retainCanonical) {
|
|
191
|
+
const canonical = yield* acceptedCanonicalObservation({
|
|
192
|
+
workspace: ws,
|
|
193
|
+
type: "skill",
|
|
194
|
+
name: target.name,
|
|
195
|
+
});
|
|
196
|
+
const packageRoot = removableAcceptedCanonicalPath(canonical);
|
|
197
|
+
if (Option.isSome(packageRoot))
|
|
198
|
+
yield* removeIfExists(fs, packageRoot.value);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
sourceHash: Option.none(),
|
|
202
|
+
treeIntegrity: Option.none(),
|
|
203
|
+
observation: {
|
|
204
|
+
agents: [],
|
|
205
|
+
targets: distinctDirs.map((dir) => ({
|
|
206
|
+
path: path.relative(baseDir, path.join(dir, sanitized)),
|
|
207
|
+
})),
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
const materializeUninstall = makeMaterializeRemoval(false);
|
|
212
|
+
const materializeDeactivate = makeMaterializeRemoval(true);
|
|
213
|
+
return {
|
|
214
|
+
type: "skill",
|
|
215
|
+
isInstalled: Effect.fn("SkillManager.isInstalled")(function* ({ target, }) {
|
|
216
|
+
return yield* isObservedInstalled(ws, "skill", target.name);
|
|
217
|
+
}),
|
|
218
|
+
materializeInstall,
|
|
219
|
+
prepareSourceTransition: ({ ref }) => prepareAcceptedCanonicalTransition({
|
|
220
|
+
workspace: ws,
|
|
221
|
+
type: "skill",
|
|
222
|
+
name: ref.skill.name,
|
|
223
|
+
ref,
|
|
224
|
+
}),
|
|
225
|
+
getConfiguredSource: Effect.fn("SkillManager.getConfiguredSource")(function* ({ target }) {
|
|
226
|
+
const configured = yield* ws.getConfiguredSkillEntries();
|
|
227
|
+
const entry = configured[target.name];
|
|
228
|
+
if (entry?.origin === "bundled") {
|
|
229
|
+
return Option.some(`bundled:@agentxm/skills/${target.name}`);
|
|
230
|
+
}
|
|
231
|
+
return Option.fromUndefinedOr(entry?.source);
|
|
232
|
+
}),
|
|
233
|
+
listMaterializable: Effect.fn("SkillManager.listMaterializable")(function* () {
|
|
234
|
+
const configured = yield* ws.records
|
|
235
|
+
.rows("skill")
|
|
236
|
+
.pipe(Effect.map(configuredRowsByName));
|
|
237
|
+
const configuredEntries = yield* ws.getConfiguredSkillEntries();
|
|
238
|
+
const configuredWithoutBundled = Object.fromEntries(Object.entries(configured).filter(([name]) => configuredEntries[name]?.origin !== "bundled"));
|
|
239
|
+
const workspaceRefs = yield* configuredSkillsToDiskRefs({ fs, path, baseDir, scope: ws.scope, layout: ws.layout }, configuredWithoutBundled);
|
|
240
|
+
const trustedRefs = yield* Effect.forEach(enabledConfiguredEntries(configured), ([name]) => configuredEntries[name]?.origin === "bundled"
|
|
241
|
+
? Effect.succeed(Option.none())
|
|
242
|
+
: usableAcceptedCanonicalRef({ workspace: ws, type: "skill", name }).pipe(Effect.map(Option.filter((ref) => ref.type === "skill"))), { concurrency: "unbounded" });
|
|
243
|
+
const refsByName = new Map(workspaceRefs.map((ref) => [ref.skill.name, ref]));
|
|
244
|
+
for (const ref of trustedRefs) {
|
|
245
|
+
if (Option.isSome(ref))
|
|
246
|
+
refsByName.set(ref.value.skill.name, ref.value);
|
|
247
|
+
}
|
|
248
|
+
return [...refsByName.values()];
|
|
249
|
+
}),
|
|
250
|
+
materializeUninstall,
|
|
251
|
+
materializeDeactivate,
|
|
252
|
+
upsertSettingsEntry: Effect.fn("SkillManager.upsertSettingsEntry")(function* ({ ref, versionRange, materialization, }) {
|
|
253
|
+
const workspaceRelativeLocalSourcePath = ref.refType === "local"
|
|
254
|
+
? makeWorkspaceRelativeSourcePath(path, baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
|
|
255
|
+
: Option.none();
|
|
256
|
+
if (ref.refType === "local" && Option.isNone(workspaceRelativeLocalSourcePath)) {
|
|
257
|
+
return yield* new SkillDefinitionInvalid({
|
|
258
|
+
detail: `Local skill source path must stay within the workspace root: ${ref.source.path}`,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const sourceHash = Option.getOrUndefined(materialization.pipe(Option.flatMap((facts) => facts.sourceHash)));
|
|
262
|
+
const treeIntegrity = Option.getOrUndefined(materialization.pipe(Option.flatMap((facts) => facts.treeIntegrity)));
|
|
263
|
+
if (ref.refType === "workspace") {
|
|
264
|
+
return yield* ws.setSkillEntry(ref.skill.name, {
|
|
265
|
+
source: "workspace",
|
|
266
|
+
enabled: true,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (sourceHash === undefined || treeIntegrity === undefined) {
|
|
270
|
+
return yield* new SkillInstallStateMissing({
|
|
271
|
+
name: ref.skill.name,
|
|
272
|
+
kind: "content-identity",
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
const lockEntry = buildSkillLockEntry(ref, workspaceRelativeLocalSourcePath, sourceHash, treeIntegrity);
|
|
276
|
+
if (lockEntry === undefined) {
|
|
277
|
+
return yield* new SkillInstallStateMissing({
|
|
278
|
+
name: ref.skill.name,
|
|
279
|
+
kind: "external-resolution",
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
if (lockEntry.type === "registry") {
|
|
283
|
+
yield* validateExactResolvedVersion(`skills.${ref.skill.name}.resolvedVersion`, lockEntry.resolvedVersion);
|
|
284
|
+
}
|
|
285
|
+
return yield* ws.setSkill({
|
|
286
|
+
name: ref.skill.name,
|
|
287
|
+
lockEntry,
|
|
288
|
+
versionRange,
|
|
289
|
+
});
|
|
290
|
+
}),
|
|
291
|
+
removeSettingsEntry: ({ target }) => ws
|
|
292
|
+
.removeSkillFromSettings(target.name)
|
|
293
|
+
.pipe(Effect.withSpan("SkillManager.removeSettingsEntry")),
|
|
294
|
+
upsertLockfileEntry: Effect.fn("SkillManager.upsertLockfileEntry")(function* ({ ref, materialization, }) {
|
|
295
|
+
const workspaceRelativeLocalSourcePath = ref.refType === "local"
|
|
296
|
+
? makeWorkspaceRelativeSourcePath(path, baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
|
|
297
|
+
: Option.none();
|
|
298
|
+
if (ref.refType === "local" && Option.isNone(workspaceRelativeLocalSourcePath)) {
|
|
299
|
+
return yield* new SkillDefinitionInvalid({
|
|
300
|
+
detail: `Local skill source path must stay within the workspace root: ${ref.source.path}`,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (ref.refType === "workspace") {
|
|
304
|
+
yield* ws.removeSkillLock(ref.skill.name);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const sourceHash = Option.getOrUndefined(materialization.pipe(Option.flatMap((facts) => facts.sourceHash)));
|
|
308
|
+
const treeIntegrity = Option.getOrUndefined(materialization.pipe(Option.flatMap((facts) => facts.treeIntegrity)));
|
|
309
|
+
if (sourceHash === undefined || treeIntegrity === undefined) {
|
|
310
|
+
return yield* new SkillInstallStateMissing({
|
|
311
|
+
name: ref.skill.name,
|
|
312
|
+
kind: "content-identity",
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const lockEntry = buildSkillLockEntry(ref, workspaceRelativeLocalSourcePath, sourceHash, treeIntegrity);
|
|
316
|
+
if (lockEntry === undefined) {
|
|
317
|
+
return yield* new SkillInstallStateMissing({
|
|
318
|
+
name: ref.skill.name,
|
|
319
|
+
kind: "external-resolution",
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
if (lockEntry.type === "registry") {
|
|
323
|
+
yield* validateExactResolvedVersion(`skills.${ref.skill.name}.resolvedVersion`, lockEntry.resolvedVersion);
|
|
324
|
+
}
|
|
325
|
+
return yield* ws.setSkillLock({
|
|
326
|
+
name: ref.skill.name,
|
|
327
|
+
lockEntry,
|
|
328
|
+
versionRange: Option.none(),
|
|
329
|
+
});
|
|
330
|
+
}),
|
|
331
|
+
removeLockfileEntry: ({ target }) => ws
|
|
332
|
+
.removeSkillLock(target.name)
|
|
333
|
+
.pipe(Effect.withSpan("SkillManager.removeLockfileEntry")),
|
|
334
|
+
};
|
|
335
|
+
}));
|
|
336
|
+
// -----------------------------------------------------------------------------
|
|
337
|
+
// Internal materialization helpers
|
|
338
|
+
// -----------------------------------------------------------------------------
|
|
339
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical skill materialization per source kind, plus the per-agent artifact
|
|
3
|
+
* writers the skill projections apply.
|
|
4
|
+
*
|
|
5
|
+
* Every function keeps the platform in `R`; nothing captures a `FileSystem` or
|
|
6
|
+
* `Path` into a closure or provides one at a leaf.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
import * as FileSystem from "effect/FileSystem";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import { SkillMaterializationFailed } from "./errors.js";
|
|
14
|
+
import { type TreeIntegrity } from "@agentxm/workspace-state";
|
|
15
|
+
import type { SkillExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/skill";
|
|
16
|
+
import type { WorkspaceLayout } from "@agentxm/workspace-state";
|
|
17
|
+
/** Reuse inputs sourced from the caller's operation context and lockfile. */
|
|
18
|
+
export type CanonicalReuseContext = {
|
|
19
|
+
readonly force: boolean;
|
|
20
|
+
readonly lockedVersion: string | undefined;
|
|
21
|
+
readonly lockedTreeIntegrity: TreeIntegrity | undefined;
|
|
22
|
+
/** Where the already-on-disk package to copy lives. */
|
|
23
|
+
readonly sourcePath: string;
|
|
24
|
+
};
|
|
25
|
+
export interface MaterializedSkillCanonical {
|
|
26
|
+
readonly skillSrcPath: string;
|
|
27
|
+
readonly treeIntegrity?: TreeIntegrity;
|
|
28
|
+
}
|
|
29
|
+
export declare const materializeSkillCanonical: (args: {
|
|
30
|
+
readonly ref: SkillExtensionRef;
|
|
31
|
+
readonly sanitizedName: string;
|
|
32
|
+
readonly baseDir: string;
|
|
33
|
+
readonly layout: WorkspaceLayout;
|
|
34
|
+
readonly reuse?: Omit<CanonicalReuseContext, "sourcePath">;
|
|
35
|
+
}) => Effect.Effect<{
|
|
36
|
+
skillSrcPath: string & import("effect/Brand").Brand<"AbsolutePath">;
|
|
37
|
+
treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
|
|
38
|
+
}, import("../index.ts").CanonicalPackageProbeFailed | import("../index.ts").PackageCopyFailed | import("@agentxm/extension-resolution").AxmSkillCompatibilityUnavailable | import("@agentxm/extension-resolution").AxmSkillIncompatible | import("@agentxm/workspace-state").MaterializedTreeInvalid | import("../extensions/canonical-directory.js").CanonicalDirectoryReplacementError, FileSystem.FileSystem | Path.Path> | Effect.Effect<{
|
|
39
|
+
skillSrcPath: string & import("effect/Brand").Brand<"AbsolutePath">;
|
|
40
|
+
treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
|
|
41
|
+
}, import("../index.ts").CanonicalPackageProbeFailed | import("../index.ts").ArchiveIntegrityMismatch | import("@agentxm/extension-resolution").AxmSkillCompatibilityUnavailable | import("@agentxm/extension-resolution").AxmSkillIncompatible | import("@agentxm/workspace-state").MaterializedTreeInvalid | import("@agentxm/registry-client").RegistryClientFailure | import("../extensions/canonical-directory.js").CanonicalDirectoryReplacementError, FileSystem.FileSystem | Path.Path | import("effect/unstable/http/HttpClient").HttpClient> | Effect.Effect<MaterializedSkillCanonical, import("@agentxm/workspace-state").PathTraversalDetected | import("@agentxm/extension-resolution").AxmSkillCompatibilityUnavailable | import("@agentxm/extension-resolution").AxmSkillIncompatible, FileSystem.FileSystem | Path.Path>;
|
|
42
|
+
export declare const ensureSkillAgentArtifact: (args: {
|
|
43
|
+
readonly canonicalSkillSrcPath: string;
|
|
44
|
+
readonly targetDir: string;
|
|
45
|
+
readonly sanitizedName: string;
|
|
46
|
+
readonly baseDir: string;
|
|
47
|
+
}) => Effect.Effect<void, SkillMaterializationFailed, FileSystem.FileSystem | Path.Path>;
|
|
48
|
+
export declare const removeSkillAgentArtifact: (args: {
|
|
49
|
+
readonly targetDir: string;
|
|
50
|
+
readonly sanitizedName: string;
|
|
51
|
+
}) => Effect.Effect<void, SkillMaterializationFailed, FileSystem.FileSystem | Path.Path>;
|
|
52
|
+
//# sourceMappingURL=materialization.d.ts.map
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical skill materialization per source kind, plus the per-agent artifact
|
|
3
|
+
* writers the skill projections apply.
|
|
4
|
+
*
|
|
5
|
+
* Every function keeps the platform in `R`; nothing captures a `FileSystem` or
|
|
6
|
+
* `Path` into a closure or provides one at a leaf.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
*/
|
|
10
|
+
import * as FileSystem from "effect/FileSystem";
|
|
11
|
+
import { stripFileProtocol } from "@agentxm/registry-client";
|
|
12
|
+
import * as Path from "effect/Path";
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import * as Option from "effect/Option";
|
|
15
|
+
import { SkillMaterializationFailed } from "./errors.js";
|
|
16
|
+
import { canReuseExternalPackage, canReuseInstalledPackage, materializeExternalPackageWithTreeIntegrity, } from "../extensions/canonical-directory.js";
|
|
17
|
+
import { materializeRegistryPackageWithTreeIntegrity } from "../registry-materialization.js";
|
|
18
|
+
import { validatePathSafety } from "@agentxm/workspace-state";
|
|
19
|
+
import { computeMaterializedTreeIntegrity } from "@agentxm/workspace-state";
|
|
20
|
+
import { copyExtensionDirectory } from "../extensions/copy-directory.js";
|
|
21
|
+
import { computeSkillPathsForLayout } from "@agentxm/workspace-state";
|
|
22
|
+
import { isPathSafe } from "@agentxm/extension-model/unstable/path-types";
|
|
23
|
+
import { createSymlink } from "@agentxm/workspace-state";
|
|
24
|
+
import { protectWorkspacePath } from "@agentxm/workspace-transactions";
|
|
25
|
+
import { validateAxmSkillCandidate } from "@agentxm/extension-resolution";
|
|
26
|
+
const replaceExternalCanonical = (baseDir, copyTarget, reuse) => Effect.gen(function* () {
|
|
27
|
+
const useExisting = yield* canReuseExternalPackage({
|
|
28
|
+
installedPath: copyTarget,
|
|
29
|
+
force: reuse.force,
|
|
30
|
+
existsFailureDetail: (target) => `Failed to check if canonical path exists: ${target}`,
|
|
31
|
+
});
|
|
32
|
+
if (useExisting && reuse.lockedTreeIntegrity !== undefined) {
|
|
33
|
+
const observedTree = yield* computeMaterializedTreeIntegrity(copyTarget);
|
|
34
|
+
if (observedTree === reuse.lockedTreeIntegrity)
|
|
35
|
+
return reuse.lockedTreeIntegrity;
|
|
36
|
+
}
|
|
37
|
+
const materialized = yield* materializeExternalPackageWithTreeIntegrity({
|
|
38
|
+
baseDir,
|
|
39
|
+
canonicalPath: copyTarget,
|
|
40
|
+
sourceLocation: reuse.sourcePath,
|
|
41
|
+
copyFailureCode: "internal",
|
|
42
|
+
copyFailureDetail: (target) => `Failed to copy skill files to ${target}`,
|
|
43
|
+
});
|
|
44
|
+
return materialized.treeIntegrity;
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Git-hosted and local skills share one shape: the package is already on disk,
|
|
48
|
+
* so the canonical tree is either the same directory or a copy of it.
|
|
49
|
+
*/
|
|
50
|
+
const materializeFromDisk = (ref, sanitizedName, baseDir, layout, reuse) => Effect.gen(function* () {
|
|
51
|
+
const pathService = yield* Path.Path;
|
|
52
|
+
const { canonicalPath, skillSrcPath } = computeSkillPathsForLayout(pathService.join, layout, ref, sanitizedName);
|
|
53
|
+
yield* validatePathSafety(pathService, baseDir, canonicalPath);
|
|
54
|
+
const packageRoot = stripFileProtocol(ref.location);
|
|
55
|
+
const sourceSkillPath = ref.portable === true ? packageRoot : pathService.join(packageRoot, "src");
|
|
56
|
+
yield* validateAxmSkillCandidate({
|
|
57
|
+
ref,
|
|
58
|
+
packageRoot,
|
|
59
|
+
skillSourcePath: sourceSkillPath,
|
|
60
|
+
});
|
|
61
|
+
const isSelfCopy = pathService.resolve(packageRoot) === pathService.resolve(canonicalPath);
|
|
62
|
+
const treeIntegrity = isSelfCopy
|
|
63
|
+
? yield* computeMaterializedTreeIntegrity(canonicalPath)
|
|
64
|
+
: yield* replaceExternalCanonical(baseDir, canonicalPath, {
|
|
65
|
+
...reuse,
|
|
66
|
+
sourcePath: packageRoot,
|
|
67
|
+
});
|
|
68
|
+
return { skillSrcPath, treeIntegrity };
|
|
69
|
+
});
|
|
70
|
+
const materializeRegistry = (ref, sanitizedName, baseDir, layout, reuse) => Effect.scoped(Effect.gen(function* () {
|
|
71
|
+
const pathService = yield* Path.Path;
|
|
72
|
+
const source = ref;
|
|
73
|
+
const { canonicalPath, skillSrcPath } = computeSkillPathsForLayout(pathService.join, layout, source, sanitizedName);
|
|
74
|
+
yield* validatePathSafety(pathService, baseDir, canonicalPath);
|
|
75
|
+
const useExisting = yield* canReuseInstalledPackage({
|
|
76
|
+
installedPath: canonicalPath,
|
|
77
|
+
force: reuse.force,
|
|
78
|
+
refVersion: ref.version,
|
|
79
|
+
hasIntegrity: Option.isSome(ref.integrity),
|
|
80
|
+
...(reuse.lockedVersion === undefined ? {} : { lockedVersion: reuse.lockedVersion }),
|
|
81
|
+
existsFailureDetail: (target) => `Failed to check if canonical path exists: ${target}`,
|
|
82
|
+
});
|
|
83
|
+
if (useExisting && reuse.lockedTreeIntegrity !== undefined) {
|
|
84
|
+
const observedTree = yield* computeMaterializedTreeIntegrity(canonicalPath);
|
|
85
|
+
if (observedTree === reuse.lockedTreeIntegrity) {
|
|
86
|
+
yield* validateAxmSkillCandidate({
|
|
87
|
+
ref,
|
|
88
|
+
packageRoot: canonicalPath,
|
|
89
|
+
skillSourcePath: skillSrcPath,
|
|
90
|
+
});
|
|
91
|
+
return { skillSrcPath, treeIntegrity: reuse.lockedTreeIntegrity };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const materialized = yield* materializeRegistryPackageWithTreeIntegrity({
|
|
95
|
+
baseDir,
|
|
96
|
+
destinationPath: canonicalPath,
|
|
97
|
+
sourceLocation: ref.source.location,
|
|
98
|
+
owner: ref.owner,
|
|
99
|
+
type: "skill",
|
|
100
|
+
name: ref.name,
|
|
101
|
+
version: ref.version,
|
|
102
|
+
integrity: ref.integrity,
|
|
103
|
+
messages: {
|
|
104
|
+
integrityMismatchDetail: `Integrity mismatch for ${ref.name}@${ref.version}`,
|
|
105
|
+
},
|
|
106
|
+
validate: (stagingPath) => validateAxmSkillCandidate({
|
|
107
|
+
ref,
|
|
108
|
+
packageRoot: stagingPath,
|
|
109
|
+
skillSourcePath: pathService.join(stagingPath, "src"),
|
|
110
|
+
}).pipe(Effect.asVoid),
|
|
111
|
+
});
|
|
112
|
+
return { skillSrcPath, treeIntegrity: materialized.treeIntegrity };
|
|
113
|
+
}));
|
|
114
|
+
const materializeWorkspace = (ref, baseDir) => Effect.gen(function* () {
|
|
115
|
+
const pathService = yield* Path.Path;
|
|
116
|
+
yield* validatePathSafety(pathService, baseDir, ref.location);
|
|
117
|
+
const skillSourcePath = pathService.join(ref.location, "src");
|
|
118
|
+
yield* validateAxmSkillCandidate({
|
|
119
|
+
ref,
|
|
120
|
+
packageRoot: ref.location,
|
|
121
|
+
skillSourcePath,
|
|
122
|
+
});
|
|
123
|
+
const materialized = { skillSrcPath: skillSourcePath };
|
|
124
|
+
return materialized;
|
|
125
|
+
});
|
|
126
|
+
const defaultReuse = {
|
|
127
|
+
force: false,
|
|
128
|
+
lockedVersion: undefined,
|
|
129
|
+
lockedTreeIntegrity: undefined,
|
|
130
|
+
};
|
|
131
|
+
export const materializeSkillCanonical = (args) => {
|
|
132
|
+
switch (args.ref.refType) {
|
|
133
|
+
case "git-hosted":
|
|
134
|
+
case "local":
|
|
135
|
+
return materializeFromDisk(args.ref, args.sanitizedName, args.baseDir, args.layout, args.reuse ?? defaultReuse);
|
|
136
|
+
case "registry":
|
|
137
|
+
return materializeRegistry(args.ref, args.sanitizedName, args.baseDir, args.layout, args.reuse ?? defaultReuse);
|
|
138
|
+
case "workspace":
|
|
139
|
+
return materializeWorkspace(args.ref, args.baseDir);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
export const ensureSkillAgentArtifact = (args) => Effect.gen(function* () {
|
|
143
|
+
const pathService = yield* Path.Path;
|
|
144
|
+
const agentSkillPath = pathService.join(args.targetDir, args.sanitizedName);
|
|
145
|
+
if (!isPathSafe(pathService, args.baseDir, agentSkillPath)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
yield* createSymlink({
|
|
149
|
+
target: args.canonicalSkillSrcPath,
|
|
150
|
+
link: agentSkillPath,
|
|
151
|
+
}).pipe(Effect.catch(() => copyExtensionDirectory(args.canonicalSkillSrcPath, agentSkillPath).pipe(
|
|
152
|
+
// If the copy fallback also fails, surface it — otherwise sync
|
|
153
|
+
// reports success with no materialized skill artifact.
|
|
154
|
+
Effect.mapError((cause) => new SkillMaterializationFailed({
|
|
155
|
+
detail: `Failed to materialize skill artifact at ${agentSkillPath}`,
|
|
156
|
+
cause,
|
|
157
|
+
})))));
|
|
158
|
+
});
|
|
159
|
+
export const removeSkillAgentArtifact = (args) => Effect.gen(function* () {
|
|
160
|
+
const fs = yield* FileSystem.FileSystem;
|
|
161
|
+
const pathService = yield* Path.Path;
|
|
162
|
+
const target = pathService.join(args.targetDir, args.sanitizedName);
|
|
163
|
+
return yield* protectWorkspacePath(target).pipe(Effect.andThen(fs.remove(target, { recursive: true, force: true })), Effect.mapError((cause) => new SkillMaterializationFailed({
|
|
164
|
+
detail: `Failed to remove skill artifact at ${target}`,
|
|
165
|
+
cause,
|
|
166
|
+
})));
|
|
167
|
+
});
|
|
168
|
+
//# sourceMappingURL=materialization.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill artifact and install-target semantics shared by the lifecycle and
|
|
3
|
+
* sync features: agent-target grouping, universal-directory folding, and the
|
|
4
|
+
* step-artifact shape describing where a skill materializes.
|
|
5
|
+
*
|
|
6
|
+
* @experimental This API is unstable and may change without notice.
|
|
7
|
+
*/
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as FileSystem from "effect/FileSystem";
|
|
10
|
+
import * as Path from "effect/Path";
|
|
11
|
+
import type { AgentId } from "@agentxm/extension-model/unstable/agents/types";
|
|
12
|
+
import type { JobStepArtifact, JobStepArtifactTarget } from "@agentxm/workspace-operations";
|
|
13
|
+
export type InstallableSkillTarget = {
|
|
14
|
+
readonly agentId: AgentId;
|
|
15
|
+
readonly targetDir: string;
|
|
16
|
+
};
|
|
17
|
+
export type InstallableSkillTargetLocation = {
|
|
18
|
+
readonly targetDir: string;
|
|
19
|
+
readonly agentIds: ReadonlyArray<AgentId>;
|
|
20
|
+
};
|
|
21
|
+
export declare const artifactAgentIdsFromTargets: (targets: ReadonlyArray<InstallableSkillTarget>) => ReadonlyArray<string>;
|
|
22
|
+
export declare const artifactTargetAgentIds: (agentIds: ReadonlyArray<AgentId>) => ReadonlyArray<string>;
|
|
23
|
+
export declare const groupInstallTargetsByDirectory: (targets: ReadonlyArray<InstallableSkillTarget>, workspaceRoot: string) => Effect.Effect<ReadonlyArray<InstallableSkillTargetLocation>, never, FileSystem.FileSystem | Path.Path>;
|
|
24
|
+
export declare const skillArtifactFromTargets: (args: {
|
|
25
|
+
readonly targets: ReadonlyArray<InstallableSkillTarget>;
|
|
26
|
+
readonly workspaceRoot: string;
|
|
27
|
+
readonly sanitizedName: string;
|
|
28
|
+
readonly scope: JobStepArtifact["scope"];
|
|
29
|
+
readonly change: JobStepArtifact["change"];
|
|
30
|
+
readonly workspaceTargets?: ReadonlyArray<JobStepArtifactTarget>;
|
|
31
|
+
}) => Effect.Effect<{
|
|
32
|
+
targets?: JobStepArtifactTarget[];
|
|
33
|
+
path: string;
|
|
34
|
+
scope: "project" | "user";
|
|
35
|
+
agents: readonly string[];
|
|
36
|
+
change: "created" | "updated" | "removed" | "unchanged";
|
|
37
|
+
}, never, FileSystem.FileSystem | Path.Path>;
|
|
38
|
+
//# sourceMappingURL=skill-artifact.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill artifact and install-target semantics shared by the lifecycle and
|
|
3
|
+
* sync features: agent-target grouping, universal-directory folding, and the
|
|
4
|
+
* step-artifact shape describing where a skill materializes.
|
|
5
|
+
*
|
|
6
|
+
* @experimental This API is unstable and may change without notice.
|
|
7
|
+
*/
|
|
8
|
+
import * as Array from "effect/Array";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as FileSystem from "effect/FileSystem";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import { UNIVERSAL_SKILLS_DIR, isUniversalSkillsDir, stripTrailingSeparators, } from "@agentxm/extension-model/unstable/extensions/universal-skills-dir";
|
|
13
|
+
const UNIVERSAL_AGENT_ID = "universal";
|
|
14
|
+
export const artifactAgentIdsFromTargets = (targets) => Array.dedupe(targets.map((target) => target.agentId).filter((agentId) => agentId !== UNIVERSAL_AGENT_ID));
|
|
15
|
+
export const artifactTargetAgentIds = (agentIds) => agentIds.filter((agentId) => agentId !== UNIVERSAL_AGENT_ID);
|
|
16
|
+
const normalizedTargetDir = (path, targetDir) => stripTrailingSeparators(path.normalize(targetDir));
|
|
17
|
+
const targetLocationKey = (targetDir, workspaceRoot) => Effect.gen(function* () {
|
|
18
|
+
const fs = yield* FileSystem.FileSystem;
|
|
19
|
+
const path = yield* Path.Path;
|
|
20
|
+
const normalizedDir = normalizedTargetDir(path, targetDir);
|
|
21
|
+
const normalizedWorkspaceRoot = normalizedTargetDir(path, workspaceRoot);
|
|
22
|
+
const realWorkspaceRoot = yield* fs.realPath(workspaceRoot).pipe(Effect.map((realPath) => normalizedTargetDir(path, realPath)), Effect.catch(() => Effect.succeed(normalizedWorkspaceRoot)));
|
|
23
|
+
if (isUniversalSkillsDir(normalizedDir, normalizedWorkspaceRoot) ||
|
|
24
|
+
isUniversalSkillsDir(normalizedDir, realWorkspaceRoot)) {
|
|
25
|
+
return normalizedTargetDir(path, path.join(realWorkspaceRoot, UNIVERSAL_SKILLS_DIR));
|
|
26
|
+
}
|
|
27
|
+
const parentDir = path.dirname(normalizedDir);
|
|
28
|
+
const realParentDir = yield* fs.realPath(parentDir).pipe(Effect.map((realPath) => normalizedTargetDir(path, realPath)), Effect.catch(() => Effect.succeed(parentDir)));
|
|
29
|
+
return normalizedTargetDir(path, path.join(realParentDir, path.basename(normalizedDir)));
|
|
30
|
+
});
|
|
31
|
+
export const groupInstallTargetsByDirectory = (targets, workspaceRoot) => Effect.gen(function* () {
|
|
32
|
+
const keyedTargets = yield* Effect.forEach(targets, (target) => targetLocationKey(target.targetDir, workspaceRoot).pipe(Effect.map((key) => ({ key, target }))), { concurrency: "unbounded" });
|
|
33
|
+
const locationsByKey = new Map();
|
|
34
|
+
for (const { key, target } of keyedTargets) {
|
|
35
|
+
const existing = locationsByKey.get(key);
|
|
36
|
+
if (existing === undefined) {
|
|
37
|
+
locationsByKey.set(key, { targetDir: target.targetDir, agentIds: [target.agentId] });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (!existing.agentIds.includes(target.agentId)) {
|
|
41
|
+
existing.agentIds.push(target.agentId);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return [...locationsByKey.values()];
|
|
45
|
+
});
|
|
46
|
+
export const skillArtifactFromTargets = (args) => Effect.gen(function* () {
|
|
47
|
+
const path = yield* Path.Path;
|
|
48
|
+
const targetLocations = yield* groupInstallTargetsByDirectory(args.targets, args.workspaceRoot);
|
|
49
|
+
const artifactTargets = [
|
|
50
|
+
...(args.workspaceTargets ?? []),
|
|
51
|
+
...targetLocations.map((location) => {
|
|
52
|
+
const agentIds = artifactTargetAgentIds(location.agentIds);
|
|
53
|
+
return {
|
|
54
|
+
path: path.relative(args.workspaceRoot, path.join(location.targetDir, args.sanitizedName)),
|
|
55
|
+
change: args.change,
|
|
56
|
+
...(agentIds.length > 0 ? { agentIds } : {}),
|
|
57
|
+
};
|
|
58
|
+
}),
|
|
59
|
+
];
|
|
60
|
+
const displayPath = artifactTargets[0]?.path ?? args.sanitizedName;
|
|
61
|
+
const artifactAgents = artifactAgentIdsFromTargets(args.targets);
|
|
62
|
+
return {
|
|
63
|
+
path: displayPath,
|
|
64
|
+
scope: args.scope,
|
|
65
|
+
agents: artifactAgents,
|
|
66
|
+
change: args.change,
|
|
67
|
+
...(artifactTargets.length > 0 ? { targets: artifactTargets } : {}),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=skill-artifact.js.map
|