@agentxm/extension-lifecycle 0.28.11 → 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.
- package/dist/src/activation/display-paths.d.ts +25 -0
- package/dist/src/activation/display-paths.js +25 -0
- package/dist/src/activation/errors.d.ts +16 -0
- package/dist/src/activation/errors.js +7 -0
- package/dist/src/activation/set-activation.d.ts +134 -0
- package/dist/src/activation/set-activation.js +772 -0
- package/dist/src/demote/demote-to-external-source.d.ts +82 -0
- package/dist/src/demote/demote-to-external-source.js +233 -0
- package/dist/src/errors.d.ts +0 -8
- package/dist/src/errors.js +0 -8
- package/dist/src/glob.d.ts +35 -0
- package/dist/src/glob.js +75 -0
- package/dist/src/hooks/configured-agent-outcomes-provider.d.ts +7 -3
- package/dist/src/hooks/configured-agent-outcomes-provider.js +11 -4
- package/dist/src/hooks/install/plan.d.ts +45 -0
- package/dist/src/hooks/install/plan.js +224 -0
- package/dist/src/hooks/uninstall/plan.d.ts +20 -0
- package/dist/src/hooks/uninstall/plan.js +77 -0
- package/dist/src/index.d.ts +48 -16
- package/dist/src/index.js +60 -17
- package/dist/src/install/aggregate-projection-step.d.ts +16 -0
- package/dist/src/install/aggregate-projection-step.js +49 -0
- package/dist/src/install/configured.d.ts +65 -0
- package/dist/src/install/configured.js +406 -0
- package/dist/src/install/inline-mcp-operation.d.ts +4 -0
- package/dist/src/install/inline-mcp-operation.js +25 -0
- package/dist/src/install/install-extensions.d.ts +110 -0
- package/dist/src/install/install-extensions.js +282 -0
- package/dist/src/install/per-type-install.d.ts +12 -0
- package/dist/src/install/per-type-install.js +12 -0
- package/dist/src/install/registry-install-target.d.ts +35 -0
- package/dist/src/install/registry-install-target.js +66 -0
- package/dist/src/install/registry-login-suggestion.d.ts +21 -0
- package/dist/src/install/registry-login-suggestion.js +34 -0
- package/dist/src/install/registry-source-resolution.d.ts +54 -0
- package/dist/src/install/registry-source-resolution.js +216 -0
- package/dist/src/install/root-intent.d.ts +19 -0
- package/dist/src/install/root-intent.js +122 -0
- package/dist/src/install/selection-interaction.d.ts +46 -0
- package/dist/src/install/selection-interaction.js +22 -0
- package/dist/src/install/vocabulary.d.ts +143 -0
- package/dist/src/install/vocabulary.js +29 -0
- package/dist/src/knowledge/install/plan.d.ts +35 -0
- package/dist/src/knowledge/install/plan.js +128 -0
- package/dist/src/knowledge/uninstall/plan.d.ts +23 -0
- package/dist/src/knowledge/uninstall/plan.js +140 -0
- package/dist/src/live.d.ts +4 -11
- package/dist/src/live.js +4 -11
- package/dist/src/mcps/install/plan.d.ts +65 -0
- package/dist/src/mcps/install/plan.js +301 -0
- package/dist/src/mcps/operations/disable.d.ts +5 -3
- package/dist/src/mcps/operations/disable.js +6 -4
- package/dist/src/mcps/operations/enable.d.ts +5 -3
- package/dist/src/mcps/operations/enable.js +8 -6
- package/dist/src/mcps/operations/sync-outcome.d.ts +1 -1
- package/dist/src/mcps/operations/uninstall.d.ts +5 -3
- package/dist/src/mcps/operations/uninstall.js +4 -4
- package/dist/src/mcps/uninstall/plan.d.ts +24 -0
- package/dist/src/mcps/uninstall/plan.js +109 -0
- package/dist/src/packs/artifact.d.ts +21 -0
- package/dist/src/packs/artifact.js +32 -0
- package/dist/src/packs/constraint-gate.d.ts +30 -0
- package/dist/src/packs/constraint-gate.js +76 -0
- package/dist/src/packs/expansion.d.ts +13 -14
- package/dist/src/packs/expansion.js +1 -2
- package/dist/src/packs/graph-transition.d.ts +64 -0
- package/dist/src/packs/graph-transition.js +219 -0
- package/dist/src/packs/install/plan.d.ts +95 -0
- package/dist/src/packs/install/plan.js +845 -0
- package/dist/src/packs/member-install-step.d.ts +29 -0
- package/dist/src/packs/member-install-step.js +156 -0
- package/dist/src/packs/operations/install.d.ts +3 -3
- package/dist/src/packs/operations/install.js +5 -5
- package/dist/src/packs/promote-authored-pack.d.ts +86 -0
- package/dist/src/packs/promote-authored-pack.js +239 -0
- package/dist/src/packs/resolved-dependency.d.ts +9 -26
- package/dist/src/packs/resolved-dependency.js +8 -20
- package/dist/src/packs/uninstall/plan.d.ts +66 -0
- package/dist/src/packs/uninstall/plan.js +344 -0
- package/dist/src/packs/uninstall/readiness.d.ts +50 -0
- package/dist/src/packs/uninstall/readiness.js +130 -0
- package/dist/src/publisher-binding.d.ts +8 -40
- package/dist/src/publisher-binding.js +8 -46
- package/dist/src/rules/install/plan.d.ts +35 -0
- package/dist/src/rules/install/plan.js +133 -0
- package/dist/src/rules/uninstall/plan.d.ts +19 -0
- package/dist/src/rules/uninstall/plan.js +54 -0
- package/dist/src/skills/install/bundled.d.ts +73 -0
- package/dist/src/skills/install/bundled.js +244 -0
- package/dist/src/skills/install/plan.d.ts +72 -0
- package/dist/src/skills/install/plan.js +429 -0
- package/dist/src/skills/install/selection.d.ts +26 -0
- package/dist/src/skills/install/selection.js +41 -0
- package/dist/src/skills/install/source.d.ts +21 -0
- package/dist/src/skills/install/source.js +76 -0
- package/dist/src/skills/operations/disable.d.ts +3 -2
- package/dist/src/skills/operations/disable.js +7 -11
- package/dist/src/skills/operations/enable.d.ts +3 -2
- package/dist/src/skills/operations/enable.js +6 -8
- package/dist/src/skills/operations/install.d.ts +4 -4
- package/dist/src/skills/operations/install.js +13 -12
- package/dist/src/skills/uninstall/plan.d.ts +23 -0
- package/dist/src/skills/uninstall/plan.js +165 -0
- package/dist/src/skills/utils.js +1 -1
- package/dist/src/step-failure-conversion.d.ts +44 -0
- package/dist/src/step-failure-conversion.js +26 -0
- package/dist/src/step-failure.d.ts +29 -0
- package/dist/src/step-failure.js +130 -0
- package/dist/src/subagents/install/plan.d.ts +49 -0
- package/dist/src/subagents/install/plan.js +270 -0
- package/dist/src/subagents/install/selection.d.ts +26 -0
- package/dist/src/subagents/install/selection.js +37 -0
- package/dist/src/subagents/install/source.d.ts +18 -0
- package/dist/src/subagents/install/source.js +72 -0
- package/dist/src/subagents/operations/disable.d.ts +5 -3
- package/dist/src/subagents/operations/disable.js +5 -3
- package/dist/src/subagents/operations/enable.d.ts +5 -3
- package/dist/src/subagents/operations/enable.js +17 -12
- package/dist/src/subagents/uninstall/plan.d.ts +20 -0
- package/dist/src/subagents/uninstall/plan.js +142 -0
- package/dist/src/test-packages.d.ts +34 -0
- package/dist/src/test-packages.js +111 -0
- package/dist/src/test-registry.d.ts +78 -0
- package/dist/src/test-registry.js +223 -0
- package/dist/src/testing.d.ts +108 -0
- package/dist/src/testing.js +241 -0
- package/dist/src/uninstall/retention-policy.d.ts +21 -0
- package/dist/src/uninstall/retention-policy.js +28 -0
- package/dist/src/uninstall/root-intent.d.ts +17 -0
- package/dist/src/uninstall/root-intent.js +101 -0
- package/dist/src/uninstall/uninstall-extensions.d.ts +61 -0
- package/dist/src/uninstall/uninstall-extensions.js +127 -0
- package/dist/src/uninstall/vocabulary.d.ts +40 -0
- package/dist/src/uninstall/vocabulary.js +14 -0
- package/dist/src/update/atomicity.d.ts +22 -0
- package/dist/src/update/atomicity.js +23 -0
- package/dist/src/update/blockers.d.ts +31 -0
- package/dist/src/update/blockers.js +78 -0
- package/dist/src/update/configured.d.ts +78 -0
- package/dist/src/update/configured.js +546 -0
- package/dist/src/update/root-request.d.ts +39 -0
- package/dist/src/update/root-request.js +117 -0
- package/dist/src/update/selective/plan.d.ts +45 -0
- package/dist/src/update/selective/plan.js +61 -0
- package/dist/src/update/selective/requirements.d.ts +17 -0
- package/dist/src/update/selective/requirements.js +14 -0
- package/dist/src/update/selective/selection.d.ts +63 -0
- package/dist/src/update/selective/selection.js +85 -0
- package/dist/src/update/selective/skills.d.ts +55 -0
- package/dist/src/update/selective/skills.js +596 -0
- package/dist/src/update/selective/subagents.d.ts +52 -0
- package/dist/src/update/selective/subagents.js +331 -0
- package/dist/src/update/selective/use-case.d.ts +121 -0
- package/dist/src/update/selective/use-case.js +28 -0
- package/dist/src/update/selective/vocabulary.d.ts +43 -0
- package/dist/src/update/selective/vocabulary.js +28 -0
- package/dist/src/update/selector.d.ts +72 -0
- package/dist/src/update/selector.js +120 -0
- package/dist/src/update/targeted-plan.d.ts +30 -0
- package/dist/src/update/targeted-plan.js +113 -0
- package/dist/src/update/update-extensions.d.ts +164 -0
- package/dist/src/update/update-extensions.js +579 -0
- package/dist/src/workspace-paths.d.ts +31 -0
- package/dist/src/workspace-paths.js +46 -0
- package/package.json +22 -12
- package/dist/src/configured-entry-resolution.d.ts +0 -111
- package/dist/src/configured-entry-resolution.js +0 -731
- package/dist/src/failure-adapter.d.ts +0 -35
- package/dist/src/failure-adapter.js +0 -23
- package/dist/src/hooks/manager.d.ts +0 -15
- package/dist/src/hooks/manager.js +0 -769
- package/dist/src/internal/fs-helpers.d.ts +0 -12
- package/dist/src/internal/fs-helpers.js +0 -13
- package/dist/src/internal/integrity.d.ts +0 -13
- package/dist/src/internal/integrity.js +0 -17
- package/dist/src/knowledge/manager.d.ts +0 -11
- package/dist/src/knowledge/manager.js +0 -711
- package/dist/src/mcps/manager.d.ts +0 -16
- package/dist/src/mcps/manager.js +0 -247
- package/dist/src/mcps/operations/artifact.d.ts +0 -24
- package/dist/src/mcps/operations/artifact.js +0 -74
- package/dist/src/mcps/operations/install.d.ts +0 -78
- package/dist/src/mcps/operations/install.js +0 -596
- package/dist/src/packs/dependency-resolution.d.ts +0 -65
- package/dist/src/packs/dependency-resolution.js +0 -414
- package/dist/src/packs/manager.d.ts +0 -16
- package/dist/src/packs/manager.js +0 -181
- package/dist/src/registry-materialization.d.ts +0 -52
- package/dist/src/registry-materialization.js +0 -72
- package/dist/src/release-age-posture.d.ts +0 -23
- package/dist/src/release-age-posture.js +0 -15
- package/dist/src/resolution-timeout.d.ts +0 -5
- package/dist/src/resolution-timeout.js +0 -11
- package/dist/src/rules/manager.d.ts +0 -20
- package/dist/src/rules/manager.js +0 -480
- package/dist/src/skills/manager.d.ts +0 -18
- package/dist/src/skills/manager.js +0 -346
- package/dist/src/skills/materialization.d.ts +0 -54
- package/dist/src/skills/materialization.js +0 -158
- package/dist/src/skills/operations/source-hash.d.ts +0 -2
- package/dist/src/skills/operations/source-hash.js +0 -6
- package/dist/src/subagents/manager.d.ts +0 -18
- package/dist/src/subagents/manager.js +0 -722
- package/dist/src/workflows/install-command/workflow.d.ts +0 -56
- package/dist/src/workflows/install-command/workflow.js +0 -46
- package/dist/src/workflows/uninstall-command/workflow.d.ts +0 -42
- 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
|