@agentxm/extension-materialization 0.29.2 → 0.30.1

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.
@@ -1,3 +1,5 @@
1
+ import { usableAcceptedCanonical } from "@agentxm/workspace-state";
2
+ import { LifecyclePostconditionViolated } from "../extensions/errors.js";
1
3
  /**
2
4
  * MCP server extension manager service.
3
5
  *
@@ -12,10 +14,10 @@ import * as Effect from "effect/Effect";
12
14
  import * as Layer from "effect/Layer";
13
15
  import * as Option from "effect/Option";
14
16
  import { McpInstallStateMissing, McpRegistryOnlyInstall } from "./errors.js";
15
- import { applyProjectionPlans, planSingletonProjection } from "@agentxm/workspace-projection";
17
+ import { applyProjectionPlans, inspectMcpServerAcrossAgents, planSingletonProjection, } from "@agentxm/workspace-projection";
16
18
  import { McpConfigIoFailed, removeMcpServerFromManifest } from "@agentxm/agent-integration";
17
19
  import { NO_MATERIALIZATION_OBSERVATION } from "../manager-contract.js";
18
- import { McpServerManager } from "../managers.js";
20
+ import { McpServerManager, } from "../managers.js";
19
21
  import { configuredMcpServersToDiskRefs } from "../extensions/materializable-from-disk.js";
20
22
  import { mcpRegistryResolutionKey, WorkspaceMutations } from "@agentxm/workspace-state";
21
23
  import { canReuseInstalledPackage } from "../extensions/canonical-directory.js";
@@ -159,12 +161,79 @@ export const McpServerManagerLive = Layer.effect(McpServerManager, Effect.gen(fu
159
161
  });
160
162
  const materializeUninstall = makeMaterializeRemoval(false);
161
163
  const materializeDeactivate = makeMaterializeRemoval(true);
164
+ const configuredAgentOutcomesForEntry = Effect.fn("McpServerManager.configuredAgentOutcomesForEntry")(function* ({ name, entry, state, }) {
165
+ const configuredAgentIds = yield* ws.getConfiguredAgents();
166
+ const canonical = entry.kind === "inline"
167
+ ? Option.none()
168
+ : (yield* acceptedCanonicalObservation({
169
+ workspace: ws,
170
+ type: "mcp-server",
171
+ name,
172
+ })).pipe(Option.flatMap(({ observation }) => Option.fromUndefinedOr(observation.path)));
173
+ const inspections = yield* inspectMcpServerAcrossAgents({
174
+ workspaceRoot: baseDir,
175
+ scope: ws.scope,
176
+ agentIds: configuredAgentIds,
177
+ serverName: name,
178
+ entry: { ...entry, enabled: true },
179
+ canonicalPaths: Option.match(canonical, { onNone: () => [], onSome: (value) => [value] }),
180
+ state,
181
+ });
182
+ return inspections.map((inspection) => ({
183
+ extensionType: "mcp-server",
184
+ name,
185
+ agentId: inspection.agentId,
186
+ outcome: inspection.status === "match"
187
+ ? state
188
+ : inspection.status === "unsupported"
189
+ ? "unsupported"
190
+ : inspection.status === "blocked"
191
+ ? "blocked"
192
+ : "failed",
193
+ reasonCode: inspection.status === "match"
194
+ ? "supported"
195
+ : inspection.status === "absent"
196
+ ? "projection-missing"
197
+ : inspection.status === "drift"
198
+ ? "stale-projection"
199
+ : `mcp-${inspection.status}`,
200
+ reason: inspection.reason ??
201
+ (inspection.status === "match"
202
+ ? `${inspection.agentId} has a matching MCP projection.`
203
+ : inspection.status === "absent"
204
+ ? `The expected ${inspection.agentId} projection is missing.`
205
+ : inspection.status === "drift"
206
+ ? `The expected ${inspection.agentId} projection is stale.`
207
+ : `MCP projection status is ${inspection.status}.`),
208
+ ...(inspection.path.length === 0 ? {} : { path: inspection.path }),
209
+ }));
210
+ });
211
+ const configuredAgentOutcomes = (state) => Effect.gen(function* () {
212
+ const entries = yield* ws.getConfiguredMcpServerEntries();
213
+ return (yield* Effect.forEach(Object.entries(entries).filter(([, entry]) => state === "projected" || entry.enabled), ([name, entry]) => configuredAgentOutcomesForEntry({ name, entry, state }), { concurrency: "unbounded" })).flat();
214
+ });
162
215
  return {
163
216
  type: "mcp-server",
164
217
  isInstalled: Effect.fn("McpServerManager.isInstalled")(function* ({ target, }) {
165
218
  return yield* isObservedInstalled(ws, "mcp-server", target.name);
166
219
  }),
167
220
  materializeInstall,
221
+ acquireCanonical: materializeInstall,
222
+ materializeRetained: ({ target }) => Effect.gen(function* () {
223
+ const canonical = yield* usableAcceptedCanonical({
224
+ workspace: ws,
225
+ type: "mcp-server",
226
+ name: target.name,
227
+ });
228
+ if (Option.isNone(canonical) || canonical.value.ref.type !== "mcp-server") {
229
+ return yield* new LifecyclePostconditionViolated({
230
+ postcondition: "materialize-observable",
231
+ targetType: "mcp-server",
232
+ targetName: target.name,
233
+ });
234
+ }
235
+ return yield* materializeInstall({ ref: canonical.value.ref });
236
+ }),
168
237
  prepareSourceTransition: ({ ref }) => prepareAcceptedCanonicalTransition({
169
238
  workspace: ws,
170
239
  type: "mcp-server",
@@ -187,9 +256,11 @@ export const McpServerManagerLive = Layer.effect(McpServerManager, Effect.gen(fu
187
256
  }),
188
257
  materializeUninstall,
189
258
  materializeDeactivate,
190
- upsertSettingsEntry: ({ ref, versionRange, materialization, }) => {
259
+ configuredAgentOutcomes,
260
+ configuredAgentOutcomesForEntry,
261
+ acceptedResolution: ({ ref, materialization, }) => {
191
262
  if (ref.refType !== "registry")
192
- return Effect.void.pipe(Effect.withSpan("McpServerManager.upsertSettingsEntry"));
263
+ return Effect.succeed(Option.none());
193
264
  const registryRef = ref;
194
265
  return validateExactResolvedVersion(`mcpServers.${ref.server.name}.resolvedVersion`, registryRef.version).pipe(Effect.flatMap(() => {
195
266
  const treeIntegrity = materialization.pipe(Option.flatMap((facts) => facts.treeIntegrity));
@@ -197,56 +268,25 @@ export const McpServerManagerLive = Layer.effect(McpServerManager, Effect.gen(fu
197
268
  return Effect.fail(new McpInstallStateMissing({ name: registryRef.server.name }));
198
269
  }
199
270
  const lockEntry = buildMcpServerLockEntry(registryRef, treeIntegrity.value);
200
- return ws.setMcpServer({
201
- name: ref.server.name,
202
- resolutionKey: mcpRegistryResolutionKey({
271
+ return Effect.succeed(Option.some({
272
+ key: mcpRegistryResolutionKey({
203
273
  authority: registryRef.source.location,
204
274
  owner: registryRef.owner,
205
275
  name: registryRef.server.name,
206
276
  }),
207
- lockEntry,
208
- versionRange,
209
- });
210
- }), Effect.withSpan("McpServerManager.upsertSettingsEntry"));
211
- },
212
- removeSettingsEntry: ({ target }) => ws
213
- .removeMcpServerSettings(target.name)
214
- .pipe(Effect.withSpan("McpServerManager.removeSettingsEntry")),
215
- upsertLockfileEntry: ({ ref, materialization, }) => {
216
- if (ref.refType !== "registry")
217
- return ws
218
- .removeMcpServerLock(ref.server.name)
219
- .pipe(Effect.withSpan("McpServerManager.upsertLockfileEntry"));
220
- const registryRef = ref;
221
- return validateExactResolvedVersion(`mcpServers.${ref.server.name}.resolvedVersion`, registryRef.version).pipe(Effect.flatMap(() => {
222
- const treeIntegrity = materialization.pipe(Option.flatMap((facts) => facts.treeIntegrity));
223
- if (Option.isNone(treeIntegrity)) {
224
- return Effect.fail(new McpInstallStateMissing({ name: registryRef.server.name }));
225
- }
226
- const lockEntry = buildMcpServerLockEntry(registryRef, treeIntegrity.value);
227
- return ws.setMcpServerLock({
228
- name: ref.server.name,
229
- resolutionKey: mcpRegistryResolutionKey({
230
- authority: registryRef.source.location,
231
- owner: registryRef.owner,
232
- name: registryRef.server.name,
233
- }),
234
- lockEntry,
235
- versionRange: Option.none(),
236
- });
237
- }), Effect.withSpan("McpServerManager.upsertLockfileEntry"));
277
+ entry: lockEntry,
278
+ }));
279
+ }), Effect.withSpan("McpServerManager.acceptedResolution"));
238
280
  },
239
- removeLockfileEntry: ({ materialization, }) => {
281
+ withdrawnResolutionKeys: ({ materialization, }) => {
240
282
  const removal = materialization.pipe(Option.flatMap((facts) => facts.removal));
241
283
  if (Option.isNone(removal)) {
242
- return Effect.void.pipe(Effect.withSpan("McpServerManager.removeLockfileEntry"));
284
+ return Effect.succeed([]);
243
285
  }
244
286
  if (removal.value.retainShared || Option.isNone(removal.value.resolutionKey)) {
245
- return Effect.void.pipe(Effect.withSpan("McpServerManager.removeLockfileEntry"));
287
+ return Effect.succeed([]);
246
288
  }
247
- return ws
248
- .removeMcpServerLock(removal.value.resolutionKey.value)
249
- .pipe(Effect.withSpan("McpServerManager.removeLockfileEntry"));
289
+ return Effect.succeed([removal.value.resolutionKey.value]);
250
290
  },
251
291
  };
252
292
  }));
@@ -1,3 +1,5 @@
1
+ import { usableAcceptedCanonical } from "@agentxm/workspace-state";
2
+ import { LifecyclePostconditionViolated } from "../extensions/errors.js";
1
3
  /**
2
4
  * Pack manager service.
3
5
  *
@@ -145,6 +147,7 @@ export const PackManagerLive = Layer.effect(PackManager, Effect.gen(function* ()
145
147
  return yield* isObservedInstalled(ws, "pack", target.name);
146
148
  }),
147
149
  materializeInstall,
150
+ acquireCanonical: materializeInstall,
148
151
  prepareSourceTransition: ({ ref }) => prepareAcceptedCanonicalTransition({
149
152
  workspace: ws,
150
153
  type: "pack",
@@ -161,29 +164,29 @@ export const PackManagerLive = Layer.effect(PackManager, Effect.gen(function* ()
161
164
  }),
162
165
  materializeUninstall,
163
166
  materializeDeactivate,
164
- upsertSettingsEntry: Effect.fn("PackManager.upsertSettingsEntry")(function* ({ ref, versionRange, materialization, }) {
165
- const args = yield* buildCurrentPackArgs(ref, versionRange, materialization);
166
- if (Option.isSome(args)) {
167
- yield* ws.setPack(args.value);
168
- }
169
- else {
170
- yield* ws.setPackEntry(ref.pack.name, {
171
- source: "workspace",
172
- enabled: true,
167
+ materializeRetained: ({ target }) => Effect.gen(function* () {
168
+ const canonical = yield* usableAcceptedCanonical({
169
+ workspace: ws,
170
+ type: "pack",
171
+ name: target.name,
172
+ });
173
+ if (Option.isNone(canonical) || canonical.value.ref.type !== "pack") {
174
+ return yield* new LifecyclePostconditionViolated({
175
+ postcondition: "materialize-observable",
176
+ targetType: "pack",
177
+ targetName: target.name,
173
178
  });
174
179
  }
180
+ return yield* materializeDeactivate({ target });
175
181
  }),
176
- removeSettingsEntry: ({ target }) => ws.removePackSettings(target.name).pipe(Effect.withSpan("PackManager.removeSettingsEntry")),
177
- upsertLockfileEntry: Effect.fn("PackManager.upsertLockfileEntry")(function* ({ ref, materialization, }) {
182
+ acceptedResolution: Effect.fn("PackManager.acceptedResolution")(function* ({ ref, materialization, }) {
178
183
  const args = yield* buildCurrentPackArgs(ref, Option.none(), materialization);
179
- if (Option.isSome(args)) {
180
- yield* ws.setPackLock(args.value);
181
- }
182
- else {
183
- yield* ws.removePackLock(ref.pack.name);
184
- }
184
+ return Option.map(args, ({ versionRange: _versionRange, ...entry }) => ({
185
+ key: ref.pack.name,
186
+ entry,
187
+ }));
185
188
  }),
186
- removeLockfileEntry: ({ target }) => ws.removePackLock(target.name).pipe(Effect.withSpan("PackManager.removeLockfileEntry")),
189
+ withdrawnResolutionKeys: ({ target }) => Effect.succeed([target.name]),
187
190
  };
188
191
  }));
189
192
  //# sourceMappingURL=manager.js.map
@@ -1,8 +1,3 @@
1
- /**
2
- * Rule manager service.
3
- *
4
- * @experimental This API is unstable and may change without notice.
5
- */
6
1
  import * as FileSystem from "effect/FileSystem";
