@agentxm/extension-lifecycle 0.28.4 → 0.28.6

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.
@@ -122,7 +122,9 @@ export const resolveConfiguredRegistryEntry = (name, source, expectedType, relea
122
122
  detail: `Configured ${expectedType} "${name}" uses a ${pluralType} Registry source`,
123
123
  });
124
124
  }
125
- if (parsedPattern?.name !== undefined && parsedPattern.name !== name) {
125
+ if (expectedType !== "mcp-server" &&
126
+ parsedPattern?.name !== undefined &&
127
+ parsedPattern.name !== name) {
126
128
  return yield* new ExtensionLifecycleFailed({
127
129
  category: "validation",
128
130
  detail: `Configured ${expectedType} "${name}" points to Registry extension "${parsedPattern.name}"`,
@@ -136,13 +138,14 @@ export const resolveConfiguredRegistryEntry = (name, source, expectedType, relea
136
138
  });
137
139
  }
138
140
  const versionRange = Option.fromUndefinedOr(parsedPattern?.versionRange);
141
+ const registryName = expectedType === "mcp-server" ? (parsedPattern?.name ?? name) : name;
139
142
  const workspace = yield* WorkspaceMutations;
140
143
  const acceptedRef = yield* acceptedResolutionRef({
141
144
  workspace,
142
145
  type: expectedType,
143
146
  name,
144
147
  });
145
- const accepted = Option.flatMap(acceptedRef, (ref) => ref.refType === "registry" && ref.owner === owner && ref.name === name
148
+ const accepted = Option.flatMap(acceptedRef, (ref) => ref.refType === "registry" && ref.owner === owner && ref.name === registryName
146
149
  ? Option.some({
147
150
  version: ref.version,
148
151
  publisherBindingId: ref.publisherBindingId,
@@ -150,7 +153,7 @@ export const resolveConfiguredRegistryEntry = (name, source, expectedType, relea
150
153
  : Option.none());
151
154
  const providers = yield* SourceHostProviders;
152
155
  const resolution = yield* providers.resolveNamedRegistry(resolvedSource, {
153
- name,
156
+ name: registryName,
154
157
  type: expectedType,
155
158
  owner,
156
159
  versionRange,
@@ -11,7 +11,7 @@ import * as Option from "effect/Option";
11
11
  import * as Path from "effect/Path";
12
12
  import * as Schema from "effect/Schema";
13
13
  import * as Semaphore from "effect/Semaphore";
14
- import { HookConfigInvalid, HookDefinitionInvalid, HookInstallStateMissing, HookIoFailed, activeContributors, applyProjectionPlans, planAggregateProjection, runWithTransientFileBackup, reconcileManagedRegionFile, managedHookCommands, readManagedHookCommands, updateHooksJson, evaluateHookAgentOutcome, WriteBackupRetained, resolveInstructionsConfig, } from "@agentxm/extension-workspace";
14
+ import { HookConfigInvalid, HookDefinitionInvalid, HookInstallStateMissing, HookIoFailed, activeContributors, applyProjectionPlans, planAggregateProjection, runWithTransientFileBackup, reconcileManagedRegionFile, projectionGeneration, managedHookCommands, readManagedHookCommands, updateHooksJson, evaluateHookAgentOutcome, WriteBackupRetained, resolveInstructionsConfig, } from "@agentxm/extension-workspace";
15
15
  import { AGENTS as CAPABILITY_AGENTS, installable, } from "@agentxm/extension-model/unstable/agent-capabilities";
16
16
  import { computePackageContentHash } from "@agentxm/workspace-state";
17
17
  import { computeMaterializedTreeIntegrity } from "@agentxm/workspace-state";
@@ -437,12 +437,25 @@ export const HookManagerLive = Layer.effect(HookManager, Effect.gen(function* ()
437
437
  const rendered = input.contributors
438
438
  .map((hook) => `### ${hook.manifest.title ?? hook.name}\n\nFor agents without a usable native hook mapping (${hook.fallbackAgentIds.join(", ")}), treat this as a managed advisory rule. After the matching lifecycle event (${hook.manifest.bindings.map((binding) => binding.on).join(", ")}), run \`${hook.command}\` and address any findings before continuing.`)
439
439
  .join("\n\n");
440
+ const generation = projectionGeneration([
441
+ "hook-fallback-region-v1",
442
+ target.workspaceRelative,
443
+ HOOK_FALLBACKS_REGION_OWNER,
444
+ ...input.contributors.flatMap((contributor) => [
445
+ contributor.name,
446
+ contributor.marker,
447
+ contributor.command,
448
+ JSON.stringify(contributor.fallbackAgentIds),
449
+ JSON.stringify(contributor.manifest),
450
+ ]),
451
+ ]);
440
452
  const { changed, observedRegion } = yield* provide(reconcileManagedRegionFile({
441
453
  targetPath: target.targetPath,
442
454
  displayPath: target.workspaceRelative,
443
455
  region: HOOK_FALLBACKS_REGION,
444
456
  owner: HOOK_FALLBACKS_REGION_OWNER,
445
457
  rendered,
458
+ generation,
446
459
  ...(options?.dryRun === undefined ? {} : { dryRun: options.dryRun }),
447
460
  unsupportedTargetDetail: `Hook fallback target does not support managed regions: ${target.workspaceRelative}`,
448
461
  }));
@@ -464,12 +477,6 @@ export const HookManagerLive = Layer.effect(HookManager, Effect.gen(function* ()
464
477
  present: Option.isSome(observedRegion),
465
478
  current: !changed,
466
479
  expectedContributors: input.contributors.map(({ marker }) => marker),
467
- observedContributors: Option.match(observedRegion, {
468
- onNone: () => [],
469
- onSome: (region) => input.contributors
470
- .filter(({ command }) => region.includes(command))
471
- .map(({ marker }) => marker),
472
- }),
473
480
  },
474
481
  };
475
482
  });
@@ -532,11 +539,16 @@ export const HookManagerLive = Layer.effect(HookManager, Effect.gen(function* ()
532
539
  .map(({ agentId }) => agentId);
533
540
  return fallbackAgentIds.length === 0 ? [] : [{ ...contributor, fallbackAgentIds }];
534
541
  });
535
- const selectNative = (agentId) => () => Effect.succeed(contributors.filter((contributor) => outcomes.some((outcome) => outcome.name === contributor.name &&
536
- outcome.agentId === agentId &&
537
- outcome.mechanism === "native" &&
538
- (outcome.outcome === "projected" || outcome.outcome === "current"))));
539
- const selectFallback = () => Effect.succeed(fallbackContributors);
542
+ // Every reachable Hook contributor is decided from desired state
543
+ // alone, so hook units never exclude one.
544
+ const selectNative = (agentId) => () => Effect.succeed({
545
+ contributors: contributors.filter((contributor) => outcomes.some((outcome) => outcome.name === contributor.name &&
546
+ outcome.agentId === agentId &&
547
+ outcome.mechanism === "native" &&
548
+ (outcome.outcome === "projected" || outcome.outcome === "current"))),
549
+ exclusions: [],
550
+ });
551
+ const selectFallback = () => Effect.succeed({ contributors: fallbackContributors, exclusions: [] });
540
552
  const fallbackAgentIds = Array.from(new Set(fallbackContributors.flatMap(({ fallbackAgentIds }) => fallbackAgentIds)));
541
553
  const materialization = {
542
554
  agents: configuredAgents,
@@ -10,7 +10,6 @@
10
10
  */
11
11
  export { ExtensionLifecycleFailed } from "./errors.js";
12
12
  export { LifecycleFailureAdapter, withAdaptedStepFailures, type LifecycleFailureAdapterService, } from "./failure-adapter.js";
13
- export { LifecycleResolutionProgress, type LifecycleResolutionProgressService, } from "./resolution-progress.js";
14
13
  export { makeConfiguredReleaseAgeEvaluation, resolveConfiguredHook, resolveConfiguredKnowledge, resolveConfiguredMcpServer, resolveConfiguredPack, resolveConfiguredRegistryEntry, resolveConfiguredRule, resolveConfiguredSkill, resolveConfiguredSubagent, } from "./configured-entry-resolution.js";
15
14
  export { CONFIGURED_ENTRY_RESOLUTION_TIMEOUT, withConfiguredEntryResolutionTimeout, } from "./resolution-timeout.js";
16
15
  export { buildInstallCommandPlan, runInstallCommandWorkflow, type InstallExtensionCommandWorkflowActions, } from "./workflows/install-command/workflow.js";
@@ -20,10 +19,9 @@ export { ensureSkillAgentArtifact, removeSkillAgentArtifact } from "./skills/mat
20
19
  export { getSkillDisplayName } from "./skills/utils.js";
21
20
  export { computeSkillSourceHash, gitHostedSkillArtifactSource, installSkill, type InstallSkillOperation, type InstallSkillOperationArgs, } from "./skills/operations/install.js";
22
21
  export type { InstallResult } from "./skills/operations/install-result.js";
23
- export { uninstallSkill, type UninstallSkillOperation, type UninstallSkillOperationArgs, } from "./skills/operations/uninstall.js";
24
22
  export { enableSkill, type EnableSkillOperation } from "./skills/operations/enable.js";
25
23
  export { disableSkill, type DisableSkillOperation } from "./skills/operations/disable.js";
26
- export { installMcpServer, type InstallMcpServerOperation, type InstallMcpServerOperationArgs, } from "./mcps/operations/install.js";
24
+ export { collectSecretInputNames, deleteMcpSecrets, installMcpServer, mcpSecretAccount, readMcpServerManifest, type InstallMcpServerOperation, type InstallMcpServerOperationArgs, } from "./mcps/operations/install.js";
27
25
  export { uninstallMcpServer, type UninstallMcpServerOperation, type UninstallMcpServerOperationArgs, } from "./mcps/operations/uninstall.js";
28
26
  export { enableMcpServer, type EnableMcpServerOperation } from "./mcps/operations/enable.js";
29
27
  export { disableMcpServer, type DisableMcpServerOperation } from "./mcps/operations/disable.js";
package/dist/src/index.js CHANGED
@@ -10,7 +10,6 @@
10
10
  */
11
11
  export { ExtensionLifecycleFailed } from "./errors.js";
12
12
  export { LifecycleFailureAdapter, withAdaptedStepFailures, } from "./failure-adapter.js";
13
- export { LifecycleResolutionProgress, } from "./resolution-progress.js";
14
13
  export { makeConfiguredReleaseAgeEvaluation, resolveConfiguredHook, resolveConfiguredKnowledge, resolveConfiguredMcpServer, resolveConfiguredPack, resolveConfiguredRegistryEntry, resolveConfiguredRule, resolveConfiguredSkill, resolveConfiguredSubagent, } from "./configured-entry-resolution.js";
15
14
  export { CONFIGURED_ENTRY_RESOLUTION_TIMEOUT, withConfiguredEntryResolutionTimeout, } from "./resolution-timeout.js";
16
15
  export { buildInstallCommandPlan, runInstallCommandWorkflow, } from "./workflows/install-command/workflow.js";
@@ -20,11 +19,10 @@ export { materializeRegistryPackage, materializeRegistryPackageWithTreeIntegrity
20
19
  export { ensureSkillAgentArtifact, removeSkillAgentArtifact } from "./skills/materialization.js";
21
20
  export { getSkillDisplayName } from "./skills/utils.js";
22
21
  export { computeSkillSourceHash, gitHostedSkillArtifactSource, installSkill, } from "./skills/operations/install.js";
23
- export { uninstallSkill, } from "./skills/operations/uninstall.js";
24
22
  export { enableSkill } from "./skills/operations/enable.js";
25
23
  export { disableSkill } from "./skills/operations/disable.js";
26
24
  // MCP server lifecycle operations
27
- export { installMcpServer, } from "./mcps/operations/install.js";
25
+ export { collectSecretInputNames, deleteMcpSecrets, installMcpServer, mcpSecretAccount, readMcpServerManifest, } from "./mcps/operations/install.js";
28
26
  export { uninstallMcpServer, } from "./mcps/operations/uninstall.js";
29
27
  export { enableMcpServer } from "./mcps/operations/enable.js";
30
28
  export { disableMcpServer } from "./mcps/operations/disable.js";
@@ -11,7 +11,7 @@ import * as Schema from "effect/Schema";
11
11
  import * as Scope from "effect/Scope";
12
12
  import * as Result from "effect/Result";
13
13
  import { PlatformError } from "effect/PlatformError";
14
- import { KnowledgeDefinitionInvalid, KnowledgeDesiredStateUnreconcilable, KnowledgeInstallStateMissing, KnowledgeIoFailed, KnowledgeObservableContractViolated, KnowledgeResolutionMissing, KnowledgeUnavailable, applyProjectionPlans, planAggregateProjection, requireCompleteGraph, KNOWLEDGE_REGION_OWNER, reconcileKnowledgeDiscovery, observedKnowledgeContributors, resolveInstructionsConfig, } from "@agentxm/extension-workspace";
14
+ import { KnowledgeDefinitionInvalid, KnowledgeDesiredStateUnreconcilable, KnowledgeInstallStateMissing, KnowledgeIoFailed, KnowledgeObservableContractViolated, KnowledgeResolutionMissing, KnowledgeUnavailable, applyProjectionPlans, formatProjectionExclusions, planAggregateProjection, requireCompleteGraph, KNOWLEDGE_REGION_OWNER, reconcileKnowledgeDiscovery, resolveInstructionsConfig, } from "@agentxm/extension-workspace";
15
15
  import { canReuseInstalledPackage, materializeExternalPackage } from "@agentxm/extension-workspace";
16
16
  import { materializeRegistryPackage } from "../registry-materialization.js";
17
17
  import { computeExtensionPathsForLayout, extensionPathSourceFromLockEntry, } from "@agentxm/workspace-state";
@@ -299,21 +299,64 @@ export const KnowledgeManagerLive = Layer.effect(KnowledgeManager, Effect.gen(fu
299
299
  .filter(isSourcedDesiredExtension)
300
300
  .filter((node) => node.type === "knowledge" && node.enabled))
301
301
  : new KnowledgeDesiredStateUnreconcilable()));
302
+ /**
303
+ * Classify a bundle whose package could not be inspected. Only the
304
+ * bundle's own content is in question here: resolution facts have already
305
+ * succeeded, so the two truthful answers are that the package is gone or
306
+ * that its content is invalid.
307
+ */
308
+ const excludedContributor = (name, root, failure) => Effect.gen(function* () {
309
+ const probe = yield* Effect.result(fs.exists(path.join(root, KNOWLEDGE_MANIFEST_FILENAME)));
310
+ const manifestPresent = Result.isSuccess(probe) && probe.success;
311
+ if (!manifestPresent)
312
+ return { contributor: name, reason: "package-missing" };
313
+ return {
314
+ contributor: name,
315
+ reason: "package-invalid",
316
+ detail: failure._tag === "KnowledgeDefinitionInvalid"
317
+ ? failure.detail
318
+ : adapter.describeFailure(failure),
319
+ };
320
+ });
321
+ /**
322
+ * Resolve the contributor set for the discovery region. A bundle that
323
+ * fails inspection cannot supply a row, so it is excluded and reported
324
+ * rather than failing every other bundle's command with it. Resolution
325
+ * failures — a missing lock entry, an unsupported workspace-authored
326
+ * bundle — remain fail-closed: they are AXM state problems, not bundle
327
+ * content problems.
328
+ */
302
329
  const selectKnowledgeBundles = (graph, locked, configured, config, instructionFilesEnabled) => Effect.forEach(graph.nodes.filter((node) => node.type === "knowledge" && node.enabled), (node) => Effect.gen(function* () {
303
330
  const root = yield* desiredCanonicalRoot(node, locked[node.name]);
304
- const inspected = yield* inspectPackage(root);
305
331
  const workspaceInstructionEntry = configured[node.name]?.instructionEntry;
306
- const resolution = resolveKnowledgeInstructionEntry({
332
+ const resolveInclusion = (manifestInstructionEntry) => resolveKnowledgeInstructionEntry({
307
333
  bundleEnabled: node.enabled,
308
334
  instructionFilesEnabled,
309
335
  knowledgeInstructionsEnabled: config.instructions,
310
336
  ...(workspaceInstructionEntry === undefined ? {} : { workspaceInstructionEntry }),
311
- ...(inspected.manifest.instructionEntry === undefined
312
- ? {}
313
- : { manifestInstructionEntry: inspected.manifest.instructionEntry }),
337
+ ...(manifestInstructionEntry === undefined ? {} : { manifestInstructionEntry }),
314
338
  });
315
- return resolution.included ? [toProjectionBundle(root, inspected)] : [];
316
- })).pipe(Effect.map((bundles) => bundles.flat()));
339
+ const inspection = yield* Effect.result(inspectPackage(root));
340
+ if (Result.isFailure(inspection)) {
341
+ // A bundle the workspace would not publish anyway is simply
342
+ // absent from the region; reporting it would be noise.
343
+ return resolveInclusion().included
344
+ ? {
345
+ contributors: [],
346
+ exclusions: [yield* excludedContributor(node.name, root, inspection.failure)],
347
+ }
348
+ : { contributors: [], exclusions: [] };
349
+ }
350
+ const inspected = inspection.success;
351
+ const resolution = resolveInclusion(inspected.manifest.instructionEntry);
352
+ return {
353
+ contributors: resolution.included ? [toProjectionBundle(root, inspected)] : [],
354
+ exclusions: [],
355
+ };
356
+ })).pipe(Effect.map((selections) => ({
357
+ contributors: selections.flatMap(({ contributors }) => contributors),
358
+ exclusions: selections.flatMap(({ exclusions }) => exclusions),
359
+ })));
317
360
  const resolveKnowledgeProjection = () => Effect.gen(function* () {
318
361
  const graph = yield* ws.getDesiredStateGraph();
319
362
  const locked = yield* getKnowledgeLockEntries(ws);
@@ -351,10 +394,6 @@ export const KnowledgeManagerLive = Layer.effect(KnowledgeManager, Effect.gen(fu
351
394
  present: Option.isSome(result.observedRegion),
352
395
  current: !result.changed,
353
396
  expectedContributors: input.contributors.map(({ owner, name }) => `${owner}/knowledge/${name}`),
354
- observedContributors: Option.match(result.observedRegion, {
355
- onNone: () => [],
356
- onSome: observedKnowledgeContributors,
357
- }),
358
397
  }))),
359
398
  apply: (input) => runKnowledgeProjectionAdapter({
360
399
  bundles: input.contributors,
@@ -366,12 +405,18 @@ export const KnowledgeManagerLive = Layer.effect(KnowledgeManager, Effect.gen(fu
366
405
  });
367
406
  const projectionPlans = () => makeKnowledgeProjectionPlan().pipe(Effect.map((plan) => [plan]));
368
407
  const applyKnowledgeProjection = projectionPlans().pipe(Effect.flatMap(applyProjectionPlans));
369
- const reconcileDiscovery = (options) => resolveKnowledgeProjection().pipe(Effect.flatMap(({ graph, locked, configured, config, instructionsTarget }) => requireCompleteGraph(graph).pipe(Effect.flatMap((completeGraph) => selectKnowledgeBundles(completeGraph, locked, configured, config, instructionsTarget.enabled)), Effect.flatMap((bundles) => runKnowledgeProjectionAdapter({
370
- bundles,
408
+ const reconcileDiscovery = (options) => resolveKnowledgeProjection().pipe(Effect.flatMap(({ graph, locked, configured, config, instructionsTarget }) => requireCompleteGraph(graph).pipe(Effect.flatMap((completeGraph) => selectKnowledgeBundles(completeGraph, locked, configured, config, instructionsTarget.enabled)), Effect.flatMap(({ contributors, exclusions }) => runKnowledgeProjectionAdapter({
409
+ bundles: contributors,
371
410
  config,
372
411
  instructionsTarget,
373
412
  ...(options?.dryRun === undefined ? {} : { dryRun: options.dryRun }),
374
- })))));
413
+ }).pipe(Effect.map((result) => ({
414
+ ...result,
415
+ warnings: formatProjectionExclusions({
416
+ exclusions,
417
+ targetFile: instructionsTarget.path,
418
+ }),
419
+ })))))));
375
420
  const buildLockEntry = (ref) => Effect.gen(function* () {
376
421
  const state = lastInstallState.get(ref.knowledge.name);
377
422
  switch (ref.refType) {
@@ -491,7 +536,7 @@ export const KnowledgeManagerLive = Layer.effect(KnowledgeManager, Effect.gen(fu
491
536
  const discovery = discovered.success;
492
537
  return {
493
538
  changed: prepared.length > 0 || discovery.changed,
494
- warnings: [],
539
+ warnings: discovery.warnings,
495
540
  artifacts: discovery.artifacts,
496
541
  };
497
542
  });
@@ -15,7 +15,7 @@ import * as Option from "effect/Option";
15
15
  import { McpConfigIoFailed, McpInstallStateMissing, McpRegistryOnlyInstall, removeMcpServerFromManifest, applyProjectionPlans, planSingletonProjection, } from "@agentxm/extension-workspace";
16
16
  import { McpServerManager } from "@agentxm/extension-workspace";
17
17
  import { configuredMcpServersToDiskRefs } from "@agentxm/extension-workspace";
18
- import { WorkspaceMutations } from "@agentxm/workspace-state";
18
+ import { mcpRegistryResolutionKey, WorkspaceMutations } from "@agentxm/workspace-state";
19
19
  import { canReuseInstalledPackage } from "@agentxm/extension-workspace";
20
20
  import { materializeRegistryPackageWithTreeIntegrity } from "../registry-materialization.js";
21
21
  import { computeExtensionPathsForLayout } from "@agentxm/workspace-state";
@@ -55,6 +55,7 @@ export const McpServerManagerLive = Layer.effect(McpServerManager, Effect.gen(fu
55
55
  const fsPathLayer = Layer.mergeAll(Layer.succeed(FileSystem.FileSystem, fs), Layer.succeed(Path.Path, path), Layer.succeed(HttpClient.HttpClient, httpClient));
56
56
  const provide = (effect) => Effect.provide(effect, fsPathLayer);
57
57
  const lastTreeIntegrities = new Map();
58
+ const pendingRemoval = new Map();
58
59
  const materializeInstall = Effect.fn("McpServerManager.materializeInstall")(function* ({ ref, force }) {
59
60
  if (ref.refType !== "registry") {
60
61
  return yield* new McpRegistryOnlyInstall({
@@ -64,7 +65,11 @@ export const McpServerManagerLive = Layer.effect(McpServerManager, Effect.gen(fu
64
65
  }
65
66
  const registryRef = ref;
66
67
  const canonicalPath = computeExtensionPathsForLayout(path.join, ws.layout, registryRef, "mcps", registryRef.name).canonicalPath;
67
- const lockedEntry = yield* ws.getLockedMcpServer(registryRef.server.name);
68
+ const lockedEntry = yield* ws.getLockedMcpServer(mcpRegistryResolutionKey({
69
+ authority: registryRef.source.location,
70
+ owner: registryRef.owner,
71
+ name: registryRef.server.name,
72
+ }));
68
73
  const lockedVersion = acceptedRegistryVersionForRef(lockedEntry, registryRef);
69
74
  const useExisting = yield* provide(canReuseInstalledPackage({
70
75
  installedPath: canonicalPath,
@@ -97,6 +102,18 @@ export const McpServerManagerLive = Layer.effect(McpServerManager, Effect.gen(fu
97
102
  lastTreeIntegrities.set(registryRef.server.name, materialized.treeIntegrity);
98
103
  }, Effect.asVoid);
99
104
  const makeMaterializeRemoval = (retainCanonical) => Effect.fn("McpServerManager.materializeRemoval")(function* ({ target }) {
105
+ const graph = yield* ws.getDesiredStateGraph();
106
+ const desiredNode = graph.nodes.find((node) => node.type === "mcp-server" && node.name === target.name);
107
+ const closure = desiredNode === undefined || desiredNode.authority === "inline"
108
+ ? undefined
109
+ : graph.mcpSourceClosures.find((candidate) => candidate.identity === desiredNode.identity);
110
+ const retainShared = closure !== undefined && closure.localNames.some((name) => name !== target.name);
111
+ pendingRemoval.set(target.name, {
112
+ ...(desiredNode === undefined || desiredNode.authority === "inline"
113
+ ? {}
114
+ : { resolutionKey: desiredNode.identity }),
115
+ retainShared,
116
+ });
100
117
  const configuredAgents = yield* ws.getConfiguredAgents();
101
118
  yield* applyProjectionPlans(configuredAgents.map((agentId) => planSingletonProjection({
102
119
  unitId: "mcp-server:native-config-entry",
@@ -119,7 +136,7 @@ export const McpServerManagerLive = Layer.effect(McpServerManager, Effect.gen(fu
119
136
  })).pipe(Effect.asVoid),
120
137
  },
121
138
  })));
122
- if (retainCanonical)
139
+ if (retainCanonical || retainShared)
123
140
  return;
124
141
  const canonical = yield* provide(acceptedCanonicalObservation({
125
142
  workspace: ws,
@@ -178,6 +195,11 @@ export const McpServerManagerLive = Layer.effect(McpServerManager, Effect.gen(fu
178
195
  const lockEntry = buildMcpServerLockEntry(registryRef, treeIntegrity);
179
196
  return ws.setMcpServer({
180
197
  name: ref.server.name,
198
+ resolutionKey: mcpRegistryResolutionKey({
199
+ authority: registryRef.source.location,
200
+ owner: registryRef.owner,
201
+ name: registryRef.server.name,
202
+ }),
181
203
  lockEntry,
182
204
  versionRange,
183
205
  });
@@ -200,14 +222,26 @@ export const McpServerManagerLive = Layer.effect(McpServerManager, Effect.gen(fu
200
222
  const lockEntry = buildMcpServerLockEntry(registryRef, treeIntegrity);
201
223
  return ws.setMcpServerLock({
202
224
  name: ref.server.name,
225
+ resolutionKey: mcpRegistryResolutionKey({
226
+ authority: registryRef.source.location,
227
+ owner: registryRef.owner,
228
+ name: registryRef.server.name,
229
+ }),
203
230
  lockEntry,
204
231
  versionRange: Option.none(),
205
232
  });
206
233
  }), Effect.withSpan("McpServerManager.upsertLockfileEntry"));
207
234
  },
208
- removeLockfileEntry: ({ target }) => ws
209
- .removeMcpServerLock(target.name)
210
- .pipe(Effect.withSpan("McpServerManager.removeLockfileEntry")),
235
+ removeLockfileEntry: ({ target }) => {
236
+ const removal = pendingRemoval.get(target.name);
237
+ pendingRemoval.delete(target.name);
238
+ if (removal?.retainShared === true || removal?.resolutionKey === undefined) {
239
+ return Effect.void.pipe(Effect.withSpan("McpServerManager.removeLockfileEntry"));
240
+ }
241
+ return ws
242
+ .removeMcpServerLock(removal.resolutionKey)
243
+ .pipe(Effect.withSpan("McpServerManager.removeLockfileEntry"));
244
+ },
211
245
  };
212
246
  }));
213
247
  //# sourceMappingURL=manager.js.map
@@ -6,12 +6,11 @@
6
6
  import * as Effect from "effect/Effect";
7
7
  import * as FileSystem from "effect/FileSystem";
8
8
  import * as Path from "effect/Path";
9
- import { CodingAgentRepository, inspectAgentMcpServer, sharedMcpTargetPolicyConflict, } from "@agentxm/extension-workspace";
9
+ import { CodingAgentRepository } from "@agentxm/extension-workspace";
10
10
  import { appendWarningsToMessage } from "@agentxm/workspace-operations";
11
11
  import { WorkspaceMutations } from "@agentxm/workspace-state";
12
12
  import { agentConfigTargets, mcpServerArtifact, mcpSettingsTarget } from "./artifact.js";
13
13
  import { mcpSyncWarnings, requireSuccessfulMcpSync } from "./sync-outcome.js";
14
- import { isMcpServerApplicableToAgent } from "@agentxm/workspace-state";
15
14
  import { LifecycleFailureAdapter, withAdaptedStepFailures } from "../../failure-adapter.js";
16
15
  import { ExtensionLifecycleFailed } from "../../errors.js";
17
16
  export const disableMcpServer = (op) => Effect.gen(function* () {
@@ -28,48 +27,13 @@ export const disableMcpServer = (op) => Effect.gen(function* () {
28
27
  });
29
28
  }
30
29
  const agents = yield* agentRepo.getConfiguredAgents();
31
- const sharedTargetConflict = sharedMcpTargetPolicyConflict({
32
- entry,
33
- agentIds: agents.map((agent) => agent.id),
34
- scope: ws.scope,
35
- });
36
- if (sharedTargetConflict !== undefined) {
37
- return yield* new ExtensionLifecycleFailed({
38
- category: "conflict",
39
- detail: sharedTargetConflict,
40
- });
41
- }
42
30
  const outcomes = yield* ws.runTransaction({
43
31
  transition: Effect.gen(function* () {
44
- const synced = yield* Effect.forEach(agents, (agent) => Effect.gen(function* () {
45
- if (isMcpServerApplicableToAgent(entry, agent.id)) {
46
- return yield* agent.removeMcpServer({
47
- workspaceRoot: ws.baseDir,
48
- scope: ws.scope,
49
- serverName: op.args.serverName,
50
- disableOnly: true,
51
- });
52
- }
53
- const inspection = yield* inspectAgentMcpServer({
54
- workspaceRoot: ws.baseDir,
55
- scope: ws.scope,
56
- agentId: agent.id,
57
- serverName: op.args.serverName,
58
- entry,
59
- });
60
- if (inspection.status === "unmanaged") {
61
- return yield* new ExtensionLifecycleFailed({
62
- category: "conflict",
63
- detail: `${agent.id} has an unmanaged MCP server named ${op.args.serverName}; AXM will not remove it while applying the target policy`,
64
- });
65
- }
66
- return inspection.status === "drift"
67
- ? yield* agent.removeMcpServer({
68
- workspaceRoot: ws.baseDir,
69
- scope: ws.scope,
70
- serverName: op.args.serverName,
71
- })
72
- : { _tag: "success", targets: [] };
32
+ const synced = yield* Effect.forEach(agents, (agent) => agent.removeMcpServer({
33
+ workspaceRoot: ws.baseDir,
34
+ scope: ws.scope,
35
+ serverName: op.args.serverName,
36
+ disableOnly: true,
73
37
  }), { concurrency: "unbounded" });
74
38
  yield* requireSuccessfulMcpSync(op.args.serverName, agents.map((agent, index) => ({
75
39
  agentId: agent.id,
@@ -7,14 +7,13 @@ import * as FileSystem from "effect/FileSystem";
7
7
  import * as Path from "effect/Path";
8
8
  import * as Effect from "effect/Effect";
9
9
  import * as Option from "effect/Option";
10
- import { CodingAgentRepository, syncInlineMcpServerToAgents, McpSharedTargetConflict, applyProjectionPlansWithResults, planSingletonProjection, inspectAgentMcpServer, sharedMcpTargetPolicyConflict, } from "@agentxm/extension-workspace";
10
+ import { CodingAgentRepository, syncInlineMcpServerToAgents, applyProjectionPlansWithResults, planSingletonProjection, } from "@agentxm/extension-workspace";
11
11
  import { normalizeHandle, parseExtensionFqnParts, } from "@agentxm/extension-model/unstable/extensions";
12
12
  import { appendWarningsToMessage } from "@agentxm/workspace-operations";
13
13
  import { WorkspaceMutations } from "@agentxm/workspace-state";
14
14
  import { agentConfigTargets, mcpServerArtifact, mcpSettingsTarget } from "./artifact.js";
15
15
  import { usableAcceptedCanonicalObservation } from "@agentxm/workspace-state";
16
16
  import { mcpSyncWarnings, requireSuccessfulMcpSync } from "./sync-outcome.js";
17
- import { isMcpServerApplicableToAgent } from "@agentxm/workspace-state";
18
17
  import { LifecycleFailureAdapter, withAdaptedStepFailures } from "../../failure-adapter.js";
19
18
  import { ExtensionLifecycleFailed } from "../../errors.js";
20
19
  const enableArtifact = (args) => {
@@ -116,17 +115,6 @@ export const enableMcpServer = (op) => Effect.gen(function* () {
116
115
  const owner = trustedIdentity?.type === "mcp-server" ? trustedIdentity.owner : normalizeHandle("@local");
117
116
  const resolvedVersion = accepted?.type === "registry" ? accepted.resolvedVersion : "0.0.0";
118
117
  const agents = yield* agentRepo.getConfiguredAgents();
119
- const sharedTargetConflict = sharedMcpTargetPolicyConflict({
120
- entry,
121
- agentIds: agents.map((agent) => agent.id),
122
- scope: ws.scope,
123
- });
124
- if (sharedTargetConflict !== undefined) {
125
- return yield* new ExtensionLifecycleFailed({
126
- category: "conflict",
127
- detail: sharedTargetConflict,
128
- });
129
- }
130
118
  const outcomes = yield* ws.runTransaction({
131
119
  transition: Effect.gen(function* () {
132
120
  const synced = yield* applyProjectionPlansWithResults(agents.map((agent) => planSingletonProjection({
@@ -143,27 +131,6 @@ export const enableMcpServer = (op) => Effect.gen(function* () {
143
131
  observedContributors: [],
144
132
  }),
145
133
  apply: () => Effect.gen(function* () {
146
- if (!isMcpServerApplicableToAgent(entry, agent.id)) {
147
- const inspection = yield* inspectAgentMcpServer({
148
- workspaceRoot: ws.baseDir,
149
- scope: ws.scope,
150
- agentId: agent.id,
151
- serverName: op.args.serverName,
152
- entry,
153
- });
154
- if (inspection.status === "unmanaged") {
155
- return yield* new McpSharedTargetConflict({
156
- reason: `${agent.id} has an unmanaged MCP server named ${op.args.serverName}; AXM will not remove it while applying the target policy`,
157
- });
158
- }
159
- return inspection.status === "drift"
160
- ? yield* agent.removeMcpServer({
161
- workspaceRoot: ws.baseDir,
162
- scope: ws.scope,
163
- serverName: op.args.serverName,
164
- })
165
- : { _tag: "success", targets: [] };
166
- }
167
134
  return yield* agent.addMcpServer({
168
135
  workspaceRoot: ws.baseDir,
169
136
  scope: ws.scope,
@@ -12,17 +12,19 @@ import * as Path from "effect/Path";
12
12
  import * as Effect from "effect/Effect";
13
13
  import * as Option from "effect/Option";
14
14
  import { CodingAgentRepository } from "@agentxm/extension-workspace";
15
- import type { ConfigurableAgentId } from "@agentxm/extension-model/unstable/agent-capabilities";
16
15
  import type { StepFailure } from "@agentxm/workspace-operations";
17
16
  import type { JobStepResult, Operation } from "@agentxm/workspace-operations";
18
17
  import { WorkspaceMutations } from "@agentxm/workspace-state";
19
18
  import type { McpServerExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/mcp-server";
19
+ import { type McpServerManifest } from "@agentxm/extension-model/unstable/mcps/manifest-schema";
20
20
  import { LifecycleFailureAdapter } from "../../failure-adapter.js";
21
21
  /**
22
22
  * Args for the install-mcp-server operation.
23
23
  */
24
24
  export type InstallMcpServerOperationArgs = {
25
25
  readonly ref: McpServerExtensionRef;
26
+ /** Local connection identity and exact agent-native MCP key. */
27
+ readonly localName?: string;
26
28
  readonly force: boolean;
27
29
  /** Explicitly permit a workspace-authored relocation during reconciliation. */
28
30
  readonly allowWorkspaceSourceTransition?: boolean;
@@ -40,8 +42,6 @@ export type InstallMcpServerOperationArgs = {
40
42
  * The transport boundary resolves flag, CI, and TTY state.
41
43
  */
42
44
  readonly nonInteractive: boolean;
43
- /** Restrict this server to a reviewed subset of configured agents. */
44
- readonly agents?: ReadonlyArray<ConfigurableAgentId>;
45
45
  };
46
46
  /**
47
47
  * Add an MCP server to the workspace.
@@ -49,6 +49,25 @@ export type InstallMcpServerOperationArgs = {
49
49
  * @experimental This API is unstable and may change without notice.
50
50
  */
51
51
  export type InstallMcpServerOperation = Operation<"install-mcp-server", InstallMcpServerOperationArgs>;
52
+ export declare const mcpSecretAccount: (args: {
53
+ readonly scopeRoot: string;
54
+ readonly localName: string;
55
+ readonly sourceIdentity: string;
56
+ readonly inputName: string;
57
+ }) => string;
58
+ export type McpSecretDeletionOutcome = {
59
+ readonly _tag: "deleted";
60
+ readonly inputName: string;
61
+ } | {
62
+ readonly _tag: "absent";
63
+ readonly inputName: string;
64
+ } | {
65
+ readonly _tag: "failed";
66
+ readonly inputName: string;
67
+ };
68
+ export declare const collectSecretInputNames: (manifest: McpServerManifest) => ReadonlySet<string>;
69
+ export declare const readMcpServerManifest: (canonicalPath: string) => Effect.Effect<Option.Option<McpServerManifest>, never, FileSystem.FileSystem | Path.Path>;
70
+ export declare const deleteMcpSecrets: (identity: Omit<Parameters<typeof mcpSecretAccount>[0], "inputName">, secretNames: ReadonlySet<string>) => Effect.Effect<ReadonlyArray<McpSecretDeletionOutcome>>;
52
71
  /**
53
72
  * Install-mcp-server operation handler.
54
73
  *