@agentxm/workspace-configuration 0.28.12 → 0.28.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/src/errors.d.ts +29 -0
  2. package/dist/src/errors.js +68 -0
  3. package/dist/src/index.d.ts +17 -12
  4. package/dist/src/index.js +28 -11
  5. package/dist/src/inline-mcp/add-inline-mcp-server.d.ts +63 -0
  6. package/dist/src/inline-mcp/add-inline-mcp-server.js +253 -0
  7. package/dist/src/{inline-mcp.d.ts → inline-mcp/definition.d.ts} +3 -3
  8. package/dist/src/{inline-mcp.js → inline-mcp/definition.js} +2 -2
  9. package/dist/src/instructions/manage-instructions.d.ts +125 -0
  10. package/dist/src/instructions/manage-instructions.js +361 -0
  11. package/dist/src/mcp-import/apply.d.ts +33 -0
  12. package/dist/src/{mcp-import.js → mcp-import/apply.js} +8 -50
  13. package/dist/src/mcp-import/import-mcp-servers.d.ts +49 -0
  14. package/dist/src/mcp-import/import-mcp-servers.js +117 -0
  15. package/dist/src/{mcp-import-preflight.d.ts → mcp-import/preflight.d.ts} +1 -1
  16. package/dist/src/{mcp-import-preflight.js → mcp-import/preflight.js} +2 -2
  17. package/dist/src/membership/agent-lifecycle.d.ts +20 -0
  18. package/dist/src/membership/agent-lifecycle.js +33 -0
  19. package/dist/src/membership/atomic-membership.d.ts +41 -0
  20. package/dist/src/membership/atomic-membership.js +125 -0
  21. package/dist/src/membership/configure-agents.d.ts +197 -0
  22. package/dist/src/membership/configure-agents.js +395 -0
  23. package/dist/src/membership/validate-agent-ids.d.ts +16 -0
  24. package/dist/src/membership/validate-agent-ids.js +85 -0
  25. package/dist/src/{initialization-interaction.d.ts → setup/initialization-interaction.d.ts} +1 -1
  26. package/dist/src/{initialization.d.ts → setup/initialization.d.ts} +11 -11
  27. package/dist/src/{initialization.js → setup/initialization.js} +3 -3
  28. package/dist/src/setup/setup-workspace.d.ts +264 -0
  29. package/dist/src/setup/setup-workspace.js +512 -0
  30. package/dist/src/testing.d.ts +95 -4
  31. package/dist/src/testing.js +215 -4
  32. package/package.json +11 -9
  33. package/dist/src/instruction-reconciliation.d.ts +0 -57
  34. package/dist/src/instruction-reconciliation.js +0 -93
  35. package/dist/src/mcp-import.d.ts +0 -40
  36. package/dist/src/membership.d.ts +0 -29
  37. package/dist/src/membership.js +0 -162
  38. /package/dist/src/{initialization-interaction.js → setup/initialization-interaction.js} +0 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Instruction-file management: whether this workspace propagates one source
