@agentxm/extension-lifecycle 0.28.12 → 0.28.13

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 (207) hide show
  1. package/dist/src/activation/display-paths.d.ts +25 -0
  2. package/dist/src/activation/display-paths.js +25 -0
  3. package/dist/src/activation/errors.d.ts +16 -0
  4. package/dist/src/activation/errors.js +7 -0
  5. package/dist/src/activation/set-activation.d.ts +134 -0
  6. package/dist/src/activation/set-activation.js +772 -0
  7. package/dist/src/demote/demote-to-external-source.d.ts +82 -0
  8. package/dist/src/demote/demote-to-external-source.js +233 -0
  9. package/dist/src/errors.d.ts +0 -8
  10. package/dist/src/errors.js +0 -8
  11. package/dist/src/glob.d.ts +35 -0
  12. package/dist/src/glob.js +75 -0
  13. package/dist/src/hooks/configured-agent-outcomes-provider.d.ts +7 -3
  14. package/dist/src/hooks/configured-agent-outcomes-provider.js +11 -4
  15. package/dist/src/hooks/install/plan.d.ts +45 -0
  16. package/dist/src/hooks/install/plan.js +224 -0
  17. package/dist/src/hooks/uninstall/plan.d.ts +20 -0
  18. package/dist/src/hooks/uninstall/plan.js +77 -0
  19. package/dist/src/index.d.ts +48 -16
  20. package/dist/src/index.js +60 -17
  21. package/dist/src/install/aggregate-projection-step.d.ts +16 -0
  22. package/dist/src/install/aggregate-projection-step.js +49 -0
  23. package/dist/src/install/configured.d.ts +65 -0
  24. package/dist/src/install/configured.js +406 -0
  25. package/dist/src/install/inline-mcp-operation.d.ts +4 -0
  26. package/dist/src/install/inline-mcp-operation.js +25 -0
  27. package/dist/src/install/install-extensions.d.ts +110 -0
  28. package/dist/src/install/install-extensions.js +282 -0
  29. package/dist/src/install/per-type-install.d.ts +12 -0
  30. package/dist/src/install/per-type-install.js +12 -0
  31. package/dist/src/install/registry-install-target.d.ts +35 -0
  32. package/dist/src/install/registry-install-target.js +66 -0
  33. package/dist/src/install/registry-login-suggestion.d.ts +21 -0
  34. package/dist/src/install/registry-login-suggestion.js +34 -0
  35. package/dist/src/install/registry-source-resolution.d.ts +54 -0
  36. package/dist/src/install/registry-source-resolution.js +216 -0
  37. package/dist/src/install/root-intent.d.ts +19 -0
  38. package/dist/src/install/root-intent.js +122 -0
  39. package/dist/src/install/selection-interaction.d.ts +46 -0
  40. package/dist/src/install/selection-interaction.js +22 -0
  41. package/dist/src/install/vocabulary.d.ts +143 -0
  42. package/dist/src/install/vocabulary.js +29 -0
  43. package/dist/src/knowledge/install/plan.d.ts +35 -0
  44. package/dist/src/knowledge/install/plan.js +128 -0
  45. package/dist/src/knowledge/uninstall/plan.d.ts +23 -0
  46. package/dist/src/knowledge/uninstall/plan.js +140 -0
  47. package/dist/src/live.d.ts +4 -11
  48. package/dist/src/live.js +4 -11
  49. package/dist/src/mcps/install/plan.d.ts +65 -0
  50. package/dist/src/mcps/install/plan.js +301 -0
  51. package/dist/src/mcps/operations/disable.d.ts +5 -3
  52. package/dist/src/mcps/operations/disable.js +6 -4
  53. package/dist/src/mcps/operations/enable.d.ts +5 -3
  54. package/dist/src/mcps/operations/enable.js +8 -6
  55. package/dist/src/mcps/operations/sync-outcome.d.ts +1 -1
  56. package/dist/src/mcps/operations/uninstall.d.ts +5 -3
  57. package/dist/src/mcps/operations/uninstall.js +4 -4
  58. package/dist/src/mcps/uninstall/plan.d.ts +24 -0
  59. package/dist/src/mcps/uninstall/plan.js +109 -0
  60. package/dist/src/packs/artifact.d.ts +21 -0
  61. package/dist/src/packs/artifact.js +32 -0
  62. package/dist/src/packs/constraint-gate.d.ts +30 -0
  63. package/dist/src/packs/constraint-gate.js +76 -0
  64. package/dist/src/packs/expansion.d.ts +13 -14
  65. package/dist/src/packs/expansion.js +1 -2
  66. package/dist/src/packs/graph-transition.d.ts +64 -0
  67. package/dist/src/packs/graph-transition.js +219 -0
  68. package/dist/src/packs/install/plan.d.ts +95 -0
  69. package/dist/src/packs/install/plan.js +845 -0
  70. package/dist/src/packs/member-install-step.d.ts +29 -0
  71. package/dist/src/packs/member-install-step.js +156 -0
  72. package/dist/src/packs/operations/install.d.ts +3 -3
  73. package/dist/src/packs/operations/install.js +5 -5
  74. package/dist/src/packs/promote-authored-pack.d.ts +86 -0
  75. package/dist/src/packs/promote-authored-pack.js +239 -0
  76. package/dist/src/packs/resolved-dependency.d.ts +9 -26
  77. package/dist/src/packs/resolved-dependency.js +8 -20
  78. package/dist/src/packs/uninstall/plan.d.ts +66 -0
  79. package/dist/src/packs/uninstall/plan.js +344 -0
  80. package/dist/src/packs/uninstall/readiness.d.ts +50 -0
  81. package/dist/src/packs/uninstall/readiness.js +130 -0
  82. package/dist/src/publisher-binding.d.ts +8 -40
  83. package/dist/src/publisher-binding.js +8 -46
  84. package/dist/src/rules/install/plan.d.ts +35 -0
  85. package/dist/src/rules/install/plan.js +133 -0
  86. package/dist/src/rules/uninstall/plan.d.ts +19 -0
  87. package/dist/src/rules/uninstall/plan.js +54 -0
  88. package/dist/src/skills/install/bundled.d.ts +73 -0
  89. package/dist/src/skills/install/bundled.js +244 -0
  90. package/dist/src/skills/install/plan.d.ts +72 -0
  91. package/dist/src/skills/install/plan.js +429 -0
  92. package/dist/src/skills/install/selection.d.ts +26 -0
  93. package/dist/src/skills/install/selection.js +41 -0
  94. package/dist/src/skills/install/source.d.ts +21 -0
  95. package/dist/src/skills/install/source.js +76 -0
  96. package/dist/src/skills/operations/disable.d.ts +3 -2
  97. package/dist/src/skills/operations/disable.js +7 -11
  98. package/dist/src/skills/operations/enable.d.ts +3 -2
  99. package/dist/src/skills/operations/enable.js +6 -8
  100. package/dist/src/skills/operations/install.d.ts +4 -4
  101. package/dist/src/skills/operations/install.js +13 -12
  102. package/dist/src/skills/uninstall/plan.d.ts +23 -0
  103. package/dist/src/skills/uninstall/plan.js +165 -0
  104. package/dist/src/skills/utils.js +1 -1
  105. package/dist/src/step-failure-conversion.d.ts +44 -0
  106. package/dist/src/step-failure-conversion.js +26 -0
  107. package/dist/src/step-failure.d.ts +29 -0
  108. package/dist/src/step-failure.js +130 -0
  109. package/dist/src/subagents/install/plan.d.ts +49 -0
  110. package/dist/src/subagents/install/plan.js +270 -0
  111. package/dist/src/subagents/install/selection.d.ts +26 -0
  112. package/dist/src/subagents/install/selection.js +37 -0
  113. package/dist/src/subagents/install/source.d.ts +18 -0
  114. package/dist/src/subagents/install/source.js +72 -0
  115. package/dist/src/subagents/operations/disable.d.ts +5 -3
  116. package/dist/src/subagents/operations/disable.js +5 -3
  117. package/dist/src/subagents/operations/enable.d.ts +5 -3
  118. package/dist/src/subagents/operations/enable.js +17 -12
  119. package/dist/src/subagents/uninstall/plan.d.ts +20 -0
  120. package/dist/src/subagents/uninstall/plan.js +142 -0
  121. package/dist/src/test-packages.d.ts +34 -0
  122. package/dist/src/test-packages.js +111 -0
  123. package/dist/src/test-registry.d.ts +78 -0
  124. package/dist/src/test-registry.js +223 -0
  125. package/dist/src/testing.d.ts +108 -0
  126. package/dist/src/testing.js +241 -0
  127. package/dist/src/uninstall/retention-policy.d.ts +21 -0
  128. package/dist/src/uninstall/retention-policy.js +28 -0
  129. package/dist/src/uninstall/root-intent.d.ts +17 -0
  130. package/dist/src/uninstall/root-intent.js +101 -0
  131. package/dist/src/uninstall/uninstall-extensions.d.ts +61 -0
  132. package/dist/src/uninstall/uninstall-extensions.js +127 -0
  133. package/dist/src/uninstall/vocabulary.d.ts +40 -0
  134. package/dist/src/uninstall/vocabulary.js +14 -0
  135. package/dist/src/update/atomicity.d.ts +22 -0
  136. package/dist/src/update/atomicity.js +23 -0
  137. package/dist/src/update/blockers.d.ts +31 -0
  138. package/dist/src/update/blockers.js +78 -0
  139. package/dist/src/update/configured.d.ts +78 -0
  140. package/dist/src/update/configured.js +546 -0
  141. package/dist/src/update/root-request.d.ts +39 -0
  142. package/dist/src/update/root-request.js +117 -0
  143. package/dist/src/update/selective/plan.d.ts +45 -0
  144. package/dist/src/update/selective/plan.js +61 -0
  145. package/dist/src/update/selective/requirements.d.ts +17 -0
  146. package/dist/src/update/selective/requirements.js +14 -0
  147. package/dist/src/update/selective/selection.d.ts +63 -0
  148. package/dist/src/update/selective/selection.js +85 -0
  149. package/dist/src/update/selective/skills.d.ts +55 -0
  150. package/dist/src/update/selective/skills.js +596 -0
  151. package/dist/src/update/selective/subagents.d.ts +52 -0
  152. package/dist/src/update/selective/subagents.js +331 -0
  153. package/dist/src/update/selective/use-case.d.ts +121 -0
  154. package/dist/src/update/selective/use-case.js +28 -0
  155. package/dist/src/update/selective/vocabulary.d.ts +43 -0
  156. package/dist/src/update/selective/vocabulary.js +28 -0
  157. package/dist/src/update/selector.d.ts +72 -0
  158. package/dist/src/update/selector.js +120 -0
  159. package/dist/src/update/targeted-plan.d.ts +30 -0
  160. package/dist/src/update/targeted-plan.js +113 -0
  161. package/dist/src/update/update-extensions.d.ts +164 -0
  162. package/dist/src/update/update-extensions.js +579 -0
  163. package/dist/src/workspace-paths.d.ts +31 -0
  164. package/dist/src/workspace-paths.js +46 -0
  165. package/package.json +22 -12
  166. package/dist/src/configured-entry-resolution.d.ts +0 -111
  167. package/dist/src/configured-entry-resolution.js +0 -731
  168. package/dist/src/failure-adapter.d.ts +0 -35
  169. package/dist/src/failure-adapter.js +0 -23
  170. package/dist/src/hooks/manager.d.ts +0 -15
  171. package/dist/src/hooks/manager.js +0 -769
  172. package/dist/src/internal/fs-helpers.d.ts +0 -12
  173. package/dist/src/internal/fs-helpers.js +0 -13
  174. package/dist/src/internal/integrity.d.ts +0 -13
  175. package/dist/src/internal/integrity.js +0 -17
  176. package/dist/src/knowledge/manager.d.ts +0 -11
  177. package/dist/src/knowledge/manager.js +0 -711
  178. package/dist/src/mcps/manager.d.ts +0 -16
  179. package/dist/src/mcps/manager.js +0 -247
  180. package/dist/src/mcps/operations/artifact.d.ts +0 -24
  181. package/dist/src/mcps/operations/artifact.js +0 -74
  182. package/dist/src/mcps/operations/install.d.ts +0 -78
  183. package/dist/src/mcps/operations/install.js +0 -596
  184. package/dist/src/packs/dependency-resolution.d.ts +0 -65
  185. package/dist/src/packs/dependency-resolution.js +0 -414
  186. package/dist/src/packs/manager.d.ts +0 -16
  187. package/dist/src/packs/manager.js +0 -181
  188. package/dist/src/registry-materialization.d.ts +0 -52
  189. package/dist/src/registry-materialization.js +0 -72
  190. package/dist/src/release-age-posture.d.ts +0 -23
  191. package/dist/src/release-age-posture.js +0 -15
  192. package/dist/src/resolution-timeout.d.ts +0 -5
  193. package/dist/src/resolution-timeout.js +0 -11
  194. package/dist/src/rules/manager.d.ts +0 -20
  195. package/dist/src/rules/manager.js +0 -480
  196. package/dist/src/skills/manager.d.ts +0 -18
  197. package/dist/src/skills/manager.js +0 -346
  198. package/dist/src/skills/materialization.d.ts +0 -54
  199. package/dist/src/skills/materialization.js +0 -158
  200. package/dist/src/skills/operations/source-hash.d.ts +0 -2
  201. package/dist/src/skills/operations/source-hash.js +0 -6
  202. package/dist/src/subagents/manager.d.ts +0 -18
  203. package/dist/src/subagents/manager.js +0 -722
  204. package/dist/src/workflows/install-command/workflow.d.ts +0 -56
  205. package/dist/src/workflows/install-command/workflow.js +0 -46
  206. package/dist/src/workflows/uninstall-command/workflow.d.ts +0 -42
  207. package/dist/src/workflows/uninstall-command/workflow.js +0 -29
