@agentxm/extension-lifecycle 0.30.1 → 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 (56) hide show
  1. package/dist/src/index.d.ts +0 -3
  2. package/dist/src/index.js +0 -2
  3. package/dist/src/install/install-extensions.d.ts +5 -5
  4. package/dist/src/install/install-extensions.js +2 -1
  5. package/dist/src/mcps/install/plan.d.ts +1 -1
  6. package/dist/src/mcps/install/plan.js +58 -1
  7. package/dist/src/packs/uninstall/plan.js +1 -1
  8. package/dist/src/skills/application/index.d.ts +42 -0
  9. package/dist/src/skills/application/index.js +37 -0
  10. package/dist/src/skills/domain/selection.d.ts +22 -0
  11. package/dist/src/skills/domain/selection.js +18 -0
  12. package/dist/src/skills/install/artifact.d.ts +4 -0
  13. package/dist/src/skills/install/artifact.js +29 -0
  14. package/dist/src/skills/install/plan.d.ts +15 -4
  15. package/dist/src/skills/install/plan.js +112 -96
  16. package/dist/src/skills/uninstall/plan.js +9 -2
  17. package/dist/src/step-failure.js +12 -0
  18. package/dist/src/subagents/application/index.d.ts +42 -0
  19. package/dist/src/subagents/application/index.js +37 -0
  20. package/dist/src/subagents/domain/selection.d.ts +22 -0
  21. package/dist/src/subagents/domain/selection.js +18 -0
  22. package/dist/src/subagents/install/plan.d.ts +2 -2
  23. package/dist/src/subagents/install/plan.js +1 -2
  24. package/dist/src/subagents/uninstall/plan.js +1 -1
  25. package/dist/src/testing.d.ts +3 -2
  26. package/dist/src/testing.js +7 -5
  27. package/dist/src/uninstall/root-intent.d.ts +1 -0
  28. package/dist/src/uninstall/root-intent.js +1 -0
  29. package/dist/src/uninstall/undesired-target.d.ts +26 -0
  30. package/dist/src/uninstall/undesired-target.js +56 -0
  31. package/dist/src/uninstall/uninstall-extensions.js +30 -6
  32. package/dist/src/update/selective/plan.d.ts +2 -3
  33. package/dist/src/update/selective/plan.js +2 -2
  34. package/dist/src/update/selective/requirements.d.ts +2 -6
  35. package/dist/src/update/selective/requirements.js +1 -3
  36. package/dist/src/update/selective/selection.js +1 -1
  37. package/dist/src/update/selective/skills.d.ts +2 -4
  38. package/dist/src/update/selective/skills.js +22 -26
  39. package/dist/src/update/selective/subagents.d.ts +1 -2
  40. package/dist/src/update/selective/subagents.js +9 -8
  41. package/dist/src/update/selective/use-case.d.ts +7 -7
  42. package/dist/src/update/selective/vocabulary.d.ts +1 -1
  43. package/dist/src/update/selector.js +1 -1
  44. package/package.json +24 -16
  45. package/dist/src/glob.d.ts +0 -35
  46. package/dist/src/glob.js +0 -75
  47. package/dist/src/install/selection-interaction.d.ts +0 -46
  48. package/dist/src/install/selection-interaction.js +0 -22
  49. package/dist/src/skills/install/selection.d.ts +0 -26
  50. package/dist/src/skills/install/selection.js +0 -41
  51. package/dist/src/skills/operations/install-result.d.ts +0 -33
  52. package/dist/src/skills/operations/install-result.js +0 -10
  53. package/dist/src/skills/operations/install.d.ts +0 -66
  54. package/dist/src/skills/operations/install.js +0 -582
  55. package/dist/src/subagents/install/selection.d.ts +0 -26
  56. package/dist/src/subagents/install/selection.js +0 -37
