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