3
+ * instruction file to the agents it configures, and the transition between
4
+ * that choice and the files on disk.
5
+ *
6
+ * Three requests share one shape. Status reports what the workspace does
7
+ * today without touching it. Enabling records the chosen source file and
8
+ * reconciles every alias AXM owns to it — including removing the aliases a
9
+ * previous choice owned, so changing the source filename never leaves the old
10
+ * arrangement behind. Disabling removes the aliases AXM owns and records that
11
+ * the workspace no longer propagates instructions.
12
+ *
13
+ * Every transition passes one readiness gate before it is planned: AXM
14
+ * refuses to reconcile a target it cannot prove it owns, and refuses to edit
15
+ * a `.gitignore` region it did not write. The gate is stated once here rather
16
+ * than at each call site, because a caller that forgot it would silently
17
+ * overwrite a person's file.
18
+ *
19
+ * @experimental This API is unstable and may change without notice.
20
+ */
21
+ import * as Effect from "effect/Effect";
22
+ import * as FileSystem from "effect/FileSystem";
23
+ import * as Option from "effect/Option";
24
+ import * as Path from "effect/Path";
25
+ import * as Schema from "effect/Schema";
26
+ import { RuleManager, type ManagerRequirements } from "@agentxm/extension-materialization";
27
+ import type { WorkspaceScope } from "@agentxm/extension-model/unstable/workspace-scope";
28
+ import { OperationJournal, ResolvePlanInteraction, type OperationResolution, type PlanExecution } from "@agentxm/workspace-operations";
29
+ import { observeInstructions, type InstructionProjectionEffect, type ResolvedInstructionsConfig } from "@agentxm/workspace-projection";
30
+ import { ConfiguredAgentOutcomesProvider, WorkspaceMutations, type WorkspaceStateReadFailure } from "@agentxm/workspace-state";
31
+ import { FootprintRecorder, WorkspaceTransactionScope } from "@agentxm/workspace-transactions";
32
+ import { WorkspaceConfigurationFailed, type WorkspaceConfigurationExecutionFailure } from "../errors.js";
33
+ /** Every failure observing or planning instruction management can surface. */
34
+ export type ManageInstructionsFailure = WorkspaceConfigurationFailed | WorkspaceStateReadFailure | Effect.Error<ReturnType<typeof observeInstructions>>;
35
+ /** What the workspace does about instruction files right now. */
36
+ export declare const InstructionsStatusSchema: Schema.Struct<{
37
+ readonly enabled: Schema.Boolean;
38
+ readonly sourceFileName: Schema.String;
39
+ readonly gitignoreAliases: Schema.Boolean;
40
+ readonly roots: Schema.$Array<Schema.String>;
41
+ readonly missingSources: Schema.$Array<Schema.String>;
42
+ readonly items: Schema.$Array<Schema.Struct<{
43
+ readonly root: Schema.String;
44
+ readonly agentId: Schema.String;
45
+ readonly agentName: Schema.String;
46
+ readonly sourceFile: Schema.String;
47
+ readonly targetFile: Schema.String;
48
+ readonly mechanism: Schema.String;
49
+ readonly health: Schema.String;
50
+ readonly ownership: Schema.String;
51
+ readonly observedForm: Schema.String;
52
+ readonly details: Schema.String;
53
+ }>>;
54
+ readonly staleTargets: Schema.$Array<Schema.Struct<{
55
+ readonly root: Schema.String;
56
+ readonly agentId: Schema.String;
57
+ readonly agentName: Schema.String;
58
+ readonly sourceFile: Schema.String;
59
+ readonly targetFile: Schema.String;
60
+ readonly mechanism: Schema.String;
61
+ readonly health: Schema.String;
62
+ readonly ownership: Schema.String;
63
+ readonly observedForm: Schema.String;
64
+ readonly details: Schema.String;
65
+ }>>;
66
+ }>;
67
+ export type InstructionsStatus = typeof InstructionsStatusSchema.Type;
68
+ /**
69
+ * Report instruction-file management without changing it. A workspace that
70
+ * does not manage instruction files reports that, rather than an empty
71
+ * observation of a configuration it does not have.
72
+ */
73
+ export declare const instructionsStatus: () => Effect.Effect<InstructionsStatus, Effect.Error<ReturnType<typeof observeInstructions>>, WorkspaceMutations | FileSystem.FileSystem | Path.Path>;
74
+ export type ManageInstructionsRequest = {
75
+ readonly action: "enable";
76
+ /** The source-of-truth instruction file every alias points at. */
77
+ readonly fileName: string;
78
+ /** Keep the propagated alias files out of Git. */
79
+ readonly gitignoreAliases: boolean;
80
+ } | {
81
+ readonly action: "disable";
82
+ };
83
+ /** A request the workspace already satisfies. */
84
+ export interface InstructionsUnchanged {
85
+ readonly _tag: "Unchanged";
86
+ readonly action: "enable" | "disable";
87
+ readonly message: string;
88
+ }
89
+ export interface ManageInstructionsCandidate {
90
+ readonly _tag: "ManageInstructions";
91
+ readonly action: "enable" | "disable";
92
+ /** The configuration recorded once the change settles. */
93
+ readonly config: ResolvedInstructionsConfig | false;
94
+ /** The configuration whose aliases the transition removes first, if any. */
95
+ readonly supersededConfig: Option.Option<ResolvedInstructionsConfig>;
96
+ /** Every file the transition creates, updates, or removes. */
97
+ readonly effects: ReadonlyArray<InstructionProjectionEffect>;
98
+ /** The gate's refusal, when the workspace is not safe to reconcile. */
99
+ readonly blocked: Option.Option<WorkspaceConfigurationFailed>;
100
+ readonly scope: WorkspaceScope;
101
+ }
102
+ /**
103
+ * Settle an instruction-management request into the transition it makes.
104
+ *
105
+ * A request the workspace already satisfies is reported as unchanged rather
106
+ * than planned: enabling the configuration that is already recorded, and
107
+ * whose targets are already current, changes nothing.
108
+ */
109
+ export declare const prepareManageInstructions: (request: ManageInstructionsRequest) => Effect.Effect<ManageInstructionsCandidate | InstructionsUnchanged, ManageInstructionsFailure, WorkspaceMutations | FileSystem.FileSystem | Path.Path | RuleManager | ManagerRequirements>;
110
+ /** Every service the instruction transition and its plan resolution need. */
111
+ export type ManageInstructionsRequirements = ConfiguredAgentOutcomesProvider | FileSystem.FileSystem | FootprintRecorder | OperationJournal | Path.Path | ResolvePlanInteraction | ManagerRequirements | RuleManager | WorkspaceMutations | WorkspaceTransactionScope;
112
+ /**
113
+ * Preview or apply a settled instruction transition. The whole transition —
114
+ * removing superseded aliases, recording the choice, and reconciling every
115
+ * target — runs inside one workspace transaction, so a reconciliation that
116
+ * cannot complete never leaves the choice recorded.
117
+ */
118
+ export declare const previewOrApplyManageInstructions: (candidate: ManageInstructionsCandidate, execution: PlanExecution) => Effect.Effect<OperationResolution<void>, WorkspaceConfigurationExecutionFailure, ManageInstructionsRequirements>;
119
+ /** The instruction-management use case. */
120
+ export declare const ManageInstructions: {
121
+ readonly status: () => Effect.Effect<InstructionsStatus, Effect.Error<ReturnType<typeof observeInstructions>>, WorkspaceMutations | FileSystem.FileSystem | Path.Path>;
122
+ readonly prepare: (request: ManageInstructionsRequest) => Effect.Effect<ManageInstructionsCandidate | InstructionsUnchanged, ManageInstructionsFailure, WorkspaceMutations | FileSystem.FileSystem | Path.Path | RuleManager | ManagerRequirements>;
123
+ readonly previewOrApply: (candidate: ManageInstructionsCandidate, execution: PlanExecution) => Effect.Effect<OperationResolution<void>, WorkspaceConfigurationExecutionFailure, ManageInstructionsRequirements>;
124
+ };
125
+ //# sourceMappingURL=manage-instructions.d.ts.map
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Instruction-file management: whether this workspace propagates one source
3
+ * instruction file to the agents it configures, and the transition between
4
+ * that choice and the files on disk.
5
+ *
6
+ * Three requests share one shape. Status reports what the workspace does
7
+ * today without touching it. Enabling records the chosen source file and
8
+ * reconciles every alias AXM owns to it — including removing the aliases a
9
+ * previous choice owned, so changing the source filename never leaves the old
10
+ * arrangement behind. Disabling removes the aliases AXM owns and records that
11
+ * the workspace no longer propagates instructions.
12
+ *
13
+ * Every transition passes one readiness gate before it is planned: AXM
14
+ * refuses to reconcile a target it cannot prove it owns, and refuses to edit
15
+ * a `.gitignore` region it did not write. The gate is stated once here rather
16
+ * than at each call site, because a caller that forgot it would silently
17
+ * overwrite a person's file.
18
+ *
19
+ * @experimental This API is unstable and may change without notice.
20
+ */
21
+ import * as Effect from "effect/Effect";
22
+ import * as FileSystem from "effect/FileSystem";
23
+ import * as Option from "effect/Option";
24
+ import * as Path from "effect/Path";
25
+ import * as Schema from "effect/Schema";
26
+ import { RuleManager } from "@agentxm/extension-materialization";
27
+ import { OperationJournal, ResolvePlanInteraction, StepFailure, prepareExecutionCandidate, resolveExecutionCandidate, } from "@agentxm/workspace-operations";
28
+ import { applyPlannedProjections, disableInstructionManagement, instructionProjectionEffects, instructionProjectionRemovalEffects, instructionReconciliationReadiness, instructionStateIsCurrent, InstructionMaintenanceFailed, observeInstructions, observeProjectionPlans, reconcileInstructionTransition, removeInstructionTargetsFor, resolveInstructionsConfig, } from "@agentxm/workspace-projection";
29
+ import { ConfiguredAgentOutcomesProvider, WorkspaceMutations, } from "@agentxm/workspace-state";
30
+ import { FootprintRecorder, WorkspaceTransactionScope, runWorkspaceTransaction, } from "@agentxm/workspace-transactions";
31
+ import { WorkspaceConfigurationFailed, configurationFailedToStepFailure, workspaceChangeFailedToStepFailure, } from "../errors.js";
32
+ // -----------------------------------------------------------------------------
33
+ // Status
34
+ // -----------------------------------------------------------------------------
35
+ const InstructionStatusItemSchema = Schema.Struct({
36
+ root: Schema.String,
37
+ agentId: Schema.String,
38
+ agentName: Schema.String,
39
+ sourceFile: Schema.String,
40
+ targetFile: Schema.String,
41
+ mechanism: Schema.String,
42
+ health: Schema.String,
43
+ ownership: Schema.String,
44
+ observedForm: Schema.String,
45
+ details: Schema.String,
46
+ });
47
+ /** What the workspace does about instruction files right now. */
48
+ export const InstructionsStatusSchema = Schema.Struct({
49
+ enabled: Schema.Boolean,
50
+ sourceFileName: Schema.String,
51
+ gitignoreAliases: Schema.Boolean,
52
+ roots: Schema.Array(Schema.String),
53
+ missingSources: Schema.Array(Schema.String),
54
+ items: Schema.Array(InstructionStatusItemSchema),
55
+ staleTargets: Schema.Array(InstructionStatusItemSchema),
56
+ });
57
+ const DISABLED_STATUS = {
58
+ enabled: false,
59
+ sourceFileName: "AGENTS.md",
60
+ gitignoreAliases: false,
61
+ roots: [],
62
+ missingSources: [],
63
+ items: [],
64
+ staleTargets: [],
65
+ };
66
+ /**
67
+ * Report instruction-file management without changing it. A workspace that
68
+ * does not manage instruction files reports that, rather than an empty
69
+ * observation of a configuration it does not have.
70
+ */
71
+ export const instructionsStatus = () => Effect.gen(function* () {
72
+ const workspace = yield* WorkspaceMutations;
73
+ const configured = yield* workspace.getInstructionsConfig();
74
+ if (Option.isNone(configured) || configured.value === false)
75
+ return DISABLED_STATUS;
76
+ const snapshot = yield* observeInstructions({
77
+ ws: workspace,
78
+ config: resolveInstructionsConfig(configured.value),
79
+ });
80
+ return snapshot.status;
81
+ });
82
+ const settingsDisplayPath = (scope) => scope === "project" ? "axm.json" : ".axm/workspace/axm.json";
83
+ /**
84
+ * The transition's artifact: the settings file that records the choice, plus
85
+ * every instruction file the reconciliation touches, workspace-relative.
86
+ */
87
+ const instructionArtifact = (args) => {
88
+ const settings = settingsDisplayPath(args.scope);
89
+ const byPath = new Map();
90
+ byPath.set(settings, { path: settings, change: "updated" });
91
+ for (const effect of args.effects) {
92
+ const relative = args.path.relative(args.baseDir, effect.path);
93
+ byPath.set(relative, { path: relative, change: effect.change });
94
+ }
95
+ return {
96
+ path: settings,
97
+ scope: args.scope,
98
+ change: "updated",
99
+ targets: [...byPath.values()].sort((left, right) => left.path.localeCompare(right.path)),
100
+ };
101
+ };
102
+ const rawConfigMatches = (observed, desired) => {
103
+ if (observed === false)
104
+ return false;
105
+ const resolved = resolveInstructionsConfig(observed);
106
+ return (resolved.fileName === desired.fileName && resolved.gitignoreAliases === desired.gitignoreAliases);
107
+ };
108
+ /**
109
+ * The one readiness gate every instruction transition passes. It is stated
110
+ * here because it decides whether AXM may write at all: an unowned target or
111
+ * an unrecognized `.gitignore` region means the workspace holds a file AXM
112
+ * did not create, and reconciliation stops before it can overwrite it.
113
+ */
114
+ const reconciliationGate = (snapshot) => Effect.gen(function* () {
115
+ const workspace = yield* WorkspaceMutations;
116
+ const failure = yield* instructionReconciliationReadiness({ ws: workspace, snapshot });
117
+ return Option.map(failure, (readiness) => new WorkspaceConfigurationFailed({
118
+ category: "conflict",
119
+ detail: readiness._tag === "InstructionMaintenanceFailed"
120
+ ? readiness.detail
121
+ : "Instruction reconciliation cannot proceed against the current workspace",
122
+ cause: readiness,
123
+ }));
124
+ });
125
+ /**
126
+ * Settle an instruction-management request into the transition it makes.
127
+ *
128
+ * A request the workspace already satisfies is reported as unchanged rather
129
+ * than planned: enabling the configuration that is already recorded, and
130
+ * whose targets are already current, changes nothing.
131
+ */
132
+ export const prepareManageInstructions = (request) => Effect.gen(function* () {
133
+ const workspace = yield* WorkspaceMutations;
134
+ const path = yield* Path.Path;
135
+ const recorded = yield* workspace.getInstructionsConfig();
136
+ if (request.action === "disable") {
137
+ if (Option.isNone(recorded) || recorded.value === false) {
138
+ const settled = {
139
+ _tag: "Unchanged",
140
+ action: "disable",
141
+ message: "Instruction-file management is already disabled.",
142
+ };
143
+ return settled;
144
+ }
145
+ const config = resolveInstructionsConfig(recorded.value);
146
+ const snapshot = yield* observeInstructions({ ws: workspace, config });
147
+ const disableCandidate = {
148
+ _tag: "ManageInstructions",
149
+ action: "disable",
150
+ config: false,
151
+ supersededConfig: Option.some(config),
152
+ effects: instructionProjectionRemovalEffects(snapshot),
153
+ blocked: yield* reconciliationGate(snapshot),
154
+ scope: workspace.scope,
155
+ };
156
+ return disableCandidate;
157
+ }
158
+ const desired = resolveInstructionsConfig({
159
+ fileName: request.fileName,
160
+ gitignoreAliases: request.gitignoreAliases,
161
+ });
162
+ const previous = Option.isSome(recorded) && recorded.value !== false
163
+ ? Option.some(resolveInstructionsConfig(recorded.value))
164
+ : Option.none();
165
+ const configChanged = Option.isSome(previous) &&
166
+ (previous.value.fileName !== desired.fileName ||
167
+ previous.value.gitignoreAliases !== desired.gitignoreAliases);
168
+ const observed = yield* observeInstructions({ ws: workspace, config: desired });
169
+ if (Option.isSome(recorded) &&
170
+ rawConfigMatches(recorded.value, desired) &&
171
+ instructionStateIsCurrent(observed)) {
172
+ const settled = {
173
+ _tag: "Unchanged",
174
+ action: "enable",
175
+ message: "Instruction-file management is already enabled.",
176
+ };
177
+ return settled;
178
+ }
179
+ // A changed configuration is preflighted against the aliases the previous
180
+ // configuration owns, because those are the files the transition removes.
181
+ const superseded = configChanged ? previous : Option.none();
182
+ const preflight = Option.isSome(superseded)
183
+ ? yield* observeInstructions({ ws: workspace, config: superseded.value })
184
+ : observed;
185
+ const ruleManager = yield* RuleManager;
186
+ const ruleEffects = (yield* ruleManager.projectionPlans().pipe(Effect.flatMap(observeProjectionPlans), Effect.mapError((cause) => new WorkspaceConfigurationFailed({
187
+ category: "internal",
188
+ detail: "Rule projections could not be observed before reconciling instructions",
189
+ cause,
190
+ }))))
191
+ .filter((observation) => !observation.current)
192
+ .map((observation) => ({
193
+ path: path.resolve(workspace.baseDir, observation.path.split("#", 1)[0] ?? observation.path),
194
+ change: "updated",
195
+ }));
196
+ const enableCandidate = {
197
+ _tag: "ManageInstructions",
198
+ action: "enable",
199
+ config: desired,
200
+ supersededConfig: superseded,
201
+ effects: [
202
+ ...(Option.isSome(superseded) ? instructionProjectionRemovalEffects(preflight) : []),
203
+ ...ruleEffects,
204
+ ...instructionProjectionEffects(observed),
205
+ ],
206
+ blocked: yield* reconciliationGate(preflight),
207
+ scope: workspace.scope,
208
+ };
209
+ return enableCandidate;
210
+ });
211
+ // -----------------------------------------------------------------------------
212
+ // previewOrApply
213
+ // -----------------------------------------------------------------------------
214
+ const PRESENTATION = {
215
+ enable: {
216
+ name: "Enable instruction-file management",
217
+ verb: { imperative: "enable", past: "Enabled", gerund: "Enabling" },
218
+ applied: "Enabled and reconciled instruction-file management",
219
+ },
220
+ disable: {
221
+ name: "Disable instruction-file management",
222
+ verb: { imperative: "disable", past: "Disabled", gerund: "Disabling" },
223
+ applied: "Disabled instruction-file management and removed owned aliases",
224
+ },
225
+ };
226
+ const transitionEffect = (candidate, config) => Effect.gen(function* () {
227
+ const workspace = yield* WorkspaceMutations;
228
+ const ruleManager = yield* RuleManager;
229
+ if (Option.isSome(candidate.supersededConfig)) {
230
+ yield* removeInstructionTargetsFor({
231
+ ws: workspace,
232
+ config: candidate.supersededConfig.value,
233
+ });
234
+ }
235
+ yield* workspace.setInstructionsConfig({
236
+ fileName: config.fileName,
237
+ gitignoreAliases: config.gitignoreAliases,
238
+ });
239
+ yield* applyPlannedProjections(ruleManager);
240
+ });
241
+ /**
242
+ * Serialize the instruction-projection failures the transition can surface.
243
+ * Ownership refusals keep the sentence the projection chose; the rest are
244
+ * workspace-change failures the shared conversion already renders.
245
+ */
246
+ /**
247
+ * Serialize whatever the recorded transition failed with. The refusals this
248
+ * feature and the projection decide keep their own sentence and category;
249
+ * anything the kernel could not complete is an internal step failure that
250
+ * carries its cause, so the resolution still reports why.
251
+ */
252
+ const transitionFailureToStepFailure = (failure) => {
253
+ if (failure instanceof StepFailure)
254
+ return failure;
255
+ if (failure instanceof WorkspaceConfigurationFailed) {
256
+ return configurationFailedToStepFailure(failure);
257
+ }
258
+ if (failure instanceof InstructionMaintenanceFailed) {
259
+ return new StepFailure({
260
+ category: failure.category,
261
+ detail: failure.detail,
262
+ ...(failure.suggestions === undefined ? {} : { suggestions: failure.suggestions }),
263
+ ...(failure.cause === undefined ? {} : { cause: failure.cause }),
264
+ });
265
+ }
266
+ return new StepFailure({
267
+ category: "internal",
268
+ detail: `Instruction reconciliation could not complete (${failure._tag})`,
269
+ cause: failure,
270
+ });
271
+ };
272
+ const transitionStep = (candidate, artifact) => {
273
+ const presentation = PRESENTATION[candidate.action];
274
+ if (Option.isSome(candidate.blocked)) {
275
+ return {
276
+ label: presentation.name,
277
+ readiness: "error",
278
+ errorMessage: candidate.blocked.value.detail,
279
+ };
280
+ }
281
+ const transition = candidate.action === "disable"
282
+ ? Effect.gen(function* () {
283
+ const workspace = yield* WorkspaceMutations;
284
+ const config = Option.getOrUndefined(candidate.supersededConfig);
285
+ if (config === undefined) {
286
+ return yield* new WorkspaceConfigurationFailed({
287
+ category: "internal",
288
+ detail: "Instruction management has no recorded configuration to disable",
289
+ });
290
+ }
291
+ yield* disableInstructionManagement({ ws: workspace, config });
292
+ }).pipe(Effect.asVoid, Effect.mapError(transitionFailureToStepFailure))
293
+ : Effect.gen(function* () {
294
+ const workspace = yield* WorkspaceMutations;
295
+ const config = candidate.config;
296
+ if (config === false) {
297
+ return yield* new WorkspaceConfigurationFailed({
298
+ category: "internal",
299
+ detail: "Enabling instruction management has no configuration to record",
300
+ });
301
+ }
302
+ yield* reconcileInstructionTransition({
303
+ ws: workspace,
304
+ config,
305
+ ...(Option.isSome(candidate.supersededConfig)
306
+ ? { preflightConfig: candidate.supersededConfig.value }
307
+ : {}),
308
+ transition: transitionEffect({ ...candidate, action: "enable" }, config),
309
+ });
310
+ }).pipe(Effect.asVoid, Effect.mapError(transitionFailureToStepFailure));
311
+ return {
312
+ label: presentation.name,
313
+ readiness: "ready",
314
+ artifact,
315
+ run: runWorkspaceTransaction({
316
+ transition,
317
+ validate: () => Effect.void,
318
+ }).pipe(Effect.mapError(workspaceChangeFailedToStepFailure), Effect.as({
319
+ result: "success",
320
+ message: presentation.applied,
321
+ artifact,
322
+ })),
323
+ };
324
+ };
325
+ /**
326
+ * Preview or apply a settled instruction transition. The whole transition —
327
+ * removing superseded aliases, recording the choice, and reconciling every
328
+ * target — runs inside one workspace transaction, so a reconciliation that
329
+ * cannot complete never leaves the choice recorded.
330
+ */
331
+ export const previewOrApplyManageInstructions = (candidate, execution) => Effect.gen(function* () {
332
+ const workspace = yield* WorkspaceMutations;
333
+ const path = yield* Path.Path;
334
+ const artifact = instructionArtifact({
335
+ scope: candidate.scope,
336
+ baseDir: workspace.baseDir,
337
+ path,
338
+ effects: candidate.effects,
339
+ });
340
+ const plan = {
341
+ _tag: "Plan",
342
+ name: PRESENTATION[candidate.action].name,
343
+ description: Option.some(candidate.action === "disable"
344
+ ? "Turn off instruction-file propagation"
345
+ : `Use ${candidate.config === false ? "AGENTS.md" : candidate.config.fileName} as the source instruction file`),
346
+ presentation: {
347
+ verb: PRESENTATION[candidate.action].verb,
348
+ subject: { singular: "instruction file", plural: "instruction files" },
349
+ },
350
+ jobs: [{ concurrency: 1, steps: [transitionStep(candidate, artifact)] }],
351
+ };
352
+ const prepared = yield* prepareExecutionCandidate(plan);
353
+ return yield* resolveExecutionCandidate(prepared, execution);
354
+ });
355
+ /** The instruction-management use case. */
356
+ export const ManageInstructions = {
357
+ status: instructionsStatus,
358
+ prepare: prepareManageInstructions,
359
+ previewOrApply: previewOrApplyManageInstructions,
360
+ };
361
+ //# sourceMappingURL=manage-instructions.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Inline MCP adoption policy: discover import sources from configured agents'
3
+ * native MCP configs, rewrite adopted entries with AXM management metadata,
4
+ * remove entries converted into managed packages, and apply an inline import
5
+ * as one validated workspace transaction. Prompting, planning, and rendering
6
+ * stay with the application.
7
+ *
8
+ * @experimental This API is unstable and may change without notice.
9
+ */
10
+ import * as Effect from "effect/Effect";
11
+ import * as FileSystem from "effect/FileSystem";
12
+ import * as Path from "effect/Path";
13
+ import { type WorkspaceMutationsService } from "@agentxm/workspace-state";
14
+ import { WorkspaceConfigurationFailed } from "../errors.js";
15
+ import type { McpImportCandidate, McpImportSource } from "./preflight.js";
16
+ /**
17
+ * Discover the native MCP config sources the configured agents contribute for
18
+ * this workspace scope, with unsupported-format findings.
19
+ */
20
+ export declare const collectMcpImportSources: (ws: WorkspaceMutationsService, fs: FileSystem.FileSystem, path: Path.Path) => Effect.Effect<{
21
+ sources: McpImportSource[];
22
+ skipped: {
23
+ readonly name: string;
24
+ readonly reason: string;
25
+ }[];
26
+ }, import("@agentxm/workspace-state").WorkspaceSettingsReadFailure | WorkspaceConfigurationFailed, never>;
27
+ /**
28
+ * Adopt the losslessly importable candidates as inline settings entries and
29
+ * mark their native entries as AXM-managed, in one validated workspace
30
+ * transaction.
31
+ */
32
+ export declare const applyMcpImport: (candidates: ReadonlyArray<McpImportCandidate>, ws: WorkspaceMutationsService, fs: FileSystem.FileSystem) => Effect.Effect<void, import("@agentxm/workspace-state").SettingsIoError | import("@agentxm/workspace-state").SettingsParseError | import("@agentxm/workspace-state").SettingsDecodeError | import("@agentxm/workspace-state").WorkspaceRootEscape | import("@agentxm/workspace-transactions").WorkspaceDirectoryError | import("@agentxm/workspace-transactions").TransitionLockError | WorkspaceConfigurationFailed | import("@agentxm/workspace-transactions").WorkspaceRestorationIncomplete | import("@agentxm/workspace-state").SettingsWriteError | import("@agentxm/workspace-transactions").WorkspaceSnapshotError | import("@agentxm/workspace-transactions").TransitionLockUnavailable | import("@agentxm/workspace-transactions").WorkspaceTransitionCompromised, FileSystem.FileSystem | Path.Path | import("@agentxm/workspace-transactions").WorkspaceTransactionScope>;
33
+ //# sourceMappingURL=apply.d.ts.map
@@ -12,9 +12,11 @@ import * as FileSystem from "effect/FileSystem";
12
12
  import * as Option from "effect/Option";