@@ -1,66 +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 type * as HttpClient from "effect/unstable/http/HttpClient";
11
- import * as Path from "effect/Path";
12
- import * as Option from "effect/Option";
13
- import type { AgentId } from "@agentxm/extension-model/unstable/agents/types";
14
- import { type RenderedFilesMap } from "@agentxm/workspace-state";
15
- import type { SkillExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/skill";
16
- import { SourceHostProviders } from "@agentxm/extension-sources";
17
- import { CodingAgentRepository } from "@agentxm/workspace-projection";
18
- import { StepFailureConversion } from "../../step-failure-conversion.js";
19
- import type { OperationHandler } from "@agentxm/workspace-operations";
20
- import type { Operation } from "@agentxm/workspace-operations";
21
- import type { JobStepArtifactSource } from "@agentxm/workspace-operations";
22
- import { WorkspaceMutations } from "@agentxm/workspace-state";
23
- import type { InstallResult } from "./install-result.js";
24
- /**
25
- * Args for the install-skill operation.
26
- */
27
- export type InstallSkillOperationArgs = {
28
- readonly ref: SkillExtensionRef;
29
- readonly force: boolean;
30
- /** Version constraint from the original input when available. */
31
- readonly versionRange: Option.Option<string>;
32
- /** When true, write to lockfile only (skip settings). Used for pack dependencies. */
33
- /** When true, fail on unknown configured agents instead of warning+skip. */
34
- readonly strictUnknownAgents: Option.Option<boolean>;
35
- /** Named registry source that provided the ref (written to lockfile for registry skills). */
36
- readonly sourceName: Option.Option<string>;
37
- };
38
- /**
39
- * Add a skill to the workspace.
40
- *
41
- * @experimental This API is unstable and may change without notice.
42
- */
43
- export type InstallSkillOperation = Operation<"install-skill", InstallSkillOperationArgs>;
44
- export declare const gitHostedSkillArtifactSource: (ref: SkillExtensionRef) => JobStepArtifactSource | undefined;
45
- export { computeSkillSourceHash } from "@agentxm/extension-materialization";
46
- /**
47
- * Build a RenderedFilesMap from per-agent copy-mode install results.
48
- * Only includes agents where mode === "copy" and success === true.
49
- *
50
- * @internal Exported for testing only.
51
- */
52
- export declare const buildRenderedFilesFromResults: (installableTargets: ReadonlyArray<{
53
- agentId: AgentId;
54
- targetDir: string;
55
- }>, agentResults: ReadonlyArray<InstallResult>, toWorkspaceRelativePath: (path: string) => string) => RenderedFilesMap;
56
- /**
57
- * Install-skill operation handler.
58
- *
59
- * Dispatches to a per-refType install function producing a MaterializedSkill,
60
- * then runs shared post-install steps:
61
- * 1. Create symlinks from each agent's skills dir (concurrent)
62
- * 2. Update lockfile/settings entry (failures logged as warnings)
63
- * 3. Compute and return overall result
64
- */
65
- export declare const installSkill: OperationHandler<InstallSkillOperation, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path | WorkspaceMutations | SourceHostProviders | CodingAgentRepository | StepFailureConversion>;
66
- //# sourceMappingURL=install.d.ts.map
@@ -1,582 +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 writeEffect = baseLockEntry === undefined
533
- ? ws.setSkillEntry(ref.skill.name, {
534
- source: ref.refType === "workspace" ? "workspace" : printSourceParams(ref.source),
535
- enabled: true,
536
- })
537
- : ws.setSkill({
538
- name: ref.skill.name,
539
- lockEntry: baseLockEntry,
540
- versionRange: materialized.versionRange,
541
- });
542
- const writeFailure = yield* writeEffect.pipe(Effect.result);
543
- if (writeFailure._tag === "Failure") {
544
- const failure = writeFailure.failure;
545
- const detail = `Installed ${ref.skill.name}, but failed to record desired state: ${adapter.describeFailure(failure)}`;
546
- return {
547
- result: "error",
548
- message: detail,
549
- error: new StepFailure({
550
- category: adapter.toStepFailure(failure).category,
551
- detail,
552
- suggestions: [
553
- {
554
- description: "Retry the install after repairing workspace write access.",
555
- cmd: `axm skills install ${baseLockEntry === undefined ? printSourceParams(ref.source) : printSkillLockSourceLocator(ref.skill.name, baseLockEntry)}`,
556
- },
557
- ],
558
- cause: failure,
559
- }),
560
- };
561
- }
562
- const warnings = [unknownAgentWarning, skippedOutcomeWarning].filter((warning) => warning !== undefined);
563
- const sourceDetails = gitHostedSkillArtifactSource(ref);
564
- return {
565
- result: "success",
566
- message: appendWarningsToMessage(`Installed ${ref.skill.name}`, warnings),
567
- artifact: {
568
- path: displayPath.length === 0 ? "." : displayPath,
569
- scope: ws.scope,
570
- agents: artifactAgents,
571
- ...(version !== undefined ? { version } : {}),
572
- change: artifactChange,
573
- ...(previousVersion !== undefined && previousVersion !== version
574
- ? { previousVersion }
575
- : {}),
576
- fileCount,
577
- ...(artifactTargets.length > 0 ? { targets: artifactTargets } : {}),
578
- ...(sourceDetails !== undefined ? { source: sourceDetails } : {}),
579
- },
580
- };
581
- }).pipe(withAdaptedStepFailures);
582
- //# 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