@agentxm/extension-lifecycle 0.28.5 → 0.28.6
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/hooks/manager.js +10 -5
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.js +0 -2
- package/dist/src/knowledge/manager.js +61 -12
- package/dist/src/mcps/operations/disable.js +6 -42
- package/dist/src/mcps/operations/enable.js +1 -34
- package/dist/src/mcps/operations/install.d.ts +0 -3
- package/dist/src/mcps/operations/install.js +1 -38
- package/dist/src/registry-materialization.js +5 -0
- package/dist/src/rules/manager.js +3 -1
- package/dist/src/workflows/install-command/workflow.d.ts +1 -2
- package/dist/src/workflows/install-command/workflow.js +6 -6
- package/package.json +8 -8
- package/dist/src/resolution-progress.d.ts +0 -18
- package/dist/src/resolution-progress.js +0 -11
- package/dist/src/skills/operations/uninstall.d.ts +0 -40
- package/dist/src/skills/operations/uninstall.js +0 -157
|
@@ -539,11 +539,16 @@ export const HookManagerLive = Layer.effect(HookManager, Effect.gen(function* ()
|
|
|
539
539
|
.map(({ agentId }) => agentId);
|
|
540
540
|
return fallbackAgentIds.length === 0 ? [] : [{ ...contributor, fallbackAgentIds }];
|
|
541
541
|
});
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
(
|
|
546
|
-
|
|
542
|
+
// Every reachable Hook contributor is decided from desired state
|
|
543
|
+
// alone, so hook units never exclude one.
|
|
544
|
+
const selectNative = (agentId) => () => Effect.succeed({
|
|
545
|
+
contributors: contributors.filter((contributor) => outcomes.some((outcome) => outcome.name === contributor.name &&
|
|
546
|
+
outcome.agentId === agentId &&
|
|
547
|
+
outcome.mechanism === "native" &&
|
|
548
|
+
(outcome.outcome === "projected" || outcome.outcome === "current"))),
|
|
549
|
+
exclusions: [],
|
|
550
|
+
});
|
|
551
|
+
const selectFallback = () => Effect.succeed({ contributors: fallbackContributors, exclusions: [] });
|
|
547
552
|
const fallbackAgentIds = Array.from(new Set(fallbackContributors.flatMap(({ fallbackAgentIds }) => fallbackAgentIds)));
|
|
548
553
|
const materialization = {
|
|
549
554
|
agents: configuredAgents,
|
package/dist/src/index.d.ts
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export { ExtensionLifecycleFailed } from "./errors.js";
|
|
12
12
|
export { LifecycleFailureAdapter, withAdaptedStepFailures, type LifecycleFailureAdapterService, } from "./failure-adapter.js";
|
|
13
|
-
export { LifecycleResolutionProgress, type LifecycleResolutionProgressService, } from "./resolution-progress.js";
|
|
14
13
|
export { makeConfiguredReleaseAgeEvaluation, resolveConfiguredHook, resolveConfiguredKnowledge, resolveConfiguredMcpServer, resolveConfiguredPack, resolveConfiguredRegistryEntry, resolveConfiguredRule, resolveConfiguredSkill, resolveConfiguredSubagent, } from "./configured-entry-resolution.js";
|
|
15
14
|
export { CONFIGURED_ENTRY_RESOLUTION_TIMEOUT, withConfiguredEntryResolutionTimeout, } from "./resolution-timeout.js";
|
|
16
15
|
export { buildInstallCommandPlan, runInstallCommandWorkflow, type InstallExtensionCommandWorkflowActions, } from "./workflows/install-command/workflow.js";
|
|
@@ -20,7 +19,6 @@ export { ensureSkillAgentArtifact, removeSkillAgentArtifact } from "./skills/mat
|
|
|
20
19
|
export { getSkillDisplayName } from "./skills/utils.js";
|
|
21
20
|
export { computeSkillSourceHash, gitHostedSkillArtifactSource, installSkill, type InstallSkillOperation, type InstallSkillOperationArgs, } from "./skills/operations/install.js";
|
|
22
21
|
export type { InstallResult } from "./skills/operations/install-result.js";
|
|
23
|
-
export { uninstallSkill, type UninstallSkillOperation, type UninstallSkillOperationArgs, } from "./skills/operations/uninstall.js";
|
|
24
22
|
export { enableSkill, type EnableSkillOperation } from "./skills/operations/enable.js";
|
|
25
23
|
export { disableSkill, type DisableSkillOperation } from "./skills/operations/disable.js";
|
|
26
24
|
export { collectSecretInputNames, deleteMcpSecrets, installMcpServer, mcpSecretAccount, readMcpServerManifest, type InstallMcpServerOperation, type InstallMcpServerOperationArgs, } from "./mcps/operations/install.js";
|
package/dist/src/index.js
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export { ExtensionLifecycleFailed } from "./errors.js";
|
|
12
12
|
export { LifecycleFailureAdapter, withAdaptedStepFailures, } from "./failure-adapter.js";
|
|
13
|
-
export { LifecycleResolutionProgress, } from "./resolution-progress.js";
|
|
14
13
|
export { makeConfiguredReleaseAgeEvaluation, resolveConfiguredHook, resolveConfiguredKnowledge, resolveConfiguredMcpServer, resolveConfiguredPack, resolveConfiguredRegistryEntry, resolveConfiguredRule, resolveConfiguredSkill, resolveConfiguredSubagent, } from "./configured-entry-resolution.js";
|
|
15
14
|
export { CONFIGURED_ENTRY_RESOLUTION_TIMEOUT, withConfiguredEntryResolutionTimeout, } from "./resolution-timeout.js";
|
|
16
15
|
export { buildInstallCommandPlan, runInstallCommandWorkflow, } from "./workflows/install-command/workflow.js";
|
|
@@ -20,7 +19,6 @@ export { materializeRegistryPackage, materializeRegistryPackageWithTreeIntegrity
|
|
|
20
19
|
export { ensureSkillAgentArtifact, removeSkillAgentArtifact } from "./skills/materialization.js";
|
|
21
20
|
export { getSkillDisplayName } from "./skills/utils.js";
|
|
22
21
|
export { computeSkillSourceHash, gitHostedSkillArtifactSource, installSkill, } from "./skills/operations/install.js";
|
|
23
|
-
export { uninstallSkill, } from "./skills/operations/uninstall.js";
|
|
24
22
|
export { enableSkill } from "./skills/operations/enable.js";
|
|
25
23
|
export { disableSkill } from "./skills/operations/disable.js";
|
|
26
24
|
// MCP server lifecycle operations
|
|
@@ -11,7 +11,7 @@ import * as Schema from "effect/Schema";
|
|
|
11
11
|
import * as Scope from "effect/Scope";
|
|
12
12
|
import * as Result from "effect/Result";
|
|
13
13
|
import { PlatformError } from "effect/PlatformError";
|
|
14
|
-
import { KnowledgeDefinitionInvalid, KnowledgeDesiredStateUnreconcilable, KnowledgeInstallStateMissing, KnowledgeIoFailed, KnowledgeObservableContractViolated, KnowledgeResolutionMissing, KnowledgeUnavailable, applyProjectionPlans, planAggregateProjection, requireCompleteGraph, KNOWLEDGE_REGION_OWNER, reconcileKnowledgeDiscovery, resolveInstructionsConfig, } from "@agentxm/extension-workspace";
|
|
14
|
+
import { KnowledgeDefinitionInvalid, KnowledgeDesiredStateUnreconcilable, KnowledgeInstallStateMissing, KnowledgeIoFailed, KnowledgeObservableContractViolated, KnowledgeResolutionMissing, KnowledgeUnavailable, applyProjectionPlans, formatProjectionExclusions, planAggregateProjection, requireCompleteGraph, KNOWLEDGE_REGION_OWNER, reconcileKnowledgeDiscovery, resolveInstructionsConfig, } from "@agentxm/extension-workspace";
|
|
15
15
|
import { canReuseInstalledPackage, materializeExternalPackage } from "@agentxm/extension-workspace";
|
|
16
16
|
import { materializeRegistryPackage } from "../registry-materialization.js";
|
|
17
17
|
import { computeExtensionPathsForLayout, extensionPathSourceFromLockEntry, } from "@agentxm/workspace-state";
|
|
@@ -299,21 +299,64 @@ export const KnowledgeManagerLive = Layer.effect(KnowledgeManager, Effect.gen(fu
|
|
|
299
299
|
.filter(isSourcedDesiredExtension)
|
|
300
300
|
.filter((node) => node.type === "knowledge" && node.enabled))
|
|
301
301
|
: new KnowledgeDesiredStateUnreconcilable()));
|
|
302
|
+
/**
|
|
303
|
+
* Classify a bundle whose package could not be inspected. Only the
|
|
304
|
+
* bundle's own content is in question here: resolution facts have already
|
|
305
|
+
* succeeded, so the two truthful answers are that the package is gone or
|
|
306
|
+
* that its content is invalid.
|
|
307
|
+
*/
|
|
308
|
+
const excludedContributor = (name, root, failure) => Effect.gen(function* () {
|
|
309
|
+
const probe = yield* Effect.result(fs.exists(path.join(root, KNOWLEDGE_MANIFEST_FILENAME)));
|
|
310
|
+
const manifestPresent = Result.isSuccess(probe) && probe.success;
|
|
311
|
+
if (!manifestPresent)
|
|
312
|
+
return { contributor: name, reason: "package-missing" };
|
|
313
|
+
return {
|
|
314
|
+
contributor: name,
|
|
315
|
+
reason: "package-invalid",
|
|
316
|
+
detail: failure._tag === "KnowledgeDefinitionInvalid"
|
|
317
|
+
? failure.detail
|
|
318
|
+
: adapter.describeFailure(failure),
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
/**
|
|
322
|
+
* Resolve the contributor set for the discovery region. A bundle that
|
|
323
|
+
* fails inspection cannot supply a row, so it is excluded and reported
|
|
324
|
+
* rather than failing every other bundle's command with it. Resolution
|
|
325
|
+
* failures — a missing lock entry, an unsupported workspace-authored
|
|
326
|
+
* bundle — remain fail-closed: they are AXM state problems, not bundle
|
|
327
|
+
* content problems.
|
|
328
|
+
*/
|
|
302
329
|
const selectKnowledgeBundles = (graph, locked, configured, config, instructionFilesEnabled) => Effect.forEach(graph.nodes.filter((node) => node.type === "knowledge" && node.enabled), (node) => Effect.gen(function* () {
|
|
303
330
|
const root = yield* desiredCanonicalRoot(node, locked[node.name]);
|
|
304
|
-
const inspected = yield* inspectPackage(root);
|
|
305
331
|
const workspaceInstructionEntry = configured[node.name]?.instructionEntry;
|
|
306
|
-
const
|
|
332
|
+
const resolveInclusion = (manifestInstructionEntry) => resolveKnowledgeInstructionEntry({
|
|
307
333
|
bundleEnabled: node.enabled,
|
|
308
334
|
instructionFilesEnabled,
|
|
309
335
|
knowledgeInstructionsEnabled: config.instructions,
|
|
310
336
|
...(workspaceInstructionEntry === undefined ? {} : { workspaceInstructionEntry }),
|
|
311
|
-
...(
|
|
312
|
-
? {}
|
|
313
|
-
: { manifestInstructionEntry: inspected.manifest.instructionEntry }),
|
|
337
|
+
...(manifestInstructionEntry === undefined ? {} : { manifestInstructionEntry }),
|
|
314
338
|
});
|
|
315
|
-
|
|
316
|
-
|
|
339
|
+
const inspection = yield* Effect.result(inspectPackage(root));
|
|
340
|
+
if (Result.isFailure(inspection)) {
|
|
341
|
+
// A bundle the workspace would not publish anyway is simply
|
|
342
|
+
// absent from the region; reporting it would be noise.
|
|
343
|
+
return resolveInclusion().included
|
|
344
|
+
? {
|
|
345
|
+
contributors: [],
|
|
346
|
+
exclusions: [yield* excludedContributor(node.name, root, inspection.failure)],
|
|
347
|
+
}
|
|
348
|
+
: { contributors: [], exclusions: [] };
|
|
349
|
+
}
|
|
350
|
+
const inspected = inspection.success;
|
|
351
|
+
const resolution = resolveInclusion(inspected.manifest.instructionEntry);
|
|
352
|
+
return {
|
|
353
|
+
contributors: resolution.included ? [toProjectionBundle(root, inspected)] : [],
|
|
354
|
+
exclusions: [],
|
|
355
|
+
};
|
|
356
|
+
})).pipe(Effect.map((selections) => ({
|
|
357
|
+
contributors: selections.flatMap(({ contributors }) => contributors),
|
|
358
|
+
exclusions: selections.flatMap(({ exclusions }) => exclusions),
|
|
359
|
+
})));
|
|
317
360
|
const resolveKnowledgeProjection = () => Effect.gen(function* () {
|
|
318
361
|
const graph = yield* ws.getDesiredStateGraph();
|
|
319
362
|
const locked = yield* getKnowledgeLockEntries(ws);
|
|
@@ -362,12 +405,18 @@ export const KnowledgeManagerLive = Layer.effect(KnowledgeManager, Effect.gen(fu
|
|
|
362
405
|
});
|
|
363
406
|
const projectionPlans = () => makeKnowledgeProjectionPlan().pipe(Effect.map((plan) => [plan]));
|
|
364
407
|
const applyKnowledgeProjection = projectionPlans().pipe(Effect.flatMap(applyProjectionPlans));
|
|
365
|
-
const reconcileDiscovery = (options) => resolveKnowledgeProjection().pipe(Effect.flatMap(({ graph, locked, configured, config, instructionsTarget }) => requireCompleteGraph(graph).pipe(Effect.flatMap((completeGraph) => selectKnowledgeBundles(completeGraph, locked, configured, config, instructionsTarget.enabled)), Effect.flatMap((
|
|
366
|
-
bundles,
|
|
408
|
+
const reconcileDiscovery = (options) => resolveKnowledgeProjection().pipe(Effect.flatMap(({ graph, locked, configured, config, instructionsTarget }) => requireCompleteGraph(graph).pipe(Effect.flatMap((completeGraph) => selectKnowledgeBundles(completeGraph, locked, configured, config, instructionsTarget.enabled)), Effect.flatMap(({ contributors, exclusions }) => runKnowledgeProjectionAdapter({
|
|
409
|
+
bundles: contributors,
|
|
367
410
|
config,
|
|
368
411
|
instructionsTarget,
|
|
369
412
|
...(options?.dryRun === undefined ? {} : { dryRun: options.dryRun }),
|
|
370
|
-
}))
|
|
413
|
+
}).pipe(Effect.map((result) => ({
|
|
414
|
+
...result,
|
|
415
|
+
warnings: formatProjectionExclusions({
|
|
416
|
+
exclusions,
|
|
417
|
+
targetFile: instructionsTarget.path,
|
|
418
|
+
}),
|
|
419
|
+
})))))));
|
|
371
420
|
const buildLockEntry = (ref) => Effect.gen(function* () {
|
|
372
421
|
const state = lastInstallState.get(ref.knowledge.name);
|
|
373
422
|
switch (ref.refType) {
|
|
@@ -487,7 +536,7 @@ export const KnowledgeManagerLive = Layer.effect(KnowledgeManager, Effect.gen(fu
|
|
|
487
536
|
const discovery = discovered.success;
|
|
488
537
|
return {
|
|
489
538
|
changed: prepared.length > 0 || discovery.changed,
|
|
490
|
-
warnings:
|
|
539
|
+
warnings: discovery.warnings,
|
|
491
540
|
artifacts: discovery.artifacts,
|
|
492
541
|
};
|
|
493
542
|
});
|
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
import * as Effect from "effect/Effect";
|
|
7
7
|
import * as FileSystem from "effect/FileSystem";
|
|
8
8
|
import * as Path from "effect/Path";
|
|
9
|
-
import { CodingAgentRepository
|
|
9
|
+
import { CodingAgentRepository } from "@agentxm/extension-workspace";
|
|
10
10
|
import { appendWarningsToMessage } from "@agentxm/workspace-operations";
|
|
11
11
|
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
12
12
|
import { agentConfigTargets, mcpServerArtifact, mcpSettingsTarget } from "./artifact.js";
|
|
13
13
|
import { mcpSyncWarnings, requireSuccessfulMcpSync } from "./sync-outcome.js";
|
|
14
|
-
import { isMcpServerApplicableToAgent } from "@agentxm/workspace-state";
|
|
15
14
|
import { LifecycleFailureAdapter, withAdaptedStepFailures } from "../../failure-adapter.js";
|
|
16
15
|
import { ExtensionLifecycleFailed } from "../../errors.js";
|
|
17
16
|
export const disableMcpServer = (op) => Effect.gen(function* () {
|
|
@@ -28,48 +27,13 @@ export const disableMcpServer = (op) => Effect.gen(function* () {
|
|
|
28
27
|
});
|
|
29
28
|
}
|
|
30
29
|
const agents = yield* agentRepo.getConfiguredAgents();
|
|
31
|
-
const sharedTargetConflict = sharedMcpTargetPolicyConflict({
|
|
32
|
-
entry,
|
|
33
|
-
agentIds: agents.map((agent) => agent.id),
|
|
34
|
-
scope: ws.scope,
|
|
35
|
-
});
|
|
36
|
-
if (sharedTargetConflict !== undefined) {
|
|
37
|
-
return yield* new ExtensionLifecycleFailed({
|
|
38
|
-
category: "conflict",
|
|
39
|
-
detail: sharedTargetConflict,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
30
|
const outcomes = yield* ws.runTransaction({
|
|
43
31
|
transition: Effect.gen(function* () {
|
|
44
|
-
const synced = yield* Effect.forEach(agents, (agent) =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
serverName: op.args.serverName,
|
|
50
|
-
disableOnly: true,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
const inspection = yield* inspectAgentMcpServer({
|
|
54
|
-
workspaceRoot: ws.baseDir,
|
|
55
|
-
scope: ws.scope,
|
|
56
|
-
agentId: agent.id,
|
|
57
|
-
serverName: op.args.serverName,
|
|
58
|
-
entry,
|
|
59
|
-
});
|
|
60
|
-
if (inspection.status === "unmanaged") {
|
|
61
|
-
return yield* new ExtensionLifecycleFailed({
|
|
62
|
-
category: "conflict",
|
|
63
|
-
detail: `${agent.id} has an unmanaged MCP server named ${op.args.serverName}; AXM will not remove it while applying the target policy`,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
return inspection.status === "drift"
|
|
67
|
-
? yield* agent.removeMcpServer({
|
|
68
|
-
workspaceRoot: ws.baseDir,
|
|
69
|
-
scope: ws.scope,
|
|
70
|
-
serverName: op.args.serverName,
|
|
71
|
-
})
|
|
72
|
-
: { _tag: "success", targets: [] };
|
|
32
|
+
const synced = yield* Effect.forEach(agents, (agent) => agent.removeMcpServer({
|
|
33
|
+
workspaceRoot: ws.baseDir,
|
|
34
|
+
scope: ws.scope,
|
|
35
|
+
serverName: op.args.serverName,
|
|
36
|
+
disableOnly: true,
|
|
73
37
|
}), { concurrency: "unbounded" });
|
|
74
38
|
yield* requireSuccessfulMcpSync(op.args.serverName, agents.map((agent, index) => ({
|
|
75
39
|
agentId: agent.id,
|
|
@@ -7,14 +7,13 @@ import * as FileSystem from "effect/FileSystem";
|
|
|
7
7
|
import * as Path from "effect/Path";
|
|
8
8
|
import * as Effect from "effect/Effect";
|
|
9
9
|
import * as Option from "effect/Option";
|
|
10
|
-
import { CodingAgentRepository, syncInlineMcpServerToAgents,
|
|
10
|
+
import { CodingAgentRepository, syncInlineMcpServerToAgents, applyProjectionPlansWithResults, planSingletonProjection, } from "@agentxm/extension-workspace";
|
|
11
11
|
import { normalizeHandle, parseExtensionFqnParts, } from "@agentxm/extension-model/unstable/extensions";
|
|
12
12
|
import { appendWarningsToMessage } from "@agentxm/workspace-operations";
|
|
13
13
|
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
14
14
|
import { agentConfigTargets, mcpServerArtifact, mcpSettingsTarget } from "./artifact.js";
|
|
15
15
|
import { usableAcceptedCanonicalObservation } from "@agentxm/workspace-state";
|
|
16
16
|
import { mcpSyncWarnings, requireSuccessfulMcpSync } from "./sync-outcome.js";
|
|
17
|
-
import { isMcpServerApplicableToAgent } from "@agentxm/workspace-state";
|
|
18
17
|
import { LifecycleFailureAdapter, withAdaptedStepFailures } from "../../failure-adapter.js";
|
|
19
18
|
import { ExtensionLifecycleFailed } from "../../errors.js";
|
|
20
19
|
const enableArtifact = (args) => {
|
|
@@ -116,17 +115,6 @@ export const enableMcpServer = (op) => Effect.gen(function* () {
|
|
|
116
115
|
const owner = trustedIdentity?.type === "mcp-server" ? trustedIdentity.owner : normalizeHandle("@local");
|
|
117
116
|
const resolvedVersion = accepted?.type === "registry" ? accepted.resolvedVersion : "0.0.0";
|
|
118
117
|
const agents = yield* agentRepo.getConfiguredAgents();
|
|
119
|
-
const sharedTargetConflict = sharedMcpTargetPolicyConflict({
|
|
120
|
-
entry,
|
|
121
|
-
agentIds: agents.map((agent) => agent.id),
|
|
122
|
-
scope: ws.scope,
|
|
123
|
-
});
|
|
124
|
-
if (sharedTargetConflict !== undefined) {
|
|
125
|
-
return yield* new ExtensionLifecycleFailed({
|
|
126
|
-
category: "conflict",
|
|
127
|
-
detail: sharedTargetConflict,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
118
|
const outcomes = yield* ws.runTransaction({
|
|
131
119
|
transition: Effect.gen(function* () {
|
|
132
120
|
const synced = yield* applyProjectionPlansWithResults(agents.map((agent) => planSingletonProjection({
|
|
@@ -143,27 +131,6 @@ export const enableMcpServer = (op) => Effect.gen(function* () {
|
|
|
143
131
|
observedContributors: [],
|
|
144
132
|
}),
|
|
145
133
|
apply: () => Effect.gen(function* () {
|
|
146
|
-
if (!isMcpServerApplicableToAgent(entry, agent.id)) {
|
|
147
|
-
const inspection = yield* inspectAgentMcpServer({
|
|
148
|
-
workspaceRoot: ws.baseDir,
|
|
149
|
-
scope: ws.scope,
|
|
150
|
-
agentId: agent.id,
|
|
151
|
-
serverName: op.args.serverName,
|
|
152
|
-
entry,
|
|
153
|
-
});
|
|
154
|
-
if (inspection.status === "unmanaged") {
|
|
155
|
-
return yield* new McpSharedTargetConflict({
|
|
156
|
-
reason: `${agent.id} has an unmanaged MCP server named ${op.args.serverName}; AXM will not remove it while applying the target policy`,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
return inspection.status === "drift"
|
|
160
|
-
? yield* agent.removeMcpServer({
|
|
161
|
-
workspaceRoot: ws.baseDir,
|
|
162
|
-
scope: ws.scope,
|
|
163
|
-
serverName: op.args.serverName,
|
|
164
|
-
})
|
|
165
|
-
: { _tag: "success", targets: [] };
|
|
166
|
-
}
|
|
167
134
|
return yield* agent.addMcpServer({
|
|
168
135
|
workspaceRoot: ws.baseDir,
|
|
169
136
|
scope: ws.scope,
|
|
@@ -12,7 +12,6 @@ import * as Path from "effect/Path";
|
|
|
12
12
|
import * as Effect from "effect/Effect";
|
|
13
13
|
import * as Option from "effect/Option";
|
|
14
14
|
import { CodingAgentRepository } from "@agentxm/extension-workspace";
|
|
15
|
-
import type { ConfigurableAgentId } from "@agentxm/extension-model/unstable/agent-capabilities";
|
|
16
15
|
import type { StepFailure } from "@agentxm/workspace-operations";
|
|
17
16
|
import type { JobStepResult, Operation } from "@agentxm/workspace-operations";
|
|
18
17
|
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
@@ -43,8 +42,6 @@ export type InstallMcpServerOperationArgs = {
|
|
|
43
42
|
* The transport boundary resolves flag, CI, and TTY state.
|
|
44
43
|
*/
|
|
45
44
|
readonly nonInteractive: boolean;
|
|
46
|
-
/** Restrict this server to a reviewed subset of configured agents. */
|
|
47
|
-
readonly agents?: ReadonlyArray<ConfigurableAgentId>;
|
|
48
45
|
};
|
|
49
46
|
/**
|
|
50
47
|
* Add an MCP server to the workspace.
|
|
@@ -21,7 +21,7 @@ import * as Effect from "effect/Effect";
|
|
|
21
21
|
import * as Option from "effect/Option";
|
|
22
22
|
import * as Schema from "effect/Schema";
|
|
23
23
|
import { createHash } from "node:crypto";
|
|
24
|
-
import { CodingAgentRepository,
|
|
24
|
+
import { CodingAgentRepository, applyProjectionPlansWithResults, planSingletonProjection, } from "@agentxm/extension-workspace";
|
|
25
25
|
import { isPathSafe, mcpRegistryResolutionKey } from "@agentxm/workspace-state";
|
|
26
26
|
import { acceptedRegistryVersionForRef, validateExactResolvedVersion, } from "@agentxm/workspace-state";
|
|
27
27
|
import { appendWarningsToMessage } from "@agentxm/workspace-operations";
|
|
@@ -33,7 +33,6 @@ import { printSourceParams } from "@agentxm/extension-model/unstable/sources/pri
|
|
|
33
33
|
import { computeMaterializedTreeIntegrity } from "@agentxm/workspace-state";
|
|
34
34
|
import { decodeVersionSync } from "@agentxm/extension-model/unstable/version-constraints";
|
|
35
35
|
import { MCP_SERVER_MANIFEST_FILENAME, McpServerManifestSchema, } from "@agentxm/extension-model/unstable/mcps/manifest-schema";
|
|
36
|
-
import { isMcpServerApplicableToAgent } from "@agentxm/workspace-state";
|
|
37
36
|
import { agentConfigTargets, mcpServerArtifact, mcpSettingsTarget, mcpSourceTarget, } from "./artifact.js";
|
|
38
37
|
import { LifecycleFailureAdapter, withAdaptedStepFailures } from "../../failure-adapter.js";
|
|
39
38
|
import { ExtensionLifecycleFailed } from "../../errors.js";
|
|
@@ -295,17 +294,6 @@ const syncConfiguredAgentsOnInstall = (args) => Effect.gen(function* () {
|
|
|
295
294
|
warnings.push(`Skipping unknown configured agents: ${unknownConfiguredAgentIds.join(", ")}`);
|
|
296
295
|
}
|
|
297
296
|
const configuredAgents = yield* agentRepo.getConfiguredAgents();
|
|
298
|
-
const sharedTargetConflict = sharedMcpTargetPolicyConflict({
|
|
299
|
-
entry: args.entry,
|
|
300
|
-
agentIds: configuredAgents.map((agent) => agent.id),
|
|
301
|
-
scope: args.scope,
|
|
302
|
-
});
|
|
303
|
-
if (sharedTargetConflict !== undefined) {
|
|
304
|
-
return yield* new ExtensionLifecycleFailed({
|
|
305
|
-
category: "conflict",
|
|
306
|
-
detail: sharedTargetConflict,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
297
|
let outcomes;
|
|
310
298
|
if (args.nothingRunnable) {
|
|
311
299
|
outcomes = configuredAgents.map((agent) => ({
|
|
@@ -331,28 +319,6 @@ const syncConfiguredAgentsOnInstall = (args) => Effect.gen(function* () {
|
|
|
331
319
|
observedContributors: [],
|
|
332
320
|
}),
|
|
333
321
|
apply: () => Effect.gen(function* () {
|
|
334
|
-
if (!isMcpServerApplicableToAgent(args.entry, agent.id)) {
|
|
335
|
-
const inspection = yield* inspectAgentMcpServer({
|
|
336
|
-
workspaceRoot: args.wsBaseDir,
|
|
337
|
-
scope: args.scope,
|
|
338
|
-
agentId: agent.id,
|
|
339
|
-
serverName: args.serverName,
|
|
340
|
-
entry: args.entry,
|
|
341
|
-
});
|
|
342
|
-
if (inspection.status === "unmanaged") {
|
|
343
|
-
return yield* new McpSharedTargetConflict({
|
|
344
|
-
reason: `${agent.id} has an unmanaged MCP server named ${args.serverName}; AXM will not remove it while applying the target policy`,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
const outcome = inspection.status === "drift"
|
|
348
|
-
? yield* agent.removeMcpServer({
|
|
349
|
-
workspaceRoot: args.wsBaseDir,
|
|
350
|
-
scope: args.scope,
|
|
351
|
-
serverName: args.serverName,
|
|
352
|
-
})
|
|
353
|
-
: { _tag: "success", targets: [] };
|
|
354
|
-
return { agentId: agent.id, outcome };
|
|
355
|
-
}
|
|
356
322
|
const outcome = yield* agent.addMcpServer({
|
|
357
323
|
workspaceRoot: args.wsBaseDir,
|
|
358
324
|
scope: args.scope,
|
|
@@ -525,13 +491,11 @@ export const installMcpServer = (op) => Effect.gen(function* () {
|
|
|
525
491
|
}
|
|
526
492
|
const persistedEnv = redactSettingsEnv(mergedEnv, secretNames);
|
|
527
493
|
const enabled = currentEntry?.enabled ?? true;
|
|
528
|
-
const agents = op.args.agents ?? currentEntry?.agents;
|
|
529
494
|
const settingsEntry = {
|
|
530
495
|
kind: "sourced",
|
|
531
496
|
source: ref.refType === "workspace" ? "workspace" : printSourceParams(ref.source),
|
|
532
497
|
env: persistedEnv,
|
|
533
498
|
enabled,
|
|
534
|
-
...(agents === undefined ? {} : { agents }),
|
|
535
499
|
};
|
|
536
500
|
const writeEffect = op.args.skipStateWrites === true
|
|
537
501
|
? Effect.void
|
|
@@ -555,7 +519,6 @@ export const installMcpServer = (op) => Effect.gen(function* () {
|
|
|
555
519
|
versionRange: op.args.versionRange,
|
|
556
520
|
env: persistedEnv,
|
|
557
521
|
enabled,
|
|
558
|
-
...(agents === undefined ? {} : { agents }),
|
|
559
522
|
});
|
|
560
523
|
yield* writeEffect;
|
|
561
524
|
const projectionNames = ref.refType === "registry" && lockedVersion !== undefined && lockedVersion !== ref.version
|
|
@@ -17,6 +17,7 @@ import { computeIntegrity } from "./internal/integrity.js";
|
|
|
17
17
|
import { ArchiveIntegrityMismatch, recoverCanonicalDirectory, replaceCanonicalDirectoryWithInspection, } from "@agentxm/extension-workspace";
|
|
18
18
|
import { CoupledDependencyFailure } from "@agentxm/extension-workspace";
|
|
19
19
|
import { coupleLifecycleDependencyFailure } from "./errors.js";
|
|
20
|
+
import { makeThrottledUnitProgress } from "@agentxm/workspace-operations";
|
|
20
21
|
import { computeMaterializedTreeIntegrity, } from "@agentxm/workspace-state";
|
|
21
22
|
const registryLocationForClient = (location) => location.protocol === "file:" ? location.pathname : location.href;
|
|
22
23
|
/**
|
|
@@ -35,12 +36,16 @@ export const materializeRegistryPackageWithTreeIntegrity = (args) => Effect.gen(
|
|
|
35
36
|
canonicalPath: args.destinationPath,
|
|
36
37
|
});
|
|
37
38
|
const client = yield* createRegistryClient(registryLocationForClient(args.sourceLocation));
|
|
39
|
+
// Continuous download progress reaches the lifecycle broadcast throttled:
|
|
40
|
+
// tens of events per archive, attributed to the unit that is running.
|
|
41
|
+
const reportProgress = yield* makeThrottledUnitProgress({ unit: "bytes" });
|
|
38
42
|
const { archive } = yield* client
|
|
39
43
|
.getExtensionPackage({
|
|
40
44
|
owner: args.owner,
|
|
41
45
|
type: args.type,
|
|
42
46
|
name: args.name,
|
|
43
47
|
version: Option.some(args.version),
|
|
48
|
+
onProgress: (progress) => reportProgress(progress.done, progress.total),
|
|
44
49
|
})
|
|
45
50
|
.pipe(Effect.mapError(coupleLifecycleDependencyFailure));
|
|
46
51
|
if (Option.isSome(args.integrity)) {
|
|
@@ -334,7 +334,9 @@ export const RuleManagerLive = Layer.effect(RuleManager, Effect.gen(function* ()
|
|
|
334
334
|
unitId: "rule:instructions-region",
|
|
335
335
|
targetFile: target.absolute,
|
|
336
336
|
graph,
|
|
337
|
-
|
|
337
|
+
// Rule contributors are decided from desired state alone, so the
|
|
338
|
+
// instructions region never excludes one.
|
|
339
|
+
select: (completeGraph) => selectRuleContributors({ graph: completeGraph, locked }).pipe(Effect.map((contributors) => ({ contributors, exclusions: [] }))),
|
|
338
340
|
adapter: {
|
|
339
341
|
observe: (input) => reconcileRulesRegion({ input, target, instructions, dryRun: true }).pipe(Effect.map(({ projectionUnitObservation }) => projectionUnitObservation)),
|
|
340
342
|
apply: (input) => reconcileRulesRegion({ input, target, instructions }).pipe(Effect.asVoid),
|
|
@@ -11,7 +11,6 @@ import type * as Scope from "effect/Scope";
|
|
|
11
11
|
import type { Plan } from "@agentxm/workspace-operations";
|
|
12
12
|
import type { OperationResolution } from "@agentxm/workspace-operations";
|
|
13
13
|
import type { PlanExecution } from "@agentxm/workspace-operations";
|
|
14
|
-
import { LifecycleResolutionProgress } from "../../resolution-progress.js";
|
|
15
14
|
/**
|
|
16
15
|
* Type-specific actions for install command workflows.
|
|
17
16
|
*
|
|
@@ -53,5 +52,5 @@ export declare const runInstallCommandWorkflow: <Args, Parsed, Req, Ref, Intent,
|
|
|
53
52
|
readonly execution: PlanExecution;
|
|
54
53
|
readonly transformIntent?: (intent: Intent) => Intent;
|
|
55
54
|
readonly transformPlan?: (plan: Plan) => Effect.Effect<Plan, TransformError, TransformRequirements>;
|
|
56
|
-
}) => Effect.Effect<OperationResolution<never>, import("@agentxm/workspace-state").WorkspaceSettingsReadFailure | ParseError | ResolveError | TransformError | import("@agentxm/workspace-operations").CandidateFingerprintFailed | import("@agentxm/workspace-operations").ApprovalRecoveryMissing | import("@agentxm/workspace-operations").PlanInteractionFailed | import("@agentxm/workspace-state").WorkspaceTransitionAcquireFailure | import("@agentxm/workspace-state").LockfileValidationError, import("@agentxm/workspace-state").WorkspaceMutations | import("effect/FileSystem").FileSystem | import("effect/Path").Path |
|
|
55
|
+
}) => Effect.Effect<OperationResolution<never>, import("@agentxm/workspace-state").WorkspaceSettingsReadFailure | ParseError | ResolveError | TransformError | import("@agentxm/workspace-operations").CandidateFingerprintFailed | import("@agentxm/workspace-operations").ApprovalRecoveryMissing | import("@agentxm/workspace-operations").PlanInteractionFailed | import("@agentxm/workspace-state").WorkspaceTransitionAcquireFailure | import("@agentxm/workspace-state").LockfileValidationError, import("@agentxm/workspace-state").WorkspaceMutations | import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("@agentxm/workspace-operations").ResolvePlanInteraction | Exclude<TransformRequirements, Scope.Scope>>;
|
|
57
56
|
//# sourceMappingURL=workflow.d.ts.map
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
* @experimental This API is unstable and may change without notice.
|
|
8
8
|
*/
|
|
9
9
|
import * as Effect from "effect/Effect";
|
|
10
|
-
import { previewOrApplyPlan } from "@agentxm/workspace-operations";
|
|
11
|
-
import { LifecycleResolutionProgress } from "../../resolution-progress.js";
|
|
10
|
+
import { observeUnit, previewOrApplyPlan, publishPhaseStarted, } from "@agentxm/workspace-operations";
|
|
12
11
|
// -----------------------------------------------------------------------------
|
|
13
12
|
// Install Command Workflow
|
|
14
13
|
// -----------------------------------------------------------------------------
|
|
@@ -20,8 +19,10 @@ import { LifecycleResolutionProgress } from "../../resolution-progress.js";
|
|
|
20
19
|
*/
|
|
21
20
|
export const buildInstallCommandPlan = (args, actions, options) => Effect.gen(function* () {
|
|
22
21
|
const parsed = yield* actions.parseArgs(args);
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
// Source resolution is the first lifecycle phase: requested sources become
|
|
23
|
+
// concrete refs, observed as one unit under `resolution`.
|
|
24
|
+
yield* publishPhaseStarted("resolution");
|
|
25
|
+
const refs = yield* observeUnit({ id: "extension-sources", label: "extension sources" }, Effect.flatMap(actions.resolveSourceRequests(parsed), (sourceRequests) => actions.discoverRefs(sourceRequests)));
|
|
25
26
|
const finalizedIntent = yield* actions.finalizeIntent(parsed, refs);
|
|
26
27
|
const intent = options?.transformIntent?.(finalizedIntent) ?? finalizedIntent;
|
|
27
28
|
const plan = yield* actions.buildPlan(intent);
|
|
@@ -34,8 +35,7 @@ export const buildInstallCommandPlan = (args, actions, options) => Effect.gen(fu
|
|
|
34
35
|
* finalizeIntent -> buildPlan -> previewOrApplyPlan.
|
|
35
36
|
*/
|
|
36
37
|
export const runInstallCommandWorkflow = (args, actions, options) => Effect.gen(function* () {
|
|
37
|
-
const
|
|
38
|
-
const plan = yield* progress.withSourceResolution(buildInstallCommandPlan(args, actions, options));
|
|
38
|
+
const plan = yield* buildInstallCommandPlan(args, actions, options);
|
|
39
39
|
return yield* previewOrApplyPlan(plan, { execution: options.execution });
|
|
40
40
|
}).pipe(Effect.scoped, Effect.map((resolution) => resolution));
|
|
41
41
|
//# sourceMappingURL=workflow.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentxm/extension-lifecycle",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.6",
|
|
4
4
|
"description": "AXM extension-lifecycle feature: install, update, uninstall, enable, and disable policy across root and type-specific command forms for the axm CLI. Unstable and unsupported — use the axm.sh CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-MIT",
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"effect": "4.0.0-rc.112",
|
|
45
45
|
"semver": "^7.8.5",
|
|
46
|
-
"@agentxm/extension-
|
|
47
|
-
"@agentxm/extension-
|
|
48
|
-
"@agentxm/
|
|
49
|
-
"@agentxm/registry-
|
|
50
|
-
"@agentxm/
|
|
51
|
-
"@agentxm/workspace-
|
|
52
|
-
"@agentxm/
|
|
46
|
+
"@agentxm/extension-sources": "^0.28.6",
|
|
47
|
+
"@agentxm/extension-model": "^0.28.6",
|
|
48
|
+
"@agentxm/extension-workspace": "^0.28.6",
|
|
49
|
+
"@agentxm/registry-client": "^0.28.6",
|
|
50
|
+
"@agentxm/workspace-operations": "^0.28.6",
|
|
51
|
+
"@agentxm/workspace-state": "^0.28.6",
|
|
52
|
+
"@agentxm/registry-protocol": "^0.28.6"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Presentation port for the source-resolution phase of lifecycle workflows.
|
|
3
|
-
* The feature marks where progress feedback belongs; the application owns the
|
|
4
|
-
* mechanism and wording (the CLI renders a spinner around the phase).
|
|
5
|
-
*
|
|
6
|
-
* @experimental This API is unstable and may change without notice.
|
|
7
|
-
*/
|
|
8
|
-
import * as ServiceMap from "effect/Context";
|
|
9
|
-
import type * as Effect from "effect/Effect";
|
|
10
|
-
export interface LifecycleResolutionProgressService {
|
|
11
|
-
/** Surround the source-resolution phase with the application's progress feedback. */
|
|
12
|
-
readonly withSourceResolution: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
|
|
13
|
-
}
|
|
14
|
-
declare const LifecycleResolutionProgress_base: ServiceMap.ServiceClass<LifecycleResolutionProgress, "@agentxm/extension-lifecycle/resolution-progress/LifecycleResolutionProgress", LifecycleResolutionProgressService>;
|
|
15
|
-
export declare class LifecycleResolutionProgress extends LifecycleResolutionProgress_base {
|
|
16
|
-
}
|
|
17
|
-
export {};
|
|
18
|
-
//# sourceMappingURL=resolution-progress.d.ts.map
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Presentation port for the source-resolution phase of lifecycle workflows.
|
|
3
|
-
* The feature marks where progress feedback belongs; the application owns the
|
|
4
|
-
* mechanism and wording (the CLI renders a spinner around the phase).
|
|
5
|
-
*
|
|
6
|
-
* @experimental This API is unstable and may change without notice.
|
|
7
|
-
*/
|
|
8
|
-
import * as ServiceMap from "effect/Context";
|
|
9
|
-
export class LifecycleResolutionProgress extends ServiceMap.Service()("@agentxm/extension-lifecycle/resolution-progress/LifecycleResolutionProgress") {
|
|
10
|
-
}
|
|
11
|
-
//# sourceMappingURL=resolution-progress.js.map
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Uninstall skill executor — orchestrates per-skill removal pipeline.
|
|
3
|
-
*
|
|
4
|
-
* Pipeline: resolve desired/observed state -> remove agent artifacts
|
|
5
|
-
* concurrently -> remove canonical source -> clear settings and accepted resolution.
|
|
6
|
-
*
|
|
7
|
-
* @experimental This API is unstable and may change without notice.
|
|
8
|
-
*/
|
|
9
|
-
import * as FileSystem from "effect/FileSystem";
|
|
10
|
-
import * as Path from "effect/Path";
|
|
11
|
-
import { LifecycleFailureAdapter } from "../../failure-adapter.js";
|
|
12
|
-
import type { OperationHandler } from "@agentxm/workspace-operations";
|
|
13
|
-
import type { Operation } from "@agentxm/workspace-operations";
|
|
14
|
-
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
15
|
-
/**
|
|
16
|
-
* Args for the uninstall-skill operation.
|
|
17
|
-
*/
|
|
18
|
-
export interface UninstallSkillOperationArgs {
|
|
19
|
-
readonly skillName: string;
|
|
20
|
-
/** Agent filter for partial uninstall. Empty = all agents. */
|
|
21
|
-
readonly agents: ReadonlyArray<string>;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Remove a skill from the workspace.
|
|
25
|
-
*
|
|
26
|
-
* @experimental This API is unstable and may change without notice.
|
|
27
|
-
*/
|
|
28
|
-
export type UninstallSkillOperation = Operation<"uninstall-skill", UninstallSkillOperationArgs>;
|
|
29
|
-
/**
|
|
30
|
-
* Uninstall-skill operation handler.
|
|
31
|
-
*
|
|
32
|
-
* Reads workspace paths from the WorkspaceMutations service, then orchestrates:
|
|
33
|
-
* 1. Sanitize skill name for filesystem
|
|
34
|
-
* 2. Resolve configured and observed state
|
|
35
|
-
* 3. Remove agent symlinks concurrently (skip missing)
|
|
36
|
-
* 4. Remove from all known canonical locations (full uninstall only)
|
|
37
|
-
* 5. Remove or update settings and accepted resolution
|
|
38
|
-
*/
|
|
39
|
-
export declare const uninstallSkill: OperationHandler<UninstallSkillOperation, FileSystem.FileSystem | Path.Path | WorkspaceMutations | LifecycleFailureAdapter>;
|
|
40
|
-
//# sourceMappingURL=uninstall.d.ts.map
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Uninstall skill executor — orchestrates per-skill removal pipeline.
|
|
3
|
-
*
|
|
4
|
-
* Pipeline: resolve desired/observed state -> remove agent artifacts
|
|
5
|
-
* concurrently -> remove canonical source -> clear settings and accepted resolution.
|
|
6
|
-
*
|
|
7
|
-
* @experimental This API is unstable and may change without notice.
|
|
8
|
-
*/
|
|
9
|
-
import * as FileSystem from "effect/FileSystem";
|
|
10
|
-
import * as Path from "effect/Path";
|
|
11
|
-
import * as Effect from "effect/Effect";
|
|
12
|
-
import * as Option from "effect/Option";
|
|
13
|
-
import * as Layer from "effect/Layer";
|
|
14
|
-
import { DefaultCodingAgentRepository } from "@agentxm/extension-workspace";
|
|
15
|
-
import { AGENTS } from "@agentxm/extension-model/unstable/agents/registry";
|
|
16
|
-
import { ExtensionLifecycleFailed } from "../../errors.js";
|
|
17
|
-
import { LifecycleFailureAdapter, withAdaptedStepFailures } from "../../failure-adapter.js";
|
|
18
|
-
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
19
|
-
import { removeIfExists } from "@agentxm/workspace-state";
|
|
20
|
-
import { sanitizeName } from "@agentxm/workspace-state";
|
|
21
|
-
import { acceptedCanonicalObservation, acceptedLockedCanonicalPath, removableAcceptedCanonicalPath, } from "@agentxm/workspace-state";
|
|
22
|
-
// Operation types
|
|
23
|
-
// -----------------------------------------------------------------------------
|
|
24
|
-
const isKnownAgentId = (id) => Object.hasOwn(AGENTS, id);
|
|
25
|
-
// -----------------------------------------------------------------------------
|
|
26
|
-
// Public API
|
|
27
|
-
// -----------------------------------------------------------------------------
|
|
28
|
-
/**
|
|
29
|
-
* Uninstall-skill operation handler.
|
|
30
|
-
*
|
|
31
|
-
* Reads workspace paths from the WorkspaceMutations service, then orchestrates:
|
|
32
|
-
* 1. Sanitize skill name for filesystem
|
|
33
|
-
* 2. Resolve configured and observed state
|
|
34
|
-
* 3. Remove agent symlinks concurrently (skip missing)
|
|
35
|
-
* 4. Remove from all known canonical locations (full uninstall only)
|
|
36
|
-
* 5. Remove or update settings and accepted resolution
|
|
37
|
-
*/
|
|
38
|
-
export const uninstallSkill = (op) => Effect.gen(function* () {
|
|
39
|
-
const fs = yield* FileSystem.FileSystem;
|
|
40
|
-
const path = yield* Path.Path;
|
|
41
|
-
const ws = yield* WorkspaceMutations;
|
|
42
|
-
const base = ws.baseDir;
|
|
43
|
-
const fsPathLayer = Layer.mergeAll(Layer.succeed(FileSystem.FileSystem, fs), Layer.succeed(Path.Path, path));
|
|
44
|
-
const sanitizedName = sanitizeName(op.args.skillName);
|
|
45
|
-
const desired = yield* ws.getDesiredStateGraph();
|
|
46
|
-
if (!desired.complete) {
|
|
47
|
-
return yield* new ExtensionLifecycleFailed({
|
|
48
|
-
category: "conflict",
|
|
49
|
-
detail: "Cannot uninstall the skill while the desired extension graph is incomplete.",
|
|
50
|
-
recover: "Repair or reinstall the configured packs, then retry.",
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
const desiredNode = desired.nodes.find((node) => node.type === "skill" && node.name === op.args.skillName);
|
|
54
|
-
const acceptedCanonical = yield* acceptedCanonicalObservation({
|
|
55
|
-
workspace: ws,
|
|
56
|
-
type: "skill",
|
|
57
|
-
name: op.args.skillName,
|
|
58
|
-
});
|
|
59
|
-
const lockedCanonical = yield* acceptedLockedCanonicalPath({
|
|
60
|
-
workspace: ws,
|
|
61
|
-
type: "skill",
|
|
62
|
-
name: op.args.skillName,
|
|
63
|
-
});
|
|
64
|
-
const removableCanonical = Option.orElse(removableAcceptedCanonicalPath(acceptedCanonical), () => lockedCanonical);
|
|
65
|
-
const installedOnDisk = yield* Option.match(removableCanonical, {
|
|
66
|
-
onNone: () => Effect.succeed(false),
|
|
67
|
-
onSome: (canonicalPath) => fs.exists(canonicalPath).pipe(Effect.catch(() => Effect.succeed(false))),
|
|
68
|
-
});
|
|
69
|
-
if (desiredNode === undefined && !installedOnDisk) {
|
|
70
|
-
return { result: "success", message: "not installed" };
|
|
71
|
-
}
|
|
72
|
-
// Determine which configured agent artifacts to remove. Per-agent state is
|
|
73
|
-
// derived from settings and disk rather than persisted in the lockfile.
|
|
74
|
-
const agentFilter = op.args.agents;
|
|
75
|
-
const isPartialUninstall = agentFilter.length > 0;
|
|
76
|
-
const materializationAgents = yield* DefaultCodingAgentRepository.getMaterializationAgents().pipe(Effect.provideService(WorkspaceMutations, ws));
|
|
77
|
-
const agentsToRemove = isPartialUninstall
|
|
78
|
-
? agentFilter
|
|
79
|
-
: materializationAgents.map((agent) => agent.id);
|
|
80
|
-
const remainingAgents = materializationAgents
|
|
81
|
-
.map((agent) => agent.id)
|
|
82
|
-
.filter((agent) => !agentsToRemove.includes(agent));
|
|
83
|
-
const agentsById = new Map(materializationAgents.map((agent) => [agent.id, agent]));
|
|
84
|
-
const resolveAgentArtifactPath = (agentId) => {
|
|
85
|
-
const configuredAgent = agentsById.get(agentId);
|
|
86
|
-
const agentEffect = configuredAgent !== undefined
|
|
87
|
-
? Effect.succeed(Option.some(configuredAgent))
|
|
88
|
-
: isKnownAgentId(agentId)
|
|
89
|
-
? DefaultCodingAgentRepository.get(agentId).pipe(Effect.map(Option.some))
|
|
90
|
-
: Effect.succeed(Option.none());
|
|
91
|
-
return agentEffect.pipe(Effect.flatMap((agentOption) => {
|
|
92
|
-
if (Option.isNone(agentOption))
|
|
93
|
-
return Effect.succeed(Option.none());
|
|
94
|
-
return agentOption.value
|
|
95
|
-
.resolveEffectiveSkillsDir({ workspaceRoot: base })
|
|
96
|
-
.pipe(Effect.map((outcome) => outcome._tag === "supported"
|
|
97
|
-
? Option.some(path.normalize(path.join(outcome.dir, sanitizedName)))
|
|
98
|
-
: Option.none()));
|
|
99
|
-
}), Effect.provide(fsPathLayer));
|
|
100
|
-
};
|
|
101
|
-
const retainedArtifactOptions = yield* Effect.forEach(remainingAgents, resolveAgentArtifactPath, { concurrency: "unbounded" });
|
|
102
|
-
const retainedArtifactPaths = new Set(retainedArtifactOptions.flatMap((artifactPath) => Option.isSome(artifactPath) ? [artifactPath.value] : []));
|
|
103
|
-
// Remove agent symlinks/copies concurrently using adapter-derived paths.
|
|
104
|
-
yield* Effect.forEach(agentsToRemove, (agentId) => {
|
|
105
|
-
const configuredAgent = agentsById.get(agentId);
|
|
106
|
-
const agentEffect = configuredAgent !== undefined
|
|
107
|
-
? Effect.succeed(Option.some(configuredAgent))
|
|
108
|
-
: isKnownAgentId(agentId)
|
|
109
|
-
? DefaultCodingAgentRepository.get(agentId).pipe(Effect.map(Option.some))
|
|
110
|
-
: Effect.succeed(Option.none());
|
|
111
|
-
return agentEffect.pipe(Effect.flatMap((agentOption) => {
|
|
112
|
-
if (Option.isNone(agentOption)) {
|
|
113
|
-
return Effect.succeed({
|
|
114
|
-
_tag: "unsupported",
|
|
115
|
-
reason: `Unknown coding agent: ${agentId}`,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return agentOption.value.resolveEffectiveSkillsDir({ workspaceRoot: base });
|
|
119
|
-
}), Effect.provide(fsPathLayer), Effect.flatMap((outcome) => outcome._tag === "supported"
|
|
120
|
-
? (() => {
|
|
121
|
-
const artifactPath = path.normalize(path.join(outcome.dir, sanitizedName));
|
|
122
|
-
return retainedArtifactPaths.has(artifactPath)
|
|
123
|
-
? Effect.void
|
|
124
|
-
: fs
|
|
125
|
-
.remove(artifactPath, { recursive: true })
|
|
126
|
-
.pipe(Effect.catch(() => Effect.void));
|
|
127
|
-
})()
|
|
128
|
-
: Effect.void));
|
|
129
|
-
}, { concurrency: "unbounded" });
|
|
130
|
-
// Handle partial vs full uninstall
|
|
131
|
-
if (isPartialUninstall) {
|
|
132
|
-
const agentList = [...agentsToRemove].join(", ");
|
|
133
|
-
return {
|
|
134
|
-
result: "success",
|
|
135
|
-
message: `Uninstalled ${op.args.skillName} from ${agentList}`,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
const packOwned = desiredNode?.origins.some((origin) => origin.type === "pack") ?? false;
|
|
139
|
-
if (packOwned) {
|
|
140
|
-
// Pack still references this skill — remove from settings only, keep lockfile + disk
|
|
141
|
-
yield* ws.removeSkillFromSettings(op.args.skillName).pipe(Effect.catch(() => Effect.void));
|
|
142
|
-
return {
|
|
143
|
-
result: "success",
|
|
144
|
-
message: `Uninstalled ${op.args.skillName}`,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
// Full uninstall removes only the canonical package proven by accepted authority.
|
|
148
|
-
if (Option.isSome(removableCanonical))
|
|
149
|
-
yield* removeIfExists(fs, removableCanonical.value);
|
|
150
|
-
// Remove from both settings and lockfile (swallow errors on full uninstall)
|
|
151
|
-
yield* ws.removeSkill(op.args.skillName).pipe(Effect.catch(() => Effect.void));
|
|
152
|
-
return {
|
|
153
|
-
result: "success",
|
|
154
|
-
message: `Uninstalled ${op.args.skillName}`,
|
|
155
|
-
};
|
|
156
|
-
}).pipe(withAdaptedStepFailures);
|
|
157
|
-
//# sourceMappingURL=uninstall.js.map
|