@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,67 @@
1
+ /**
2
+ * Registry-backed canonical package materialization.
3
+ *
4
+ * The canonical-directory staging/swap machinery lives in
5
+ * `@agentxm/extension-workspace`; this module owns only the registry
6
+ * fetch/verify/extract path.
7
+ *
8
+ * @experimental This API is unstable and may change without notice.
9
+ */
10
+ import * as Effect from "effect/Effect";
11
+ import * as FileSystem from "effect/FileSystem";
12
+ import * as Option from "effect/Option";
13
+ import * as Path from "effect/Path";
14
+ import * as HttpClient from "effect/unstable/http/HttpClient";
15
+ import { createRegistryClient, extractZip } from "@agentxm/registry-client";
16
+ import { computeIntegrity } from "./internal/integrity.js";
17
+ import { ArchiveIntegrityMismatch, recoverCanonicalDirectory, replaceCanonicalDirectoryWithInspection, } from "@agentxm/extension-workspace";
18
+ import { CoupledDependencyFailure } from "@agentxm/extension-workspace";
19
+ import { coupleLifecycleDependencyFailure } from "./errors.js";
20
+ import { computeMaterializedTreeIntegrity, } from "@agentxm/workspace-state";
21
+ const registryLocationForClient = (location) => location.protocol === "file:" ? location.pathname : location.href;
22
+ /**
23
+ * Fetch, verify, and extract a registry package into `destinationPath`.
24
+ *
25
+ * Always writes. Whether new bytes are needed at all is
26
+ * `canReuseInstalledPackage`'s decision, which the caller makes against the
27
+ * canonical installed path before choosing a destination.
28
+ *
29
+ * Registry client and archive-extraction failures travel opaquely as coupled
30
+ * dependency failures; the application boundary restores and converts them.
31
+ */
32
+ export const materializeRegistryPackageWithTreeIntegrity = (args) => Effect.gen(function* () {
33
+ yield* recoverCanonicalDirectory({
34
+ baseDir: args.baseDir,
35
+ canonicalPath: args.destinationPath,
36
+ });
37
+ const client = yield* createRegistryClient(registryLocationForClient(args.sourceLocation));
38
+ const { archive } = yield* client
39
+ .getExtensionPackage({
40
+ owner: args.owner,
41
+ type: args.type,
42
+ name: args.name,
43
+ version: Option.some(args.version),
44
+ })
45
+ .pipe(Effect.mapError(coupleLifecycleDependencyFailure));
46
+ if (Option.isSome(args.integrity)) {
47
+ const actualIntegrity = yield* computeIntegrity(archive);
48
+ if (actualIntegrity !== args.integrity.value) {
49
+ return yield* new ArchiveIntegrityMismatch({
50
+ subject: args.messages.integrityMismatchDetail,
51
+ });
52
+ }
53
+ }
54
+ const result = yield* replaceCanonicalDirectoryWithInspection({
55
+ baseDir: args.baseDir,
56
+ canonicalPath: args.destinationPath,
57
+ populate: (stagingPath) => extractZip(archive, stagingPath).pipe(Effect.mapError(coupleLifecycleDependencyFailure)),
58
+ ...(args.validate === undefined ? {} : { validate: args.validate }),
59
+ inspect: computeMaterializedTreeIntegrity,
60
+ });
61
+ return {
62
+ canonicalPath: result.canonicalPath,
63
+ treeIntegrity: result.inspection,
64
+ };
65
+ });
66
+ export const materializeRegistryPackage = (args) => materializeRegistryPackageWithTreeIntegrity(args).pipe(Effect.map(({ canonicalPath }) => canonicalPath));
67
+ //# sourceMappingURL=registry-materialization.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Presentation port for the source-resolution phase of lifecycle workflows.
3
+ * The feature marks where progress feedback belongs; the application owns the
4
+ * mechanism and wording (the CLI renders a spinner around the phase).
5
+ *
6
+ * @experimental This API is unstable and may change without notice.
7
+ */
8
+ import * as ServiceMap from "effect/Context";
9
+ import type * as Effect from "effect/Effect";
10
+ export interface LifecycleResolutionProgressService {
11
+ /** Surround the source-resolution phase with the application's progress feedback. */
12
+ readonly withSourceResolution: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
13
+ }
14
+ declare const LifecycleResolutionProgress_base: ServiceMap.ServiceClass<LifecycleResolutionProgress, "@agentxm/extension-lifecycle/resolution-progress/LifecycleResolutionProgress", LifecycleResolutionProgressService>;
15
+ export declare class LifecycleResolutionProgress extends LifecycleResolutionProgress_base {
16
+ }
17
+ export {};
18
+ //# sourceMappingURL=resolution-progress.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Presentation port for the source-resolution phase of lifecycle workflows.
3
+ * The feature marks where progress feedback belongs; the application owns the
4
+ * mechanism and wording (the CLI renders a spinner around the phase).
5
+ *
6
+ * @experimental This API is unstable and may change without notice.
7
+ */
8
+ import * as ServiceMap from "effect/Context";
9
+ export class LifecycleResolutionProgress extends ServiceMap.Service()("@agentxm/extension-lifecycle/resolution-progress/LifecycleResolutionProgress") {
10
+ }
11
+ //# sourceMappingURL=resolution-progress.js.map
@@ -0,0 +1,5 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { ExtensionLifecycleFailed } from "./errors.js";
3
+ export declare const CONFIGURED_ENTRY_RESOLUTION_TIMEOUT = "2 seconds";
4
+ export declare const withConfiguredEntryResolutionTimeout: (_source: string) => <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, ExtensionLifecycleFailed | E, R>;
5
+ //# sourceMappingURL=resolution-timeout.d.ts.map
@@ -0,0 +1,11 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { ExtensionLifecycleFailed } from "./errors.js";
3
+ export const CONFIGURED_ENTRY_RESOLUTION_TIMEOUT = "2 seconds";
4
+ export const withConfiguredEntryResolutionTimeout = (_source) => (effect) => effect.pipe(Effect.timeoutOrElse({
5
+ duration: CONFIGURED_ENTRY_RESOLUTION_TIMEOUT,
6
+ orElse: () => Effect.fail(new ExtensionLifecycleFailed({
7
+ category: "network",
8
+ detail: "Timed out while resolving a configured extension source",
9
+ })),
10
+ }));
11
+ //# sourceMappingURL=resolution-timeout.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Rule manager service.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ */
6
+ import * as FileSystem from "effect/FileSystem";
7
+ import * as HttpClient from "effect/unstable/http/HttpClient";
8
+ import * as Layer from "effect/Layer";
9
+ import * as Path from "effect/Path";
10
+ import { RuleManager } from "@agentxm/extension-workspace";
11
+ import { SourceHostProviders, WorkspaceCatalog } from "@agentxm/extension-sources";
12
+ import type { MaterializationObservation } from "@agentxm/extension-workspace";
13
+ import { WorkspaceMutations } from "@agentxm/workspace-state";
14
+ import { LifecycleFailureAdapter } from "../failure-adapter.js";
15
+ export declare const ruleMaterializationObservation: (managedTarget: string, instructionItems: ReadonlyArray<{
16
+ readonly agentId: string;
17
+ readonly health: string;
18
+ }>) => MaterializationObservation;
19
+ export declare const RuleManagerLive: Layer.Layer<RuleManager, never, LifecycleFailureAdapter | SourceHostProviders | WorkspaceCatalog | WorkspaceMutations | FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>;
20
+ //# sourceMappingURL=manager.d.ts.map
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Rule manager service.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as FileSystem from "effect/FileSystem";
8
+ import * as HttpClient from "effect/unstable/http/HttpClient";
9
+ import * as Layer from "effect/Layer";
10
+ import * as Option from "effect/Option";
11
+ import * as Path from "effect/Path";
12
+ import * as Schema from "effect/Schema";
13
+ import { RuleDefinitionInvalid, RuleInstallStateMissing, activeContributors, applyProjectionPlans, planAggregateProjection, reconcileManagedRegionFile, MARKER_KIND_POINT, MARKER_VERSION, parseMarker, serializeMarker, assertInstructionTargetsSafe, assertInstructionsGitignoreSafe, observeInstructionProjection, reconcileInstructionTargets, resolveInstructionsConfig, } from "@agentxm/extension-workspace";
14
+ import { decodeExtensionNameSync, formatFqn } from "@agentxm/extension-model/unstable/extensions";
15
+ import { canReuseInstalledPackage, enabledConfiguredEntries, materializeExternalPackageWithTreeIntegrity, } from "@agentxm/extension-workspace";
16
+ import { materializeRegistryPackageWithTreeIntegrity } from "../registry-materialization.js";
17
+ import { computeExtensionPathsForLayout } from "@agentxm/workspace-state";
18
+ import { RULES_REGION_OWNER, RuleManager } from "@agentxm/extension-workspace";
19
+ import { parseFrontmatterEffect } from "@agentxm/registry-protocol/unstable/content/frontmatter";
20
+ import { computePackageContentHash } from "@agentxm/workspace-state";
21
+ import { computeMaterializedTreeIntegrity } from "@agentxm/workspace-state";
22
+ import {} from "@agentxm/extension-model/unstable/sources/source-hash";
23
+ import { acceptedRegistryVersionForRef, validateExactResolvedVersion, } from "@agentxm/workspace-state";
24
+ import { MaterializedFileTargetSchema } from "@agentxm/workspace-state";
25
+ import { gitSourceLockFields } from "@agentxm/workspace-state";
26
+ import { SourceHostProviders, WorkspaceCatalog } from "@agentxm/extension-sources";
27
+ import { stripFileProtocol } from "../internal/fs-helpers.js";
28
+ import { makeWorkspaceRelativeSourcePath } from "@agentxm/extension-model/unstable/path-types";
29
+ import { removeIfExists } from "@agentxm/workspace-state";
30
+ import { makeWorkspaceRelativePath } from "@agentxm/extension-model/unstable/path-types";
31
+ import { decodeVersionSync } from "@agentxm/extension-model/unstable/version-constraints";
32
+ import { WorkspaceMutations } from "@agentxm/workspace-state";
33
+ import { makeConfiguredReleaseAgeEvaluation, resolveConfiguredRule, } from "../configured-entry-resolution.js";
34
+ import { coupleLifecycleDependencyFailure } from "../errors.js";
35
+ import { LifecycleFailureAdapter } from "../failure-adapter.js";
36
+ import { isObservedInstalled } from "@agentxm/workspace-state";
37
+ import { acceptedCanonicalObservation, prepareAcceptedCanonicalTransition, removableAcceptedCanonicalPath, } from "@agentxm/workspace-state";
38
+ import { RULE_BODY_FILENAME, RULE_EXTENSION_DIR, RULE_MANIFEST_FILENAME, RuleManifestSchema, } from "@agentxm/extension-model/unstable/rules/manifest-schema";
39
+ import {} from "@agentxm/extension-model/unstable/extensions/refs/rule";
40
+ const RULES_REGION = "rules";
41
+ const decodeRuleManifest = Schema.decodeUnknownEffect(RuleManifestSchema);
42
+ const decodeMaterializedTarget = Schema.decodeUnknownSync(MaterializedFileTargetSchema);
43
+ const registryRuleLockEntry = (ref, treeIntegrity) => ({
44
+ type: "registry",
45
+ sourceType: "registry",
46
+ packageFormat: "agentxm",
47
+ endpoint: ref.source.location,
48
+ extensionType: "rule",
49
+ workspaceName: ref.rule.name,
50
+ owner: ref.owner,
51
+ name: ref.name,
52
+ resolvedVersion: decodeVersionSync(ref.version),
53
+ integrity: Option.getOrElse(ref.integrity, () => ""),
54
+ sourceName: ref.source.name,
55
+ publisherBindingId: ref.publisherBindingId,
56
+ treeIntegrity,
57
+ });
58
+ const gitRuleLockEntry = (ref, contentIdentity, treeIntegrity) => ({
59
+ ...gitSourceLockFields(ref.source, "rule", ref.rule.name, Option.fromUndefinedOr(ref.sourcePath), ref.gitCommitSha, ref.gitTreeSha, contentIdentity, ref.owner, ref.name, treeIntegrity),
60
+ });
61
+ const localRuleLockEntry = (ref, workspaceRelativeLocalSourcePath, contentIdentity, treeIntegrity) => ({
62
+ type: "local",
63
+ sourceType: "local",
64
+ sourceName: "local",
65
+ extensionType: "rule",
66
+ workspaceName: ref.rule.name,
67
+ packageFormat: "agentxm",
68
+ packageOwner: ref.owner,
69
+ packageName: ref.name,
70
+ path: Option.getOrElse(workspaceRelativeLocalSourcePath, () => ref.source.path),
71
+ contentIdentity,
72
+ treeIntegrity,
73
+ });
74
+ const normalizeMarkdown = (content) => content
75
+ .replace(/\r\n?/g, "\n")
76
+ .split("\n")
77
+ .map((line) => line.trimEnd())
78
+ .join("\n")
79
+ .trim();
80
+ export const ruleMaterializationObservation = (managedTarget, instructionItems) => {
81
+ const agents = Array.from(new Set(instructionItems
82
+ .filter(({ agentId, health }) => health === "ok" && agentId !== "universal")
83
+ .map(({ agentId }) => agentId)));
84
+ return {
85
+ agents,
86
+ targets: [
87
+ {
88
+ path: managedTarget,
89
+ ...(agents.length === 0 ? {} : { agentIds: agents }),
90
+ },
91
+ ],
92
+ };
93
+ };
94
+ export const RuleManagerLive = Layer.effect(RuleManager, Effect.gen(function* () {
95
+ const ws = yield* WorkspaceMutations;
96
+ const fs = yield* FileSystem.FileSystem;
97
+ const httpClient = yield* HttpClient.HttpClient;
98
+ const path = yield* Path.Path;
99
+ const sources = yield* SourceHostProviders;
100
+ const catalog = yield* WorkspaceCatalog;
101
+ const adapter = yield* LifecycleFailureAdapter;
102
+ const baseDir = ws.baseDir;
103
+ const workspaceScope = ws.scope;
104
+ const fsPathLayer = Layer.mergeAll(Layer.succeed(FileSystem.FileSystem, fs), Layer.succeed(HttpClient.HttpClient, httpClient), Layer.succeed(Path.Path, path));
105
+ const envLayer = Layer.mergeAll(fsPathLayer, Layer.succeed(WorkspaceMutations, ws), Layer.succeed(SourceHostProviders, sources), Layer.succeed(WorkspaceCatalog, catalog), Layer.succeed(LifecycleFailureAdapter, adapter));
106
+ const provide = (effect) => Effect.provide(effect, envLayer);
107
+ const lastInstallState = new Map();
108
+ let lastProjection;
109
+ const materializeFromRegistry = (ref, force) => Effect.gen(function* () {
110
+ const canonicalPath = computeExtensionPathsForLayout(path.join, ws.layout, ref, RULE_EXTENSION_DIR, ref.name).canonicalPath;
111
+ const lockedEntry = yield* ws.getLockedRuleEntry(ref.rule.name);
112
+ const lockedVersion = acceptedRegistryVersionForRef(lockedEntry, ref);
113
+ const reuse = yield* provide(canReuseInstalledPackage({
114
+ installedPath: canonicalPath,
115
+ force,
116
+ refVersion: ref.version,
117
+ hasIntegrity: Option.isSome(ref.integrity),
118
+ ...(lockedVersion === undefined ? {} : { lockedVersion }),
119
+ existsFailureDetail: (target) => `Failed to check if canonical rule package path exists: ${target}`,
120
+ }));
121
+ if (reuse && Option.isSome(lockedEntry)) {
122
+ const observedTree = yield* provide(computeMaterializedTreeIntegrity(canonicalPath));
123
+ if (observedTree === lockedEntry.value.treeIntegrity) {
124
+ return { packageRoot: canonicalPath, treeIntegrity: lockedEntry.value.treeIntegrity };
125
+ }
126
+ }
127
+ const materialized = yield* provide(materializeRegistryPackageWithTreeIntegrity({
128
+ baseDir,
129
+ destinationPath: canonicalPath,
130
+ sourceLocation: ref.source.location,
131
+ owner: ref.owner,
132
+ type: "rule",
133
+ name: ref.name,
134
+ version: ref.version,
135
+ integrity: ref.integrity,
136
+ messages: {
137
+ integrityMismatchDetail: `Integrity mismatch for rule:${ref.name}@${ref.version}`,
138
+ },
139
+ }));
140
+ return {
141
+ packageRoot: materialized.canonicalPath,
142
+ treeIntegrity: materialized.treeIntegrity,
143
+ };
144
+ });
145
+ const materializeFromExternal = (ref) => provide(materializeExternalPackageWithTreeIntegrity({
146
+ baseDir,
147
+ canonicalPath: computeExtensionPathsForLayout(path.join, ws.layout, ref, RULE_EXTENSION_DIR, ref.rule.name).canonicalPath,
148
+ sourceLocation: ref.location,
149
+ copyFailureCode: "validation",
150
+ copyFailureDetail: (canonicalPath) => `Failed to copy rule package files to ${canonicalPath}`,
151
+ }).pipe(Effect.map((materialized) => ({
152
+ packageRoot: materialized.canonicalPath,
153
+ treeIntegrity: materialized.treeIntegrity,
154
+ }))));
155
+ const materializePackage = (ref, force = false) => Effect.gen(function* () {
156
+ switch (ref.refType) {
157
+ case "registry":
158
+ return yield* materializeFromRegistry(ref, force);
159
+ case "git-hosted":
160
+ case "local":
161
+ return yield* materializeFromExternal(ref);
162
+ case "workspace": {
163
+ const expectedPath = computeExtensionPathsForLayout(path.join, ws.layout, ref, RULE_EXTENSION_DIR, ref.name).canonicalPath;
164
+ if (ref.scope !== ws.scope ||
165
+ path.resolve(ref.location) !== path.resolve(expectedPath)) {
166
+ return yield* new RuleDefinitionInvalid({
167
+ detail: `Invalid workspace rule source location: ${ref.location}`,
168
+ });
169
+ }
170
+ return {
171
+ packageRoot: ref.location,
172
+ treeIntegrity: yield* provide(computeMaterializedTreeIntegrity(ref.location)),
173
+ };
174
+ }
175
+ }
176
+ });
177
+ const readManifest = (packageRoot) => fs.readFileString(path.join(packageRoot, RULE_MANIFEST_FILENAME)).pipe(Effect.flatMap((content) => Effect.try({
178
+ try: () => JSON.parse(content),
179
+ catch: (error) => new RuleDefinitionInvalid({
180
+ detail: `Failed to parse ${RULE_MANIFEST_FILENAME}`,
181
+ cause: error,
182
+ }),
183
+ })), Effect.flatMap((content) => decodeRuleManifest(content)), Effect.mapError((error) => new RuleDefinitionInvalid({
184
+ detail: `Failed to read ${RULE_MANIFEST_FILENAME}`,
185
+ cause: error,
186
+ })));
187
+ const sourceFileTarget = () => Effect.gen(function* () {
188
+ const config = yield* ws.getInstructionsConfig();
189
+ const resolved = resolveInstructionsConfig(Option.isSome(config) && config.value !== false ? config.value : undefined);
190
+ const relative = makeWorkspaceRelativePath(path, baseDir, resolved.fileName);
191
+ if (Option.isNone(relative)) {
192
+ return yield* new RuleDefinitionInvalid({
193
+ detail: `Rule instruction source escapes workspace: ${resolved.fileName}`,
194
+ });
195
+ }
196
+ return {
197
+ relative: relative.value,
198
+ absolute: path.resolve(baseDir, relative.value),
199
+ };
200
+ });
201
+ const activeInstructions = () => Effect.gen(function* () {
202
+ const config = yield* ws.getInstructionsConfig();
203
+ if (Option.isNone(config) || config.value === false) {
204
+ return Option.none();
205
+ }
206
+ return Option.some({
207
+ config: resolveInstructionsConfig(config.value),
208
+ agents: yield* ws.getConfiguredAgents(),
209
+ });
210
+ });
211
+ const readRuleBody = (packageRoot) => fs.readFileString(path.join(packageRoot, "src", RULE_BODY_FILENAME)).pipe(Effect.flatMap((content) => parseFrontmatterEffect(content)), Effect.map((parsed) => normalizeMarkdown(parsed.body)), Effect.mapError((error) => new RuleDefinitionInvalid({
212
+ detail: `Failed to read src/${RULE_BODY_FILENAME}`,
213
+ cause: error,
214
+ })));
215
+ const renderRuleBlock = (args) => {
216
+ const header = args.manifest.title !== undefined && !args.body.startsWith("#")
217
+ ? `# ${args.manifest.title}\n\n`
218
+ : "";
219
+ const marker = serializeMarker({
220
+ kind: MARKER_KIND_POINT,
221
+ v: MARKER_VERSION,
222
+ pointKind: "rule",
223
+ ext: `${args.marker}@${args.manifest.version}`,
224
+ }, { kind: "block", open: "<!--", close: "-->" });
225
+ return `${marker}\n\n${header}${args.body}`;
226
+ };
227
+ const selectRuleContributors = (args) => provide(activeContributors({
228
+ layout: ws.layout,
229
+ path,
230
+ type: "rule",
231
+ extensionDir: RULE_EXTENSION_DIR,
232
+ graph: args.graph,
233
+ locked: args.locked,
234
+ })).pipe(Effect.flatMap((contributors) => Effect.forEach(contributors, (contributor) => Effect.gen(function* () {
235
+ const manifest = yield* readManifest(contributor.packageRoot);
236
+ const body = yield* readRuleBody(contributor.packageRoot);
237
+ const marker = Option.match(contributor.identityOwner, {
238
+ onSome: (owner) => formatFqn({
239
+ owner,
240
+ type: "rule",
241
+ name: decodeExtensionNameSync(contributor.node.name),
242
+ }),
243
+ onNone: () => formatFqn({ owner: manifest.owner, type: "rule", name: manifest.name }),
244
+ });
245
+ return { name: contributor.node.name, marker, manifest, body };
246
+ }), { concurrency: "unbounded" })), Effect.map((resolved) => {
247
+ const sorted = [...resolved].sort((a, b) => {
248
+ const byPriority = (a.manifest.priority ?? 100) - (b.manifest.priority ?? 100);
249
+ if (byPriority !== 0)
250
+ return byPriority;
251
+ return a.marker.localeCompare(b.marker);
252
+ });
253
+ return sorted;
254
+ }));
255
+ const observedRuleContributors = (content) => content.split(/\r?\n/u).flatMap((line) => {
256
+ const parsed = parseMarker(line, { kind: "block", open: "<!--", close: "-->" });
257
+ if (parsed.state !== "complete" ||
258
+ parsed.marker.kind !== MARKER_KIND_POINT ||
259
+ parsed.marker.pointKind !== "rule") {
260
+ return [];
261
+ }
262
+ const separator = parsed.marker.ext.lastIndexOf("@");
263
+ return separator > 0 ? [parsed.marker.ext.slice(0, separator)] : [];
264
+ });
265
+ const reconcileRulesRegion = (args) => Effect.gen(function* () {
266
+ const { target } = args;
267
+ const contributors = args.input.contributors;
268
+ const rendered = contributors.map(renderRuleBlock).join("\n\n");
269
+ const instructions = args.instructions;
270
+ if (args.dryRun !== true && Option.isSome(instructions)) {
271
+ yield* provide(Effect.gen(function* () {
272
+ const snapshot = yield* observeInstructionProjection({
273
+ workspaceRoot: baseDir,
274
+ scope: workspaceScope,
275
+ configuredAgents: instructions.value.agents,
276
+ config: instructions.value.config,
277
+ });
278
+ yield* assertInstructionTargetsSafe(snapshot.status);
279
+ yield* assertInstructionsGitignoreSafe(baseDir);
280
+ }));
281
+ }
282
+ const reconciliation = yield* provide(reconcileManagedRegionFile({
283
+ targetPath: target.absolute,
284
+ displayPath: target.relative,
285
+ region: RULES_REGION,
286
+ owner: RULES_REGION_OWNER,
287
+ rendered,
288
+ ...(args.dryRun === undefined ? {} : { dryRun: args.dryRun }),
289
+ writeWhenMissing: true,
290
+ unsupportedTargetDetail: `Instruction source does not support managed regions: ${target.relative}`,
291
+ }));
292
+ const { changed, observedRegion } = reconciliation;
293
+ const materializedTarget = decodeMaterializedTarget({
294
+ target: target.relative,
295
+ mode: "managed-region",
296
+ region: RULES_REGION,
297
+ });
298
+ const projectionUnitObservation = {
299
+ unitId: "rule:instructions-region",
300
+ path: `${target.relative}#${RULES_REGION}`,
301
+ owner: RULES_REGION_OWNER,
302
+ present: Option.isSome(observedRegion),
303
+ current: !changed,
304
+ expectedContributors: contributors.map(({ marker }) => marker),
305
+ observedContributors: Option.match(observedRegion, {
306
+ onNone: () => [],
307
+ onSome: observedRuleContributors,
308
+ }),
309
+ };
310
+ if (args.dryRun === true) {
311
+ return {
312
+ materializedTarget,
313
+ materialization: ruleMaterializationObservation(target.relative, []),
314
+ changed,
315
+ projectionUnitObservation,
316
+ };
317
+ }
318
+ const instructionItems = Option.isSome(instructions)
319
+ ? (yield* provide(reconcileInstructionTargets({
320
+ workspaceRoot: baseDir,
321
+ scope: workspaceScope,
322
+ configuredAgents: instructions.value.agents,
323
+ config: instructions.value.config,
324
+ }))).snapshot.status.items
325
+ : [];
326
+ const materialization = ruleMaterializationObservation(target.relative, instructionItems);
327
+ lastProjection = materialization;
328
+ return { materializedTarget, materialization, changed, projectionUnitObservation };
329
+ });
330
+ const makeRulesProjectionPlan = () => Effect.gen(function* () {
331
+ const target = yield* sourceFileTarget();
332
+ const instructions = yield* activeInstructions();
333
+ const graph = yield* ws.getDesiredStateGraph();
334
+ const locked = yield* ws.getLockedRules();
335
+ return yield* planAggregateProjection({
336
+ unitId: "rule:instructions-region",
337
+ targetFile: target.absolute,
338
+ graph,
339
+ select: (completeGraph) => selectRuleContributors({ graph: completeGraph, locked }),
340
+ adapter: {
341
+ observe: (input) => reconcileRulesRegion({ input, target, instructions, dryRun: true }).pipe(Effect.map(({ projectionUnitObservation }) => projectionUnitObservation)),
342
+ apply: (input) => reconcileRulesRegion({ input, target, instructions }).pipe(Effect.asVoid),
343
+ },
344
+ });
345
+ });
346
+ const projectionPlans = () => makeRulesProjectionPlan().pipe(Effect.map((plan) => [plan]));
347
+ const applyRulesProjection = projectionPlans().pipe(Effect.flatMap(applyProjectionPlans));
348
+ const materializeInstall = Effect.fn("RuleManager.materializeInstall")(function* ({ ref, force }) {
349
+ const materialized = yield* materializePackage(ref, force === true);
350
+ const packageRoot = materialized.packageRoot;
351
+ yield* readManifest(packageRoot);
352
+ const workspaceRelativeLocalSourcePath = ref.refType === "local"
353
+ ? makeWorkspaceRelativeSourcePath(path, baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
354
+ : Option.none();
355
+ if (ref.refType === "local" && Option.isNone(workspaceRelativeLocalSourcePath)) {
356
+ return yield* new RuleDefinitionInvalid({
357
+ detail: `Local rule source path must stay within the workspace root: ${ref.source.path}`,
358
+ });
359
+ }
360
+ const sourceHash = yield* provide(computePackageContentHash(packageRoot));
361
+ lastInstallState.set(ref.rule.name, {
362
+ ref,
363
+ workspaceRelativeLocalSourcePath,
364
+ sourceHash,
365
+ treeIntegrity: materialized.treeIntegrity,
366
+ });
367
+ }, Effect.asVoid);
368
+ const buildLockEntry = (ref) => Effect.gen(function* () {
369
+ const state = lastInstallState.get(ref.rule.name);
370
+ switch (ref.refType) {
371
+ case "registry":
372
+ return state === undefined
373
+ ? yield* new RuleInstallStateMissing({ name: ref.rule.name, kind: "tree-integrity" })
374
+ : Option.some(registryRuleLockEntry(ref, state.treeIntegrity));
375
+ case "git-hosted":
376
+ return state === undefined
377
+ ? yield* new RuleInstallStateMissing({
378
+ name: ref.rule.name,
379
+ kind: "content-identity",
380
+ })
381
+ : Option.some(gitRuleLockEntry(ref, state.sourceHash, state.treeIntegrity));
382
+ case "local":
383
+ return state === undefined
384
+ ? yield* new RuleInstallStateMissing({
385
+ name: ref.rule.name,
386
+ kind: "content-identity",
387
+ })
388
+ : Option.some(localRuleLockEntry(ref, state.workspaceRelativeLocalSourcePath, state.sourceHash, state.treeIntegrity));
389
+ case "workspace":
390
+ return Option.none();
391
+ }
392
+ });
393
+ // Canonical removal only. The shared operation flow re-renders the region
394
+ // after settings and lock removal, once the target has left the graph.
395
+ const materializeUninstall = Effect.fn("RuleManager.materializeUninstall")(function* ({ target }) {
396
+ const canonical = yield* provide(acceptedCanonicalObservation({
397
+ workspace: ws,
398
+ type: "rule",
399
+ name: target.name,
400
+ }));
401
+ const packageRoot = removableAcceptedCanonicalPath(canonical);
402
+ if (Option.isSome(packageRoot)) {
403
+ yield* removeIfExists(fs, packageRoot.value);
404
+ }
405
+ }, Effect.asVoid);
406
+ // Deactivation retains canonical content; the caller updates settings
407
+ // first, so re-rendering the whole region drops this rule's contribution.
408
+ const materializeDeactivate = Effect.fn("RuleManager.materializeDeactivate")(() => applyRulesProjection);
409
+ return {
410
+ type: "rule",
411
+ projectionPlans,
412
+ runTransaction: ws.runTransaction,
413
+ isInstalled: ({ target }) => isObservedInstalled(ws, "rule", target.name).pipe(Effect.withSpan("RuleManager.isInstalled")),
414
+ materializeInstall,
415
+ prepareSourceTransition: ({ ref }) => provide(prepareAcceptedCanonicalTransition({
416
+ workspace: ws,
417
+ type: "rule",
418
+ name: ref.rule.name,
419
+ ref,
420
+ })),
421
+ getLastMaterialization: () => Effect.succeed(lastProjection ?? { agents: [], targets: [] }),
422
+ getConfiguredSource: Effect.fn("RuleManager.getConfiguredSource")(function* ({ target }) {
423
+ const configured = yield* ws.getConfiguredRuleEntries();
424
+ return Option.fromUndefinedOr(configured[target.name]?.source);
425
+ }),
426
+ listMaterializable: Effect.fn("RuleManager.listMaterializable")(function* () {
427
+ const configured = yield* ws.getConfiguredRuleEntries();
428
+ const releaseAgeEvaluation = yield* provide(makeConfiguredReleaseAgeEvaluation("enforce")).pipe(Effect.mapError(coupleLifecycleDependencyFailure));
429
+ const refs = yield* Effect.scoped(Effect.forEach(enabledConfiguredEntries(configured), ([name, entry]) => provide(resolveConfiguredRule(name, entry.source, releaseAgeEvaluation)).pipe(Effect.mapError(coupleLifecycleDependencyFailure), Effect.map(({ ref }) => ref)), { concurrency: "unbounded" }));
430
+ return refs;
431
+ }),
432
+ materializeUninstall,
433
+ materializeDeactivate,
434
+ upsertSettingsEntry: Effect.fn("RuleManager.upsertSettingsEntry")(function* ({ ref, versionRange, }) {
435
+ const lockEntry = yield* buildLockEntry(ref);
436
+ if (Option.isNone(lockEntry)) {
437
+ yield* ws.setRuleEntry(ref.rule.name, {
438
+ source: "workspace",
439
+ enabled: true,
440
+ });
441
+ return;
442
+ }
443
+ if (lockEntry.value.type === "registry") {
444
+ yield* validateExactResolvedVersion(`rules.${ref.rule.name}.resolvedVersion`, lockEntry.value.resolvedVersion);
445
+ }
446
+ yield* ws.setRule({
447
+ name: ref.rule.name,
448
+ lockEntry: lockEntry.value,
449
+ versionRange,
450
+ });
451
+ }),
452
+ removeSettingsEntry: Effect.fn("RuleManager.removeSettingsEntry")(function* ({ target }) {
453
+ yield* ws.removeRuleSettings(target.name);
454
+ }),
455
+ upsertLockfileEntry: Effect.fn("RuleManager.upsertLockfileEntry")(function* ({ ref }) {
456
+ const lockEntry = yield* buildLockEntry(ref);
457
+ if (Option.isNone(lockEntry)) {
458
+ yield* ws.removeRuleLock(ref.rule.name);
459
+ return;
460
+ }
461
+ if (lockEntry.value.type === "registry") {
462
+ yield* validateExactResolvedVersion(`rules.${ref.rule.name}.resolvedVersion`, lockEntry.value.resolvedVersion);
463
+ }
464
+ yield* ws.setRuleLock({
465
+ name: ref.rule.name,
466
+ lockEntry: lockEntry.value,
467
+ versionRange: Option.none(),
468
+ });
469
+ }),
470
+ removeLockfileEntry: Effect.fn("RuleManager.removeLockfileEntry")(function* ({ target }) {
471
+ yield* ws.removeRuleLock(target.name);
472
+ }),
473
+ };
474
+ }));
475
+ //# sourceMappingURL=manager.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Skill extension manager service.
3
+ *
4
+ * Implements ExtensionManager<SkillExtensionRef> with native/non-native
5
+ * branching in materializeInstall and agent symlink creation for all
6
+ * configured agents.
7
+ *
8
+ * @experimental This API is unstable and may change without notice.
9
+ */
10
+ import * as FileSystem from "effect/FileSystem";
11
+ import * as HttpClient from "effect/unstable/http/HttpClient";
12
+ import * as Path from "effect/Path";
13
+ import * as Layer from "effect/Layer";
14
+ import { SourceHostProviders } from "@agentxm/extension-sources";
15
+ import { WorkspaceMutations } from "@agentxm/workspace-state";
16
+ import { CodingAgentRepository, SkillManager } from "@agentxm/extension-workspace";
17
+ export declare const SkillManagerLive: Layer.Layer<SkillManager, never, SourceHostProviders | WorkspaceMutations | FileSystem.FileSystem | HttpClient.HttpClient | Path.Path | CodingAgentRepository>;
18
+ //# sourceMappingURL=manager.d.ts.map