13
13
  import * as Path from "effect/Path";
14
14
  import { CONFIGURABLE_AGENTS_BY_ID, } from "@agentxm/extension-model/unstable/agent-capabilities";
15
- import { buildAxmMcpMetadataFromSettingsSource } from "@agentxm/extension-workspace";
16
- import { AXM_MCP_METADATA_KEY, isAxmManagedMcpEntry, } from "@agentxm/workspace-state";
17
- import { WorkspaceConfigurationFailed } from "./errors.js";
15
+ import { buildAxmMcpMetadataFromSettingsSource } from "@agentxm/agent-integration";
16
+ import {} from "@agentxm/workspace-state";
17
+ import { AXM_MCP_METADATA_KEY, isAxmManagedMcpEntry } from "@agentxm/agent-integration";
18
+ import { runWorkspaceTransaction } from "@agentxm/workspace-transactions";
19
+ import { WorkspaceConfigurationFailed } from "../errors.js";
18
20
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19
21
  const isCapabilityAgentId = (id) => Object.hasOwn(CONFIGURABLE_AGENTS_BY_ID, id);
20
22
  const readAgentMcpConfig = (agent) => {
@@ -154,47 +156,6 @@ const writeAdoptedMcpConfig = (fs, adoption) => Effect.gen(function* () {
154
156
  cause: error,
155
157
  })));
