@agentxm/extension-lifecycle 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.
- package/dist/src/activation/display-paths.d.ts +25 -0
- package/dist/src/activation/display-paths.js +25 -0
- package/dist/src/activation/errors.d.ts +16 -0
- package/dist/src/activation/errors.js +7 -0
- package/dist/src/activation/set-activation.d.ts +134 -0
- package/dist/src/activation/set-activation.js +772 -0
- package/dist/src/demote/demote-to-external-source.d.ts +82 -0
- package/dist/src/demote/demote-to-external-source.js +233 -0
- package/dist/src/errors.d.ts +0 -8
- package/dist/src/errors.js +0 -8
- package/dist/src/glob.d.ts +35 -0
- package/dist/src/glob.js +75 -0
- package/dist/src/hooks/configured-agent-outcomes-provider.d.ts +7 -3
- package/dist/src/hooks/configured-agent-outcomes-provider.js +11 -4
- package/dist/src/hooks/install/plan.d.ts +45 -0
- package/dist/src/hooks/install/plan.js +224 -0
- package/dist/src/hooks/uninstall/plan.d.ts +20 -0
- package/dist/src/hooks/uninstall/plan.js +77 -0
- package/dist/src/index.d.ts +48 -16
- package/dist/src/index.js +60 -17
- package/dist/src/install/aggregate-projection-step.d.ts +16 -0
- package/dist/src/install/aggregate-projection-step.js +49 -0
- package/dist/src/install/configured.d.ts +65 -0
- package/dist/src/install/configured.js +406 -0
- package/dist/src/install/inline-mcp-operation.d.ts +4 -0
- package/dist/src/install/inline-mcp-operation.js +25 -0
- package/dist/src/install/install-extensions.d.ts +110 -0
- package/dist/src/install/install-extensions.js +282 -0
- package/dist/src/install/per-type-install.d.ts +12 -0
- package/dist/src/install/per-type-install.js +12 -0
- package/dist/src/install/registry-install-target.d.ts +35 -0
- package/dist/src/install/registry-install-target.js +66 -0
- package/dist/src/install/registry-login-suggestion.d.ts +21 -0
- package/dist/src/install/registry-login-suggestion.js +34 -0
- package/dist/src/install/registry-source-resolution.d.ts +54 -0
- package/dist/src/install/registry-source-resolution.js +216 -0
- package/dist/src/install/root-intent.d.ts +19 -0
- package/dist/src/install/root-intent.js +122 -0
- package/dist/src/install/selection-interaction.d.ts +46 -0
- package/dist/src/install/selection-interaction.js +22 -0
- package/dist/src/install/vocabulary.d.ts +143 -0
- package/dist/src/install/vocabulary.js +29 -0
- package/dist/src/knowledge/install/plan.d.ts +35 -0
- package/dist/src/knowledge/install/plan.js +128 -0
- package/dist/src/knowledge/uninstall/plan.d.ts +23 -0
- package/dist/src/knowledge/uninstall/plan.js +140 -0
- package/dist/src/live.d.ts +4 -11
- package/dist/src/live.js +4 -11
- package/dist/src/mcps/install/plan.d.ts +65 -0
- package/dist/src/mcps/install/plan.js +301 -0
- package/dist/src/mcps/operations/disable.d.ts +5 -3
- package/dist/src/mcps/operations/disable.js +6 -4
- package/dist/src/mcps/operations/enable.d.ts +5 -3
- package/dist/src/mcps/operations/enable.js +8 -6
- package/dist/src/mcps/operations/sync-outcome.d.ts +1 -1
- package/dist/src/mcps/operations/uninstall.d.ts +5 -3
- package/dist/src/mcps/operations/uninstall.js +4 -4
- package/dist/src/mcps/uninstall/plan.d.ts +24 -0
- package/dist/src/mcps/uninstall/plan.js +109 -0
- package/dist/src/packs/artifact.d.ts +21 -0
- package/dist/src/packs/artifact.js +32 -0
- package/dist/src/packs/constraint-gate.d.ts +30 -0
- package/dist/src/packs/constraint-gate.js +76 -0
- package/dist/src/packs/expansion.d.ts +13 -14
- package/dist/src/packs/expansion.js +1 -2
- package/dist/src/packs/graph-transition.d.ts +64 -0
- package/dist/src/packs/graph-transition.js +219 -0
- package/dist/src/packs/install/plan.d.ts +95 -0
- package/dist/src/packs/install/plan.js +845 -0
- package/dist/src/packs/member-install-step.d.ts +29 -0
- package/dist/src/packs/member-install-step.js +156 -0
- package/dist/src/packs/operations/install.d.ts +3 -3
- package/dist/src/packs/operations/install.js +5 -5
- package/dist/src/packs/promote-authored-pack.d.ts +86 -0
- package/dist/src/packs/promote-authored-pack.js +239 -0
- package/dist/src/packs/resolved-dependency.d.ts +9 -26
- package/dist/src/packs/resolved-dependency.js +8 -20
- package/dist/src/packs/uninstall/plan.d.ts +66 -0
- package/dist/src/packs/uninstall/plan.js +344 -0
- package/dist/src/packs/uninstall/readiness.d.ts +50 -0
- package/dist/src/packs/uninstall/readiness.js +130 -0
- package/dist/src/publisher-binding.d.ts +8 -40
- package/dist/src/publisher-binding.js +8 -46
- package/dist/src/rules/install/plan.d.ts +35 -0
- package/dist/src/rules/install/plan.js +133 -0
- package/dist/src/rules/uninstall/plan.d.ts +19 -0
- package/dist/src/rules/uninstall/plan.js +54 -0
- package/dist/src/skills/install/bundled.d.ts +73 -0
- package/dist/src/skills/install/bundled.js +244 -0
- package/dist/src/skills/install/plan.d.ts +72 -0
- package/dist/src/skills/install/plan.js +429 -0
- package/dist/src/skills/install/selection.d.ts +26 -0
- package/dist/src/skills/install/selection.js +41 -0
- package/dist/src/skills/install/source.d.ts +21 -0
- package/dist/src/skills/install/source.js +76 -0
- package/dist/src/skills/operations/disable.d.ts +3 -2
- package/dist/src/skills/operations/disable.js +7 -11
- package/dist/src/skills/operations/enable.d.ts +3 -2
- package/dist/src/skills/operations/enable.js +6 -8
- package/dist/src/skills/operations/install.d.ts +4 -4
- package/dist/src/skills/operations/install.js +13 -12
- package/dist/src/skills/uninstall/plan.d.ts +23 -0
- package/dist/src/skills/uninstall/plan.js +165 -0
- package/dist/src/skills/utils.js +1 -1
- package/dist/src/step-failure-conversion.d.ts +44 -0
- package/dist/src/step-failure-conversion.js +26 -0
- package/dist/src/step-failure.d.ts +29 -0
- package/dist/src/step-failure.js +130 -0
- package/dist/src/subagents/install/plan.d.ts +49 -0
- package/dist/src/subagents/install/plan.js +270 -0
- package/dist/src/subagents/install/selection.d.ts +26 -0
- package/dist/src/subagents/install/selection.js +37 -0
- package/dist/src/subagents/install/source.d.ts +18 -0
- package/dist/src/subagents/install/source.js +72 -0
- package/dist/src/subagents/operations/disable.d.ts +5 -3
- package/dist/src/subagents/operations/disable.js +5 -3
- package/dist/src/subagents/operations/enable.d.ts +5 -3
- package/dist/src/subagents/operations/enable.js +17 -12
- package/dist/src/subagents/uninstall/plan.d.ts +20 -0
- package/dist/src/subagents/uninstall/plan.js +142 -0
- package/dist/src/test-packages.d.ts +34 -0
- package/dist/src/test-packages.js +111 -0
- package/dist/src/test-registry.d.ts +78 -0
- package/dist/src/test-registry.js +223 -0
- package/dist/src/testing.d.ts +108 -0
- package/dist/src/testing.js +241 -0
- package/dist/src/uninstall/retention-policy.d.ts +21 -0
- package/dist/src/uninstall/retention-policy.js +28 -0
- package/dist/src/uninstall/root-intent.d.ts +17 -0
- package/dist/src/uninstall/root-intent.js +101 -0
- package/dist/src/uninstall/uninstall-extensions.d.ts +61 -0
- package/dist/src/uninstall/uninstall-extensions.js +127 -0
- package/dist/src/uninstall/vocabulary.d.ts +40 -0
- package/dist/src/uninstall/vocabulary.js +14 -0
- package/dist/src/update/atomicity.d.ts +22 -0
- package/dist/src/update/atomicity.js +23 -0
- package/dist/src/update/blockers.d.ts +31 -0
- package/dist/src/update/blockers.js +78 -0
- package/dist/src/update/configured.d.ts +78 -0
- package/dist/src/update/configured.js +546 -0
- package/dist/src/update/root-request.d.ts +39 -0
- package/dist/src/update/root-request.js +117 -0
- package/dist/src/update/selective/plan.d.ts +45 -0
- package/dist/src/update/selective/plan.js +61 -0
- package/dist/src/update/selective/requirements.d.ts +17 -0
- package/dist/src/update/selective/requirements.js +14 -0
- package/dist/src/update/selective/selection.d.ts +63 -0
- package/dist/src/update/selective/selection.js +85 -0
- package/dist/src/update/selective/skills.d.ts +55 -0
- package/dist/src/update/selective/skills.js +596 -0
- package/dist/src/update/selective/subagents.d.ts +52 -0
- package/dist/src/update/selective/subagents.js +331 -0
- package/dist/src/update/selective/use-case.d.ts +121 -0
- package/dist/src/update/selective/use-case.js +28 -0
- package/dist/src/update/selective/vocabulary.d.ts +43 -0
- package/dist/src/update/selective/vocabulary.js +28 -0
- package/dist/src/update/selector.d.ts +72 -0
- package/dist/src/update/selector.js +120 -0
- package/dist/src/update/targeted-plan.d.ts +30 -0
- package/dist/src/update/targeted-plan.js +113 -0
- package/dist/src/update/update-extensions.d.ts +164 -0
- package/dist/src/update/update-extensions.js +579 -0
- package/dist/src/workspace-paths.d.ts +31 -0
- package/dist/src/workspace-paths.js +46 -0
- package/package.json +22 -12
- package/dist/src/configured-entry-resolution.d.ts +0 -111
- package/dist/src/configured-entry-resolution.js +0 -731
- package/dist/src/failure-adapter.d.ts +0 -35
- package/dist/src/failure-adapter.js +0 -23
- package/dist/src/hooks/manager.d.ts +0 -15
- package/dist/src/hooks/manager.js +0 -769
- package/dist/src/internal/fs-helpers.d.ts +0 -12
- package/dist/src/internal/fs-helpers.js +0 -13
- package/dist/src/internal/integrity.d.ts +0 -13
- package/dist/src/internal/integrity.js +0 -17
- package/dist/src/knowledge/manager.d.ts +0 -11
- package/dist/src/knowledge/manager.js +0 -711
- package/dist/src/mcps/manager.d.ts +0 -16
- package/dist/src/mcps/manager.js +0 -247
- package/dist/src/mcps/operations/artifact.d.ts +0 -24
- package/dist/src/mcps/operations/artifact.js +0 -74
- package/dist/src/mcps/operations/install.d.ts +0 -78
- package/dist/src/mcps/operations/install.js +0 -596
- package/dist/src/packs/dependency-resolution.d.ts +0 -65
- package/dist/src/packs/dependency-resolution.js +0 -414
- package/dist/src/packs/manager.d.ts +0 -16
- package/dist/src/packs/manager.js +0 -181
- package/dist/src/registry-materialization.d.ts +0 -52
- package/dist/src/registry-materialization.js +0 -72
- package/dist/src/release-age-posture.d.ts +0 -23
- package/dist/src/release-age-posture.js +0 -15
- package/dist/src/resolution-timeout.d.ts +0 -5
- package/dist/src/resolution-timeout.js +0 -11
- package/dist/src/rules/manager.d.ts +0 -20
- package/dist/src/rules/manager.js +0 -480
- package/dist/src/skills/manager.d.ts +0 -18
- package/dist/src/skills/manager.js +0 -346
- package/dist/src/skills/materialization.d.ts +0 -54
- package/dist/src/skills/materialization.js +0 -158
- package/dist/src/skills/operations/source-hash.d.ts +0 -2
- package/dist/src/skills/operations/source-hash.js +0 -6
- package/dist/src/subagents/manager.d.ts +0 -18
- package/dist/src/subagents/manager.js +0 -722
- package/dist/src/workflows/install-command/workflow.d.ts +0 -56
- package/dist/src/workflows/install-command/workflow.js +0 -46
- package/dist/src/workflows/uninstall-command/workflow.d.ts +0 -42
- package/dist/src/workflows/uninstall-command/workflow.js +0 -29
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activation: turning one installed extension on or off without changing
|
|
3
|
+
* what the workspace acquired.
|
|
4
|
+
*
|
|
5
|
+
* All seven extension types share one request, one settled candidate, and one
|
|
6
|
+
* resolution, because activation means the same thing for every type: desired
|
|
7
|
+
* state records the preference, and the projections that preference implies
|
|
8
|
+
* are created or removed. What differs is only how a type is projected — a
|
|
9
|
+
* skill through agent artifacts, an MCP server through each agent's native
|
|
10
|
+
* configuration, a rule or Knowledge bundle through a shared managed region,
|
|
11
|
+
* a Pack through the members it contributes.
|
|
12
|
+
*
|
|
13
|
+
* Two invariants hold across every type. Disabling never removes canonical
|
|
14
|
+
* content or retires an accepted resolution, so re-enabling never re-resolves
|
|
15
|
+
* or re-acquires. And a type whose projection shares a file with instruction
|
|
16
|
+
* management reconciles that file inside the same transaction, so the
|
|
17
|
+
* workspace is never left with a settings change the files do not reflect.
|
|
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 { resolveInstalledIdentifierNameOrInput } from "@agentxm/extension-sources";
|
|
27
|
+
import { SourceHostProviders, WorkspaceCatalog, } from "@agentxm/extension-sources";
|
|
28
|
+
import { buildInstallOperation, collectRetainedMaterializeSteps, ExtensionManagers, installMcpServer, RetainedContentUnusable, SubagentManager, } from "@agentxm/extension-materialization";
|
|
29
|
+
import { makeConfiguredReleaseAgeEvaluation, ReleaseAgePosture, resolveConfiguredHook, resolveConfiguredKnowledge, resolveConfiguredRule, } from "@agentxm/extension-resolution";
|
|
30
|
+
import { activeInstructionsConfig, applyProjectionPlans, CodingAgentRepository, findManagedSubagentFiles, instructionReconciliationReadiness, observeInstructions, projectionPlanExclusionWarnings, reconcileInstructionTransition, } from "@agentxm/workspace-projection";
|
|
31
|
+
import { OperationJournal, operationPresentation, prepareExecutionCandidate, resolveExecutionCandidate, ResolvePlanInteraction, StepFailure, } from "@agentxm/workspace-operations";
|
|
32
|
+
import { acquiredExtensionDisplayPathFromLockEntry, ConfiguredAgentOutcomesProvider, installedRowsByName, isDesiredExtensionActive, RenderedFilePathSchema, sanitizeName, WorkspaceMutations, } from "@agentxm/workspace-state";
|
|
33
|
+
import { FootprintRecorder, runWorkspaceTransaction, WorkspaceTransactionScope, } from "@agentxm/workspace-transactions";
|
|
34
|
+
import { ExtensionLifecycleFailed } from "../errors.js";
|
|
35
|
+
import { StepFailureConversion, withAdaptedStepFailures, } from "../step-failure-conversion.js";
|
|
36
|
+
import { enableMcpServer } from "../mcps/operations/enable.js";
|
|
37
|
+
import { disableMcpServer } from "../mcps/operations/disable.js";
|
|
38
|
+
import { enableSkill } from "../skills/operations/enable.js";
|
|
39
|
+
import { disableSkill } from "../skills/operations/disable.js";
|
|
40
|
+
import { enableSubagent } from "../subagents/operations/enable.js";
|
|
41
|
+
import { disableSubagent } from "../subagents/operations/disable.js";
|
|
42
|
+
import { canonicalNodeDisplayPath, canonicalRootDisplayPath, lockfileDisplayPath, settingsDisplayPath, } from "./display-paths.js";
|
|
43
|
+
// -----------------------------------------------------------------------------
|
|
44
|
+
// Shared vocabulary
|
|
45
|
+
// -----------------------------------------------------------------------------
|
|
46
|
+
const decodeRenderedFilePath = Schema.decodeUnknownSync(RenderedFilePathSchema);
|
|
47
|
+
const TYPE_LABEL = {
|
|
48
|
+
skill: "Skill",
|
|
49
|
+
subagent: "Subagent",
|
|
50
|
+
"mcp-server": "MCP server",
|
|
51
|
+
rule: "rule",
|
|
52
|
+
hook: "hooks package",
|
|
53
|
+
knowledge: "Knowledge bundle",
|
|
54
|
+
pack: "Pack",
|
|
55
|
+
};
|
|
56
|
+
/** How the plan names its subject, matching the command that raised it. */
|
|
57
|
+
const PLAN_SUBJECT = {
|
|
58
|
+
skill: "skill",
|
|
59
|
+
subagent: "subagent",
|
|
60
|
+
"mcp-server": "MCP server",
|
|
61
|
+
rule: "rules",
|
|
62
|
+
hook: "hooks",
|
|
63
|
+
knowledge: "knowledge bundle",
|
|
64
|
+
pack: "pack",
|
|
65
|
+
};
|
|
66
|
+
const AGGREGATE_UNIT_TYPES = new Set([
|
|
67
|
+
"rule",
|
|
68
|
+
"hook",
|
|
69
|
+
"knowledge",
|
|
70
|
+
]);
|
|
71
|
+
const unchanged = (request, name, message) => ({
|
|
72
|
+
_tag: "Unchanged",
|
|
73
|
+
type: request.type,
|
|
74
|
+
name,
|
|
75
|
+
enabled: request.enabled,
|
|
76
|
+
message,
|
|
77
|
+
});
|
|
78
|
+
const notInstalled = (type, name, listCommand) => new ExtensionLifecycleFailed({
|
|
79
|
+
category: "not_found",
|
|
80
|
+
detail: `${TYPE_LABEL[type]} '${name}' is not installed`,
|
|
81
|
+
suggestions: [{ description: `Inspect installed ${type}s`, cmd: listCommand }],
|
|
82
|
+
});
|
|
83
|
+
const settingsArtifact = (scope) => ({
|
|
84
|
+
path: settingsDisplayPath(scope),
|
|
85
|
+
scope,
|
|
86
|
+
change: "updated",
|
|
87
|
+
targets: [{ path: settingsDisplayPath(scope), change: "updated" }],
|
|
88
|
+
});
|
|
89
|
+
const normalizedPackIdentity = (identity) => identity.startsWith("workspace:") ? identity.slice("workspace:".length) : identity;
|
|
90
|
+
const packContributesTo = (node, packIdentity) => node.origins.some((origin) => origin.type === "pack" && normalizedPackIdentity(origin.pack) === packIdentity);
|
|
91
|
+
const remainsActiveWithoutPack = (node, packIdentity) => isDesiredExtensionActive(node.origins.filter((origin) => origin.type !== "pack" || normalizedPackIdentity(origin.pack) !== packIdentity));
|
|
92
|
+
// -----------------------------------------------------------------------------
|
|
93
|
+
// prepare
|
|
94
|
+
// -----------------------------------------------------------------------------
|
|
95
|
+
/** The gate a rule transition passes before it may reconcile instruction files. */
|
|
96
|
+
const instructionGate = (ws) => Effect.gen(function* () {
|
|
97
|
+
const config = yield* activeInstructionsConfig(ws);
|
|
98
|
+
if (Option.isNone(config)) {
|
|
99
|
+
return { config, blocked: Option.none() };
|
|
100
|
+
}
|
|
101
|
+
const snapshot = yield* observeInstructions({ ws, config: config.value });
|
|
102
|
+
const readiness = yield* instructionReconciliationReadiness({ ws, snapshot });
|
|
103
|
+
return {
|
|
104
|
+
config,
|
|
105
|
+
blocked: Option.map(readiness, (failure) => new ExtensionLifecycleFailed({
|
|
106
|
+
category: "conflict",
|
|
107
|
+
detail: failure._tag === "InstructionMaintenanceFailed"
|
|
108
|
+
? failure.detail
|
|
109
|
+
: "Instruction reconciliation cannot proceed against the current workspace",
|
|
110
|
+
cause: failure,
|
|
111
|
+
})),
|
|
112
|
+
};
|
|
113
|
+
});
|
|
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
|
+
/**
|
|
139
|
+
* Settle an activation request into the change it makes.
|
|
140
|
+
*
|
|
141
|
+
* A request the workspace already satisfies settles as `Unchanged` rather
|
|
142
|
+
* than as an empty plan, so repeating a command reports what is true instead
|
|
143
|
+
* of offering to do nothing. A request naming a subject the workspace does
|
|
144
|
+
* not hold fails with the type's own recovery route.
|
|
145
|
+
*/
|
|
146
|
+
export const prepareSetActivation = (request) => Effect.gen(function* () {
|
|
147
|
+
const ws = yield* WorkspaceMutations;
|
|
148
|
+
const path = yield* Path.Path;
|
|
149
|
+
const scope = ws.scope;
|
|
150
|
+
const verb = request.enabled ? "enabled" : "disabled";
|
|
151
|
+
switch (request.type) {
|
|
152
|
+
case "skill":
|
|
153
|
+
case "subagent": {
|
|
154
|
+
const name = yield* resolveInstalledIdentifierNameOrInput({
|
|
155
|
+
input: request.name,
|
|
156
|
+
resourceType: request.type,
|
|
157
|
+
});
|
|
158
|
+
const rows = yield* ws.records.rows(request.type).pipe(Effect.map(installedRowsByName));
|
|
159
|
+
const entry = rows[name];
|
|
160
|
+
if (entry === undefined) {
|
|
161
|
+
return yield* notInstalled(request.type, request.name, `axm ${request.type}s list`);
|
|
162
|
+
}
|
|
163
|
+
if (request.enabled && entry.enabled) {
|
|
164
|
+
return unchanged(request, name, `${TYPE_LABEL[request.type]} '${name}' is already enabled`);
|
|
165
|
+
}
|
|
166
|
+
// An implicit member is always active, so only a configured row can
|
|
167
|
+
// already be disabled; disabling an implicit one promotes it.
|
|
168
|
+
if (!request.enabled && entry.lifecycle === "configured" && !entry.enabled) {
|
|
169
|
+
return unchanged(request, name, `${TYPE_LABEL[request.type]} '${name}' is already disabled`);
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
_tag: "SetActivation",
|
|
173
|
+
type: request.type,
|
|
174
|
+
name,
|
|
175
|
+
enabled: request.enabled,
|
|
176
|
+
scope,
|
|
177
|
+
artifact: settingsArtifact(scope),
|
|
178
|
+
transition: { kind: request.type },
|
|
179
|
+
instructions: Option.none(),
|
|
180
|
+
blocked: Option.none(),
|
|
181
|
+
warning: Option.none(),
|
|
182
|
+
agentOutcomes: [],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
case "mcp-server": {
|
|
186
|
+
const configured = yield* ws.getConfiguredMcpServerEntries();
|
|
187
|
+
const entry = configured[request.name];
|
|
188
|
+
if (entry === undefined) {
|
|
189
|
+
return unchanged(request, request.name, `MCP server "${request.name}" is not configured`);
|
|
190
|
+
}
|
|
191
|
+
if (entry.enabled === request.enabled) {
|
|
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,
|
|
259
|
+
});
|
|
260
|
+
if (!request.enabled) {
|
|
261
|
+
return {
|
|
262
|
+
_tag: "SetActivation",
|
|
263
|
+
type: "hook",
|
|
264
|
+
name: request.name,
|
|
265
|
+
enabled: false,
|
|
266
|
+
scope,
|
|
267
|
+
artifact,
|
|
268
|
+
transition: { kind: "deactivate-hook" },
|
|
269
|
+
instructions: Option.none(),
|
|
270
|
+
blocked: Option.none(),
|
|
271
|
+
warning: Option.none(),
|
|
272
|
+
agentOutcomes: [],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const { ref, versionRange } = yield* resolveConfiguredHook(request.name, entry.source, yield* makeConfiguredReleaseAgeEvaluation());
|
|
276
|
+
const managers = yield* ExtensionManagers;
|
|
277
|
+
// Hook packages report per-agent outcomes because a hooks package can
|
|
278
|
+
// be projected for one agent and not another.
|
|
279
|
+
const agentOutcomes = managers.hook.configuredAgentOutcomesForRef === undefined
|
|
280
|
+
? []
|
|
281
|
+
: yield* managers.hook.configuredAgentOutcomesForRef(ref, "projected");
|
|
282
|
+
return {
|
|
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) {
|
|
300
|
+
return yield* new ExtensionLifecycleFailed({
|
|
301
|
+
category: "not_found",
|
|
302
|
+
detail: `Knowledge bundle "${request.name}" is not configured`,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
if (entry.enabled === request.enabled) {
|
|
306
|
+
return unchanged(request, request.name, `Knowledge bundle "${request.name}" is already ${verb}`);
|
|
307
|
+
}
|
|
308
|
+
if (!request.enabled) {
|
|
309
|
+
return {
|
|
310
|
+
_tag: "SetActivation",
|
|
311
|
+
type: "knowledge",
|
|
312
|
+
name: request.name,
|
|
313
|
+
enabled: false,
|
|
314
|
+
scope,
|
|
315
|
+
artifact: settingsArtifact(scope),
|
|
316
|
+
transition: { kind: "deactivate-knowledge" },
|
|
317
|
+
instructions: Option.none(),
|
|
318
|
+
blocked: Option.none(),
|
|
319
|
+
warning: Option.none(),
|
|
320
|
+
agentOutcomes: [],
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
const { ref, versionRange } = yield* resolveConfiguredKnowledge(request.name, entry.source, yield* makeConfiguredReleaseAgeEvaluation());
|
|
324
|
+
return {
|
|
325
|
+
_tag: "SetActivation",
|
|
326
|
+
type: "knowledge",
|
|
327
|
+
name: request.name,
|
|
328
|
+
enabled: true,
|
|
329
|
+
scope,
|
|
330
|
+
artifact: settingsArtifact(scope),
|
|
331
|
+
transition: { kind: "reinstall", ref, versionRange },
|
|
332
|
+
instructions: Option.none(),
|
|
333
|
+
blocked: Option.none(),
|
|
334
|
+
warning: Option.none(),
|
|
335
|
+
agentOutcomes: [],
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
case "pack": {
|
|
339
|
+
const configured = yield* ws.getConfiguredPackEntries();
|
|
340
|
+
const entry = configured[request.name];
|
|
341
|
+
if (entry === undefined) {
|
|
342
|
+
return yield* new ExtensionLifecycleFailed({
|
|
343
|
+
category: "not_found",
|
|
344
|
+
detail: `Pack "${request.name}" is not configured`,
|
|
345
|
+
suggestions: [{ description: "Inspect installed packs", cmd: "axm packs list" }],
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (entry.enabled === request.enabled) {
|
|
349
|
+
return unchanged(request, request.name, `Pack "${request.name}" is already ${verb}`);
|
|
350
|
+
}
|
|
351
|
+
const graph = yield* ws.getDesiredStateGraph();
|
|
352
|
+
if (!graph.complete) {
|
|
353
|
+
return yield* new ExtensionLifecycleFailed({
|
|
354
|
+
category: "conflict",
|
|
355
|
+
detail: `Cannot ${request.enabled ? "enable" : "disable"} the pack while desired state is unresolved`,
|
|
356
|
+
suggestions: [{ description: "Inspect workspace facts", cmd: "axm lint" }],
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const packNode = graph.nodes.find((node) => node.type === "pack" && node.name === request.name);
|
|
360
|
+
if (packNode === undefined) {
|
|
361
|
+
return yield* new ExtensionLifecycleFailed({
|
|
362
|
+
category: "not_found",
|
|
363
|
+
detail: `Pack "${request.name}" was not found`,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
const identity = normalizedPackIdentity(packNode.identity);
|
|
367
|
+
// Disabling moves only the members that lose their last active origin;
|
|
368
|
+
// enabling moves every member the Pack contributes.
|
|
369
|
+
const affected = graph.nodes.filter((node) => node.type !== "pack" &&
|
|
370
|
+
node.enabled &&
|
|
371
|
+
packContributesTo(node, identity) &&
|
|
372
|
+
!remainsActiveWithoutPack(node, identity));
|
|
373
|
+
const contributed = graph.nodes.filter((node) => node.type !== "pack" && packContributesTo(node, identity));
|
|
374
|
+
const members = request.enabled ? contributed : affected;
|
|
375
|
+
const memberTargets = members.map((node) => ({
|
|
376
|
+
path: canonicalNodeDisplayPath(path, ws, node),
|
|
377
|
+
change: "unchanged",
|
|
378
|
+
}));
|
|
379
|
+
return {
|
|
380
|
+
_tag: "SetActivation",
|
|
381
|
+
type: "pack",
|
|
382
|
+
name: request.name,
|
|
383
|
+
enabled: request.enabled,
|
|
384
|
+
scope,
|
|
385
|
+
artifact: {
|
|
386
|
+
path: settingsDisplayPath(scope),
|
|
387
|
+
scope,
|
|
388
|
+
change: "updated",
|
|
389
|
+
fileCount: memberTargets.length + 1,
|
|
390
|
+
targets: [{ path: settingsDisplayPath(scope), change: "updated" }, ...memberTargets],
|
|
391
|
+
},
|
|
392
|
+
transition: {
|
|
393
|
+
kind: "pack",
|
|
394
|
+
identity,
|
|
395
|
+
members,
|
|
396
|
+
aggregateTypes: new Set(members.map((node) => node.type)),
|
|
397
|
+
},
|
|
398
|
+
instructions: Option.none(),
|
|
399
|
+
blocked: Option.none(),
|
|
400
|
+
warning: Option.none(),
|
|
401
|
+
agentOutcomes: [],
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
// -----------------------------------------------------------------------------
|
|
407
|
+
// previewOrApply
|
|
408
|
+
// -----------------------------------------------------------------------------
|
|
409
|
+
/** Remove one Pack member's own projection, by what the member projects into. */
|
|
410
|
+
const dematerializeMember = (node) => Effect.gen(function* () {
|
|
411
|
+
const ws = yield* WorkspaceMutations;
|
|
412
|
+
const path = yield* Path.Path;
|
|
413
|
+
const agentRepo = yield* CodingAgentRepository;
|
|
414
|
+
const managers = yield* ExtensionManagers;
|
|
415
|
+
switch (node.type) {
|
|
416
|
+
case "skill":
|
|
417
|
+
return yield* managers.skill.materializeDeactivate({
|
|
418
|
+
target: { type: "skill", name: node.name },
|
|
419
|
+
});
|
|
420
|
+
case "mcp-server": {
|
|
421
|
+
const agents = yield* agentRepo.getConfiguredAgents();
|
|
422
|
+
return yield* Effect.forEach(agents, (agent) => agent
|
|
423
|
+
.removeMcpServer({
|
|
424
|
+
workspaceRoot: ws.baseDir,
|
|
425
|
+
scope: ws.scope,
|
|
426
|
+
serverName: node.name,
|
|
427
|
+
disableOnly: true,
|
|
428
|
+
})
|
|
429
|
+
.pipe(Effect.flatMap((outcome) => outcome._tag === "disabled" ||
|
|
430
|
+
outcome._tag === "nothing-runnable" ||
|
|
431
|
+
outcome._tag === "needs-input" ||
|
|
432
|
+
outcome._tag === "misconfigured" ||
|
|
433
|
+
outcome._tag === "failed"
|
|
434
|
+
? Effect.fail(new ExtensionLifecycleFailed({
|
|
435
|
+
category: "conflict",
|
|
436
|
+
detail: `MCP server removal failed for ${agent.id}: ${outcome.reason}`,
|
|
437
|
+
}))
|
|
438
|
+
: Effect.void)), { concurrency: "unbounded", discard: true });
|
|
439
|
+
}
|
|
440
|
+
case "subagent": {
|
|
441
|
+
const agents = yield* agentRepo.getConfiguredAgents();
|
|
442
|
+
return yield* Effect.forEach(agents, (agent) => agent.resolveEffectiveSubagentsDir({ workspaceRoot: ws.baseDir, scope: ws.scope }).pipe(Effect.flatMap((resolved) => resolved._tag !== "supported"
|
|
443
|
+
? Effect.void
|
|
444
|
+
: findManagedSubagentFiles(resolved.dir, sanitizeName(node.name)).pipe(Effect.flatMap((managedPaths) => agent
|
|
445
|
+
.removeSubagent({
|
|
446
|
+
workspaceRoot: ws.baseDir,
|
|
447
|
+
scope: ws.scope,
|
|
448
|
+
subagentName: node.name,
|
|
449
|
+
renderedFilePaths: managedPaths.map((filePath) => decodeRenderedFilePath(path.relative(ws.baseDir, filePath))),
|
|
450
|
+
})
|
|
451
|
+
.pipe(Effect.flatMap((outcome) => outcome._tag === "conflict"
|
|
452
|
+
? new ExtensionLifecycleFailed({
|
|
453
|
+
category: "conflict",
|
|
454
|
+
detail: `Subagent removal failed for ${agent.id}: ${outcome.reason}`,
|
|
455
|
+
})
|
|
456
|
+
: Effect.void)))))), { concurrency: "unbounded", discard: true });
|
|
457
|
+
}
|
|
458
|
+
case "rule":
|
|
459
|
+
case "hook":
|
|
460
|
+
case "knowledge":
|
|
461
|
+
case "pack":
|
|
462
|
+
// These types own only shared aggregate units. One trailing render
|
|
463
|
+
// reflects the post-transition graph for the whole closure.
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
/**
|
|
468
|
+
* Render the shared aggregate units the moved member types contribute to,
|
|
469
|
+
* once for the whole closure rather than once per contributor.
|
|
470
|
+
*/
|
|
471
|
+
const reconcileAggregateProjections = (types) => Effect.gen(function* () {
|
|
472
|
+
const managers = yield* ExtensionManagers;
|
|
473
|
+
const plans = [];
|
|
474
|
+
if (types.has("rule"))
|
|
475
|
+
plans.push(...(yield* managers.rule.projectionPlans()));
|
|
476
|
+
if (types.has("hook"))
|
|
477
|
+
plans.push(...(yield* managers.hook.projectionPlans()));
|
|
478
|
+
if (types.has("knowledge"))
|
|
479
|
+
plans.push(...(yield* managers.knowledge.projectionPlans()));
|
|
480
|
+
yield* applyProjectionPlans(plans);
|
|
481
|
+
return projectionPlanExclusionWarnings(plans);
|
|
482
|
+
});
|
|
483
|
+
/**
|
|
484
|
+
* The Pack graph after the change must say what the change claimed: the Pack
|
|
485
|
+
* carries its new activation, and every member the change deactivated is
|
|
486
|
+
* inactive. Validated inside the transaction, so a graph that disagrees is
|
|
487
|
+
* restored rather than committed.
|
|
488
|
+
*/
|
|
489
|
+
const validatePackActivation = (candidate) => Effect.gen(function* () {
|
|
490
|
+
const ws = yield* WorkspaceMutations;
|
|
491
|
+
const graph = yield* ws.getDesiredStateGraph();
|
|
492
|
+
const packNode = graph.nodes.find((node) => node.type === "pack" && node.name === candidate.name);
|
|
493
|
+
if (packNode === undefined || packNode.enabled !== candidate.enabled) {
|
|
494
|
+
return yield* new ExtensionLifecycleFailed({
|
|
495
|
+
category: "conflict",
|
|
496
|
+
detail: `Pack "${candidate.name}" did not reach the ${candidate.enabled ? "enabled" : "disabled"} state`,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
if (candidate.enabled)
|
|
500
|
+
return;
|
|
501
|
+
const stillActive = candidate.members.filter((member) => graph.nodes.some((node) => node.type === member.type && node.name === member.name && node.enabled));
|
|
502
|
+
if (stillActive.length > 0) {
|
|
503
|
+
return yield* new ExtensionLifecycleFailed({
|
|
504
|
+
category: "conflict",
|
|
505
|
+
detail: `Disabling "${candidate.name}" left members active: ${stillActive
|
|
506
|
+
.map((member) => `${member.type} ${member.name}`)
|
|
507
|
+
.join(", ")}`,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
const NO_WARNINGS = [];
|
|
512
|
+
/** Disabling a rule: record the preference, then remove its projection. */
|
|
513
|
+
const deactivateRuleTransition = (candidate) => Effect.gen(function* () {
|
|
514
|
+
const ws = yield* WorkspaceMutations;
|
|
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);
|
|
544
|
+
});
|
|
545
|
+
/** Enabling a Pack, or disabling it and the members it alone kept active. */
|
|
546
|
+
const packTransition = (candidate, transition) => Effect.gen(function* () {
|
|
547
|
+
const ws = yield* WorkspaceMutations;
|
|
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 });
|
|
558
|
+
if (candidate.enabled) {
|
|
559
|
+
// A disabled Pack contributes nothing, so the members the planner saw
|
|
560
|
+
// still carried the old inactive state. Re-read the closure from the
|
|
561
|
+
// graph this transition just produced: retained re-materialization
|
|
562
|
+
// covers active members only, and they became active one line ago.
|
|
563
|
+
const graph = yield* ws.getDesiredStateGraph();
|
|
564
|
+
const activeMembers = graph.nodes.filter((node) => node.type !== "pack" && packContributesTo(node, transition.identity));
|
|
565
|
+
// The capability refuses when accepted content is not usable; that is
|
|
566
|
+
// this feature's own conflict, reported with the recovery route the
|
|
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 });
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
yield* Effect.forEach(transition.members.filter((node) => !AGGREGATE_UNIT_TYPES.has(node.type)), dematerializeMember, { concurrency: 1, discard: true });
|
|
601
|
+
}
|
|
602
|
+
return yield* reconcileAggregateProjections(transition.aggregateTypes);
|
|
603
|
+
});
|
|
604
|
+
/**
|
|
605
|
+
* The step for a type whose executor owns its whole transition, including the
|
|
606
|
+
* transaction it opens and the artifact it observes.
|
|
607
|
+
*/
|
|
608
|
+
const executorStep = (candidate, kind) => {
|
|
609
|
+
const run = kind === "skill"
|
|
610
|
+
? candidate.enabled
|
|
611
|
+
? enableSkill({ name: "enable-skill", args: { skillName: candidate.name } })
|
|
612
|
+
: disableSkill({ name: "disable-skill", args: { skillName: candidate.name } })
|
|
613
|
+
: kind === "subagent"
|
|
614
|
+
? candidate.enabled
|
|
615
|
+
? enableSubagent({ name: "enable-subagent", args: { subagentName: candidate.name } })
|
|
616
|
+
: disableSubagent({ name: "disable-subagent", args: { subagentName: candidate.name } })
|
|
617
|
+
: candidate.enabled
|
|
618
|
+
? enableMcpServer({ name: "enable-mcp-server", args: { serverName: candidate.name } })
|
|
619
|
+
: disableMcpServer({ name: "disable-mcp-server", args: { serverName: candidate.name } });
|
|
620
|
+
return { label: candidate.name, readiness: "ready", run };
|
|
621
|
+
};
|
|
622
|
+
/**
|
|
623
|
+
* The step for a type whose transition this use case composes itself: the
|
|
624
|
+
* preference, its projections, and any instruction file they share, inside
|
|
625
|
+
* one transaction.
|
|
626
|
+
*/
|
|
627
|
+
const transactionStep = (candidate, transition, validate) => {
|
|
628
|
+
const { artifact } = candidate;
|
|
629
|
+
return {
|
|
630
|
+
label: candidate.name,
|
|
631
|
+
readiness: "ready",
|
|
632
|
+
artifact,
|
|
633
|
+
...(candidate.agentOutcomes.length === 0 ? {} : { agentOutcomes: candidate.agentOutcomes }),
|
|
634
|
+
run: Effect.gen(function* () {
|
|
635
|
+
const ws = yield* WorkspaceMutations;
|
|
636
|
+
const warnings = yield* runWorkspaceTransaction({
|
|
637
|
+
transition: Option.match(candidate.instructions, {
|
|
638
|
+
onNone: () => transition,
|
|
639
|
+
onSome: (config) => reconcileInstructionTransition({ ws, config, transition }),
|
|
640
|
+
}),
|
|
641
|
+
validate,
|
|
642
|
+
});
|
|
643
|
+
return {
|
|
644
|
+
result: "success",
|
|
645
|
+
message: `${candidate.enabled ? "Enabled" : "Disabled"} ${candidate.name}`,
|
|
646
|
+
artifact,
|
|
647
|
+
...(warnings.length === 0 ? {} : { warnings }),
|
|
648
|
+
};
|
|
649
|
+
}).pipe(withAdaptedStepFailures),
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
const activationStep = (candidate) => {
|
|
653
|
+
if (Option.isSome(candidate.blocked)) {
|
|
654
|
+
return {
|
|
655
|
+
label: candidate.name,
|
|
656
|
+
readiness: "error",
|
|
657
|
+
errorMessage: candidate.blocked.value.detail ?? "Activation cannot proceed",
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
const transition = candidate.transition;
|
|
661
|
+
switch (transition.kind) {
|
|
662
|
+
case "skill":
|
|
663
|
+
case "subagent":
|
|
664
|
+
case "mcp-server":
|
|
665
|
+
return executorStep(candidate, transition.kind);
|
|
666
|
+
case "reinstall":
|
|
667
|
+
return reinstallStep(candidate, transition);
|
|
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);
|
|
674
|
+
case "pack":
|
|
675
|
+
return transactionStep(candidate, packTransition(candidate, transition), () => validatePackActivation({
|
|
676
|
+
name: candidate.name,
|
|
677
|
+
enabled: candidate.enabled,
|
|
678
|
+
members: transition.members,
|
|
679
|
+
}));
|
|
680
|
+
}
|
|
681
|
+
};
|
|
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
|
+
/**
|
|
739
|
+
* Preview or apply a settled activation. The whole change — the preference,
|
|
740
|
+
* the projections it implies, and any instruction file it shares — runs in
|
|
741
|
+
* one workspace transaction, so a projection that cannot be written never
|
|
742
|
+
* leaves the preference recorded.
|
|
743
|
+
*/
|
|
744
|
+
export const previewOrApplySetActivation = (candidate, execution) => Effect.gen(function* () {
|
|
745
|
+
const plan = {
|
|
746
|
+
_tag: "Plan",
|
|
747
|
+
name: `${candidate.enabled ? "Enable" : "Disable"} ${PLAN_SUBJECT[candidate.type]}`,
|
|
748
|
+
description: Option.some(`${candidate.enabled ? "Enable" : "Disable"} ${candidate.name}${candidate.type === "pack" ? " without changing locked versions" : ""}`),
|
|
749
|
+
presentation: operationPresentation(candidate.enabled
|
|
750
|
+
? { imperative: "enable", past: "Enabled", gerund: "Enabling" }
|
|
751
|
+
: { imperative: "disable", past: "Disabled", gerund: "Disabling" }, candidate.type),
|
|
752
|
+
jobs: [{ concurrency: 1, steps: [activationStep(candidate)] }],
|
|
753
|
+
};
|
|
754
|
+
// Every activation moves exactly one extension to one desired state, so
|
|
755
|
+
// the resolution projects that operation's configured-agent outcomes
|
|
756
|
+
// before the change and verifies them after it.
|
|
757
|
+
const configuredAgentOperations = [
|
|
758
|
+
{
|
|
759
|
+
extensionType: candidate.type,
|
|
760
|
+
name: candidate.name,
|
|
761
|
+
plannedState: candidate.enabled ? "enabled" : "disabled",
|
|
762
|
+
},
|
|
763
|
+
];
|
|
764
|
+
const prepared = yield* prepareExecutionCandidate(plan, { configuredAgentOperations });
|
|
765
|
+
return yield* resolveExecutionCandidate(prepared, execution);
|
|
766
|
+
});
|
|
767
|
+
/** The activation use case. */
|
|
768
|
+
export const SetActivation = {
|
|
769
|
+
prepare: prepareSetActivation,
|
|
770
|
+
previewOrApply: previewOrApplySetActivation,
|
|
771
|
+
};
|
|
772
|
+
//# sourceMappingURL=set-activation.js.map
|