@agentxm/extension-lifecycle 0.29.2 → 0.30.1

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 (55) hide show
  1. package/dist/src/activation/set-activation.d.ts +22 -40
  2. package/dist/src/activation/set-activation.js +199 -331
  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/configured-agent-outcomes-provider.d.ts +4 -4
  6. package/dist/src/hooks/configured-agent-outcomes-provider.js +23 -17
  7. package/dist/src/hooks/install/plan.js +6 -3
  8. package/dist/src/hooks/uninstall/plan.js +4 -3
  9. package/dist/src/index.d.ts +1 -2
  10. package/dist/src/index.js +1 -2
  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/live.d.ts +1 -1
  16. package/dist/src/live.js +1 -1
  17. package/dist/src/mcps/install/plan.js +2 -3
  18. package/dist/src/mcps/operations/disable.js +1 -1
  19. package/dist/src/mcps/operations/enable.js +20 -35
  20. package/dist/src/mcps/operations/uninstall.js +2 -1
  21. package/dist/src/mcps/uninstall/plan.js +13 -4
  22. package/dist/src/packs/graph-transition.d.ts +0 -25
  23. package/dist/src/packs/graph-transition.js +0 -101
  24. package/dist/src/packs/install/plan.d.ts +0 -13
  25. package/dist/src/packs/install/plan.js +12 -9
  26. package/dist/src/packs/member-install-step.js +8 -21
  27. package/dist/src/packs/promote-authored-pack.js +6 -3
  28. package/dist/src/packs/uninstall/plan.js +52 -20
  29. package/dist/src/rules/install/plan.js +6 -3
  30. package/dist/src/rules/uninstall/plan.js +4 -3
  31. package/dist/src/skills/install/bundled.js +4 -4
  32. package/dist/src/skills/install/plan.js +3 -2
  33. package/dist/src/skills/operations/install.d.ts +0 -1
  34. package/dist/src/skills/operations/install.js +9 -18
  35. package/dist/src/skills/uninstall/plan.js +4 -3
  36. package/dist/src/subagents/install/plan.js +3 -2
  37. package/dist/src/subagents/operations/enable.js +1 -3
  38. package/dist/src/subagents/uninstall/plan.js +4 -3
  39. package/dist/src/testing.d.ts +1 -1
  40. package/dist/src/testing.js +2 -1
  41. package/dist/src/uninstall/root-intent.d.ts +1 -1
  42. package/dist/src/uninstall/uninstall-extensions.d.ts +1 -1
  43. package/dist/src/uninstall/uninstall-extensions.js +77 -1
  44. package/dist/src/update/root-request.d.ts +2 -2
  45. package/dist/src/update/selective/skills.d.ts +1 -1
  46. package/dist/src/update/selective/skills.js +0 -1
  47. package/dist/src/update/selective/subagents.d.ts +1 -1
  48. package/dist/src/update/selective/subagents.js +3 -2
  49. package/dist/src/update/selective/use-case.d.ts +2 -2
  50. package/dist/src/update/targeted-plan.js +4 -2
  51. package/dist/src/workspace-paths.d.ts +0 -6
  52. package/dist/src/workspace-paths.js +0 -10
  53. package/package.json +22 -24
  54. package/dist/src/uninstall/retention-policy.d.ts +0 -21
  55. package/dist/src/uninstall/retention-policy.js +0 -28
@@ -11,109 +11,8 @@
11
11
  */
12
12
  import * as Effect from "effect/Effect";
13
13
  import { WorkspaceMutations, desiredStateProblemsText, } from "@agentxm/workspace-state";
14
- import { runWorkspaceTransaction } from "@agentxm/workspace-transactions";
15
- import { StepFailure, } from "@agentxm/workspace-operations";
16
- import { lifecycleStepFailure } from "../step-failure.js";
17
14
  import { installRefused } from "../install/vocabulary.js";
18
15
  const normalizedIdentity = (identity) => identity.startsWith("workspace:") ? identity.slice("workspace:".length) : identity;
