@agentxm/workspace-state 0.28.4-bootstrap.0 → 0.28.5

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.
@@ -54,6 +54,7 @@ export { hookLockEntryToRef, knowledgeLockEntryToRef, mcpServerLockEntryToRef, p
54
54
  export { lockEntryToSourceParams, printSkillLockSourceLocator, } from "./workspace/lock-entry-to-source-params.js";
55
55
  export { sourceToLockEntry, type SourceToLockEntryInput, } from "./workspace/source-to-lock-entry.js";
56
56
  export { deriveSourceMetaFromLockType, type SourceMeta } from "./workspace/source-metadata.js";
57
+ export { mcpRegistryResolutionKey, mcpResolutionKey } from "./workspace/mcp-source-identity.js";
57
58
  export { READ_MODEL_EXTENSION_FAMILY_BY_TYPE, makeWorkspaceReadModel, WorkspaceReadModelConfig, type RawSourceBytes, type ScopedOwnerApi, type ScopedSourceHostsApi, type ScopedStateApi, type WorkspaceReadModel, type WorkspaceReadModelConfigService, } from "./workspace/read-model/service.js";
58
59
  export { AgentRootResolver, AgentRootResolverLive, } from "./workspace/read-model/agent-root-resolver.js";
59
60
  export { ExtensionInventoryClassificationSchema, ExtensionInventoryLifecycleSchema, ExtensionInventoryRowSchema, ExtensionInventorySchema, projectExtensionInventory, type ExtensionInventory, type ExtensionInventoryClassification, type ExtensionInventoryLifecycle, type ExtensionInventoryObservation, type ExtensionInventoryRow, type LifecycleInventoryCandidate, type ProjectExtensionInventoryInput, } from "./workspace/read-model/extensions/inventory.js";
@@ -62,7 +63,7 @@ export type { ActualMcpServer, ActualPack, ActualSkill, ActualSubagent, Installe
62
63
  export type { FileSpec, FixtureSpec, ScopeFiles, TreeFiles, } from "./workspace/read-model/__fixtures__/builder.js";
63
64
  export { WorkspaceMutations, type MakeWorkspaceTransactionCapabilities, type WorkspaceMutationsService, type WorkspaceMutationsError, type WorkspaceMutationsOptions, type WorkspaceSettingsReadFailure, type WorkspaceLockfileReadFailure, type WorkspaceStateReadFailure, type WorkspaceSettingsMutationFailure, type WorkspaceLockfileMutationFailure, type WorkspaceStateMutationFailure, type SetSkillArgs, type SetPackArgs, type SetMcpServerArgs, type SetSubagentArgs, type PackDirPath, type ExtensionTarget, type ExtensionTargetFor, type LockfileState, type WorkspaceTransactionRunner, type WorkspaceTransactionCapabilities, type WorkspaceTransitionAcquirer, type WorkspaceTransitionRequest, type WorkspaceLifecycleTransactionArgs, type SkillExtensionTarget, type PackExtensionTarget, type McpServerExtensionTarget, type SubagentExtensionTarget, type RuleExtensionTarget, type HookExtensionTarget, type KnowledgeExtensionTarget, } from "./workspace/service-interface.js";
64
65
  export { makeWorkspaceMutations, type WorkspaceLayerOptions } from "./workspace/service.js";
65
- export { LockfileDecodeError, LockfileIoError, LockfileParseError, SettingsDecodeError, SettingsIoError, SettingsParseError, SkillDiscoveryRootInvalid, SubagentScanFailed, WorkspaceRootEscape, type LockfileReadError, type SettingsReadError, } from "./workspace/read-model/errors.js";
66
+ export { LockfileDecodeError, LockfileIoError, LockfileParseError, LockfileVersionUnsupported, SettingsDecodeError, SettingsIoError, SettingsParseError, SkillDiscoveryRootInvalid, SubagentScanFailed, WorkspaceRootEscape, type LockfileReadError, type SettingsReadError, } from "./workspace/read-model/errors.js";
66
67
  export { AcceptedResolutionMissing, CanonicalPathRemovalError, DesiredPackGraphIncomplete, InlineExtensionSourceMissing, InvalidAgentId, LockedSkillMissing, LockEntryEndpointConflict, LockEntryNameInvalid, LockEntrySourceMissing, LockEntrySourceTypeConflict, LockEntryUrlInvalid, PackageContentHashFailed, SettingsEntryMissing, SupersededCanonicalRemovalFailed, SymlinkCreationError, WorkspaceLayoutError, WorkspaceNotInitialized, WorkspaceSourceInvalid, } from "./workspace/errors.js";
67
68
  export { CurrentWorkspaceClosure, CurrentWorkspaceTransaction, protectCreatedAncestors, protectInContext, protectWorkspacePath, readPendingClosureRestorationFailures, type Snapshot, type WorkspaceTransactionContext, TransitionLockError, TransitionLockUnavailable, WorkspaceDirectoryError, WorkspaceRestorationError, WorkspaceRestorationIncomplete, WorkspaceSnapshotError, WorkspaceTransitionCompromised, type TransitionContention, type TransitionLockHolder, type WorkspaceTransactionFailure, type WorkspaceTransitionAcquireFailure, } from "./workspace/transaction.js";
68
69
  export { setupScopeSupport, setupScopeSupportOutcomes, type SetupScopeSupportCategory, type SetupScopeSupportOutcome, type SetupScopeSupportReasonCode, type SetupScopeSupportStatus, } from "./workspace/setup-scope-support.js";
package/dist/src/index.js CHANGED
@@ -67,6 +67,7 @@ export { lockEntryToSourceParams, printSkillLockSourceLocator, } from "./workspa
67
67
  export { sourceToLockEntry, } from "./workspace/source-to-lock-entry.js";
68
68
  // Source metadata
69
69
  export { deriveSourceMetaFromLockType } from "./workspace/source-metadata.js";
70
+ export { mcpRegistryResolutionKey, mcpResolutionKey } from "./workspace/mcp-source-identity.js";
70
71
  // Workspace read model
71
72
  export { READ_MODEL_EXTENSION_FAMILY_BY_TYPE, makeWorkspaceReadModel, WorkspaceReadModelConfig, } from "./workspace/read-model/service.js";
72
73
  export { AgentRootResolver, AgentRootResolverLive, } from "./workspace/read-model/agent-root-resolver.js";
@@ -76,7 +77,7 @@ export { getPriorityDirectories, parsePluginManifests, scanAgentSubagentFiles, s
76
77
  export { WorkspaceMutations, } from "./workspace/service-interface.js";
77
78
  export { makeWorkspaceMutations } from "./workspace/service.js";
78
79
  // Read-model per-source typed failure families
79
- export { LockfileDecodeError, LockfileIoError, LockfileParseError, SettingsDecodeError, SettingsIoError, SettingsParseError, SkillDiscoveryRootInvalid, SubagentScanFailed, WorkspaceRootEscape, } from "./workspace/read-model/errors.js";
80
+ export { LockfileDecodeError, LockfileIoError, LockfileParseError, LockfileVersionUnsupported, SettingsDecodeError, SettingsIoError, SettingsParseError, SkillDiscoveryRootInvalid, SubagentScanFailed, WorkspaceRootEscape, } from "./workspace/read-model/errors.js";
80
81
  // Workspace-state typed failure families
81
82
  export { AcceptedResolutionMissing, CanonicalPathRemovalError, DesiredPackGraphIncomplete, InlineExtensionSourceMissing, InvalidAgentId, LockedSkillMissing, LockEntryEndpointConflict, LockEntryNameInvalid, LockEntrySourceMissing, LockEntrySourceTypeConflict, LockEntryUrlInvalid, PackageContentHashFailed, SettingsEntryMissing, SupersededCanonicalRemovalFailed, SymlinkCreationError, WorkspaceLayoutError, WorkspaceNotInitialized, WorkspaceSourceInvalid, } from "./workspace/errors.js";
82
83
  // Workspace transaction registration (the WS half of the transaction seam)
@@ -173,7 +173,7 @@ export declare const commitLockfileUpdates: (axmDir: string, lockfile: Lockfile,
173
173
  readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
174
174
  };
175
175
  };
176
- readonly lockfileVersion: 6;
176
+ readonly lockfileVersion: 7;
177
177
  readonly knowledge?: {
178
178
  readonly [x: string]: {
179
179
  readonly type: "github";
@@ -913,7 +913,7 @@ export declare const commitLockfileSnapshotUpdateAtPath: (lockfilePath: string,
913
913
  readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
914
914
  };
915
915
  };
916
- readonly lockfileVersion: 6;
916
+ readonly lockfileVersion: 7;
917
917
  readonly knowledge?: {
918
918
  readonly [x: string]: {
919
919
  readonly type: "github";
@@ -1642,7 +1642,7 @@ export declare const commitLockfileSnapshotUpdate: (axmDir: string, base: Lockfi
1642
1642
  readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1643
1643
  };
1644
1644
  };
1645
- readonly lockfileVersion: 6;
1645
+ readonly lockfileVersion: 7;
1646
1646
  readonly knowledge?: {
1647
1647
  readonly [x: string]: {
1648
1648
  readonly type: "github";
@@ -4,13 +4,13 @@
4
4
  * The lockfile (axm-lock.yaml) records accepted immutable resolutions for
5
5
  * externally sourced extensions.
6
6
  *
7
- * Lockfile v6 is authority, not receipt history. It contains no authored,
7
+ * Lockfile v7 is authority, not receipt history. It contains no authored,
8
8
  * bundled, inline, projection, completion-time, or command-history state.
9
9
  *
10
10
  * @experimental This API is unstable and may change without notice.
11
11
  */
12
12
  import * as Schema from "effect/Schema";
13
- export declare const LOCKFILE_VERSION = 6;
13
+ export declare const LOCKFILE_VERSION = 7;
14
14
  /**
15
15
  * Lock entry for a single installed skill.
16
16
  * Discriminated union by the `type` field.
@@ -667,7 +667,8 @@ export declare const McpServerLockEntrySchema: Schema.Union<readonly [Schema.Str
667
667
  */
668
668
  export type McpServerLockEntry = Schema.Schema.Type<typeof McpServerLockEntrySchema>;
669
669
  /**
670
- * Map of MCP server names to their lock entries.
670
+ * Map of canonical MCP source-resolution identities to their lock entries.
671
+ * Local connection names live only in settings and native projections.
671
672
  *
672
673
  * @experimental This API is unstable and may change without notice.
673
674
  */
@@ -2339,7 +2340,7 @@ export declare const LOCK_ENTRY_SCHEMA_BY_TYPE: {
2339
2340
  }>]>;
2340
2341
  };
2341
2342
  export declare const LockfileSchema: Schema.Struct<{
2342
- readonly lockfileVersion: Schema.Literal<6>;
2343
+ readonly lockfileVersion: Schema.Literal<7>;
2343
2344
  readonly skills: Schema.$Record<Schema.String, Schema.Union<readonly [Schema.Struct<{
2344
2345
  readonly type: Schema.Literal<"github">;
2345
2346
  readonly sourceType: Schema.Literal<"github">;
@@ -4,7 +4,7 @@
4
4
  * The lockfile (axm-lock.yaml) records accepted immutable resolutions for
5
5
  * externally sourced extensions.
6
6
  *
7
- * Lockfile v6 is authority, not receipt history. It contains no authored,
7
+ * Lockfile v7 is authority, not receipt history. It contains no authored,
8
8
  * bundled, inline, projection, completion-time, or command-history state.
9
9
  *
10
10
  * @experimental This API is unstable and may change without notice.
@@ -16,7 +16,7 @@ import { TreeIntegritySchema } from "../workspace/materialized-tree.js";
16
16
  import { ExtensionNameSchema } from "@agentxm/extension-model/unstable/extensions/common";
17
17
  import { VersionSchema } from "@agentxm/extension-model/unstable/version-constraints";
18
18
  import { SourceNamespaceSchema, SourceRefSchema, SourceSegmentSchema, SourceSubPathSchema, } from "@agentxm/extension-model/unstable/sources/types";
19
- export const LOCKFILE_VERSION = 6;
19
+ export const LOCKFILE_VERSION = 7;
20
20
  // =============================================================================
21
21
  // Flat Source Schemas (discriminated by type field)
22
22
  // =============================================================================
@@ -234,7 +234,8 @@ export const McpServerLockEntrySchema = makeSourceLockUnion("mcp-server", Handle
234
234
  // MCP Servers Lock Map
235
235
  // =============================================================================
236
236
  /**
237
- * Map of MCP server names to their lock entries.
237
+ * Map of canonical MCP source-resolution identities to their lock entries.
238
+ * Local connection names live only in settings and native projections.
238
239
  *
239
240
  * @experimental This API is unstable and may change without notice.
240
241
  */
@@ -379,7 +380,7 @@ export const LOCK_ENTRY_SCHEMA_BY_TYPE = {
379
380
  * enabling reproducible installations across environments.
380
381
  *
381
382
  * Structure:
382
- * - lockfileVersion: Schema version (currently 6)
383
+ * - lockfileVersion: Schema version (currently 7)
383
384
  * - skills: Map of skill names to their lock entries
384
385
  * - packs: Map of pack names to their lock entries (optional)
385
386
  *
@@ -9,7 +9,7 @@ import type { WorkspaceSnapshotError } from "./transaction.js";
9
9
  import { type LockEntryToRefError } from "./lock-entry-to-ref.js";
10
10
  import { type AcceptedExtensionResolution, type CanonicalObservation } from "./canonical-observation.js";
11
11
  import type { DesiredExtensionNode } from "./desired-state-graph.js";
12
- import type { WorkspaceLockfileReadFailure, WorkspaceMutationsService, WorkspaceStateReadFailure } from "./service-interface.js";
12
+ import type { WorkspaceMutationsService, WorkspaceStateReadFailure } from "./service-interface.js";
13
13
  interface AcceptedCanonicalRefArgs {
14
14
  readonly workspace: WorkspaceMutationsService;
15
15
  readonly type: DesiredExtensionNode["type"];
@@ -34,7 +34,7 @@ export interface UsableAcceptedCanonical extends UsableAcceptedCanonicalObservat
34
34
  /** Canonical package AXM may delete when removing desired state. Authored source is durable. */
35
35
  export declare const removableAcceptedCanonicalPath: (canonical: Option.Option<AcceptedCanonicalObservation>) => Option.Option<string>;
36
36
  /** Exact acquired canonical path reconstructed directly from accepted lock authority. */
37
- export declare const acceptedLockedCanonicalPath: (args: AcceptedCanonicalRefArgs) => Effect.Effect<Option.Option<string>, WorkspaceLockfileReadFailure, Path.Path>;
37
+ export declare const acceptedLockedCanonicalPath: (args: AcceptedCanonicalRefArgs) => Effect.Effect<Option.Option<string>, WorkspaceStateReadFailure, Path.Path>;
38
38
  /**
39
39
  * Capture exact cleanup for a superseded accepted package. The old path is
40
40
  * read before the lock transition; the returned effect runs afterward and
@@ -42,7 +42,7 @@ export declare const acceptedLockedCanonicalPath: (args: AcceptedCanonicalRefArg
42
42
  */
43
43
  export declare const prepareAcceptedCanonicalTransition: (args: AcceptedCanonicalRefArgs & {
44
44
  readonly ref: ExtensionRef;
45
- }) => Effect.Effect<Effect.Effect<void, WorkspaceSnapshotError | SupersededCanonicalRemovalFailed>, WorkspaceLockfileReadFailure, FileSystem.FileSystem | Path.Path>;
45
+ }) => Effect.Effect<Effect.Effect<void, WorkspaceSnapshotError | SupersededCanonicalRemovalFailed>, WorkspaceStateReadFailure, FileSystem.FileSystem | Path.Path>;
46
46
  /** Reconstruct a ref directly from accepted lock authority without desired reachability. */
47
47
  export declare const acceptedLockedResolutionRef: (args: AcceptedCanonicalRefArgs) => Effect.Effect<Option.Option<ExtensionRef>, AcceptedCanonicalRefError, FileSystem.FileSystem | Path.Path>;
48
48
  /**
@@ -21,7 +21,7 @@ const getAcceptedResolution = (workspace, type, name) => {
21
21
  case "skill":
22
22
  return workspace.getLockedSkill(name);
23
23
  case "mcp-server":
24
- return workspace.getLockedMcpServer(name);
24
+ return workspace.getLockedMcpServerForConnection(name);
25
25
  case "subagent":
26
26
  return workspace.getLockedSubagent(name);
27
27
  case "rule":
@@ -101,7 +101,7 @@ const refFromAcceptedResolution = (workspace, type, name) => Effect.gen(function
101
101
  });
102
102
  }
103
103
  case "mcp-server": {
104
- const entry = yield* workspace.getLockedMcpServer(name);
104
+ const entry = yield* workspace.getLockedMcpServerForConnection(name);
105
105
  return yield* Option.match(entry, {
106
106
  onNone: () => Effect.fail(missingAccepted("MCP", name)),
107
107
  onSome: (value) => mcpServerLockEntryToRef(name, value, deps),
@@ -19,6 +19,7 @@ import { printSourceParams } from "@agentxm/extension-model/unstable/sources/pri
19
19
  import { lockEntryToSourceParams } from "./lock-entry-to-source-params.js";
20
20
  import { collectDesiredConstraintContributors, } from "./desired-state-graph.js";
21
21
  import { computeExtensionPathsForLayout, extensionPathSourceFromLockEntry, } from "./extension-paths.js";
22
+ import { mcpResolutionKey } from "./mcp-source-identity.js";
22
23
  const MANIFEST_CONTRACTS = {
23
24
  skill: { filename: "skill.json", schema: SkillManifestSchema },
24
25
  "mcp-server": { filename: "mcp.json", schema: McpServerManifestSchema },
@@ -100,11 +101,13 @@ export const observeCanonicalExtension = ({ layout, desired, accepted, }) => Eff
100
101
  status: "missing-resolution",
101
102
  };
102
103
  }
103
- const acceptedIdentity = accepted?.type === "registry"
104
- ? `${accepted.owner}/${toExtensionTypePlural(desired.type)}/${accepted.name}`
105
- : accepted === undefined
106
- ? undefined
107
- : printSourceParams(lockEntryToSourceParams(accepted));
104
+ const acceptedIdentity = accepted?.extensionType === "mcp-server"
105
+ ? mcpResolutionKey(accepted)
106
+ : accepted?.type === "registry"
107
+ ? `${accepted.owner}/${toExtensionTypePlural(desired.type)}/${accepted.name}`
108
+ : accepted === undefined
109
+ ? undefined
110
+ : printSourceParams(lockEntryToSourceParams(accepted));
108
111
  if (!workspaceAuthored &&
109
112
  !bundled &&
110
113
  acceptedIdentity !== desired.identity &&
@@ -7,12 +7,14 @@ import type { Settings } from "../settings/index.js";
7
7
  import { type WorkspaceLayout } from "./layout.js";
8
8
  export type DesiredExtensionOrigin = {
9
9
  readonly type: "settings";
10
+ readonly localName?: string;
10
11
  readonly authority?: "sourced";
11
12
  readonly source: string;
12
13
  readonly enabled: boolean;
13
14
  readonly constraint?: string;
14
15
  } | {
15
16
  readonly type: "settings";
17
+ readonly localName?: string;
16
18
  readonly authority: "inline";
17
19
  readonly source?: undefined;
18
20
  readonly constraint?: undefined;
@@ -52,6 +54,7 @@ export interface DesiredConstraintContributor {
52
54
  readonly range: string;
53
55
  readonly location: string;
54
56
  readonly dependingPack?: string;
57
+ readonly localName?: string;
55
58
  }
56
59
  export type DesiredStateProblem = {
57
60
  readonly type: "workspace-owner-missing";
@@ -98,8 +101,15 @@ export type DesiredStateProblem = {
98
101
  export interface DesiredStateGraph {
99
102
  readonly complete: boolean;
100
103
  readonly nodes: ReadonlyArray<DesiredExtensionNode>;
104
+ readonly mcpSourceClosures: ReadonlyArray<DesiredMcpSourceClosure>;
101
105
  readonly problems: ReadonlyArray<DesiredStateProblem>;
102
106
  }
107
+ export interface DesiredMcpSourceClosure {
108
+ readonly identity: string;
109
+ readonly localNames: ReadonlyArray<string>;
110
+ readonly constraints: ReadonlyArray<string>;
111
+ readonly origins: ReadonlyArray<DesiredExtensionOrigin>;
112
+ }
103
113
  export type ProspectivePackRef = Pick<PackRef, "owner" | "pack" | "version">;
104
114
  interface DesiredStateGraphArgs {
105
115
  readonly baseDir: string;
@@ -107,8 +117,10 @@ interface DesiredStateGraphArgs {
107
117
  readonly layout?: WorkspaceLayout;
108
118
  /** Resolved Pack roots whose manifests supersede the currently materialized copy. */
109
119
  readonly prospectivePacks?: ReadonlyArray<ProspectivePackRef>;
120
+ /** Registry source aliases mapped to their stable authority endpoints. */
121
+ readonly registryAuthorities?: Readonly<Record<string, URL | string>>;
110
122
  }
111
123
  export declare const collectDesiredConstraintContributors: (_path: Path.Path, origins: ReadonlyArray<DesiredExtensionOrigin>) => ReadonlyArray<DesiredConstraintContributor>;
112
- export declare const buildDesiredStateGraph: ({ baseDir, settings, layout, prospectivePacks, }: DesiredStateGraphArgs) => Effect.Effect<DesiredStateGraph, never, FileSystem.FileSystem | Path.Path>;
124
+ export declare const buildDesiredStateGraph: ({ baseDir, settings, layout, prospectivePacks, registryAuthorities, }: DesiredStateGraphArgs) => Effect.Effect<DesiredStateGraph, never, FileSystem.FileSystem | Path.Path>;
113
125
  export {};
114
126
  //# sourceMappingURL=desired-state-graph.d.ts.map
@@ -13,6 +13,7 @@ import { configuredAuthoredDirectory } from "./layout.js";
13
13
  import { SETTINGS_FILENAME } from "@agentxm/extension-model/unstable/workspace-files";
14
14
  import { ACQUIRED_EXTENSIONS_DIR } from "./constants.js";
15
15
  import { intersectVersionConstraints } from "@agentxm/extension-model/unstable/version-constraints";
16
+ import { mcpRegistryResolutionKey } from "./mcp-source-identity.js";
16
17
  export const isInlineDesiredExtension = (node) => node.authority === "inline";
17
18
  export const isSourcedDesiredExtension = (node) => node.authority !== "inline";
18
19
  const nodeKey = (type, name) => `${type}:${name}`;
@@ -26,7 +27,17 @@ const registryLocator = (source) => {
26
27
  const ref = source.slice(separator + 1);
27
28
  return ref.startsWith("@") ? { sourceName: source.slice(0, separator), ref } : undefined;
28
29
  };
29
- const sourceIdentity = (type, name, source, settings) => {
30
+ const withVersionConstraint = (source, constraint) => {
31
+ const locator = registryLocator(source);
32
+ if (locator === undefined)
33
+ return source;
34
+ const parsed = parseRegistrySourceRef(locator.ref);
35
+ if (parsed === undefined)
36
+ return source;
37
+ const prefix = source.startsWith("@") ? "" : `${locator.sourceName}:`;
38
+ return `${prefix}${parsed.owner}/${parsed.type}/${parsed.name}@${constraint}`;
39
+ };
40
+ const sourceIdentity = (type, name, source, settings, registryAuthorities) => {
30
41
  if (isWorkspaceSourceLocator(source)) {
31
42
  return settings.owner === undefined
32
43
  ? { identity: source }
@@ -35,8 +46,15 @@ const sourceIdentity = (type, name, source, settings) => {
35
46
  const locator = registryLocator(source);
36
47
  const parsed = locator === undefined ? undefined : parseRegistrySourceRef(locator.ref);
37
48
  if (parsed !== undefined && parsed.type === toExtensionTypePlural(type)) {
49
+ const registryAuthority = locator === undefined ? undefined : registryAuthorities[locator.sourceName];
38
50
  return {
39
- identity: `${parsed.owner}/${parsed.type}/${parsed.name}`,
51
+ identity: type === "mcp-server" && registryAuthority !== undefined
52
+ ? mcpRegistryResolutionKey({
53
+ authority: registryAuthority,
54
+ owner: parsed.owner,
55
+ name: parsed.name,
56
+ })
57
+ : `${parsed.owner}/${parsed.type}/${parsed.name}`,
40
58
  ...(parsed.versionRange === undefined ? {} : { constraint: parsed.versionRange }),
41
59
  };
42
60
  }
@@ -83,6 +101,7 @@ export const collectDesiredConstraintContributors = (_path, origins) => origins
83
101
  source: "settings",
84
102
  range: origin.constraint,
85
103
  location: SETTINGS_FILENAME,
104
+ ...(origin.localName === undefined ? {} : { localName: origin.localName }),
86
105
  },
87
106
  ];
88
107
  }
@@ -113,7 +132,7 @@ const parsePackManifest = (raw) => {
113
132
  const decoded = Schema.decodeUnknownResult(PackManifestSchema)(parsed);
114
133
  return Result.isSuccess(decoded) ? decoded.success : undefined;
115
134
  };
116
- export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectivePacks = [], }) => Effect.gen(function* () {
135
+ export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectivePacks = [], registryAuthorities = {}, }) => Effect.gen(function* () {
117
136
  const fs = yield* FileSystem.FileSystem;
118
137
  const path = yield* Path.Path;
119
138
  const candidates = [];
@@ -124,7 +143,7 @@ export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectiveP
124
143
  const bundled = type === "skill" && entry.origin === "bundled";
125
144
  const identity = bundled
126
145
  ? { identity: `bundled:@agentxm/skills/${name}` }
127
- : sourceIdentity(type, name, entry.source, settings);
146
+ : sourceIdentity(type, name, entry.source, settings, registryAuthorities);
128
147
  if (!bundled && isWorkspaceSourceLocator(entry.source) && settings.owner === undefined) {
129
148
  problems.push({ type: "workspace-owner-missing", extensionType: type, name });
130
149
  }
@@ -138,6 +157,7 @@ export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectiveP
138
157
  ...(identity.constraint === undefined ? {} : { constraint: identity.constraint }),
139
158
  origin: {
140
159
  type: "settings",
160
+ localName: name,
141
161
  authority: "sourced",
142
162
  source: entry.source,
143
163
  enabled: entry.enabled,
@@ -155,11 +175,16 @@ export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectiveP
155
175
  identity: `@workspace/mcps/${name}`,
156
176
  authority: "inline",
157
177
  enabled: entry.enabled,
158
- origin: { type: "settings", authority: "inline", enabled: entry.enabled },
178
+ origin: {
179
+ type: "settings",
180
+ localName: name,
181
+ authority: "inline",
182
+ enabled: entry.enabled,
183
+ },
159
184
  });
160
185
  continue;
161
186
  }
162
- const identity = sourceIdentity("mcp-server", name, entry.source, settings);
187
+ const identity = sourceIdentity("mcp-server", name, entry.source, settings, registryAuthorities);
163
188
  if (isWorkspaceSourceLocator(entry.source) && settings.owner === undefined) {
164
189
  problems.push({ type: "workspace-owner-missing", extensionType: "mcp-server", name });
165
190
  }
@@ -173,6 +198,7 @@ export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectiveP
173
198
  ...(identity.constraint === undefined ? {} : { constraint: identity.constraint }),
174
199
  origin: {
175
200
  type: "settings",
201
+ localName: name,
176
202
  authority: "sourced",
177
203
  source: entry.source,
178
204
  enabled: entry.enabled,
@@ -207,6 +233,7 @@ export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectiveP
207
233
  ...(identity.constraint === undefined ? {} : { constraint: identity.constraint }),
208
234
  origin: {
209
235
  type: "settings",
236
+ localName: identity.name,
210
237
  authority: "sourced",
211
238
  source: entry.source,
212
239
  enabled: entry.enabled !== false,
@@ -267,10 +294,18 @@ export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectiveP
267
294
  const parsed = parseExtensionFqnParts(fqn);
268
295
  if (parsed === undefined || parsed.type === "pack")
269
296
  continue;
297
+ const dependencyIdentity = parsed.type === "mcp-server" &&
298
+ registryAuthorities[configuredRegistrySource] !== undefined
299
+ ? mcpRegistryResolutionKey({
300
+ authority: registryAuthorities[configuredRegistrySource],
301
+ owner: parsed.owner,
302
+ name: parsed.name,
303
+ })
304
+ : `${parsed.owner}/${toExtensionTypePlural(parsed.type)}/${parsed.name}`;
270
305
  candidates.push({
271
306
  type: parsed.type,
272
307
  name: parsed.name,
273
- identity: `${parsed.owner}/${toExtensionTypePlural(parsed.type)}/${parsed.name}`,
308
+ identity: dependencyIdentity,
274
309
  authority: "sourced",
275
310
  source: `${fqn}@${constraint}`,
276
311
  enabled: entry.enabled !== false,
@@ -344,7 +379,21 @@ export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectiveP
344
379
  origins,
345
380
  });
346
381
  }
382
+ const mcpClosuresByIdentity = new Map();
347
383
  for (const node of nodes.values()) {
384
+ if (node.type === "mcp-server" && isSourcedDesiredExtension(node)) {
385
+ const existing = mcpClosuresByIdentity.get(node.identity);
386
+ mcpClosuresByIdentity.set(node.identity, {
387
+ identity: node.identity,
388
+ localNames: [...(existing?.localNames ?? []), node.name].sort(),
389
+ constraints: [
390
+ ...(existing?.constraints ?? []),
391
+ ...node.constraints.filter((constraint) => !(existing?.constraints ?? []).includes(constraint)),
392
+ ],
393
+ origins: [...(existing?.origins ?? []), ...node.origins],
394
+ });
395
+ continue;
396
+ }
348
397
  if (intersectVersionConstraints(node.constraints) === undefined) {
349
398
  problems.push({
350
399
  type: "constraint-conflict",
@@ -355,16 +404,36 @@ export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectiveP
355
404
  });
356
405
  }
357
406
  }
407
+ const mcpSourceClosures = [...mcpClosuresByIdentity.values()].sort((left, right) => left.identity.localeCompare(right.identity));
408
+ for (const closure of mcpSourceClosures) {
409
+ if (intersectVersionConstraints(closure.constraints) === undefined) {
410
+ problems.push({
411
+ type: "constraint-conflict",
412
+ extensionType: "mcp-server",
413
+ name: closure.localNames.join(", "),
414
+ constraints: closure.constraints,
415
+ contributors: collectDesiredConstraintContributors(path, closure.origins),
416
+ });
417
+ }
418
+ }
358
419
  const typeOrder = new Map(extensionTypes.map((type, index) => [type, index]));
359
420
  const orderedNodes = [...nodes.values()]
360
421
  .map((node) => {
361
422
  if (isInlineDesiredExtension(node))
362
423
  return node;
363
- const constraint = intersectVersionConstraints(node.constraints);
424
+ const constraints = node.type === "mcp-server"
425
+ ? (mcpClosuresByIdentity.get(node.identity)?.constraints ?? node.constraints)
426
+ : node.constraints;
427
+ const constraint = intersectVersionConstraints(constraints);
364
428
  return {
365
429
  ...node,
366
- source: node.identity.startsWith("@") && node.constraints.length > 0 && constraint !== undefined
367
- ? `${node.identity}@${constraint}`
430
+ constraints,
431
+ source: constraints.length > 0 && constraint !== undefined
432
+ ? node.type === "mcp-server"
433
+ ? withVersionConstraint(node.source, constraint)
434
+ : node.identity.startsWith("@")
435
+ ? `${node.identity}@${constraint}`
436
+ : node.source
368
437
  : node.source,
369
438
  };
370
439
  })
@@ -378,6 +447,7 @@ export const buildDesiredStateGraph = ({ baseDir, settings, layout, prospectiveP
378
447
  return {
379
448
  complete: problems.length === 0,
380
449
  nodes: orderedNodes,
450
+ mcpSourceClosures,
381
451
  problems,
382
452
  };
383
453
  });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Canonical identity for one accepted MCP source-resolution closure.
3
+ *
4
+ * Connection names deliberately do not participate: several local MCP
5
+ * connections may share this identity and therefore one accepted resolution.
6
+ */
7
+ import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
8
+ import type { ExtensionName } from "@agentxm/extension-model/unstable/extensions/common";
9
+ import type { McpServerLockEntry } from "../lockfile/schema.js";
10
+ export declare const mcpRegistryResolutionKey: (args: {
11
+ readonly authority: URL | string;
12
+ readonly owner: Handle | string;
13
+ readonly name: ExtensionName | string;
14
+ }) => string;
15
+ /** Deterministic lock-map key for every currently accepted MCP source class. */
16
+ export declare const mcpResolutionKey: (entry: McpServerLockEntry) => string;
17
+ //# sourceMappingURL=mcp-source-identity.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Canonical identity for one accepted MCP source-resolution closure.
3
+ *
4
+ * Connection names deliberately do not participate: several local MCP
5
+ * connections may share this identity and therefore one accepted resolution.
6
+ */
7
+ const normalizeAuthority = (authority) => {
8
+ const raw = authority instanceof URL ? authority.href : authority;
9
+ try {
10
+ const normalized = new URL(raw).href;
11
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
12
+ }
13
+ catch {
14
+ return raw.endsWith("/") ? raw.slice(0, -1) : raw;
15
+ }
16
+ };
17
+ const encodeIdentityPart = (value) => encodeURIComponent(value);
18
+ export const mcpRegistryResolutionKey = (args) => `registry:${encodeIdentityPart(normalizeAuthority(args.authority))}:${args.owner}/mcps/${args.name}`;
19
+ /** Deterministic lock-map key for every currently accepted MCP source class. */
20
+ export const mcpResolutionKey = (entry) => {
21
+ switch (entry.type) {
22
+ case "registry":
23
+ return mcpRegistryResolutionKey({
24
+ authority: entry.endpoint,
25
+ owner: entry.owner,
26
+ name: entry.name,
27
+ });
28
+ case "github":
29
+ case "gitlab":
30
+ case "bitbucket":
31
+ return `${entry.type}:${encodeIdentityPart(normalizeAuthority(entry.endpoint))}:${entry.owner}/${entry.repo}:${entry.packageOwner ?? ""}/mcps/${entry.packageName}`;
32
+ case "azurerepos":
33
+ return `azurerepos:${encodeIdentityPart(normalizeAuthority(entry.endpoint))}:${entry.organization}/${entry.project}/${entry.repo}:${entry.packageOwner ?? ""}/mcps/${entry.packageName}`;
34
+ case "git":
35
+ return `git:${encodeIdentityPart(normalizeAuthority(entry.url))}:${entry.packageOwner ?? ""}/mcps/${entry.packageName}`;
36
+ case "local":
37
+ return `local:${encodeIdentityPart(entry.path)}:${entry.packageOwner ?? ""}/mcps/${entry.packageName}`;
38
+ }
39
+ };
40
+ //# sourceMappingURL=mcp-source-identity.js.map
@@ -362,7 +362,7 @@ const validSettingsContents = {
362
362
  skills: { "managed-tool": { source: "github:owner/repo", enabled: true } },
363
363
  };
364
364
  const validLockfileContents = {
365
- lockfileVersion: 6,
365
+ lockfileVersion: 7,
366
366
  skills: {
367
367
  "managed-tool": {
368
368
  type: "github",
@@ -59,8 +59,18 @@ export declare class LockfileDecodeError extends LockfileDecodeError_base<{
59
59
  readonly raw: unknown;
60
60
  }> {
61
61
  }
62
- /** Lockfile-read failure union (IO, parse, decode). */
63
- export type LockfileReadError = LockfileIoError | LockfileParseError | LockfileDecodeError;
62
+ declare const LockfileVersionUnsupported_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 & {
63
+ readonly _tag: "LockfileVersionUnsupported";
64
+ } & Readonly<A>;
65
+ /** A syntactically valid lockfile declares a version this CLI cannot read. */
66
+ export declare class LockfileVersionUnsupported extends LockfileVersionUnsupported_base<{
67
+ readonly path: string;
68
+ readonly observedVersion: number;
69
+ readonly supportedVersion: number;
70
+ }> {
71
+ }
72
+ /** Lockfile-read failure union (IO, parse, decode, unsupported version). */
73
+ export type LockfileReadError = LockfileIoError | LockfileParseError | LockfileDecodeError | LockfileVersionUnsupported;
64
74
  declare const WorkspaceRootEscape_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 & {
65
75
  readonly _tag: "WorkspaceRootEscape";
66
76
  } & Readonly<A>;
@@ -18,6 +18,9 @@ export class LockfileParseError extends Data.TaggedError("LockfileParseError") {
18
18
  /** Lockfile schema decode failure; `raw` carries the parsed value. */
19
19
  export class LockfileDecodeError extends Data.TaggedError("LockfileDecodeError") {
20
20
  }
21
+ /** A syntactically valid lockfile declares a version this CLI cannot read. */
22
+ export class LockfileVersionUnsupported extends Data.TaggedError("LockfileVersionUnsupported") {
23
+ }
21
24
  /** Provider-construction error: workspace root escapes the configured allowed root. */
22
25
  export class WorkspaceRootEscape extends Data.TaggedError("WorkspaceRootEscape") {
23
26
  }
@@ -10,6 +10,7 @@
10
10
  import * as Effect from "effect/Effect";
11
11
  import * as Option from "effect/Option";
12
12
  import { decodeExtensionNameSync, } from "@agentxm/extension-model/unstable/extensions/common";
13
+ import { parseSourceQualifiedRegistrySourcePatternParts } from "@agentxm/extension-model/unstable/extensions";
13
14
  import { filterMapOccurrences } from "./actual-helpers.js";
14
15
  import { canonicalAxmPackageRoot } from "./package-root.js";
15
16
  import { makeProjectedSubjectCells, projectInstalledExtensions, } from "./projection.js";
@@ -24,18 +25,39 @@ const declaredFromSettings = (settings) => {
24
25
  entry,
25
26
  }));
26
27
  };
27
- const resolvedFromLockfile = (lockfile) => {
28
- if (lockfile.mcpServers === undefined)
29
- return [];
30
- return Object.entries(lockfile.mcpServers).map(([name, lockEntry]) => ({
31
- name: decodeExtensionNameSync(name),
32
- lockEntry,
33
- }));
28
+ const resolvedFromState = (settings, lockfile, packs) => {
29
+ const locked = Object.values(lockfile.mcpServers ?? {});
30
+ const resolved = [];
31
+ const names = new Set();
32
+ for (const [localName, entry] of Object.entries(settings.mcpServers ?? {})) {
33
+ if (entry.kind === "inline")
34
+ continue;
35
+ const parsed = parseSourceQualifiedRegistrySourcePatternParts(entry.source);
36
+ const lockEntry = locked.find((candidate) => parsed !== undefined && candidate.type === "registry"
37
+ ? candidate.sourceName === parsed.sourceName &&
38
+ candidate.owner === parsed.owner &&
39
+ candidate.name === parsed.name
40
+ : candidate.workspaceName === localName);
41
+ if (lockEntry === undefined)
42
+ continue;
43
+ names.add(localName);
44
+ resolved.push({ name: decodeExtensionNameSync(localName), lockEntry });
45
+ }
46
+ for (const member of packs.flatMap((pack) => pack.mcpServers)) {
47
+ if (names.has(member.name))
48
+ continue;
49
+ const lockEntry = locked.find((candidate) => candidate.workspaceName === member.name);
50
+ if (lockEntry === undefined)
51
+ continue;
52
+ names.add(member.name);
53
+ resolved.push({ name: member.name, lockEntry });
54
+ }
55
+ return resolved;
34
56
  };
35
- const canonicalToActual = (occ, scope) => {
57
+ const canonicalToActual = (occ, scope, localName = occ.name) => {
36
58
  const packageRoot = canonicalAxmPackageRoot(occ);
37
59
  return {
38
- key: { scope, type: "mcp-server", name: occ.name },
60
+ key: { scope, type: "mcp-server", name: localName },
39
61
  origin: occ.origin === "canonical-axm"
40
62
  ? { _tag: "canonical-axm-mcp-server" }
41
63
  : { _tag: "external-axm-mcp-server" },
@@ -94,11 +116,24 @@ const mcpServerPolicy = (scope) => ({
94
116
  export const makeMcpServerExtensionsApi = (deps) => Effect.gen(function* () {
95
117
  const { scope, loaders, scanners, installedPacks, diagnostics } = deps;
96
118
  const declared = loaders.settings.pipe(Effect.map((opt) => Option.map(opt, declaredFromSettings)));
97
- const resolved = loaders.lockfile.pipe(Effect.map((opt) => Option.map(opt, resolvedFromLockfile)));
119
+ const resolved = Effect.all({
120
+ settings: loaders.settings.pipe(Effect.catch(() => Effect.succeed(Option.none()))),
121
+ lockfile: loaders.lockfile,
122
+ packs: installedPacks.pipe(Effect.catch(() => Effect.succeed([]))),
123
+ }).pipe(Effect.map(({ settings, lockfile, packs }) => Option.all({ settings, lockfile }).pipe(Option.map(({ settings: decodedSettings, lockfile: decodedLockfile }) => resolvedFromState(decodedSettings, decodedLockfile, packs)))));
98
124
  const actual = Effect.gen(function* () {
99
125
  const canonical = yield* scanners.canonical;
100
126
  const mcpConfig = yield* scanners.mcpConfig;
101
- const fromCanonical = filterMapOccurrences(canonical, "mcp-server", (occ) => canonicalToActual(occ, scope));
127
+ const accepted = yield* resolved.pipe(Effect.catch(() => Effect.succeed(Option.none())));
128
+ const resolvedEntries = Option.getOrElse(accepted, () => []);
129
+ const fromCanonical = filterMapOccurrences(canonical, "mcp-server", (occ) => occ).flatMap((occ) => {
130
+ const matchingNames = resolvedEntries
131
+ .filter((entry) => entry.lockEntry.type === "registry" && entry.lockEntry.name === occ.name)
132
+ .map((entry) => entry.name);
133
+ return matchingNames.length === 0
134
+ ? [canonicalToActual(occ, scope)]
135
+ : matchingNames.map((name) => canonicalToActual(occ, scope, name));
136
+ });
102
137
  const fromMcpConfig = mcpConfig.map((occ) => mcpConfigToActual(occ, scope));
103
138
  return [...fromCanonical, ...fromMcpConfig];
104
139
  });
@@ -3,10 +3,10 @@ import * as Effect from "effect/Effect";
3
3
  import * as Option from "effect/Option";
4
4
  import * as Schema from "effect/Schema";
5
5
  import YAML from "yaml";
6
- import { LockfileSchema } from "../../lockfile/schema.js";
6
+ import { LOCKFILE_VERSION, LockfileSchema } from "../../lockfile/schema.js";
7
7
  import { formatSchemaIssuesToLines } from "@agentxm/extension-model/unstable/schema-issues";
8
8
  import { SettingsSchema } from "../../settings/schema.js";
9
- import { LockfileDecodeError, LockfileIoError, LockfileParseError, SettingsDecodeError, SettingsIoError, SettingsParseError, } from "./errors.js";
9
+ import { LockfileDecodeError, LockfileIoError, LockfileParseError, LockfileVersionUnsupported, SettingsDecodeError, SettingsIoError, SettingsParseError, } from "./errors.js";
10
10
  // ---------------------------------------------------------------------------
11
11
  // Raw bytes loaders (shared by the decoded loaders and the public raw cells)
12
12
  // ---------------------------------------------------------------------------
@@ -71,6 +71,22 @@ const loadLockfile = (rawCell) => Effect.gen(function* () {
71
71
  try: () => YAML.parse(bytes),
72
72
  catch: (cause) => new LockfileParseError({ path, raw: bytes, cause }),
73
73
  });
74
+ if (typeof parsed === "object" &&
75
+ parsed !== null &&
76
+ !Array.isArray(parsed) &&
77
+ Object.hasOwn(parsed, "lockfileVersion")) {
78
+ const observedVersion = Reflect.get(parsed, "lockfileVersion");
79
+ if (typeof observedVersion === "number" &&
80
+ Number.isSafeInteger(observedVersion) &&
81
+ observedVersion > 0 &&
82
+ observedVersion !== LOCKFILE_VERSION) {
83
+ return yield* Effect.fail(new LockfileVersionUnsupported({
84
+ path,
85
+ observedVersion,
86
+ supportedVersion: LOCKFILE_VERSION,
87
+ }));
88
+ }
89
+ }
74
90
  const decoded = yield* Schema.decodeUnknownEffect(LockfileSchema)(parsed).pipe(Effect.mapError((error) => new LockfileDecodeError({
75
91
  path,
76
92
  issues: formatSchemaIssuesToLines(error.issue),
@@ -63,7 +63,7 @@ export interface InstalledPackRef {
63
63
  *
64
64
  * The failure channels are intentionally narrow: `declared` only fails with
65
65
  * `SettingsReadError` (3 tags), `resolved` only fails with
66
- * `LockfileReadError` (3 tags), and `actual` never fails — workspace-root
66
+ * `LockfileReadError` (4 tags), and `actual` never fails — workspace-root
67
67
  * path-escape is validated once at provider construction (Layer-level), not
68
68
  * per cell.
69
69
  */
@@ -212,6 +212,8 @@ export interface SetSubagentArgs {
212
212
  */
213
213
  export interface SetMcpServerArgs {
214
214
  readonly name: string;
215
+ /** Canonical source-resolution key. Unlike name, this is not connection-scoped. */
216
+ readonly resolutionKey: string;
215
217
  readonly lockEntry: McpServerLockEntry;
216
218
  readonly versionRange: Option.Option<string>;
217
219
  readonly env?: Readonly<Record<string, string>>;
@@ -408,8 +410,10 @@ export interface WorkspaceMutationsService {
408
410
  readonly removeSubagentLock: (name: string) => Effect.Effect<void, WorkspaceLockfileMutationFailure>;
409
411
  /** Read lockfile and return the MCP servers lock map. */
410
412
  readonly getLockedMcpServers: () => Effect.Effect<McpServersLockMap, WorkspaceLockfileReadFailure>;
411
- /** Read lockfile and return the entry for a specific MCP server, or Option.none(). */
412
- readonly getLockedMcpServer: (name: string) => Effect.Effect<Option.Option<McpServerLockEntry>, WorkspaceLockfileReadFailure>;
413
+ /** Read lockfile and return an MCP accepted resolution by source-resolution identity. */
414
+ readonly getLockedMcpServer: (resolutionKey: string) => Effect.Effect<Option.Option<McpServerLockEntry>, WorkspaceLockfileReadFailure>;
415
+ /** Resolve a local MCP connection name to its shared accepted resolution. */
416
+ readonly getLockedMcpServerForConnection: (localName: string) => Effect.Effect<Option.Option<McpServerLockEntry>, WorkspaceStateReadFailure>;
413
417
  /** Update desired MCP settings and any external accepted resolution atomically. */
414
418
  readonly setMcpServer: (args: SetMcpServerArgs) => Effect.Effect<void, WorkspaceStateMutationFailure>;
415
419
  /** Add or update an MCP server in lockfile only (skip settings). Used for pack dependencies. Serialized by semaphore. */
@@ -469,11 +473,11 @@ export interface WorkspaceMutationsOptions {
469
473
  readonly allowUninitialized?: boolean;
470
474
  }
471
475
  /**
472
- * Error loading workspace mutations: scoped settings reads, layout
473
- * resolution, and the initialization gate.
476
+ * Error loading workspace mutations: scoped settings and lockfile reads,
477
+ * layout resolution, and the initialization gate.
474
478
  *
475
479
  * @experimental This API is unstable and may change without notice.
476
480
  */
477
- export type WorkspaceMutationsError = WorkspaceSettingsReadFailure | WorkspaceLayoutError | WorkspaceNotInitialized;
481
+ export type WorkspaceMutationsError = WorkspaceSettingsReadFailure | WorkspaceLockfileReadFailure | WorkspaceLayoutError | WorkspaceNotInitialized;
478
482
  export {};
479
483
  //# sourceMappingURL=service-interface.d.ts.map
@@ -22,6 +22,7 @@ import * as Path from "effect/Path";
22
22
  import * as Option from "effect/Option";
23
23
  import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
24
24
  import { DesiredPackGraphIncomplete, InvalidAgentId, LockedSkillMissing, SettingsEntryMissing, WorkspaceNotInitialized } from "./errors.js";
25
+ import { LockfileVersionUnsupported } from "./read-model/errors.js";
25
26
  import { LockfileValidationError } from "../lockfile/errors.js";
26
27
  import { type HookEntry, type KnowledgeEntry, type InstructionsConfigValue, type McpServerEntry, type SkillEntry, type SubagentEntry, type PackEntry, type RuleEntry, type SourceHostConfig } from "../settings/index.js";
27
28
  import { type ScopedReleaseAgeExcludePattern } from "@agentxm/extension-model/unstable/extensions/release-age";
@@ -36,8 +37,9 @@ export type WorkspaceLayerOptions = WorkspaceMutationsOptions;
36
37
  /**
37
38
  * Create the workspace mutations service from an existing workspace on disk.
38
39
  *
39
- * The workspace must already be initialized. Missing or invalid settings fail
40
- * fast with a typed `WorkspaceMutationsError`. The two operations-side
40
+ * The workspace must already be initialized. Missing or invalid settings and
41
+ * invalid or unsupported lockfiles fail fast with a typed
42
+ * `WorkspaceMutationsError`. The two operations-side
41
43
  * capabilities — the transaction runner and transition acquirer — are
42
44
  * injected through `makeCapabilities`; the composition seam in
43
45
  * `./operations/load-workspace.ts` supplies the live implementation.
@@ -52,7 +54,7 @@ export declare const makeWorkspaceMutations: (options: WorkspaceLayerOptions, ma
52
54
  getLockfileState: () => Effect.Effect<LockfileState, LockfileValidationError | WorkspaceRootEscape>;
53
55
  getDesiredStateGraph: (graphOptions?: Parameters<(graphOptions?: {
54
56
  readonly prospectivePacks?: Parameters<typeof buildDesiredStateGraph>[0]["prospectivePacks"];
55
- }) => Effect.Effect<import("./desired-state-graph.js").DesiredStateGraph, SettingsReadError | LockfileReadError | WorkspaceRootEscape, never>>[0]) => Effect.Effect<import("./desired-state-graph.js").DesiredStateGraph, SettingsReadError | LockfileReadError | WorkspaceRootEscape, never>;
57
+ }) => Effect.Effect<import("./desired-state-graph.js").DesiredStateGraph, import("./read-model/errors.js").SettingsIoError | import("./read-model/errors.js").SettingsParseError | import("./read-model/errors.js").SettingsDecodeError | import("./read-model/errors.js").LockfileIoError | import("./read-model/errors.js").LockfileParseError | import("./read-model/errors.js").LockfileDecodeError | LockfileVersionUnsupported | WorkspaceRootEscape, never>>[0]) => Effect.Effect<import("./desired-state-graph.js").DesiredStateGraph, import("./read-model/errors.js").SettingsIoError | import("./read-model/errors.js").SettingsParseError | import("./read-model/errors.js").SettingsDecodeError | import("./read-model/errors.js").LockfileIoError | import("./read-model/errors.js").LockfileParseError | import("./read-model/errors.js").LockfileDecodeError | LockfileVersionUnsupported | WorkspaceRootEscape, never>;
56
58
  getConfiguredSources: () => Effect.Effect<ReadonlyArray<SourceHostConfig>, WorkspaceSettingsReadFailure>;
57
59
  getConfiguredSourceByName: (name: string) => Effect.Effect<Option.Option<{
58
60
  readonly name: string;
@@ -1534,7 +1536,7 @@ export declare const makeWorkspaceMutations: (options: WorkspaceLayerOptions, ma
1534
1536
  readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1535
1537
  };
1536
1538
  }, LockfileReadError | WorkspaceRootEscape, never>;
1537
- getLockedMcpServer: (name: string) => Effect.Effect<Option.Option<{
1539
+ getLockedMcpServer: (resolutionKey: string) => Effect.Effect<Option.Option<{
1538
1540
  readonly type: "github";
1539
1541
  readonly owner: string;
1540
1542
  readonly sourceType: "github";
@@ -1650,8 +1652,239 @@ export declare const makeWorkspaceMutations: (options: WorkspaceLayerOptions, ma
1650
1652
  readonly publisherBindingId: string;
1651
1653
  readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1652
1654
  }>, LockfileReadError | WorkspaceRootEscape, never>;
1653
- setMcpServer: ({ name, lockEntry, versionRange, env, enabled, agents }: SetMcpServerArgs) => Effect.Effect<void, import("../settings/errors.ts").SettingsWriteError | import("./transaction.ts").WorkspaceSnapshotError | import("../lockfile/errors.js").LockfileWriteError | LockfileValidationError | SettingsReadError | LockfileReadError | WorkspaceRootEscape, never>;
1654
- setMcpServerLock: ({ name, lockEntry }: SetMcpServerArgs) => Effect.Effect<void, import("../lockfile/errors.js").LockfileWriteError | LockfileValidationError | LockfileReadError | WorkspaceRootEscape, never>;
1655
+ getLockedMcpServerForConnection: (localName: string) => Effect.Effect<Option.None<{
1656
+ readonly type: "github";
1657
+ readonly owner: string;
1658
+ readonly sourceType: "github";
1659
+ readonly extensionType: "mcp-server";
1660
+ readonly sourceName: string;
1661
+ readonly packageFormat: "agentxm";
1662
+ readonly endpoint: URL;
1663
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1664
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1665
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1666
+ readonly repo: string;
1667
+ readonly resolvedCommit: string;
1668
+ readonly resolvedTree: string;
1669
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1670
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1671
+ readonly path?: string | undefined;
1672
+ readonly ref?: string | undefined;
1673
+ } | {
1674
+ readonly type: "gitlab";
1675
+ readonly owner: string;
1676
+ readonly sourceType: "gitlab";
1677
+ readonly extensionType: "mcp-server";
1678
+ readonly sourceName: string;
1679
+ readonly packageFormat: "agentxm";
1680
+ readonly endpoint: URL;
1681
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1682
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1683
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1684
+ readonly repo: string;
1685
+ readonly resolvedCommit: string;
1686
+ readonly resolvedTree: string;
1687
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1688
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1689
+ readonly path?: string | undefined;
1690
+ readonly ref?: string | undefined;
1691
+ } | {
1692
+ readonly type: "bitbucket";
1693
+ readonly owner: string;
1694
+ readonly sourceType: "bitbucket";
1695
+ readonly extensionType: "mcp-server";
1696
+ readonly sourceName: string;
1697
+ readonly packageFormat: "agentxm";
1698
+ readonly endpoint: URL;
1699
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1700
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1701
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1702
+ readonly repo: string;
1703
+ readonly resolvedCommit: string;
1704
+ readonly resolvedTree: string;
1705
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1706
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1707
+ readonly path?: string | undefined;
1708
+ readonly ref?: string | undefined;
1709
+ } | {
1710
+ readonly type: "azurerepos";
1711
+ readonly sourceType: "azurerepos";
1712
+ readonly extensionType: "mcp-server";
1713
+ readonly sourceName: string;
1714
+ readonly packageFormat: "agentxm";
1715
+ readonly endpoint: URL;
1716
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1717
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1718
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1719
+ readonly repo: string;
1720
+ readonly resolvedCommit: string;
1721
+ readonly resolvedTree: string;
1722
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1723
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1724
+ readonly organization: string;
1725
+ readonly project: string;
1726
+ readonly path?: string | undefined;
1727
+ readonly ref?: string | undefined;
1728
+ } | {
1729
+ readonly type: "git";
1730
+ readonly url: string;
1731
+ readonly sourceType: "git";
1732
+ readonly extensionType: "mcp-server";
1733
+ readonly sourceName: "git";
1734
+ readonly packageFormat: "agentxm";
1735
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1736
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1737
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1738
+ readonly resolvedCommit: string;
1739
+ readonly resolvedTree: string;
1740
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1741
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1742
+ readonly path?: string | undefined;
1743
+ readonly ref?: string | undefined;
1744
+ } | {
1745
+ readonly type: "local";
1746
+ readonly sourceType: "local";
1747
+ readonly sourceName: "local";
1748
+ readonly extensionType: "mcp-server";
1749
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1750
+ readonly packageFormat: "agentxm";
1751
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1752
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1753
+ readonly path: string;
1754
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1755
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1756
+ } | {
1757
+ readonly type: "registry";
1758
+ readonly sourceType: "registry";
1759
+ readonly endpoint: URL;
1760
+ readonly extensionType: "mcp-server";
1761
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1762
+ readonly packageFormat: "agentxm";
1763
+ readonly owner: string & import("effect/Brand").Brand<"Handle">;
1764
+ readonly name: string & import("effect/Brand").Brand<"ExtensionName">;
1765
+ readonly resolvedVersion: string & import("effect/Brand").Brand<"Version">;
1766
+ readonly integrity: string;
1767
+ readonly sourceName: string;
1768
+ readonly publisherBindingId: string;
1769
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1770
+ }> | Option.Some<{
1771
+ readonly type: "github";
1772
+ readonly owner: string;
1773
+ readonly sourceType: "github";
1774
+ readonly extensionType: "mcp-server";
1775
+ readonly sourceName: string;
1776
+ readonly packageFormat: "agentxm";
1777
+ readonly endpoint: URL;
1778
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1779
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1780
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1781
+ readonly repo: string;
1782
+ readonly resolvedCommit: string;
1783
+ readonly resolvedTree: string;
1784
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1785
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1786
+ readonly path?: string | undefined;
1787
+ readonly ref?: string | undefined;
1788
+ } | {
1789
+ readonly type: "gitlab";
1790
+ readonly owner: string;
1791
+ readonly sourceType: "gitlab";
1792
+ readonly extensionType: "mcp-server";
1793
+ readonly sourceName: string;
1794
+ readonly packageFormat: "agentxm";
1795
+ readonly endpoint: URL;
1796
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1797
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1798
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1799
+ readonly repo: string;
1800
+ readonly resolvedCommit: string;
1801
+ readonly resolvedTree: string;
1802
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1803
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1804
+ readonly path?: string | undefined;
1805
+ readonly ref?: string | undefined;
1806
+ } | {
1807
+ readonly type: "bitbucket";
1808
+ readonly owner: string;
1809
+ readonly sourceType: "bitbucket";
1810
+ readonly extensionType: "mcp-server";
1811
+ readonly sourceName: string;
1812
+ readonly packageFormat: "agentxm";
1813
+ readonly endpoint: URL;
1814
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1815
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1816
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1817
+ readonly repo: string;
1818
+ readonly resolvedCommit: string;
1819
+ readonly resolvedTree: string;
1820
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1821
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1822
+ readonly path?: string | undefined;
1823
+ readonly ref?: string | undefined;
1824
+ } | {
1825
+ readonly type: "azurerepos";
1826
+ readonly sourceType: "azurerepos";
1827
+ readonly extensionType: "mcp-server";
1828
+ readonly sourceName: string;
1829
+ readonly packageFormat: "agentxm";
1830
+ readonly endpoint: URL;
1831
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1832
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1833
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1834
+ readonly repo: string;
1835
+ readonly resolvedCommit: string;
1836
+ readonly resolvedTree: string;
1837
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1838
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1839
+ readonly organization: string;
1840
+ readonly project: string;
1841
+ readonly path?: string | undefined;
1842
+ readonly ref?: string | undefined;
1843
+ } | {
1844
+ readonly type: "git";
1845
+ readonly url: string;
1846
+ readonly sourceType: "git";
1847
+ readonly extensionType: "mcp-server";
1848
+ readonly sourceName: "git";
1849
+ readonly packageFormat: "agentxm";
1850
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1851
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1852
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1853
+ readonly resolvedCommit: string;
1854
+ readonly resolvedTree: string;
1855
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1856
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1857
+ readonly path?: string | undefined;
1858
+ readonly ref?: string | undefined;
1859
+ } | {
1860
+ readonly type: "local";
1861
+ readonly sourceType: "local";
1862
+ readonly sourceName: "local";
1863
+ readonly extensionType: "mcp-server";
1864
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1865
+ readonly packageFormat: "agentxm";
1866
+ readonly packageOwner: string & import("effect/Brand").Brand<"Handle">;
1867
+ readonly packageName: string & import("effect/Brand").Brand<"ExtensionName">;
1868
+ readonly path: string;
1869
+ readonly contentIdentity: string & import("effect/Brand").Brand<"SourceHash">;
1870
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1871
+ } | {
1872
+ readonly type: "registry";
1873
+ readonly sourceType: "registry";
1874
+ readonly endpoint: URL;
1875
+ readonly extensionType: "mcp-server";
1876
+ readonly workspaceName: string & import("effect/Brand").Brand<"ExtensionName">;
1877
+ readonly packageFormat: "agentxm";
1878
+ readonly owner: string & import("effect/Brand").Brand<"Handle">;
1879
+ readonly name: string & import("effect/Brand").Brand<"ExtensionName">;
1880
+ readonly resolvedVersion: string & import("effect/Brand").Brand<"Version">;
1881
+ readonly integrity: string;
1882
+ readonly sourceName: string;
1883
+ readonly publisherBindingId: string;
1884
+ readonly treeIntegrity: string & import("effect/Brand").Brand<"TreeIntegrity">;
1885
+ }>, import("./read-model/errors.js").SettingsIoError | import("./read-model/errors.js").SettingsParseError | import("./read-model/errors.js").SettingsDecodeError | LockfileReadError | WorkspaceRootEscape, never>;
1886
+ setMcpServer: ({ name, resolutionKey, lockEntry, versionRange, env, enabled, agents, }: SetMcpServerArgs) => Effect.Effect<void, import("../settings/errors.ts").SettingsWriteError | import("./transaction.ts").WorkspaceSnapshotError | import("../lockfile/errors.js").LockfileWriteError | LockfileValidationError | SettingsReadError | LockfileReadError | WorkspaceRootEscape, never>;
1887
+ setMcpServerLock: ({ name: _name, resolutionKey, lockEntry }: SetMcpServerArgs) => Effect.Effect<void, import("../lockfile/errors.js").LockfileWriteError | LockfileValidationError | LockfileReadError | WorkspaceRootEscape, never>;
1655
1888
  updateMcpServerEntry: (name: string, updater: (entry: McpServerEntry) => McpServerEntry) => Effect.Effect<void, import("../settings/errors.ts").SettingsWriteError | import("./transaction.ts").WorkspaceSnapshotError | SettingsEntryMissing | SettingsReadError | WorkspaceRootEscape, never>;
1656
1889
  setMcpServerEntry: (name: string, entry: McpServerEntry) => Effect.Effect<void, import("../settings/errors.ts").SettingsWriteError | import("./transaction.ts").WorkspaceSnapshotError | SettingsReadError | WorkspaceRootEscape, never>;
1657
1890
  removeMcpServer: (name: string) => Effect.Effect<void, import("../settings/errors.ts").SettingsWriteError | import("./transaction.ts").WorkspaceSnapshotError | import("../lockfile/errors.js").LockfileWriteError | LockfileValidationError | SettingsReadError | LockfileReadError | WorkspaceRootEscape, never>;
@@ -1660,6 +1893,6 @@ export declare const makeWorkspaceMutations: (options: WorkspaceLayerOptions, ma
1660
1893
  removeMcpServerLock: (name: string) => Effect.Effect<void, import("../lockfile/errors.js").LockfileWriteError | LockfileValidationError | LockfileReadError | WorkspaceRootEscape, never>;
1661
1894
  removePackSettings: (name: string) => Effect.Effect<void, import("../settings/errors.ts").SettingsWriteError | import("./transaction.ts").WorkspaceSnapshotError | SettingsReadError | WorkspaceRootEscape, never>;
1662
1895
  removePackLock: (name: string) => Effect.Effect<void, import("../lockfile/errors.js").LockfileWriteError | LockfileValidationError | LockfileReadError | WorkspaceRootEscape, never>;
1663
- isExtensionRequiredByInstalledPack: (target: ExtensionTarget) => Effect.Effect<boolean, DesiredPackGraphIncomplete | SettingsReadError | LockfileReadError | WorkspaceRootEscape, never>;
1664
- }, import("./errors.js").WorkspaceLayoutError | WorkspaceNotInitialized | WorkspaceSettingsReadFailure, FileSystem.FileSystem | Path.Path>;
1896
+ isExtensionRequiredByInstalledPack: (target: ExtensionTarget) => Effect.Effect<boolean, DesiredPackGraphIncomplete | import("./read-model/errors.js").SettingsIoError | import("./read-model/errors.js").SettingsParseError | import("./read-model/errors.js").SettingsDecodeError | import("./read-model/errors.js").LockfileIoError | import("./read-model/errors.js").LockfileParseError | import("./read-model/errors.js").LockfileDecodeError | LockfileVersionUnsupported | WorkspaceRootEscape, never>;
1897
+ }, import("./errors.js").WorkspaceLayoutError | WorkspaceNotInitialized | LockfileReadError | WorkspaceSettingsReadFailure, FileSystem.FileSystem | Path.Path>;
1665
1898
  //# sourceMappingURL=service.d.ts.map
@@ -28,6 +28,7 @@ import { computePackPathsForLayout } from "./pack-paths.js";
28
28
  import { sanitizeName } from "./extension-name.js";
29
29
  import { ConfigurableAgentIdSchema, decodeExtensionNameSync, formatFqn, parseSourceQualifiedRegistrySourcePatternParts, } from "@agentxm/extension-model/unstable/extensions";
30
30
  import { DesiredPackGraphIncomplete, InvalidAgentId, LockedSkillMissing, SettingsEntryMissing, WorkspaceNotInitialized, } from "./errors.js";
31
+ import { LockfileVersionUnsupported } from "./read-model/errors.js";
31
32
  import { LockfileValidationError } from "../lockfile/errors.js";
32
33
  import { createDefaultSettings, writeSettingsAtPath, } from "../settings/index.js";
33
34
  import { DEFAULT_MINIMUM_RELEASE_AGE } from "@agentxm/registry-protocol/unstable/registry/release-age-policy";
@@ -71,15 +72,22 @@ const lockEntrySemanticallyEqual = (current, next) => current !== undefined && s
71
72
  const skillLockEntrySemanticallyEqual = (current, next) => lockEntrySemanticallyEqual(current, next);
72
73
  const preserveAcceptedResolutionOnNoop = (current, next) => (lockEntrySemanticallyEqual(current, next) && current !== undefined ? current : next);
73
74
  const subagentLockEntrySemanticallyEqual = (current, next) => lockEntrySemanticallyEqual(current, next);
74
- const requireInitializedWorkspace = (settingsPath, settings) => settings.pipe(Effect.flatMap(Option.match({
75
- onNone: () => Effect.fail(new WorkspaceNotInitialized({ settingsPath })),
75
+ const requireInitializedWorkspace = (settingsPath, settings, lockfile) => settings.pipe(Effect.flatMap(Option.match({
76
+ onNone: () => lockfile.pipe(Effect.matchEffect({
77
+ onFailure: (error) => error instanceof LockfileVersionUnsupported &&
78
+ error.observedVersion > error.supportedVersion
79
+ ? Effect.fail(error)
80
+ : Effect.fail(new WorkspaceNotInitialized({ settingsPath })),
81
+ onSuccess: () => Effect.fail(new WorkspaceNotInitialized({ settingsPath })),
82
+ })),
76
83
  onSome: () => Effect.void,
77
84
  })));
78
85
  /**
79
86
  * Create the workspace mutations service from an existing workspace on disk.
80
87
  *
81
- * The workspace must already be initialized. Missing or invalid settings fail
82
- * fast with a typed `WorkspaceMutationsError`. The two operations-side
88
+ * The workspace must already be initialized. Missing or invalid settings and
89
+ * invalid or unsupported lockfiles fail fast with a typed
90
+ * `WorkspaceMutationsError`. The two operations-side
83
91
  * capabilities — the transaction runner and transition acquirer — are
84
92
  * injected through `makeCapabilities`; the composition seam in
85
93
  * `./operations/load-workspace.ts` supplies the live implementation.
@@ -112,7 +120,8 @@ export const makeWorkspaceMutations = (options, makeCapabilities) => Effect.gen(
112
120
  const readSettingsCell = (dir, sharedScope) => makeWorkspaceReadModel(scopeForDir(dir, sharedScope)).pipe(Effect.flatMap((readModel) => readModel.state.settings), Effect.provide(contextEnv));
113
121
  const readLockfileCell = (dir, sharedScope) => makeWorkspaceReadModel(scopeForDir(dir, sharedScope)).pipe(Effect.flatMap((readModel) => readModel.state.lockfile), Effect.map(Option.getOrElse(createEmptyLockfile)), Effect.provide(contextEnv));
114
122
  if (options.allowUninitialized !== true) {
115
- yield* requireInitializedWorkspace(settingsPath, readSettingsCell(workspaceDir));
123
+ yield* requireInitializedWorkspace(settingsPath, readSettingsCell(workspaceDir), readLockfileCell(workspaceDir));
124
+ yield* readLockfileCell(workspaceDir);
116
125
  }
117
126
  const projectSettings = yield* readSettingsCell(localRuntimeDir, "project").pipe(Effect.map(Option.getOrElse(() => createDefaultSettings())));
118
127
  const userSettings = yield* readSettingsCell(userRuntimeDir, "user").pipe(Effect.map(Option.getOrElse(() => createDefaultSettings())));
@@ -161,15 +170,23 @@ export const makeWorkspaceMutations = (options, makeCapabilities) => Effect.gen(
161
170
  }
162
171
  // An unreadable or corrupt lockfile is actionable workspace state,
163
172
  // not a violated invariant.
164
- return yield* readLockfileSafe(workspaceDir).pipe(Effect.as("ok"), Effect.catchTag(["LockfileIoError", "LockfileParseError", "LockfileDecodeError"], () => Effect.succeed("invalid")));
173
+ return yield* readLockfileSafe(workspaceDir).pipe(Effect.as("ok"), Effect.catchTag([
174
+ "LockfileIoError",
175
+ "LockfileParseError",
176
+ "LockfileDecodeError",
177
+ "LockfileVersionUnsupported",
178
+ ], () => Effect.succeed("invalid")));
165
179
  }).pipe(Effect.withSpan("WorkspaceMutations.getLockfileState"));
166
180
  const readScopedContext = (f) => makeWorkspaceReadModel(scopeForDir(workspaceDir)).pipe(Effect.flatMap(f), Effect.provide(contextEnv));
167
181
  const readDesiredStateGraph = (graphOptions) => Effect.gen(function* () {
168
182
  const settings = yield* readSettingsSafe(workspaceDir);
183
+ const configuredSources = yield* getConfiguredSources();
184
+ const registryAuthorities = Object.fromEntries(configuredSources.flatMap((source) => source.type === "registry" ? [[source.name, source.location]] : []));
169
185
  const graph = yield* buildDesiredStateGraph({
170
186
  baseDir,
171
187
  settings,
172
188
  layout,
189
+ registryAuthorities,
173
190
  ...(graphOptions?.prospectivePacks === undefined
174
191
  ? {}
175
192
  : { prospectivePacks: graphOptions.prospectivePacks }),
@@ -1083,8 +1100,16 @@ export const makeWorkspaceMutations = (options, makeCapabilities) => Effect.gen(
1083
1100
  // MCP Server methods
1084
1101
  // -----------------------------------------------------------------------
1085
1102
  getLockedMcpServers: () => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => lf.mcpServers ?? {})),
1086
- getLockedMcpServer: (name) => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => Option.fromUndefinedOr((lf.mcpServers ?? {})[name]))),
1087
- setMcpServer: ({ name, lockEntry, versionRange, env, enabled, agents }) => withMutex(Effect.gen(function* () {
1103
+ getLockedMcpServer: (resolutionKey) => readLockfileSafe(workspaceDir).pipe(Effect.map((lf) => Option.fromUndefinedOr((lf.mcpServers ?? {})[resolutionKey]))),
1104
+ getLockedMcpServerForConnection: (localName) => Effect.gen(function* () {
1105
+ const graph = yield* readDesiredStateGraph();
1106
+ const node = graph.nodes.find((candidate) => candidate.type === "mcp-server" && candidate.name === localName);
1107
+ if (node === undefined || node.authority === "inline")
1108
+ return Option.none();
1109
+ const lockfile = yield* readLockfileSafe(workspaceDir);
1110
+ return Option.fromUndefinedOr((lockfile.mcpServers ?? {})[node.identity]);
1111
+ }),
1112
+ setMcpServer: ({ name, resolutionKey, lockEntry, versionRange, env, enabled, agents, }) => withMutex(Effect.gen(function* () {
1088
1113
  // Update settings (uses "mcpServers" key)
1089
1114
  const currentSettings = yield* readSettingsSafe(workspaceDir);
1090
1115
  const currentMcpServers = currentSettings.mcpServers ?? {};
@@ -1098,7 +1123,7 @@ export const makeWorkspaceMutations = (options, makeCapabilities) => Effect.gen(
1098
1123
  const fqn = formatFqn({
1099
1124
  owner: lockEntry.owner,
1100
1125
  type: "mcp-server",
1101
- name: decodeExtensionNameSync(name),
1126
+ name: lockEntry.name,
1102
1127
  });
1103
1128
  const locator = Option.isSome(versionRange)
1104
1129
  ? `${fqn}@${versionRange.value}`
@@ -1127,12 +1152,12 @@ export const makeWorkspaceMutations = (options, makeCapabilities) => Effect.gen(
1127
1152
  ...currentLockfile,
1128
1153
  mcpServers: {
1129
1154
  ...currentLockedMcpServers,
1130
- [name]: preserveAcceptedResolutionOnNoop(currentLockedMcpServers[name], lockEntry),
1155
+ [resolutionKey]: preserveAcceptedResolutionOnNoop(currentLockedMcpServers[resolutionKey], lockEntry),
1131
1156
  },
1132
1157
  };
1133
1158
  yield* commitWorkspaceState(currentLockfile, updatedLockfile);
1134
1159
  })).pipe(Effect.withSpan("WorkspaceMutations.setMcpServer")),
1135
- setMcpServerLock: ({ name, lockEntry }) => withMutex(Effect.gen(function* () {
1160
+ setMcpServerLock: ({ name: _name, resolutionKey, lockEntry }) => withMutex(Effect.gen(function* () {
1136
1161
  // Update lockfile only (skip settings) — used for pack dependencies
1137
1162
  const currentLockfile = yield* readLockfileSafe(workspaceDir);
1138
1163
  const currentLockedMcpServers = currentLockfile.mcpServers ?? {};
@@ -1140,7 +1165,7 @@ export const makeWorkspaceMutations = (options, makeCapabilities) => Effect.gen(
1140
1165
  ...currentLockfile,
1141
1166
  mcpServers: {
1142
1167
  ...currentLockedMcpServers,
1143
- [name]: preserveAcceptedResolutionOnNoop(currentLockedMcpServers[name], lockEntry),
1168
+ [resolutionKey]: preserveAcceptedResolutionOnNoop(currentLockedMcpServers[resolutionKey], lockEntry),
1144
1169
  },
1145
1170
  };
1146
1171
  yield* commitWorkspaceState(currentLockfile, updatedLockfile);
@@ -1166,6 +1191,11 @@ export const makeWorkspaceMutations = (options, makeCapabilities) => Effect.gen(
1166
1191
  yield* writeScopedSettings(updatedSettings).pipe(Effect.provide(fsLayer));
1167
1192
  })),
1168
1193
  removeMcpServer: (name) => withMutex(Effect.gen(function* () {
1194
+ const graph = yield* readDesiredStateGraph();
1195
+ const desiredNode = graph.nodes.find((node) => node.type === "mcp-server" && node.name === name);
1196
+ const closure = desiredNode === undefined || desiredNode.authority === "inline"
1197
+ ? undefined
1198
+ : graph.mcpSourceClosures.find((candidate) => candidate.identity === desiredNode.identity);
1169
1199
  // Update settings (uses "mcpServers" key)
1170
1200
  const currentSettings = yield* readSettingsSafe(workspaceDir);
1171
1201
  const currentMcpServers = currentSettings.mcpServers ?? {};
@@ -1182,8 +1212,14 @@ export const makeWorkspaceMutations = (options, makeCapabilities) => Effect.gen(
1182
1212
  // Update lockfile (uses "mcpServers" key)
1183
1213
  const currentLockfile = yield* readLockfileSafe(workspaceDir);
1184
1214
  const currentLockedMcpServers = currentLockfile.mcpServers ?? {};
1185
- if (name in currentLockedMcpServers) {
1186
- const { [name]: __, ...remainingLockedMcpServers } = currentLockedMcpServers;
1215
+ const resolutionKey = desiredNode?.identity;
1216
+ const keepSharedResolution = closure !== undefined &&
1217
+ (closure.localNames.length > 1 ||
1218
+ closure.origins.some((origin) => origin.type === "pack"));
1219
+ if (resolutionKey !== undefined &&
1220
+ !keepSharedResolution &&
1221
+ resolutionKey in currentLockedMcpServers) {
1222
+ const { [resolutionKey]: __, ...remainingLockedMcpServers } = currentLockedMcpServers;
1187
1223
  void __;
1188
1224
  const updatedLockfile = {
1189
1225
  ...currentLockfile,
@@ -188,7 +188,7 @@ export const makeBaseWorkspaceMock = (axmDir = "/tmp/axm", overrides) => {
188
188
  ],
189
189
  };
190
190
  }));
191
- return { complete: true, nodes, problems: [] };
191
+ return { complete: true, nodes, mcpSourceClosures: [], problems: [] };
192
192
  });
193
193
  const runTransaction = (args) => Effect.gen(function* () {
194
194
  const value = yield* args.transition;
@@ -304,6 +304,7 @@ export const makeBaseWorkspaceMock = (axmDir = "/tmp/axm", overrides) => {
304
304
  removeSubagentLock: () => Effect.void,
305
305
  getLockedMcpServers: lockedMcpServers,
306
306
  getLockedMcpServer: entryFrom(lockedMcpServers),
307
+ getLockedMcpServerForConnection: entryFrom(lockedMcpServers),
307
308
  setMcpServer: () => Effect.void,
308
309
  setMcpServerLock: () => Effect.void,
309
310
  updateMcpServerEntry: () => Effect.void,
@@ -337,7 +338,7 @@ export const writeWorkspaceFiles = (runtimeDir, opts = {}) => {
337
338
  ...(opts.sources && { sources: opts.sources }),
338
339
  };
339
340
  const lockfile = {
340
- lockfileVersion: 6,
341
+ lockfileVersion: 7,
341
342
  skills: opts.lockfileSkills ?? {},
342
343
  ...(hasEntries(opts.lockfileMcpServers) && { mcps: opts.lockfileMcpServers }),
343
344
  ...(hasEntries(opts.lockfileSubagents) && { subagents: opts.lockfileSubagents }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentxm/workspace-state",
3
- "version": "0.28.4-bootstrap.0",
3
+ "version": "0.28.5",
4
4
  "description": "AXM workspace state kernel: settings, lockfile, read model, and desired-state vocabulary for the axm CLI. Unstable and unsupported — use the axm.sh CLI.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-MIT",
@@ -18,10 +18,12 @@
18
18
  "exports": {
19
19
  ".": {
20
20
  "types": "./dist/src/index.d.ts",
21
+ "axm-source": "./src/index.ts",
21
22
  "default": "./dist/src/index.js"
22
23
  },
23
24
  "./testing": {
24
25
  "types": "./dist/src/testing.d.ts",
26
+ "axm-source": "./src/testing.ts",
25
27
  "default": "./dist/src/testing.js"
26
28
  }
27
29
  },
@@ -43,8 +45,8 @@
43
45
  "jsonc-parser": "^3.3.1",
44
46
  "semver": "^7.8.5",
45
47
  "yaml": "^2.9.0",
46
- "@agentxm/registry-protocol": "^0.28.4-bootstrap.0",
47
- "@agentxm/extension-model": "^0.28.4-bootstrap.0"
48
+ "@agentxm/extension-model": "^0.28.5",
49
+ "@agentxm/registry-protocol": "^0.28.5"
48
50
  },
49
51
  "devDependencies": {
50
52
  "@effect/platform-node": "4.0.0-rc.112",