@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
@@ -0,0 +1,29 @@
1
+ import * as Option from "effect/Option";
2
+ const gitHostedSourceOrigin = (ref) => {
3
+ const source = ref.source;
4
+ switch (source.type) {
5
+ case "github":
6
+ case "gitlab":
7
+ case "bitbucket":
8
+ return `${source.url.origin}/${source.owner}/${source.repo}`;
9
+ case "azurerepos":
10
+ return `${source.url.origin}/${source.organization}/${source.project}/_git/${source.repo}`;
11
+ case "git":
12
+ return source.url.href;
13
+ }
14
+ };
15
+ export const gitHostedSkillArtifactSource = (ref) => {
16
+ if (ref.refType !== "git-hosted")
17
+ return undefined;
18
+ const gitTreeHash = ref.gitTreeSha;
19
+ const gitRef = Option.getOrUndefined(ref.source.ref);
20
+ const directory = ref.sourcePath === undefined || ref.sourcePath.length === 0 ? "." : ref.sourcePath;
21
+ return {
22
+ type: ref.source.type,
23
+ origin: gitHostedSourceOrigin(ref),
24
+ ...(gitRef !== undefined ? { ref: gitRef } : {}),
25
+ directory,
26
+ gitTreeHash,
27
+ };
28
+ };
29
+ //# sourceMappingURL=artifact.js.map
@@ -1,3 +1,4 @@
1
+ import { renderAxmSkillRecovery } from "@agentxm/cli-maintenance/official-skill/adapters/cli";
1
2
  /**
2
3
  * Installing the official AXM skill that ships inside the CLI.
3
4
  *
@@ -16,7 +17,7 @@ import * as FileSystem from "effect/FileSystem";
16
17
  import * as Option from "effect/Option";
17
18
  import * as Path from "effect/Path";
18
19
  import { ensureSkillAgentArtifact, replaceCanonicalDirectory, } from "@agentxm/extension-materialization";
19
- import { AXM_SKILL_CLI_VERSION_METADATA_KEY, AXM_SKILL_CLI_VERSION_RANGE_METADATA_KEY, AXM_SKILL_FQN, evaluateAxmSkillCompatibility, } from "@agentxm/extension-resolution";
20
+ import { AXM_SKILL_CLI_VERSION_METADATA_KEY, AXM_SKILL_CLI_VERSION_RANGE_METADATA_KEY, AXM_SKILL_FQN, evaluateAxmSkillCompatibility, } from "@agentxm/cli-maintenance/official-skill/domain";
20
21
  import { operationPresentation, } from "@agentxm/workspace-operations";
21
22
  import { CodingAgentRepository } from "@agentxm/workspace-projection";
22
23
  import { WorkspaceMutations, sanitizeName } from "@agentxm/workspace-state";
@@ -173,13 +174,12 @@ export const installBundledAxmSkill = Effect.gen(function* () {
173
174
  },
174
175
  });
175
176
  if (compatibility.status === "incompatible") {
177
+ const recovery = renderAxmSkillRecovery(compatibility.recovery);
176
178
  return yield* installRefused({
177
179
  category: "internal",
178
180
  detail: compatibility.detail ??
179
181
  "Bundled AXM skill remained incompatible after workspace installation",
180
- ...(compatibility.recovery.nextAction === null
181
- ? {}
182
- : { cmd: compatibility.recovery.nextAction }),
182
+ ...(recovery.nextAction === null ? {} : { cmd: recovery.nextAction }),
183
183
  });
184
184
  }
185
185
  }),
@@ -21,12 +21,11 @@ import { type PackageUrlParts } from "@agentxm/extension-model/unstable/packagin
21
21
  import type { Source } from "@agentxm/extension-model/unstable/sources/types";
22
22
  import type { VersionRange } from "@agentxm/extension-model/unstable/version-constraints";
23
23
  import { type SourceResolutionFailure } from "@agentxm/extension-sources";
24
- import { type Plan } from "@agentxm/workspace-operations";
24
+ import { type Plan, type PlannedJobStep } from "@agentxm/workspace-operations";
25
25
  import type { ExtensionLifecycleFailed } from "../../errors.js";
26
26
  import type { RegistryLookupProbe } from "../../install/registry-source-resolution.js";
27
- import type { ExtensionSelectionCancelled } from "../../install/selection-interaction.js";
28
- import { ExtensionSelectionInteraction } from "../../install/selection-interaction.js";
29
27
  import { type InstallStepRequirements, type ResolveInstallRequirements, type SkillInstallIntent } from "../../install/vocabulary.js";
28
+ import { SkillSelectionInteraction, type SkillSelectionFailure } from "../application/index.js";
30
29
  /** A skill source after grammar parsing, before anything is discovered. */
