@agentxm/extension-lifecycle 0.29.2 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/activation/set-activation.d.ts +22 -40
- package/dist/src/activation/set-activation.js +199 -331
- 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/configured-agent-outcomes-provider.d.ts +4 -4
- package/dist/src/hooks/configured-agent-outcomes-provider.js +23 -17
- 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/live.d.ts +1 -1
- package/dist/src/live.js +1 -1
- package/dist/src/mcps/install/plan.js +2 -3
- package/dist/src/mcps/operations/disable.js +1 -1
- package/dist/src/mcps/operations/enable.js +20 -35
- 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,157 +160,93 @@ export const prepareSetActivation = (request) => Effect.gen(function* () {
|
|
|
182
160
|
agentOutcomes: [],
|
|
183
161
|
};
|
|
184
162
|
}
|
|
185
|
-
case "mcp-server":
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return unchanged(request, request.name, `MCP server "${request.name}" is already ${verb}`);
|
|
193
|
-
}
|
|
194
|
-
return {
|
|
195
|
-
_tag: "SetActivation",
|
|
196
|
-
type: "mcp-server",
|
|
197
|
-
name: request.name,
|
|
198
|
-
enabled: request.enabled,
|
|
199
|
-
scope,
|
|
200
|
-
artifact: settingsArtifact(scope),
|
|
201
|
-
transition: { kind: "mcp-server" },
|
|
202
|
-
instructions: Option.none(),
|
|
203
|
-
blocked: Option.none(),
|
|
204
|
-
warning: Option.none(),
|
|
205
|
-
agentOutcomes: [],
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
case "rule": {
|
|
209
|
-
const configured = yield* ws.getConfiguredRuleEntries();
|
|
210
|
-
const entry = configured[request.name];
|
|
211
|
-
if (entry === undefined) {
|
|
212
|
-
return unchanged(request, request.name, `rule "${request.name}" is not configured`);
|
|
213
|
-
}
|
|
214
|
-
if (entry.enabled === request.enabled) {
|
|
215
|
-
return unchanged(request, request.name, `rule "${request.name}" is already ${verb}`);
|
|
216
|
-
}
|
|
217
|
-
const gate = yield* instructionGate(ws);
|
|
218
|
-
const resolved = request.enabled
|
|
219
|
-
? Option.some(yield* resolveConfiguredRule(request.name, entry.source, yield* makeConfiguredReleaseAgeEvaluation()))
|
|
220
|
-
: Option.none();
|
|
221
|
-
return {
|
|
222
|
-
_tag: "SetActivation",
|
|
223
|
-
type: "rule",
|
|
224
|
-
name: request.name,
|
|
225
|
-
enabled: request.enabled,
|
|
226
|
-
scope,
|
|
227
|
-
artifact: settingsArtifact(scope),
|
|
228
|
-
transition: Option.match(resolved, {
|
|
229
|
-
onNone: () => ({ kind: "deactivate-rule" }),
|
|
230
|
-
onSome: ({ ref, versionRange }) => ({
|
|
231
|
-
kind: "reinstall",
|
|
232
|
-
ref,
|
|
233
|
-
versionRange,
|
|
234
|
-
}),
|
|
235
|
-
}),
|
|
236
|
-
instructions: gate.config,
|
|
237
|
-
blocked: gate.blocked,
|
|
238
|
-
warning: Option.none(),
|
|
239
|
-
agentOutcomes: [],
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
case "hook": {
|
|
243
|
-
const configured = yield* ws.getConfiguredHookEntries();
|
|
244
|
-
const entry = configured[request.name];
|
|
245
|
-
if (entry === undefined) {
|
|
246
|
-
return unchanged(request, request.name, `hooks package "${request.name}" is not configured`);
|
|
247
|
-
}
|
|
248
|
-
if (entry.enabled === request.enabled) {
|
|
249
|
-
return unchanged(request, request.name, `hooks package "${request.name}" is already ${verb}`);
|
|
250
|
-
}
|
|
251
|
-
const lockEntry = yield* ws
|
|
252
|
-
.getLockedHookEntry(request.name)
|
|
253
|
-
.pipe(Effect.catch(() => Effect.succeed(Option.none())));
|
|
254
|
-
const artifact = hookLockArtifact({
|
|
255
|
-
lockEntry,
|
|
256
|
-
name: request.name,
|
|
257
|
-
scope,
|
|
258
|
-
enabled: request.enabled,
|
|
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,
|
|
259
170
|
});
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
};
|
|
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`);
|
|
274
184
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
_tag: "SetActivation",
|
|
284
|
-
type: "hook",
|
|
285
|
-
name: request.name,
|
|
286
|
-
enabled: true,
|
|
287
|
-
scope,
|
|
288
|
-
artifact,
|
|
289
|
-
transition: { kind: "reinstall", ref, versionRange },
|
|
290
|
-
instructions: Option.none(),
|
|
291
|
-
blocked: Option.none(),
|
|
292
|
-
warning: Option.none(),
|
|
293
|
-
agentOutcomes,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
case "knowledge": {
|
|
297
|
-
const configured = yield* ws.getConfiguredKnowledgeEntries();
|
|
298
|
-
const entry = configured[request.name];
|
|
299
|
-
if (entry === undefined) {
|
|
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) {
|
|
300
193
|
return yield* new ExtensionLifecycleFailed({
|
|
301
|
-
category: "
|
|
302
|
-
detail:
|
|
194
|
+
category: "conflict",
|
|
195
|
+
detail: "Activation requires complete desired state",
|
|
303
196
|
});
|
|
304
197
|
}
|
|
305
|
-
|
|
306
|
-
|
|
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
|
+
}
|
|
307
215
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
name: request.name,
|
|
313
|
-
enabled: false,
|
|
314
|
-
scope,
|
|
315
|
-
artifact: settingsArtifact(scope),
|
|
316
|
-
transition: { kind: "deactivate-knowledge" },
|
|
317
|
-
instructions: Option.none(),
|
|
216
|
+
const gate = request.type === "rule"
|
|
217
|
+
? yield* instructionGate(ws)
|
|
218
|
+
: {
|
|
219
|
+
config: Option.none(),
|
|
318
220
|
blocked: Option.none(),
|
|
319
|
-
warning: Option.none(),
|
|
320
|
-
agentOutcomes: [],
|
|
321
221
|
};
|
|
322
|
-
|
|
323
|
-
const
|
|
222
|
+
const entry = settingsEntries["mcp-server"].entry(proposal.settings, name);
|
|
223
|
+
const managers = yield* ExtensionManagers;
|
|
224
|
+
const agentOutcomes = request.type === "mcp-server" && request.enabled && Option.isSome(entry)
|
|
225
|
+
? yield* managers["mcp-server"].configuredAgentOutcomesForEntry({
|
|
226
|
+
name,
|
|
227
|
+
entry: entry.value,
|
|
228
|
+
state: "projected",
|
|
229
|
+
})
|
|
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
|
+
: [];
|
|
324
235
|
return {
|
|
325
236
|
_tag: "SetActivation",
|
|
326
|
-
type:
|
|
327
|
-
name
|
|
328
|
-
enabled:
|
|
237
|
+
type: request.type,
|
|
238
|
+
name,
|
|
239
|
+
enabled: request.enabled,
|
|
329
240
|
scope,
|
|
330
241
|
artifact: settingsArtifact(scope),
|
|
331
|
-
transition:
|
|
332
|
-
|
|
333
|
-
|
|
242
|
+
transition: request.type === "mcp-server"
|
|
243
|
+
? { kind: "mcp-server" }
|
|
244
|
+
: { kind: "aggregate", proposal },
|
|
245
|
+
proposal,
|
|
246
|
+
instructions: gate.config,
|
|
247
|
+
blocked: gate.blocked,
|
|
334
248
|
warning: Option.none(),
|
|
335
|
-
agentOutcomes
|
|
249
|
+
agentOutcomes,
|
|
336
250
|
};
|
|
337
251
|
}
|
|
338
252
|
case "pack": {
|
|
@@ -348,7 +262,14 @@ export const prepareSetActivation = (request) => Effect.gen(function* () {
|
|
|
348
262
|
if (entry.enabled === request.enabled) {
|
|
349
263
|
return unchanged(request, request.name, `Pack "${request.name}" is already ${verb}`);
|
|
350
264
|
}
|
|
351
|
-
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;
|
|
352
273
|
if (!graph.complete) {
|
|
353
274
|
return yield* new ExtensionLifecycleFailed({
|
|
354
275
|
category: "conflict",
|
|
@@ -370,12 +291,52 @@ export const prepareSetActivation = (request) => Effect.gen(function* () {
|
|
|
370
291
|
node.enabled &&
|
|
371
292
|
packContributesTo(node, identity) &&
|
|
372
293
|
!remainsActiveWithoutPack(node, identity));
|
|
373
|
-
const contributed =
|
|
294
|
+
const contributed = proposal.after.nodes.filter((node) => node.type !== "pack" && packContributesTo(node, identity));
|
|
374
295
|
const members = request.enabled ? contributed : affected;
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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();
|
|
379
340
|
return {
|
|
380
341
|
_tag: "SetActivation",
|
|
381
342
|
type: "pack",
|
|
@@ -386,11 +347,22 @@ export const prepareSetActivation = (request) => Effect.gen(function* () {
|
|
|
386
347
|
path: settingsDisplayPath(scope),
|
|
387
348
|
scope,
|
|
388
349
|
change: "updated",
|
|
389
|
-
fileCount:
|
|
390
|
-
|
|
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
|
+
],
|
|
391
360
|
},
|
|
392
361
|
transition: {
|
|
393
362
|
kind: "pack",
|
|
363
|
+
proposal,
|
|
364
|
+
retirement,
|
|
365
|
+
...(materialization === undefined ? {} : { materialization }),
|
|
394
366
|
identity,
|
|
395
367
|
members,
|
|
396
368
|
aggregateTypes: new Set(members.map((node) => node.type)),
|
|
@@ -424,7 +396,7 @@ const dematerializeMember = (node) => Effect.gen(function* () {
|
|
|
424
396
|
workspaceRoot: ws.baseDir,
|
|
425
397
|
scope: ws.scope,
|
|
426
398
|
serverName: node.name,
|
|
427
|
-
disableOnly:
|
|
399
|
+
disableOnly: false,
|
|
428
400
|
})
|
|
429
401
|
.pipe(Effect.flatMap((outcome) => outcome._tag === "disabled" ||
|
|
430
402
|
outcome._tag === "nothing-runnable" ||
|
|
@@ -508,98 +480,38 @@ const validatePackActivation = (candidate) => Effect.gen(function* () {
|
|
|
508
480
|
});
|
|
509
481
|
}
|
|
510
482
|
});
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const managers = yield* ExtensionManagers;
|
|
516
|
-
yield* ws.updateRuleEntry(candidate.name, (current) => ({ ...current, enabled: false }));
|
|
517
|
-
yield* managers.rule.materializeDeactivate({
|
|
518
|
-
target: { type: "rule", name: candidate.name },
|
|
519
|
-
});
|
|
520
|
-
return NO_WARNINGS;
|
|
521
|
-
});
|
|
522
|
-
/** Disabling a hooks package: record the preference, then stop projecting it. */
|
|
523
|
-
const deactivateHookTransition = (candidate) => Effect.gen(function* () {
|
|
524
|
-
const ws = yield* WorkspaceMutations;
|
|
525
|
-
const managers = yield* ExtensionManagers;
|
|
526
|
-
yield* ws.updateHookEntry(candidate.name, (current) => ({ ...current, enabled: false }));
|
|
527
|
-
yield* managers.hook.materializeDeactivate({
|
|
528
|
-
target: { type: "hook", name: candidate.name },
|
|
529
|
-
});
|
|
530
|
-
return NO_WARNINGS;
|
|
531
|
-
});
|
|
532
|
-
/**
|
|
533
|
-
* Disabling a Knowledge bundle: record the preference, then re-render the
|
|
534
|
-
* discovery region. Re-rendering is what deactivation means for Knowledge, so
|
|
535
|
-
* this step carries the region's own exclusion report.
|
|
536
|
-
*/
|
|
537
|
-
const deactivateKnowledgeTransition = (candidate) => Effect.gen(function* () {
|
|
538
|
-
const ws = yield* WorkspaceMutations;
|
|
539
|
-
const managers = yield* ExtensionManagers;
|
|
540
|
-
yield* ws.updateKnowledgeEntry(candidate.name, (current) => ({ ...current, enabled: false }));
|
|
541
|
-
const plans = yield* managers.knowledge.projectionPlans();
|
|
542
|
-
yield* applyProjectionPlans(plans);
|
|
543
|
-
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]));
|
|
544
487
|
});
|
|
545
488
|
/** Enabling a Pack, or disabling it and the members it alone kept active. */
|
|
546
489
|
const packTransition = (candidate, transition) => Effect.gen(function* () {
|
|
547
|
-
|
|
548
|
-
const conversion = yield* StepFailureConversion;
|
|
549
|
-
const configured = yield* ws.getConfiguredPackEntries();
|
|
550
|
-
const entry = configured[candidate.name];
|
|
551
|
-
if (entry === undefined) {
|
|
552
|
-
return yield* new ExtensionLifecycleFailed({
|
|
553
|
-
category: "not_found",
|
|
554
|
-
detail: `Pack "${candidate.name}" is no longer configured`,
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
yield* ws.setPackEntry(candidate.name, { ...entry, enabled: candidate.enabled });
|
|
490
|
+
yield* publishDesiredState(transition.proposal);
|
|
558
491
|
if (candidate.enabled) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
// operator can actually take.
|
|
568
|
-
const { steps } = yield* collectRetainedMaterializeSteps({
|
|
569
|
-
nodes: activeMembers,
|
|
570
|
-
adapter: { toStepFailure: conversion.toStepFailure },
|
|
571
|
-
runMcpServerInstall: ({ ref }) => installMcpServer({
|
|
572
|
-
name: "install-mcp-server",
|
|
573
|
-
args: {
|
|
574
|
-
ref,
|
|
575
|
-
nonInteractive: true,
|
|
576
|
-
force: false,
|
|
577
|
-
allowWorkspaceSourceTransition: false,
|
|
578
|
-
versionRange: Option.none(),
|
|
579
|
-
skipSettings: Option.some(true),
|
|
580
|
-
},
|
|
581
|
-
}).pipe(withAdaptedStepFailures),
|
|
582
|
-
}).pipe(Effect.mapError((failure) => failure instanceof RetainedContentUnusable
|
|
583
|
-
? new ExtensionLifecycleFailed({
|
|
584
|
-
category: "conflict",
|
|
585
|
-
detail: failure.detail,
|
|
586
|
-
suggestions: [
|
|
587
|
-
{
|
|
588
|
-
description: "Refresh the pack and its retained members",
|
|
589
|
-
cmd: "axm packs update --yes",
|
|
590
|
-
},
|
|
591
|
-
],
|
|
592
|
-
cause: failure,
|
|
593
|
-
})
|
|
594
|
-
: failure));
|
|
595
|
-
yield* Effect.forEach(steps, (step) => step.readiness === "error"
|
|
596
|
-
? Effect.fail(new ExtensionLifecycleFailed({ category: "conflict", detail: step.errorMessage }))
|
|
597
|
-
: 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
|
+
}
|
|
598
500
|
}
|
|
599
501
|
else {
|
|
600
502
|
yield* Effect.forEach(transition.members.filter((node) => !AGGREGATE_UNIT_TYPES.has(node.type)), dematerializeMember, { concurrency: 1, discard: true });
|
|
601
503
|
}
|
|
602
|
-
|
|
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;
|
|
603
515
|
});
|
|
604
516
|
/**
|
|
605
517
|
* The step for a type whose executor owns its whole transition, including the
|
|
@@ -617,7 +529,18 @@ const executorStep = (candidate, kind) => {
|
|
|
617
529
|
: candidate.enabled
|
|
618
530
|
? enableMcpServer({ name: "enable-mcp-server", args: { serverName: candidate.name } })
|
|
619
531
|
: disableMcpServer({ name: "disable-mcp-server", args: { serverName: candidate.name } });
|
|
620
|
-
|
|
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);
|
|
538
|
+
return {
|
|
539
|
+
label: candidate.name,
|
|
540
|
+
readiness: "ready",
|
|
541
|
+
...(candidate.agentOutcomes.length === 0 ? {} : { agentOutcomes: candidate.agentOutcomes }),
|
|
542
|
+
run: applied,
|
|
543
|
+
};
|
|
621
544
|
};
|
|
622
545
|
/**
|
|
623
546
|
* The step for a type whose transition this use case composes itself: the
|
|
@@ -663,14 +586,8 @@ const activationStep = (candidate) => {
|
|
|
663
586
|
case "subagent":
|
|
664
587
|
case "mcp-server":
|
|
665
588
|
return executorStep(candidate, transition.kind);
|
|
666
|
-
case "
|
|
667
|
-
return
|
|
668
|
-
case "deactivate-rule":
|
|
669
|
-
return transactionStep(candidate, deactivateRuleTransition(candidate), () => Effect.void);
|
|
670
|
-
case "deactivate-hook":
|
|
671
|
-
return transactionStep(candidate, deactivateHookTransition(candidate), () => Effect.void);
|
|
672
|
-
case "deactivate-knowledge":
|
|
673
|
-
return transactionStep(candidate, deactivateKnowledgeTransition(candidate), () => Effect.void);
|
|
589
|
+
case "aggregate":
|
|
590
|
+
return transactionStep(candidate, aggregateTransition(transition.proposal, candidate.type), () => Effect.void);
|
|
674
591
|
case "pack":
|
|
675
592
|
return transactionStep(candidate, packTransition(candidate, transition), () => validatePackActivation({
|
|
676
593
|
name: candidate.name,
|
|
@@ -679,69 +596,13 @@ const activationStep = (candidate) => {
|
|
|
679
596
|
}));
|
|
680
597
|
}
|
|
681
598
|
};
|
|
682
|
-
/**
|
|
683
|
-
* Enabling a rule, hooks package, or Knowledge bundle re-runs the configured
|
|
684
|
-
* install closure for the entry the workspace already accepted, so the same
|
|
685
|
-
* publisher-trust and release-age evidence a first install carries travels
|
|
686
|
-
* with the re-activation.
|
|
687
|
-
*/
|
|
688
|
-
const reinstallStep = (candidate, transition) => {
|
|
689
|
-
const { artifact } = candidate;
|
|
690
|
-
const install = (managers, toStepFailure) => {
|
|
691
|
-
const common = {
|
|
692
|
-
toStepFailure,
|
|
693
|
-
versionRange: transition.versionRange,
|
|
694
|
-
message: `Enabled ${candidate.name}`,
|
|
695
|
-
buildArtifact: () => Effect.succeed(artifact),
|
|
696
|
-
};
|
|
697
|
-
switch (transition.ref.type) {
|
|
698
|
-
case "rule":
|
|
699
|
-
return buildInstallOperation(managers.rule, { ...common, ref: transition.ref });
|
|
700
|
-
case "hook":
|
|
701
|
-
return buildInstallOperation(managers.hook, { ...common, ref: transition.ref });
|
|
702
|
-
case "knowledge":
|
|
703
|
-
return buildInstallOperation(managers.knowledge, { ...common, ref: transition.ref });
|
|
704
|
-
case "skill":
|
|
705
|
-
return buildInstallOperation(managers.skill, { ...common, ref: transition.ref });
|
|
706
|
-
case "subagent":
|
|
707
|
-
return buildInstallOperation(managers.subagent, { ...common, ref: transition.ref });
|
|
708
|
-
case "mcp-server":
|
|
709
|
-
return buildInstallOperation(managers["mcp-server"], { ...common, ref: transition.ref });
|
|
710
|
-
case "pack":
|
|
711
|
-
return buildInstallOperation(managers.pack, { ...common, ref: transition.ref });
|
|
712
|
-
}
|
|
713
|
-
};
|
|
714
|
-
const run = Effect.gen(function* () {
|
|
715
|
-
const ws = yield* WorkspaceMutations;
|
|
716
|
-
const managers = yield* ExtensionManagers;
|
|
717
|
-
const conversion = yield* StepFailureConversion;
|
|
718
|
-
const step = install(managers, conversion.toStepFailure);
|
|
719
|
-
if (step.readiness === "error") {
|
|
720
|
-
return yield* new StepFailure({ category: "conflict", detail: step.errorMessage });
|
|
721
|
-
}
|
|
722
|
-
return yield* Option.match(candidate.instructions, {
|
|
723
|
-
onNone: () => step.run,
|
|
724
|
-
onSome: (config) => runWorkspaceTransaction({
|
|
725
|
-
transition: reconcileInstructionTransition({ ws, config, transition: step.run }),
|
|
726
|
-
validate: () => Effect.void,
|
|
727
|
-
}).pipe(withAdaptedStepFailures),
|
|
728
|
-
});
|
|
729
|
-
});
|
|
730
|
-
return {
|
|
731
|
-
label: candidate.name,
|
|
732
|
-
readiness: "ready",
|
|
733
|
-
artifact,
|
|
734
|
-
...(candidate.agentOutcomes.length === 0 ? {} : { agentOutcomes: candidate.agentOutcomes }),
|
|
735
|
-
run,
|
|
736
|
-
};
|
|
737
|
-
};
|
|
738
599
|
/**
|
|
739
600
|
* Preview or apply a settled activation. The whole change — the preference,
|
|
740
601
|
* the projections it implies, and any instruction file it shares — runs in
|
|
741
602
|
* one workspace transaction, so a projection that cannot be written never
|
|
742
603
|
* leaves the preference recorded.
|
|
743
604
|
*/
|
|
744
|
-
|
|
605
|
+
const prepareActivationExecution = (candidate) => Effect.gen(function* () {
|
|
745
606
|
const plan = {
|
|
746
607
|
_tag: "Plan",
|
|
747
608
|
name: `${candidate.enabled ? "Enable" : "Disable"} ${PLAN_SUBJECT[candidate.type]}`,
|
|
@@ -762,8 +623,15 @@ export const previewOrApplySetActivation = (candidate, execution) => Effect.gen(
|
|
|
762
623
|
},
|
|
763
624
|
];
|
|
764
625
|
const prepared = yield* prepareExecutionCandidate(plan, { configuredAgentOperations });
|
|
765
|
-
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);
|
|
766
633
|
});
|
|
634
|
+
export const previewOrApplySetActivation = (candidate, execution) => resolveExecutionCandidate(candidate.execution, execution);
|
|
767
635
|
/** The activation use case. */
|
|
768
636
|
export const SetActivation = {
|
|
769
637
|
prepare: prepareSetActivation,
|