156
158
  });
157
- /**
158
- * Remove a native entry converted into a managed package, refusing when the
159
- * native config changed since the conversion was planned.
160
- */
161
- export const removeConvertedMcpConfig = (fs, adoption) => Effect.gen(function* () {
162
- const config = yield* readJsonObject(fs, adoption.filePath);
163
- if (Option.isNone(config)) {
164
- return yield* new WorkspaceConfigurationFailed({
165
- category: "conflict",
166
- detail: `MCP config disappeared before package conversion: ${adoption.filePath}`,
167
- });
168
- }
169
- const servers = config.value[adoption.serversKey];
170
- if (!isRecord(servers)) {
171
- return yield* new WorkspaceConfigurationFailed({
172
- category: "conflict",
173
- detail: `MCP server collection changed before package conversion: ${adoption.filePath}`,
174
- });
175
- }
176
- const entry = servers[adoption.name];
177
- if (entry === undefined)
178
- return;
179
- if (!isRecord(entry)) {
180
- return yield* new WorkspaceConfigurationFailed({
181
- category: "conflict",
182
- detail: `MCP server ${adoption.name} changed before package conversion`,
183
- });
184
- }
185
- const remainingServers = Object.fromEntries(Object.entries(servers).filter(([name]) => name !== adoption.name));
186
- const updatedConfig = {
187
- ...config.value,
188
- [adoption.serversKey]: remainingServers,
189
- };
190
- yield* fs
191
- .writeFileString(adoption.filePath, `${JSON.stringify(updatedConfig, null, 2)}\n`)
192
- .pipe(Effect.mapError((error) => new WorkspaceConfigurationFailed({
193
- category: "internal",
194
- detail: `Failed to replace native MCP config: ${adoption.filePath}`,
195
- cause: error,
196
- })));
197
- });
198
159
  const recordsEqual = (left, right) => {
199
160
  const leftEntries = Object.entries(left ?? {}).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey));