19
- const failedStep = (label, result) => result.result === "error"
20
- ? Effect.fail(result.error ??
21
- lifecycleStepFailure(installRefused({
22
- category: "internal",
23
- detail: `${label} failed: ${result.message}`,
24
- })))
25
- : Effect.succeed(result);
26
- const aggregatePackCoverage = (results, scope) => Effect.gen(function* () {
27
- const applicableArtifacts = results.flatMap(({ result, coverage }) => coverage === "eligible" &&
28
- result.result === "success" &&
29
- result.artifact?.agents !== undefined
30
- ? [result.artifact]
31
- : []);
32
- const agents = [];
33
- for (const artifact of applicableArtifacts) {
34
- if (artifact.scope !== scope) {
35
- return yield* installRefused({
36
- category: "internal",
37
- detail: `Pack coverage spans ${scope} and ${artifact.scope} scopes`,
38
- });
39
- }
40
- const artifactAgents = new Set(artifact.agents);
41
- for (const target of artifact.targets ?? []) {
42
- for (const agent of target.agentIds ?? []) {
43
- if (!artifactAgents.has(agent)) {
44
- return yield* installRefused({
45
- category: "internal",
46
- detail: `Pack child target agent ${agent} is absent from its artifact agents`,
47
- });
48
- }
49
- }
50
- }
51
- for (const agent of artifact.agents ?? []) {
52
- if (agent !== "universal" && !agents.includes(agent))
53
- agents.push(agent);
54
- }
55
- }
56
- return { applicable: applicableArtifacts.length > 0, agents };
57
- });
58
- /** Wrap a pack transition's children in one workspace transaction. */
59
- export const buildAtomicPackGraphStep = (args) => Effect.sync(() => {
60
- const readinessErrors = args.children.flatMap(({ step }) => step.readiness === "error" ? [step.errorMessage] : []);
61
- if (readinessErrors.length > 0) {
62
- const blockingConditionIds = args.children.flatMap(({ step }) => step.readiness === "error" ? (step.blockingConditionIds ?? []) : []);
63
- return {
64
- readiness: "error",
65
- label: args.label,
66
- errorMessage: readinessErrors.join("; "),
67
- artifact: args.artifact,
68
- ...(blockingConditionIds.length === 0 ? {} : { blockingConditionIds }),
69
- };
70
- }
71
- const readinessWarnings = args.children.flatMap(({ step }) => step.readiness === "warn" ? [step.warnMessage] : []);
72
- const runnableChildren = args.children.filter((child) => child.step.readiness !== "error");
73
- let validatedCoverage = { applicable: false, agents: [] };
74
- const run = runWorkspaceTransaction({
75
- transition: Effect.gen(function* () {
76
- if (args.preTransition !== undefined) {
77
- yield* args.preTransition.pipe(Effect.mapError(lifecycleStepFailure));
78
- }
79
- return yield* Effect.forEach(runnableChildren, ({ step, coverage }) => step.run.pipe(Effect.flatMap((result) => failedStep(step.label, result)), Effect.map((result) => ({ result, coverage }))), { concurrency: 1 });
80
- }),
81
- validate: (results) => Effect.gen(function* () {
82
- yield* args.validate;
83
- validatedCoverage = yield* aggregatePackCoverage(results, args.artifact.scope);
84
- }).pipe(Effect.mapError(lifecycleStepFailure)),
85
- }).pipe(Effect.mapError(lifecycleStepFailure), Effect.map((results) => {
86
- const warnings = results.flatMap(({ result }) => result.result === "success" ? (result.warnings ?? []) : []);
87
- const allChildrenUnchanged = args.reportUnchangedWhenChildrenUnchanged === true &&
88
- results.length > 0 &&
89
- results.every(({ result }) => result.result === "success" && result.artifact?.change === "unchanged");
90
- const artifact = allChildrenUnchanged
91
- ? { ...args.artifact, change: "unchanged" }
92
- : args.artifact;
93
- return {
94
- result: "success",
95
- message: args.message,
96
- artifact: !validatedCoverage.applicable
97
- ? artifact
98
- : { ...artifact, agents: validatedCoverage.agents },
99
- ...(warnings.length === 0 ? {} : { warnings }),
100
- };
101
- }));
102
- return readinessWarnings.length === 0
103
- ? {
104
- readiness: "ready",
105
- label: args.label,
106
- artifact: args.artifact,
107
- run,
108
- }
109
- : {
110
- readiness: "warn",
111
- label: args.label,
112
- warnMessage: readinessWarnings.join("; "),
113
- artifact: args.artifact,
114
- run,
115
- };
116
- });
117
16
  /** The desired-graph predicate a completed pack transition must satisfy. */