@@ -0,0 +1,845 @@
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
+ import * as DateTime from "effect/DateTime";
15
+ import * as Effect from "effect/Effect";
16
+ import * as Option from "effect/Option";
17
+ import * as Path from "effect/Path";
18
+ 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 { parseExtensionFqnParts, toExtensionTypePlural, } from "@agentxm/extension-model/unstable/extensions";
21
+ import { isWorkspaceSourceLocator } from "@agentxm/extension-model/unstable/sources/workspace";
22
+ import { versionSatisfiesRange, } from "@agentxm/extension-model/unstable/version-constraints";
23
+ import { evaluateSourceAuthority, normalizeReleaseAgeRecords, parseMinimumReleaseAge, } from "@agentxm/extension-resolution";
24
+ import { SourceHostProviders, resolveSource, sourceResolutionFailureCategory, } from "@agentxm/extension-sources";
25
+ import { operationPresentation, } from "@agentxm/workspace-operations";
26
+ import { WorkspaceMutations, acceptedLockedCanonicalPath, isDesiredExtensionActive, usableAcceptedCanonical, } from "@agentxm/workspace-state";
27
+ import { lifecycleStepFailure } from "../../step-failure.js";
28
+ import { buildAggregateProjectionStep } from "../../install/aggregate-projection-step.js";
29
+ import { parseRegistryInstallTarget } from "../../install/registry-install-target.js";
30
+ import { registryLoginSuggestions } from "../../install/registry-login-suggestion.js";
31
+ import { formatRegistryProbe, sourceFailureDetail, } from "../../install/registry-source-resolution.js";
32
+ import { installRefused, } from "../../install/vocabulary.js";
33
+ import { expandPackInstallRefs, expandPackInstallRefsWithReleaseAge } from "../expansion.js";
34
+ import { buildAtomicPackGraphStep, validatePackGraphPostcondition } from "../graph-transition.js";
35
+ import { buildPackMemberInstallStep } from "../member-install-step.js";
36
+ import { registrySourceArtifact, registrySourcePath } from "../artifact.js";
37
+ import { makeWorkspaceRetentionPolicy } from "../../uninstall/retention-policy.js";
38
+ const isRemoteReadNotImplemented = (error) => {
39
+ const detail = sourceFailureDetail(error);
40
+ return detail.includes("not implemented") || detail.includes("not yet supported");
41
+ };
42
+ const summarizeLookupFailure = (error) => `${sourceFailureDetail(error)} (${sourceResolutionFailureCategory(error)})`;
43
+ const makePackDependencyNameSets = () => ({
44
+ skill: new Set(),
45
+ "mcp-server": new Set(),
46
+ subagent: new Set(),
47
+ rule: new Set(),
48
+ hook: new Set(),
49
+ knowledge: new Set(),
50
+ });
51
+ const collectResolvedDependencyNames = (refs) => {
52
+ const names = makePackDependencyNameSets();
53
+ for (const ref of refs) {
54
+ switch (ref.type) {
55
+ case "pack":
56
+ break;
57
+ case "skill":
58
+ names.skill.add(ref.skill.name);
59
+ break;
60
+ case "mcp-server":
61
+ names["mcp-server"].add(ref.server.name);
62
+ break;
63
+ case "subagent":
64
+ names.subagent.add(ref.subagent.name);
65
+ break;
66
+ case "rule":
67
+ names.rule.add(ref.rule.name);
68
+ break;
69
+ case "hook":
70
+ names.hook.add(ref.hook.name);
71
+ break;
72
+ case "knowledge":
73
+ names.knowledge.add(ref.knowledge.name);
74
+ break;
75
+ }
76
+ }
77
+ return names;
78
+ };
79
+ /**
80
+ * Members the replaced pack owned that the incoming graph no longer declares
81
+ * and no other origin retains. Removing them is what makes a pack upgrade a
82
+ * graph transition rather than an accumulation.
83
+ */
84
+ const collectDroppedPackDependencyTargets = (args) => {
85
+ const droppedTargets = [];
86
+ for (const node of args.graph.nodes) {
87
+ if (node.type === "pack")
88
+ continue;
89
+ const belongedToReplacedPack = node.origins.some((origin) => origin.type === "pack" && origin.pack === args.replacingPackIdentity);
90
+ if (!belongedToReplacedPack || args.nextDependencies[node.type].has(node.name))
91
+ continue;
92
+ const retainedElsewhere = node.origins.some((origin) => origin.type === "settings" ||
93
+ (origin.type === "pack" && origin.pack !== args.replacingPackIdentity));
94
+ if (!retainedElsewhere)
95
+ droppedTargets.push({ target: { type: node.type, name: node.name } });
96
+ }
97
+ return droppedTargets;
98
+ };
99
+ const formatRegistrySourceLabel = (args) => {
100
+ const matched = args.registryHosts.find((host) => host.location.href === args.source.location.href);
101
+ return matched === undefined
102
+ ? args.source.location.href
103
+ : `${matched.name} (${matched.location.href})`;
104
+ };
105
+ const packInstallCoverage = (ref) => {
106
+ switch (ref?.type) {
107
+ case "skill":
108
+ case "mcp-server":
109
+ case "subagent":
110
+ case "rule":
111
+ case "hook":
112
+ return "eligible";
113
+ case "pack":
114
+ case "knowledge":
115
+ case undefined:
116
+ return "ineligible";
117
+ }
118
+ };
119
+ const resolveMinimumReleaseAge = (unattended) => Effect.gen(function* () {
120
+ if (!unattended)
121
+ return Option.none();
122
+ const ws = yield* WorkspaceMutations;
123
+ const minimumReleaseAge = yield* ws.getMinimumReleaseAge().pipe(Effect.mapError((cause) => installRefused({
124
+ category: "internal",
125
+ detail: "Minimum release age could not be read",
126
+ cause,
127
+ })));
128
+ const minimumAge = parseMinimumReleaseAge(minimumReleaseAge);
129
+ if (Option.isNone(minimumAge)) {
130
+ return yield* installRefused({
131
+ category: "validation",
132
+ detail: `Invalid minimumReleaseAge "${minimumReleaseAge}"`,
133
+ recover: "Use a duration such as 24h, 1440m, or 0s.",
134
+ });
135
+ }
136
+ return minimumAge;
137
+ });
138
+ const readDesiredGraph = Effect.gen(function* () {
139
+ const ws = yield* WorkspaceMutations;
140
+ return yield* ws
141
+ .getDesiredStateGraph()
142
+ .pipe(Effect.mapError((cause) => installRefused({ category: "internal", detail: "Desired state could not be read", cause })));
143
+ });
144
+ const scanWorkspaceAuthority = Effect.fn("InstallExtensions.scanWorkspaceAuthority")(function* (pack) {
145
+ const ws = yield* WorkspaceMutations;
146
+ const graph = yield* readDesiredGraph;
147
+ const packIdentity = `${pack.owner}/packs/${pack.name}`;
148
+ const blockers = [];
149
+ const workspaceRefs = new Map();
150
+ const root = graph.nodes.find((node) => node.type === "pack" &&
151
+ node.name === pack.name &&
152
+ node.origins.some((origin) => origin.type === "settings" &&
153
+ origin.source !== undefined &&
154
+ isWorkspaceSourceLocator(origin.source)));
155
+ if (root !== undefined) {
156
+ const decision = evaluateSourceAuthority({
157
+ target: { type: "pack", name: pack.name, identity: packIdentity },
158
+ relationship: { kind: "root" },
159
+ requested: {
160
+ identity: `${pack.refType}:${packIdentity}`,
161
+ workspace: pack.refType === "workspace",
162
+ },
163
+ configured: {
164
+ identity: root.identity,
165
+ workspace: root.identity.startsWith("workspace:"),
166
+ },
167
+ });
168
+ if (decision.kind === "blocked")
169
+ blockers.push(decision.fact);
170
+ }
171
+ const dependencies = Object.entries(pack.pack.dependencies).sort(([left], [right]) => left.localeCompare(right));
172
+ for (const [fqn, constraint] of dependencies) {
173
+ const parsed = parseExtensionFqnParts(fqn);
174
+ if (parsed === undefined || parsed.type === "pack")
175
+ continue;
176
+ const desired = graph.nodes.find((node) => node.type === parsed.type &&
177
+ node.name === parsed.name &&
178
+ node.origins.some((origin) => origin.type === "settings" &&
179
+ origin.source !== undefined &&
180
+ isWorkspaceSourceLocator(origin.source)));
181
+ if (desired === undefined)
182
+ continue;
183
+ const canonical = yield* usableAcceptedCanonical({
184
+ workspace: ws,
185
+ type: parsed.type,
186
+ name: parsed.name,
187
+ }).pipe(Effect.mapError((cause) => installRefused({
188
+ category: "internal",
189
+ detail: `Accepted canonical content for ${parsed.name} could not be inspected`,
190
+ cause,
191
+ })));
192
+ const targetIdentity = `${parsed.owner}/${toExtensionTypePlural(parsed.type)}/${parsed.name}`;
193
+ const configuredVersion = Option.isSome(canonical) &&
194
+ (canonical.value.ref.refType === "registry" || canonical.value.ref.refType === "workspace")
195
+ ? canonical.value.ref.version
196
+ : undefined;
197
+ const configured = {
198
+ identity: desired.identity,
199
+ workspace: desired.identity.startsWith("workspace:"),
200
+ ...(configuredVersion === undefined ? {} : { version: configuredVersion }),
201
+ status: Option.isSome(canonical) ? canonical.value.observation.status : "missing",
202
+ };
203
+ const input = {
204
+ target: { type: parsed.type, name: parsed.name, identity: targetIdentity },
205
+ relationship: { kind: "member", root: packIdentity },
206
+ requested: { identity: `registry:${targetIdentity}`, workspace: false },
207
+ configured,
208
+ requiredVersionRange: constraint,
209
+ };
210
+ const decision = evaluateSourceAuthority(input);
211
+ if (decision.kind === "blocked") {
212
+ blockers.push(decision.fact);
213
+ continue;
214
+ }
215
+ if (decision.kind !== "workspace-satisfied")
216
+ continue;
217
+ if (Option.isNone(canonical)) {
218
+ const unusable = evaluateSourceAuthority({
219
+ ...input,
220
+ configured: { ...configured, status: "wrong-origin" },
221
+ });
222
+ if (unusable.kind === "blocked")
223
+ blockers.push(unusable.fact);
224
+ continue;
225
+ }
226
+ const ref = canonical.value.ref;
227
+ if (ref.refType !== "workspace" ||
228
+ ref.type !== parsed.type ||
229
+ ref.owner !== parsed.owner ||
230
+ ref.name !== parsed.name) {
231
+ const mismatched = evaluateSourceAuthority({
232
+ ...input,
233
+ configured: { ...configured, status: "wrong-origin" },
234
+ });
235
+ if (mismatched.kind === "blocked")
236
+ blockers.push(mismatched.fact);
237
+ continue;
238
+ }
239
+ workspaceRefs.set(`${parsed.type}:${parsed.owner}/${parsed.name}`, ref);
240
+ }
241
+ const workspaceResolver = ({ owner, type, name }) => Effect.succeed(Option.match(Option.fromUndefinedOr(workspaceRefs.get(`${type}:${owner}/${name}`)), {
242
+ onNone: () => ({ kind: "absent" }),
243
+ onSome: (ref) => ({ kind: "selected", ref }),
244
+ }));
245
+ const fingerprint = [...workspaceRefs.entries()]
246
+ .map(([key, ref]) => ref.refType === "workspace" ? `${key}:${ref.version}:${ref.sourceHash}` : key)
247
+ .sort()
248
+ .join("|");
249
+ return { graph, blockers, workspaceResolver, fingerprint };
250
+ });
251
+ /** Read the pack source grammar: which owner, which pack, which range. */
252
+ export const parsePackInstallRequest = Effect.fn("InstallExtensions.parsePackRequest")(function* (args) {
253
+ const ws = yield* WorkspaceMutations;
254
+ const trimmed = args.source.trim();
255
+ const parsed = parseRegistryInstallTarget(trimmed, {
256
+ expectedType: "pack",
257
+ allowBareName: true,
258
+ allowBareVersionRange: true,
259
+ });
260
+ if (Result.isFailure(parsed)) {
261
+ switch (parsed.failure.kind) {
262
+ case "wrong-type":
263
+ return yield* installRefused({
264
+ category: "validation",
265
+ detail: "Pack source must include /packs/ segment",
266
+ suggestions: [
267
+ {
268
+ description: "Use @owner/packs/pack-name format. The /packs/ segment distinguishes packs from skills.",
269
+ },
270
+ ],
271
+ });
272
+ case "missing-name":
273
+ return yield* installRefused({
274
+ category: "not_found",
275
+ detail: "Pack source must include a pack name",
276
+ suggestions: [{ description: "Use @owner/packs/pack-name format." }],
277
+ });
278
+ default:
279
+ return yield* installRefused({
280
+ category: "usage",
281
+ detail: "Packs can only be installed from a registry",
282
+ suggestions: [
283
+ {
284
+ description: "Use @owner/packs/pack-name or just pack-name (resolved to default owner).",
285
+ },
286
+ ],
287
+ });
288
+ }
289
+ }
290
+ const policy = {
291
+ unattended: args.unattended ?? false,
292
+ nonInteractive: args.nonInteractive,
293
+ ...(args.releaseAgeEvaluation === undefined
294
+ ? {}
295
+ : { releaseAgeEvaluation: args.releaseAgeEvaluation }),
296
+ ...(args.releaseAgeHoldbackBehavior === undefined
297
+ ? {}
298
+ : { releaseAgeHoldbackBehavior: args.releaseAgeHoldbackBehavior }),
299
+ };
300
+ if (parsed.success.kind === "registry") {
301
+ return {
302
+ inputKind: "registry-pattern-input",
303
+ owner: parsed.success.owner,
304
+ packName: parsed.success.name,
305
+ versionRange: Option.fromUndefinedOr(parsed.success.versionRange),
306
+ resolvedInput: trimmed,
307
+ ...policy,
308
+ };
309
+ }
310
+ const owner = yield* ws.getConfiguredOwner().pipe(Effect.mapError((cause) => installRefused({
311
+ category: "internal",
312
+ detail: "Configured workspace owner could not be read",
313
+ cause,
314
+ })), Effect.flatMap(Option.match({
315
+ onNone: () => Effect.fail(installRefused({
316
+ category: "validation",
317
+ detail: `Cannot resolve bare pack name "${parsed.success.name}" without a configured owner`,
318
+ suggestions: [
319
+ {
320
+ description: "Use the fully-qualified `@owner/packs/name` form, set `owner` in settings, or sign in.",
321
+ cmd: "axm login",
322
+ },
323
+ ],
324
+ })),
325
+ onSome: Effect.succeed,
326
+ })));
327
+ const versionRange = Option.fromUndefinedOr(parsed.success.versionRange);
328
+ const resolvedInput = Option.match(versionRange, {
329
+ onNone: () => `${owner}/packs/${parsed.success.name}`,
330
+ onSome: (constraint) => `${owner}/packs/${parsed.success.name}@${constraint}`,
331
+ });
332
+ return {
333
+ inputKind: parsed.success.versionRange === undefined
334
+ ? "name-input"
335
+ : "name-input-with-version",
336
+ owner,
337
+ packName: parsed.success.name,
338
+ versionRange,
339
+ resolvedInput,
340
+ sourceResolution: `${trimmed} -> ${resolvedInput}`,
341
+ ...policy,
342
+ };
343
+ });
344
+ /** Resolve the registry the parsed pack request names. */
345
+ export const resolvePackSourceRequest = Effect.fn("InstallExtensions.resolvePackSource")(function* (request) {
346
+ const source = yield* resolveSource(request.resolvedInput).pipe(Effect.mapError((cause) => installRefused({
347
+ category: "validation",
348
+ detail: `Invalid source: ${cause.message}`,
349
+ suggestions: [{ description: "Use @owner/packs/pack-name or just pack-name." }],
350
+ cause,
351
+ })));
352
+ if (source.type !== "registry") {
353
+ return yield* installRefused({
354
+ category: "usage",
355
+ detail: "Packs can only be installed from a registry",
356
+ suggestions: [{ description: "Use a registry source: @owner/packs/pack-name" }],
357
+ });
358
+ }
359
+ return {
360
+ source,
361
+ owner: request.owner,
362
+ packName: request.packName,
363
+ versionRange: request.versionRange,
364
+ ...(request.sourceResolution === undefined
365
+ ? {}
366
+ : { sourceResolution: request.sourceResolution }),
367
+ };
368
+ });
369
+ /**
370
+ * Find the pack. When the first host cannot answer because remote reads are
371
+ * unsupported, every configured `file://` registry is consulted in turn, and
372
+ * every probe is recorded so the operator can see what was tried.
373
+ */
374
+ export const discoverPackRef = Effect.fn("InstallExtensions.discoverPack")(function* (request) {
375
+ const ws = yield* WorkspaceMutations;
376
+ const sources = yield* SourceHostProviders;
377
+ const findWith = (candidate) => sources.find(candidate, {
378
+ names: [request.packName],
379
+ type: "pack",
380
+ owner: Option.some(request.owner),
381
+ versionRange: request.versionRange,
382
+ });
383
+ const probes = [];
384
+ const initialResult = yield* findWith(request.source).pipe(Effect.result);
385
+ probes.push(initialResult._tag === "Success"
386
+ ? {
387
+ location: request.source.location.href,
388
+ outcome: initialResult.success.length > 0 ? "matched" : "not-found",
389
+ reason: Option.none(),
390
+ }
391
+ : {
392
+ location: request.source.location.href,
393
+ outcome: "error",
394
+ reason: Option.some(summarizeLookupFailure(initialResult.failure)),
395
+ });
396
+ let resolvedRefs;
397
+ let resolvedSource = request.source;
398
+ if (initialResult._tag === "Success" && initialResult.success.length > 0) {
399
+ resolvedRefs = initialResult.success.filter((ref) => ref.type === "pack");
400
+ }
401
+ else if (initialResult._tag === "Failure" &&
402
+ isRemoteReadNotImplemented(initialResult.failure)) {
403
+ const registryHosts = yield* ws.getRegistrySourceHosts().pipe(Effect.mapError((cause) => installRefused({
404
+ category: "internal",
405
+ detail: "Configured registry hosts could not be read",
406
+ cause,
407
+ })));
408
+ const fallbackSources = registryHosts
409
+ .filter((host) => host.location.protocol === "file:")
410
+ .map((host) => ({
411
+ type: "registry",
412
+ name: host.name,
413
+ location: host.location,
414
+ owner: Option.some(request.owner),
415
+ }));
416
+ for (const fallbackSource of fallbackSources) {
417
+ if (fallbackSource.location.href === request.source.location.href)
418
+ continue;
419
+ const fallbackResult = yield* findWith(fallbackSource).pipe(Effect.result);
420
+ probes.push(fallbackResult._tag === "Success"
421
+ ? {
422
+ location: fallbackSource.location.href,
423
+ outcome: fallbackResult.success.length > 0 ? "matched" : "not-found",
424
+ reason: Option.none(),
425
+ }
426
+ : {
427
+ location: fallbackSource.location.href,
428
+ outcome: "error",
429
+ reason: Option.some(summarizeLookupFailure(fallbackResult.failure)),
430
+ });
431
+ if (fallbackResult._tag === "Success" && fallbackResult.success.length > 0) {
432
+ resolvedRefs = fallbackResult.success.filter((ref) => ref.type === "pack");
433
+ resolvedSource = fallbackSource;
434
+ break;
435
+ }
436
+ }
437
+ if (resolvedRefs === undefined) {
438
+ return yield* installRefused({
439
+ category: "network",
440
+ detail: "Pack could not be fetched from registry",
441
+ suggestions: [
442
+ {
443
+ description: "Remote registry discovery is not yet supported. Configure a file:// registry source or use a local registry source name.",
444
+ },
445
+ ],
446
+ });
447
+ }
448
+ }
449
+ else if (initialResult._tag === "Failure") {
450
+ return yield* installRefused({
451
+ category: "network",
452
+ detail: "Pack could not be fetched from registry",
453
+ suggestions: [{ description: "Verify the pack name and registry configuration." }],
454
+ cause: initialResult.failure,
455
+ });
456
+ }
457
+ const registryHosts = yield* ws.getRegistrySourceHosts().pipe(Effect.mapError((cause) => installRefused({
458
+ category: "internal",
459
+ detail: "Configured registry hosts could not be read",
460
+ cause,
461
+ })));
462
+ const ref = resolvedRefs?.[0];
463
+ if (ref === undefined) {
464
+ const loginSuggestions = yield* registryLoginSuggestions(probes.map((probe) => probe.location));
465
+ return yield* installRefused({
466
+ category: "not_found",
467
+ detail: `Pack "${request.packName}" not found in registry`,
468
+ suggestions: [
469
+ { description: "Verify the pack name and check available packs." },
470
+ ...loginSuggestions,
471
+ ],
472
+ });
473
+ }
474
+ return {
475
+ ref,
476
+ probes,
477
+ registrySourceLabel: formatRegistrySourceLabel({ source: resolvedSource, registryHosts }),
478
+ };
479
+ });
480
+ /** Render the probe evidence a discovery collected. */
481
+ export const packDiscoveryDiagnostics = (request, discovery) => [
482
+ `Pack: ${request.owner}/packs/${request.packName}`,
483
+ ...(request.sourceResolution === undefined
484
+ ? []
485
+ : [`Source resolution: ${request.sourceResolution}`]),
486
+ ...(discovery.probes.length > 0
487
+ ? [`Host resolution: ${discovery.probes.map(formatRegistryProbe).join("; ")}`]
488
+ : []),
489
+ `Registry source: ${discovery.registrySourceLabel}`,
490
+ "Found pack",
491
+ ];
492
+ /** Settle the pack this request installs. */
493
+ export const finalizePackInstallIntent = (request, discovery) => ({
494
+ packToInstall: discovery.ref,
495
+ versionRange: request.versionRange,
496
+ unattended: request.unattended,
497
+ nonInteractive: request.nonInteractive,
498
+ ...(request.releaseAgeEvaluation === undefined
499
+ ? {}
500
+ : { releaseAgeEvaluation: request.releaseAgeEvaluation }),
501
+ ...(request.releaseAgeHoldbackBehavior === undefined
502
+ ? {}
503
+ : { releaseAgeHoldbackBehavior: request.releaseAgeHoldbackBehavior }),
504
+ });
505
+ /** The single atomic closure a settled pack intent becomes. */
506
+ export const planPackInstall = Effect.fn("InstallExtensions.planPack")(function* (intent) {
507
+ const ws = yield* WorkspaceMutations;
508
+ const path = yield* Path.Path;
509
+ const sources = yield* SourceHostProviders;
510
+ const packManager = yield* PackManager;
511
+ const skillManager = yield* SkillManager;
512
+ const subagentManager = yield* SubagentManager;
513
+ const ruleManager = yield* RuleManager;
514
+ const hookManager = yield* HookManager;
515
+ const knowledgeManager = yield* KnowledgeManager;
516
+ const mcpServerManager = yield* McpServerManager;
517
+ const packIdentity = `${intent.packToInstall.owner}/packs/${intent.packToInstall.name}`;
518
+ const authority = yield* scanWorkspaceAuthority(intent.packToInstall);
519
+ if (authority.blockers.length > 0) {
520
+ const suggestions = authority.blockers
521
+ .flatMap((fact) => fact.recovery)
522
+ .filter((suggestion, index, all) => all.findIndex((candidate) => candidate.description === suggestion.description) === index);
523
+ return {
524
+ _tag: "Plan",
525
+ name: "Install pack",
526
+ description: Option.some("Workspace source authority prevents this pack graph transition."),
527
+ jobs: [
528
+ {
529
+ concurrency: 1,
530
+ steps: [
531
+ {
532
+ readiness: "error",
533
+ label: packIdentity,
534
+ errorMessage: authority.blockers.map((fact) => fact.detail).join("; "),
535
+ blockingConditionIds: authority.blockers.map((fact) => fact.id),
536
+ artifact: { path: "pack graph", scope: ws.scope, change: "unchanged", fileCount: 0 },
537
+ },
538
+ ],
539
+ },
540
+ ],
541
+ presentation: operationPresentation({ imperative: "install", past: "Installed", gerund: "Installing" }, "pack"),
542
+ riskConditions: authority.blockers.map((fact) => ({
543
+ level: "blocked",
544
+ id: fact.id,
545
+ detail: fact.detail,
546
+ errorCode: "conflict",
547
+ })),
548
+ failureSuggestions: suggestions,
549
+ };
550
+ }
551
+ const minimumReleaseAge = yield* resolveMinimumReleaseAge(intent.unattended ?? false);
552
+ const supportedDependencyTypes = [
553
+ "skill",
554
+ "mcp-server",
555
+ "subagent",
556
+ "rule",
557
+ "hook",
558
+ "knowledge",
559
+ ];
560
+ const expansion = yield* (intent.releaseAgeEvaluation === undefined
561
+ ? expandPackInstallRefs({
562
+ pack: intent.packToInstall,
563
+ supportedDependencyTypes,
564
+ sources,
565
+ minimumReleaseAge,
566
+ workspaceResolver: authority.workspaceResolver,
567
+ ...(intent.dependencyResolver === undefined
568
+ ? {}
569
+ : { dependencyResolver: intent.dependencyResolver }),
570
+ }).pipe(Effect.map((refs) => ({
571
+ kind: "selected",
572
+ refs,
573
+ holdbacks: [],
574
+ bypasses: [],
575
+ })))
576
+ : expandPackInstallRefsWithReleaseAge({
577
+ pack: intent.packToInstall,
578
+ supportedDependencyTypes,
579
+ sources,
580
+ releaseAgeEvaluation: intent.releaseAgeEvaluation,
581
+ workspaceResolver: authority.workspaceResolver,
582
+ ...(intent.dependencyResolver === undefined
583
+ ? {}
584
+ : { dependencyResolver: intent.dependencyResolver }),
585
+ })).pipe(Effect.mapError((cause) => installRefused({
586
+ category: "conflict",
587
+ detail: `Pack ${packIdentity} could not be expanded`,
588
+ cause,
589
+ })));
590
+ const releaseAge = intent.releaseAgeEvaluation === undefined
591
+ ? undefined
592
+ : {
593
+ evaluatedAt: DateTime.formatIso(intent.releaseAgeEvaluation.evaluatedAt),
594
+ holdbacks: normalizeReleaseAgeRecords(expansion.holdbacks),
595
+ bypasses: normalizeReleaseAgeRecords(expansion.bypasses),
596
+ };
597
+ if (expansion.kind === "policy_held") {
598
+ // A held-back release preserves the current graph only when that graph is
599
+ // complete and every one of its members is still usable; otherwise there
600
+ // is nothing to preserve and the operation is blocked.
601
+ let preservable = false;
602
+ if (intent.releaseAgeHoldbackBehavior === "preserve-or-block") {
603
+ const graph = yield* readDesiredGraph;
604
+ if (graph.complete) {
605
+ const currentPack = yield* usableAcceptedCanonical({
606
+ workspace: ws,
607
+ type: "pack",
608
+ name: intent.packToInstall.pack.name,
609
+ }).pipe(Effect.mapError((cause) => installRefused({
610
+ category: "internal",
611
+ detail: "Accepted pack content could not be inspected",
612
+ cause,
613
+ })));
614
+ if (Option.isSome(currentPack) &&
615
+ currentPack.value.ref.refType === "registry" &&
616
+ currentPack.value.ref.owner === intent.packToInstall.owner &&
617
+ currentPack.value.ref.name === intent.packToInstall.name &&
618
+ (Option.isNone(intent.versionRange) ||
619
+ versionSatisfiesRange(currentPack.value.ref.version, intent.versionRange.value))) {
620
+ const nodes = graph.nodes.filter((node) => (node.type === "pack" && node.name === intent.packToInstall.pack.name) ||
621
+ node.origins.some((origin) => origin.type === "pack" && origin.pack === packIdentity));
622
+ const usable = yield* Effect.forEach(nodes, (node) => usableAcceptedCanonical({ workspace: ws, type: node.type, name: node.name }).pipe(Effect.map(Option.isSome), Effect.mapError((cause) => installRefused({
623
+ category: "internal",
624
+ detail: `Accepted content for ${node.name} could not be inspected`,
625
+ cause,
626
+ }))));
627
+ preservable = usable.every((value) => value);
628
+ }
629
+ }
630
+ }
631
+ const blocked = intent.releaseAgeHoldbackBehavior === "preserve-or-block" && !preservable;
632
+ return {
633
+ _tag: "Plan",
634
+ name: "Install pack",
635
+ description: Option.some(blocked
636
+ ? "The selected pack graph includes a release held by the minimum release age"
637
+ : "The current pack graph is unchanged while a required release ages"),
638
+ presentation: operationPresentation({ imperative: "install", past: "Installed", gerund: "Installing" }, "pack"),
639
+ jobs: [],
640
+ ...(releaseAge === undefined ? {} : { releaseAge }),
641
+ ...(blocked
642
+ ? {
643
+ riskConditions: [
644
+ {
645
+ level: "blocked",
646
+ id: "minimum-release-age",
647
+ detail: "The selected pack graph has no complete usable accepted resolution.",
648
+ errorCode: "conflict",
649
+ },
650
+ ],
651
+ }
652
+ : {}),
653
+ };
654
+ }
655
+ const refs = expansion.refs;
656
+ const graph = authority.graph;
657
+ const currentPackNode = graph.nodes.find((node) => node.type === "pack" && node.name === intent.packToInstall.pack.name);
658
+ const preservedPackActivation = currentPackNode?.enabled ?? true;
659
+ // Installing is also the recovery path for a configured pack whose canonical
660
+ // manifest or accepted resolution is unavailable. Preserve fail-closed
661
+ // cleanup by suppressing dropped-member removal until the pre-install graph
662
+ // is complete.
663
+ const existingPack = graph.complete ? currentPackNode : undefined;
664
+ const retentionPolicy = makeWorkspaceRetentionPolicy(ws);
665
+ const installSteps = yield* Effect.forEach(refs, (ref) => ref.type === "pack"
666
+ ? Effect.succeed(buildInstallOperation(packManager, {
667
+ toStepFailure: lifecycleStepFailure,
668
+ ref,
669
+ versionRange: intent.versionRange,
670
+ ...(intent.forceCanonical === true ? { force: true } : {}),
671
+ installedBefore: graph.complete
672
+ ? packManager.isInstalled({
673
+ target: { type: "pack", name: ref.pack.name, owner: ref.owner },
674
+ })
675
+ : Effect.succeed(false),
676
+ buildArtifact: ({ installedBefore }) => Effect.succeed(registrySourceArtifact({ ref, scope: ws.scope, installedBefore })),
677
+ }))
678
+ : buildPackMemberInstallStep({
679
+ ref,
680
+ graphComplete: graph.complete,
681
+ nonInteractive: intent.nonInteractive,
682
+ }), { concurrency: 1 });
683
+ const nextDependencies = collectResolvedDependencyNames(refs);
684
+ const unresolvedDroppedTargets = existingPack === undefined
685
+ ? []
686
+ : collectDroppedPackDependencyTargets({
687
+ graph,
688
+ replacingPackIdentity: existingPack.identity,
689
+ nextDependencies,
690
+ });
691
+ const droppedTargets = yield* Effect.forEach(unresolvedDroppedTargets, (dropped) => acceptedLockedCanonicalPath({
692
+ workspace: ws,
693
+ type: dropped.target.type,
694
+ name: dropped.target.name,
695
+ }).pipe(Effect.map((canonicalPath) => ({
696
+ ...dropped,
697
+ sourcePath: Option.match(canonicalPath, {
698
+ onNone: () => toLabel(dropped.target),
699
+ onSome: (value) => path.relative(ws.baseDir, value),
700
+ }),
701
+ })), Effect.mapError((cause) => installRefused({
702
+ category: "internal",
703
+ detail: `Accepted canonical path for ${dropped.target.name} could not be read`,
704
+ cause,
705
+ }))), { concurrency: 1 });
706
+ const uninstallSteps = droppedTargets.map(({ target }) => {
707
+ switch (target.type) {
708
+ case "skill":
709
+ return buildUninstallOperation(skillManager, retentionPolicy, {
710
+ toStepFailure: lifecycleStepFailure,
711
+ target,
712
+ });
713
+ case "mcp-server":
714
+ return buildUninstallOperation(mcpServerManager, retentionPolicy, {
715
+ toStepFailure: lifecycleStepFailure,
716
+ target,
717
+ });
718
+ case "subagent":
719
+ return buildUninstallOperation(subagentManager, retentionPolicy, {
720
+ toStepFailure: lifecycleStepFailure,
721
+ target,
722
+ });
723
+ case "rule":
724
+ return buildUninstallOperation(ruleManager, retentionPolicy, {
725
+ toStepFailure: lifecycleStepFailure,
726
+ target,
727
+ skipProjections: true,
728
+ });
729
+ case "hook":
730
+ return buildUninstallOperation(hookManager, retentionPolicy, {
731
+ toStepFailure: lifecycleStepFailure,
732
+ target,
733
+ skipProjections: true,
734
+ });
735
+ case "knowledge":
736
+ return buildUninstallOperation(knowledgeManager, retentionPolicy, {
737
+ toStepFailure: lifecycleStepFailure,
738
+ target,
739
+ skipProjections: true,
740
+ });
741
+ }
742
+ });
743
+ const resolvedTargets = refs.map(targetFromRef);
744
+ // A member's activation survives a pack replacement: what the incoming pack
745
+ // decides is combined with every origin the member still has.
746
+ const expectedMemberActivation = (target) => {
747
+ const currentNode = graph.nodes.find((node) => node.type === target.type && node.name === target.name);
748
+ const preservedOrigins = (currentNode?.origins ?? []).filter((origin) => origin.type !== "pack" ||
749
+ origin.pack.replace(/^workspace:/, "") !== packIdentity.replace(/^workspace:/, ""));
750
+ return isDesiredExtensionActive([
751
+ ...preservedOrigins,
752
+ { type: "pack", enabled: preservedPackActivation },
753
+ ]);
754
+ };
755
+ const artifactTargets = [
756
+ ...refs.map((ref) => {
757
+ const target = targetFromRef(ref);
758
+ if (ref.refType === "workspace")
759
+ return { path: ref.location, change: "unchanged" };
760
+ return {
761
+ path: registrySourcePath(ref, ws.scope),
762
+ change: graph.nodes.some((node) => node.type === target.type && node.name === target.name)
763
+ ? "updated"
764
+ : "created",
765
+ };
766
+ }),
767
+ ...droppedTargets.map((dropped) => ({
768
+ path: dropped.sourcePath,
769
+ change: "removed",
770
+ })),
771
+ ];
772
+ const projectionStep = intent.deferProjections === true
773
+ ? Option.none()
774
+ : yield* buildAggregateProjectionStep({
775
+ types: new Set([
776
+ ...refs.map((ref) => ref.type),
777
+ ...droppedTargets.map(({ target }) => target.type),
778
+ ]),
779
+ });
780
+ const graphStep = yield* buildAtomicPackGraphStep({
781
+ label: packIdentity,
782
+ message: `Installed ${packIdentity} and ${refs.length - 1} pack member${refs.length === 2 ? "" : "s"}`,
783
+ artifact: {
784
+ path: "pack graph",
785
+ scope: ws.scope,
786
+ change: "updated",
787
+ fileCount: refs.length + droppedTargets.length,
788
+ targets: artifactTargets,
789
+ },
790
+ children: [
791
+ ...installSteps.map((step, index) => ({
792
+ step,
793
+ coverage: packInstallCoverage(refs[index]),
794
+ })),
795
+ ...uninstallSteps.map((step) => ({ step, coverage: "ineligible" })),
796
+ ...Option.toArray(projectionStep).map((step) => ({
797
+ step,
798
+ coverage: "ineligible",
799
+ })),
800
+ ],
801
+ // The plan was built against one workspace authority; if that authority
802
+ // moved, this candidate describes a decision that is no longer available.
803
+ preTransition: Effect.gen(function* () {
804
+ const refreshed = yield* scanWorkspaceAuthority(intent.packToInstall);
805
+ if (refreshed.blockers.length > 0 || refreshed.fingerprint !== authority.fingerprint) {
806
+ return yield* installRefused({
807
+ category: "conflict",
808
+ detail: "Workspace source authority changed before the pack transition applied",
809
+ suggestions: refreshed.blockers.length === 0
810
+ ? [{ description: "Rerun the command to resolve a fresh pack candidate." }]
811
+ : refreshed.blockers.flatMap((fact) => fact.recovery),
812
+ });
813
+ }
814
+ }).pipe(Effect.asVoid),
815
+ validate: validatePackGraphPostcondition({
816
+ requiredPacks: [
817
+ {
818
+ name: intent.packToInstall.name,
819
+ identity: packIdentity,
820
+ enabled: preservedPackActivation,
821
+ },
822
+ ],
823
+ requiredMembers: resolvedTargets.flatMap((target) => target.type === "pack"
824
+ ? []
825
+ : [
826
+ {
827
+ type: target.type,
828
+ name: target.name,
829
+ packIdentity,
830
+ enabled: expectedMemberActivation(target),
831
+ },
832
+ ]),
833
+ absent: droppedTargets.map(({ target }) => target),
834
+ }),
835
+ });
836
+ return {
837
+ _tag: "Plan",
838
+ name: "Install pack",
839
+ description: Option.none(),
840
+ presentation: operationPresentation({ imperative: "install", past: "Installed", gerund: "Installing" }, "pack"),
841
+ jobs: [{ concurrency: 1, steps: [graphStep] }],
842
+ ...(releaseAge === undefined ? {} : { releaseAge }),
843
+ };
844
+ });
845
+ //# sourceMappingURL=plan.js.map