200
161
  const rightEntries = Object.entries(right ?? {}).sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey));
@@ -230,7 +191,7 @@ const validateAdoption = (fs, adoption) => Effect.gen(function* () {
230
191
  * mark their native entries as AXM-managed, in one validated workspace
231
192
  * transaction.
232
193
  */
233
- export const applyMcpImport = (candidates, ws, fs, hooks = {}) => {
194
+ export const applyMcpImport = (candidates, ws, fs) => {
234
195
  const adoptions = candidates.flatMap((candidate) => candidate.adoptions);
235
196
  const settingsEntry = (candidate) => ({
236
197
  kind: "inline",
@@ -240,16 +201,13 @@ export const applyMcpImport = (candidates, ws, fs, hooks = {}) => {
240
201
  env: candidate.env,
241
202
  enabled: true,
242
203
  });
243
- return ws.runTransaction({
204
+ return runWorkspaceTransaction({
244
205
  targets: Array.from(new Set(adoptions.map((adoption) => adoption.filePath))).sort(),
245
206
  transition: Effect.gen(function* () {
246
207
  for (const candidate of candidates) {
247
208
  yield* ws.setMcpServerEntry(candidate.name, settingsEntry(candidate));
248
209
  }
249
210
  for (const adoption of adoptions) {
250
- if (hooks.beforeAdoptionWrite !== undefined) {
251
- yield* hooks.beforeAdoptionWrite(adoption);
252
- }
253
211
  yield* writeAdoptedMcpConfig(fs, adoption);
254
212
  }
255
213
  }),
@@ -269,4 +227,4 @@ export const applyMcpImport = (candidates, ws, fs, hooks = {}) => {
269
227
  }),
270
228
  });
271
229
  };
272
- //# sourceMappingURL=mcp-import.js.map
230
+ //# sourceMappingURL=apply.js.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Adopting the MCP servers a workspace already had.
3
+ *
4
+ * People arrive with MCP servers configured directly in their coding agents.
5
+ * Import records each one the workspace can represent losslessly as an inline
6
+ * settings entry and marks the native entry as AXM-managed, so the next
7
+ * reconciliation recognises it instead of treating it as someone else's file.
8
+ * A server whose definitions disagree across agents is a conflict, not a
9
+ * choice AXM makes on the person's behalf.
10
+ *
11
+ * @experimental This API is unstable and may change without notice.
12
+ */
13
+ import * as Effect from "effect/Effect";
14
+ import * as FileSystem from "effect/FileSystem";
15
+ import * as Path from "effect/Path";
16
+ import type { WorkspaceScope } from "@agentxm/extension-model/unstable/workspace-scope";
17
+ import { OperationJournal, ResolvePlanInteraction, type OperationResolution, type PlanExecution } from "@agentxm/workspace-operations";
18
+ import { ConfiguredAgentOutcomesProvider, WorkspaceMutations } from "@agentxm/workspace-state";
19
+ import { FootprintRecorder, WorkspaceTransactionScope } from "@agentxm/workspace-transactions";
20
+ import { WorkspaceConfigurationFailed, type WorkspaceConfigurationExecutionFailure } from "../errors.js";
21
+ import { collectMcpImportSources } from "./apply.js";
22
+ import { type McpImportPreflight } from "./preflight.js";
23
+ export interface ImportMcpServersCandidate {
24
+ readonly _tag: "ImportMcpServers";
25
+ /** Every unmanaged server the workspace found, classified. */
26
+ readonly preflight: McpImportPreflight;
27
+ readonly scope: WorkspaceScope;
28
+ }
29
+ /**
30
+ * Discover and classify every unmanaged MCP server the configured agents and
31
+ * the workspace itself declare. Nothing is written; the candidate names the
32
+ * servers that can be adopted, the ones that conflict, and the ones skipped.
33
+ */
34
+ export declare const prepareImportMcpServers: () => Effect.Effect<ImportMcpServersCandidate, WorkspaceConfigurationFailed | Effect.Error<ReturnType<typeof collectMcpImportSources>>, WorkspaceMutations | FileSystem.FileSystem | Path.Path>;
35
+ /** Every service an inline import and its plan resolution need. */
36
+ export type ImportMcpServersRequirements = ConfiguredAgentOutcomesProvider | FileSystem.FileSystem | FootprintRecorder | OperationJournal | Path.Path | ResolvePlanInteraction | WorkspaceMutations | WorkspaceTransactionScope;
37
+ /**
38
+ * Preview or apply the inline adoption. Every adopted entry is recorded and
39
+ * every native entry marked in one transaction, and the transaction validates
40
+ * the readback before it settles: a half-adopted server would look managed
41
+ * without being managed.
42
+ */
43
+ export declare const previewOrApplyImportMcpServers: (candidate: ImportMcpServersCandidate, execution: PlanExecution) => Effect.Effect<OperationResolution<void>, WorkspaceConfigurationExecutionFailure, ImportMcpServersRequirements>;
44
+ /** The inline MCP import use case. */
45
+ export declare const ImportMcpServers: {
46
+ readonly prepare: () => Effect.Effect<ImportMcpServersCandidate, WorkspaceConfigurationFailed | Effect.Error<ReturnType<typeof collectMcpImportSources>>, WorkspaceMutations | FileSystem.FileSystem | Path.Path>;
47
+ readonly previewOrApply: (candidate: ImportMcpServersCandidate, execution: PlanExecution) => Effect.Effect<OperationResolution<void>, WorkspaceConfigurationExecutionFailure, ImportMcpServersRequirements>;
48
+ };
49
+ //# sourceMappingURL=import-mcp-servers.d.ts.map