118
17
  export const validatePackGraphPostcondition = (args) => Effect.gen(function* () {
119
18
  const ws = yield* WorkspaceMutations;
@@ -1,16 +1,3 @@
1
- /**
2
- * Installing a pack.
3
- *
4
- * A pack install is a graph transition, not a package copy: the pack's own
5
- * package, every member its manifest resolves to, and every member the
6
- * previous graph owned and this one drops all change together. Workspace
7
- * source authority decides whether a locally-authored package may stand in
8
- * for a declared member, the minimum-release-age policy decides whether a
9
- * held-back release preserves the current graph or blocks, and the transition
10
- * is refused if the authority it was planned against changed underneath it.
11
- *
12
- * @experimental This API is unstable and may change without notice.
13
- */
14
1
  import * as Effect from "effect/Effect";
15
2
  import * as Option from "effect/Option";
16
3
  import * as Path from "effect/Path";
@@ -1,3 +1,4 @@
1
+ import { buildReconciliationClosure } from "@agentxm/workspace-reconciliation";
1
2
  /**
2
3
  * Installing a pack.
3
4
  *
@@ -16,7 +17,8 @@ import * as Effect from "effect/Effect";
16
17
  import * as Option from "effect/Option";
17
18
  import * as Path from "effect/Path";
18
19
  import * as Result from "effect/Result";
19
- import { HookManager, KnowledgeManager, McpServerManager, PackManager, RuleManager, SkillManager, SubagentManager, buildInstallOperation, buildUninstallOperation, targetFromRef, toLabel, } from "@agentxm/extension-materialization";
20
+ import { HookManager, KnowledgeManager, McpServerManager, PackManager, RuleManager, SkillManager, SubagentManager, } from "@agentxm/extension-materialization";
21
+ import { buildInstallOperation, buildUninstallOperation, targetFromRef, toLabel, } from "@agentxm/workspace-reconciliation";
20
22
  import { parseExtensionFqnParts, toExtensionTypePlural, } from "@agentxm/extension-model/unstable/extensions";
21
23
  import { isWorkspaceSourceLocator } from "@agentxm/extension-model/unstable/sources/workspace";
22
24
  import { versionSatisfiesRange, } from "@agentxm/extension-model/unstable/version-constraints";
@@ -31,10 +33,10 @@ import { registryLoginSuggestions } from "../../install/registry-login-suggestio
31
33
  import { formatRegistryProbe, sourceFailureDetail, } from "../../install/registry-source-resolution.js";
32
34
  import { installRefused, } from "../../install/vocabulary.js";
33
35
  import { expandPackInstallRefs, expandPackInstallRefsWithReleaseAge } from "../expansion.js";
34
- import { buildAtomicPackGraphStep, validatePackGraphPostcondition } from "../graph-transition.js";
36
+ import { validatePackGraphPostcondition } from "../graph-transition.js";
35
37
  import { buildPackMemberInstallStep } from "../member-install-step.js";
36
38
  import { registrySourceArtifact, registrySourcePath } from "../artifact.js";
37
- import { makeWorkspaceRetentionPolicy } from "../../uninstall/retention-policy.js";
39
+ import { makeWorkspaceRetentionPolicy } from "@agentxm/workspace-reconciliation";
38
40
  const isRemoteReadNotImplemented = (error) => {
39
41
  const detail = sourceFailureDetail(error);
40
42
  return detail.includes("not implemented") || detail.includes("not yet supported");
@@ -661,12 +663,12 @@ export const planPackInstall = Effect.fn("InstallExtensions.planPack")(function*
661
663
  // cleanup by suppressing dropped-member removal until the pre-install graph
662
664
  // is complete.
663
665
  const existingPack = graph.complete ? currentPackNode : undefined;
664
- const retentionPolicy = makeWorkspaceRetentionPolicy(ws);
666
+ const retentionPolicy = makeWorkspaceRetentionPolicy(ws, lifecycleStepFailure);
665
667
  const installSteps = yield* Effect.forEach(refs, (ref) => ref.type === "pack"
666
668
  ? Effect.succeed(buildInstallOperation(packManager, {
667
669
  toStepFailure: lifecycleStepFailure,
668
670
  ref,
669
- versionRange: intent.versionRange,
671
+ declaration: { name: ref.pack.name, versionRange: intent.versionRange },
670
672
  ...(intent.forceCanonical === true ? { force: true } : {}),
671
673
  installedBefore: graph.complete
672
674
  ? packManager.isInstalled({
@@ -724,19 +726,19 @@ export const planPackInstall = Effect.fn("InstallExtensions.planPack")(function*
724
726
  return buildUninstallOperation(ruleManager, retentionPolicy, {
725
727
  toStepFailure: lifecycleStepFailure,
726
728
  target,
727
- skipProjections: true,
729
+ enclosingClosure: { projections: [target.type] },
728
730
  });
729
731
  case "hook":
730
732
  return buildUninstallOperation(hookManager, retentionPolicy, {
731
733
  toStepFailure: lifecycleStepFailure,
732
734
  target,
733
- skipProjections: true,
735
+ enclosingClosure: { projections: [target.type] },
734
736
  });
735
737
  case "knowledge":
736
738
  return buildUninstallOperation(knowledgeManager, retentionPolicy, {
737
739
  toStepFailure: lifecycleStepFailure,
738
740
  target,
739
- skipProjections: true,
741
+ enclosingClosure: { projections: [target.type] },
740
742
  });
741
743
  }
742
744
  });
@@ -777,7 +779,8 @@ export const planPackInstall = Effect.fn("InstallExtensions.planPack")(function*
777
779
  ...droppedTargets.map(({ target }) => target.type),
778
780
  ]),
779
781
  });
780
- const graphStep = yield* buildAtomicPackGraphStep({
782
+ const graphStep = yield* buildReconciliationClosure({
783
+ toStepFailure: lifecycleStepFailure,
781
784
  label: packIdentity,
782
785
  message: `Installed ${packIdentity} and ${refs.length - 1} pack member${refs.length === 2 ? "" : "s"}`,
783
786
  artifact: {
@@ -10,7 +10,9 @@
10
10
  */
11
11
  import * as Effect from "effect/Effect";
12
12
  import * as Option from "effect/Option";
13
- import { HookManager, KnowledgeManager, NO_MATERIALIZATION_OBSERVATION, RuleManager, SkillManager, SubagentManager, buildInstallOperation, extensionRefLifecycleWarnings, extensionRefRegistryLifecycle, installMcpServer, toLabelWithCompanions, } from "@agentxm/extension-materialization";
13
+ import { HookManager, KnowledgeManager, NO_MATERIALIZATION_OBSERVATION, RuleManager, SkillManager, SubagentManager, } from "@agentxm/extension-materialization";
14
+ import { installMcpServer } from "@agentxm/workspace-reconciliation";
15
+ import { buildInstallOperation, extensionRefLifecycleWarnings, extensionRefRegistryLifecycle, toLabelWithCompanions, } from "@agentxm/workspace-reconciliation";
14
16
  import { WorkspaceMutations } from "@agentxm/workspace-state";
15
17
  import { lifecycleStepFailure } from "../step-failure.js";
16
18
  import { registrySourceArtifact } from "./artifact.js";
@@ -28,9 +30,7 @@ export const buildPackMemberInstallStep = Effect.fn("InstallExtensions.buildPack
28
30
  return buildInstallOperation(skillManager, {
29
31
  toStepFailure: lifecycleStepFailure,
30
32
  ref,
31
- versionRange: Option.none(),
32
- skipSettings: true,
33
- deferObservableValidation: true,
33
+ enclosingClosure: { projections: [], postconditions: [ref.type] },
34
34
  installedBefore: args.graphComplete
35
35
  ? skillManager.isInstalled({ target: { type: "skill", name: ref.skill.name } })
36
36
  : Effect.succeed(false),
@@ -55,8 +55,6 @@ export const buildPackMemberInstallStep = Effect.fn("InstallExtensions.buildPack
55
55
  ref,
56
56
  nonInteractive: args.nonInteractive,
57
57
  force: false,
58
- versionRange: Option.none(),
59
- skipSettings: Option.some(true),
60
58
  strictAgentSync: Option.some(true),
61
59
  env: Option.none(),
62
60
  },
@@ -82,9 +80,7 @@ export const buildPackMemberInstallStep = Effect.fn("InstallExtensions.buildPack
82
80
  return buildInstallOperation(subagentManager, {
83
81
  toStepFailure: lifecycleStepFailure,
84
82
  ref,
85
- versionRange: Option.none(),
86
- skipSettings: true,
87
- deferObservableValidation: true,
83
+ enclosingClosure: { projections: [], postconditions: [ref.type] },
88
84
  installedBefore: args.graphComplete
89
85
  ? subagentManager.isInstalled({ target: { type: "subagent", name: ref.subagent.name } })
90
86
  : Effect.succeed(false),
@@ -104,10 +100,7 @@ export const buildPackMemberInstallStep = Effect.fn("InstallExtensions.buildPack
104
100
  return buildInstallOperation(ruleManager, {
105
101
  toStepFailure: lifecycleStepFailure,
106
102
  ref,
107
- versionRange: Option.none(),
108
- skipSettings: true,
109
- skipProjections: true,
110
- deferObservableValidation: true,
103
+ enclosingClosure: { projections: [ref.type], postconditions: [ref.type] },
111
104
  installedBefore: args.graphComplete
112
105
  ? ruleManager.isInstalled({ target: { type: "rule", name: ref.rule.name } })
113
106
  : Effect.succeed(false),
@@ -124,10 +117,7 @@ export const buildPackMemberInstallStep = Effect.fn("InstallExtensions.buildPack
124
117
  return buildInstallOperation(hookManager, {
125
118
  toStepFailure: lifecycleStepFailure,
126
119
  ref,
127
- versionRange: Option.none(),
128
- skipSettings: true,
129
- skipProjections: true,
130
- deferObservableValidation: true,
120
+ enclosingClosure: { projections: [ref.type], postconditions: [ref.type] },
131
121
  installedBefore: args.graphComplete
132
122
  ? hookManager.isInstalled({ target: { type: "hook", name: ref.hook.name } })
133
123
  : Effect.succeed(false),
@@ -143,10 +133,7 @@ export const buildPackMemberInstallStep = Effect.fn("InstallExtensions.buildPack
143
133
  return buildInstallOperation(knowledgeManager, {
144
134
  toStepFailure: lifecycleStepFailure,
145
135
  ref,
146
- versionRange: Option.none(),
147
- skipSettings: true,
148
- skipProjections: true,
149
- deferObservableValidation: true,
136
+ enclosingClosure: { projections: [ref.type], postconditions: [ref.type] },
150
137
  installedBefore: args.graphComplete
151
138
  ? knowledgeManager.isInstalled({ target: { type: "knowledge", name: ref.knowledge.name } })
152
139
  : Effect.succeed(false),
@@ -1,3 +1,4 @@
1
+ import { buildReconciliationClosure } from "@agentxm/workspace-reconciliation";
1
2
  /**
2
3
  * Unpacking a Pack: promoting its members to direct declarations.
3
4
  *
@@ -22,12 +23,13 @@
22
23
  import * as Effect from "effect/Effect";
23
24
  import * as Option from "effect/Option";
24
25
  import * as Path from "effect/Path";
25
- import { PackManager, buildUninstallOperation, } from "@agentxm/extension-materialization";
26
+ import { PackManager } from "@agentxm/extension-materialization";
27
+ import { buildUninstallOperation, } from "@agentxm/workspace-reconciliation";
26
28
  import { operationPresentation, prepareExecutionCandidate, resolveExecutionCandidate, } from "@agentxm/workspace-operations";
27
29
  import { WorkspaceMutations, usableAcceptedCanonical, } from "@agentxm/workspace-state";
28
30
  import { ExtensionLifecycleFailed } from "../errors.js";
29
31
  import { lifecycleStepFailure } from "../step-failure.js";
30
- import { buildAtomicPackGraphStep, validatePackGraphPostcondition } from "./graph-transition.js";
32
+ import { validatePackGraphPostcondition } from "./graph-transition.js";
31
33
  // -----------------------------------------------------------------------------
32
34
  // Promotion
33
35
  // -----------------------------------------------------------------------------
@@ -186,7 +188,8 @@ const settleUnpack = Effect.fn("PromoteAuthoredPack.prepare")(function* (request
186
188
  })),
187
189
  { path: packDisplayPath(path, ws, packNode), change: "removed" },
188
190
  ];
189
- const graphStep = yield* buildAtomicPackGraphStep({
191
+ const graphStep = yield* buildReconciliationClosure({
192
+ toStepFailure: lifecycleStepFailure,
190
193
  label: packNode.identity,
191
194
  message: `Unpacked ${packNode.identity} into ${promotions.length} direct declaration${promotions.length === 1 ? "" : "s"}`,
192
195
  artifact: {
@@ -1,3 +1,4 @@
1
+ import { buildReconciliationClosure } from "@agentxm/workspace-reconciliation";
1
2
  /**
2
3
  * Uninstalling packs.
3
4
  *
@@ -13,7 +14,8 @@ import * as Effect from "effect/Effect";
13
14
  import * as FileSystem from "effect/FileSystem";
14
15
  import * as Option from "effect/Option";
15
16
  import * as Path from "effect/Path";
16
- import { HookManager, KnowledgeManager, McpServerManager, PackManager, RuleManager, SkillManager, SubagentManager, buildUninstallOperation, toLabel, } from "@agentxm/extension-materialization";
17
+ import { HookManager, KnowledgeManager, McpServerManager, PackManager, RuleManager, SkillManager, SubagentManager, } from "@agentxm/extension-materialization";
18
+ import { buildUninstallOperation, prepareUninstallArtifact, collectCleanupStep, proposeDesiredState, } from "@agentxm/workspace-reconciliation";
17
19
  import { parseExtensionFqnParts, } from "@agentxm/extension-model/unstable/extensions";
18
20
  import { operationPresentation, } from "@agentxm/workspace-operations";
19
21
  import { WorkspaceMutations, decodeDesiredExtensionIdentity, } from "@agentxm/workspace-state";
@@ -21,9 +23,8 @@ import { expandGlob } from "../../glob.js";
21
23
  import { lifecycleStepFailure } from "../../step-failure.js";
22
24
  import { buildAggregateProjectionStep } from "../../install/aggregate-projection-step.js";
23
25
  import { installRefused } from "../../install/vocabulary.js";
24
- import { exclusiveMemberRetentionPolicy, makeWorkspaceRetentionPolicy, } from "../../uninstall/retention-policy.js";
25
- import { workspaceCanonicalNodePath } from "../../workspace-paths.js";
26
- import { buildAtomicPackGraphStep, validatePackGraphPostcondition } from "../graph-transition.js";
26
+ import { exclusiveMemberRetentionPolicy, makeWorkspaceRetentionPolicy, } from "@agentxm/workspace-reconciliation";
27
+ import { validatePackGraphPostcondition } from "../graph-transition.js";
27
28
  import { PACK_UNINSTALL_GRAPH_BLOCKER_ID, planPackUninstallGraphReadiness, } from "./readiness.js";
28
29
  const normalizedPackIdentity = (identity) => identity.startsWith("workspace:") ? identity.slice("workspace:".length) : identity;
29
30
  const identityValidationError = (identity) => installRefused({
@@ -211,7 +212,7 @@ export const planPackUninstall = Effect.fn("UninstallExtensions.planPacks")(func
211
212
  const retirement = retirementByIdentity.get(normalizedPackIdentity(pack.desiredIdentity));
212
213
  return retirement === undefined ? [] : [[pack.name, retirement]];
213
214
  }));
214
- const retentionPolicy = makeWorkspaceRetentionPolicy(ws);
215
+ const retentionPolicy = makeWorkspaceRetentionPolicy(ws, lifecycleStepFailure);
215
216
  const allTargets = new Map();
216
217
  for (const pack of intent.packsToUninstall) {
217
218
  allTargets.set(`pack:${pack.name}`, pack);
@@ -234,10 +235,19 @@ export const planPackUninstall = Effect.fn("UninstallExtensions.planPacks")(func
234
235
  const packTargets = [...allTargets.values()].filter((target) => target.type === "pack");
235
236
  const depTargets = [...allTargets.values()].filter((target) => target.type !== "pack");
236
237
  const orderedTargets = [...depTargets, ...packTargets];
237
- const sourcePathByTarget = new Map(graph.nodes.map((node) => [
238
- `${node.type}:${node.name}`,
239
- workspaceCanonicalNodePath(path, ws, node),
240
- ]));
238
+ const proposal = yield* proposeDesiredState(intent.packsToUninstall.map((pack) => ({ kind: "remove", type: "pack", name: pack.name }))).pipe(Effect.mapError((cause) => installRefused({
239
+ category: "conflict",
240
+ detail: "Pack retirement intent could not be evaluated",
241
+ cause,
242
+ })));
243
+ const artifacts = yield* Effect.forEach(orderedTargets, (target) => prepareUninstallArtifact(target, proposal)).pipe(Effect.mapError((cause) => installRefused({
244
+ category: "conflict",
245
+ detail: "Pack retirement effects could not be established",
246
+ cause,
247
+ })));
248
+ const artifactTargets = artifacts
249
+ .flatMap((artifact) => artifact.targets ?? [])
250
+ .filter((target, index, all) => all.findIndex((candidate) => candidate.path === target.path && candidate.change === target.change) === index);
241
251
  const steps = orderedTargets.map((target) => {
242
252
  switch (target.type) {
243
253
  case "pack": {
@@ -276,44 +286,66 @@ export const planPackUninstall = Effect.fn("UninstallExtensions.planPacks")(func
276
286
  return buildUninstallOperation(ruleManager, exclusiveMemberRetentionPolicy, {
277
287
  toStepFailure: lifecycleStepFailure,
278
288
  target,
279
- skipProjections: true,
289
+ enclosingClosure: { projections: [target.type] },
280
290
  });
281
291
  case "hook":
282
292
  return buildUninstallOperation(hookManager, exclusiveMemberRetentionPolicy, {
283
293
  toStepFailure: lifecycleStepFailure,
284
294
  target,
285
- skipProjections: true,
295
+ enclosingClosure: { projections: [target.type] },
286
296
  });
287
297
  case "knowledge":
288
298
  return buildUninstallOperation(knowledgeManager, exclusiveMemberRetentionPolicy, {
289
299
  toStepFailure: lifecycleStepFailure,
290
300
  target,
291
- skipProjections: true,
301
+ enclosingClosure: { projections: [target.type] },
292
302
  });
293
303
  }
294
304
  });
305
+ const activeNames = (type) => new Set(proposal.after.nodes
306
+ .filter((node) => node.type === type && node.enabled)
307
+ .map((node) => node.name));
308
+ const cleanup = plannedRetirements.length > 0
309
+ ? Option.none()
310
+ : yield* collectCleanupStep({
311
+ expectedSkillNames: activeNames("skill"),
312
+ expectedSubagentNames: activeNames("subagent"),
313
+ expectedMcpServerNames: activeNames("mcp-server"),
314
+ expectedHookNames: activeNames("hook"),
315
+ subjects: orderedTargets,
316
+ adapter: {
317
+ toStepFailure: (cause) => lifecycleStepFailure(installRefused({
318
+ category: "conflict",
319
+ detail: ("detail" in cause ? cause.detail : undefined) ?? cause._tag,
320
+ cause,
321
+ })),
322
+ },
323
+ }).pipe(Effect.mapError((cause) => installRefused({
324
+ category: "conflict",
325
+ detail: "Cannot establish owned Pack uninstall outputs",
326
+ cause,
327
+ })));
295
328
  const projectionStep = yield* buildAggregateProjectionStep({
296
329
  types: new Set(orderedTargets.map((target) => target.type)),
297
330
  });
298
331
  const registrationOnly = plannedRetirements.length === 0
299
332
  ? ""
300
333
  : ` (${plannedRetirements.length} pack${plannedRetirements.length === 1 ? "" : "s"} unregistered without removing package content)`;
301
- const graphStep = yield* buildAtomicPackGraphStep({
334
+ const graphStep = yield* buildReconciliationClosure({
335
+ toStepFailure: lifecycleStepFailure,
302
336
  label: `${intent.packsToUninstall.length} pack${intent.packsToUninstall.length === 1 ? "" : "s"}`,
303
337
  message: `Uninstalled ${intent.packsToUninstall.length} pack${intent.packsToUninstall.length === 1 ? "" : "s"} and ${depTargets.length} exclusive member${depTargets.length === 1 ? "" : "s"}${registrationOnly}`,
304
338
  artifact: {
305
- path: "pack graph",
339
+ path: artifacts[0]?.path ?? path.relative(ws.baseDir, ws.layout.settingsPath),
306
340
  scope: ws.scope,
307
341
  change: "removed",
308
342
  fileCount: orderedTargets.length,
309
- targets: orderedTargets.map((target) => ({
310
- path: sourcePathByTarget.get(`${target.type}:${target.name}`) ?? toLabel(target),
311
- // Content AXM could not verify is preserved, so its canonical path is
312
- // never reported as removed.
313
- change: target.type === "pack" && retirementByPackName.has(target.name) ? "unchanged" : "removed",
314
- })),
343
+ targets: artifactTargets,
344
+ references: artifacts.flatMap((artifact) => artifact.references ?? []),
345
+ managedRegions: artifacts.flatMap((artifact) => artifact.managedRegions ?? []),
315
346
  },
316
347
  children: [
348
+ ...Option.toArray(cleanup).map((step) => ({ step, coverage: "ineligible" })),
317
349
  ...steps.map((step) => ({ step, coverage: "ineligible" })),
318
350
  ...Option.toArray(projectionStep).map((step) => ({
319
351
  step,
@@ -9,7 +9,8 @@
9
9
  */
10
10
  import * as Effect from "effect/Effect";
11
11
  import * as Option from "effect/Option";
12
- import { RuleManager, buildInstallOperation } from "@agentxm/extension-materialization";
12
+ import { RuleManager } from "@agentxm/extension-materialization";
13
+ import { buildInstallOperation } from "@agentxm/workspace-reconciliation";
13
14
  import { parseSourceQualifiedRegistrySourcePatternParts, } from "@agentxm/extension-model/unstable/extensions";
14
15
  import { SourceHostProviders, resolveSource } from "@agentxm/extension-sources";
15
16
  import { operationPresentation, } from "@agentxm/workspace-operations";
@@ -86,8 +87,10 @@ export const planRuleInstall = Effect.fn("InstallExtensions.planRules")(function
86
87
  const memberSteps = intent.refs.map(({ ref, versionRange }) => buildInstallOperation(ruleManager, {
87
88
  toStepFailure: lifecycleStepFailure,
88
89
  ref,
89
- versionRange,
90
- skipProjections: deferProjections,
90
+ declaration: { name: ref.rule.name, versionRange },
91
+ ...(deferProjections
92
+ ? { enclosingClosure: { projections: [ref.type], postconditions: [] } }
93
+ : {}),
91
94
  installedBefore: ruleManager.isInstalled({
92
95
  target: { type: "rule", name: ref.rule.name },
93
96
  }),
@@ -8,11 +8,12 @@
8
8
  */
9
9
  import * as Effect from "effect/Effect";
10
10
  import * as Option from "effect/Option";
11
- import { RuleManager, buildUninstallOperation } from "@agentxm/extension-materialization";
11
+ import { RuleManager } from "@agentxm/extension-materialization";
12
+ import { buildUninstallOperation } from "@agentxm/workspace-reconciliation";
12
13
  import { WorkspaceMutations } from "@agentxm/workspace-state";
13
14
  import { lifecycleStepFailure } from "../../step-failure.js";
14
15
  import { installRefused } from "../../install/vocabulary.js";
15
- import { makeWorkspaceRetentionPolicy } from "../../uninstall/retention-policy.js";
16
+ import { makeWorkspaceRetentionPolicy } from "@agentxm/workspace-reconciliation";
16
17
  /** Settle whether this rule has anything to remove. */
17
18
  export const parseRuleUninstallRequest = Effect.fn("UninstallExtensions.parseRuleRequest")(function* (selector) {
18
19
  const ruleManager = yield* RuleManager;
@@ -35,7 +36,7 @@ export const parseRuleUninstallRequest = Effect.fn("UninstallExtensions.parseRul
35
36
  export const planRuleUninstall = Effect.fn("UninstallExtensions.planRules")(function* (intent) {
36
37
  const ws = yield* WorkspaceMutations;
37
38
  const ruleManager = yield* RuleManager;
38
- const retentionPolicy = makeWorkspaceRetentionPolicy(ws);
39
+ const retentionPolicy = makeWorkspaceRetentionPolicy(ws, lifecycleStepFailure);
39
40
  return {
40
41
  _tag: "Plan",
41
42
  name: "Uninstall rule",
@@ -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
  }),
@@ -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";
@@ -379,7 +380,7 @@ export const planSkillInstall = Effect.fn("InstallExtensions.planSkills")(functi
379
380
  return withPlanWarning(buildInstallOperation(skillManager, {
380
381
  toStepFailure: lifecycleStepFailure,
381
382
  ref,
382
- versionRange: entry.versionRange,
383
+ declaration: { name: ref.skill.name, versionRange: entry.versionRange },
383
384
  force: intent.force === true,
384
385
  installedBefore: Effect.succeed(installedBefore),
385
386
  buildArtifact: ({ installedBefore }) => Effect.gen(function* () {
@@ -30,7 +30,6 @@ export type InstallSkillOperationArgs = {
30
30
  /** Version constraint from the original input when available. */
31
31
  readonly versionRange: Option.Option<string>;
32
32
  /** When true, write to lockfile only (skip settings). Used for pack dependencies. */
33
- readonly skipSettings: Option.Option<boolean>;
34
33
  /** When true, fail on unknown configured agents instead of warning+skip. */
35
34
  readonly strictUnknownAgents: Option.Option<boolean>;
36
35
  /** Named registry source that provided the ref (written to lockfile for registry skills). */
@@ -529,25 +529,16 @@ export const installSkill = (op) => Effect.gen(function* () {
529
529
  }),
530
530
  };
531
531
  }
532
- const skipSettings = Option.getOrElse(op.args.skipSettings, () => false);
533
532
  const writeEffect = baseLockEntry === undefined
534
- ? skipSettings
535
- ? Effect.void
536
- : ws.setSkillEntry(ref.skill.name, {
537
- source: ref.refType === "workspace" ? "workspace" : printSourceParams(ref.source),
538
- enabled: true,
539
- })
540
- : skipSettings
541
- ? ws.setSkillLock({
542
- name: ref.skill.name,
543
- lockEntry: baseLockEntry,
544
- versionRange: materialized.versionRange,
545
- })
546
- : ws.setSkill({
547
- name: ref.skill.name,
548
- lockEntry: baseLockEntry,
549
- versionRange: materialized.versionRange,
550
- });
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
+ });
551
542
  const writeFailure = yield* writeEffect.pipe(Effect.result);
552
543
  if (writeFailure._tag === "Failure") {
553
544
  const failure = writeFailure.failure;
@@ -12,14 +12,15 @@ 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";
17
18
  import { CodingAgentRepository } from "@agentxm/workspace-projection";
18
19
  import { WorkspaceMutations, acquiredExtensionDisplayPathFromLockEntry, installedRowsByName, sanitizeName, } from "@agentxm/workspace-state";
19
20
  import { expandGlob } from "../../glob.js";
20
21
  import { lifecycleStepFailure } from "../../step-failure.js";
21
22
  import { installRefused, } from "../../install/vocabulary.js";
22
- import { makeWorkspaceRetentionPolicy } from "../../uninstall/retention-policy.js";
23
+ import { makeWorkspaceRetentionPolicy } from "@agentxm/workspace-reconciliation";
23
24
  import { workspaceAuthoredPath, workspaceCanonicalPath, workspaceCanonicalRoot, workspaceLockfilePath, workspaceSettingsPath, } from "../../workspace-paths.js";
24
25
  const skillSourceTarget = (ws, path, configuredSource, lockEntry, sanitizedName) => {
25
26
  if (Option.isSome(configuredSource) && configuredSource.value === "workspace") {
@@ -69,7 +70,7 @@ export const planSkillUninstall = Effect.fn("UninstallExtensions.planSkills")(fu
69
70
  const skillManager = yield* SkillManager;
70
71
  const agentRepo = yield* CodingAgentRepository;
71
72
  const path = yield* Path.Path;
72
- const retentionPolicy = makeWorkspaceRetentionPolicy(ws);
73
+ const retentionPolicy = makeWorkspaceRetentionPolicy(ws, lifecycleStepFailure);
73
74
  const configuredAgents = yield* agentRepo.getMaterializationAgents().pipe(Effect.mapError((cause) => installRefused({
74
75
  category: "internal",
75
76
  detail: "Configured agents could not be read",
@@ -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";
@@ -216,7 +217,7 @@ export const planSubagentInstall = Effect.fn("InstallExtensions.planSubagents")(
216
217
  return buildInstallOperation(subagentManager, {
217
218
  toStepFailure: lifecycleStepFailure,
218
219
  ref,
219
- versionRange: entry.versionRange,
220
+ declaration: { name: ref.subagent.name, versionRange: entry.versionRange },
220
221
  installedBefore: subagentManager
221
222
  .isInstalled({ target: { type: "subagent", name: ref.subagent.name } })
222
223
  .pipe(Effect.catch(() => Effect.succeed(false))),