@agentxm/workspace-configuration 0.28.12 → 0.28.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/errors.d.ts +29 -0
- package/dist/src/errors.js +68 -0
- package/dist/src/index.d.ts +17 -12
- package/dist/src/index.js +28 -11
- package/dist/src/inline-mcp/add-inline-mcp-server.d.ts +63 -0
- package/dist/src/inline-mcp/add-inline-mcp-server.js +253 -0
- package/dist/src/{inline-mcp.d.ts → inline-mcp/definition.d.ts} +3 -3
- package/dist/src/{inline-mcp.js → inline-mcp/definition.js} +2 -2
- package/dist/src/instructions/manage-instructions.d.ts +125 -0
- package/dist/src/instructions/manage-instructions.js +361 -0
- package/dist/src/mcp-import/apply.d.ts +33 -0
- package/dist/src/{mcp-import.js → mcp-import/apply.js} +8 -50
- package/dist/src/mcp-import/import-mcp-servers.d.ts +49 -0
- package/dist/src/mcp-import/import-mcp-servers.js +117 -0
- package/dist/src/{mcp-import-preflight.d.ts → mcp-import/preflight.d.ts} +1 -1
- package/dist/src/{mcp-import-preflight.js → mcp-import/preflight.js} +2 -2
- package/dist/src/membership/agent-lifecycle.d.ts +20 -0
- package/dist/src/membership/agent-lifecycle.js +33 -0
- package/dist/src/membership/atomic-membership.d.ts +41 -0
- package/dist/src/membership/atomic-membership.js +125 -0
- package/dist/src/membership/configure-agents.d.ts +197 -0
- package/dist/src/membership/configure-agents.js +395 -0
- package/dist/src/membership/validate-agent-ids.d.ts +16 -0
- package/dist/src/membership/validate-agent-ids.js +85 -0
- package/dist/src/{initialization-interaction.d.ts → setup/initialization-interaction.d.ts} +1 -1
- package/dist/src/{initialization.d.ts → setup/initialization.d.ts} +11 -11
- package/dist/src/{initialization.js → setup/initialization.js} +3 -3
- package/dist/src/setup/setup-workspace.d.ts +264 -0
- package/dist/src/setup/setup-workspace.js +512 -0
- package/dist/src/testing.d.ts +95 -4
- package/dist/src/testing.js +215 -4
- package/package.json +11 -9
- package/dist/src/instruction-reconciliation.d.ts +0 -57
- package/dist/src/instruction-reconciliation.js +0 -93
- package/dist/src/mcp-import.d.ts +0 -40
- package/dist/src/membership.d.ts +0 -29
- package/dist/src/membership.js +0 -162
- /package/dist/src/{initialization-interaction.js → setup/initialization-interaction.js} +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adopting the MCP servers a workspace already had.
|
|
3
|
+
*
|
|
4
|
+
* People arrive with MCP servers configured directly in their coding agents.
|
|
5
|
+
* Import records each one the workspace can represent losslessly as an inline
|
|
6
|
+
* settings entry and marks the native entry as AXM-managed, so the next
|
|
7
|
+
* reconciliation recognises it instead of treating it as someone else's file.
|
|
8
|
+
* A server whose definitions disagree across agents is a conflict, not a
|
|
9
|
+
* choice AXM makes on the person's behalf.
|
|
10
|
+
*
|
|
11
|
+
* @experimental This API is unstable and may change without notice.
|
|
12
|
+
*/
|
|
13
|
+
import * as DateTime from "effect/DateTime";
|
|
14
|
+
import * as Effect from "effect/Effect";
|
|
15
|
+
import * as FileSystem from "effect/FileSystem";
|
|
16
|
+
import * as Option from "effect/Option";
|
|
17
|
+
import * as Path from "effect/Path";
|
|
18
|
+
import { OperationJournal, ResolvePlanInteraction, operationPresentation, prepareExecutionCandidate, resolveExecutionCandidate, } from "@agentxm/workspace-operations";
|
|
19
|
+
import { ConfiguredAgentOutcomesProvider, WorkspaceMutations } from "@agentxm/workspace-state";
|
|
20
|
+
import { FootprintRecorder, WorkspaceTransactionScope } from "@agentxm/workspace-transactions";
|
|
21
|
+
import { WorkspaceConfigurationFailed, configurationFailedToStepFailure, workspaceChangeFailedToStepFailure, } from "../errors.js";
|
|
22
|
+
import { applyMcpImport, collectMcpImportSources } from "./apply.js";
|
|
23
|
+
import { preflightMcpImports } from "./preflight.js";
|
|
24
|
+
const plural = (count, singular) => `${String(count)} ${singular}${count === 1 ? "" : "s"}`;
|
|
25
|
+
const settingsDisplayPath = (scope) => scope === "project" ? "axm.json" : ".axm/workspace/axm.json";
|
|
26
|
+
/**
|
|
27
|
+
* Discover and classify every unmanaged MCP server the configured agents and
|
|
28
|
+
* the workspace itself declare. Nothing is written; the candidate names the
|
|
29
|
+
* servers that can be adopted, the ones that conflict, and the ones skipped.
|
|
30
|
+
*/
|
|
31
|
+
export const prepareImportMcpServers = () => Effect.gen(function* () {
|
|
32
|
+
const workspace = yield* WorkspaceMutations;
|
|
33
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
34
|
+
const path = yield* Path.Path;
|
|
35
|
+
const now = yield* DateTime.now;
|
|
36
|
+
const configured = yield* workspace.getConfiguredMcpServerEntries();
|
|
37
|
+
const discovery = yield* collectMcpImportSources(workspace, fileSystem, path);
|
|
38
|
+
const normalized = preflightMcpImports({
|
|
39
|
+
configuredNames: new Set(Object.keys(configured)),
|
|
40
|
+
now,
|
|
41
|
+
sources: discovery.sources,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
_tag: "ImportMcpServers",
|
|
45
|
+
preflight: {
|
|
46
|
+
...normalized,
|
|
47
|
+
skipped: [...normalized.skipped, ...discovery.skipped].sort((left, right) => left.name.localeCompare(right.name) || left.reason.localeCompare(right.reason)),
|
|
48
|
+
},
|
|
49
|
+
scope: workspace.scope,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
const importArtifact = (candidate, baseDir, path) => {
|
|
53
|
+
const adoptions = candidate.preflight.candidates.flatMap((entry) => entry.adoptions);
|
|
54
|
+
const files = [...new Set(adoptions.map((adoption) => adoption.filePath))].sort();
|
|
55
|
+
return {
|
|
56
|
+
path: settingsDisplayPath(candidate.scope),
|
|
57
|
+
scope: candidate.scope,
|
|
58
|
+
change: "updated",
|
|
59
|
+
fileCount: 1 + files.length,
|
|
60
|
+
targets: [
|
|
61
|
+
{ path: settingsDisplayPath(candidate.scope), change: "updated" },
|
|
62
|
+
...files.map((filePath) => ({
|
|
63
|
+
path: path.relative(baseDir, filePath),
|
|
64
|
+
change: "updated",
|
|
65
|
+
})),
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Preview or apply the inline adoption. Every adopted entry is recorded and
|
|
71
|
+
* every native entry marked in one transaction, and the transaction validates
|
|
72
|
+
* the readback before it settles: a half-adopted server would look managed
|
|
73
|
+
* without being managed.
|
|
74
|
+
*/
|
|
75
|
+
export const previewOrApplyImportMcpServers = (candidate, execution) => Effect.gen(function* () {
|
|
76
|
+
const workspace = yield* WorkspaceMutations;
|
|
77
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
78
|
+
const path = yield* Path.Path;
|
|
79
|
+
const artifact = importArtifact(candidate, workspace.baseDir, path);
|
|
80
|
+
const conflictSteps = candidate.preflight.conflicts.map((conflict) => ({
|
|
81
|
+
label: conflict.name,
|
|
82
|
+
readiness: "error",
|
|
83
|
+
errorMessage: conflict.reason,
|
|
84
|
+
}));
|
|
85
|
+
const importSteps = candidate.preflight.candidates.length === 0
|
|
86
|
+
? []
|
|
87
|
+
: [
|
|
88
|
+
{
|
|
89
|
+
label: `Import ${plural(candidate.preflight.candidates.length, "MCP server")}`,
|
|
90
|
+
readiness: "ready",
|
|
91
|
+
message: `Candidates: ${candidate.preflight.candidates.map((entry) => entry.name).join(", ")}`,
|
|
92
|
+
artifact,
|
|
93
|
+
run: applyMcpImport(candidate.preflight.candidates, workspace, fileSystem).pipe(Effect.mapError((failure) => failure instanceof WorkspaceConfigurationFailed
|
|
94
|
+
? configurationFailedToStepFailure(failure)
|
|
95
|
+
: workspaceChangeFailedToStepFailure(failure)), Effect.as({
|
|
96
|
+
result: "success",
|
|
97
|
+
message: `Imported ${plural(candidate.preflight.candidates.length, "MCP server")}`,
|
|
98
|
+
artifact,
|
|
99
|
+
})),
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
const plan = {
|
|
103
|
+
_tag: "Plan",
|
|
104
|
+
name: "Import MCP servers",
|
|
105
|
+
description: Option.some(`Adopt ${plural(candidate.preflight.candidates.length, "unmanaged MCP server")}`),
|
|
106
|
+
presentation: operationPresentation({ imperative: "import", past: "Imported", gerund: "Importing" }, "mcp-server"),
|
|
107
|
+
jobs: [{ concurrency: 1, steps: [...conflictSteps, ...importSteps] }],
|
|
108
|
+
};
|
|
109
|
+
const prepared = yield* prepareExecutionCandidate(plan);
|
|
110
|
+
return yield* resolveExecutionCandidate(prepared, execution);
|
|
111
|
+
});
|
|
112
|
+
/** The inline MCP import use case. */
|
|
113
|
+
export const ImportMcpServers = {
|
|
114
|
+
prepare: prepareImportMcpServers,
|
|
115
|
+
previewOrApply: previewOrApplyImportMcpServers,
|
|
116
|
+
};
|
|
117
|
+
//# sourceMappingURL=import-mcp-servers.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as DateTime from "effect/DateTime";
|
|
2
|
-
import { isAxmManagedMcpEntry } from "@agentxm/
|
|
2
|
+
import { isAxmManagedMcpEntry } from "@agentxm/agent-integration";
|
|
3
3
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
4
|
const stringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string") ? value : undefined;
|
|
5
5
|
const stringRecord = (value) => {
|
|
@@ -266,4 +266,4 @@ export const preflightMcpImports = (args) => {
|
|
|
266
266
|
conflicts: uniqueConflicts,
|
|
267
267
|
};
|
|
268
268
|
};
|
|
269
|
-
//# sourceMappingURL=
|
|
269
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the vendor still maintains a coding agent, and what a person must
|
|
3
|
+
* be told before AXM configures one it has stopped maintaining.
|
|
4
|
+
*
|
|
5
|
+
* The catalog records the lifecycle; membership policy decides what it means:
|
|
6
|
+
* a retired agent is never configured by detection alone, and configuring one
|
|
7
|
+
* explicitly reports why the vendor stopped maintaining it.
|
|
8
|
+
*
|
|
9
|
+
* @experimental This API is unstable and may change without notice.
|
|
10
|
+
*/
|
|
11
|
+
import { type AgentId, type AgentLifecycle } from "@agentxm/extension-model/unstable/agent-capabilities";
|
|
12
|
+
/** Narrows a raw id to a catalog agent id. */
|
|
13
|
+
export declare const isCatalogAgentId: (id: string) => id is AgentId;
|
|
14
|
+
/** Lifecycle for a catalog agent; non-catalog ids (e.g. `universal`) are active. */
|
|
15
|
+
export declare const agentLifecycle: (id: string) => AgentLifecycle;
|
|
16
|
+
/** Whether an agent must only be configured through an explicit user choice. */
|
|
17
|
+
export declare const isRetiredAgent: (id: string) => boolean;
|
|
18
|
+
/** Warning text for configuring an agent its vendor no longer maintains. */
|
|
19
|
+
export declare const lifecycleWarning: (id: string) => string | undefined;
|
|
20
|
+
//# sourceMappingURL=agent-lifecycle.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the vendor still maintains a coding agent, and what a person must
|
|
3
|
+
* be told before AXM configures one it has stopped maintaining.
|
|
4
|
+
*
|
|
5
|
+
* The catalog records the lifecycle; membership policy decides what it means:
|
|
6
|
+
* a retired agent is never configured by detection alone, and configuring one
|
|
7
|
+
* explicitly reports why the vendor stopped maintaining it.
|
|
8
|
+
*
|
|
9
|
+
* @experimental This API is unstable and may change without notice.
|
|
10
|
+
*/
|
|
11
|
+
import { AGENTS_BY_ID, AGENT_IDS, } from "@agentxm/extension-model/unstable/agent-capabilities";
|
|
12
|
+
const catalogAgentIds = new Set(AGENT_IDS);
|
|
13
|
+
/** Narrows a raw id to a catalog agent id. */
|
|
14
|
+
export const isCatalogAgentId = (id) => catalogAgentIds.has(id);
|
|
15
|
+
const ACTIVE = { state: "active" };
|
|
16
|
+
/** Lifecycle for a catalog agent; non-catalog ids (e.g. `universal`) are active. */
|
|
17
|
+
export const agentLifecycle = (id) => isCatalogAgentId(id) ? AGENTS_BY_ID[id].lifecycle : ACTIVE;
|
|
18
|
+
/** Whether an agent must only be configured through an explicit user choice. */
|
|
19
|
+
export const isRetiredAgent = (id) => agentLifecycle(id).state === "retired";
|
|
20
|
+
/** Warning text for configuring an agent its vendor no longer maintains. */
|
|
21
|
+
export const lifecycleWarning = (id) => {
|
|
22
|
+
if (!isCatalogAgentId(id))
|
|
23
|
+
return undefined;
|
|
24
|
+
const name = AGENTS_BY_ID[id].name;
|
|
25
|
+
const lifecycle = agentLifecycle(id);
|
|
26
|
+
if (lifecycle.state === "active")
|
|
27
|
+
return undefined;
|
|
28
|
+
const since = lifecycle.since === null ? "" : ` since ${lifecycle.since}`;
|
|
29
|
+
const successor = lifecycle.supersededBy === null ? "" : ` Superseded by ${lifecycle.supersededBy}.`;
|
|
30
|
+
const note = lifecycle.note === null ? "" : ` ${lifecycle.note}`;
|
|
31
|
+
return `${name} is ${lifecycle.state}${since}.${successor}${note}`;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=agent-lifecycle.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One workspace transaction for a configured-agent membership change.
|
|
3
|
+
*
|
|
4
|
+
* Membership is a set: a request that records `cursor` and then fails to
|
|
5
|
+
* realize its outputs must leave the set as it found it. Every step of the
|
|
6
|
+
* change therefore runs inside a single transaction, and the transaction only
|
|
7
|
+
* settles once the workspace actually reports the transition the request
|
|
8
|
+
* asked for. Both the post-transaction check and the failure vocabulary
|
|
9
|
+
* belong here — an application that supplied either could make the same
|
|
10
|
+
* membership change mean two different things.
|
|
11
|
+
*
|
|
12
|
+
* @experimental This API is unstable and may change without notice.
|
|
13
|
+
*/
|
|
14
|
+
import * as Effect from "effect/Effect";
|
|
15
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
16
|
+
import type * as Path from "effect/Path";
|
|
17
|
+
import { type PlannedJobStep } from "@agentxm/workspace-operations";
|
|
18
|
+
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
19
|
+
import { WorkspaceTransactionScope } from "@agentxm/workspace-transactions";
|
|
20
|
+
/** What the workspace must report once the transition has settled. */
|
|
21
|
+
export type MembershipTransition = {
|
|
22
|
+
readonly kind: "add";
|
|
23
|
+
readonly agentIds: ReadonlyArray<string>;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: "remove";
|
|
26
|
+
readonly agentIds: ReadonlyArray<string>;
|
|
27
|
+
};
|
|
28
|
+
interface AtomicMembershipStepsArgs<Requirements, Output> {
|
|
29
|
+
readonly steps: ReadonlyArray<PlannedJobStep<Requirements, Output>>;
|
|
30
|
+
/** The membership change the settled workspace is checked against. */
|
|
31
|
+
readonly transition: MembershipTransition;
|
|
32
|
+
}
|
|
33
|
+
/** The transaction scope and platform every atomic membership step joins. */
|
|
34
|
+
export type AtomicMembershipRequirements<Requirements> = Requirements | FileSystem.FileSystem | Path.Path | WorkspaceTransactionScope;
|
|
35
|
+
/**
|
|
36
|
+
* Keep plan-level preview and per-step results while applying every membership
|
|
37
|
+
* and artifact step through one workspace transaction.
|
|
38
|
+
*/
|
|
39
|
+
export declare const makeAtomicMembershipSteps: <Requirements, Output>(args: AtomicMembershipStepsArgs<Requirements, Output>) => Effect.Effect<ReadonlyArray<PlannedJobStep<AtomicMembershipRequirements<Requirements>, Output>>, never, WorkspaceMutations>;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=atomic-membership.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One workspace transaction for a configured-agent membership change.
|
|
3
|
+
*
|
|
4
|
+
* Membership is a set: a request that records `cursor` and then fails to
|
|
5
|
+
* realize its outputs must leave the set as it found it. Every step of the
|
|
6
|
+
* change therefore runs inside a single transaction, and the transaction only
|
|
7
|
+
* settles once the workspace actually reports the transition the request
|
|
8
|
+
* asked for. Both the post-transaction check and the failure vocabulary
|
|
9
|
+
* belong here — an application that supplied either could make the same
|
|
10
|
+
* membership change mean two different things.
|
|
11
|
+
*
|
|
12
|
+
* @experimental This API is unstable and may change without notice.
|
|
13
|
+
*/
|
|
14
|
+
import * as Effect from "effect/Effect";
|
|
15
|
+
import * as Ref from "effect/Ref";
|
|
16
|
+
import { StepFailure, } from "@agentxm/workspace-operations";
|
|
17
|
+
import { WorkspaceMutations } from "@agentxm/workspace-state";
|
|
18
|
+
import { WorkspaceTransactionScope, runWorkspaceTransaction, } from "@agentxm/workspace-transactions";
|
|
19
|
+
import { WorkspaceConfigurationFailed, configurationFailedToStepFailure, workspaceChangeFailedToStepFailure, } from "../errors.js";
|
|
20
|
+
const failedResult = (error, message = error.detail) => ({
|
|
21
|
+
result: "error",
|
|
22
|
+
message,
|
|
23
|
+
error,
|
|
24
|
+
});
|
|
25
|
+
const blockedResult = (message) => failedResult(new StepFailure({
|
|
26
|
+
category: "conflict",
|
|
27
|
+
detail: message,
|
|
28
|
+
}), message);
|
|
29
|
+
const rollbackResults = (executable, attempt, transactionError) => {
|
|
30
|
+
const actualFailureIndex = attempt.failedIndex ?? Math.max(0, attempt.results.length - 1);
|
|
31
|
+
const failedLabel = executable[actualFailureIndex]?.label ?? "atomic agent membership validation";
|
|
32
|
+
return executable.map((_, index) => {
|
|
33
|
+
if (index < actualFailureIndex) {
|
|
34
|
+
return blockedResult(`blocked: rolled back after ${failedLabel} failed`);
|
|
35
|
+
}
|
|
36
|
+
if (index > actualFailureIndex) {
|
|
37
|
+
return blockedResult(`blocked by ${failedLabel} failure`);
|
|
38
|
+
}
|
|
39
|
+
const attempted = attempt.results[index];
|
|
40
|
+
return attempted?.result === "error"
|
|
41
|
+
? attempted
|
|
42
|
+
: failedResult(transactionError, transactionError.detail);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Read the settled membership back and refuse the transaction when it does
|
|
47
|
+
* not show the requested transition. A transition that wrote nothing and one
|
|
48
|
+
* that wrote half are the same answer to the caller: the change did not
|
|
49
|
+
* happen, and the transaction restores what it found.
|
|
50
|
+
*/
|
|
51
|
+
const verifyTransition = (workspace, transition) => Effect.gen(function* () {
|
|
52
|
+
const configured = new Set(yield* workspace
|
|
53
|
+
.getConfiguredAgents()
|
|
54
|
+
.pipe(Effect.mapError(workspaceChangeFailedToStepFailure)));
|
|
55
|
+
const unmet = transition.kind === "add"
|
|
56
|
+
? transition.agentIds.filter((agentId) => !configured.has(agentId))
|
|
57
|
+
: transition.agentIds.filter((agentId) => configured.has(agentId));
|
|
58
|
+
if (unmet.length === 0)
|
|
59
|
+
return;
|
|
60
|
+
return yield* configurationFailedToStepFailure(new WorkspaceConfigurationFailed({
|
|
61
|
+
category: "internal",
|
|
62
|
+
detail: transition.kind === "add"
|
|
63
|
+
? `Agent membership transition did not configure: ${unmet.join(", ")}`
|
|
64
|
+
: `Agent membership transition did not remove: ${unmet.join(", ")}`,
|
|
65
|
+
}));
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Keep plan-level preview and per-step results while applying every membership
|
|
69
|
+
* and artifact step through one workspace transaction.
|
|
70
|
+
*/
|
|
71
|
+
export const makeAtomicMembershipSteps = (args) => Effect.gen(function* () {
|
|
72
|
+
const workspace = yield* WorkspaceMutations;
|
|
73
|
+
if (args.steps.some((step) => step.readiness === "error")) {
|
|
74
|
+
return args.steps.map((step) => step);
|
|
75
|
+
}
|
|
76
|
+
const executable = args.steps.filter((step) => step.readiness !== "error");
|
|
77
|
+
const attemptRef = yield* Ref.make({ results: [] });
|
|
78
|
+
const transition = runWorkspaceTransaction({
|
|
79
|
+
transition: Effect.gen(function* () {
|
|
80
|
+
const results = [];
|
|
81
|
+
for (const [index, step] of executable.entries()) {
|
|
82
|
+
const result = yield* step.run.pipe(Effect.catch((error) => Effect.succeed(failedResult(error))));
|
|
83
|
+
results.push(result);
|
|
84
|
+
yield* Ref.set(attemptRef, {
|
|
85
|
+
results: [...results],
|
|
86
|
+
...(result.result === "error" ? { failedIndex: index } : {}),
|
|
87
|
+
});
|
|
88
|
+
if (result.result === "error") {
|
|
89
|
+
return yield* result.error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}),
|
|
94
|
+
validate: () => verifyTransition(workspace, args.transition),
|
|
95
|
+
}).pipe(Effect.catch((transactionError) => Ref.get(attemptRef).pipe(Effect.map((attempt) => rollbackResults(executable, attempt, transactionError._tag === "StepFailure"
|
|
96
|
+
? transactionError
|
|
97
|
+
: new StepFailure({
|
|
98
|
+
category: "internal",
|
|
99
|
+
detail: "The agent membership transition did not complete",
|
|
100
|
+
cause: transactionError,
|
|
101
|
+
}))))));
|
|
102
|
+
const sharedTransition = yield* Effect.cached(transition);
|
|
103
|
+
let resultIndex = 0;
|
|
104
|
+
// Every step now runs the shared transaction, so the transaction scope and
|
|
105
|
+
// platform join its requirements; the plan carries them to the boundary.
|
|
106
|
+
return args.steps.map((step) => {
|
|
107
|
+
if (step.readiness === "error")
|
|
108
|
+
return step;
|
|
109
|
+
const index = resultIndex;
|
|
110
|
+
resultIndex += 1;
|
|
111
|
+
return {
|
|
112
|
+
...step,
|
|
113
|
+
run: sharedTransition.pipe(Effect.flatMap((results) => {
|
|
114
|
+
const result = results[index];
|
|
115
|
+
return result === undefined
|
|
116
|
+
? new StepFailure({
|
|
117
|
+
category: "internal",
|
|
118
|
+
detail: `Atomic agent membership transition omitted step ${index + 1}`,
|
|
119
|
+
})
|
|
120
|
+
: Effect.succeed(result);
|
|
121
|
+
})),
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
//# sourceMappingURL=atomic-membership.js.map
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configured-agent membership: which coding agents this workspace configures,
|
|
3
|
+
* and the change a request makes to that set.
|
|
4
|
+
*
|
|
5
|
+
* Membership is a set of decisions, not a list of writes. Adding names the
|
|
6
|
+
* agents a request actually configures — the ids it gave plus what detection
|
|
7
|
+
* found, minus the ones already configured and the ones whose vendor retired
|
|
8
|
+
* them unless the request named them itself. Removing names the agents that
|
|
9
|
+
* leave and the projection state the workspace must be reconciled to once
|
|
10
|
+
* they have. Either change settles atomically: the recorded set and the agent
|
|
11
|
+
* outputs realized for it move together or not at all.
|
|
12
|
+
*
|
|
13
|
+
* Realizing installed extensions for a new membership, and cleaning up the
|
|
14
|
+
* outputs a departing agent owned, are reconciliation work. The application
|
|
15
|
+
* plans those steps through the reconciliation feature and hands them to
|
|
16
|
+
* `previewOrApply` as ordinary plan steps, so this feature never depends on a
|
|
17
|
+
* peer feature and the whole change still settles as one closure.
|
|
18
|
+
*
|
|
19
|
+
* @experimental This API is unstable and may change without notice.
|
|
20
|
+
*/
|
|
21
|
+
import * as Effect from "effect/Effect";
|
|
22
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
23
|
+
import type * as Path from "effect/Path";
|
|
24
|
+
import * as Schema from "effect/Schema";
|
|
25
|
+
import { detectAgentsForScope } from "@agentxm/agent-integration";
|
|
26
|
+
import type { PerAgentType } from "@agentxm/extension-model/unstable/extensions/common";
|
|
27
|
+
import type { WorkspaceScope } from "@agentxm/extension-model/unstable/workspace-scope";
|
|
28
|
+
import { OperationJournal, ResolvePlanInteraction, type OperationResolution, type PlanExecution, type PlannedJobStep } from "@agentxm/workspace-operations";
|
|
29
|
+
import { ConfiguredAgentOutcomesProvider, WorkspaceMutations, type WorkspaceStateReadFailure } from "@agentxm/workspace-state";
|
|
30
|
+
import { FootprintRecorder, WorkspaceTransactionScope } from "@agentxm/workspace-transactions";
|
|
31
|
+
import { WorkspaceConfigurationFailed, type WorkspaceConfigurationExecutionFailure } from "../errors.js";
|
|
32
|
+
/** Every failure a membership request settles into before it is planned. */
|
|
33
|
+
export type ConfigureAgentsFailure = WorkspaceConfigurationFailed | WorkspaceStateReadFailure | Effect.Error<ReturnType<typeof detectAgentsForScope>>;
|
|
34
|
+
/** Every failure resolving a prepared membership change can surface. */
|
|
35
|
+
export type MembershipExecutionFailure = WorkspaceConfigurationExecutionFailure;
|
|
36
|
+
/**
|
|
37
|
+
* A request that changes nothing still has an answer: what it named and why
|
|
38
|
+
* the workspace already satisfies it.
|
|
39
|
+
*/
|
|
40
|
+
export interface ConfiguredAgentsUnchanged {
|
|
41
|
+
readonly _tag: "Unchanged";
|
|
42
|
+
readonly reason: "already-configured" | "no-active-detected" | "already-absent";
|
|
43
|
+
readonly message: string;
|
|
44
|
+
/** Retired agents detection offered and did not configure, if any. */
|
|
45
|
+
readonly retiredDetected: ReadonlyArray<RetiredAgentNotice>;
|
|
46
|
+
}
|
|
47
|
+
/** A retired agent detection found, and why a person should know about it. */
|
|
48
|
+
export interface RetiredAgentNotice {
|
|
49
|
+
readonly agentId: string;
|
|
50
|
+
/** The vendor-lifecycle sentence, and that detection did not configure it. */
|
|
51
|
+
readonly detail: string;
|
|
52
|
+
}
|
|
53
|
+
export interface AddConfiguredAgentsRequest {
|
|
54
|
+
/** Agent ids the person named explicitly. */
|
|
55
|
+
readonly ids: ReadonlyArray<string>;
|
|
56
|
+
/** Also configure the agents detection finds for this workspace's scope. */
|
|
57
|
+
readonly detected: boolean;
|
|
58
|
+
/** The person accepted the vendor-lifecycle warnings this change carries. */
|
|
59
|
+
readonly acceptWarnings?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export interface AddConfiguredAgentsCandidate {
|
|
62
|
+
readonly _tag: "AddConfiguredAgents";
|
|
63
|
+
/** The agents this change configures, in the order they were decided. */
|
|
64
|
+
readonly agentIds: ReadonlyArray<string>;
|
|
65
|
+
/** The membership the workspace holds once the change settles. */
|
|
66
|
+
readonly configuredAgents: ReadonlyArray<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Retired agents detection offered. They are not configured by detection
|
|
69
|
+
* alone; naming one explicitly configures it and warns.
|
|
70
|
+
*/
|
|
71
|
+
readonly retiredDetected: ReadonlyArray<RetiredAgentNotice>;
|
|
72
|
+
/** One sentence per configured agent whose vendor no longer maintains it. */
|
|
73
|
+
readonly lifecycleWarnings: ReadonlyArray<string>;
|
|
74
|
+
readonly acceptWarnings: boolean;
|
|
75
|
+
readonly scope: WorkspaceScope;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Settle which agents an add request configures.
|
|
79
|
+
*
|
|
80
|
+
* Explicitly named ids are validated first, so a typo is refused before
|
|
81
|
+
* detection runs. Detection contributes what it found minus the agents whose
|
|
82
|
+
* vendor retired them: a retired agent is a deliberate choice, never a
|
|
83
|
+
* consequence of having it installed.
|
|
84
|
+
*/
|
|
85
|
+
export declare const prepareAddConfiguredAgents: (request: AddConfiguredAgentsRequest) => Effect.Effect<AddConfiguredAgentsCandidate | ConfiguredAgentsUnchanged, ConfigureAgentsFailure, WorkspaceMutations | FileSystem.FileSystem | Path.Path>;
|
|
86
|
+
/**
|
|
87
|
+
* Steps the application planned through the reconciliation feature, applied
|
|
88
|
+
* inside this change's closure so membership and realized outputs settle
|
|
89
|
+
* together.
|
|
90
|
+
*/
|
|
91
|
+
export interface MembershipReconciliation<Requirements, Output> {
|
|
92
|
+
readonly steps: ReadonlyArray<PlannedJobStep<Requirements, Output>>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The plan pipeline a membership change resolves through, plus the workspace
|
|
96
|
+
* transition its steps settle inside. `Requirements` carries whatever the
|
|
97
|
+
* reconciliation steps the application supplied still need.
|
|
98
|
+
*/
|
|
99
|
+
export type MembershipExecutionRequirements<Requirements> = Requirements | ConfiguredAgentOutcomesProvider | FileSystem.FileSystem | FootprintRecorder | OperationJournal | Path.Path | ResolvePlanInteraction | WorkspaceMutations | WorkspaceTransactionScope;
|
|
100
|
+
/**
|
|
101
|
+
* Preview or apply a settled add. The membership writes and the supplied
|
|
102
|
+
* reconciliation steps run inside one workspace transaction, and the
|
|
103
|
+
* transaction only settles once the workspace reports the arriving agents as
|
|
104
|
+
* configured.
|
|
105
|
+
*/
|
|
106
|
+
export declare const previewOrApplyAddConfiguredAgents: <MaterializeRequirements = never, Output = void>(candidate: AddConfiguredAgentsCandidate, execution: PlanExecution, reconciliation?: MembershipReconciliation<MaterializeRequirements, Output>) => Effect.Effect<OperationResolution<Output>, MembershipExecutionFailure, MembershipExecutionRequirements<MaterializeRequirements>>;
|
|
107
|
+
export interface RemoveConfiguredAgentsRequest {
|
|
108
|
+
readonly ids: ReadonlyArray<string>;
|
|
109
|
+
}
|
|
110
|
+
/** The projection state the workspace is reconciled to once the agents leave. */
|
|
111
|
+
export interface DepartingAgentReconciliation {
|
|
112
|
+
readonly desiredAgentIds: ReadonlySet<string>;
|
|
113
|
+
readonly expectedNames: Readonly<Record<PerAgentType, ReadonlySet<string>>>;
|
|
114
|
+
}
|
|
115
|
+
export interface RemoveConfiguredAgentsCandidate {
|
|
116
|
+
readonly _tag: "RemoveConfiguredAgents";
|
|
117
|
+
readonly agentIds: ReadonlyArray<string>;
|
|
118
|
+
/** The membership the workspace holds once the change settles. */
|
|
119
|
+
readonly configuredAgents: ReadonlyArray<string>;
|
|
120
|
+
readonly reconciliation: DepartingAgentReconciliation;
|
|
121
|
+
/** The workspace root removed artifacts are reported relative to. */
|
|
122
|
+
readonly baseDir: string;
|
|
123
|
+
readonly scope: WorkspaceScope;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Settle which agents a remove request takes out of membership, and the
|
|
127
|
+
* reconciliation state their outputs are cleaned up against.
|
|
128
|
+
*
|
|
129
|
+
* Removing every named agent when none of them is configured is a reported
|
|
130
|
+
* no-op; removing a mixture is a refusal, because the request described a
|
|
131
|
+
* workspace that does not exist. Cleaning up owned projections needs a
|
|
132
|
+
* complete desired graph — without one, AXM cannot tell its own output from
|
|
133
|
+
* a person's file, so it refuses rather than guess.
|
|
134
|
+
*/
|
|
135
|
+
export declare const prepareRemoveConfiguredAgents: (request: RemoveConfiguredAgentsRequest) => Effect.Effect<RemoveConfiguredAgentsCandidate | ConfiguredAgentsUnchanged, ConfigureAgentsFailure, WorkspaceMutations>;
|
|
136
|
+
/**
|
|
137
|
+
* Preview or apply a settled removal. The cleanup steps the application
|
|
138
|
+
* planned run before the membership writes and inside the same transaction,
|
|
139
|
+
* so a cleanup that cannot complete leaves the agent configured.
|
|
140
|
+
*/
|
|
141
|
+
export declare const previewOrApplyRemoveConfiguredAgents: <CleanupRequirements = never, Output = void>(candidate: RemoveConfiguredAgentsCandidate, execution: PlanExecution, reconciliation?: MembershipReconciliation<CleanupRequirements, Output>) => Effect.Effect<OperationResolution<Output>, MembershipExecutionFailure, MembershipExecutionRequirements<CleanupRequirements>>;
|
|
142
|
+
declare const ConfiguredAgentRowSchema: Schema.Struct<{
|
|
143
|
+
readonly id: Schema.String;
|
|
144
|
+
readonly name: Schema.String;
|
|
145
|
+
readonly configured: Schema.Boolean;
|
|
146
|
+
readonly detected: Schema.Boolean;
|
|
147
|
+
/** Instruction-projection health for a configured agent; `-` when not configured. */
|
|
148
|
+
readonly instructions: Schema.String;
|
|
149
|
+
/** Whether the vendor still maintains the agent: active, deprecated, retired. */
|
|
150
|
+
readonly lifecycle: Schema.String;
|
|
151
|
+
}>;
|
|
152
|
+
export type ConfiguredAgentRow = typeof ConfiguredAgentRowSchema.Type;
|
|
153
|
+
export declare const ConfiguredAgentInventorySchema: Schema.Struct<{
|
|
154
|
+
readonly items: Schema.$Array<Schema.Struct<{
|
|
155
|
+
readonly id: Schema.String;
|
|
156
|
+
readonly name: Schema.String;
|
|
157
|
+
readonly configured: Schema.Boolean;
|
|
158
|
+
readonly detected: Schema.Boolean;
|
|
159
|
+
/** Instruction-projection health for a configured agent; `-` when not configured. */
|
|
160
|
+
readonly instructions: Schema.String;
|
|
161
|
+
/** Whether the vendor still maintains the agent: active, deprecated, retired. */
|
|
162
|
+
readonly lifecycle: Schema.String;
|
|
163
|
+
}>>;
|
|
164
|
+
readonly configured: Schema.$Array<Schema.String>;
|
|
165
|
+
readonly detected: Schema.$Array<Schema.String>;
|
|
166
|
+
readonly available: Schema.$Array<Schema.String>;
|
|
167
|
+
readonly count: Schema.Number;
|
|
168
|
+
}>;
|
|
169
|
+
export type ConfiguredAgentInventory = typeof ConfiguredAgentInventorySchema.Type;
|
|
170
|
+
export interface ListConfiguredAgentsRequest {
|
|
171
|
+
/** Report only the agents detection found. */
|
|
172
|
+
readonly detected?: boolean;
|
|
173
|
+
/** Report every supported agent id, configured or not. */
|
|
174
|
+
readonly available?: boolean;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Report configured, detected, and available coding agents.
|
|
178
|
+
*
|
|
179
|
+
* The default view answers "what is in play here": the agents this workspace
|
|
180
|
+
* configures and the ones present on the machine. `universal` is materialized
|
|
181
|
+
* for every workspace and is never reported as a membership choice.
|
|
182
|
+
*/
|
|
183
|
+
export declare const listConfiguredAgents: (request?: ListConfiguredAgentsRequest) => Effect.Effect<ConfiguredAgentInventory, ConfigureAgentsFailure, WorkspaceMutations | FileSystem.FileSystem | Path.Path>;
|
|
184
|
+
/** The configured-agent membership use case. */
|
|
185
|
+
export declare const ConfigureAgents: {
|
|
186
|
+
readonly add: {
|
|
187
|
+
readonly prepare: (request: AddConfiguredAgentsRequest) => Effect.Effect<AddConfiguredAgentsCandidate | ConfiguredAgentsUnchanged, ConfigureAgentsFailure, WorkspaceMutations | FileSystem.FileSystem | Path.Path>;
|
|
188
|
+
readonly previewOrApply: <MaterializeRequirements = never, Output = void>(candidate: AddConfiguredAgentsCandidate, execution: PlanExecution, reconciliation?: MembershipReconciliation<MaterializeRequirements, Output>) => Effect.Effect<OperationResolution<Output>, MembershipExecutionFailure, MembershipExecutionRequirements<MaterializeRequirements>>;
|
|
189
|
+
};
|
|
190
|
+
readonly remove: {
|
|
191
|
+
readonly prepare: (request: RemoveConfiguredAgentsRequest) => Effect.Effect<RemoveConfiguredAgentsCandidate | ConfiguredAgentsUnchanged, ConfigureAgentsFailure, WorkspaceMutations>;
|
|
192
|
+
readonly previewOrApply: <CleanupRequirements = never, Output = void>(candidate: RemoveConfiguredAgentsCandidate, execution: PlanExecution, reconciliation?: MembershipReconciliation<CleanupRequirements, Output>) => Effect.Effect<OperationResolution<Output>, MembershipExecutionFailure, MembershipExecutionRequirements<CleanupRequirements>>;
|
|
193
|
+
};
|
|
194
|
+
readonly list: (request?: ListConfiguredAgentsRequest) => Effect.Effect<ConfiguredAgentInventory, ConfigureAgentsFailure, WorkspaceMutations | FileSystem.FileSystem | Path.Path>;
|
|
195
|
+
};
|
|
196
|
+
export {};
|
|
197
|
+
//# sourceMappingURL=configure-agents.d.ts.map
|