7
2
  import * as Layer from "effect/Layer";
8
3
  import * as Path from "effect/Path";
@@ -1,3 +1,5 @@
1
+ import { usableAcceptedCanonical } from "@agentxm/workspace-state";
2
+ import { LifecyclePostconditionViolated } from "../extensions/errors.js";
1
3
  /**
2
4
  * Rule manager service.
3
5
  *
@@ -424,6 +426,22 @@ export const RuleManagerLive = Layer.effect(RuleManager, Effect.gen(function* ()
424
426
  aggregateProjectionObservation: Ref.get(lastProjection),
425
427
  isInstalled: ({ target }) => isObservedInstalled(ws, "rule", target.name).pipe(Effect.withSpan("RuleManager.isInstalled")),
426
428
  materializeInstall,
429
+ acquireCanonical: materializeInstall,
430
+ materializeRetained: ({ target }) => Effect.gen(function* () {
431
+ const canonical = yield* usableAcceptedCanonical({
432
+ workspace: ws,
433
+ type: "rule",
434
+ name: target.name,
435
+ });
436
+ if (Option.isNone(canonical) || canonical.value.ref.type !== "rule") {
437
+ return yield* new LifecyclePostconditionViolated({
438
+ postcondition: "materialize-observable",
439
+ targetType: "rule",
440
+ targetName: target.name,
441
+ });
442
+ }
443
+ return yield* materializeInstall({ ref: canonical.value.ref });
444
+ }),
427
445
  prepareSourceTransition: ({ ref }) => provide(prepareAcceptedCanonicalTransition({
428
446
  workspace: ws,
429
447
  type: "rule",
@@ -448,45 +466,17 @@ export const RuleManagerLive = Layer.effect(RuleManager, Effect.gen(function* ()
448
466
  }),
449
467
  materializeUninstall,
450
468
  materializeDeactivate,
451
- upsertSettingsEntry: Effect.fn("RuleManager.upsertSettingsEntry")(function* ({ ref, versionRange, materialization, }) {
452
- const lockEntry = yield* buildLockEntry(ref, materialization);
453
- if (Option.isNone(lockEntry)) {
454
- yield* ws.setRuleEntry(ref.rule.name, {
455
- source: "workspace",
456
- enabled: true,
457
- });
458
- return;
459
- }
460
- if (lockEntry.value.type === "registry") {
461
- yield* validateExactResolvedVersion(`rules.${ref.rule.name}.resolvedVersion`, lockEntry.value.resolvedVersion);
462
- }
463
- yield* ws.setRule({
464
- name: ref.rule.name,
465
- lockEntry: lockEntry.value,
466
- versionRange,
467
- });
468
- }),
469
- removeSettingsEntry: Effect.fn("RuleManager.removeSettingsEntry")(function* ({ target }) {
470
- yield* ws.removeRuleSettings(target.name);
471
- }),
472
- upsertLockfileEntry: Effect.fn("RuleManager.upsertLockfileEntry")(function* ({ ref, materialization, }) {
469
+ acceptedResolution: Effect.fn("RuleManager.acceptedResolution")(function* ({ ref, materialization, }) {
473
470
  const lockEntry = yield* buildLockEntry(ref, materialization);
474
471
  if (Option.isNone(lockEntry)) {
475
- yield* ws.removeRuleLock(ref.rule.name);
476
- return;
472
+ return Option.none();
477
473
  }
478
474
  if (lockEntry.value.type === "registry") {
479
475
  yield* validateExactResolvedVersion(`rules.${ref.rule.name}.resolvedVersion`, lockEntry.value.resolvedVersion);
480
476
  }
481
- yield* ws.setRuleLock({
482
- name: ref.rule.name,
483
- lockEntry: lockEntry.value,
484
- versionRange: Option.none(),
485
- });
486
- }),
487
- removeLockfileEntry: Effect.fn("RuleManager.removeLockfileEntry")(function* ({ target }) {
488
- yield* ws.removeRuleLock(target.name);
477
+ return Option.some({ key: ref.rule.name, entry: lockEntry.value });
489
478
  }),
479
+ withdrawnResolutionKeys: ({ target }) => Effect.succeed([target.name]),
490
480
  };
491
481
  }));
492
482
  //# sourceMappingURL=manager.js.map
@@ -5,7 +5,8 @@
5
5
  *
6
6
  * @experimental This API is unstable and may change without notice.
7
7
  */
