@agentxm/extension-lifecycle 0.29.4 → 0.30.2

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.
Files changed (85) hide show
  1. package/dist/src/activation/set-activation.d.ts +22 -40
  2. package/dist/src/activation/set-activation.js +191 -336
  3. package/dist/src/demote/demote-to-external-source.d.ts +1 -1
  4. package/dist/src/demote/demote-to-external-source.js +53 -24
  5. package/dist/src/hooks/install/plan.js +6 -3
  6. package/dist/src/hooks/uninstall/plan.js +4 -3
  7. package/dist/src/index.d.ts +1 -5
  8. package/dist/src/index.js +1 -4
  9. package/dist/src/install/install-extensions.d.ts +5 -5
  10. package/dist/src/install/install-extensions.js +2 -1
  11. package/dist/src/install/root-intent.d.ts +2 -2
  12. package/dist/src/install/vocabulary.d.ts +5 -4
  13. package/dist/src/knowledge/install/plan.js +2 -1
  14. package/dist/src/knowledge/uninstall/plan.js +4 -3
  15. package/dist/src/mcps/install/plan.d.ts +1 -1
  16. package/dist/src/mcps/install/plan.js +59 -3
  17. package/dist/src/mcps/operations/uninstall.js +2 -1
  18. package/dist/src/mcps/uninstall/plan.js +13 -4
  19. package/dist/src/packs/graph-transition.d.ts +0 -25
  20. package/dist/src/packs/graph-transition.js +0 -101
  21. package/dist/src/packs/install/plan.d.ts +0 -13
  22. package/dist/src/packs/install/plan.js +12 -9
  23. package/dist/src/packs/member-install-step.js +8 -21
  24. package/dist/src/packs/promote-authored-pack.js +6 -3
  25. package/dist/src/packs/uninstall/plan.js +53 -21
  26. package/dist/src/rules/install/plan.js +6 -3
  27. package/dist/src/rules/uninstall/plan.js +4 -3
  28. package/dist/src/skills/application/index.d.ts +42 -0
  29. package/dist/src/skills/application/index.js +37 -0
  30. package/dist/src/skills/domain/selection.d.ts +22 -0
  31. package/dist/src/skills/domain/selection.js +18 -0
  32. package/dist/src/skills/install/artifact.d.ts +4 -0
  33. package/dist/src/skills/install/artifact.js +29 -0
  34. package/dist/src/skills/install/bundled.js +4 -4
  35. package/dist/src/skills/install/plan.d.ts +15 -4
  36. package/dist/src/skills/install/plan.js +114 -97
  37. package/dist/src/skills/uninstall/plan.js +13 -5
  38. package/dist/src/step-failure.js +12 -0
  39. package/dist/src/subagents/application/index.d.ts +42 -0
  40. package/dist/src/subagents/application/index.js +37 -0
  41. package/dist/src/subagents/domain/selection.d.ts +22 -0
  42. package/dist/src/subagents/domain/selection.js +18 -0
  43. package/dist/src/subagents/install/plan.d.ts +2 -2
  44. package/dist/src/subagents/install/plan.js +4 -4
  45. package/dist/src/subagents/operations/enable.js +1 -3
  46. package/dist/src/subagents/uninstall/plan.js +5 -4
  47. package/dist/src/testing.d.ts +3 -2
  48. package/dist/src/testing.js +9 -6
  49. package/dist/src/uninstall/root-intent.d.ts +2 -1
  50. package/dist/src/uninstall/root-intent.js +1 -0
  51. package/dist/src/uninstall/undesired-target.d.ts +26 -0
  52. package/dist/src/uninstall/undesired-target.js +56 -0
  53. package/dist/src/uninstall/uninstall-extensions.d.ts +1 -1
  54. package/dist/src/uninstall/uninstall-extensions.js +103 -3
  55. package/dist/src/update/root-request.d.ts +2 -2
  56. package/dist/src/update/selective/plan.d.ts +2 -3
  57. package/dist/src/update/selective/plan.js +2 -2
  58. package/dist/src/update/selective/requirements.d.ts +2 -6
  59. package/dist/src/update/selective/requirements.js +1 -3
  60. package/dist/src/update/selective/selection.js +1 -1
  61. package/dist/src/update/selective/skills.d.ts +2 -4
  62. package/dist/src/update/selective/skills.js +22 -27
  63. package/dist/src/update/selective/subagents.d.ts +2 -3
  64. package/dist/src/update/selective/subagents.js +11 -9
  65. package/dist/src/update/selective/use-case.d.ts +7 -7
  66. package/dist/src/update/selective/vocabulary.d.ts +1 -1
  67. package/dist/src/update/selector.js +1 -1
  68. package/dist/src/update/targeted-plan.js +4 -2
  69. package/dist/src/workspace-paths.d.ts +0 -6
  70. package/dist/src/workspace-paths.js +0 -10
  71. package/package.json +30 -24
  72. package/dist/src/glob.d.ts +0 -35
  73. package/dist/src/glob.js +0 -75
  74. package/dist/src/install/selection-interaction.d.ts +0 -46
  75. package/dist/src/install/selection-interaction.js +0 -22
  76. package/dist/src/skills/install/selection.d.ts +0 -26
  77. package/dist/src/skills/install/selection.js +0 -41
  78. package/dist/src/skills/operations/install-result.d.ts +0 -33
  79. package/dist/src/skills/operations/install-result.js +0 -10
  80. package/dist/src/skills/operations/install.d.ts +0 -67
  81. package/dist/src/skills/operations/install.js +0 -591
  82. package/dist/src/subagents/install/selection.d.ts +0 -26
  83. package/dist/src/subagents/install/selection.js +0 -37
  84. package/dist/src/uninstall/retention-policy.d.ts +0 -21
  85. package/dist/src/uninstall/retention-policy.js +0 -28
