@agentxm/extension-lifecycle 0.29.4 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/activation/set-activation.d.ts +22 -40
- package/dist/src/activation/set-activation.js +191 -336
- package/dist/src/demote/demote-to-external-source.d.ts +1 -1
- package/dist/src/demote/demote-to-external-source.js +53 -24
- package/dist/src/hooks/install/plan.js +6 -3
- package/dist/src/hooks/uninstall/plan.js +4 -3
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +1 -2
- package/dist/src/install/root-intent.d.ts +2 -2
- package/dist/src/install/vocabulary.d.ts +5 -4
- package/dist/src/knowledge/install/plan.js +2 -1
- package/dist/src/knowledge/uninstall/plan.js +4 -3
- package/dist/src/mcps/install/plan.js +2 -3
- package/dist/src/mcps/operations/uninstall.js +2 -1
- package/dist/src/mcps/uninstall/plan.js +13 -4
- package/dist/src/packs/graph-transition.d.ts +0 -25
- package/dist/src/packs/graph-transition.js +0 -101
- package/dist/src/packs/install/plan.d.ts +0 -13
- package/dist/src/packs/install/plan.js +12 -9
- package/dist/src/packs/member-install-step.js +8 -21
- package/dist/src/packs/promote-authored-pack.js +6 -3
- package/dist/src/packs/uninstall/plan.js +52 -20
- package/dist/src/rules/install/plan.js +6 -3
- package/dist/src/rules/uninstall/plan.js +4 -3
- package/dist/src/skills/install/bundled.js +4 -4
- package/dist/src/skills/install/plan.js +3 -2
- package/dist/src/skills/operations/install.d.ts +0 -1
- package/dist/src/skills/operations/install.js +9 -18
- package/dist/src/skills/uninstall/plan.js +4 -3
- package/dist/src/subagents/install/plan.js +3 -2
- package/dist/src/subagents/operations/enable.js +1 -3
- package/dist/src/subagents/uninstall/plan.js +4 -3
- package/dist/src/testing.d.ts +1 -1
- package/dist/src/testing.js +2 -1
- package/dist/src/uninstall/root-intent.d.ts +1 -1
- package/dist/src/uninstall/uninstall-extensions.d.ts +1 -1
- package/dist/src/uninstall/uninstall-extensions.js +77 -1
- package/dist/src/update/root-request.d.ts +2 -2
- package/dist/src/update/selective/skills.d.ts +1 -1
- package/dist/src/update/selective/skills.js +0 -1
- package/dist/src/update/selective/subagents.d.ts +1 -1
- package/dist/src/update/selective/subagents.js +3 -2
- package/dist/src/update/selective/use-case.d.ts +2 -2
- package/dist/src/update/targeted-plan.js +4 -2
- package/dist/src/workspace-paths.d.ts +0 -6
- package/dist/src/workspace-paths.js +0 -10
- package/package.json +22 -24
- package/dist/src/uninstall/retention-policy.d.ts +0 -21
- package/dist/src/uninstall/retention-policy.js +0 -28
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* configuration, a rule or Knowledge bundle through a shared managed region,
|
|
11
11
|
* a Pack through the members it contributes.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Disabling a leaf preserves its canonical content and accepted resolution.
|
|
14
|
+
* Disabling a Pack withdraws its dependency route and retires exclusive acquired
|
|
15
|
+
* members. A type whose projection shares a file with instruction
|
|
16
16
|
* management reconciles that file inside the same transaction, so the
|
|
17
17
|
* workspace is never left with a settings change the files do not reflect.
|
|
18
18
|
*
|
|
@@ -25,11 +25,13 @@ import * as Path from "effect/Path";
|
|
|
25
25
|
import * as Schema from "effect/Schema";
|
|
26
26
|
import { resolveInstalledIdentifierNameOrInput } from "@agentxm/extension-sources";
|
|
27
27
|
import { SourceHostProviders, WorkspaceCatalog, } from "@agentxm/extension-sources";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
28
|
+
import { ExtensionManagers, HookManager, SkillManager, RuleManager, KnowledgeManager, PackManager, McpServerManager, SubagentManager, } from "@agentxm/extension-materialization";
|
|
29
|
+
import {} from "@agentxm/workspace-reconciliation";
|
|
30
|
+
import { collectMaterializeSteps, collectUnreachableRetirement, proposeDesiredState, publishDesiredState, } from "@agentxm/workspace-reconciliation";
|
|
31
|
+
import { ReleaseAgePosture, } from "@agentxm/extension-resolution";
|
|
30
32
|
import { activeInstructionsConfig, applyProjectionPlans, CodingAgentRepository, findManagedSubagentFiles, instructionReconciliationReadiness, observeInstructions, projectionPlanExclusionWarnings, reconcileInstructionTransition, } from "@agentxm/workspace-projection";
|
|
31
|
-
import { OperationJournal, operationPresentation, prepareExecutionCandidate, resolveExecutionCandidate, ResolvePlanInteraction,
|
|
32
|
-
import {
|
|
33
|
+
import { OperationJournal, operationPresentation, prepareExecutionCandidate, resolveExecutionCandidate, ResolvePlanInteraction, } from "@agentxm/workspace-operations";
|
|
34
|
+
import { acceptedCanonicalObservation, usableAcceptedCanonical, LockfileReader, SettingsReader, settingsEntries, SettingsWriter, ConfiguredAgentOutcomesProvider, installedRowsByName, isDesiredExtensionActive, RenderedFilePathSchema, sanitizeName, WorkspaceMutations, } from "@agentxm/workspace-state";
|
|
33
35
|
import { FootprintRecorder, runWorkspaceTransaction, WorkspaceTransactionScope, } from "@agentxm/workspace-transactions";
|
|
34
36
|
import { ExtensionLifecycleFailed } from "../errors.js";
|
|
35
37
|
import { StepFailureConversion, withAdaptedStepFailures, } from "../step-failure-conversion.js";
|
|
@@ -39,7 +41,7 @@ import { enableSkill } from "../skills/operations/enable.js";
|
|
|
39
41
|
import { disableSkill } from "../skills/operations/disable.js";
|
|
40
42
|
import { enableSubagent } from "../subagents/operations/enable.js";
|
|
41
43
|
import { disableSubagent } from "../subagents/operations/disable.js";
|
|
42
|
-
import {
|
|
44
|
+
import { settingsDisplayPath } from "./display-paths.js";
|
|
43
45
|
// -----------------------------------------------------------------------------
|
|
44
46
|
// Shared vocabulary
|
|
45
47
|
// -----------------------------------------------------------------------------
|
|
@@ -111,30 +113,6 @@ const instructionGate = (ws) => Effect.gen(function* () {
|
|
|
111
113
|
})),
|
|
112
114
|
};
|
|
113
115
|
});
|
|
114
|
-
const hookLockArtifact = (args) => {
|
|
115
|
-
if (Option.isNone(args.lockEntry))
|
|
116
|
-
return settingsArtifact(args.scope);
|
|
117
|
-
const entry = args.lockEntry.value;
|
|
118
|
-
const packagePath = acquiredExtensionDisplayPathFromLockEntry(canonicalRootDisplayPath(args.scope), entry, "hooks", args.name);
|
|
119
|
-
const targets = (args.enabled
|
|
120
|
-
? [
|
|
121
|
-
{ path: settingsDisplayPath(args.scope), change: "updated" },
|
|
122
|
-
{ path: lockfileDisplayPath(args.scope), change: "updated" },
|
|
123
|
-
{ path: packagePath, change: "created" },
|
|
124
|
-
]
|
|
125
|
-
: [
|
|
126
|
-
{ path: settingsDisplayPath(args.scope), change: "updated" },
|
|
127
|
-
{ path: packagePath, change: "removed" },
|
|
128
|
-
]).sort((left, right) => left.path.localeCompare(right.path));
|
|
129
|
-
const version = entry.type === "registry" ? entry.resolvedVersion : undefined;
|
|
130
|
-
return {
|
|
131
|
-
path: settingsDisplayPath(args.scope),
|
|
132
|
-
scope: args.scope,
|
|
133
|
-
...(args.enabled && version !== undefined ? { version } : {}),
|
|
134
|
-
change: "updated",
|
|
135
|
-
targets,
|
|
136
|
-
};
|
|
137
|
-
};
|
|
138
116
|
/**
|
|
139
117
|
* Settle an activation request into the change it makes.
|
|
140
118
|
*
|
|
@@ -143,7 +121,7 @@ const hookLockArtifact = (args) => {
|
|
|
143
121
|
* of offering to do nothing. A request naming a subject the workspace does
|
|
144
122
|
* not hold fails with the type's own recovery route.
|
|
145
123
|
*/
|
|
146
|
-
|
|
124
|
+
const settleActivation = (request) => Effect.gen(function* () {
|
|
147
125
|
const ws = yield* WorkspaceMutations;
|
|
148
126
|
const path = yield* Path.Path;
|
|
149
127
|
const scope = ws.scope;
|
|
@@ -182,167 +160,95 @@ export const prepareSetActivation = (request) => Effect.gen(function* () {
|
|
|
182
160
|
agentOutcomes: [],
|
|
183
161
|
};
|
|
184
162
|
}
|
|
185
|
-
case "mcp-server":
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
163
|
+
case "mcp-server":
|
|
164
|
+
case "rule":
|
|
165
|
+
case "hook":
|
|
166
|
+
case "knowledge": {
|
|
167
|
+
const name = yield* resolveInstalledIdentifierNameOrInput({
|
|
168
|
+
input: request.name,
|
|
169
|
+
resourceType: request.type,
|
|
170
|
+
});
|
|
171
|
+
const currentGraph = yield* ws.getDesiredStateGraph();
|
|
172
|
+
if (!currentGraph.nodes.some((node) => node.type === request.type && node.name === name)) {
|
|
173
|
+
if (request.type === "knowledge")
|
|
174
|
+
return yield* new ExtensionLifecycleFailed({
|
|
175
|
+
category: "not_found",
|
|
176
|
+
detail: `Knowledge bundle "${name}" is not configured`,
|
|
177
|
+
});
|
|
178
|
+
const label = request.type === "mcp-server"
|
|
179
|
+
? "MCP server"
|
|
180
|
+
: request.type === "hook"
|
|
181
|
+
? "hooks package"
|
|
182
|
+
: "rule";
|
|
183
|
+
return unchanged(request, name, `${label} "${name}" is not configured`);
|
|
184
|
+
}
|
|
185
|
+
const proposal = yield* proposeDesiredState([
|
|
186
|
+
{ kind: "activation", type: request.type, name, enabled: request.enabled },
|
|
187
|
+
]).pipe(Effect.catchTag("WorkspaceSyncFailed", (failure) => new ExtensionLifecycleFailed({
|
|
188
|
+
category: failure.category,
|
|
189
|
+
detail: failure.detail,
|
|
190
|
+
cause: failure,
|
|
191
|
+
})));
|
|
192
|
+
if (!proposal.before.complete || !proposal.after.complete) {
|
|
193
|
+
return yield* new ExtensionLifecycleFailed({
|
|
194
|
+
category: "conflict",
|
|
195
|
+
detail: "Activation requires complete desired state",
|
|
196
|
+
});
|
|
190
197
|
}
|
|
191
|
-
|
|
192
|
-
|
|
198
|
+
const node = proposal.before.nodes.find((node) => node.type === request.type && node.name === name);
|
|
199
|
+
if (node === undefined)
|
|
200
|
+
return yield* notInstalled(request.type, name, `axm ${request.type}s list`);
|
|
201
|
+
if (node.enabled === request.enabled && (request.type !== "mcp-server" || !request.enabled))
|
|
202
|
+
return unchanged(request, name, `${TYPE_LABEL[request.type]} "${name}" is already ${verb}`);
|
|
203
|
+
if (request.enabled && node.authority !== "inline") {
|
|
204
|
+
const canonical = yield* usableAcceptedCanonical({
|
|
205
|
+
workspace: ws,
|
|
206
|
+
type: request.type,
|
|
207
|
+
name,
|
|
208
|
+
});
|
|
209
|
+
if (Option.isNone(canonical)) {
|
|
210
|
+
return yield* new ExtensionLifecycleFailed({
|
|
211
|
+
category: "conflict",
|
|
212
|
+
detail: `Accepted ${request.type} content for "${name}" is not usable; sync it before enabling`,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
193
215
|
}
|
|
216
|
+
const gate = request.type === "rule"
|
|
217
|
+
? yield* instructionGate(ws)
|
|
218
|
+
: {
|
|
219
|
+
config: Option.none(),
|
|
220
|
+
blocked: Option.none(),
|
|
221
|
+
};
|
|
222
|
+
const entry = settingsEntries["mcp-server"].entry(proposal.settings, name);
|
|
194
223
|
const managers = yield* ExtensionManagers;
|
|
195
|
-
const agentOutcomes = request.enabled
|
|
224
|
+
const agentOutcomes = request.type === "mcp-server" && request.enabled && Option.isSome(entry)
|
|
196
225
|
? yield* managers["mcp-server"].configuredAgentOutcomesForEntry({
|
|
197
|
-
name
|
|
198
|
-
entry,
|
|
226
|
+
name,
|
|
227
|
+
entry: entry.value,
|
|
199
228
|
state: "projected",
|
|
200
229
|
})
|
|
201
|
-
:
|
|
230
|
+
: request.type === "hook" &&
|
|
231
|
+
request.enabled &&
|
|
232
|
+
managers.hook.configuredAgentOutcomes !== undefined
|
|
233
|
+
? (yield* managers.hook.configuredAgentOutcomes("projected", proposal.after)).filter((outcome) => outcome.name === name)
|
|
234
|
+
: [];
|
|
202
235
|
return {
|
|
203
236
|
_tag: "SetActivation",
|
|
204
|
-
type:
|
|
205
|
-
name
|
|
206
|
-
enabled: request.enabled,
|
|
207
|
-
scope,
|
|
208
|
-
artifact: settingsArtifact(scope),
|
|
209
|
-
transition: { kind: "mcp-server" },
|
|
210
|
-
instructions: Option.none(),
|
|
211
|
-
blocked: Option.none(),
|
|
212
|
-
warning: Option.none(),
|
|
213
|
-
agentOutcomes,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
case "rule": {
|
|
217
|
-
const configured = yield* ws.getConfiguredRuleEntries();
|
|
218
|
-
const entry = configured[request.name];
|
|
219
|
-
if (entry === undefined) {
|
|
220
|
-
return unchanged(request, request.name, `rule "${request.name}" is not configured`);
|
|
221
|
-
}
|
|
222
|
-
if (entry.enabled === request.enabled) {
|
|
223
|
-
return unchanged(request, request.name, `rule "${request.name}" is already ${verb}`);
|
|
224
|
-
}
|
|
225
|
-
const gate = yield* instructionGate(ws);
|
|
226
|
-
const resolved = request.enabled
|
|
227
|
-
? Option.some(yield* resolveConfiguredRule(request.name, entry.source, yield* makeConfiguredReleaseAgeEvaluation()))
|
|
228
|
-
: Option.none();
|
|
229
|
-
return {
|
|
230
|
-
_tag: "SetActivation",
|
|
231
|
-
type: "rule",
|
|
232
|
-
name: request.name,
|
|
237
|
+
type: request.type,
|
|
238
|
+
name,
|
|
233
239
|
enabled: request.enabled,
|
|
234
240
|
scope,
|
|
235
241
|
artifact: settingsArtifact(scope),
|
|
236
|
-
transition:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
ref,
|
|
241
|
-
versionRange,
|
|
242
|
-
}),
|
|
243
|
-
}),
|
|
242
|
+
transition: request.type === "mcp-server"
|
|
243
|
+
? { kind: "mcp-server" }
|
|
244
|
+
: { kind: "aggregate", proposal },
|
|
245
|
+
proposal,
|
|
244
246
|
instructions: gate.config,
|
|
245
247
|
blocked: gate.blocked,
|
|
246
248
|
warning: Option.none(),
|
|
247
|
-
agentOutcomes: [],
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
case "hook": {
|
|
251
|
-
const configured = yield* ws.getConfiguredHookEntries();
|
|
252
|
-
const entry = configured[request.name];
|
|
253
|
-
if (entry === undefined) {
|
|
254
|
-
return unchanged(request, request.name, `hooks package "${request.name}" is not configured`);
|
|
255
|
-
}
|
|
256
|
-
if (entry.enabled === request.enabled) {
|
|
257
|
-
return unchanged(request, request.name, `hooks package "${request.name}" is already ${verb}`);
|
|
258
|
-
}
|
|
259
|
-
const lockEntry = yield* ws
|
|
260
|
-
.getLockedHookEntry(request.name)
|
|
261
|
-
.pipe(Effect.catch(() => Effect.succeed(Option.none())));
|
|
262
|
-
const artifact = hookLockArtifact({
|
|
263
|
-
lockEntry,
|
|
264
|
-
name: request.name,
|
|
265
|
-
scope,
|
|
266
|
-
enabled: request.enabled,
|
|
267
|
-
});
|
|
268
|
-
if (!request.enabled) {
|
|
269
|
-
return {
|
|
270
|
-
_tag: "SetActivation",
|
|
271
|
-
type: "hook",
|
|
272
|
-
name: request.name,
|
|
273
|
-
enabled: false,
|
|
274
|
-
scope,
|
|
275
|
-
artifact,
|
|
276
|
-
transition: { kind: "deactivate-hook" },
|
|
277
|
-
instructions: Option.none(),
|
|
278
|
-
blocked: Option.none(),
|
|
279
|
-
warning: Option.none(),
|
|
280
|
-
agentOutcomes: [],
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
const { ref, versionRange } = yield* resolveConfiguredHook(request.name, entry.source, yield* makeConfiguredReleaseAgeEvaluation());
|
|
284
|
-
const managers = yield* ExtensionManagers;
|
|
285
|
-
// Hook packages report per-agent outcomes because a hooks package can
|
|
286
|
-
// be projected for one agent and not another.
|
|
287
|
-
const agentOutcomes = managers.hook.configuredAgentOutcomesForRef === undefined
|
|
288
|
-
? []
|
|
289
|
-
: yield* managers.hook.configuredAgentOutcomesForRef(ref, "projected");
|
|
290
|
-
return {
|
|
291
|
-
_tag: "SetActivation",
|
|
292
|
-
type: "hook",
|
|
293
|
-
name: request.name,
|
|
294
|
-
enabled: true,
|
|
295
|
-
scope,
|
|
296
|
-
artifact,
|
|
297
|
-
transition: { kind: "reinstall", ref, versionRange },
|
|
298
|
-
instructions: Option.none(),
|
|
299
|
-
blocked: Option.none(),
|
|
300
|
-
warning: Option.none(),
|
|
301
249
|
agentOutcomes,
|
|
302
250
|
};
|
|
303
251
|
}
|
|
304
|
-
case "knowledge": {
|
|
305
|
-
const configured = yield* ws.getConfiguredKnowledgeEntries();
|
|
306
|
-
const entry = configured[request.name];
|
|
307
|
-
if (entry === undefined) {
|
|
308
|
-
return yield* new ExtensionLifecycleFailed({
|
|
309
|
-
category: "not_found",
|
|
310
|
-
detail: `Knowledge bundle "${request.name}" is not configured`,
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
if (entry.enabled === request.enabled) {
|
|
314
|
-
return unchanged(request, request.name, `Knowledge bundle "${request.name}" is already ${verb}`);
|
|
315
|
-
}
|
|
316
|
-
if (!request.enabled) {
|
|
317
|
-
return {
|
|
318
|
-
_tag: "SetActivation",
|
|
319
|
-
type: "knowledge",
|
|
320
|
-
name: request.name,
|
|
321
|
-
enabled: false,
|
|
322
|
-
scope,
|
|
323
|
-
artifact: settingsArtifact(scope),
|
|
324
|
-
transition: { kind: "deactivate-knowledge" },
|
|
325
|
-
instructions: Option.none(),
|
|
326
|
-
blocked: Option.none(),
|
|
327
|
-
warning: Option.none(),
|
|
328
|
-
agentOutcomes: [],
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
const { ref, versionRange } = yield* resolveConfiguredKnowledge(request.name, entry.source, yield* makeConfiguredReleaseAgeEvaluation());
|
|
332
|
-
return {
|
|
333
|
-
_tag: "SetActivation",
|
|
334
|
-
type: "knowledge",
|
|
335
|
-
name: request.name,
|
|
336
|
-
enabled: true,
|
|
337
|
-
scope,
|
|
338
|
-
artifact: settingsArtifact(scope),
|
|
339
|
-
transition: { kind: "reinstall", ref, versionRange },
|
|
340
|
-
instructions: Option.none(),
|
|
341
|
-
blocked: Option.none(),
|
|
342
|
-
warning: Option.none(),
|
|
343
|
-
agentOutcomes: [],
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
252
|
case "pack": {
|
|
347
253
|
const configured = yield* ws.getConfiguredPackEntries();
|
|
348
254
|
const entry = configured[request.name];
|
|
@@ -356,7 +262,14 @@ export const prepareSetActivation = (request) => Effect.gen(function* () {
|
|
|
356
262
|
if (entry.enabled === request.enabled) {
|
|
357
263
|
return unchanged(request, request.name, `Pack "${request.name}" is already ${verb}`);
|
|
358
264
|
}
|
|
359
|
-
const
|
|
265
|
+
const proposal = yield* proposeDesiredState([
|
|
266
|
+
{ kind: "activation", type: "pack", name: request.name, enabled: request.enabled },
|
|
267
|
+
]).pipe(Effect.mapError((cause) => new ExtensionLifecycleFailed({
|
|
268
|
+
category: "conflict",
|
|
269
|
+
detail: "Cannot derive Pack activation",
|
|
270
|
+
cause,
|
|
271
|
+
})));
|
|
272
|
+
const graph = proposal.before;
|
|
360
273
|
if (!graph.complete) {
|
|
361
274
|
return yield* new ExtensionLifecycleFailed({
|
|
362
275
|
category: "conflict",
|
|
@@ -378,12 +291,52 @@ export const prepareSetActivation = (request) => Effect.gen(function* () {
|
|
|
378
291
|
node.enabled &&
|
|
379
292
|
packContributesTo(node, identity) &&
|
|
380
293
|
!remainsActiveWithoutPack(node, identity));
|
|
381
|
-
const contributed =
|
|
294
|
+
const contributed = proposal.after.nodes.filter((node) => node.type !== "pack" && packContributesTo(node, identity));
|
|
382
295
|
const members = request.enabled ? contributed : affected;
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
296
|
+
const conversion = yield* StepFailureConversion;
|
|
297
|
+
const adapter = {
|
|
298
|
+
toStepFailure: (cause) => conversion.toStepFailure(new ExtensionLifecycleFailed({
|
|
299
|
+
category: "conflict",
|
|
300
|
+
detail: "detail" in cause ? cause.detail : cause._tag,
|
|
301
|
+
cause,
|
|
302
|
+
})),
|
|
303
|
+
};
|
|
304
|
+
const retirement = request.enabled
|
|
305
|
+
? Option.none()
|
|
306
|
+
: yield* collectUnreachableRetirement(adapter, {
|
|
307
|
+
resultingGraph: proposal.after,
|
|
308
|
+
subjects: members,
|
|
309
|
+
}).pipe(Effect.mapError((cause) => new ExtensionLifecycleFailed({
|
|
310
|
+
category: "conflict",
|
|
311
|
+
detail: "Cannot verify Pack member retirement",
|
|
312
|
+
cause,
|
|
313
|
+
})));
|
|
314
|
+
const materialization = request.enabled
|
|
315
|
+
? yield* collectMaterializeSteps({
|
|
316
|
+
desiredState: proposal.after,
|
|
317
|
+
selection: { target: Option.some(identity), type: Option.none() },
|
|
318
|
+
adapter,
|
|
319
|
+
}).pipe(Effect.mapError((cause) => new ExtensionLifecycleFailed({
|
|
320
|
+
category: "conflict",
|
|
321
|
+
detail: "Cannot prepare Pack member realization",
|
|
322
|
+
cause,
|
|
323
|
+
})))
|
|
324
|
+
: undefined;
|
|
325
|
+
const retainedReferences = (yield* Effect.forEach(members, (node) => acceptedCanonicalObservation({ workspace: ws, type: node.type, name: node.name }).pipe(Effect.map((canonical) => {
|
|
326
|
+
if (Option.isNone(canonical) || canonical.value.observation.path === undefined)
|
|
327
|
+
return [];
|
|
328
|
+
const relative = path.relative(ws.baseDir, canonical.value.observation.path);
|
|
329
|
+
if (Option.isSome(retirement) &&
|
|
330
|
+
retirement.value.artifact?.targets?.some((target) => target.path === relative))
|
|
331
|
+
return [];
|
|
332
|
+
return [
|
|
333
|
+
{
|
|
334
|
+
path: relative,
|
|
335
|
+
state: canonical.value.observation.status === "usable" ? "retained" : "unknown",
|
|
336
|
+
reason: "canonical content required by resulting desired state",
|
|
337
|
+
},
|
|
338
|
+
];
|
|
339
|
+
})))).flat();
|
|
387
340
|
return {
|
|
388
341
|
_tag: "SetActivation",
|
|
389
342
|
type: "pack",
|
|
@@ -394,11 +347,22 @@ export const prepareSetActivation = (request) => Effect.gen(function* () {
|
|
|
394
347
|
path: settingsDisplayPath(scope),
|
|
395
348
|
scope,
|
|
396
349
|
change: "updated",
|
|
397
|
-
fileCount:
|
|
398
|
-
|
|
350
|
+
fileCount: 1 +
|
|
351
|
+
(Option.isSome(retirement) ? (retirement.value.artifact?.targets?.length ?? 0) : 0),
|
|
352
|
+
targets: [
|
|
353
|
+
{ path: settingsDisplayPath(scope), change: "updated" },
|
|
354
|
+
...(Option.isSome(retirement) ? (retirement.value.artifact?.targets ?? []) : []),
|
|
355
|
+
],
|
|
356
|
+
references: [
|
|
357
|
+
...retainedReferences,
|
|
358
|
+
...(Option.isSome(retirement) ? (retirement.value.artifact?.references ?? []) : []),
|
|
359
|
+
],
|
|
399
360
|
},
|
|
400
361
|
transition: {
|
|
401
362
|
kind: "pack",
|
|
363
|
+
proposal,
|
|
364
|
+
retirement,
|
|
365
|
+
...(materialization === undefined ? {} : { materialization }),
|
|
402
366
|
identity,
|
|
403
367
|
members,
|
|
404
368
|
aggregateTypes: new Set(members.map((node) => node.type)),
|
|
@@ -516,98 +480,38 @@ const validatePackActivation = (candidate) => Effect.gen(function* () {
|
|
|
516
480
|
});
|
|
517
481
|
}
|
|
518
482
|
});
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const managers = yield* ExtensionManagers;
|
|
524
|
-
yield* ws.updateRuleEntry(candidate.name, (current) => ({ ...current, enabled: false }));
|
|
525
|
-
yield* managers.rule.materializeDeactivate({
|
|
526
|
-
target: { type: "rule", name: candidate.name },
|
|
527
|
-
});
|
|
528
|
-
return NO_WARNINGS;
|
|
529
|
-
});
|
|
530
|
-
/** Disabling a hooks package: record the preference, then stop projecting it. */
|
|
531
|
-
const deactivateHookTransition = (candidate) => Effect.gen(function* () {
|
|
532
|
-
const ws = yield* WorkspaceMutations;
|
|
533
|
-
const managers = yield* ExtensionManagers;
|
|
534
|
-
yield* ws.updateHookEntry(candidate.name, (current) => ({ ...current, enabled: false }));
|
|
535
|
-
yield* managers.hook.materializeDeactivate({
|
|
536
|
-
target: { type: "hook", name: candidate.name },
|
|
537
|
-
});
|
|
538
|
-
return NO_WARNINGS;
|
|
539
|
-
});
|
|
540
|
-
/**
|
|
541
|
-
* Disabling a Knowledge bundle: record the preference, then re-render the
|
|
542
|
-
* discovery region. Re-rendering is what deactivation means for Knowledge, so
|
|
543
|
-
* this step carries the region's own exclusion report.
|
|
544
|
-
*/
|
|
545
|
-
const deactivateKnowledgeTransition = (candidate) => Effect.gen(function* () {
|
|
546
|
-
const ws = yield* WorkspaceMutations;
|
|
547
|
-
const managers = yield* ExtensionManagers;
|
|
548
|
-
yield* ws.updateKnowledgeEntry(candidate.name, (current) => ({ ...current, enabled: false }));
|
|
549
|
-
const plans = yield* managers.knowledge.projectionPlans();
|
|
550
|
-
yield* applyProjectionPlans(plans);
|
|
551
|
-
return projectionPlanExclusionWarnings(plans);
|
|
483
|
+
/** Aggregate projections read the accepted content selected by proposed intent. */
|
|
484
|
+
const aggregateTransition = (proposal, type) => Effect.gen(function* () {
|
|
485
|
+
yield* publishDesiredState(proposal);
|
|
486
|
+
return yield* reconcileAggregateProjections(new Set([type]));
|
|
552
487
|
});
|
|
553
488
|
/** Enabling a Pack, or disabling it and the members it alone kept active. */
|
|
554
489
|
const packTransition = (candidate, transition) => Effect.gen(function* () {
|
|
555
|
-
|
|
556
|
-
const conversion = yield* StepFailureConversion;
|
|
557
|
-
const configured = yield* ws.getConfiguredPackEntries();
|
|
558
|
-
const entry = configured[candidate.name];
|
|
559
|
-
if (entry === undefined) {
|
|
560
|
-
return yield* new ExtensionLifecycleFailed({
|
|
561
|
-
category: "not_found",
|
|
562
|
-
detail: `Pack "${candidate.name}" is no longer configured`,
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
yield* ws.setPackEntry(candidate.name, { ...entry, enabled: candidate.enabled });
|
|
490
|
+
yield* publishDesiredState(transition.proposal);
|
|
566
491
|
if (candidate.enabled) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
// operator can actually take.
|
|
576
|
-
const { steps } = yield* collectRetainedMaterializeSteps({
|
|
577
|
-
nodes: activeMembers,
|
|
578
|
-
adapter: { toStepFailure: conversion.toStepFailure },
|
|
579
|
-
runMcpServerInstall: ({ ref }) => installMcpServer({
|
|
580
|
-
name: "install-mcp-server",
|
|
581
|
-
args: {
|
|
582
|
-
ref,
|
|
583
|
-
nonInteractive: true,
|
|
584
|
-
force: false,
|
|
585
|
-
allowWorkspaceSourceTransition: false,
|
|
586
|
-
versionRange: Option.none(),
|
|
587
|
-
skipSettings: Option.some(true),
|
|
588
|
-
},
|
|
589
|
-
}).pipe(withAdaptedStepFailures),
|
|
590
|
-
}).pipe(Effect.mapError((failure) => failure instanceof RetainedContentUnusable
|
|
591
|
-
? new ExtensionLifecycleFailed({
|
|
592
|
-
category: "conflict",
|
|
593
|
-
detail: failure.detail,
|
|
594
|
-
suggestions: [
|
|
595
|
-
{
|
|
596
|
-
description: "Refresh the pack and its retained members",
|
|
597
|
-
cmd: "axm packs update --yes",
|
|
598
|
-
},
|
|
599
|
-
],
|
|
600
|
-
cause: failure,
|
|
601
|
-
})
|
|
602
|
-
: failure));
|
|
603
|
-
yield* Effect.forEach(steps, (step) => step.readiness === "error"
|
|
604
|
-
? Effect.fail(new ExtensionLifecycleFailed({ category: "conflict", detail: step.errorMessage }))
|
|
605
|
-
: step.run.pipe(Effect.asVoid), { concurrency: 1, discard: true });
|
|
492
|
+
for (const step of transition.materialization?.steps ?? []) {
|
|
493
|
+
if (step.readiness === "error")
|
|
494
|
+
return yield* new ExtensionLifecycleFailed({
|
|
495
|
+
category: "conflict",
|
|
496
|
+
detail: step.errorMessage,
|
|
497
|
+
});
|
|
498
|
+
yield* step.run;
|
|
499
|
+
}
|
|
606
500
|
}
|
|
607
501
|
else {
|
|
608
502
|
yield* Effect.forEach(transition.members.filter((node) => !AGGREGATE_UNIT_TYPES.has(node.type)), dematerializeMember, { concurrency: 1, discard: true });
|
|
609
503
|
}
|
|
610
|
-
|
|
504
|
+
const warnings = yield* reconcileAggregateProjections(transition.aggregateTypes);
|
|
505
|
+
if (Option.isSome(transition.retirement)) {
|
|
506
|
+
const step = transition.retirement.value;
|
|
507
|
+
if (step.readiness === "error")
|
|
508
|
+
return yield* new ExtensionLifecycleFailed({
|
|
509
|
+
category: "conflict",
|
|
510
|
+
detail: step.errorMessage,
|
|
511
|
+
});
|
|
512
|
+
yield* step.run;
|
|
513
|
+
}
|
|
514
|
+
return warnings;
|
|
611
515
|
});
|
|
612
516
|
/**
|
|
613
517
|
* The step for a type whose executor owns its whole transition, including the
|
|
@@ -625,11 +529,17 @@ const executorStep = (candidate, kind) => {
|
|
|
625
529
|
: candidate.enabled
|
|
626
530
|
? enableMcpServer({ name: "enable-mcp-server", args: { serverName: candidate.name } })
|
|
627
531
|
: disableMcpServer({ name: "disable-mcp-server", args: { serverName: candidate.name } });
|
|
532
|
+
const applied = candidate.proposal === undefined
|
|
533
|
+
? run
|
|
534
|
+
: runWorkspaceTransaction({
|
|
535
|
+
transition: publishDesiredState(candidate.proposal).pipe(Effect.andThen(run)),
|
|
536
|
+
validate: () => Effect.void,
|
|
537
|
+
}).pipe(withAdaptedStepFailures);
|
|
628
538
|
return {
|
|
629
539
|
label: candidate.name,
|
|
630
540
|
readiness: "ready",
|
|
631
541
|
...(candidate.agentOutcomes.length === 0 ? {} : { agentOutcomes: candidate.agentOutcomes }),
|
|
632
|
-
run,
|
|
542
|
+
run: applied,
|
|
633
543
|
};
|
|
634
544
|
};
|
|
635
545
|
/**
|
|
@@ -676,14 +586,8 @@ const activationStep = (candidate) => {
|
|
|
676
586
|
case "subagent":
|
|
677
587
|
case "mcp-server":
|
|
678
588
|
return executorStep(candidate, transition.kind);
|
|
679
|
-
case "
|
|
680
|
-
return
|
|
681
|
-
case "deactivate-rule":
|
|
682
|
-
return transactionStep(candidate, deactivateRuleTransition(candidate), () => Effect.void);
|
|
683
|
-
case "deactivate-hook":
|
|
684
|
-
return transactionStep(candidate, deactivateHookTransition(candidate), () => Effect.void);
|
|
685
|
-
case "deactivate-knowledge":
|
|
686
|
-
return transactionStep(candidate, deactivateKnowledgeTransition(candidate), () => Effect.void);
|
|
589
|
+
case "aggregate":
|
|
590
|
+
return transactionStep(candidate, aggregateTransition(transition.proposal, candidate.type), () => Effect.void);
|
|
687
591
|
case "pack":
|
|
688
592
|
return transactionStep(candidate, packTransition(candidate, transition), () => validatePackActivation({
|
|
689
593
|
name: candidate.name,
|
|
@@ -692,69 +596,13 @@ const activationStep = (candidate) => {
|
|
|
692
596
|
}));
|
|
693
597
|
}
|
|
694
598
|
};
|
|
695
|
-
/**
|
|
696
|
-
* Enabling a rule, hooks package, or Knowledge bundle re-runs the configured
|
|
697
|
-
* install closure for the entry the workspace already accepted, so the same
|
|
698
|
-
* publisher-trust and release-age evidence a first install carries travels
|
|
699
|
-
* with the re-activation.
|
|
700
|
-
*/
|
|
701
|
-
const reinstallStep = (candidate, transition) => {
|
|
702
|
-
const { artifact } = candidate;
|
|
703
|
-
const install = (managers, toStepFailure) => {
|
|
704
|
-
const common = {
|
|
705
|
-
toStepFailure,
|
|
706
|
-
versionRange: transition.versionRange,
|
|
707
|
-
message: `Enabled ${candidate.name}`,
|
|
708
|
-
buildArtifact: () => Effect.succeed(artifact),
|
|
709
|
-
};
|
|
710
|
-
switch (transition.ref.type) {
|
|
711
|
-
case "rule":
|
|
712
|
-
return buildInstallOperation(managers.rule, { ...common, ref: transition.ref });
|
|
713
|
-
case "hook":
|
|
714
|
-
return buildInstallOperation(managers.hook, { ...common, ref: transition.ref });
|
|
715
|
-
case "knowledge":
|
|
716
|
-
return buildInstallOperation(managers.knowledge, { ...common, ref: transition.ref });
|
|
717
|
-
case "skill":
|
|
718
|
-
return buildInstallOperation(managers.skill, { ...common, ref: transition.ref });
|
|
719
|
-
case "subagent":
|
|
720
|
-
return buildInstallOperation(managers.subagent, { ...common, ref: transition.ref });
|
|
721
|
-
case "mcp-server":
|
|
722
|
-
return buildInstallOperation(managers["mcp-server"], { ...common, ref: transition.ref });
|
|
723
|
-
case "pack":
|
|
724
|
-
return buildInstallOperation(managers.pack, { ...common, ref: transition.ref });
|
|
725
|
-
}
|
|
726
|
-
};
|
|
727
|
-
const run = Effect.gen(function* () {
|
|
728
|
-
const ws = yield* WorkspaceMutations;
|
|
729
|
-
const managers = yield* ExtensionManagers;
|
|
730
|
-
const conversion = yield* StepFailureConversion;
|
|
731
|
-
const step = install(managers, conversion.toStepFailure);
|
|
732
|
-
if (step.readiness === "error") {
|
|
733
|
-
return yield* new StepFailure({ category: "conflict", detail: step.errorMessage });
|
|
734
|
-
}
|
|
735
|
-
return yield* Option.match(candidate.instructions, {
|
|
736
|
-
onNone: () => step.run,
|
|
737
|
-
onSome: (config) => runWorkspaceTransaction({
|
|
738
|
-
transition: reconcileInstructionTransition({ ws, config, transition: step.run }),
|
|
739
|
-
validate: () => Effect.void,
|
|
740
|
-
}).pipe(withAdaptedStepFailures),
|
|
741
|
-
});
|
|
742
|
-
});
|
|
743
|
-
return {
|
|
744
|
-
label: candidate.name,
|
|
745
|
-
readiness: "ready",
|
|
746
|
-
artifact,
|
|
747
|
-
...(candidate.agentOutcomes.length === 0 ? {} : { agentOutcomes: candidate.agentOutcomes }),
|
|
748
|
-
run,
|
|
749
|
-
};
|
|
750
|
-
};
|
|
751
599
|
/**
|
|
752
600
|
* Preview or apply a settled activation. The whole change — the preference,
|
|
753
601
|
* the projections it implies, and any instruction file it shares — runs in
|
|
754
602
|
* one workspace transaction, so a projection that cannot be written never
|
|
755
603
|
* leaves the preference recorded.
|
|
756
604
|
*/
|
|
757
|
-
|
|
605
|
+
const prepareActivationExecution = (candidate) => Effect.gen(function* () {
|
|
758
606
|
const plan = {
|
|
759
607
|
_tag: "Plan",
|
|
760
608
|
name: `${candidate.enabled ? "Enable" : "Disable"} ${PLAN_SUBJECT[candidate.type]}`,
|
|
@@ -775,8 +623,15 @@ export const previewOrApplySetActivation = (candidate, execution) => Effect.gen(
|
|
|
775
623
|
},
|
|
776
624
|
];
|
|
777
625
|
const prepared = yield* prepareExecutionCandidate(plan, { configuredAgentOperations });
|
|
778
|
-
return
|
|
626
|
+
return { ...candidate, execution: prepared };
|
|
627
|
+
});
|
|
628
|
+
export const prepareSetActivation = (request) => Effect.gen(function* () {
|
|
629
|
+
const candidate = yield* settleActivation(request);
|
|
630
|
+
if (candidate._tag === "Unchanged")
|
|
631
|
+
return candidate;
|
|
632
|
+
return yield* prepareActivationExecution(candidate);
|
|
779
633
|
});
|
|
634
|
+
export const previewOrApplySetActivation = (candidate, execution) => resolveExecutionCandidate(candidate.execution, execution);
|
|
780
635
|
/** The activation use case. */
|
|
781
636
|
export const SetActivation = {
|
|
782
637
|
prepare: prepareSetActivation,
|