@agentxm/workspace-configuration 0.28.11 → 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,395 @@
|
|
|
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 * as Option from "effect/Option";
|
|
23
|
+
import * as Schema from "effect/Schema";
|
|
24
|
+
import { detectAgentsForScope } from "@agentxm/agent-integration";
|
|
25
|
+
import { AGENTS } from "@agentxm/extension-model/unstable/agents/registry";
|
|
26
|
+
import { CONFIGURABLE_AGENT_IDS } from "@agentxm/extension-model/unstable/agents/types";
|
|
27
|
+
import { OperationJournal, observeUnit, prepareExecutionCandidate, ResolvePlanInteraction, resolveExecutionCandidate, } from "@agentxm/workspace-operations";
|
|
28
|
+
import { UNIVERSAL_AGENT_ID, expectedProjectionNames, observeInstructionProjection, resolveInstructionsConfig, } from "@agentxm/workspace-projection";
|
|
29
|
+
import { ConfiguredAgentOutcomesProvider, WorkspaceMutations, } from "@agentxm/workspace-state";
|
|
30
|
+
import { FootprintRecorder, WorkspaceTransactionScope } from "@agentxm/workspace-transactions";
|
|
31
|
+
import { WorkspaceConfigurationFailed, workspaceChangeFailedToStepFailure, } from "../errors.js";
|
|
32
|
+
import { agentLifecycle, isRetiredAgent, lifecycleWarning } from "./agent-lifecycle.js";
|
|
33
|
+
import { makeAtomicMembershipSteps } from "./atomic-membership.js";
|
|
34
|
+
import { dedupe, validateAgentIds } from "./validate-agent-ids.js";
|
|
35
|
+
/** Stable workspace-relative path of the settings file membership is recorded in. */
|
|
36
|
+
const settingsDisplayPath = (scope) => scope === "project" ? "axm.json" : ".axm/workspace/axm.json";
|
|
37
|
+
const membershipArtifact = (scope, agentId, change) => ({
|
|
38
|
+
path: settingsDisplayPath(scope),
|
|
39
|
+
scope,
|
|
40
|
+
agents: [agentId],
|
|
41
|
+
change,
|
|
42
|
+
fileCount: 1,
|
|
43
|
+
targets: [{ path: settingsDisplayPath(scope), change, agentIds: [agentId] }],
|
|
44
|
+
});
|
|
45
|
+
const unchanged = (reason, message, retiredDetected = []) => ({ _tag: "Unchanged", reason, message, retiredDetected });
|
|
46
|
+
/**
|
|
47
|
+
* Settle which agents an add request configures.
|
|
48
|
+
*
|
|
49
|
+
* Explicitly named ids are validated first, so a typo is refused before
|
|
50
|
+
* detection runs. Detection contributes what it found minus the agents whose
|
|
51
|
+
* vendor retired them: a retired agent is a deliberate choice, never a
|
|
52
|
+
* consequence of having it installed.
|
|
53
|
+
*/
|
|
54
|
+
export const prepareAddConfiguredAgents = (request) => Effect.gen(function* () {
|
|
55
|
+
const workspace = yield* WorkspaceMutations;
|
|
56
|
+
if (request.ids.length === 0 && !request.detected) {
|
|
57
|
+
return yield* new WorkspaceConfigurationFailed({
|
|
58
|
+
category: "usage",
|
|
59
|
+
detail: "Provide one or more agent IDs, or pass --detected.",
|
|
60
|
+
suggestions: [
|
|
61
|
+
{ description: "List supported IDs.", cmd: "axm agents list --available" },
|
|
62
|
+
{ description: "Configure detected agents.", cmd: "axm agents add --detected" },
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const requested = yield* validateAgentIds(request.ids);
|
|
67
|
+
const configured = yield* workspace.getConfiguredAgents();
|
|
68
|
+
const configuredSet = new Set(configured);
|
|
69
|
+
const detected = request.detected
|
|
70
|
+
? yield* observeUnit({ id: "detect-agents", label: "coding agent detection" }, detectAgentsForScope(workspace.baseDir, workspace.scope).pipe(Effect.map((agents) => agents.map((agent) => agent.id))))
|
|
71
|
+
: [];
|
|
72
|
+
const detectedConfigurable = yield* validateAgentIds(detected);
|
|
73
|
+
const requestedSet = new Set(requested);
|
|
74
|
+
const retiredDetected = detectedConfigurable.flatMap((id) => isRetiredAgent(id) && !requestedSet.has(id)
|
|
75
|
+
? [
|
|
76
|
+
{
|
|
77
|
+
agentId: id,
|
|
78
|
+
detail: `${lifecycleWarning(id) ?? `${id} is retired.`} It was not added automatically; run \`axm agents add ${id}\` to opt in.`,
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
: []);
|
|
82
|
+
const autoDetected = detectedConfigurable.filter((id) => !isRetiredAgent(id) || requestedSet.has(id));
|
|
83
|
+
const agentIds = dedupe([...requested, ...autoDetected]).filter((id) => !configuredSet.has(id));
|
|
84
|
+
if (agentIds.length === 0) {
|
|
85
|
+
return unchanged(retiredDetected.length > 0 && requested.length === 0
|
|
86
|
+
? "no-active-detected"
|
|
87
|
+
: "already-configured", retiredDetected.length > 0 && requested.length === 0
|
|
88
|
+
? "No active detected agents to configure"
|
|
89
|
+
: "All requested agents are already configured", retiredDetected);
|
|
90
|
+
}
|
|
91
|
+
// Warn rather than block: the workspace may still need a retired agent
|
|
92
|
+
// configured, but the person must know the vendor stopped maintaining it.
|
|
93
|
+
const lifecycleWarnings = agentIds.flatMap((agentId) => {
|
|
94
|
+
const warning = lifecycleWarning(agentId);
|
|
95
|
+
return warning === undefined ? [] : [`${agentId}: ${warning}`];
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
_tag: "AddConfiguredAgents",
|
|
99
|
+
agentIds,
|
|
100
|
+
configuredAgents: [...configured, ...agentIds],
|
|
101
|
+
retiredDetected,
|
|
102
|
+
lifecycleWarnings,
|
|
103
|
+
acceptWarnings: request.acceptWarnings ?? false,
|
|
104
|
+
scope: workspace.scope,
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
const addAgentStep = (scope, agentId) => ({
|
|
108
|
+
label: `Add ${agentId}`,
|
|
109
|
+
readiness: "ready",
|
|
110
|
+
artifact: membershipArtifact(scope, agentId, "updated"),
|
|
111
|
+
run: Effect.gen(function* () {
|
|
112
|
+
const workspace = yield* WorkspaceMutations;
|
|
113
|
+
yield* workspace
|
|
114
|
+
.addConfiguredAgent(agentId)
|
|
115
|
+
.pipe(Effect.mapError(workspaceChangeFailedToStepFailure));
|
|
116
|
+
return {
|
|
117
|
+
result: "success",
|
|
118
|
+
message: `Configured ${agentId}`,
|
|
119
|
+
artifact: membershipArtifact(scope, agentId, "updated"),
|
|
120
|
+
};
|
|
121
|
+
}),
|
|
122
|
+
});
|
|
123
|
+
/** Aggregate a filtered artifact's change from the targets that survived. */
|
|
124
|
+
const aggregateArtifactChange = (targets, fallback) => {
|
|
125
|
+
if (targets.length === 0)
|
|
126
|
+
return fallback;
|
|
127
|
+
if (targets.some((target) => target.change === "created"))
|
|
128
|
+
return "created";
|
|
129
|
+
if (targets.some((target) => target.change === "updated"))
|
|
130
|
+
return "updated";
|
|
131
|
+
if (targets.some((target) => target.change === "removed"))
|
|
132
|
+
return "removed";
|
|
133
|
+
return "unchanged";
|
|
134
|
+
};
|
|
135
|
+
const materializationArtifact = (scope, agentIds) => ({
|
|
136
|
+
path: "managed agent artifacts",
|
|
137
|
+
scope,
|
|
138
|
+
agents: agentIds,
|
|
139
|
+
change: "updated",
|
|
140
|
+
targets: agentIds.map((agentId) => ({
|
|
141
|
+
path: `${agentId} managed agent artifacts`,
|
|
142
|
+
change: "updated",
|
|
143
|
+
agentIds: [agentId],
|
|
144
|
+
})),
|
|
145
|
+
});
|
|
146
|
+
/**
|
|
147
|
+
* A reconciliation step planned for the whole workspace reports every agent
|
|
148
|
+
* it touches. Adding agents only claims the arriving ones, so each artifact
|
|
149
|
+
* is narrowed to the targets that belong to them.
|
|
150
|
+
*/
|
|
151
|
+
const narrowToArrivingAgents = (artifact, agentIds) => {
|
|
152
|
+
if (artifact.targets === undefined || artifact.targets.length === 0)
|
|
153
|
+
return artifact;
|
|
154
|
+
const targets = artifact.targets.filter((target) => target.agentIds === undefined ||
|
|
155
|
+
target.agentIds.length === 0 ||
|
|
156
|
+
target.agentIds.some((agentId) => agentIds.includes(agentId)));
|
|
157
|
+
if (targets.length === 0)
|
|
158
|
+
return artifact;
|
|
159
|
+
const agents = artifact.agents?.filter((agentId) => agentIds.includes(agentId));
|
|
160
|
+
return {
|
|
161
|
+
...artifact,
|
|
162
|
+
path: targets[0]?.path ?? artifact.path,
|
|
163
|
+
...(agents === undefined || agents.length === 0 ? {} : { agents }),
|
|
164
|
+
change: aggregateArtifactChange(targets, artifact.change),
|
|
165
|
+
targets,
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
const attributeToArrivingAgents = (scope, agentIds, step) => {
|
|
169
|
+
if (step.readiness === "error")
|
|
170
|
+
return step;
|
|
171
|
+
const artifact = step.artifact === undefined
|
|
172
|
+
? materializationArtifact(scope, agentIds)
|
|
173
|
+
: narrowToArrivingAgents(step.artifact, agentIds);
|
|
174
|
+
return {
|
|
175
|
+
...step,
|
|
176
|
+
artifact,
|
|
177
|
+
run: Effect.gen(function* () {
|
|
178
|
+
const result = yield* step.run;
|
|
179
|
+
if (result.result === "error")
|
|
180
|
+
return result;
|
|
181
|
+
return result.artifact === undefined
|
|
182
|
+
? { ...result, artifact: materializationArtifact(scope, agentIds) }
|
|
183
|
+
: { ...result, artifact: narrowToArrivingAgents(result.artifact, agentIds) };
|
|
184
|
+
}),
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
const addPlan = (candidate, steps) => ({
|
|
188
|
+
_tag: "Plan",
|
|
189
|
+
name: "Add coding agents",
|
|
190
|
+
description: Option.some(`Configure ${candidate.agentIds.join(", ")} and materialize installed extensions`),
|
|
191
|
+
presentation: {
|
|
192
|
+
verb: { imperative: "configure", past: "Configured", gerund: "Configuring" },
|
|
193
|
+
subject: { singular: "agent", plural: "agents" },
|
|
194
|
+
},
|
|
195
|
+
...(candidate.lifecycleWarnings.length === 0
|
|
196
|
+
? {}
|
|
197
|
+
: {
|
|
198
|
+
riskConditions: [
|
|
199
|
+
{
|
|
200
|
+
level: "override-required",
|
|
201
|
+
id: "retired-agent-lifecycle-warnings",
|
|
202
|
+
policy: "accept-warnings",
|
|
203
|
+
requiredFlag: "--accept-warnings",
|
|
204
|
+
detail: candidate.lifecycleWarnings.join("; "),
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
}),
|
|
208
|
+
jobs: [{ concurrency: 1, executionPolicy: "best-effort", steps }],
|
|
209
|
+
});
|
|
210
|
+
/**
|
|
211
|
+
* Preview or apply a settled add. The membership writes and the supplied
|
|
212
|
+
* reconciliation steps run inside one workspace transaction, and the
|
|
213
|
+
* transaction only settles once the workspace reports the arriving agents as
|
|
214
|
+
* configured.
|
|
215
|
+
*/
|
|
216
|
+
export const previewOrApplyAddConfiguredAgents = (candidate, execution, reconciliation) => Effect.gen(function* () {
|
|
217
|
+
const materialization = (reconciliation?.steps ?? []).map((step) => attributeToArrivingAgents(candidate.scope, candidate.agentIds, step));
|
|
218
|
+
const steps = [
|
|
219
|
+
...candidate.agentIds.map((agentId) => addAgentStep(candidate.scope, agentId)),
|
|
220
|
+
...materialization,
|
|
221
|
+
];
|
|
222
|
+
const atomicSteps = yield* makeAtomicMembershipSteps({
|
|
223
|
+
steps,
|
|
224
|
+
transition: { kind: "add", agentIds: candidate.agentIds },
|
|
225
|
+
});
|
|
226
|
+
const prepared = yield* prepareExecutionCandidate(addPlan(candidate, atomicSteps));
|
|
227
|
+
return yield* resolveExecutionCandidate(prepared, execution);
|
|
228
|
+
});
|
|
229
|
+
/**
|
|
230
|
+
* Settle which agents a remove request takes out of membership, and the
|
|
231
|
+
* reconciliation state their outputs are cleaned up against.
|
|
232
|
+
*
|
|
233
|
+
* Removing every named agent when none of them is configured is a reported
|
|
234
|
+
* no-op; removing a mixture is a refusal, because the request described a
|
|
235
|
+
* workspace that does not exist. Cleaning up owned projections needs a
|
|
236
|
+
* complete desired graph — without one, AXM cannot tell its own output from
|
|
237
|
+
* a person's file, so it refuses rather than guess.
|
|
238
|
+
*/
|
|
239
|
+
export const prepareRemoveConfiguredAgents = (request) => Effect.gen(function* () {
|
|
240
|
+
const workspace = yield* WorkspaceMutations;
|
|
241
|
+
const agentIds = yield* validateAgentIds(request.ids);
|
|
242
|
+
const configured = yield* workspace.getConfiguredAgents();
|
|
243
|
+
const configuredSet = new Set(configured);
|
|
244
|
+
const missing = agentIds.filter((id) => !configuredSet.has(id));
|
|
245
|
+
if (missing.length === agentIds.length) {
|
|
246
|
+
return unchanged("already-absent", "All requested agents are already absent");
|
|
247
|
+
}
|
|
248
|
+
if (missing.length > 0) {
|
|
249
|
+
return yield* new WorkspaceConfigurationFailed({
|
|
250
|
+
category: "validation",
|
|
251
|
+
detail: `Agent is not configured: ${missing.join(", ")}`,
|
|
252
|
+
suggestions: [{ description: "Inspect configured agents.", cmd: "axm agents list" }],
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
const graph = yield* workspace.getDesiredStateGraph();
|
|
256
|
+
if (!graph.complete) {
|
|
257
|
+
return yield* new WorkspaceConfigurationFailed({
|
|
258
|
+
category: "validation",
|
|
259
|
+
detail: "Cannot safely clean agent projections while desired workspace state is incomplete",
|
|
260
|
+
suggestions: [{ description: "Inspect workspace facts.", cmd: "axm lint" }],
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const departing = new Set(agentIds);
|
|
264
|
+
const remaining = configured.filter((agentId) => !departing.has(agentId));
|
|
265
|
+
return {
|
|
266
|
+
_tag: "RemoveConfiguredAgents",
|
|
267
|
+
agentIds,
|
|
268
|
+
configuredAgents: remaining,
|
|
269
|
+
reconciliation: {
|
|
270
|
+
desiredAgentIds: new Set([UNIVERSAL_AGENT_ID, ...remaining]),
|
|
271
|
+
expectedNames: expectedProjectionNames(graph),
|
|
272
|
+
},
|
|
273
|
+
baseDir: workspace.baseDir,
|
|
274
|
+
scope: workspace.scope,
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
const removeAgentStep = (scope, agentId) => ({
|
|
278
|
+
label: `Remove ${agentId}`,
|
|
279
|
+
readiness: "ready",
|
|
280
|
+
artifact: membershipArtifact(scope, agentId, "updated"),
|
|
281
|
+
run: Effect.gen(function* () {
|
|
282
|
+
const workspace = yield* WorkspaceMutations;
|
|
283
|
+
yield* workspace
|
|
284
|
+
.removeConfiguredAgent(agentId)
|
|
285
|
+
.pipe(Effect.mapError(workspaceChangeFailedToStepFailure));
|
|
286
|
+
return {
|
|
287
|
+
result: "success",
|
|
288
|
+
message: `Removed ${agentId}`,
|
|
289
|
+
artifact: membershipArtifact(scope, agentId, "updated"),
|
|
290
|
+
};
|
|
291
|
+
}),
|
|
292
|
+
});
|
|
293
|
+
const removePlan = (candidate, steps) => ({
|
|
294
|
+
_tag: "Plan",
|
|
295
|
+
name: "Remove coding agents",
|
|
296
|
+
description: Option.some(`Remove ${candidate.agentIds.join(", ")} and clean up managed artifacts`),
|
|
297
|
+
presentation: {
|
|
298
|
+
verb: { imperative: "remove", past: "Removed", gerund: "Removing" },
|
|
299
|
+
subject: { singular: "agent", plural: "agents" },
|
|
300
|
+
},
|
|
301
|
+
jobs: [{ concurrency: 1, executionPolicy: "best-effort", steps }],
|
|
302
|
+
});
|
|
303
|
+
/**
|
|
304
|
+
* Preview or apply a settled removal. The cleanup steps the application
|
|
305
|
+
* planned run before the membership writes and inside the same transaction,
|
|
306
|
+
* so a cleanup that cannot complete leaves the agent configured.
|
|
307
|
+
*/
|
|
308
|
+
export const previewOrApplyRemoveConfiguredAgents = (candidate, execution, reconciliation) => Effect.gen(function* () {
|
|
309
|
+
const steps = [
|
|
310
|
+
...(reconciliation?.steps ?? []),
|
|
311
|
+
...candidate.agentIds.map((agentId) => removeAgentStep(candidate.scope, agentId)),
|
|
312
|
+
];
|
|
313
|
+
const atomicSteps = yield* makeAtomicMembershipSteps({
|
|
314
|
+
steps,
|
|
315
|
+
transition: { kind: "remove", agentIds: candidate.agentIds },
|
|
316
|
+
});
|
|
317
|
+
const prepared = yield* prepareExecutionCandidate(removePlan(candidate, atomicSteps));
|
|
318
|
+
return yield* resolveExecutionCandidate(prepared, execution);
|
|
319
|
+
});
|
|
320
|
+
// -----------------------------------------------------------------------------
|
|
321
|
+
// List
|
|
322
|
+
// -----------------------------------------------------------------------------
|
|
323
|
+
const ConfiguredAgentRowSchema = Schema.Struct({
|
|
324
|
+
id: Schema.String,
|
|
325
|
+
name: Schema.String,
|
|
326
|
+
configured: Schema.Boolean,
|
|
327
|
+
detected: Schema.Boolean,
|
|
328
|
+
/** Instruction-projection health for a configured agent; `-` when not configured. */
|
|
329
|
+
instructions: Schema.String,
|
|
330
|
+
/** Whether the vendor still maintains the agent: active, deprecated, retired. */
|
|
331
|
+
lifecycle: Schema.String,
|
|
332
|
+
});
|
|
333
|
+
export const ConfiguredAgentInventorySchema = Schema.Struct({
|
|
334
|
+
items: Schema.Array(ConfiguredAgentRowSchema),
|
|
335
|
+
configured: Schema.Array(Schema.String),
|
|
336
|
+
detected: Schema.Array(Schema.String),
|
|
337
|
+
available: Schema.Array(Schema.String),
|
|
338
|
+
count: Schema.Number,
|
|
339
|
+
});
|
|
340
|
+
/**
|
|
341
|
+
* Report configured, detected, and available coding agents.
|
|
342
|
+
*
|
|
343
|
+
* The default view answers "what is in play here": the agents this workspace
|
|
344
|
+
* configures and the ones present on the machine. `universal` is materialized
|
|
345
|
+
* for every workspace and is never reported as a membership choice.
|
|
346
|
+
*/
|
|
347
|
+
export const listConfiguredAgents = (request = {}) => Effect.gen(function* () {
|
|
348
|
+
const workspace = yield* WorkspaceMutations;
|
|
349
|
+
const configured = yield* workspace.getConfiguredAgents();
|
|
350
|
+
const detected = yield* detectAgentsForScope(workspace.baseDir, workspace.scope).pipe(Effect.map((agents) => agents.map((agent) => agent.id)));
|
|
351
|
+
const configuredSet = new Set(configured);
|
|
352
|
+
const detectedSet = new Set(detected);
|
|
353
|
+
const instructionsConfig = yield* workspace.getInstructionsConfig();
|
|
354
|
+
const instructionHealth = Option.isSome(instructionsConfig) && instructionsConfig.value !== false
|
|
355
|
+
? yield* observeInstructionProjection({
|
|
356
|
+
workspaceRoot: workspace.baseDir,
|
|
357
|
+
scope: workspace.scope,
|
|
358
|
+
configuredAgents: configured,
|
|
359
|
+
config: resolveInstructionsConfig(instructionsConfig.value),
|
|
360
|
+
}).pipe(Effect.map(({ status }) => new Map(status.items.map((item) => [item.agentId, item.health]))))
|
|
361
|
+
: new Map();
|
|
362
|
+
const baseIds = request.available === true || request.detected === true
|
|
363
|
+
? CONFIGURABLE_AGENT_IDS
|
|
364
|
+
: CONFIGURABLE_AGENT_IDS.filter((id) => configuredSet.has(id) || detectedSet.has(id));
|
|
365
|
+
const items = baseIds
|
|
366
|
+
.filter((id) => request.detected !== true || detectedSet.has(id))
|
|
367
|
+
.map((id) => ({
|
|
368
|
+
id,
|
|
369
|
+
name: AGENTS[id].name,
|
|
370
|
+
configured: configuredSet.has(id),
|
|
371
|
+
detected: detectedSet.has(id),
|
|
372
|
+
instructions: configuredSet.has(id) ? (instructionHealth.get(id) ?? "manual") : "-",
|
|
373
|
+
lifecycle: agentLifecycle(id).state,
|
|
374
|
+
}));
|
|
375
|
+
return {
|
|
376
|
+
items,
|
|
377
|
+
configured: configured.filter((id) => id !== UNIVERSAL_AGENT_ID),
|
|
378
|
+
detected,
|
|
379
|
+
available: [...CONFIGURABLE_AGENT_IDS],
|
|
380
|
+
count: items.length,
|
|
381
|
+
};
|
|
382
|
+
});
|
|
383
|
+
/** The configured-agent membership use case. */
|
|
384
|
+
export const ConfigureAgents = {
|
|
385
|
+
add: {
|
|
386
|
+
prepare: prepareAddConfiguredAgents,
|
|
387
|
+
previewOrApply: previewOrApplyAddConfiguredAgents,
|
|
388
|
+
},
|
|
389
|
+
remove: {
|
|
390
|
+
prepare: prepareRemoveConfiguredAgents,
|
|
391
|
+
previewOrApply: previewOrApplyRemoveConfiguredAgents,
|
|
392
|
+
},
|
|
393
|
+
list: listConfiguredAgents,
|
|
394
|
+
};
|
|
395
|
+
//# sourceMappingURL=configure-agents.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which coding-agent identifiers a membership or capability request may name.
|
|
3
|
+
*
|
|
4
|
+
* The configurable catalog is the whole answer: `universal` is materialized
|
|
5
|
+
* for every workspace and is never a membership choice, a hosted agent is
|
|
6
|
+
* installed through its vendor rather than through workspace configuration,
|
|
7
|
+
* and anything else is refused before a change is planned, naming the nearest
|
|
8
|
+
* supported id when one is close enough to be a typo.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
*/
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import { WorkspaceConfigurationFailed } from "../errors.js";
|
|
14
|
+
export declare const dedupe: (values: ReadonlyArray<string>) => ReadonlyArray<string>;
|
|
15
|
+
export declare const validateAgentIds: (ids: ReadonlyArray<string>) => Effect.Effect<ReadonlyArray<string>, WorkspaceConfigurationFailed>;
|
|
16
|
+
//# sourceMappingURL=validate-agent-ids.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which coding-agent identifiers a membership or capability request may name.
|
|
3
|
+
*
|
|
4
|
+
* The configurable catalog is the whole answer: `universal` is materialized
|
|
5
|
+
* for every workspace and is never a membership choice, a hosted agent is
|
|
6
|
+
* installed through its vendor rather than through workspace configuration,
|
|
7
|
+
* and anything else is refused before a change is planned, naming the nearest
|
|
8
|
+
* supported id when one is close enough to be a typo.
|
|
9
|
+
*
|
|
10
|
+
* @experimental This API is unstable and may change without notice.
|
|
11
|
+
*/
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import { HOSTED_AGENTS_BY_ID, HOSTED_AGENT_IDS, } from "@agentxm/extension-model/unstable/agent-capabilities";
|
|
14
|
+
import { CONFIGURABLE_AGENT_IDS } from "@agentxm/extension-model/unstable/agents/types";
|
|
15
|
+
import { WorkspaceConfigurationFailed } from "../errors.js";
|
|
16
|
+
const configurableAgentIds = new Set(CONFIGURABLE_AGENT_IDS);
|
|
17
|
+
const hostedAgentIds = new Set(HOSTED_AGENT_IDS);
|
|
18
|
+
const isHostedAgentId = (id) => hostedAgentIds.has(id);
|
|
19
|
+
const numberAt = (values, index) => values[index] ?? 0;
|
|
20
|
+
const editDistance = (left, right) => {
|
|
21
|
+
const previous = Array.from({ length: right.length + 1 }, (_value, index) => index);
|
|
22
|
+
for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
|
|
23
|
+
const current = [leftIndex + 1];
|
|
24
|
+
for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
|
|
25
|
+
const substitutionCost = left[leftIndex] === right[rightIndex] ? 0 : 1;
|
|
26
|
+
current[rightIndex + 1] = Math.min(numberAt(current, rightIndex) + 1, numberAt(previous, rightIndex + 1) + 1, numberAt(previous, rightIndex) + substitutionCost);
|
|
27
|
+
}
|
|
28
|
+
previous.splice(0, previous.length, ...current);
|
|
29
|
+
}
|
|
30
|
+
return previous[right.length] ?? left.length;
|
|
31
|
+
};
|
|
32
|
+
const nearestAgentId = (input) => {
|
|
33
|
+
let best;
|
|
34
|
+
for (const id of CONFIGURABLE_AGENT_IDS) {
|
|
35
|
+
const distance = editDistance(input, id);
|
|
36
|
+
if (best === undefined || distance < best.distance) {
|
|
37
|
+
best = { id, distance };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (best === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
return best.distance <= Math.max(3, Math.floor(input.length / 2)) ? best.id : undefined;
|
|
43
|
+
};
|
|
44
|
+
export const dedupe = (values) => Array.from(new Set(values));
|
|
45
|
+
export const validateAgentIds = (ids) => Effect.gen(function* () {
|
|
46
|
+
for (const id of ids) {
|
|
47
|
+
if (id === "universal") {
|
|
48
|
+
return yield* new WorkspaceConfigurationFailed({
|
|
49
|
+
category: "validation",
|
|
50
|
+
detail: "`universal` is always materialized automatically and cannot be added or removed.",
|
|
51
|
+
suggestions: [{ description: "Choose one of the configurable coding-agent IDs." }],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (isHostedAgentId(id)) {
|
|
55
|
+
const agent = HOSTED_AGENTS_BY_ID[id];
|
|
56
|
+
return yield* new WorkspaceConfigurationFailed({
|
|
57
|
+
category: "validation",
|
|
58
|
+
detail: `${agent.name} is a hosted agent and cannot be added to local workspace configuration. ${agent.installTarget.instructions}`,
|
|
59
|
+
suggestions: [
|
|
60
|
+
{
|
|
61
|
+
description: `Open the ${agent.name} skill installation guide.`,
|
|
62
|
+
url: agent.installTarget.docs,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (!configurableAgentIds.has(id)) {
|
|
68
|
+
const nearest = nearestAgentId(id);
|
|
69
|
+
return yield* new WorkspaceConfigurationFailed({
|
|
70
|
+
category: "validation",
|
|
71
|
+
detail: `Unknown agent ID: ${id}`,
|
|
72
|
+
suggestions: [
|
|
73
|
+
nearest === undefined
|
|
74
|
+
? { description: "Inspect supported agent IDs.", cmd: "axm agents list --available" }
|
|
75
|
+
: {
|
|
76
|
+
description: `Did you mean "${nearest}"?`,
|
|
77
|
+
cmd: `axm agents add ${nearest}`,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return dedupe(ids);
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=validate-agent-ids.js.map
|
|
@@ -2,7 +2,7 @@ import * as Effect from "effect/Effect";
|
|
|
2
2
|
import * as Layer from "effect/Layer";
|
|
3
3
|
import * as ServiceMap from "effect/Context";
|
|
4
4
|
import type { AgentDescriptor } from "@agentxm/extension-model/unstable/agents/types";
|
|
5
|
-
import type { WorkspaceConfigurationFailed } from "
|
|
5
|
+
import type { WorkspaceConfigurationFailed } from "../errors.js";
|
|
6
6
|
import type { WorkspaceScope } from "@agentxm/extension-model/unstable/workspace-scope";
|
|
7
7
|
import type { SetupScopeSupportCategory } from "@agentxm/workspace-state";
|
|
8
8
|
declare const WorkspaceInitializationCancelled_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
@@ -10,10 +10,10 @@ import * as FileSystem from "effect/FileSystem";
|
|
|
10
10
|
import * as Path from "effect/Path";
|
|
11
11
|
import * as Effect from "effect/Effect";
|
|
12
12
|
import { type ConfigurableAgentId } from "@agentxm/extension-model/unstable/agents/types";
|
|
13
|
-
import { WorkspaceConfigurationFailed } from "
|
|
13
|
+
import { WorkspaceConfigurationFailed } from "../errors.js";
|
|
14
14
|
import { type Settings } from "@agentxm/workspace-state";
|
|
15
15
|
import type { WorkspaceMutationsOptions } from "@agentxm/workspace-state";
|
|
16
|
-
import { type
|
|
16
|
+
import { type LocatedWorkspace } from "@agentxm/workspace-state";
|
|
17
17
|
export interface SetupAgentCandidate {
|
|
18
18
|
readonly id: ConfigurableAgentId;
|
|
19
19
|
readonly name: string;
|
|
@@ -127,24 +127,24 @@ export declare const initializeProjectWorkspace: (localDir: string, options: Wor
|
|
|
127
127
|
readonly [x: string]: string;
|
|
128
128
|
};
|
|
129
129
|
readonly url?: string | undefined;
|
|
130
|
+
readonly command?: string | undefined;
|
|
131
|
+
readonly kind?: "sourced" | undefined;
|
|
130
132
|
readonly headers?: {
|
|
131
133
|
readonly [x: string]: string;
|
|
132
134
|
} | undefined;
|
|
133
|
-
readonly command?: string | undefined;
|
|
134
135
|
readonly args?: readonly string[] | undefined;
|
|
135
|
-
readonly kind?: "sourced" | undefined;
|
|
136
136
|
} | {
|
|
137
137
|
readonly enabled: boolean;
|
|
138
|
+
readonly kind: "inline";
|
|
138
139
|
readonly env: {
|
|
139
140
|
readonly [x: string]: string;
|
|
140
141
|
};
|
|
141
|
-
readonly kind: "inline";
|
|
142
142
|
readonly url?: string | undefined;
|
|
143
143
|
readonly source?: undefined;
|
|
144
|
+
readonly command?: string | undefined;
|
|
144
145
|
readonly headers?: {
|
|
145
146
|
readonly [x: string]: string;
|
|
146
147
|
} | undefined;
|
|
147
|
-
readonly command?: string | undefined;
|
|
148
148
|
readonly args?: readonly string[] | undefined;
|
|
149
149
|
};
|
|
150
150
|
};
|
|
@@ -159,7 +159,7 @@ export declare const initializeProjectWorkspace: (localDir: string, options: Wor
|
|
|
159
159
|
};
|
|
160
160
|
agentCandidates: readonly SetupAgentCandidate[];
|
|
161
161
|
confirmed: boolean;
|
|
162
|
-
}, WorkspaceConfigurationFailed | import("
|
|
162
|
+
}, WorkspaceConfigurationFailed | import("@agentxm/workspace-state").SettingsWriteError | import("@agentxm/workspace-state").LockfileWriteError | import("./initialization-interaction.js").WorkspaceInitializationCancelled | import("@agentxm/workspace-projection").InstructionMaintenanceFailure, FileSystem.FileSystem | Path.Path>;
|
|
163
163
|
interface WorkspaceInitializationState {
|
|
164
164
|
readonly settings: Settings;
|
|
165
165
|
readonly initialized: boolean;
|
|
@@ -174,7 +174,7 @@ interface WorkspaceInitializationState {
|
|
|
174
174
|
*
|
|
175
175
|
* @param workspaceRoot - Path to the user workspace root
|
|
176
176
|
*/
|
|
177
|
-
export declare const ensureUserWorkspaceInitialized: (workspaceRoot: string, options: WorkspaceMutationsOptions) => Effect.Effect<WorkspaceInitializationState, WorkspaceConfigurationFailed | import("
|
|
177
|
+
export declare const ensureUserWorkspaceInitialized: (workspaceRoot: string, options: WorkspaceMutationsOptions) => Effect.Effect<WorkspaceInitializationState, WorkspaceConfigurationFailed | import("@agentxm/workspace-state").SettingsWriteError | import("@agentxm/workspace-transactions").WorkspaceSnapshotError | import("@agentxm/workspace-state").LockfileWriteError | import("./initialization-interaction.js").WorkspaceInitializationCancelled, FileSystem.FileSystem | Path.Path>;
|
|
178
178
|
/**
|
|
179
179
|
* Ensure project workspace is initialized, returning local settings.
|
|
180
180
|
*
|
|
@@ -184,14 +184,14 @@ export declare const ensureUserWorkspaceInitialized: (workspaceRoot: string, opt
|
|
|
184
184
|
* @param options - WorkspaceMutations options
|
|
185
185
|
* @returns Effect yielding local Settings
|
|
186
186
|
*/
|
|
187
|
-
export declare const ensureProjectWorkspaceInitialized: (localDir: string, options: WorkspaceMutationsOptions) => Effect.Effect<WorkspaceInitializationState, WorkspaceConfigurationFailed | import("
|
|
187
|
+
export declare const ensureProjectWorkspaceInitialized: (localDir: string, options: WorkspaceMutationsOptions) => Effect.Effect<WorkspaceInitializationState, WorkspaceConfigurationFailed | import("@agentxm/workspace-state").SettingsWriteError | import("@agentxm/workspace-state").LockfileWriteError | import("./initialization-interaction.js").WorkspaceInitializationCancelled | import("@agentxm/workspace-projection").InstructionMaintenanceFailure, FileSystem.FileSystem | Path.Path>;
|
|
188
188
|
export declare const bootstrapWorkspace: (options: WorkspaceMutationsOptions) => Effect.Effect<{
|
|
189
|
-
location:
|
|
189
|
+
location: LocatedWorkspace;
|
|
190
190
|
settings: Settings;
|
|
191
191
|
initialized: boolean;
|
|
192
192
|
wouldInitialize: boolean;
|
|
193
193
|
cancelled: boolean;
|
|
194
194
|
agentCandidates: ReadonlyArray<SetupAgentCandidate>;
|
|
195
|
-
}, WorkspaceConfigurationFailed | import("
|
|
195
|
+
}, WorkspaceConfigurationFailed | import("@agentxm/workspace-state").SettingsWriteError | import("@agentxm/workspace-state").LockfileWriteError | import("./initialization-interaction.js").WorkspaceInitializationCancelled | import("@agentxm/workspace-projection").InstructionMaintenanceFailure, FileSystem.FileSystem | Path.Path>;
|
|
196
196
|
export {};
|
|
197
197
|
//# sourceMappingURL=initialization.d.ts.map
|
|
@@ -16,7 +16,7 @@ import { CONFIGURABLE_AGENTS_BY_ID } from "@agentxm/extension-model/unstable/age
|
|
|
16
16
|
import { detectAgentScopeResults } from "@agentxm/agent-integration";
|
|
17
17
|
import { AGENTS } from "@agentxm/extension-model/unstable/agents/registry";
|
|
18
18
|
import { isConfigurableAgentId, } from "@agentxm/extension-model/unstable/agents/types";
|
|
19
|
-
import { WorkspaceConfigurationFailed } from "
|
|
19
|
+
import { WorkspaceConfigurationFailed } from "../errors.js";
|
|
20
20
|
import { isGitManaged } from "@agentxm/extension-sources";
|
|
21
21
|
import { LOCKFILE_NAME } from "@agentxm/extension-model/unstable/workspace-files";
|
|
22
22
|
import { LOCKFILE_VERSION, writeLockfileAtPath } from "@agentxm/workspace-state";
|
|
@@ -27,10 +27,10 @@ import { makeWorkspaceReadModel, WorkspaceReadModelConfig } from "@agentxm/works
|
|
|
27
27
|
import { WorkspaceInitializationInteraction, } from "./initialization-interaction.js";
|
|
28
28
|
import { locateWorkspace, resolveUserHome } from "@agentxm/workspace-state";
|
|
29
29
|
import { setupScopeSupport } from "@agentxm/workspace-state";
|
|
30
|
-
import { protectWorkspacePath } from "@agentxm/workspace-
|
|
30
|
+
import { protectWorkspacePath } from "@agentxm/workspace-transactions";
|
|
31
31
|
import { LOCK_FILENAME } from "@agentxm/workspace-state";
|
|
32
32
|
import { SETTINGS_FILENAME } from "@agentxm/extension-model/unstable/workspace-files";
|
|
33
|
-
import { resolveInstructionTarget, syncInstructions } from "@agentxm/
|
|
33
|
+
import { resolveInstructionTarget, syncInstructions } from "@agentxm/workspace-projection";
|
|
34
34
|
const SELECT_AGENTS_PROMPT_MISSING = new WorkspaceConfigurationFailed({
|
|
35
35
|
category: "usage",
|
|
36
36
|
detail: "Interactive prompt required: Select agents to configure",
|