@@ -1,591 +0,0 @@
1
- /**
2
- * Install skill executor — orchestrates the full per-skill installation pipeline.
3
- *
4
- * Dispatches to a per-refType install function via `switch(ref.refType)`, then
5
- * runs shared post-install steps (agent symlinks, lockfile/settings writes).
6
- *
7
- * @experimental This API is unstable and may change without notice.
8
- */
9
- import * as FileSystem from "effect/FileSystem";
10
- import { stripFileProtocol } from "@agentxm/registry-client";
11
- import * as Path from "effect/Path";
12
- import * as Array from "effect/Array";
13
- import * as Effect from "effect/Effect";
14
- import * as Option from "effect/Option";
15
- import { printSourceParams } from "@agentxm/extension-model/unstable/sources/printer";
16
- import { printSkillLockSourceLocator } from "@agentxm/workspace-state";
17
- import { sourceToLockEntry } from "@agentxm/workspace-state";
18
- import { canReuseInstalledPackage, materializeExternalPackage, } from "@agentxm/extension-materialization";
19
- import { materializeRegistryPackage } from "@agentxm/extension-materialization";
20
- import { computePackageContentHash } from "@agentxm/workspace-state";
21
- import { RenderedFilePathSchema, } from "@agentxm/workspace-state";
22
- import { computeMaterializedTreeIntegrity } from "@agentxm/workspace-state";
23
- import * as Schema from "effect/Schema";
24
- import { SourceHostProviders } from "@agentxm/extension-sources";
25
- import { CodingAgentRepository } from "@agentxm/workspace-projection";
26
- import { isPathSafe } from "@agentxm/extension-model/unstable/path-types";
27
- import { makeWorkspaceRelativeSourcePath } from "@agentxm/extension-model/unstable/path-types";
28
- import { createSymlink } from "@agentxm/workspace-state";
29
- import { validatePathSafety } from "@agentxm/workspace-state";
30
- import { StepFailure } from "@agentxm/workspace-operations";
31
- import { acceptedRegistryVersionForRef, validateExactResolvedVersion, } from "@agentxm/workspace-state";
32
- import { ExtensionLifecycleFailed } from "../../errors.js";
33
- import { StepFailureConversion, withAdaptedStepFailures } from "../../step-failure-conversion.js";
34
- import { appendWarningsToMessage } from "@agentxm/workspace-operations";
35
- import { WorkspaceMutations } from "@agentxm/workspace-state";
36
- import { copyExtensionDirectory, formatCopyExtensionDirectoryFailure, } from "@agentxm/extension-materialization";
37
- import { artifactAgentIdsFromTargets, artifactTargetAgentIds, groupInstallTargetsByDirectory, } from "@agentxm/extension-materialization";
38
- import { sanitizeName } from "@agentxm/workspace-state";
39
- import { computeSkillSourceHash } from "@agentxm/extension-materialization";
40
- import { validateAxmSkillCandidate } from "@agentxm/extension-resolution";
41
- const countFiles = (dir) => Effect.gen(function* () {
42
- const fs = yield* FileSystem.FileSystem;
43
- const path = yield* Path.Path;
44
- const entries = yield* fs.readDirectory(dir).pipe(Effect.catch(() => Effect.succeed([])));
45
- let total = 0;
46
- for (const entry of entries) {
47
- const fullPath = path.join(dir, entry);
48
- const statOption = yield* fs.stat(fullPath).pipe(Effect.option);
49
- if (Option.isNone(statOption))
50
- continue;
51
- if (statOption.value.type === "Directory") {
52
- total += yield* countFiles(fullPath);
53
- }
54
- else {
55
- total += 1;
56
- }
57
- }
58
- return total;
59
- });
60
- const expectedSkillSrcPath = (ref) => Effect.gen(function* () {
61
- const ws = yield* WorkspaceMutations;
62
- switch (ref.refType) {
63
- case "registry": {
64
- const { skillSrcPath } = yield* ws.getSkillDir(ref.skill.name, ref);
65
- return skillSrcPath;
66
- }
67
- case "workspace": {
68
- const { skillSrcPath } = yield* ws.getSkillDir(ref.skill.name, ref);
69
- return skillSrcPath;
70
- }
71
- case "git-hosted":
72
- case "local": {
73
- const { skillSrcPath } = yield* ws.getSkillDir(ref.skill.name, ref);
74
- return skillSrcPath;
75
- }
76
- }
77
- });
78
- const existingSourceHash = (ref) => Effect.gen(function* () {
79
- const fs = yield* FileSystem.FileSystem;
80
- const previousPath = yield* expectedSkillSrcPath(ref);
81
- const exists = yield* fs.exists(previousPath).pipe(Effect.catch(() => Effect.succeed(false)));
82
- if (!exists)
83
- return undefined;
84
- return yield* computeSkillSourceHash(previousPath);
85
- });
86
- const symlinkResultToChange = (result) => {
87
- switch (result) {
88
- case "created":
89
- return "created";
90
- case "replaced":
91
- return "updated";
92
- case "no-op":
93
- case "skipped":
94
- return "unchanged";
95
- }
96
- };
97
- const artifactChangeFromTargets = (fallback, targets) => {
98
- if (targets.length === 0)
99
- return fallback;
100
- if (targets.some((target) => target.change === "created"))
101
- return "created";
102
- if (targets.some((target) => target.change === "updated" || target.change === undefined)) {
103
- return "updated";
104
- }
105
- return fallback === "updated" ? "updated" : "unchanged";
106
- };
107
- const gitHostedSourceOrigin = (ref) => {
108
- const source = ref.source;
109
- switch (source.type) {
110
- case "github":
111
- case "gitlab":
112
- case "bitbucket":
113
- return `${source.url.origin}/${source.owner}/${source.repo}`;
114
- case "azurerepos":
115
- return `${source.url.origin}/${source.organization}/${source.project}/_git/${source.repo}`;
116
- case "git":
117
- return source.url.href;
118
- }
119
- };
120
- export const gitHostedSkillArtifactSource = (ref) => {
121
- if (ref.refType !== "git-hosted")
122
- return undefined;
123
- const gitTreeHash = ref.gitTreeSha;
124
- const gitRef = Option.getOrUndefined(ref.source.ref);
125
- const directory = ref.sourcePath === undefined || ref.sourcePath.length === 0 ? "." : ref.sourcePath;
126
- return {
127
- type: ref.source.type,
128
- origin: gitHostedSourceOrigin(ref),
129
- ...(gitRef !== undefined ? { ref: gitRef } : {}),
130
- directory,
131
- gitTreeHash,
132
- };
133
- };
134
- // -----------------------------------------------------------------------------
135
- // Shared helpers
136
- // -----------------------------------------------------------------------------
137
- const preCleanAndCopy = (sanitizedName, sourcePath, copyTarget) => Effect.gen(function* () {
138
- const adapter = yield* StepFailureConversion;
139
- const fs = yield* FileSystem.FileSystem;
140
- const ws = yield* WorkspaceMutations;
141
- const sourceExists = yield* fs
142
- .exists(sourcePath)
143
- .pipe(Effect.catch(() => Effect.succeed(false)));
144
- if (!sourceExists) {
145
- return yield* new ExtensionLifecycleFailed({
146
- category: "validation",
147
- detail: formatCopyExtensionDirectoryFailure({
148
- sourcePath,
149
- targetPath: copyTarget,
150
- subject: "skill files",
151
- sourceExists,
152
- }),
153
- suggestions: [{ description: "Check the extension package and try again." }],
154
- });
155
- }
156
- yield* materializeExternalPackage({
157
- baseDir: ws.baseDir,
158
- canonicalPath: copyTarget,
159
- sourceLocation: sourcePath,
160
- copyFailureCode: "internal",
161
- copyFailureDetail: () => formatCopyExtensionDirectoryFailure({
162
- sourcePath,
163
- targetPath: copyTarget,
164
- subject: "skill files",
165
- sourceExists,
166
- }),
167
- }).pipe(Effect.mapError((error) => new ExtensionLifecycleFailed({
168
- category: "validation",
169
- detail: adapter.describeFailure(error),
170
- suggestions: [{ description: "Check the extension package and try again." }],
171
- cause: error,
172
- })));
173
- });
174
- const decodeRenderedFilePath = Schema.decodeUnknownSync(RenderedFilePathSchema);
175
- export { computeSkillSourceHash } from "@agentxm/extension-materialization";
176
- /**
177
- * Build a RenderedFilesMap from per-agent copy-mode install results.
178
- * Only includes agents where mode === "copy" and success === true.
179
- *
180
- * @internal Exported for testing only.
181
- */
182
- export const buildRenderedFilesFromResults = (installableTargets, agentResults, toWorkspaceRelativePath) => {
183
- const result = {};
184
- for (const [target, installResult] of Array.zip(installableTargets, agentResults)) {
185
- if (installResult.mode === "copy" && installResult.success) {
186
- result[target.agentId] = [
187
- { path: decodeRenderedFilePath(toWorkspaceRelativePath(installResult.path)) },
188
- ];
189
- }
190
- }
191
- return result;
192
- };
193
- // -----------------------------------------------------------------------------
194
- // Per-refType install functions
195
- // -----------------------------------------------------------------------------
196
- const installFromGitHosted = (ref, sanitizedName) => Effect.gen(function* () {
197
- const path = yield* Path.Path;
198
- const ws = yield* WorkspaceMutations;
199
- const { canonicalPath, skillSrcPath } = yield* ws.getSkillDir(ref.skill.name, ref);
200
- yield* validatePathSafety(path, ws.baseDir, canonicalPath);
201
- const packageRoot = stripFileProtocol(ref.location);
202
- const sourceSkillPath = ref.portable === true ? packageRoot : path.join(packageRoot, "src");
203
- yield* validateAxmSkillCandidate({
204
- ref,
205
- packageRoot,
206
- skillSourcePath: sourceSkillPath,
207
- });
208
- yield* preCleanAndCopy(sanitizedName, packageRoot, canonicalPath);
209
- return { skillSrcPath, versionRange: Option.none() };
210
- });
211
- const installFromLocal = (ref, sanitizedName) => Effect.gen(function* () {
212
- const path = yield* Path.Path;
213
- const ws = yield* WorkspaceMutations;
214
- const { canonicalPath, skillSrcPath } = yield* ws.getSkillDir(ref.skill.name, ref);
215
- yield* validatePathSafety(path, ws.baseDir, canonicalPath);
216
- const packageRoot = stripFileProtocol(ref.location);
217
- const sourceSkillPath = ref.portable === true ? packageRoot : path.join(packageRoot, "src");
218
- yield* validateAxmSkillCandidate({
219
- ref,
220
- packageRoot,
221
- skillSourcePath: sourceSkillPath,
222
- });
223
- const isSelfCopy = path.resolve(packageRoot) === path.resolve(canonicalPath);
224
- if (!isSelfCopy) {
225
- yield* preCleanAndCopy(sanitizedName, packageRoot, canonicalPath);
226
- }
227
- return { skillSrcPath, versionRange: Option.none() };
228
- });
229
- const installFromRegistry = (ref, sanitizedName, versionRange, reuse) => Effect.gen(function* () {
230
- const path = yield* Path.Path;
231
- const ws = yield* WorkspaceMutations;
232
- const { canonicalPath, skillSrcPath } = yield* ws.getSkillDir(ref.skill.name, ref);
233
- yield* validatePathSafety(path, ws.baseDir, canonicalPath);
234
- const useExisting = yield* canReuseInstalledPackage({
235
- installedPath: canonicalPath,
236
- force: reuse.force,
237
- refVersion: ref.version,
238
- hasIntegrity: Option.isSome(ref.integrity),
239
- ...(reuse.lockedVersion === undefined ? {} : { lockedVersion: reuse.lockedVersion }),
240
- existsFailureDetail: (target) => `Failed to check if canonical path exists: ${target}`,
241
- });
242
- if (!useExisting) {
243
- yield* materializeRegistryPackage({
244
- baseDir: ws.baseDir,
245
- destinationPath: canonicalPath,
246
- sourceLocation: ref.source.location,
247
- owner: ref.owner,
248
- type: "skill",
249
- name: ref.name,
250
- version: ref.version,
251
- integrity: ref.integrity,
252
- messages: {
253
- integrityMismatchDetail: `Integrity mismatch for ${ref.name}@${ref.version}`,
254
- },
255
- validate: (stagingPath) => validateAxmSkillCandidate({
256
- ref,
257
- packageRoot: stagingPath,
258
- skillSourcePath: path.join(stagingPath, "src"),
259
- }).pipe(Effect.asVoid),
260
- });
261
- }
262
- else {
263
- yield* validateAxmSkillCandidate({
264
- ref,
265
- packageRoot: canonicalPath,
266
- skillSourcePath: skillSrcPath,
267
- });
268
- }
269
- return { skillSrcPath, versionRange };
270
- });
271
- const installFromWorkspace = (ref) => Effect.gen(function* () {
272
- const fs = yield* FileSystem.FileSystem;
273
- const path = yield* Path.Path;
274
- const ws = yield* WorkspaceMutations;
275
- if (ref.scope !== ws.scope) {
276
- return yield* new ExtensionLifecycleFailed({
277
- category: "validation",
278
- detail: `Workspace skill ${ref.name} belongs to ${ref.scope} scope, not ${ws.scope} scope`,
279
- });
280
- }
281
- yield* validatePathSafety(path, ws.baseDir, ref.location);
282
- const skillSrcPath = path.join(ref.location, "src");
283
- const exists = yield* fs.exists(skillSrcPath).pipe(Effect.mapError((error) => new ExtensionLifecycleFailed({
284
- category: "internal",
285
- detail: `Failed to inspect workspace skill source: ${skillSrcPath}`,
286
- cause: error,
287
- })));
288
- if (!exists) {
289
- return yield* new ExtensionLifecycleFailed({
290
- category: "validation",
291
- detail: `Workspace skill source is missing: ${skillSrcPath}`,
292
- });
293
- }
294
- yield* validateAxmSkillCandidate({
295
- ref,
296
- packageRoot: ref.location,
297
- skillSourcePath: skillSrcPath,
298
- });
299
- return { skillSrcPath, versionRange: Option.none() };
300
- });
301
- // -----------------------------------------------------------------------------
302
- // Agent symlink helper
303
- // -----------------------------------------------------------------------------
304
- const installForDirectory = (opts) => Effect.gen(function* () {
305
- const path = yield* Path.Path;
306
- const ws = yield* WorkspaceMutations;
307
- const agentSkillPath = path.join(opts.targetDir, opts.sanitizedName);
308
- // Validate agent path safety
309
- if (!isPathSafe(path, ws.baseDir, agentSkillPath)) {
310
- return {
311
- success: false,
312
- mode: "symlink",
313
- symlinkFailed: false,
314
- error: Option.some(`Path traversal detected for target directory ${opts.targetDir}`),
315
- path: agentSkillPath,
316
- canonicalPath: opts.canonicalSkillSrcPath,
317
- change: "updated",
318
- };
319
- }
320
- // Try symlink, fall back to copy
321
- return yield* createSymlink({
322
- target: opts.canonicalSkillSrcPath,
323
- link: agentSkillPath,
324
- }).pipe(Effect.map((result) => ({
325
- success: true,
326
- mode: "symlink",
327
- symlinkFailed: false,
328
- error: Option.none(),
329
- path: agentSkillPath,
330
- canonicalPath: opts.canonicalSkillSrcPath,
331
- change: symlinkResultToChange(result),
332
- })), Effect.catch(() => copyExtensionDirectory(opts.canonicalSkillSrcPath, agentSkillPath).pipe(Effect.map(() => ({
333
- success: true,
334
- mode: "copy",
335
- symlinkFailed: true,
336
- error: Option.none(),
337
- path: agentSkillPath,
338
- canonicalPath: opts.canonicalSkillSrcPath,
339
- change: "updated",
340
- })), Effect.catch((copyErr) => Effect.succeed({
341
- success: false,
342
- mode: "copy",
343
- symlinkFailed: true,
344
- error: Option.some(`Copy fallback failed: ${copyErr.message}`),
345
- path: agentSkillPath,
346
- canonicalPath: opts.canonicalSkillSrcPath,
347
- change: "updated",
348
- })))));
349
- });
350
- const materializeSkill = (ref, sanitizedName, versionRange, reuse) => {
351
- switch (ref.refType) {
352
- case "git-hosted":
353
- return installFromGitHosted(ref, sanitizedName);
354
- case "registry":
355
- return installFromRegistry(ref, sanitizedName, versionRange, reuse);
356
- case "local":
357
- return installFromLocal(ref, sanitizedName);
358
- case "workspace":
359
- return installFromWorkspace(ref);
360
- }
361
- };
362
- // -----------------------------------------------------------------------------
363
- // Public API
364
- // -----------------------------------------------------------------------------
365
- /**
366
- * Install-skill operation handler.
367
- *
368
- * Dispatches to a per-refType install function producing a MaterializedSkill,
369
- * then runs shared post-install steps:
370
- * 1. Create symlinks from each agent's skills dir (concurrent)
371
- * 2. Update lockfile/settings entry (failures logged as warnings)
372
- * 3. Compute and return overall result
373
- */
374
- export const installSkill = (op) => Effect.gen(function* () {
375
- const adapter = yield* StepFailureConversion;
376
- const ws = yield* WorkspaceMutations;
377
- const path = yield* Path.Path;
378
- const agentRepo = yield* CodingAgentRepository;
379
- const { ref } = op.args;
380
- const sanitizedName = sanitizeName(ref.skill.name);
381
- const strictUnknownAgents = Option.getOrElse(op.args.strictUnknownAgents, () => false);
382
- const previousLock = yield* ws.getLockedSkill(ref.skill.name);
383
- const previousVersion = ref.refType === "registry" ? acceptedRegistryVersionForRef(previousLock, ref) : undefined;
384
- const previouslyAccepted = Option.isSome(previousLock);
385
- const sourceHashBeforeInstall = yield* existingSourceHash(ref);
386
- // ── Per-refType: resolve source, copy to canonical ──────────────
387
- const materialized = yield* materializeSkill(ref, sanitizedName, op.args.versionRange, {
388
- force: op.args.force,
389
- lockedVersion: previousVersion,
390
- });
391
- // ── Shared: resolve agent targets + install once per distinct dir ────────
392
- const configuredAgents = yield* agentRepo
393
- .getMaterializationAgents()
394
- .pipe(Effect.provideService(WorkspaceMutations, ws));
395
- const unknownConfiguredAgentIds = yield* agentRepo
396
- .getUnknownConfiguredAgentIds()
397
- .pipe(Effect.provideService(WorkspaceMutations, ws));
398
- if (strictUnknownAgents && unknownConfiguredAgentIds.length > 0) {
399
- const message = `Unknown configured agents in strict mode: ${unknownConfiguredAgentIds.join(", ")}`;
400
- return {
401
- result: "error",
402
- message,
403
- error: new StepFailure({
404
- category: "not_found",
405
- detail: message,
406
- }),
407
- };
408
- }
409
- const unknownAgentWarning = unknownConfiguredAgentIds.length === 0
410
- ? undefined
411
- : `Skipping unknown configured agents: ${unknownConfiguredAgentIds.join(", ")}`;
412
- const resolvedAgents = yield* Effect.forEach(configuredAgents, (agent) => agent
413
- .resolveEffectiveSkillsDir({ workspaceRoot: ws.baseDir })
414
- .pipe(Effect.map((outcome) => ({ agentId: agent.id, outcome }))), { concurrency: "unbounded" });
415
- const misconfigured = Array.filter(resolvedAgents, ({ outcome }) => outcome._tag === "misconfigured");
416
- if (misconfigured.length > 0) {
417
- const message = `Failed to resolve skills directories for ${ref.skill.name}`;
418
- return {
419
- result: "error",
420
- message,
421
- error: new StepFailure({
422
- category: "validation",
423
- detail: message,
424
- }),
425
- };
426
- }
427
- const skippedByOutcome = Array.filter(resolvedAgents, ({ outcome }) => outcome._tag === "unsupported" || outcome._tag === "disabled");
428
- const skippedOutcomeWarning = skippedByOutcome.length === 0
429
- ? undefined
430
- : `Skipping non-installable configured agents: ${skippedByOutcome
431
- .map(({ agentId, outcome }) => outcome._tag === "supported"
432
- ? `${agentId}: not skipped`
433
- : `${agentId}: ${outcome.reason}`)
434
- .join(", ")}`;
435
- const installableTargets = [];
436
- for (const { agentId, outcome } of resolvedAgents) {
437
- if (outcome._tag === "supported") {
438
- installableTargets.push({
439
- agentId,
440
- targetDir: path.normalize(outcome.dir),
441
- });
442
- }
443
- }
444
- const targetLocations = yield* groupInstallTargetsByDirectory(installableTargets, ws.baseDir);
445
- const displayTargetDir = targetLocations[0]?.targetDir;
446
- const perDirectoryResults = yield* Effect.forEach(targetLocations, (location) => installForDirectory({
447
- targetDir: location.targetDir,
448
- canonicalSkillSrcPath: materialized.skillSrcPath,
449
- sanitizedName,
450
- }).pipe(Effect.map((result) => ({ location, result }))), { concurrency: "unbounded" });
451
- const agentResults = installableTargets.map((target) => {
452
- const matched = perDirectoryResults.find((item) => item.location.agentIds.includes(target.agentId));
453
- if (matched === undefined) {
454
- return {
455
- success: false,
456
- mode: "copy",
457
- symlinkFailed: true,
458
- error: Option.some(`No installation result for target directory ${target.targetDir}`),
459
- path: target.targetDir,
460
- canonicalPath: materialized.skillSrcPath,
461
- change: "updated",
462
- };
463
- }
464
- return matched.result;
465
- });
466
- const sourceHash = ref.refType === "workspace"
467
- ? ref.sourceHash
468
- : ref.refType === "registry"
469
- ? yield* computePackageContentHash(path.dirname(materialized.skillSrcPath))
470
- : yield* computeSkillSourceHash(materialized.skillSrcPath);
471
- // ── Shared: update lockfile + settings ──────────────────────────
472
- const workspaceRelativeLocalSourcePath = ref.refType === "local"
473
- ? makeWorkspaceRelativeSourcePath(path, ws.baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
474
- : Option.none();
475
- if (ref.refType === "local" && Option.isNone(workspaceRelativeLocalSourcePath)) {
476
- return yield* new ExtensionLifecycleFailed({
477
- category: "validation",
478
- detail: `Local skill source path must stay within the workspace root: ${ref.source.path}`,
479
- });
480
- }
481
- const baseLockEntry = sourceToLockEntry({
482
- ref,
483
- sourceName: op.args.sourceName,
484
- contentIdentity: sourceHash,
485
- treeIntegrity: yield* computeMaterializedTreeIntegrity((yield* ws.getSkillDir(ref.skill.name, ref)).canonicalPath),
486
- workspaceRelativeLocalSourcePath,
487
- });
488
- if (baseLockEntry?.type === "registry") {
489
- yield* validateExactResolvedVersion(`skills.${ref.skill.name}.resolvedVersion`, baseLockEntry.resolvedVersion);
490
- }
491
- // ── Shared: compute result ──────────────────────────────────────
492
- const anyFailed = agentResults.some((r) => !r.success);
493
- const fileCount = yield* countFiles(materialized.skillSrcPath);
494
- const currentSourceHash = yield* computeSkillSourceHash(materialized.skillSrcPath);
495
- const displayPath = displayTargetDir === undefined
496
- ? path.relative(ws.baseDir, materialized.skillSrcPath)
497
- : path.join(path.relative(ws.baseDir, displayTargetDir), sanitizedName);
498
- const artifactAgents = artifactAgentIdsFromTargets(installableTargets);
499
- const artifactTargets = perDirectoryResults.map(({ location, result }) => {
500
- const agentIds = artifactTargetAgentIds(location.agentIds);
501
- return {
502
- path: path.relative(ws.baseDir, result.path),
503
- change: result.change ?? "updated",
504
- ...(agentIds.length > 0 ? { agentIds } : {}),
505
- };
506
- });
507
- const version = baseLockEntry?.type === "registry"
508
- ? baseLockEntry.resolvedVersion
509
- : Option.getOrUndefined(op.args.versionRange);
510
- const sameVersion = previousVersion === version;
511
- const sameSource = sourceHashBeforeInstall === currentSourceHash;
512
- const fallbackChange = !previouslyAccepted
513
- ? "created"
514
- : sameVersion && sameSource
515
- ? "unchanged"
516
- : "updated";
517
- const artifactChange = artifactChangeFromTargets(fallbackChange, artifactTargets);
518
- if (anyFailed) {
519
- const failedAgents = agentResults
520
- .filter((r) => !r.success)
521
- .map((r) => Option.getOrElse(r.error, () => "unknown error"));
522
- const message = `Failed to install ${ref.skill.name} for some agents: ${failedAgents.join(", ")}`;
523
- return {
524
- result: "error",
525
- message,
526
- error: new StepFailure({
527
- category: "internal",
528
- detail: message,
529
- }),
530
- };
531
- }
532
- const skipSettings = Option.getOrElse(op.args.skipSettings, () => false);
533
- const writeEffect = baseLockEntry === undefined
534
- ? skipSettings
535
- ? Effect.void
536
- : ws.setSkillEntry(ref.skill.name, {
537
- source: ref.refType === "workspace" ? "workspace" : printSourceParams(ref.source),
538
- enabled: true,
539
- })
540
- : skipSettings
541
- ? ws.setSkillLock({
542
- name: ref.skill.name,
543
- lockEntry: baseLockEntry,
544
- versionRange: materialized.versionRange,
545
- })
546
- : ws.setSkill({
547
- name: ref.skill.name,
548
- lockEntry: baseLockEntry,
549
- versionRange: materialized.versionRange,
550
- });
551
- const writeFailure = yield* writeEffect.pipe(Effect.result);
552
- if (writeFailure._tag === "Failure") {
553
- const failure = writeFailure.failure;
554
- const detail = `Installed ${ref.skill.name}, but failed to record desired state: ${adapter.describeFailure(failure)}`;
555
- return {
556
- result: "error",
557
- message: detail,
558
- error: new StepFailure({
559
- category: adapter.toStepFailure(failure).category,
560
- detail,
561
- suggestions: [
562
- {
563
- description: "Retry the install after repairing workspace write access.",
564
- cmd: `axm skills install ${baseLockEntry === undefined ? printSourceParams(ref.source) : printSkillLockSourceLocator(ref.skill.name, baseLockEntry)}`,
565
- },
566
- ],
567
- cause: failure,
568
- }),
569
- };
570
- }
571
- const warnings = [unknownAgentWarning, skippedOutcomeWarning].filter((warning) => warning !== undefined);
572
- const sourceDetails = gitHostedSkillArtifactSource(ref);
573
- return {
574
- result: "success",
575
- message: appendWarningsToMessage(`Installed ${ref.skill.name}`, warnings),
576
- artifact: {
577
- path: displayPath.length === 0 ? "." : displayPath,
578
- scope: ws.scope,
579
- agents: artifactAgents,
580
- ...(version !== undefined ? { version } : {}),
581
- change: artifactChange,
582
- ...(previousVersion !== undefined && previousVersion !== version
583
- ? { previousVersion }
584
- : {}),
585
- fileCount,
586
- ...(artifactTargets.length > 0 ? { targets: artifactTargets } : {}),
587
- ...(sourceDetails !== undefined ? { source: sourceDetails } : {}),
588
- },
589
- };
590
- }).pipe(withAdaptedStepFailures);
591
- //# sourceMappingURL=install.js.map
@@ -1,26 +0,0 @@
1
- /**
2
- * Which of a source's subagents this request installs.
3
- *
4
- * The same decision the skill route makes, over subagent names: named
5
- * patterns must match, `--all` and an unattended invocation take everything,
6
- * a single subagent needs no decision, and anything else is a person's
7
- * choice.
8
- *
9
- * @experimental This API is unstable and may change without notice.
10
- */
11
- import * as Array from "effect/Array";
12
- import * as Effect from "effect/Effect";
13
- import type { SubagentExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/subagent";
14
- import type { ExtensionLifecycleFailed } from "../../errors.js";
15
- import { ExtensionSelectionInteraction, type ExtensionSelectionCancelled } from "../../install/selection-interaction.js";
16
- export interface SubagentSelectionRequest {
17
- /** Names or glob patterns the request named explicitly. */
18
- readonly requestedSubagents: ReadonlyArray<string>;
19
- /** Take everything the source offers without asking. */
20
- readonly all: boolean;
21
- /** No prompt can open in this invocation. */
22
- readonly nonInteractive: boolean;
23
- }
24
- /** Settle which discovered subagents this request installs. */
25
- export declare const determineSubagentsToInstall: (subagents: Array.NonEmptyReadonlyArray<SubagentExtensionRef>, request: SubagentSelectionRequest) => Effect.Effect<ReadonlyArray<SubagentExtensionRef>, ExtensionLifecycleFailed | ExtensionSelectionCancelled, ExtensionSelectionInteraction>;
26
- //# sourceMappingURL=selection.d.ts.map
@@ -1,37 +0,0 @@
1
- /**
2
- * Which of a source's subagents this request installs.
3
- *
4
- * The same decision the skill route makes, over subagent names: named
5
- * patterns must match, `--all` and an unattended invocation take everything,
6
- * a single subagent needs no decision, and anything else is a person's
7
- * choice.
8
- *
9
- * @experimental This API is unstable and may change without notice.
10
- */
11
- import * as Array from "effect/Array";
12
- import * as Effect from "effect/Effect";
13
- import { expandGlobs } from "../../glob.js";
14
- import { ExtensionSelectionInteraction, } from "../../install/selection-interaction.js";
15
- import { installRefused } from "../../install/vocabulary.js";
16
- /** Settle which discovered subagents this request installs. */
17
- export const determineSubagentsToInstall = Effect.fn("InstallExtensions.determineSubagentsToInstall")(function* (subagents, request) {
18
- if (request.requestedSubagents.length > 0) {
19
- const allNames = Array.map(subagents, (subagent) => subagent.subagent.name);
20
- const matched = expandGlobs(request.requestedSubagents, allNames);
21
- if (matched.length === 0) {
22
- return yield* installRefused({
23
- category: "internal",
24
- detail: `No subagents matched: ${request.requestedSubagents.join(", ")}`,
25
- suggestions: [{ description: "Check the subagent names or patterns and try again." }],
26
- });
27
- }
28
- return Array.filter(subagents, (subagent) => matched.includes(subagent.subagent.name));
29
- }
30
- if (request.all || request.nonInteractive)
31
- return subagents;
32
- if (subagents.length === 1)
33
- return subagents;
34
- const interaction = yield* ExtensionSelectionInteraction;
35
- return yield* interaction.selectSubagents(subagents);
36
- });
37
- //# sourceMappingURL=selection.js.map
@@ -1,21 +0,0 @@
1
- /**
2
- * Whether a removal may delete the package it withdraws.
3
- *
4
- * An extension declared directly and also required by an installed pack keeps
5
- * its package when the direct declaration goes: the pack still depends on it.
6
- * The membership fact is workspace state's; deciding that it means "retain"
7
- * is this feature's.
8
- *
9
- * @experimental This API is unstable and may change without notice.
10
- */
11
- import type { UninstallRetentionPolicy } from "@agentxm/extension-materialization";
12
- import type { WorkspaceMutationsService } from "@agentxm/workspace-state";
13
- import type { ExtensionLifecycleFailed } from "../errors.js";
14
- /** Retain a package an installed pack still requires. */
15
- export declare const makeWorkspaceRetentionPolicy: (ws: WorkspaceMutationsService) => UninstallRetentionPolicy<ExtensionLifecycleFailed>;
16
- /**
17
- * A pack's own exclusive members carry no separate retention question: the
18
- * pack that required them is being removed in the same transition.
19
- */
20
- export declare const exclusiveMemberRetentionPolicy: UninstallRetentionPolicy<ExtensionLifecycleFailed>;
21
- //# sourceMappingURL=retention-policy.d.ts.map