8
- import type { AxmSkillCompatibilityUnavailable, AxmSkillIncompatible } from "@agentxm/extension-resolution";
8
+ import type { AxmSkillCompatibilityUnavailable } from "@agentxm/cli-maintenance/official-skill/application";
9
+ import type { AxmSkillIncompatible } from "@agentxm/cli-maintenance/official-skill/domain";
9
10
  declare const SkillDefinitionInvalid_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
10
11
  readonly _tag: "SkillDefinitionInvalid";
11
12
  } & Readonly<A>;
@@ -1,3 +1,5 @@
1
+ import { usableAcceptedCanonical } from "@agentxm/workspace-state";
2
+ import { LifecyclePostconditionViolated } from "../extensions/errors.js";
1
3
  /**
2
4
  * Skill extension manager service.
3
5
  *
@@ -216,6 +218,45 @@ export const SkillManagerLive = Layer.effect(SkillManager, Effect.gen(function*
216
218
  return yield* isObservedInstalled(ws, "skill", target.name);
217
219
  }),
218
220
  materializeInstall,
221
+ acquireCanonical: ({ ref, force }) => Effect.gen(function* () {
222
+ const locked = yield* ws.getLockedSkill(ref.skill.name);
223
+ const materialized = yield* materializeSkillCanonical({
224
+ ref,
225
+ sanitizedName: sanitizeName(ref.skill.name),
226
+ baseDir,
227
+ layout: ws.layout,
228
+ reuse: {
229
+ force: force === true,
230
+ lockedVersion: ref.refType === "registry" ? acceptedRegistryVersionForRef(locked, ref) : undefined,
231
+ lockedTreeIntegrity: Option.isSome(locked) ? locked.value.treeIntegrity : undefined,
232
+ },
233
+ });
234
+ const sourceHash = ref.refType === "workspace"
235
+ ? ref.sourceHash
236
+ : ref.refType === "registry"
237
+ ? yield* computePackageContentHash(path.dirname(materialized.skillSrcPath))
238
+ : yield* computeSkillSourceHash(materialized.skillSrcPath);
239
+ return {
240
+ sourceHash: Option.some(sourceHash),
241
+ treeIntegrity: Option.fromUndefinedOr(materialized.treeIntegrity),
242
+ observation: { agents: [], targets: [] },
243
+ };
244
+ }),
245
+ materializeRetained: ({ target }) => Effect.gen(function* () {
246
+ const canonical = yield* usableAcceptedCanonical({
247
+ workspace: ws,
248
+ type: "skill",
249
+ name: target.name,
250
+ });
251
+ if (Option.isNone(canonical) || canonical.value.ref.type !== "skill") {
252
+ return yield* new LifecyclePostconditionViolated({
253
+ postcondition: "materialize-observable",
254
+ targetType: "skill",
255
+ targetName: target.name,
256
+ });
257
+ }
258
+ return yield* materializeInstall({ ref: canonical.value.ref });
259
+ }),
219
260
  prepareSourceTransition: ({ ref }) => prepareAcceptedCanonicalTransition({
220
261
  workspace: ws,
221
262
  type: "skill",
@@ -249,49 +290,7 @@ export const SkillManagerLive = Layer.effect(SkillManager, Effect.gen(function*
249
290
  }),
250
291
  materializeUninstall,
251
292
  materializeDeactivate,
252
- upsertSettingsEntry: Effect.fn("SkillManager.upsertSettingsEntry")(function* ({ ref, versionRange, materialization, }) {
253
- const workspaceRelativeLocalSourcePath = ref.refType === "local"
254
- ? makeWorkspaceRelativeSourcePath(path, baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
255
- : Option.none();
256
- if (ref.refType === "local" && Option.isNone(workspaceRelativeLocalSourcePath)) {
257
- return yield* new SkillDefinitionInvalid({
258
- detail: `Local skill source path must stay within the workspace root: ${ref.source.path}`,
259
- });
260
- }
261
- const sourceHash = Option.getOrUndefined(materialization.pipe(Option.flatMap((facts) => facts.sourceHash)));
262
- const treeIntegrity = Option.getOrUndefined(materialization.pipe(Option.flatMap((facts) => facts.treeIntegrity)));
263
- if (ref.refType === "workspace") {
264
- return yield* ws.setSkillEntry(ref.skill.name, {
265
- source: "workspace",
266
- enabled: true,
267
- });
268
- }
269
- if (sourceHash === undefined || treeIntegrity === undefined) {
270
- return yield* new SkillInstallStateMissing({
271
- name: ref.skill.name,
272
- kind: "content-identity",
273
- });
274
- }
275
- const lockEntry = buildSkillLockEntry(ref, workspaceRelativeLocalSourcePath, sourceHash, treeIntegrity);
276
- if (lockEntry === undefined) {
277
- return yield* new SkillInstallStateMissing({
278
- name: ref.skill.name,
279
- kind: "external-resolution",
280
- });
281
- }
282
- if (lockEntry.type === "registry") {
283
- yield* validateExactResolvedVersion(`skills.${ref.skill.name}.resolvedVersion`, lockEntry.resolvedVersion);
284
- }
285
- return yield* ws.setSkill({
286
- name: ref.skill.name,
287
- lockEntry,
288
- versionRange,
289
- });
290
- }),
291
- removeSettingsEntry: ({ target }) => ws
292
- .removeSkillFromSettings(target.name)
293
- .pipe(Effect.withSpan("SkillManager.removeSettingsEntry")),
294
- upsertLockfileEntry: Effect.fn("SkillManager.upsertLockfileEntry")(function* ({ ref, materialization, }) {
293
+ acceptedResolution: Effect.fn("SkillManager.acceptedResolution")(function* ({ ref, materialization, }) {
295
294
  const workspaceRelativeLocalSourcePath = ref.refType === "local"
296
295
  ? makeWorkspaceRelativeSourcePath(path, baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
297
296
  : Option.none();
@@ -301,8 +300,7 @@ export const SkillManagerLive = Layer.effect(SkillManager, Effect.gen(function*
301
300
  });
302
301
  }
303
302
  if (ref.refType === "workspace") {
304
- yield* ws.removeSkillLock(ref.skill.name);
305
- return;
303
+ return Option.none();
306
304
  }
307
305
  const sourceHash = Option.getOrUndefined(materialization.pipe(Option.flatMap((facts) => facts.sourceHash)));
308
306
  const treeIntegrity = Option.getOrUndefined(materialization.pipe(Option.flatMap((facts) => facts.treeIntegrity)));
@@ -322,15 +320,9 @@ export const SkillManagerLive = Layer.effect(SkillManager, Effect.gen(function*
322
320
  if (lockEntry.type === "registry") {
323
321
  yield* validateExactResolvedVersion(`skills.${ref.skill.name}.resolvedVersion`, lockEntry.resolvedVersion);
324
322
  }
325
- return yield* ws.setSkillLock({
326
- name: ref.skill.name,
327
- lockEntry,
328
- versionRange: Option.none(),
329
- });
323
+ return Option.some({ key: ref.skill.name, entry: lockEntry });
330
324
  }),
331
- removeLockfileEntry: ({ target }) => ws
332
- .removeSkillLock(target.name)
333
- .pipe(Effect.withSpan("SkillManager.removeLockfileEntry")),
325
+ withdrawnResolutionKeys: ({ target }) => Effect.succeed([target.name]),
334
326
  };
335
327
  }));
336
328
  // -----------------------------------------------------------------------------
@@ -35,10 +35,10 @@ export declare const materializeSkillCanonical: (args: {
35
35
  }) => Effect.Effect<{
36
36
  skillSrcPath: string & import("effect/Brand").Brand<"AbsolutePath">;
37
37
  treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
38
- }, import("../index.ts").CanonicalPackageProbeFailed | import("../index.ts").PackageCopyFailed | import("@agentxm/extension-resolution").AxmSkillCompatibilityUnavailable | import("@agentxm/extension-resolution").AxmSkillIncompatible | import("@agentxm/workspace-state").MaterializedTreeInvalid | import("../extensions/canonical-directory.js").CanonicalDirectoryReplacementError, FileSystem.FileSystem | Path.Path> | Effect.Effect<{
38
+ }, import("../index.ts").CanonicalPackageProbeFailed | import("../index.ts").PackageCopyFailed | import("@agentxm/cli-maintenance/official-skill/application").AxmSkillCompatibilityUnavailable | import("@agentxm/cli-maintenance/official-skill/domain").AxmSkillIncompatible | import("@agentxm/workspace-state").MaterializedTreeInvalid | import("../extensions/canonical-directory.js").CanonicalDirectoryReplacementError, FileSystem.FileSystem | Path.Path> | Effect.Effect<{
39
39
  skillSrcPath: string & import("effect/Brand").Brand<"AbsolutePath">;
40
40
  treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
41
- }, import("../index.ts").CanonicalPackageProbeFailed | import("../index.ts").ArchiveIntegrityMismatch | import("@agentxm/extension-resolution").AxmSkillCompatibilityUnavailable | import("@agentxm/extension-resolution").AxmSkillIncompatible | import("@agentxm/workspace-state").MaterializedTreeInvalid | import("@agentxm/registry-client").RegistryClientFailure | import("../extensions/canonical-directory.js").CanonicalDirectoryReplacementError, FileSystem.FileSystem | Path.Path | import("effect/unstable/http/HttpClient").HttpClient> | Effect.Effect<MaterializedSkillCanonical, import("@agentxm/workspace-state").PathTraversalDetected | import("@agentxm/extension-resolution").AxmSkillCompatibilityUnavailable | import("@agentxm/extension-resolution").AxmSkillIncompatible, FileSystem.FileSystem | Path.Path>;
41
+ }, import("../index.ts").CanonicalPackageProbeFailed | import("../index.ts").ArchiveIntegrityMismatch | import("@agentxm/cli-maintenance/official-skill/application").AxmSkillCompatibilityUnavailable | import("@agentxm/cli-maintenance/official-skill/domain").AxmSkillIncompatible | import("@agentxm/workspace-state").MaterializedTreeInvalid | import("@agentxm/registry-client").RegistryClientFailure | import("../extensions/canonical-directory.js").CanonicalDirectoryReplacementError, FileSystem.FileSystem | Path.Path | import("effect/unstable/http/HttpClient").HttpClient> | Effect.Effect<MaterializedSkillCanonical, import("@agentxm/workspace-state").PathTraversalDetected | import("@agentxm/cli-maintenance/official-skill/application").AxmSkillCompatibilityUnavailable | import("@agentxm/cli-maintenance/official-skill/domain").AxmSkillIncompatible, FileSystem.FileSystem | Path.Path>;
42
42
  export declare const ensureSkillAgentArtifact: (args: {
43
43
  readonly canonicalSkillSrcPath: string;
44
44
  readonly targetDir: string;
@@ -29,7 +29,11 @@ export declare const skillArtifactFromTargets: (args: {
29
29
  readonly change: JobStepArtifact["change"];
30
30
  readonly workspaceTargets?: ReadonlyArray<JobStepArtifactTarget>;
31
31
  }) => Effect.Effect<{
32
- targets?: JobStepArtifactTarget[];
32
+ targets?: (JobStepArtifactTarget | {
33
+ agentIds?: readonly string[];
34
+ path: string;
35
+ change: "created" | "updated" | "removed" | "unchanged";
36
+ })[];
33
37
  path: string;
34
38
  scope: "project" | "user";
35
39
  agents: readonly string[];
@@ -1,3 +1,5 @@
1
+ import { usableAcceptedCanonical } from "@agentxm/workspace-state";
2
+ import { LifecyclePostconditionViolated } from "../extensions/errors.js";
1
3
  /**
2
4
  * Subagent extension manager service.
3
5
  *
@@ -633,6 +635,31 @@ export const SubagentManagerLive = Layer.effect(SubagentManager, Effect.gen(func
633
635
  return yield* isObservedInstalled(ws, "subagent", target.name);
634
636
  }),
635
637
  materializeInstall,
638
+ acquireCanonical: ({ ref, force }) => Effect.gen(function* () {
639
+ const { sanitized, paths: { canonicalPath, subagentSrcPath }, } = getCanonicalPaths(ref);
640
+ const treeIntegrity = yield* materializeCanonical(ref, sanitized, canonicalPath, subagentSrcPath, force === true);
641
+ yield* readSubagentContent(subagentSrcPath, ref.subagent.name);
642
+ return {
643
+ sourceHash: Option.some(yield* computePackageContentHash(canonicalPath)),
644
+ treeIntegrity: Option.fromUndefinedOr(treeIntegrity),
645
+ observation: { agents: [], targets: [] },
646
+ };
647
+ }),
648
+ materializeRetained: ({ target }) => Effect.gen(function* () {
649
+ const canonical = yield* usableAcceptedCanonical({
650
+ workspace: ws,
651
+ type: "subagent",
652
+ name: target.name,
653
+ });
654
+ if (Option.isNone(canonical) || canonical.value.ref.type !== "subagent") {
655
+ return yield* new LifecyclePostconditionViolated({
656
+ postcondition: "materialize-observable",
657
+ targetType: "subagent",
658
+ targetName: target.name,
659
+ });
660
+ }
661
+ return yield* materializeInstall({ ref: canonical.value.ref });
662
+ }),
636
663
  prepareSourceTransition: ({ ref }) => prepareAcceptedCanonicalTransition({
637
664
  workspace: ws,
638
665
  type: "subagent",
@@ -651,7 +678,7 @@ export const SubagentManagerLive = Layer.effect(SubagentManager, Effect.gen(func
651
678
  }),
652
679
  materializeUninstall,
653
680
  materializeDeactivate,
654
- upsertSettingsEntry: Effect.fn("SubagentManager.upsertSettingsEntry")(function* ({ ref, versionRange, materialization, }) {
681
+ acceptedResolution: Effect.fn("SubagentManager.acceptedResolution")(function* ({ ref, materialization, }) {
655
682
  const workspaceRelativeLocalSourcePath = ref.refType === "local"
656
683
  ? makeWorkspaceRelativeSourcePath(path, baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
657
684
  : Option.none();
@@ -660,50 +687,8 @@ export const SubagentManagerLive = Layer.effect(SubagentManager, Effect.gen(func
660
687
  detail: `Local subagent source path must stay within the workspace root: ${ref.source.path}`,
661
688
  });
662
689
  }
663
- const state = acquiredState(materialization);
664
690
  if (ref.refType === "workspace") {
665
- return yield* ws.setSubagentEntry(ref.subagent.name, {
666
- source: "workspace",
667
- enabled: true,
668
- });
669
- }
670
- if (state === undefined) {
671
- return yield* new SubagentInstallStateMissing({
672
- name: ref.subagent.name,
673
- kind: "content-identity",
674
- });
675
- }
676
- const lockEntry = buildSubagentLockEntry(ref, state.sourceHash, state.treeIntegrity, workspaceRelativeLocalSourcePath);
677
- if (lockEntry === undefined) {
678
- return yield* new SubagentInstallStateMissing({
679
- name: ref.subagent.name,
680
- kind: "external-resolution",
681
- });
682
- }
683
- if (lockEntry.type === "registry") {
684
- yield* validateExactResolvedVersion(`subagents.${ref.subagent.name}.resolvedVersion`, lockEntry.resolvedVersion);
685
- }
686
- return yield* ws.setSubagent({
687
- name: ref.subagent.name,
688
- lockEntry,
689
- versionRange,
690
- });
691
- }),
692
- removeSettingsEntry: ({ target }) => ws
693
- .removeSubagentSettings(target.name)
694
- .pipe(Effect.withSpan("SubagentManager.removeSettingsEntry")),
695
- upsertLockfileEntry: Effect.fn("SubagentManager.upsertLockfileEntry")(function* ({ ref, materialization, }) {
696
- const workspaceRelativeLocalSourcePath = ref.refType === "local"
697
- ? makeWorkspaceRelativeSourcePath(path, baseDir, ref.sourcePath ?? stripFileProtocol(ref.location))
698
- : Option.none();
699
- if (ref.refType === "local" && Option.isNone(workspaceRelativeLocalSourcePath)) {
700
- return yield* new SubagentDefinitionInvalid({
701
- detail: `Local subagent source path must stay within the workspace root: ${ref.source.path}`,
702
- });
703
- }
704
- if (ref.refType === "workspace") {
705
- yield* ws.removeSubagentLock(ref.subagent.name);
706
- return;
691
+ return Option.none();
707
692
  }
708
693
  const state = acquiredState(materialization);
709
694
  if (state === undefined) {
@@ -722,15 +707,9 @@ export const SubagentManagerLive = Layer.effect(SubagentManager, Effect.gen(func
722
707
  if (lockEntry.type === "registry") {
723
708
  yield* validateExactResolvedVersion(`subagents.${ref.subagent.name}.resolvedVersion`, lockEntry.resolvedVersion);
724
709
  }
725
- return yield* ws.setSubagentLock({
726
- name: ref.subagent.name,
727
- lockEntry,
728
- versionRange: Option.none(),
729
- });
710
+ return Option.some({ key: ref.subagent.name, entry: lockEntry });
730
711
  }),
731
- removeLockfileEntry: ({ target }) => ws
732
- .removeSubagentLock(target.name)
733
- .pipe(Effect.withSpan("SubagentManager.removeLockfileEntry")),
712
+ withdrawnResolutionKeys: ({ target }) => Effect.succeed([target.name]),
734
713
  };
735
714
  }));
736
715
  //# sourceMappingURL=manager.js.map