@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,579 @@
1
+ /**
2
+ * Advancing what the workspace already accepted.
3
+ *
4
+ * Update is one use case with two shapes of request. A person can name one
5
+ * extension by its Registry identity, or ask the workspace to sweep every
6
+ * configured entry — optionally narrowed to one type, or to a selection of
7
+ * installed names. Both shapes answer the same question: within the intent
8
+ * the workspace recorded, is there a newer version its source is willing to
9
+ * offer, and may this workspace accept it?
10
+ *
11
+ * Everything that decides the answer lives here. A named update is classified
12
+ * against the desired graph first, so a refusal is reported before any
13
+ * Registry is contacted: an extension the workspace never asked for is not
14
+ * installed on its behalf, a Pack-owned member is not forked into direct
15
+ * intent, and a bundled or workspace-authored source is never replaced from
16
+ * the Registry. A sweep resolves each configured entry independently, so one
17
+ * unreachable source blocks one unit rather than the operation.
18
+ *
19
+ * `prepare` settles all of it and writes nothing: it produces either a frozen
20
+ * execution candidate or the settled reason there is nothing to execute.
21
+ * `previewOrApply` resolves that same candidate, so the preview a person
22
+ * reads and the apply that follows describe one decision.
23
+ *
24
+ * @experimental This API is unstable and may change without notice.
25
+ */
26
+ import * as DateTime from "effect/DateTime";
27
+ import * as Effect from "effect/Effect";
28
+ import * as Option from "effect/Option";
29
+ import { HookManager, KnowledgeManager, McpServerManager, PackManager, RuleManager, SkillManager, SubagentManager, } from "@agentxm/extension-materialization";
30
+ import { parseSourceQualifiedRegistrySourcePatternParts, decodeExtensionNameSync, } from "@agentxm/extension-model/unstable/extensions";
31
+ import { decodeVersionRangeSync, versionSatisfiesRange, } from "@agentxm/extension-model/unstable/version-constraints";
32
+ import { ReleaseAgePosture, makeConfiguredReleaseAgeEvaluation, normalizeReleaseAgeRecords, resolveTargetedUpdateContext, } from "@agentxm/extension-resolution";
33
+ import { SourceHostProviders, WorkspaceCatalog, resolveSource, } from "@agentxm/extension-sources";
34
+ import { makeOperationResolution, operationPresentation, prepareExecutionCandidate, resolveExecutionCandidate, } from "@agentxm/workspace-operations";
35
+ import { WorkspaceMutations, WorkspaceRecords, acceptedResolutionRef, usableAcceptedCanonical, } from "@agentxm/workspace-state";
36
+ import { ExtensionLifecycleFailed } from "../errors.js";
37
+ import { buildPackMemberInstallStep } from "../packs/member-install-step.js";
38
+ import { withPublisherTrust } from "../publisher-binding.js";
39
+ import { planHookInstall } from "../hooks/install/plan.js";
40
+ import { planKnowledgeInstall } from "../knowledge/install/plan.js";
41
+ import { planMcpServerInstall } from "../mcps/install/plan.js";
42
+ import { planPackInstall } from "../packs/install/plan.js";
43
+ import { planRuleInstall } from "../rules/install/plan.js";
44
+ import { planSkillInstall } from "../skills/install/plan.js";
45
+ import { planSubagentInstall } from "../subagents/install/plan.js";
46
+ import { blockerClass, blockerDetail, staleOutputContext } from "./blockers.js";
47
+ import { buildWorkspaceUpdatePlan } from "./configured.js";
48
+ import { resolveConfiguredUpdateSelection } from "./selector.js";
49
+ import { resolveRootUpdateIntent } from "./root-request.js";
50
+ import { wrapTargetedUpdatePlan } from "./targeted-plan.js";
51
+ // -----------------------------------------------------------------------------
52
+ // Release-age evidence
53
+ // -----------------------------------------------------------------------------
54
+ const updatePresentation = (type) => operationPresentation({ imperative: "update", past: "Updated", gerund: "Updating" }, type);
55
+ const releaseAgeRecord = (args) => ({
56
+ reason: "minimum-release-age",
57
+ target: args.intent.target,
58
+ dependencyPath: [args.intent.target],
59
+ ...Option.match(args.intent.versionRange, {
60
+ onNone: () => ({}),
61
+ onSome: (requestedRange) => ({ requestedRange }),
62
+ }),
63
+ ...(args.currentVersion === undefined ? {} : { currentVersion: args.currentVersion }),
64
+ ...(args.selectedVersion === undefined ? {} : { selectedVersion: args.selectedVersion }),
65
+ candidateVersion: args.evidence.version,
66
+ publishedAt: args.evidence.publishedAt,
67
+ eligibleAt: args.evidence.eligibleAt,
68
+ minimumReleaseAgeSeconds: args.evidence.minimumReleaseAgeSeconds,
69
+ });
70
+ // -----------------------------------------------------------------------------
71
+ // Desired-state facts a targeted update is decided against
72
+ // -----------------------------------------------------------------------------
73
+ const normalizedPackIdentity = (identity) => identity.startsWith("workspace:") ? identity.slice("workspace:".length) : identity;
74
+ const desiredNodeForIntent = (graph, intent) => graph.nodes.find((node) => {
75
+ if (node.type !== intent.type)
76
+ return false;
77
+ if (normalizedPackIdentity(node.identity) === intent.target)
78
+ return true;
79
+ if (node.type !== "mcp-server" || node.source === undefined)
80
+ return false;
81
+ const parsed = parseSourceQualifiedRegistrySourcePatternParts(node.source);
82
+ return (parsed?.name !== undefined &&
83
+ `${parsed.owner}/${parsed.type}/${parsed.name}` === intent.target);
84
+ });
85
+ /**
86
+ * The version a held release would preserve: the accepted one, when it is
87
+ * still usable, still the same package, and still inside the requested range.
88
+ * A Pack preserves only when its whole member closure is usable too.
89
+ */
90
+ const preservableRegistryVersion = Effect.fn("UpdateExtensions.preservableVersion")(function* (intent) {
91
+ const workspace = yield* WorkspaceMutations;
92
+ const graph = yield* workspace.getDesiredStateGraph();
93
+ if (!graph.complete)
94
+ return Option.none();
95
+ const desired = desiredNodeForIntent(graph, intent);
96
+ if (desired === undefined)
97
+ return Option.none();
98
+ const canonical = yield* usableAcceptedCanonical({
99
+ workspace,
100
+ type: intent.type,
101
+ name: desired.name,
102
+ });
103
+ if (Option.isNone(canonical))
104
+ return Option.none();
105
+ const ref = canonical.value.ref;
106
+ if (ref.refType !== "registry" || ref.owner !== intent.owner || ref.name !== intent.name) {
107
+ return Option.none();
108
+ }
109
+ if (Option.isSome(intent.versionRange) &&
110
+ !versionSatisfiesRange(ref.version, decodeVersionRangeSync(intent.versionRange.value))) {
111
+ return Option.none();
112
+ }
113
+ if (intent.type === "pack") {
114
+ const graphNodes = graph.nodes.filter((node) => (node.type === "pack" && node.name === intent.name) ||
115
+ node.origins.some((origin) => origin.type === "pack" && normalizedPackIdentity(origin.pack) === intent.target));
116
+ const usable = yield* Effect.forEach(graphNodes, (node) => usableAcceptedCanonical({ workspace, type: node.type, name: node.name }).pipe(Effect.map(Option.isSome)));
117
+ if (usable.some((value) => !value))
118
+ return Option.none();
119
+ }
120
+ return Option.some(ref.version);
121
+ });
122
+ /** The accepted version a resolution must not fall below, when there is one. */
123
+ const acceptedRegistryFloor = Effect.fn("UpdateExtensions.acceptedFloor")(function* (intent) {
124
+ const workspace = yield* WorkspaceMutations;
125
+ const graph = yield* workspace.getDesiredStateGraph();
126
+ const desired = desiredNodeForIntent(graph, intent);
127
+ if (desired === undefined) {
128
+ return Option.none();
129
+ }
130
+ const accepted = yield* acceptedResolutionRef({
131
+ workspace,
132
+ type: intent.type,
133
+ name: desired.name,
134
+ });
135
+ return Option.flatMap(accepted, (ref) => ref.refType === "registry" && ref.owner === intent.owner && ref.name === intent.name
136
+ ? Option.some({ version: ref.version, publisherBindingId: ref.publisherBindingId })
137
+ : Option.none());
138
+ });
139
+ // -----------------------------------------------------------------------------
140
+ // Per-type planning from one resolved ref
141
+ // -----------------------------------------------------------------------------
142
+ /**
143
+ * Plan the advance for one resolved Registry ref through the same per-type
144
+ * planner the install routes use: advancing an accepted resolution is
145
+ * installing the version its source now offers.
146
+ */
147
+ const planResolvedTarget = Effect.fn("UpdateExtensions.planResolvedTarget")(function* (args) {
148
+ const { intent, ref, versionRange } = args;
149
+ const mismatch = () => Effect.fail(new ExtensionLifecycleFailed({
150
+ category: "internal",
151
+ detail: `Registry resolved ${intent.target} as ${ref.type}, expected ${intent.type}`,
152
+ }));
153
+ switch (intent.type) {
154
+ case "skill":
155
+ return ref.type !== "skill"
156
+ ? yield* mismatch()
157
+ : yield* planSkillInstall({ skillsToInstall: [{ ref, versionRange }] });
158
+ case "subagent":
159
+ return ref.type !== "subagent"
160
+ ? yield* mismatch()
161
+ : yield* planSubagentInstall({ subagentsToInstall: [{ ref, versionRange }] });
162
+ case "rule":
163
+ return ref.type !== "rule"
164
+ ? yield* mismatch()
165
+ : yield* planRuleInstall({ refs: [{ ref, versionRange }] });
166
+ case "hook":
167
+ return ref.type !== "hook"
168
+ ? yield* mismatch()
169
+ : yield* planHookInstall({ refs: [{ ref, versionRange }] });
170
+ case "knowledge":
171
+ return ref.type !== "knowledge"
172
+ ? yield* mismatch()
173
+ : yield* planKnowledgeInstall({ refs: [{ ref, versionRange }] });
174
+ case "mcp-server": {
175
+ if (ref.type !== "mcp-server")
176
+ return yield* mismatch();
177
+ const workspace = yield* WorkspaceMutations;
178
+ const graph = yield* workspace.getDesiredStateGraph();
179
+ const desired = desiredNodeForIntent(graph, intent);
180
+ return yield* planMcpServerInstall({
181
+ ref,
182
+ localName: decodeExtensionNameSync(desired?.name ?? ref.server.name),
183
+ versionRange: Option.map(versionRange, (range) => range),
184
+ force: false,
185
+ nonInteractive: args.nonInteractive,
186
+ });
187
+ }
188
+ case "pack":
189
+ return ref.type !== "pack"
190
+ ? yield* mismatch()
191
+ : yield* planPackInstall({
192
+ packToInstall: ref,
193
+ versionRange,
194
+ unattended: true,
195
+ nonInteractive: args.nonInteractive,
196
+ releaseAgeEvaluation: args.releaseAgeEvaluation,
197
+ releaseAgeHoldbackBehavior: "preserve-or-block",
198
+ });
199
+ }
200
+ });
201
+ // -----------------------------------------------------------------------------
202
+ // Targeted preparation
203
+ // -----------------------------------------------------------------------------
204
+ const blockedCandidate = (context) => ({
205
+ outcome: "blocked",
206
+ name: `Update ${context.target.fqn}`,
207
+ detail: blockerDetail(context),
208
+ subject: context.target.fqn,
209
+ subjectType: context.target.type,
210
+ blockingClass: blockerClass(context.blocker),
211
+ ...(context.blocker === undefined ? {} : { reference: context.blocker }),
212
+ targetedContext: context,
213
+ });
214
+ /**
215
+ * A Pack is classified as a closure rather than a member, so the
216
+ * member-oriented blockers never see it. Desired state still gates it:
217
+ * updating a Pack the workspace never asked for would acquire the Pack and
218
+ * its whole member closure.
219
+ */
220
+ const blockedUndesiredPack = (intent) => ({
221
+ outcome: "blocked",
222
+ name: `Update ${intent.target}`,
223
+ detail: `${intent.target} is not desired by this workspace`,
224
+ subject: intent.target,
225
+ subjectType: intent.type,
226
+ blockingClass: "precondition-unmet",
227
+ reference: "not-desired",
228
+ });
229
+ /** The outcome a held release settles into: preserve, or block with evidence. */
230
+ const heldCandidate = Effect.fn("UpdateExtensions.heldCandidate")(function* (args) {
231
+ const currentVersion = yield* preservableRegistryVersion(args.intent);
232
+ const record = releaseAgeRecord({
233
+ intent: args.intent,
234
+ evidence: args.evidence,
235
+ ...(Option.isNone(currentVersion)
236
+ ? {}
237
+ : { currentVersion: currentVersion.value, selectedVersion: currentVersion.value }),
238
+ });
239
+ const releaseAge = {
240
+ evaluatedAt: args.evaluatedAt,
241
+ holdbacks: [record],
242
+ bypasses: [],
243
+ };
244
+ if (Option.isSome(currentVersion)) {
245
+ return {
246
+ outcome: "preserved",
247
+ name: `Update ${args.intent.target}`,
248
+ description: `Preserve ${args.intent.target} until its selected release is eligible`,
249
+ subjectType: args.intent.type,
250
+ releaseAge,
251
+ ...(args.context === undefined ? {} : { targetedContext: args.context }),
252
+ };
253
+ }
254
+ return {
255
+ outcome: "blocked",
256
+ name: `Update ${args.intent.target}`,
257
+ detail: `${args.intent.target}'s selected release is held by the minimum release age until ${args.evidence.eligibleAt}`,
258
+ subject: args.intent.target,
259
+ subjectType: args.intent.type,
260
+ blockingClass: "policy-excluded",
261
+ reference: "release-age-held",
262
+ releaseAge,
263
+ ...(args.context === undefined ? {} : { targetedContext: args.context }),
264
+ };
265
+ });
266
+ /** Settle a request that names one extension by its Registry identity. */
267
+ const prepareTargeted = Effect.fn("UpdateExtensions.prepareTargeted")(function* (request) {
268
+ const intent = yield* resolveRootUpdateIntent(request.source);
269
+ let targetedContext;
270
+ if (intent.type === "pack") {
271
+ const workspace = yield* WorkspaceMutations;
272
+ const graph = yield* workspace.getDesiredStateGraph();
273
+ if (desiredNodeForIntent(graph, intent) === undefined) {
274
+ return blockedUndesiredPack(intent);
275
+ }
276
+ }
277
+ else {
278
+ targetedContext = yield* resolveTargetedUpdateContext({
279
+ target: { type: intent.type, name: intent.name, fqn: intent.target },
280
+ ...(Option.isNone(intent.versionRange) ? {} : { explicitRange: intent.versionRange.value }),
281
+ });
282
+ if (targetedContext.public.blocker !== undefined) {
283
+ return blockedCandidate(targetedContext.public);
284
+ }
285
+ }
286
+ const releaseAgeEvaluation = yield* makeConfiguredReleaseAgeEvaluation();
287
+ const evaluatedAtText = DateTime.formatIso(releaseAgeEvaluation.evaluatedAt);
288
+ const source = yield* resolveSource(intent.source);
289
+ if (source.type !== "registry") {
290
+ return yield* new ExtensionLifecycleFailed({
291
+ category: "usage",
292
+ detail: "Root update requires a Registry FQN",
293
+ });
294
+ }
295
+ const providers = yield* SourceHostProviders;
296
+ const accepted = yield* acceptedRegistryFloor(intent);
297
+ const selected = yield* providers.resolveNamedRegistry(source, {
298
+ name: intent.name,
299
+ type: intent.type,
300
+ owner: intent.owner,
301
+ versionRange: targetedContext?.public.effectiveConstraint === undefined
302
+ ? intent.versionRange
303
+ : Option.some(decodeVersionRangeSync(targetedContext.public.effectiveConstraint)),
304
+ releaseAgeEvaluation,
305
+ ...(Option.isSome(accepted) ? { accepted: accepted.value } : {}),
306
+ });
307
+ if (selected.kind === "not_found") {
308
+ return yield* new ExtensionLifecycleFailed({
309
+ category: "not_found",
310
+ detail: `Registry extension "${selected.target}" was not found`,
311
+ });
312
+ }
313
+ if (selected.kind === "version_unsatisfied") {
314
+ return yield* new ExtensionLifecycleFailed({
315
+ category: "conflict",
316
+ title: "No compatible version",
317
+ detail: `No visible version of "${selected.target}" satisfies ${selected.requestedRange}`,
318
+ });
319
+ }
320
+ if (selected.kind === "policy_held") {
321
+ return yield* heldCandidate({
322
+ intent,
323
+ evidence: selected.candidate,
324
+ evaluatedAt: evaluatedAtText,
325
+ ...(targetedContext === undefined ? {} : { context: targetedContext.public }),
326
+ });
327
+ }
328
+ // A Pack-owned member advances inside its Pack's graph, so its step is the
329
+ // member transition rather than a direct install: nothing writes a direct
330
+ // declaration the workspace did not ask for.
331
+ const plan = targetedContext?.public.authority === "pack-aware"
332
+ ? yield* wrapTargetedUpdatePlan({
333
+ plan: yield* withPublisherTrust({
334
+ _tag: "Plan",
335
+ name: `Update ${intent.target}`,
336
+ description: Option.some(`Update pack-derived member ${intent.target}`),
337
+ jobs: [
338
+ {
339
+ concurrency: 1,
340
+ steps: [
341
+ yield* buildPackMemberInstallStep({
342
+ ref: yield* requirePackMemberRef(intent, selected.ref),
343
+ graphComplete: true,
344
+ nonInteractive: request.nonInteractive,
345
+ }),
346
+ ],
347
+ },
348
+ ],
349
+ }),
350
+ context: targetedContext,
351
+ })
352
+ : yield* planTargetedDirect({
353
+ intent,
354
+ ref: selected.ref,
355
+ nonInteractive: request.nonInteractive,
356
+ releaseAgeEvaluation,
357
+ ...(targetedContext === undefined ? {} : { context: targetedContext }),
358
+ });
359
+ const holdbacks = selected.kind === "exempted" || selected.newerHeld === undefined
360
+ ? []
361
+ : [
362
+ releaseAgeRecord({
363
+ intent,
364
+ evidence: selected.newerHeld,
365
+ selectedVersion: selected.ref.version,
366
+ ...(Option.isSome(accepted) && accepted.value.version === selected.ref.version
367
+ ? { currentVersion: accepted.value.version }
368
+ : {}),
369
+ }),
370
+ ];
371
+ const bypasses = selected.kind === "selected"
372
+ ? []
373
+ : [
374
+ {
375
+ ...releaseAgeRecord({
376
+ intent,
377
+ evidence: selected.bypassed,
378
+ selectedVersion: selected.ref.version,
379
+ }),
380
+ ...selected.exemption,
381
+ },
382
+ ];
383
+ return {
384
+ outcome: "planned",
385
+ subjectType: intent.type,
386
+ execution: yield* prepareExecutionCandidate(plan),
387
+ releaseAge: { evaluatedAt: evaluatedAtText, holdbacks, bypasses },
388
+ ...(targetedContext === undefined
389
+ ? {}
390
+ : { targetedContext: targetedContext.public, fingerprintedContext: targetedContext }),
391
+ };
392
+ });
393
+ /** A Pack member step accepts every installable ref except a nested Pack. */
394
+ const requirePackMemberRef = (intent, ref) => ref.type === "pack"
395
+ ? Effect.fail(new ExtensionLifecycleFailed({
396
+ category: "internal",
397
+ detail: `Registry resolved ${intent.target} as an unexpected pack`,
398
+ }))
399
+ : ref.type !== intent.type
400
+ ? Effect.fail(new ExtensionLifecycleFailed({
401
+ category: "internal",
402
+ detail: `Registry resolved ${intent.target} as ${ref.type}, expected ${intent.type}`,
403
+ }))
404
+ : Effect.succeed(ref);
405
+ /**
406
+ * Plan a direct advance. The durable range a direct declaration keeps is the
407
+ * one the request named, or the one the declaration already recorded — a
408
+ * targeted update never widens the intent the workspace expressed.
409
+ */
410
+ const planTargetedDirect = Effect.fn("UpdateExtensions.planTargetedDirect")(function* (args) {
411
+ const { intent, context } = args;
412
+ const durableRange = intent.type === "pack"
413
+ ? intent.versionRange
414
+ : Option.fromUndefinedOr(Option.getOrUndefined(intent.versionRange) ?? context?.public.direct?.constraint).pipe(Option.map(decodeVersionRangeSync));
415
+ const planned = yield* planResolvedTarget({
416
+ intent,
417
+ ref: args.ref,
418
+ versionRange: durableRange,
419
+ nonInteractive: args.nonInteractive,
420
+ releaseAgeEvaluation: args.releaseAgeEvaluation,
421
+ });
422
+ if (context === undefined) {
423
+ return {
424
+ ...planned,
425
+ presentation: updatePresentation(intent.type),
426
+ };
427
+ }
428
+ return yield* wrapTargetedUpdatePlan({
429
+ plan: yield* withPublisherTrust(planned),
430
+ context,
431
+ ...(Option.isNone(intent.versionRange) ? {} : { explicitRange: intent.versionRange.value }),
432
+ });
433
+ });
434
+ // -----------------------------------------------------------------------------
435
+ // Configured preparation
436
+ // -----------------------------------------------------------------------------
437
+ /**
438
+ * Which configured-agent outcomes the candidate projects: every selected name
439
+ * whose unit can actually converge. A unit that is not applicable, or that
440
+ * failed to plan, is excluded — projecting an outcome for it would promise an
441
+ * agent state nothing is going to establish.
442
+ */
443
+ const configuredAgentOperations = (plan, type, names) => Option.match(type, {
444
+ onNone: () => [],
445
+ onSome: (extensionType) => {
446
+ const nonConverging = new Set(plan.jobs.flatMap((job) => job.steps.flatMap((step) => step.key?.startsWith("not-applicable:") === true ||
447
+ step.key?.endsWith(":planning-error") === true
448
+ ? [step.label]
449
+ : [])));
450
+ return [
451
+ ...new Set(names ??
452
+ plan.jobs.flatMap((job) => job.steps.map((step) => step.label.replace(/^(?:Skip|Update)\s+/u, "")))),
453
+ ]
454
+ .filter((name) => !nonConverging.has(name))
455
+ .map((name) => ({ extensionType, name, plannedState: "enabled" }));
456
+ },
457
+ });
458
+ /** Settle a request that sweeps the configured entries. */
459
+ const prepareConfigured = Effect.fn("UpdateExtensions.prepareConfigured")(function* (request) {
460
+ const nothingToAdvance = (message) => ({
461
+ outcome: "nothing-configured",
462
+ message,
463
+ subjectType: Option.getOrElse(request.type, () => "mixed"),
464
+ planDescription: request.planDescription,
465
+ });
466
+ const selection = request.selector === undefined
467
+ ? { _tag: "All" }
468
+ : yield* resolveConfiguredUpdateSelection(request.selector);
469
+ if (selection._tag === "NoMatch")
470
+ return nothingToAdvance(selection.message);
471
+ const names = selection._tag === "Names" ? selection.names : undefined;
472
+ const result = yield* buildWorkspaceUpdatePlan({
473
+ type: request.type,
474
+ planName: request.planName,
475
+ planDescription: request.planDescription,
476
+ nonInteractive: request.nonInteractive,
477
+ ...(names === undefined ? {} : { names }),
478
+ });
479
+ if (result._tag === "NoConfiguredExtensions") {
480
+ return nothingToAdvance(result.message);
481
+ }
482
+ // Every Registry acceptance the sweep proposed is classified against the
483
+ // accepted resolution, so a replaced publisher binding carries the same
484
+ // interactive-only condition here as on the install routes.
485
+ const plan = yield* withPublisherTrust(result.plan);
486
+ return {
487
+ outcome: "planned",
488
+ subjectType: Option.getOrElse(request.type, () => "mixed"),
489
+ execution: yield* prepareExecutionCandidate(plan, {
490
+ configuredAgentOperations: configuredAgentOperations(plan, request.type, names),
491
+ }),
492
+ ...(names === undefined ? {} : { selectedNames: names }),
493
+ };
494
+ });
495
+ // -----------------------------------------------------------------------------
496
+ // prepare
497
+ // -----------------------------------------------------------------------------
498
+ /**
499
+ * Settle an update: read the request, decide what may advance, and freeze
500
+ * either the execution candidate or the reason there is nothing to execute.
501
+ */
502
+ export const prepareUpdate = Effect.fn("UpdateExtensions.prepare")(function* (request) {
503
+ return request.kind === "targeted"
504
+ ? yield* prepareTargeted(request)
505
+ : yield* prepareConfigured(request);
506
+ });
507
+ // -----------------------------------------------------------------------------
508
+ // previewOrApply
509
+ // -----------------------------------------------------------------------------
510
+ /** A settled refusal or preservation, said as one operation outcome. */
511
+ const settledResolution = (candidate, execution) => {
512
+ const mode = execution.request.mode;
513
+ switch (candidate.outcome) {
514
+ case "blocked":
515
+ return makeOperationResolution({
516
+ name: candidate.name,
517
+ description: Option.some(candidate.detail),
518
+ mode,
519
+ atomicity: { declared: "closure-atomic", applied: "closure-atomic" },
520
+ units: [],
521
+ presentation: updatePresentation(candidate.subjectType),
522
+ ...(candidate.releaseAge === undefined ? {} : { releaseAge: candidate.releaseAge }),
523
+ blocking: {
524
+ class: candidate.blockingClass,
525
+ subject: candidate.subject,
526
+ phase: "planning",
527
+ detail: candidate.detail,
528
+ causeCode: "conflict",
529
+ ...(candidate.reference === undefined ? {} : { reference: candidate.reference }),
530
+ },
531
+ });
532
+ case "preserved":
533
+ return makeOperationResolution({
534
+ name: candidate.name,
535
+ description: Option.some(candidate.description),
536
+ mode,
537
+ atomicity: { declared: "closure-atomic", applied: "closure-atomic" },
538
+ units: [],
539
+ presentation: updatePresentation(candidate.subjectType),
540
+ releaseAge: candidate.releaseAge,
541
+ });
542
+ }
543
+ };
544
+ /**
545
+ * Preview or apply a settled update, resolving to one operation outcome.
546
+ *
547
+ * A sweep that found nothing configured has no operation to resolve: the
548
+ * application reports it as the no-op it is, so this takes only the
549
+ * candidates that do describe an operation.
550
+ */
551
+ export const previewOrApplyUpdate = (candidate, execution) => candidate.outcome === "planned"
552
+ ? resolveExecutionCandidate(candidate.execution, execution).pipe(Effect.map((resolution) => candidate.releaseAge === undefined
553
+ ? resolution
554
+ : {
555
+ ...resolution,
556
+ releaseAge: {
557
+ evaluatedAt: candidate.releaseAge.evaluatedAt,
558
+ holdbacks: normalizeReleaseAgeRecords([
559
+ ...candidate.releaseAge.holdbacks,
560
+ ...(resolution.releaseAge?.holdbacks ?? []),
561
+ ]),
562
+ bypasses: normalizeReleaseAgeRecords([
563
+ ...candidate.releaseAge.bypasses,
564
+ ...(resolution.releaseAge?.bypasses ?? []),
565
+ ]),
566
+ },
567
+ }))
568
+ : Effect.succeed(settledResolution(candidate, execution));
569
+ /**
570
+ * The ownership context an outcome reports: a stale-candidate outcome says
571
+ * nothing moved, whatever the classification said before the transition.
572
+ */
573
+ export const contextForResolution = (context, resolution) => resolution.blocking?.class === "stale-candidate" ? staleOutputContext(context) : context;
574
+ /** The update use case: settle a request, then preview or apply it. */
575
+ export const UpdateExtensions = {
576
+ prepare: prepareUpdate,
577
+ previewOrApply: previewOrApplyUpdate,
578
+ };
579
+ //# sourceMappingURL=update-extensions.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Stable workspace-relative paths a plan and its result name.
3
+ *
4
+ * A removal reports what it touched — settings, the lockfile, the canonical
5
+ * package — as paths a person can open. They are derived from the workspace
6
+ * layout rather than from where the workspace happens to sit on disk, so the
7
+ * same operation reads identically from any checkout.
8
+ *
9
+ * @experimental This API is unstable and may change without notice.
10
+ */
11
+ import type * as Path from "effect/Path";
12
+ import type { ExtensionType } from "@agentxm/extension-model/unstable/extensions";
13
+ import type { WorkspaceScope } from "@agentxm/extension-model/unstable/workspace-scope";
14
+ import type { WorkspaceMutationsService } from "@agentxm/workspace-state";
15
+ /** Where the workspace's settings file sits, relative to the workspace root. */
16
+ export declare const workspaceSettingsPath: (scope: WorkspaceScope) => string;
17
+ /** Where the workspace's lockfile sits, relative to the workspace root. */
18
+ export declare const workspaceLockfilePath: (scope: WorkspaceScope) => string;
19
+ /** The root every acquired package sits under, relative to the workspace root. */
20
+ export declare const workspaceCanonicalRoot: (scope: WorkspaceScope) => string;
21
+ /** One acquired package's path, relative to the workspace root. */
22
+ export declare const workspaceCanonicalPath: (scope: WorkspaceScope, relativePath: string) => string;
23
+ /** Where one desired-graph node's package sits, authored or acquired. */
24
+ export declare const workspaceCanonicalNodePath: (path: Path.Path, ws: WorkspaceMutationsService, node: {
25
+ readonly type: ExtensionType;
26
+ readonly name: string;
27
+ readonly identity: string;
28
+ }) => string;
29
+ /** Where a workspace-authored package of this type and name sits. */
30
+ export declare const workspaceAuthoredPath: (path: Path.Path, ws: WorkspaceMutationsService, type: ExtensionType, name: string) => string;
31
+ //# sourceMappingURL=workspace-paths.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Stable workspace-relative paths a plan and its result name.
3
+ *
4
+ * A removal reports what it touched — settings, the lockfile, the canonical
5
+ * package — as paths a person can open. They are derived from the workspace
6
+ * layout rather than from where the workspace happens to sit on disk, so the
7
+ * same operation reads identically from any checkout.
8
+ *
9
+ * @experimental This API is unstable and may change without notice.
10
+ */
11
+ /** Where the workspace's settings file sits, relative to the workspace root. */
12
+ export const workspaceSettingsPath = (scope) => scope === "project" ? "axm.json" : ".axm/workspace/axm.json";
13
+ /** Where the workspace's lockfile sits, relative to the workspace root. */
14
+ export const workspaceLockfilePath = (scope) => scope === "project" ? "axm-lock.yaml" : ".axm/workspace/axm-lock.yaml";
15
+ /** The root every acquired package sits under, relative to the workspace root. */
16
+ export const workspaceCanonicalRoot = (scope) => scope === "project" ? "agent_extensions" : ".axm/workspace/agent_extensions";
17
+ /** One acquired package's path, relative to the workspace root. */
18
+ export const workspaceCanonicalPath = (scope, relativePath) => `${workspaceCanonicalRoot(scope)}/${relativePath.replace(/^\/+/, "")}`;
19
+ const extensionDirectory = {
20
+ skill: "skills",
21
+ "mcp-server": "mcps",
22
+ subagent: "subagents",
23
+ rule: "rules",
24
+ hook: "hooks",
25
+ knowledge: "knowledge",
26
+ pack: "packs",
27
+ };
28
+ /** Where one desired-graph node's package sits, authored or acquired. */
29
+ export const workspaceCanonicalNodePath = (path, ws, node) => {
30
+ if (node.identity.startsWith("workspace:") && ws.layout.scope === "project") {
31
+ return path.join(path.relative(ws.baseDir, ws.layout.authoredRoot(node.type)), node.name);
32
+ }
33
+ const identity = node.identity.startsWith("workspace:")
34
+ ? node.identity.slice("workspace:".length)
35
+ : node.identity;
36
+ return workspaceCanonicalPath(ws.scope, identity);
37
+ };
38
+ /** Where a workspace-authored package of this type and name sits. */
39
+ export const workspaceAuthoredPath = (path, ws, type, name) => {
40
+ if (ws.layout.scope === "project") {
41
+ return path.join(path.relative(ws.baseDir, ws.layout.authoredRoot(type)), name);
42
+ }
43
+ const owner = ws.layout.owner ?? "@workspace";
44
+ return workspaceCanonicalPath(ws.scope, `${owner}/${extensionDirectory[type]}/${name}`);
45
+ };
46
+ //# sourceMappingURL=workspace-paths.js.map