31
30
  export interface ParsedSkillInstallRequest {
32
31
  readonly source: Source;
@@ -66,7 +65,19 @@ export declare const parseSkillInstallRequest: (args: SkillInstallArgs) => Effec
66
65
  /** Discover the skills the parsed source offers, or refuse when it has none. */
67
66
  export declare const discoverSkillRefs: (request: ParsedSkillInstallRequest) => Effect.Effect<ReadonlyArray<SkillExtensionRef>, ExtensionLifecycleFailed, ResolveInstallRequirements>;
68
67
  /** Settle which of the discovered skills this request installs. */
69
- export declare const finalizeSkillInstallIntent: (request: ParsedSkillInstallRequest, discovered: ReadonlyArray<SkillExtensionRef>) => Effect.Effect<SkillInstallIntent, ExtensionLifecycleFailed | ExtensionSelectionCancelled, ExtensionSelectionInteraction>;
68
+ export declare const finalizeSkillInstallIntent: (request: ParsedSkillInstallRequest, discovered: ReadonlyArray<SkillExtensionRef>) => Effect.Effect<SkillInstallIntent, ExtensionLifecycleFailed | SkillSelectionFailure, SkillSelectionInteraction>;
69
+ /** Installing declares intent; an update advances the resolution of existing intent. */
70
+ export type SkillInstallationStepInput = {
71
+ readonly ref: SkillExtensionRef;
72
+ readonly force: boolean;
73
+ } & ({
74
+ readonly operation: "install";
75
+ readonly versionRange: Option.Option<VersionRange>;
76
+ } | {
77
+ readonly operation: "update";
78
+ });
79
+ /** One skill-owned application step, shared by install and selective update. */
80
+ export declare const planSkillInstallationStep: (input: SkillInstallationStepInput) => Effect.Effect<PlannedJobStep<InstallStepRequirements>, ExtensionLifecycleFailed, InstallStepRequirements | SkillManager | FileSystem.FileSystem | Path.Path | HttpClient.HttpClient>;
70
81
  /** The closures a settled skill intent becomes. */
71
82
  export declare const planSkillInstall: (intent: SkillInstallIntent) => Effect.Effect<Plan<InstallStepRequirements>, ExtensionLifecycleFailed, InstallStepRequirements | SkillManager | FileSystem.FileSystem | Path.Path | HttpClient.HttpClient>;
72
83
  //# sourceMappingURL=plan.d.ts.map
@@ -17,7 +17,8 @@ import * as Option from "effect/Option";
17
17
  import * as Path from "effect/Path";
18
18
  import * as Result from "effect/Result";
19
19
  import * as Schema from "effect/Schema";
20
- import { SkillManager, artifactAgentIdsFromTargets, artifactTargetAgentIds, buildInstallOperation, computeSkillSourceHash, groupInstallTargetsByDirectory, } from "@agentxm/extension-materialization";
20
+ import { SkillManager, artifactAgentIdsFromTargets, artifactTargetAgentIds, computeSkillSourceHash, groupInstallTargetsByDirectory, } from "@agentxm/extension-materialization";
21
+ import { buildInstallOperation } from "@agentxm/workspace-reconciliation";
21
22
  import { matchesReleaseAgeExcludePattern, } from "@agentxm/extension-model/unstable/extensions";
22
23
  import { PackageUrlPartsSchema, formatPackageDisplay, } from "@agentxm/extension-model/unstable/packaging";
23
24
  import { parseInputPattern, } from "@agentxm/extension-model/unstable/sources/parser";
@@ -27,11 +28,10 @@ import { createRegistryClient } from "@agentxm/registry-client";
27
28
  import { operationPresentation, } from "@agentxm/workspace-operations";
28
29
  import { CodingAgentRepository } from "@agentxm/workspace-projection";
29
30
  import { WorkspaceMutations, sanitizeName } from "@agentxm/workspace-state";
30
- import { gitHostedSkillArtifactSource } from "../operations/install.js";
31
+ import { gitHostedSkillArtifactSource } from "./artifact.js";
31
32
  import { lifecycleStepFailure } from "../../step-failure.js";
32
- import { ExtensionSelectionInteraction } from "../../install/selection-interaction.js";
33
33
  import { installRefused, } from "../../install/vocabulary.js";
34
- import { determineSkillsToInstall } from "./selection.js";
34
+ import { determineSkillsToInstall, SkillSelectionInteraction, } from "../application/index.js";
35
35
  import { resolveSkillInstallSource } from "./source.js";
36
36
  const noSkillsFoundHowToFix = (source) => {
37
37
  if (source.type === "registry") {
@@ -316,104 +316,121 @@ const targetChangeBeforeInstall = (args) => Effect.gen(function* () {
316
316
  const exists = yield* fs.exists(args.linkPath).pipe(Effect.catch(() => Effect.succeed(false)));
317
317
  return exists ? "updated" : "created";
318
318
  });
319
- /** The closures a settled skill intent becomes. */
320
- export const planSkillInstall = Effect.fn("InstallExtensions.planSkills")(function* (intent) {
319
+ /** One skill-owned application step, shared by install and selective update. */
320
+ export const planSkillInstallationStep = (input) => Effect.gen(function* () {
321
321
  const ws = yield* WorkspaceMutations;
322
322
  const skillManager = yield* SkillManager;
323
323
  const agentRepo = yield* CodingAgentRepository;
324
324
  const fs = yield* FileSystem.FileSystem;
325
325
  const path = yield* Path.Path;
326
- const steps = yield* Effect.forEach(intent.skillsToInstall, (entry) => Effect.gen(function* () {
327
- const ref = entry.ref;
328
- const previousLockEntry = yield* ws
329
- .getLockedSkill(ref.skill.name)
330
- .pipe(Effect.catch(() => Effect.succeed(Option.none())));
331
- const previousVersion = Option.match(previousLockEntry, {
332
- onNone: () => undefined,
333
- onSome: previousResolvedVersion,
334
- });
335
- const { skillSrcPath } = yield* ws.getSkillDir(ref.skill.name, skillPathSourceFor(ref));
336
- const sourceHashBeforeInstall = Option.match(previousLockEntry, {
337
- onNone: () => undefined,
338
- onSome: previousSourceHash,
339
- }) ??
340
- (yield* Effect.gen(function* () {
341
- const exists = yield* fs
342
- .exists(skillSrcPath)
343
- .pipe(Effect.catch(() => Effect.succeed(false)));
344
- if (!exists)
345
- return undefined;
346
- return yield* computeSkillSourceHash(skillSrcPath);
347
- }));
348
- const configuredAgents = yield* agentRepo.getMaterializationAgents();
349
- const resolvedAgents = yield* Effect.forEach(configuredAgents, (agent) => agent
350
- .resolveEffectiveSkillsDir({ workspaceRoot: ws.baseDir })
351
- .pipe(Effect.map((outcome) => ({ agentId: agent.id, outcome }))), { concurrency: "unbounded" });
352
- const sanitizedName = sanitizeName(ref.skill.name);
353
- const installableTargets = resolvedAgents.flatMap(({ agentId, outcome }) => outcome._tag === "supported"
354
- ? [{ agentId, targetDir: path.normalize(outcome.dir) }]
355
- : []);
356
- const targetLocations = yield* groupInstallTargetsByDirectory(installableTargets, ws.baseDir);
357
- const artifactAgents = artifactAgentIdsFromTargets(installableTargets);
358
- const targets = yield* Effect.forEach(targetLocations, (location) => {
359
- const linkPath = path.join(location.targetDir, sanitizedName);
360
- return targetChangeBeforeInstall({
361
- linkPath,
362
- canonicalSkillSrcPath: skillSrcPath,
363
- }).pipe(Effect.map((change) => {
364
- const agentIds = artifactTargetAgentIds(location.agentIds);
365
- return {
366
- path: path.relative(ws.baseDir, linkPath),
367
- change,
368
- ...(agentIds.length > 0 ? { agentIds } : {}),
369
- };
370
- }));
371
- }, { concurrency: "unbounded" });
372
- const firstTarget = targets[0];
373
- const rawDisplayPath = firstTarget === undefined ? path.relative(ws.baseDir, skillSrcPath) : firstTarget.path;
374
- const version = ref.refType === "registry" ? ref.version : undefined;
375
- const installedBefore = yield* skillManager
376
- .isInstalled({ target: { type: "skill", name: ref.skill.name } })
377
- .pipe(Effect.catch(() => Effect.succeed(false)));
378
- const releaseAgeWarning = yield* brandNewReleaseAgeWarning(ref, installedBefore);
379
- return withPlanWarning(buildInstallOperation(skillManager, {
380
- toStepFailure: lifecycleStepFailure,
381
- ref,
382
- versionRange: entry.versionRange,
383
- force: intent.force === true,
384
- installedBefore: Effect.succeed(installedBefore),
385
- buildArtifact: ({ installedBefore }) => Effect.gen(function* () {
386
- const fileCount = yield* countFiles(fs, path, skillSrcPath);
387
- const currentSourceHash = yield* computeSkillSourceHash(skillSrcPath);
388
- const sameVersion = previousVersion === version;
389
- const sameSource = sourceHashBeforeInstall === currentSourceHash;
390
- const fallbackChange = !installedBefore
391
- ? "created"
392
- : sameVersion && sameSource
393
- ? "unchanged"
394
- : "updated";
395
- const artifactChange = artifactChangeFromTargets(fallbackChange, targets);
396
- const sourceDetails = gitHostedSkillArtifactSource(ref);
397
- return {
398
- path: rawDisplayPath.length === 0 ? "." : rawDisplayPath,
399
- scope: ws.scope,
400
- agents: artifactAgents,
401
- ...(version !== undefined ? { version } : {}),
402
- change: artifactChange,
403
- ...(previousVersion !== undefined && previousVersion !== version
404
- ? { previousVersion }
405
- : {}),
406
- fileCount,
407
- ...(targets.length > 0 ? { targets } : {}),
408
- ...(sourceDetails !== undefined ? { source: sourceDetails } : {}),
409
- };
410
- }),
411
- }), releaseAgeWarning);
412
- }).pipe(Effect.mapError((cause) => installRefused({
413
- category: "internal",
414
- detail: `Skill install planning failed for ${entry.ref.skill.name}`,
415
- cause,
416
- }))), { concurrency: 1 });
326
+ const ref = input.ref;
327
+ const previousLockEntry = yield* ws
328
+ .getLockedSkill(ref.skill.name)
329
+ .pipe(Effect.catch(() => Effect.succeed(Option.none())));
330
+ const previousVersion = Option.match(previousLockEntry, {
331
+ onNone: () => undefined,
332
+ onSome: previousResolvedVersion,
333
+ });
334
+ const { skillSrcPath } = yield* ws.getSkillDir(ref.skill.name, skillPathSourceFor(ref));
335
+ const sourceHashBeforeInstall = Option.match(previousLockEntry, {
336
+ onNone: () => undefined,
337
+ onSome: previousSourceHash,
338
+ }) ??
339
+ (yield* Effect.gen(function* () {
340
+ const exists = yield* fs
341
+ .exists(skillSrcPath)
342
+ .pipe(Effect.catch(() => Effect.succeed(false)));
343
+ if (!exists)
344
+ return undefined;
345
+ return yield* computeSkillSourceHash(skillSrcPath);
346
+ }));
347
+ const configuredAgents = yield* agentRepo.getMaterializationAgents();
348
+ const resolvedAgents = yield* Effect.forEach(configuredAgents, (agent) => agent
349
+ .resolveEffectiveSkillsDir({ workspaceRoot: ws.baseDir })
350
+ .pipe(Effect.map((outcome) => ({ agentId: agent.id, outcome }))), { concurrency: "unbounded" });
351
+ const unknownAgents = yield* agentRepo.getUnknownConfiguredAgentIds();
352
+ const skippedAgents = resolvedAgents.flatMap(({ agentId, outcome }) => outcome._tag === "unsupported" || outcome._tag === "disabled"
353
+ ? [`${agentId}: ${outcome.reason}`]
354
+ : []);
355
+ const warnings = [
356
+ ...(unknownAgents.length === 0
357
+ ? []
358
+ : [`Skipping unknown configured agents: ${unknownAgents.join(", ")}`]),
359
+ ...(skippedAgents.length === 0
360
+ ? []
361
+ : [`Skipping non-installable configured agents: ${skippedAgents.join(", ")}`]),
362
+ ];
363
+ const sanitizedName = sanitizeName(ref.skill.name);
364
+ const installableTargets = resolvedAgents.flatMap(({ agentId, outcome }) => outcome._tag === "supported" ? [{ agentId, targetDir: path.normalize(outcome.dir) }] : []);
365
+ const targetLocations = yield* groupInstallTargetsByDirectory(installableTargets, ws.baseDir);
366
+ const artifactAgents = artifactAgentIdsFromTargets(installableTargets);
367
+ const targets = yield* Effect.forEach(targetLocations, (location) => {
368
+ const linkPath = path.join(location.targetDir, sanitizedName);
369
+ return targetChangeBeforeInstall({
370
+ linkPath,
371
+ canonicalSkillSrcPath: skillSrcPath,
372
+ }).pipe(Effect.map((change) => {
373
+ const agentIds = artifactTargetAgentIds(location.agentIds);
374
+ return {
375
+ path: path.relative(ws.baseDir, linkPath),
376
+ change,
377
+ ...(agentIds.length > 0 ? { agentIds } : {}),
378
+ };
379
+ }));
380
+ }, { concurrency: "unbounded" });
381
+ const firstTarget = targets[0];
382
+ const rawDisplayPath = firstTarget === undefined ? path.relative(ws.baseDir, skillSrcPath) : firstTarget.path;
383
+ const version = ref.refType === "registry" ? ref.version : undefined;
384
+ const installedBefore = yield* skillManager
385
+ .isInstalled({ target: { type: "skill", name: ref.skill.name } })
386
+ .pipe(Effect.catch(() => Effect.succeed(false)));
387
+ const releaseAgeWarning = input.operation === "install"
388
+ ? yield* brandNewReleaseAgeWarning(ref, installedBefore)
389
+ : Option.none();
390
+ const step = withPlanWarning(buildInstallOperation(skillManager, {
391
+ toStepFailure: lifecycleStepFailure,
392
+ ref,
393
+ ...(input.operation === "install"
394
+ ? { declaration: { name: ref.skill.name, versionRange: input.versionRange } }
395
+ : {}),
396
+ force: input.force,
397
+ installedBefore: Effect.succeed(installedBefore),
398
+ buildArtifact: ({ installedBefore }) => Effect.gen(function* () {
399
+ const fileCount = yield* countFiles(fs, path, skillSrcPath);
400
+ const currentSourceHash = yield* computeSkillSourceHash(skillSrcPath);
401
+ const sameVersion = previousVersion === version;
402
+ const sameSource = sourceHashBeforeInstall === currentSourceHash;
403
+ const fallbackChange = !installedBefore
404
+ ? "created"
405
+ : sameVersion && sameSource
406
+ ? "unchanged"
407
+ : "updated";
408
+ const artifactChange = artifactChangeFromTargets(fallbackChange, targets);
409
+ const sourceDetails = gitHostedSkillArtifactSource(ref);
410
+ return {
411
+ path: rawDisplayPath.length === 0 ? "." : rawDisplayPath,
412
+ scope: ws.scope,
413
+ agents: artifactAgents,
414
+ ...(version !== undefined ? { version } : {}),
415
+ change: artifactChange,
416
+ ...(previousVersion !== undefined && previousVersion !== version
417
+ ? { previousVersion }
418
+ : {}),
419
+ fileCount,
420
+ ...(targets.length > 0 ? { targets } : {}),
421
+ ...(sourceDetails !== undefined ? { source: sourceDetails } : {}),
422
+ };
423
+ }),
424
+ }), releaseAgeWarning);
425
+ return warnings.length === 0 ? step : withPlanWarning(step, Option.some(warnings.join("; ")));
426
+ }).pipe(Effect.mapError((cause) => installRefused({
427
+ category: "internal",
428
+ detail: `Skill install planning failed for ${input.ref.skill.name}`,
429
+ cause,
430
+ })), Effect.withSpan("SkillInstallation.planStep"));
431
+ /** The closures a settled skill intent becomes. */
432
+ export const planSkillInstall = Effect.fn("InstallExtensions.planSkills")(function* (intent) {
433
+ const steps = yield* Effect.forEach(intent.skillsToInstall, (entry) => planSkillInstallationStep({ ...entry, operation: "install", force: intent.force === true }), { concurrency: 1 });
417
434
  return {
418
435
  _tag: "Plan",
419
436
  name: intent.skillsToInstall.length === 0
@@ -12,14 +12,16 @@ import * as Effect from "effect/Effect";
12
12
  import * as FileSystem from "effect/FileSystem";
13
13
  import * as Option from "effect/Option";
14
14
  import * as Path from "effect/Path";
15
- import { SkillManager, buildUninstallOperation, skillArtifactFromTargets, } from "@agentxm/extension-materialization";
15
+ import { SkillManager, skillArtifactFromTargets, } from "@agentxm/extension-materialization";
16
+ import { buildUninstallOperation } from "@agentxm/workspace-reconciliation";
16
17
  import { resolveInstalledIdentifierNameOrInput } from "@agentxm/extension-sources";
18
+ import { parseExtensionFqnParts } from "@agentxm/extension-model/unstable/extensions";
17
19
  import { CodingAgentRepository } from "@agentxm/workspace-projection";
18
20
  import { WorkspaceMutations, acquiredExtensionDisplayPathFromLockEntry, installedRowsByName, sanitizeName, } from "@agentxm/workspace-state";
19
- import { expandGlob } from "../../glob.js";
21
+ import { expandGlob } from "@agentxm/extension-model/unstable/extensions/name-patterns";
20
22
  import { lifecycleStepFailure } from "../../step-failure.js";
21
23
  import { installRefused, } from "../../install/vocabulary.js";
22
- import { makeWorkspaceRetentionPolicy } from "../../uninstall/retention-policy.js";
24
+ import { makeWorkspaceRetentionPolicy } from "@agentxm/workspace-reconciliation";
23
25
  import { workspaceAuthoredPath, workspaceCanonicalPath, workspaceCanonicalRoot, workspaceLockfilePath, workspaceSettingsPath, } from "../../workspace-paths.js";
24
26
  const skillSourceTarget = (ws, path, configuredSource, lockEntry, sanitizedName) => {
25
27
  if (Option.isSome(configuredSource) && configuredSource.value === "workspace") {
@@ -47,13 +49,19 @@ export const parseSkillUninstallRequest = Effect.fn("UninstallExtensions.parseSk
47
49
  if (selector.includes("*") && matched.length === 0) {
48
50
  return { targets: [] };
49
51
  }
52
+ // An identifier nothing installed answers to still names one skill: its
53
+ // extension name, never the fully qualified spelling as a name.
54
+ const unresolvedName = (input) => {
55
+ const fqn = parseExtensionFqnParts(input);
56
+ return fqn?.type === "skill" ? fqn.name : input;
57
+ };
50
58
  const names = matched.length > 0
51
59
  ? matched
52
60
  : [
53
61
  yield* resolveInstalledIdentifierNameOrInput({
54
62
  input: selector,
55
63
  resourceType: "skill",
56
- }).pipe(Effect.mapError((cause) => installRefused({
64
+ }).pipe(Effect.map((name) => (name === selector.trim() ? unresolvedName(name) : name)), Effect.mapError((cause) => installRefused({
57
65
  category: "not_found",
58
66
  detail: `Skill "${selector}" could not be resolved`,
59
67
  cause,
@@ -69,7 +77,7 @@ export const planSkillUninstall = Effect.fn("UninstallExtensions.planSkills")(fu
69
77
  const skillManager = yield* SkillManager;
70
78
  const agentRepo = yield* CodingAgentRepository;
71
79
  const path = yield* Path.Path;
72
- const retentionPolicy = makeWorkspaceRetentionPolicy(ws);
80
+ const retentionPolicy = makeWorkspaceRetentionPolicy(ws, lifecycleStepFailure);
73
81
  const configuredAgents = yield* agentRepo.getMaterializationAgents().pipe(Effect.mapError((cause) => installRefused({
74
82
  category: "internal",
75
83
  detail: "Configured agents could not be read",
@@ -85,6 +85,18 @@ const carriedFailure = (failure) => {
85
85
  export const lifecycleStepFailure = (failure) => {
86
86
  if (failure instanceof StepFailure)
87
87
  return failure;
88
+ if (failure._tag === "McpSharedTargetConflict") {
89
+ return new StepFailure({
90
+ category: "conflict",
91
+ detail: failure.reason,
92
+ suggestions: [
93
+ {
94
+ description: "Use an MCP package whose transport and symbolic inputs are supported by every configured reader of the shared target.",
95
+ },
96
+ ],
97
+ cause: failure,
98
+ });
99
+ }
88
100
  if (failure instanceof ExtensionLifecycleFailed) {
89
101
  return new StepFailure({
90
102
  category: failure.category,
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Subagents selection application contract and workflow.
3
+ * @experimental This API is unstable and may change without notice.
4
+ */
5
+ import * as Array from "effect/Array";
6
+ import * as Context from "effect/Context";
7
+ import * as Effect from "effect/Effect";
8
+ import type { SubagentExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/subagent";
9
+ import { type SubagentSelectionRequest } from "../domain/selection.js";
10
+ declare const SubagentSelectionNotFound_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
11
+ readonly _tag: "SubagentSelectionNotFound";
12
+ } & Readonly<A>;
13
+ export declare class SubagentSelectionNotFound extends SubagentSelectionNotFound_base<{
14
+ readonly requested: ReadonlyArray<string>;
15
+ readonly available: ReadonlyArray<string>;
16
+ }> {
17
+ }
18
+ declare const SubagentSelectionCancelled_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
19
+ readonly _tag: "SubagentSelectionCancelled";
20
+ } & Readonly<A>;
21
+ export declare class SubagentSelectionCancelled extends SubagentSelectionCancelled_base<{
22
+ readonly message: string;
23
+ }> {
24
+ }
25
+ declare const SubagentSelectionUnavailable_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
26
+ readonly _tag: "SubagentSelectionUnavailable";
27
+ } & Readonly<A>;
28
+ /** The interface could not obtain a choice; this is distinct from cancellation. */
29
+ export declare class SubagentSelectionUnavailable extends SubagentSelectionUnavailable_base<{
30
+ readonly cause?: unknown;
31
+ }> {
32
+ }
33
+ declare const SubagentSelectionInteraction_base: Context.ServiceClass<SubagentSelectionInteraction, "@agentxm/extension-lifecycle/subagents/SubagentSelectionInteraction", {
34
+ readonly select: (candidates: Array.NonEmptyReadonlyArray<SubagentExtensionRef>) => Effect.Effect<ReadonlyArray<SubagentExtensionRef>, SubagentSelectionCancelled | SubagentSelectionUnavailable>;
35
+ }>;
36
+ export declare class SubagentSelectionInteraction extends SubagentSelectionInteraction_base {
37
+ }
38
+ export type SubagentSelectionFailure = SubagentSelectionNotFound | SubagentSelectionCancelled | SubagentSelectionUnavailable;
39
+ /** Resolve the subagent-owned decision through this capability's interaction port. */
40
+ export declare const determineSubagentsToInstall: (candidates: readonly [SubagentExtensionRef, ...SubagentExtensionRef[]], request: SubagentSelectionRequest) => Effect.Effect<readonly SubagentExtensionRef[], SubagentSelectionFailure, SubagentSelectionInteraction>;
41
+ export {};
42
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Subagents selection application contract and workflow.
3
+ * @experimental This API is unstable and may change without notice.
4
+ */
5
+ import * as Array from "effect/Array";
6
+ import * as Context from "effect/Context";
7
+ import * as Data from "effect/Data";
8
+ import * as Effect from "effect/Effect";
9
+ import { decideSubagentSelection } from "../domain/selection.js";
10
+ export class SubagentSelectionNotFound extends Data.TaggedError("SubagentSelectionNotFound") {
11
+ }
12
+ export class SubagentSelectionCancelled extends Data.TaggedError("SubagentSelectionCancelled") {
13
+ }
14
+ /** The interface could not obtain a choice; this is distinct from cancellation. */
15
+ export class SubagentSelectionUnavailable extends Data.TaggedError("SubagentSelectionUnavailable") {
16
+ }
17
+ export class SubagentSelectionInteraction extends Context.Service()("@agentxm/extension-lifecycle/subagents/SubagentSelectionInteraction") {
18
+ }
19
+ /** Resolve the subagent-owned decision through this capability's interaction port. */
20
+ export const determineSubagentsToInstall = Effect.fn("Subagents.determineSelection")(function* (candidates, request) {
21
+ const names = Array.map(candidates, (ref) => ref.subagent.name);
22
+ const decision = decideSubagentSelection(names, request);
23
+ switch (decision.kind) {
24
+ case "selected":
25
+ return candidates.filter((ref) => decision.names.includes(ref.subagent.name));
26
+ case "unmatched":
27
+ return yield* new SubagentSelectionNotFound({
28
+ requested: request.requestedSubagents,
29
+ available: names,
30
+ });
31
+ case "choice-required": {
32
+ const interaction = yield* SubagentSelectionInteraction;
33
+ return yield* interaction.select(candidates);
34
+ }
35
+ }
36
+ });
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,22 @@
1
+ import type * as Array from "effect/Array";
2
+ export interface SubagentSelectionRequest {
3
+ readonly requestedSubagents: ReadonlyArray<string>;
4
+ readonly all: boolean;
5
+ readonly nonInteractive: boolean;
6
+ }
7
+ export type SubagentSelectionDecision = {
8
+ readonly kind: "selected";
9
+ readonly names: ReadonlyArray<string>;
10
+ } | {
11
+ readonly kind: "choice-required";
12
+ } | {
13
+ readonly kind: "unmatched";
14
+ };
15
+ /**
16
+ * Select this request's subagents, preserving source order.
17
+ * Named patterns take precedence over all/unattended selection. The existing
18
+ * union-of-matches behavior is retained; unresolved selector requirements remain
19
+ * explicit in the installation specification.
20
+ */
21
+ export declare const decideSubagentSelection: (names: Array.NonEmptyReadonlyArray<string>, request: SubagentSelectionRequest) => SubagentSelectionDecision;
22
+ //# sourceMappingURL=selection.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { expandGlobs } from "@agentxm/extension-model/unstable/extensions/name-patterns";
2
+ /**
3
+ * Select this request's subagents, preserving source order.
4
+ * Named patterns take precedence over all/unattended selection. The existing
5
+ * union-of-matches behavior is retained; unresolved selector requirements remain
6
+ * explicit in the installation specification.
7
+ */
8
+ export const decideSubagentSelection = (names, request) => {
9
+ if (request.requestedSubagents.length > 0) {
10
+ const matched = expandGlobs(request.requestedSubagents, names);
11
+ return matched.length === 0 ? { kind: "unmatched" } : { kind: "selected", names: matched };
12
+ }
13
+ if (request.all || request.nonInteractive || names.length === 1) {
14
+ return { kind: "selected", names };
15
+ }
16
+ return { kind: "choice-required" };
17
+ };
18
+ //# sourceMappingURL=selection.js.map
@@ -18,8 +18,8 @@ import { type SourceResolutionFailure } from "@agentxm/extension-sources";
18
18
  import { type Plan } from "@agentxm/workspace-operations";
19
19
  import type { ExtensionLifecycleFailed } from "../../errors.js";
20
20
  import { type RegistryLookupProbe } from "../../install/registry-source-resolution.js";
21
- import { ExtensionSelectionInteraction, type ExtensionSelectionCancelled } from "../../install/selection-interaction.js";
22
21
  import { type InstallStepRequirements, type ResolveInstallRequirements, type SubagentInstallIntent } from "../../install/vocabulary.js";
22
+ import { SubagentSelectionInteraction, type SubagentSelectionFailure } from "../application/index.js";
23
23
  /** A subagent source after grammar parsing, before anything is discovered. */
24
24
  export interface ParsedSubagentInstallRequest {
25
25
  readonly source: Source;
@@ -43,7 +43,7 @@ export declare const parseSubagentInstallRequest: (args: SubagentInstallArgs) =>
43
43
  /** Discover the subagents the parsed source offers, or refuse when it has none. */
44
44
  export declare const discoverSubagentRefs: (request: ParsedSubagentInstallRequest) => Effect.Effect<ReadonlyArray<SubagentExtensionRef>, ExtensionLifecycleFailed, ResolveInstallRequirements>;
45
45
  /** Settle which of the discovered subagents this request installs. */
46
- export declare const finalizeSubagentInstallIntent: (request: ParsedSubagentInstallRequest, discovered: ReadonlyArray<SubagentExtensionRef>) => Effect.Effect<SubagentInstallIntent, ExtensionLifecycleFailed | ExtensionSelectionCancelled, ExtensionSelectionInteraction>;
46
+ export declare const finalizeSubagentInstallIntent: (request: ParsedSubagentInstallRequest, discovered: ReadonlyArray<SubagentExtensionRef>) => Effect.Effect<SubagentInstallIntent, ExtensionLifecycleFailed | SubagentSelectionFailure, SubagentSelectionInteraction>;
47
47
  /** The closures a settled subagent intent becomes. */
48
48
  export declare const planSubagentInstall: (intent: SubagentInstallIntent) => Effect.Effect<Plan<InstallStepRequirements>, ExtensionLifecycleFailed, InstallStepRequirements | SubagentManager>;
49
49
  //# sourceMappingURL=plan.d.ts.map
@@ -10,7 +10,8 @@
10
10
  import * as Array from "effect/Array";
11
11
  import * as Effect from "effect/Effect";
12
12
  import * as Option from "effect/Option";
13
- import { NO_MATERIALIZATION_OBSERVATION, SubagentManager, buildInstallOperation, } from "@agentxm/extension-materialization";
13
+ import { NO_MATERIALIZATION_OBSERVATION, SubagentManager, } from "@agentxm/extension-materialization";
14
+ import { buildInstallOperation } from "@agentxm/workspace-reconciliation";
14
15
  import { parseInputPattern, } from "@agentxm/extension-model/unstable/sources/parser";
15
16
  import { SourceHostProviders, sourceResolutionFailureCategory, } from "@agentxm/extension-sources";
16
17
  import { operationPresentation, } from "@agentxm/workspace-operations";
@@ -18,9 +19,8 @@ import { CodingAgentRepository } from "@agentxm/workspace-projection";
18
19
  import { WorkspaceMutations } from "@agentxm/workspace-state";
19
20
  import { lifecycleStepFailure } from "../../step-failure.js";
20
21
  import { sourceFailureDetail, } from "../../install/registry-source-resolution.js";
21
- import { ExtensionSelectionInteraction, } from "../../install/selection-interaction.js";
22
22
  import { installRefused, } from "../../install/vocabulary.js";
23
- import { determineSubagentsToInstall } from "./selection.js";
23
+ import { determineSubagentsToInstall, SubagentSelectionInteraction, } from "../application/index.js";
24
24
  import { resolveSubagentInstallSource } from "./source.js";
25
25
  const isRemoteReadNotImplemented = (error) => sourceFailureDetail(error).includes("not implemented");
26
26
  const discoverHowToFix = (source, error) => {
@@ -216,7 +216,7 @@ export const planSubagentInstall = Effect.fn("InstallExtensions.planSubagents")(
216
216
  return buildInstallOperation(subagentManager, {
217
217
  toStepFailure: lifecycleStepFailure,
218
218
  ref,
219
- versionRange: entry.versionRange,
219
+ declaration: { name: ref.subagent.name, versionRange: entry.versionRange },
220
220
  installedBefore: subagentManager
221
221
  .isInstalled({ target: { type: "subagent", name: ref.subagent.name } })
222
222
  .pipe(Effect.catch(() => Effect.succeed(false))),
@@ -64,9 +64,7 @@ export const enableSubagent = (op) => Effect.gen(function* () {
64
64
  });
65
65
  }
66
66
  const baseDir = ws.baseDir;
67
- const subagentSrcPath = canonical.value.ref.refType === "registry" || canonical.value.ref.refType === "workspace"
68
- ? path.join(canonical.value.observation.path, "src")
69
- : canonical.value.observation.path;
67
+ const subagentSrcPath = path.join(canonical.value.observation.path, "src");
70
68
  // Read and parse the subagent content file
71
69
  const expectedFilename = subagentContentFilename(op.args.subagentName);
72
70
  const contentPath = subagentContentPath(path.join, subagentSrcPath, op.args.subagentName);