@agentxm/extension-lifecycle 0.28.4-bootstrap.0

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 (84) hide show
  1. package/LICENSE +110 -0
  2. package/dist/src/configured-entry-resolution.d.ts +110 -0
  3. package/dist/src/configured-entry-resolution.js +723 -0
  4. package/dist/src/errors.d.ts +39 -0
  5. package/dist/src/errors.js +45 -0
  6. package/dist/src/failure-adapter.d.ts +35 -0
  7. package/dist/src/failure-adapter.js +23 -0
  8. package/dist/src/hooks/configured-agent-outcomes-provider.d.ts +18 -0
  9. package/dist/src/hooks/configured-agent-outcomes-provider.js +39 -0
  10. package/dist/src/hooks/manager.d.ts +15 -0
  11. package/dist/src/hooks/manager.js +751 -0
  12. package/dist/src/index.d.ts +38 -0
  13. package/dist/src/index.js +41 -0
  14. package/dist/src/internal/fs-helpers.d.ts +12 -0
  15. package/dist/src/internal/fs-helpers.js +13 -0
  16. package/dist/src/internal/integrity.d.ts +13 -0
  17. package/dist/src/internal/integrity.js +17 -0
  18. package/dist/src/knowledge/manager.d.ts +11 -0
  19. package/dist/src/knowledge/manager.js +655 -0
  20. package/dist/src/live.d.ts +18 -0
  21. package/dist/src/live.js +18 -0
  22. package/dist/src/mcps/manager.d.ts +16 -0
  23. package/dist/src/mcps/manager.js +213 -0
  24. package/dist/src/mcps/operations/artifact.d.ts +24 -0
  25. package/dist/src/mcps/operations/artifact.js +74 -0
  26. package/dist/src/mcps/operations/disable.d.ts +18 -0
  27. package/dist/src/mcps/operations/disable.js +113 -0
  28. package/dist/src/mcps/operations/enable.d.ts +18 -0
  29. package/dist/src/mcps/operations/enable.js +218 -0
  30. package/dist/src/mcps/operations/install.d.ts +59 -0
  31. package/dist/src/mcps/operations/install.js +566 -0
  32. package/dist/src/mcps/operations/sync-outcome.d.ts +10 -0
  33. package/dist/src/mcps/operations/sync-outcome.js +27 -0
  34. package/dist/src/mcps/operations/uninstall.d.ts +41 -0
  35. package/dist/src/mcps/operations/uninstall.js +189 -0
  36. package/dist/src/packs/dependency-resolution.d.ts +65 -0
  37. package/dist/src/packs/dependency-resolution.js +414 -0
  38. package/dist/src/packs/expansion.d.ts +59 -0
  39. package/dist/src/packs/expansion.js +44 -0
  40. package/dist/src/packs/manager.d.ts +16 -0
  41. package/dist/src/packs/manager.js +181 -0
  42. package/dist/src/packs/operations/install.d.ts +67 -0
  43. package/dist/src/packs/operations/install.js +156 -0
  44. package/dist/src/packs/resolved-dependency.d.ts +29 -0
  45. package/dist/src/packs/resolved-dependency.js +28 -0
  46. package/dist/src/registry-materialization.d.ts +52 -0
  47. package/dist/src/registry-materialization.js +67 -0
  48. package/dist/src/resolution-progress.d.ts +18 -0
  49. package/dist/src/resolution-progress.js +11 -0
  50. package/dist/src/resolution-timeout.d.ts +5 -0
  51. package/dist/src/resolution-timeout.js +11 -0
  52. package/dist/src/rules/manager.d.ts +20 -0
  53. package/dist/src/rules/manager.js +475 -0
  54. package/dist/src/skills/manager.d.ts +18 -0
  55. package/dist/src/skills/manager.js +346 -0
  56. package/dist/src/skills/materialization.d.ts +54 -0
  57. package/dist/src/skills/materialization.js +158 -0
  58. package/dist/src/skills/operations/disable.d.ts +31 -0
  59. package/dist/src/skills/operations/disable.js +110 -0
  60. package/dist/src/skills/operations/enable.d.ts +31 -0
  61. package/dist/src/skills/operations/enable.js +113 -0
  62. package/dist/src/skills/operations/install-result.d.ts +33 -0
  63. package/dist/src/skills/operations/install-result.js +10 -0
  64. package/dist/src/skills/operations/install.d.ts +67 -0
  65. package/dist/src/skills/operations/install.js +590 -0
  66. package/dist/src/skills/operations/source-hash.d.ts +2 -0
  67. package/dist/src/skills/operations/source-hash.js +6 -0
  68. package/dist/src/skills/operations/uninstall.d.ts +40 -0
  69. package/dist/src/skills/operations/uninstall.js +157 -0
  70. package/dist/src/skills/utils.d.ts +11 -0
  71. package/dist/src/skills/utils.js +29 -0
  72. package/dist/src/subagents/manager.d.ts +18 -0
  73. package/dist/src/subagents/manager.js +677 -0
  74. package/dist/src/subagents/operations/artifact.d.ts +18 -0
  75. package/dist/src/subagents/operations/artifact.js +43 -0
  76. package/dist/src/subagents/operations/disable.d.ts +32 -0
  77. package/dist/src/subagents/operations/disable.js +116 -0
  78. package/dist/src/subagents/operations/enable.d.ts +30 -0
  79. package/dist/src/subagents/operations/enable.js +160 -0
  80. package/dist/src/workflows/install-command/workflow.d.ts +57 -0
  81. package/dist/src/workflows/install-command/workflow.js +41 -0
  82. package/dist/src/workflows/uninstall-command/workflow.d.ts +42 -0
  83. package/dist/src/workflows/uninstall-command/workflow.js +29 -0
  84. package/package.json +62 -0
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Uninstall MCP server executor — orchestrates per-server removal pipeline.
3
+ *
4
+ * Pipeline: resolve desired/observed state -> remove canonical source -> clear
5
+ * settings and accepted resolution.
6
+ * Simpler than skills — no agent symlinks.
7
+ *
8
+ * @experimental This API is unstable and may change without notice.
9
+ */
10
+ import * as FileSystem from "effect/FileSystem";
11
+ import * as Path from "effect/Path";
12
+ import * as Array from "effect/Array";
13
+ import * as Effect from "effect/Effect";
14
+ import * as Option from "effect/Option";
15
+ import { CodingAgentRepository } from "@agentxm/extension-workspace";
16
+ import { appendWarningsToMessage } from "@agentxm/workspace-operations";
17
+ import { WorkspaceMutations } from "@agentxm/workspace-state";
18
+ import { removeIfExists } from "@agentxm/workspace-state";
19
+ import { acceptedCanonicalObservation, acceptedLockedCanonicalPath, removableAcceptedCanonicalPath, } from "@agentxm/workspace-state";
20
+ import { agentConfigTarget, mcpServerArtifact, mcpSettingsTarget } from "./artifact.js";
21
+ import { LifecycleFailureAdapter, withAdaptedStepFailures } from "../../failure-adapter.js";
22
+ import { ExtensionLifecycleFailed } from "../../errors.js";
23
+ const REQUIRED_AGENT_IDS = new Set([
24
+ "claude-code",
25
+ "opencode",
26
+ "github-copilot-cli",
27
+ "cursor",
28
+ "gemini-cli",
29
+ "codex",
30
+ ]);
31
+ const formatAgentSyncWarning = (serverName, outcomes) => {
32
+ const warningMessage = outcomes
33
+ .map(({ agentId, outcome }) => outcome._tag === "success"
34
+ ? `${agentId}:success`
35
+ : outcome._tag === "fallback"
36
+ ? `${agentId}:fallback(${outcome.fallbackFrom}):${outcome.reason}`
37
+ : `${agentId}:${outcome.reason}`)
38
+ .join(", ");
39
+ return `MCP agent sync warnings for ${serverName}: ${warningMessage}`;
40
+ };
41
+ const summarizeAgentSync = (outcomes, warnings) => {
42
+ const degraded = outcomes.some(({ outcome }) => outcome._tag === "failed" || outcome._tag === "fallback");
43
+ const details = outcomes.map(({ agentId, outcome }) => outcome._tag === "success"
44
+ ? `${agentId}:success`
45
+ : outcome._tag === "fallback"
46
+ ? `${agentId}:fallback:${outcome.fallbackFrom}`
47
+ : `${agentId}:${outcome._tag}`);
48
+ return {
49
+ status: degraded ? "degraded" : "green",
50
+ details,
51
+ warnings,
52
+ agentIds: outcomes.map(({ agentId }) => agentId),
53
+ };
54
+ };
55
+ const syncConfiguredAgentsOnUninstall = (args) => Effect.gen(function* () {
56
+ const agentRepo = yield* CodingAgentRepository;
57
+ const warnings = [];
58
+ const unknownConfiguredAgentIds = yield* agentRepo.getUnknownConfiguredAgentIds();
59
+ if (args.strict && unknownConfiguredAgentIds.length > 0) {
60
+ const message = `Unknown configured agents in strict mode: ${unknownConfiguredAgentIds.join(", ")}`;
61
+ return yield* new ExtensionLifecycleFailed({
62
+ category: "not_found",
63
+ detail: message,
64
+ });
65
+ }
66
+ if (unknownConfiguredAgentIds.length > 0) {
67
+ warnings.push(`Skipping unknown configured agents: ${unknownConfiguredAgentIds.join(", ")}`);
68
+ }
69
+ const configuredAgents = yield* agentRepo.getConfiguredAgents();
70
+ const outcomes = yield* Effect.forEach(configuredAgents, (agent) => agent
71
+ .removeMcpServer({
72
+ workspaceRoot: args.wsBaseDir,
73
+ scope: args.scope,
74
+ serverName: args.serverName,
75
+ })
76
+ .pipe(Effect.map((outcome) => ({ agentId: agent.id, outcome }))), { concurrency: "unbounded" });
77
+ const misconfigured = Array.filter(outcomes, ({ outcome }) => outcome._tag === "misconfigured");
78
+ if (misconfigured.length > 0) {
79
+ return yield* new ExtensionLifecycleFailed({
80
+ category: "internal",
81
+ detail: `MCP server ${args.serverName} could not be removed from configured agents`,
82
+ });
83
+ }
84
+ const failed = Array.filter(outcomes, ({ outcome }) => outcome._tag === "failed");
85
+ if (args.strict && failed.length > 0) {
86
+ return yield* new ExtensionLifecycleFailed({
87
+ category: "internal",
88
+ detail: `MCP server ${args.serverName} removal sync failed in strict mode`,
89
+ });
90
+ }
91
+ const strictDisabledFailures = Array.filter(outcomes, ({ agentId, outcome }) => (outcome._tag === "disabled" ||
92
+ (outcome._tag === "fallback" && outcome.fallbackFrom === "disabled")) &&
93
+ args.strict &&
94
+ REQUIRED_AGENT_IDS.has(agentId));
95
+ if (strictDisabledFailures.length > 0) {
96
+ return yield* new ExtensionLifecycleFailed({
97
+ category: "internal",
98
+ detail: `MCP server ${args.serverName} removal sync disabled for required configured agents`,
99
+ });
100
+ }
101
+ const warningOutcomes = Array.filter(outcomes, ({ outcome }) => outcome._tag === "unsupported" ||
102
+ outcome._tag === "disabled" ||
103
+ outcome._tag === "needs-input" ||
104
+ outcome._tag === "failed" ||
105
+ outcome._tag === "fallback");
106
+ if (warningOutcomes.length > 0) {
107
+ warnings.push(formatAgentSyncWarning(args.serverName, warningOutcomes));
108
+ }
109
+ return summarizeAgentSync(outcomes, warnings);
110
+ });
111
+ // -----------------------------------------------------------------------------
112
+ // Public API
113
+ // -----------------------------------------------------------------------------
114
+ /**
115
+ * Uninstall-mcp-server operation handler.
116
+ *
117
+ * 1. Resolve configured and observed state
118
+ * 2. Remove canonical directory from disk (if exists)
119
+ * 3. Remove settings and accepted resolution
120
+ */
121
+ export const uninstallMcpServer = (op) => Effect.gen(function* () {
122
+ const adapter = yield* LifecycleFailureAdapter;
123
+ const fs = yield* FileSystem.FileSystem;
124
+ const ws = yield* WorkspaceMutations;
125
+ const strictAgentSync = Option.getOrElse(op.args.strictAgentSync ?? Option.none(), () => false);
126
+ const desired = yield* ws.getDesiredStateGraph();
127
+ if (!desired.complete) {
128
+ return yield* new ExtensionLifecycleFailed({
129
+ category: "conflict",
130
+ detail: "Cannot uninstall the MCP server while the desired extension graph is incomplete.",
131
+ recover: "Repair or reinstall the configured packs, then retry.",
132
+ });
133
+ }
134
+ const desiredNode = desired.nodes.find((node) => node.type === "mcp-server" && node.name === op.args.serverName);
135
+ const acceptedCanonical = yield* acceptedCanonicalObservation({
136
+ workspace: ws,
137
+ type: "mcp-server",
138
+ name: op.args.serverName,
139
+ });
140
+ const lockedCanonical = yield* acceptedLockedCanonicalPath({
141
+ workspace: ws,
142
+ type: "mcp-server",
143
+ name: op.args.serverName,
144
+ });
145
+ const removableCanonical = Option.orElse(removableAcceptedCanonicalPath(acceptedCanonical), () => lockedCanonical);
146
+ const installedOnDisk = yield* Option.match(removableCanonical, {
147
+ onNone: () => Effect.succeed(false),
148
+ onSome: (canonicalPath) => fs.exists(canonicalPath).pipe(Effect.catch(() => Effect.succeed(false))),
149
+ });
150
+ if (desiredNode === undefined && !installedOnDisk) {
151
+ return { result: "success", message: "not installed" };
152
+ }
153
+ if (desiredNode?.origins.some((origin) => origin.type === "pack") === true) {
154
+ yield* ws.removeMcpServerSettings(op.args.serverName);
155
+ return {
156
+ result: "success",
157
+ message: "Kept on disk because dependency is still required by an installed pack",
158
+ };
159
+ }
160
+ if (Option.isSome(removableCanonical))
161
+ yield* removeIfExists(fs, removableCanonical.value);
162
+ // Remove from settings + lockfile (best-effort; preserve warning in result).
163
+ const removeWarning = yield* ws.removeMcpServer(op.args.serverName).pipe(Effect.as(Option.none()), Effect.catch((e) => Effect.succeed(Option.some(`MCP server removal from settings failed: ${adapter.describeFailure(e)}`))));
164
+ const agentSync = yield* syncConfiguredAgentsOnUninstall({
165
+ wsBaseDir: ws.baseDir,
166
+ scope: ws.scope,
167
+ strict: strictAgentSync,
168
+ serverName: op.args.serverName,
169
+ });
170
+ const warnings = Option.match(removeWarning, {
171
+ onNone: () => agentSync.warnings,
172
+ onSome: (warning) => [warning, ...agentSync.warnings],
173
+ });
174
+ const agentTarget = agentConfigTarget("removed", agentSync.agentIds);
175
+ return {
176
+ result: "success",
177
+ message: appendWarningsToMessage(`Uninstalled ${op.args.serverName} (canonical=success, agent-sync=${agentSync.status})`, warnings),
178
+ artifact: mcpServerArtifact({
179
+ lockEntry: undefined,
180
+ scope: ws.scope,
181
+ change: "removed",
182
+ targets: [
183
+ mcpSettingsTarget(ws.scope, "removed"),
184
+ ...(agentTarget === undefined ? [] : [agentTarget]),
185
+ ],
186
+ }),
187
+ };
188
+ }).pipe(withAdaptedStepFailures);
189
+ //# sourceMappingURL=uninstall.js.map
@@ -0,0 +1,65 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import type { SourceAuthorityBlockedFact } from "@agentxm/extension-workspace";
4
+ /** Failures pack dependency resolution can surface. */
5
+ type PackDependencyResolutionError = SourceResolutionFailure | PackManagerError | SourceAuthorityBlocked;
6
+ import type { ExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/extension-ref";
7
+ import type { Handle, ExtensionName, ExtensionType } from "@agentxm/extension-model/unstable/extensions";
8
+ import type { ResolvedPackDependencyMap } from "./resolved-dependency.js";
9
+ import type { SourceHostProvidersService, SourceResolutionFailure } from "@agentxm/extension-sources";
10
+ import type { RegistrySource } from "@agentxm/extension-model/unstable/sources/types";
11
+ import { SourceAuthorityBlocked, type PackManagerError } from "@agentxm/extension-workspace";
12
+ import type { PackRef } from "@agentxm/extension-model/unstable/extensions/refs/pack";
13
+ import type * as Duration from "effect/Duration";
14
+ import type { VersionRange } from "@agentxm/extension-model/unstable/version-constraints";
15
+ import type { ReleaseAgeBypassRecord, ReleaseAgeHoldbackRecord } from "@agentxm/registry-protocol/unstable/registry/release-age-policy";
16
+ import type { ReleaseAgeEvaluation } from "@agentxm/extension-model/unstable/extensions/release-age";
17
+ /** Every extension type a pack can depend on — packs cannot nest. */
18
+ type SupportedPackDependencyType = Exclude<ExtensionType, "pack">;
19
+ export type WorkspacePackDependencyResolution = {
20
+ readonly kind: "absent";
21
+ } | {
22
+ readonly kind: "selected";
23
+ readonly ref: ExtensionRef;
24
+ } | {
25
+ readonly kind: "blocked";
26
+ readonly fact: SourceAuthorityBlockedFact;
27
+ };
28
+ export type WorkspacePackDependencyResolver<E = never> = (args: {
29
+ readonly owner: Handle;
30
+ readonly type: SupportedPackDependencyType;
31
+ readonly name: ExtensionName;
32
+ readonly constraint: VersionRange;
33
+ readonly root: string;
34
+ }) => Effect.Effect<WorkspacePackDependencyResolution, E>;
35
+ /** Resolve a Pack member from an already-authorized immutable candidate. */
36
+ export type PackDependencyRefResolver<E = never> = (args: {
37
+ readonly owner: Handle;
38
+ readonly type: SupportedPackDependencyType;
39
+ readonly name: ExtensionName;
40
+ readonly constraint: VersionRange;
41
+ readonly root: string;
42
+ }) => Effect.Effect<ExtensionRef, E>;
43
+ export interface ResolvedPackDependencies {
44
+ readonly resolvedSkills: ResolvedPackDependencyMap;
45
+ readonly resolvedMcpServers: ResolvedPackDependencyMap;
46
+ readonly resolvedSubagents: ResolvedPackDependencyMap;
47
+ readonly resolvedRules: ResolvedPackDependencyMap;
48
+ readonly resolvedHooks: ResolvedPackDependencyMap;
49
+ readonly resolvedKnowledge: ResolvedPackDependencyMap;
50
+ readonly dependencyRefs: ReadonlyArray<ExtensionRef>;
51
+ }
52
+ export type ReleaseAgeAwarePackDependencyResolution = {
53
+ readonly kind: "selected";
54
+ readonly dependencies: ResolvedPackDependencies;
55
+ readonly holdbacks: ReadonlyArray<ReleaseAgeHoldbackRecord>;
56
+ readonly bypasses: ReadonlyArray<ReleaseAgeBypassRecord>;
57
+ } | {
58
+ readonly kind: "policy_held";
59
+ readonly holdbacks: ReadonlyArray<ReleaseAgeHoldbackRecord>;
60
+ readonly bypasses: ReadonlyArray<ReleaseAgeBypassRecord>;
61
+ };
62
+ export declare const resolvePackDependencies: <E = never>(pack: PackRef, sources: SourceHostProvidersService, minimumReleaseAge?: Option.Option<Duration.Duration>, sourceOverride?: RegistrySource, workspaceResolver?: WorkspacePackDependencyResolver<E>, dependencyResolver?: PackDependencyRefResolver<E>) => Effect.Effect<ResolvedPackDependencies, PackDependencyResolutionError | E>;
63
+ export declare const resolvePackDependenciesWithReleaseAge: <E = never>(pack: PackRef, sources: SourceHostProvidersService, evaluation: ReleaseAgeEvaluation, sourceOverride?: RegistrySource, workspaceResolver?: WorkspacePackDependencyResolver<E>, dependencyResolver?: PackDependencyRefResolver<E>) => Effect.Effect<ReleaseAgeAwarePackDependencyResolution, PackDependencyResolutionError | E>;
64
+ export {};
65
+ //# sourceMappingURL=dependency-resolution.d.ts.map
@@ -0,0 +1,414 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import { formatFqn, parseFqnOrThrow, toExtensionTypePlural, } from "@agentxm/extension-model/unstable/extensions";
4
+ import { SourceAuthorityBlocked, PackConstraintShadowed, PackDependencyConflict, PackDependencyInvalid, PackDependencyMissing, PackDependencyUnsatisfied, } from "@agentxm/extension-workspace";
5
+ import * as semver from "semver";
6
+ import { releaseAgeExemptionForIdentity } from "@agentxm/registry-protocol/unstable/registry/release-age-policy";
7
+ const validateSelectedDependency = (candidate, expectedType, parsed, fqn, constraint) => {
8
+ if (candidate.type !== expectedType ||
9
+ (candidate.refType !== "registry" && candidate.refType !== "workspace") ||
10
+ candidate.owner !== parsed.owner ||
11
+ candidate.name !== parsed.name) {
12
+ return new PackDependencyConflict({
13
+ detail: `Authorized dependency resolution does not match pack dependency ${fqn}`,
14
+ });
15
+ }
16
+ if (!semver.satisfies(candidate.version, constraint)) {
17
+ return new PackDependencyConflict({
18
+ detail: `Authorized dependency ${fqn}@${candidate.version} does not satisfy ${constraint}`,
19
+ });
20
+ }
21
+ return Effect.succeed({
22
+ owner: parsed.owner,
23
+ type: expectedType,
24
+ name: parsed.name,
25
+ ref: candidate,
26
+ });
27
+ };
28
+ const registrySourceForDependency = (pack, owner, sourceOverride) => {
29
+ const source = sourceOverride ?? (pack.source.type === "registry" ? pack.source : undefined);
30
+ if (source === undefined) {
31
+ return Effect.fail(new PackDependencyInvalid({
32
+ detail: `Cannot resolve pack dependencies from non-registry source`,
33
+ }));
34
+ }
35
+ return Effect.succeed({
36
+ ...source,
37
+ owner: Option.some(owner),
38
+ });
39
+ };
40
+ const workspaceConstraintConflict = (pack, memberFqn, workspaceVersion, constraint) => new PackConstraintShadowed({
41
+ packSource: pack.source.type === "workspace" ? "workspace" : "registry",
42
+ packFqn: formatFqn({ owner: pack.owner, type: "pack", name: pack.pack.name }),
43
+ memberFqn,
44
+ constraint,
45
+ workspaceVersion,
46
+ });
47
+ const resolveDependencyRef = (pack, expectedType, fqn, constraint, sources, minimumReleaseAge, sourceOverride, workspaceResolver, dependencyResolver) => Effect.gen(function* () {
48
+ const parsed = parseFqnOrThrow(fqn);
49
+ if (parsed.type !== expectedType) {
50
+ return yield* new PackDependencyInvalid({
51
+ detail: `Pack dependency type mismatch for expected ${expectedType}`,
52
+ });
53
+ }
54
+ if (workspaceResolver !== undefined) {
55
+ const workspace = yield* workspaceResolver({
56
+ owner: parsed.owner,
57
+ type: expectedType,
58
+ name: parsed.name,
59
+ constraint,
60
+ root: formatFqn({ owner: pack.owner, type: "pack", name: pack.pack.name }),
61
+ });
62
+ if (workspace.kind === "blocked") {
63
+ return yield* new SourceAuthorityBlocked({
64
+ detail: workspace.fact.detail,
65
+ recovery: workspace.fact.recovery,
66
+ });
67
+ }
68
+ if (workspace.kind === "selected") {
69
+ const candidate = workspace.ref;
70
+ if (candidate.type !== expectedType ||
71
+ candidate.refType !== "workspace" ||
72
+ candidate.owner !== parsed.owner ||
73
+ candidate.name !== parsed.name) {
74
+ return yield* new PackDependencyConflict({
75
+ detail: `Configured workspace authority does not match pack dependency ${fqn}`,
76
+ });
77
+ }
78
+ if (!semver.satisfies(candidate.version, constraint)) {
79
+ return yield* workspaceConstraintConflict(pack, fqn, candidate.version, constraint);
80
+ }
81
+ return {
82
+ owner: parsed.owner,
83
+ type: expectedType,
84
+ name: parsed.name,
85
+ ref: candidate,
86
+ };
87
+ }
88
+ }
89
+ if (dependencyResolver !== undefined) {
90
+ const candidate = yield* dependencyResolver({
91
+ owner: parsed.owner,
92
+ type: expectedType,
93
+ name: parsed.name,
94
+ constraint,
95
+ root: formatFqn({ owner: pack.owner, type: "pack", name: pack.pack.name }),
96
+ });
97
+ return yield* validateSelectedDependency(candidate, expectedType, parsed, fqn, constraint);
98
+ }
99
+ const source = yield* registrySourceForDependency(pack, parsed.owner, sourceOverride);
100
+ const matches = yield* Effect.scoped(sources.find(source, {
101
+ names: [parsed.name],
102
+ type: expectedType,
103
+ owner: Option.some(parsed.owner),
104
+ versionRange: Option.some(constraint),
105
+ ...(minimumReleaseAge === undefined ? {} : { minimumReleaseAge }),
106
+ }));
107
+ const matchingRef = matches.find((candidate) => candidate.type === expectedType &&
108
+ candidate.refType === "registry" &&
109
+ candidate.owner === parsed.owner &&
110
+ candidate.name === parsed.name);
111
+ if (matchingRef === undefined) {
112
+ return yield* new PackDependencyInvalid({
113
+ detail: `Unable to resolve pack dependency ${fqn}@${constraint}`,
114
+ });
115
+ }
116
+ return {
117
+ owner: parsed.owner,
118
+ type: expectedType,
119
+ name: parsed.name,
120
+ ref: matchingRef,
121
+ };
122
+ });
123
+ const dependencyReleaseAgeRecord = (args) => ({
124
+ reason: "minimum-release-age",
125
+ target: args.dependencyTarget,
126
+ dependencyPath: [args.packTarget, args.dependencyTarget],
127
+ requestedRange: args.constraint,
128
+ ...(args.selectedVersion === undefined ? {} : { selectedVersion: args.selectedVersion }),
129
+ candidateVersion: args.evidence.version,
130
+ publishedAt: args.evidence.publishedAt,
131
+ eligibleAt: args.evidence.eligibleAt,
132
+ minimumReleaseAgeSeconds: args.evidence.minimumReleaseAgeSeconds,
133
+ });
134
+ const resolveDependencyRefWithReleaseAge = (pack, expectedType, fqn, constraint, sources, evaluation, sourceOverride, workspaceResolver, dependencyResolver) => Effect.gen(function* () {
135
+ const parsed = parseFqnOrThrow(fqn);
136
+ if (parsed.type !== expectedType) {
137
+ return yield* new PackDependencyInvalid({
138
+ detail: `Pack dependency type mismatch for expected ${expectedType}`,
139
+ });
140
+ }
141
+ if (workspaceResolver !== undefined) {
142
+ const workspace = yield* workspaceResolver({
143
+ owner: parsed.owner,
144
+ type: expectedType,
145
+ name: parsed.name,
146
+ constraint,
147
+ root: formatFqn({ owner: pack.owner, type: "pack", name: pack.pack.name }),
148
+ });
149
+ if (workspace.kind === "blocked") {
150
+ return yield* new SourceAuthorityBlocked({
151
+ detail: workspace.fact.detail,
152
+ recovery: workspace.fact.recovery,
153
+ });
154
+ }
155
+ if (workspace.kind === "selected") {
156
+ const candidate = workspace.ref;
157
+ if (candidate.type !== expectedType ||
158
+ candidate.refType !== "workspace" ||
159
+ candidate.owner !== parsed.owner ||
160
+ candidate.name !== parsed.name) {
161
+ return yield* new PackDependencyConflict({
162
+ detail: `Configured workspace authority does not match pack dependency ${fqn}`,
163
+ });
164
+ }
165
+ if (!semver.satisfies(candidate.version, constraint)) {
166
+ return yield* workspaceConstraintConflict(pack, fqn, candidate.version, constraint);
167
+ }
168
+ return {
169
+ kind: "selected",
170
+ dependency: {
171
+ owner: parsed.owner,
172
+ type: expectedType,
173
+ name: parsed.name,
174
+ ref: candidate,
175
+ },
176
+ holdbacks: [],
177
+ bypasses: [],
178
+ };
179
+ }
180
+ }
181
+ if (dependencyResolver !== undefined) {
182
+ const candidate = yield* dependencyResolver({
183
+ owner: parsed.owner,
184
+ type: expectedType,
185
+ name: parsed.name,
186
+ constraint,
187
+ root: formatFqn({ owner: pack.owner, type: "pack", name: pack.pack.name }),
188
+ });
189
+ return {
190
+ kind: "selected",
191
+ dependency: yield* validateSelectedDependency(candidate, expectedType, parsed, fqn, constraint),
192
+ holdbacks: [],
193
+ bypasses: [],
194
+ };
195
+ }
196
+ const source = yield* registrySourceForDependency(pack, parsed.owner, sourceOverride);
197
+ const resolution = yield* Effect.scoped(sources.resolveNamedRegistry(source, {
198
+ name: parsed.name,
199
+ type: expectedType,
200
+ owner: parsed.owner,
201
+ versionRange: Option.some(constraint),
202
+ releaseAgeEvaluation: evaluation,
203
+ }));
204
+ const packTarget = formatFqn({ owner: pack.owner, type: "pack", name: pack.pack.name });
205
+ const dependencyTarget = formatFqn(parsed);
206
+ if (resolution.kind === "not_found") {
207
+ return yield* new PackDependencyMissing({ dependencyTarget });
208
+ }
209
+ if (resolution.kind === "version_unsatisfied") {
210
+ return yield* new PackDependencyUnsatisfied({ dependencyTarget, constraint });
211
+ }
212
+ if (resolution.kind === "policy_held") {
213
+ return {
214
+ kind: "policy_held",
215
+ holdback: dependencyReleaseAgeRecord({
216
+ packTarget,
217
+ dependencyTarget,
218
+ constraint,
219
+ evidence: resolution.candidate,
220
+ }),
221
+ };
222
+ }
223
+ const ref = resolution.ref;
224
+ return {
225
+ kind: "selected",
226
+ dependency: {
227
+ owner: parsed.owner,
228
+ type: expectedType,
229
+ name: parsed.name,
230
+ ref,
231
+ },
232
+ holdbacks: resolution.kind === "exempted" || resolution.newerHeld === undefined
233
+ ? []
234
+ : [
235
+ dependencyReleaseAgeRecord({
236
+ packTarget,
237
+ dependencyTarget,
238
+ constraint,
239
+ evidence: resolution.newerHeld,
240
+ selectedVersion: ref.version,
241
+ }),
242
+ ],
243
+ bypasses: resolution.kind === "selected"
244
+ ? []
245
+ : [
246
+ {
247
+ ...dependencyReleaseAgeRecord({
248
+ packTarget,
249
+ dependencyTarget,
250
+ constraint,
251
+ evidence: resolution.bypassed,
252
+ selectedVersion: ref.version,
253
+ }),
254
+ ...resolution.exemption,
255
+ },
256
+ ],
257
+ };
258
+ });
259
+ const toResolvedMap = (dependencies) => Object.fromEntries(dependencies.map((dependency) => {
260
+ const ref = dependency.ref;
261
+ return [
262
+ formatFqn(dependency),
263
+ ref.refType === "registry"
264
+ ? {
265
+ source: "registry",
266
+ version: ref.version,
267
+ publisherBindingId: ref.publisherBindingId,
268
+ integrity: Option.getOrElse(ref.integrity, () => ""),
269
+ }
270
+ : {
271
+ source: "workspace",
272
+ version: ref.version,
273
+ sourceIdentity: `workspace:${ref.owner}/${toExtensionTypePlural(ref.type)}/${ref.name}`,
274
+ contentIdentity: ref.sourceHash,
275
+ },
276
+ ];
277
+ }));
278
+ const resolveDependencyGroup = (pack, dependencies, expectedType, sources, minimumReleaseAge, sourceOverride, workspaceResolver, dependencyResolver) => Effect.forEach(dependencies, ([fqn, constraint]) => resolveDependencyRef(pack, expectedType, fqn, constraint, sources, minimumReleaseAge, sourceOverride, workspaceResolver, dependencyResolver), { concurrency: "unbounded" });
279
+ /**
280
+ * Group dependency FQNs by extension type.
281
+ *
282
+ * The `groups` record is keyed by every non-pack extension type, so a new type
283
+ * fails compile here rather than being silently dropped from pack membership.
284
+ */
285
+ const partitionDependencies = (dependencies) => {
286
+ const groups = {
287
+ skill: [],
288
+ "mcp-server": [],
289
+ subagent: [],
290
+ rule: [],
291
+ hook: [],
292
+ knowledge: [],
293
+ };
294
+ const unsupported = [];
295
+ for (const [fqn, constraint] of Object.entries(dependencies)) {
296
+ const parsed = parseFqnOrThrow(fqn);
297
+ if (parsed.type === "pack") {
298
+ unsupported.push(fqn);
299
+ continue;
300
+ }
301
+ groups[parsed.type].push([fqn, constraint]);
302
+ }
303
+ return { groups, unsupported };
304
+ };
305
+ export const resolvePackDependencies = (pack, sources, minimumReleaseAge, sourceOverride, workspaceResolver, dependencyResolver) => Effect.gen(function* () {
306
+ const dependencies = partitionDependencies(pack.pack.dependencies);
307
+ if (dependencies.unsupported.length > 0) {
308
+ return yield* new PackDependencyInvalid({
309
+ detail: `Pack declares ${dependencies.unsupported.length} unsupported dependency type${dependencies.unsupported.length === 1 ? "" : "s"}: ${dependencies.unsupported.join(", ")}`,
310
+ });
311
+ }
312
+ const resolveGroup = (type) => resolveDependencyGroup(pack, dependencies.groups[type], type, sources, minimumReleaseAge, sourceOverride, workspaceResolver, dependencyResolver);
313
+ const resolvedSkills = yield* resolveGroup("skill");
314
+ const resolvedMcpServers = yield* resolveGroup("mcp-server");
315
+ const resolvedSubagents = yield* resolveGroup("subagent");
316
+ const resolvedRules = yield* resolveGroup("rule");
317
+ const resolvedHooks = yield* resolveGroup("hook");
318
+ const resolvedKnowledge = yield* resolveGroup("knowledge");
319
+ return {
320
+ resolvedSkills: toResolvedMap(resolvedSkills),
321
+ resolvedMcpServers: toResolvedMap(resolvedMcpServers),
322
+ resolvedSubagents: toResolvedMap(resolvedSubagents),
323
+ resolvedRules: toResolvedMap(resolvedRules),
324
+ resolvedHooks: toResolvedMap(resolvedHooks),
325
+ resolvedKnowledge: toResolvedMap(resolvedKnowledge),
326
+ dependencyRefs: [
327
+ ...resolvedSkills,
328
+ ...resolvedMcpServers,
329
+ ...resolvedSubagents,
330
+ ...resolvedRules,
331
+ ...resolvedHooks,
332
+ ...resolvedKnowledge,
333
+ ].map((dependency) => dependency.ref),
334
+ };
335
+ });
336
+ export const resolvePackDependenciesWithReleaseAge = (pack, sources, evaluation, sourceOverride, workspaceResolver, dependencyResolver) => Effect.gen(function* () {
337
+ const packExemption = pack.refType === "registry"
338
+ ? releaseAgeExemptionForIdentity(evaluation, {
339
+ owner: pack.owner,
340
+ type: "pack",
341
+ name: pack.pack.name,
342
+ })
343
+ : undefined;
344
+ const dependencyEvaluation = packExemption?.bypassCause === "exclude"
345
+ ? { ...evaluation, grantedExemption: packExemption }
346
+ : evaluation;
347
+ const dependencies = partitionDependencies(pack.pack.dependencies);
348
+ if (dependencies.unsupported.length > 0) {
349
+ return yield* new PackDependencyInvalid({
350
+ detail: `Pack declares ${dependencies.unsupported.length} unsupported dependency type${dependencies.unsupported.length === 1 ? "" : "s"}: ${dependencies.unsupported.join(", ")}`,
351
+ });
352
+ }
353
+ const entries = [
354
+ ...dependencies.groups.skill.map(([fqn, constraint]) => ({
355
+ type: "skill",
356
+ fqn,
357
+ constraint,
358
+ })),
359
+ ...dependencies.groups["mcp-server"].map(([fqn, constraint]) => ({
360
+ type: "mcp-server",
361
+ fqn,
362
+ constraint,
363
+ })),
364
+ ...dependencies.groups.subagent.map(([fqn, constraint]) => ({
365
+ type: "subagent",
366
+ fqn,
367
+ constraint,
368
+ })),
369
+ ...dependencies.groups.rule.map(([fqn, constraint]) => ({
370
+ type: "rule",
371
+ fqn,
372
+ constraint,
373
+ })),
374
+ ...dependencies.groups.hook.map(([fqn, constraint]) => ({
375
+ type: "hook",
376
+ fqn,
377
+ constraint,
378
+ })),
379
+ ...dependencies.groups.knowledge.map(([fqn, constraint]) => ({
380
+ type: "knowledge",
381
+ fqn,
382
+ constraint,
383
+ })),
384
+ ];
385
+ const resolutions = yield* Effect.forEach(entries, ({ type, fqn, constraint }) => resolveDependencyRefWithReleaseAge(pack, type, fqn, constraint, sources, dependencyEvaluation, sourceOverride, workspaceResolver, dependencyResolver), { concurrency: "unbounded" });
386
+ const holdbacks = resolutions.flatMap((resolution) => resolution.kind === "policy_held" ? [resolution.holdback] : resolution.holdbacks);
387
+ const bypasses = resolutions.flatMap((resolution) => resolution.kind === "selected" ? resolution.bypasses : []);
388
+ if (resolutions.some((resolution) => resolution.kind === "policy_held")) {
389
+ return { kind: "policy_held", holdbacks, bypasses };
390
+ }
391
+ const selected = resolutions.flatMap((resolution) => resolution.kind === "selected" ? [resolution.dependency] : []);
392
+ const byType = (type) => selected.filter((dependency) => dependency.type === type);
393
+ const resolvedSkills = byType("skill");
394
+ const resolvedMcpServers = byType("mcp-server");
395
+ const resolvedSubagents = byType("subagent");
396
+ const resolvedRules = byType("rule");
397
+ const resolvedHooks = byType("hook");
398
+ const resolvedKnowledge = byType("knowledge");
399
+ return {
400
+ kind: "selected",
401
+ holdbacks,
402
+ bypasses,
403
+ dependencies: {
404
+ resolvedSkills: toResolvedMap(resolvedSkills),
405
+ resolvedMcpServers: toResolvedMap(resolvedMcpServers),
406
+ resolvedSubagents: toResolvedMap(resolvedSubagents),
407
+ resolvedRules: toResolvedMap(resolvedRules),
408
+ resolvedHooks: toResolvedMap(resolvedHooks),
409
+ resolvedKnowledge: toResolvedMap(resolvedKnowledge),
410
+ dependencyRefs: selected.map((dependency) => dependency.ref),
411
+ },
412
+ };
413
+ });
414
+ //# sourceMappingURL=dependency-resolution.js.map