@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,512 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setting up an AXM workspace: deciding what a setup request would do,
|
|
3
|
+
* initializing the workspace under one transaction, and reporting exactly
|
|
4
|
+
* which files the workspace now occupies.
|
|
5
|
+
*
|
|
6
|
+
* Two rules decide the shape of this use case. An unattended apply happens
|
|
7
|
+
* only when the request said so completely — approval, scope, and at least
|
|
8
|
+
* one agent — because a workspace initialized from guessed defaults is a
|
|
9
|
+
* workspace nobody chose. And initialization is one transaction: a run that
|
|
10
|
+
* cannot finish leaves no half-initialized workspace behind.
|
|
11
|
+
*
|
|
12
|
+
* The report is the same document whether the request was previewed or
|
|
13
|
+
* applied, so a preview can be trusted as a description of the apply. It
|
|
14
|
+
* names the files the workspace occupies rather than the calls that wrote
|
|
15
|
+
* them, which is why the enumeration lives here beside the code that writes
|
|
16
|
+
* them rather than in whichever adapter happens to be rendering.
|
|
17
|
+
*
|
|
18
|
+
* @experimental This API is unstable and may change without notice.
|
|
19
|
+
*/
|
|
20
|
+
import * as Effect from "effect/Effect";
|
|
21
|
+
import * as FileSystem from "effect/FileSystem";
|
|
22
|
+
import * as Option from "effect/Option";
|
|
23
|
+
import * as Path from "effect/Path";
|
|
24
|
+
import * as Schema from "effect/Schema";
|
|
25
|
+
import { AGENTS } from "@agentxm/extension-model/unstable/agents/registry";
|
|
26
|
+
import { ExtensionTypeSchema } from "@agentxm/extension-model/unstable/extensions";
|
|
27
|
+
import { isGitManaged } from "@agentxm/extension-sources";
|
|
28
|
+
import { CodingAgentRepository, resolveInstructionTarget, } from "@agentxm/workspace-projection";
|
|
29
|
+
import { AXM_DIR_NAME, ArtifactChangeSchema, LOCK_FILENAME, resolveUserWorkspaceRoot, scanAllSubagentFiles, setupScopeSupport, } from "@agentxm/workspace-state";
|
|
30
|
+
import { WorkspaceTransactionScope, runWorkspaceTransaction, } from "@agentxm/workspace-transactions";
|
|
31
|
+
import { WorkspaceConfigurationFailed } from "../errors.js";
|
|
32
|
+
import { WorkspaceInitializationInteraction } from "./initialization-interaction.js";
|
|
33
|
+
import { bootstrapWorkspace } from "./initialization.js";
|
|
34
|
+
// -----------------------------------------------------------------------------
|
|
35
|
+
// Typed outcome
|
|
36
|
+
// -----------------------------------------------------------------------------
|
|
37
|
+
const SubagentSummarySchema = Schema.Struct({
|
|
38
|
+
agentId: Schema.String,
|
|
39
|
+
agentName: Schema.String,
|
|
40
|
+
subagentDir: Schema.String,
|
|
41
|
+
files: Schema.Array(Schema.Struct({ path: Schema.String })),
|
|
42
|
+
});
|
|
43
|
+
const SetupArtifactTargetSchema = Schema.Struct({
|
|
44
|
+
path: Schema.String,
|
|
45
|
+
change: ArtifactChangeSchema,
|
|
46
|
+
agentIds: Schema.optional(Schema.Array(Schema.String)),
|
|
47
|
+
});
|
|
48
|
+
const SetupPlanStepArtifactSchema = Schema.Struct({
|
|
49
|
+
path: Schema.optional(Schema.String),
|
|
50
|
+
scope: Schema.Literals(["project", "user"]),
|
|
51
|
+
agents: Schema.optional(Schema.Array(Schema.String)),
|
|
52
|
+
version: Schema.optional(Schema.String),
|
|
53
|
+
change: ArtifactChangeSchema,
|
|
54
|
+
previousVersion: Schema.optional(Schema.String),
|
|
55
|
+
fileCount: Schema.optional(Schema.Number),
|
|
56
|
+
targets: Schema.optional(Schema.Array(SetupArtifactTargetSchema)),
|
|
57
|
+
});
|
|
58
|
+
const SetupPlanStepSchema = Schema.Struct({
|
|
59
|
+
label: Schema.String,
|
|
60
|
+
status: Schema.Literals([
|
|
61
|
+
"ready",
|
|
62
|
+
"warning",
|
|
63
|
+
"error",
|
|
64
|
+
"applied",
|
|
65
|
+
"unchanged",
|
|
66
|
+
"failed",
|
|
67
|
+
"blocked",
|
|
68
|
+
]),
|
|
69
|
+
message: Schema.optional(Schema.String),
|
|
70
|
+
artifact: Schema.optional(SetupPlanStepArtifactSchema),
|
|
71
|
+
});
|
|
72
|
+
const SetupAgentCandidateSchema = Schema.Struct({
|
|
73
|
+
id: Schema.String,
|
|
74
|
+
name: Schema.String,
|
|
75
|
+
projectDetected: Schema.Boolean,
|
|
76
|
+
userDetected: Schema.Boolean,
|
|
77
|
+
state: Schema.Literals(["selected", "suggested", "available", "retired"]),
|
|
78
|
+
selectionReason: Schema.optional(Schema.Literals([
|
|
79
|
+
"explicit",
|
|
80
|
+
"project-detected",
|
|
81
|
+
"user-detected",
|
|
82
|
+
"catalog-suggestion",
|
|
83
|
+
])),
|
|
84
|
+
});
|
|
85
|
+
const SetupScopeSupportOutcomeSchema = Schema.Struct({
|
|
86
|
+
target: Schema.Literals(["workspace", "container", "agent", "agent-set"]),
|
|
87
|
+
agentId: Schema.optional(Schema.String),
|
|
88
|
+
agentName: Schema.optional(Schema.String),
|
|
89
|
+
status: Schema.Literals(["supported", "project-only", "unsupported", "refused"]),
|
|
90
|
+
reasonCode: Schema.Literals([
|
|
91
|
+
"supported",
|
|
92
|
+
"no-configured-agents",
|
|
93
|
+
"unknown-agent",
|
|
94
|
+
"native-capability-unavailable",
|
|
95
|
+
"axm-capability-unavailable",
|
|
96
|
+
"project-only",
|
|
97
|
+
"scope-not-modeled",
|
|
98
|
+
]),
|
|
99
|
+
reason: Schema.String,
|
|
100
|
+
});
|
|
101
|
+
const SetupScopeSupportCategorySchema = Schema.Struct({
|
|
102
|
+
type: ExtensionTypeSchema,
|
|
103
|
+
label: Schema.String,
|
|
104
|
+
placement: Schema.Literals(["per-agent", "workspace", "container"]),
|
|
105
|
+
outcomes: Schema.Array(SetupScopeSupportOutcomeSchema),
|
|
106
|
+
});
|
|
107
|
+
/**
|
|
108
|
+
* How a preview chose the answers an interactive setup would have asked for.
|
|
109
|
+
*/
|
|
110
|
+
const SetupPreviewDefaultsSchema = Schema.Struct({
|
|
111
|
+
agents: Schema.Literals(["explicit", "detected", "suggested"]),
|
|
112
|
+
instructions: Schema.optional(Schema.Struct({ enabled: Schema.Boolean, fileName: Schema.String })),
|
|
113
|
+
});
|
|
114
|
+
export const SetupOutcomeSchema = Schema.Struct({
|
|
115
|
+
outcome: Schema.Literals(["previewed", "cancelled", "applied", "no-op", "failed"]),
|
|
116
|
+
planName: Schema.String,
|
|
117
|
+
planDescription: Schema.optional(Schema.String),
|
|
118
|
+
message: Schema.optional(Schema.String),
|
|
119
|
+
totalSteps: Schema.Number,
|
|
120
|
+
readyCount: Schema.Number,
|
|
121
|
+
warningCount: Schema.Number,
|
|
122
|
+
errorCount: Schema.Number,
|
|
123
|
+
appliedCount: Schema.Number,
|
|
124
|
+
failedCount: Schema.Number,
|
|
125
|
+
blockedCount: Schema.Number,
|
|
126
|
+
steps: Schema.Array(SetupPlanStepSchema),
|
|
127
|
+
reason: Schema.optional(Schema.Literal("approval-required")),
|
|
128
|
+
errorCode: Schema.optional(Schema.Literal("usage")),
|
|
129
|
+
status: Schema.Literals([
|
|
130
|
+
"initialized",
|
|
131
|
+
"already-initialized",
|
|
132
|
+
"preview",
|
|
133
|
+
"cancelled",
|
|
134
|
+
"approval-required",
|
|
135
|
+
]),
|
|
136
|
+
changed: Schema.Boolean,
|
|
137
|
+
defaultSkillInstalled: Schema.Boolean,
|
|
138
|
+
scope: Schema.String,
|
|
139
|
+
agents: Schema.Array(Schema.Struct({ id: Schema.String, name: Schema.String })),
|
|
140
|
+
agentCandidates: Schema.optional(Schema.Array(SetupAgentCandidateSchema)),
|
|
141
|
+
scopeSupport: Schema.Array(SetupScopeSupportCategorySchema),
|
|
142
|
+
settingsPath: Schema.String,
|
|
143
|
+
instructions: Schema.optional(Schema.Struct({
|
|
144
|
+
enabled: Schema.Boolean,
|
|
145
|
+
fileName: Schema.optional(Schema.String),
|
|
146
|
+
gitignoreAliases: Schema.optional(Schema.Boolean),
|
|
147
|
+
})),
|
|
148
|
+
telemetryEnabled: Schema.Boolean,
|
|
149
|
+
subagentFiles: Schema.optional(Schema.Array(SubagentSummarySchema)),
|
|
150
|
+
previewDefaults: Schema.optional(SetupPreviewDefaultsSchema),
|
|
151
|
+
});
|
|
152
|
+
const APPROVAL_REQUIRED_DETAIL = "Explicit approval, scope, and agents are required for unattended setup";
|
|
153
|
+
/**
|
|
154
|
+
* Settle a setup request.
|
|
155
|
+
*
|
|
156
|
+
* An unattended apply into a workspace that has no settings yet must name
|
|
157
|
+
* every input it depends on. Anything less is refused before a file is
|
|
158
|
+
* touched, and the refusal names the preview that would show the candidate.
|
|
159
|
+
*/
|
|
160
|
+
export const prepareSetupWorkspace = (request) => Effect.gen(function* () {
|
|
161
|
+
const path = yield* Path.Path;
|
|
162
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
163
|
+
const userWorkspaceRoot = yield* resolveUserWorkspaceRoot();
|
|
164
|
+
const workspaceDir = request.scope === "user"
|
|
165
|
+
? path.join(userWorkspaceRoot, AXM_DIR_NAME)
|
|
166
|
+
: path.join(request.projectRoot, AXM_DIR_NAME);
|
|
167
|
+
const settingsPath = request.scope === "user"
|
|
168
|
+
? path.join(userWorkspaceRoot, "axm.json")
|
|
169
|
+
: path.join(request.projectRoot, "axm.json");
|
|
170
|
+
const settingsExist = yield* fileSystem.exists(settingsPath).pipe(Effect.mapError((cause) => new WorkspaceConfigurationFailed({
|
|
171
|
+
category: "internal",
|
|
172
|
+
detail: `Failed to inspect setup state: ${workspaceDir}`,
|
|
173
|
+
cause,
|
|
174
|
+
})));
|
|
175
|
+
const unattended = request.nonInteractive || request.yes === true;
|
|
176
|
+
const intentComplete = request.yes === true &&
|
|
177
|
+
(request.agents?.length ?? 0) > 0 &&
|
|
178
|
+
(request.scopeExplicit === undefined || request.scopeExplicit);
|
|
179
|
+
if (!settingsExist &&
|
|
180
|
+
unattended &&
|
|
181
|
+
request.scopeExplicit !== undefined &&
|
|
182
|
+
request.preview !== true &&
|
|
183
|
+
!intentComplete) {
|
|
184
|
+
return {
|
|
185
|
+
_tag: "ApprovalRequired",
|
|
186
|
+
outcome: {
|
|
187
|
+
outcome: "failed",
|
|
188
|
+
planName: "Set up AXM workspace",
|
|
189
|
+
planDescription: `Set up AXM (${request.scope})`,
|
|
190
|
+
message: APPROVAL_REQUIRED_DETAIL,
|
|
191
|
+
totalSteps: 0,
|
|
192
|
+
readyCount: 0,
|
|
193
|
+
warningCount: 0,
|
|
194
|
+
errorCount: 1,
|
|
195
|
+
appliedCount: 0,
|
|
196
|
+
failedCount: 0,
|
|
197
|
+
blockedCount: 0,
|
|
198
|
+
steps: [],
|
|
199
|
+
reason: "approval-required",
|
|
200
|
+
errorCode: "usage",
|
|
201
|
+
status: "approval-required",
|
|
202
|
+
changed: false,
|
|
203
|
+
defaultSkillInstalled: false,
|
|
204
|
+
scope: request.scope,
|
|
205
|
+
agents: [],
|
|
206
|
+
scopeSupport: setupScopeSupport([], request.scope),
|
|
207
|
+
settingsPath: request.scope === "user" ? settingsPath : "axm.json",
|
|
208
|
+
telemetryEnabled: false,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
_tag: "SetupWorkspace",
|
|
214
|
+
request,
|
|
215
|
+
settingsExist,
|
|
216
|
+
settingsPath,
|
|
217
|
+
workspaceDir,
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
/**
|
|
221
|
+
* Initialize the workspace, or resolve what initializing it would do.
|
|
222
|
+
*
|
|
223
|
+
* A first-time initialization runs inside a workspace transaction it opens
|
|
224
|
+
* itself: no workspace exists yet, so there is no located workspace to supply
|
|
225
|
+
* the transaction scope, and a run that fails halfway must leave the
|
|
226
|
+
* directory as it found it.
|
|
227
|
+
*/
|
|
228
|
+
export const previewOrApplySetupWorkspace = (candidate, options) => Effect.gen(function* () {
|
|
229
|
+
const path = yield* Path.Path;
|
|
230
|
+
const workspaceOptions = {
|
|
231
|
+
scope: candidate.request.scope,
|
|
232
|
+
projectRoot: candidate.request.projectRoot,
|
|
233
|
+
nonInteractive: candidate.request.nonInteractive,
|
|
234
|
+
...(candidate.request.agents !== undefined && candidate.request.agents.length > 0
|
|
235
|
+
? { agents: candidate.request.agents }
|
|
236
|
+
: {}),
|
|
237
|
+
...(candidate.request.yes !== undefined ? { yes: candidate.request.yes } : {}),
|
|
238
|
+
...(candidate.request.preview !== undefined ? { preview: candidate.request.preview } : {}),
|
|
239
|
+
};
|
|
240
|
+
const initialize = Effect.gen(function* () {
|
|
241
|
+
const settled = yield* bootstrapWorkspace(workspaceOptions);
|
|
242
|
+
if (settled.initialized && options?.bundledSkill !== undefined) {
|
|
243
|
+
yield* options.bundledSkill;
|
|
244
|
+
}
|
|
245
|
+
return settled;
|
|
246
|
+
});
|
|
247
|
+
if (candidate.request.preview === true || candidate.settingsExist)
|
|
248
|
+
return yield* initialize;
|
|
249
|
+
return yield* runWorkspaceTransaction({
|
|
250
|
+
claimDefaultTargets: false,
|
|
251
|
+
transition: initialize,
|
|
252
|
+
validate: () => Effect.void,
|
|
253
|
+
}).pipe(Effect.provide(WorkspaceTransactionScope.layer({
|
|
254
|
+
workspaceDir: candidate.workspaceDir,
|
|
255
|
+
settingsPath: candidate.settingsPath,
|
|
256
|
+
lockPath: path.join(path.dirname(candidate.settingsPath), LOCK_FILENAME),
|
|
257
|
+
})));
|
|
258
|
+
});
|
|
259
|
+
// -----------------------------------------------------------------------------
|
|
260
|
+
// report
|
|
261
|
+
// -----------------------------------------------------------------------------
|
|
262
|
+
const isKnownAgentId = (id) => Object.hasOwn(AGENTS, id);
|
|
263
|
+
const stepStatus = (status, hasChange) => status === "preview" ? "ready" : hasChange ? "applied" : "unchanged";
|
|
264
|
+
const artifactChange = (status, hasChange) => status === "preview"
|
|
265
|
+
? "created"
|
|
266
|
+
: hasChange
|
|
267
|
+
? "created"
|
|
268
|
+
: "unchanged";
|
|
269
|
+
/**
|
|
270
|
+
* How a preview chose its membership candidate: the explicit request when one
|
|
271
|
+
* was given, otherwise detection where it produced a strong signal, otherwise
|
|
272
|
+
* the catalog suggestion set.
|
|
273
|
+
*/
|
|
274
|
+
export const previewAgentDefault = (candidates) => {
|
|
275
|
+
const reasons = candidates.flatMap((entry) => entry.state === "selected" && entry.selectionReason !== undefined
|
|
276
|
+
? [entry.selectionReason]
|
|
277
|
+
: []);
|
|
278
|
+
if (reasons.includes("explicit"))
|
|
279
|
+
return "explicit";
|
|
280
|
+
if (reasons.some((reason) => reason === "project-detected" || reason === "user-detected")) {
|
|
281
|
+
return "detected";
|
|
282
|
+
}
|
|
283
|
+
return "suggested";
|
|
284
|
+
};
|
|
285
|
+
const bundledSkillDisplayPath = (scope) => scope === "project"
|
|
286
|
+
? "agent_extensions/agentxm/@agentxm/skills/axm"
|
|
287
|
+
: ".axm/workspace/agent_extensions/agentxm/@agentxm/skills/axm";
|
|
288
|
+
/**
|
|
289
|
+
* Describe the settled setup: which files the workspace now occupies, which
|
|
290
|
+
* agents it configures, and what the same request would do again.
|
|
291
|
+
*/
|
|
292
|
+
export const reportSetupWorkspace = (request) => Effect.gen(function* () {
|
|
293
|
+
const path = yield* Path.Path;
|
|
294
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
295
|
+
const agentRepository = yield* CodingAgentRepository;
|
|
296
|
+
const { settings, location, initialized, wouldInitialize, cancelled, agentCandidates } = request.transition;
|
|
297
|
+
const scope = location.scope;
|
|
298
|
+
const agentIds = settings.agents ?? [];
|
|
299
|
+
const scopeAgentIds = cancelled
|
|
300
|
+
? agentCandidates.flatMap((entry) => (entry.state === "selected" ? [entry.id] : []))
|
|
301
|
+
: agentIds;
|
|
302
|
+
const scopeSupport = setupScopeSupport(scopeAgentIds, scope);
|
|
303
|
+
const agents = [
|
|
304
|
+
...agentIds.flatMap((id) => isKnownAgentId(id) ? [{ id: AGENTS[id].id, name: AGENTS[id].name }] : []),
|
|
305
|
+
...agentIds.filter((id) => !isKnownAgentId(id)).map((id) => ({ id, name: id })),
|
|
306
|
+
];
|
|
307
|
+
const settingsPath = scope === "user" ? location.settingsPath : "axm.json";
|
|
308
|
+
const instructionsValue = settings.instructionFiles;
|
|
309
|
+
const instructions = instructionsValue === undefined
|
|
310
|
+
? undefined
|
|
311
|
+
: instructionsValue === false
|
|
312
|
+
? { enabled: false }
|
|
313
|
+
: {
|
|
314
|
+
enabled: true,
|
|
315
|
+
...(instructionsValue.fileName !== undefined
|
|
316
|
+
? { fileName: instructionsValue.fileName }
|
|
317
|
+
: {}),
|
|
318
|
+
...(instructionsValue.gitignoreAliases !== undefined
|
|
319
|
+
? { gitignoreAliases: instructionsValue.gitignoreAliases }
|
|
320
|
+
: {}),
|
|
321
|
+
};
|
|
322
|
+
const subagentFiles = agents.length > 0 ? yield* scanAllSubagentFiles(location.baseDir) : [];
|
|
323
|
+
const status = wouldInitialize
|
|
324
|
+
? "preview"
|
|
325
|
+
: initialized
|
|
326
|
+
? "initialized"
|
|
327
|
+
: "already-initialized";
|
|
328
|
+
const resolvedStatus = cancelled ? "cancelled" : status;
|
|
329
|
+
const gitManaged = scope === "project" && (yield* isGitManaged(location.baseDir));
|
|
330
|
+
const changeForPath = (filePath) => Effect.gen(function* () {
|
|
331
|
+
if (resolvedStatus === "already-initialized" || resolvedStatus === "cancelled") {
|
|
332
|
+
return "unchanged";
|
|
333
|
+
}
|
|
334
|
+
if (resolvedStatus === "initialized")
|
|
335
|
+
return "created";
|
|
336
|
+
const exists = yield* fileSystem
|
|
337
|
+
.exists(filePath)
|
|
338
|
+
.pipe(Effect.catch(() => Effect.succeed(false)));
|
|
339
|
+
return exists ? "updated" : "created";
|
|
340
|
+
});
|
|
341
|
+
const displayTargetPath = (filePath) => {
|
|
342
|
+
const relative = path.relative(location.baseDir, filePath);
|
|
343
|
+
return relative === "" || relative.startsWith("..") || path.isAbsolute(relative)
|
|
344
|
+
? filePath
|
|
345
|
+
: relative;
|
|
346
|
+
};
|
|
347
|
+
const targetsFor = (paths) => Effect.forEach([...new Set(paths)], (filePath) => changeForPath(filePath).pipe(Effect.map((change) => ({
|
|
348
|
+
path: displayTargetPath(filePath),
|
|
349
|
+
change,
|
|
350
|
+
}))));
|
|
351
|
+
const workspaceTargets = yield* targetsFor([
|
|
352
|
+
request.candidate.settingsPath,
|
|
353
|
+
location.lockPath,
|
|
354
|
+
...(gitManaged ? [path.join(location.baseDir, ".gitignore")] : []),
|
|
355
|
+
]);
|
|
356
|
+
const instructionTargets = instructions?.enabled === true
|
|
357
|
+
? yield* targetsFor([
|
|
358
|
+
path.join(location.baseDir, instructions.fileName ?? "AGENTS.md"),
|
|
359
|
+
...agentIds.flatMap((agentId) => {
|
|
360
|
+
if (!isKnownAgentId(agentId))
|
|
361
|
+
return [];
|
|
362
|
+
const resolution = resolveInstructionTarget({
|
|
363
|
+
instructions: AGENTS[agentId].instructions,
|
|
364
|
+
sourceFileName: instructions.fileName ?? "AGENTS.md",
|
|
365
|
+
symlinkSupported: true,
|
|
366
|
+
});
|
|
367
|
+
return resolution.action === "write"
|
|
368
|
+
? [path.join(location.baseDir, resolution.relativeTarget)]
|
|
369
|
+
: [];
|
|
370
|
+
}),
|
|
371
|
+
])
|
|
372
|
+
: [];
|
|
373
|
+
const skillTargets = (yield* Effect.forEach(agentIds.flatMap((agentId) => (isKnownAgentId(agentId) ? [agentId] : [])), (agentId) => agentRepository.get(agentId).pipe(Effect.flatMap((agent) => agent.resolveEffectiveSkillsDir({ workspaceRoot: location.baseDir })), Effect.flatMap((resolved) => resolved._tag !== "supported"
|
|
374
|
+
? Effect.succeed(Option.none())
|
|
375
|
+
: changeForPath(path.join(resolved.dir, "axm")).pipe(Effect.map((change) => Option.some({
|
|
376
|
+
path: displayTargetPath(path.join(resolved.dir, "axm")),
|
|
377
|
+
change,
|
|
378
|
+
agentIds: [agentId],
|
|
379
|
+
}))))))).flatMap(Option.toArray);
|
|
380
|
+
const steps = [
|
|
381
|
+
{
|
|
382
|
+
label: "Workspace configuration",
|
|
383
|
+
status: stepStatus(resolvedStatus, initialized),
|
|
384
|
+
message: resolvedStatus === "preview"
|
|
385
|
+
? "Would initialize workspace configuration"
|
|
386
|
+
: initialized
|
|
387
|
+
? "Initialized workspace configuration"
|
|
388
|
+
: "Workspace configuration already exists",
|
|
389
|
+
artifact: {
|
|
390
|
+
path: settingsPath,
|
|
391
|
+
scope,
|
|
392
|
+
change: artifactChange(resolvedStatus, initialized),
|
|
393
|
+
targets: workspaceTargets,
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
];
|
|
397
|
+
if (instructions !== undefined) {
|
|
398
|
+
steps.push({
|
|
399
|
+
label: "Instruction files",
|
|
400
|
+
status: stepStatus(resolvedStatus, initialized),
|
|
401
|
+
message: resolvedStatus === "preview"
|
|
402
|
+
? "Would configure instruction files"
|
|
403
|
+
: initialized
|
|
404
|
+
? "Configured instruction files"
|
|
405
|
+
: "Instruction files already configured",
|
|
406
|
+
artifact: {
|
|
407
|
+
path: instructionTargets[0]?.path ?? "instructions",
|
|
408
|
+
scope,
|
|
409
|
+
change: artifactChange(resolvedStatus, initialized),
|
|
410
|
+
targets: instructionTargets,
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
if (request.bundledSkill.installed || resolvedStatus === "preview") {
|
|
415
|
+
const change = artifactChange(resolvedStatus, request.bundledSkill.installed);
|
|
416
|
+
steps.push({
|
|
417
|
+
label: "@agentxm/skills/axm",
|
|
418
|
+
status: stepStatus(resolvedStatus, request.bundledSkill.installed),
|
|
419
|
+
message: resolvedStatus === "preview"
|
|
420
|
+
? "Would install the bundled AXM skill"
|
|
421
|
+
: "Installed the bundled AXM skill",
|
|
422
|
+
artifact: {
|
|
423
|
+
path: bundledSkillDisplayPath(scope),
|
|
424
|
+
scope,
|
|
425
|
+
agents: agentIds,
|
|
426
|
+
version: request.bundledSkill.version,
|
|
427
|
+
change,
|
|
428
|
+
targets: [{ path: bundledSkillDisplayPath(scope), change }, ...skillTargets],
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
if (agentIds.length === 0 && resolvedStatus !== "preview") {
|
|
433
|
+
steps.push({
|
|
434
|
+
label: "Agent materialization",
|
|
435
|
+
status: "warning",
|
|
436
|
+
message: `No coding-agent targets are configured. Run \`axm agents add --detected${scope === "user" ? " --scope user" : ""}\` to materialize installed extensions.`,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
const readyCount = steps.filter((step) => step.status === "ready").length;
|
|
440
|
+
const warningCount = steps.filter((step) => step.status === "warning").length;
|
|
441
|
+
const appliedCount = steps.filter((step) => step.status === "applied").length;
|
|
442
|
+
const blockedCount = steps.filter((step) => step.status === "blocked").length;
|
|
443
|
+
const failedCount = steps.filter((step) => step.status === "failed").length;
|
|
444
|
+
const membershipRequested = (request.candidate.request.agents?.length ?? 0) > 0;
|
|
445
|
+
const agentNames = agents.map((agent) => agent.name).join(", ");
|
|
446
|
+
const message = cancelled
|
|
447
|
+
? "Setup cancelled — no changes applied"
|
|
448
|
+
: wouldInitialize
|
|
449
|
+
? "Setup plan ready"
|
|
450
|
+
: !initialized
|
|
451
|
+
? membershipRequested
|
|
452
|
+
? "Workspace already initialized; use `axm agents add` or `axm agents remove` to change coding agents"
|
|
453
|
+
: agents.length > 0
|
|
454
|
+
? `Workspace already initialized with agents: ${agentNames}`
|
|
455
|
+
: "Workspace already initialized with no coding agents"
|
|
456
|
+
: agents.length > 0
|
|
457
|
+
? `Initialized with agents: ${agentNames}`
|
|
458
|
+
: "Workspace initialized with no coding agents";
|
|
459
|
+
return {
|
|
460
|
+
outcome: resolvedStatus === "preview"
|
|
461
|
+
? "previewed"
|
|
462
|
+
: resolvedStatus === "cancelled"
|
|
463
|
+
? "cancelled"
|
|
464
|
+
: appliedCount > 0
|
|
465
|
+
? "applied"
|
|
466
|
+
: "no-op",
|
|
467
|
+
planName: "Set up AXM workspace",
|
|
468
|
+
planDescription: `Set up AXM (${scope})`,
|
|
469
|
+
message,
|
|
470
|
+
totalSteps: steps.length,
|
|
471
|
+
readyCount,
|
|
472
|
+
warningCount,
|
|
473
|
+
errorCount: 0,
|
|
474
|
+
appliedCount,
|
|
475
|
+
failedCount,
|
|
476
|
+
blockedCount,
|
|
477
|
+
steps,
|
|
478
|
+
status: resolvedStatus,
|
|
479
|
+
changed: initialized && request.candidate.request.preview !== true,
|
|
480
|
+
defaultSkillInstalled: request.bundledSkill.installed,
|
|
481
|
+
scope,
|
|
482
|
+
agents,
|
|
483
|
+
...(agentCandidates.length > 0 ? { agentCandidates: [...agentCandidates] } : {}),
|
|
484
|
+
scopeSupport,
|
|
485
|
+
settingsPath,
|
|
486
|
+
...(instructions === undefined ? {} : { instructions }),
|
|
487
|
+
telemetryEnabled: request.candidate.request.telemetryEnabled,
|
|
488
|
+
...(subagentFiles.length > 0 ? { subagentFiles: [...subagentFiles] } : {}),
|
|
489
|
+
...(resolvedStatus === "preview"
|
|
490
|
+
? {
|
|
491
|
+
previewDefaults: {
|
|
492
|
+
agents: previewAgentDefault(agentCandidates),
|
|
493
|
+
...(instructions === undefined
|
|
494
|
+
? {}
|
|
495
|
+
: {
|
|
496
|
+
instructions: {
|
|
497
|
+
enabled: instructions.enabled,
|
|
498
|
+
fileName: instructions.fileName ?? "AGENTS.md",
|
|
499
|
+
},
|
|
500
|
+
}),
|
|
501
|
+
},
|
|
502
|
+
}
|
|
503
|
+
: {}),
|
|
504
|
+
};
|
|
505
|
+
});
|
|
506
|
+
/** The workspace setup use case. */
|
|
507
|
+
export const SetupWorkspace = {
|
|
508
|
+
prepare: prepareSetupWorkspace,
|
|
509
|
+
previewOrApply: previewOrApplySetupWorkspace,
|
|
510
|
+
report: reportSetupWorkspace,
|
|
511
|
+
};
|
|
512
|
+
//# sourceMappingURL=setup-workspace.js.map
|
package/dist/src/testing.d.ts
CHANGED
|
@@ -1,9 +1,100 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @agentxm/workspace-configuration deterministic fixtures and ports.
|
|
3
|
+
*
|
|
4
|
+
* A throwaway workspace with settings, lockfile, and agent-native files
|
|
5
|
+
* written as the product writes them, plus every service a configuration use
|
|
6
|
+
* case reads or writes through: the workspace state and its transaction
|
|
7
|
+
* scope, the projection capability, the rule manager, and the plan pipeline's
|
|
8
|
+
* per-invocation services with a recording interaction port. Production
|
|
9
|
+
* source never imports this module.
|
|
5
10
|
*
|
|
6
11
|
* @experimental This API is unstable and may change without notice.
|
|
7
12
|
*/
|
|
8
|
-
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
15
|
+
import { AgentExecutableResolver } from "@agentxm/agent-integration";
|
|
16
|
+
import { SourceHostProviders } from "@agentxm/extension-sources";
|
|
17
|
+
import type { WorkspaceScope } from "@agentxm/extension-model/unstable/workspace-scope";
|
|
18
|
+
import { type ResolvePlanInteractionTestState } from "@agentxm/workspace-operations/testing";
|
|
19
|
+
import { ProjectionParticipants } from "@agentxm/workspace-projection";
|
|
20
|
+
import { WorkspaceInitializationInteractionTest, type WorkspaceInitializationInteractionTestState } from "./setup/initialization-interaction.js";
|
|
21
|
+
export { WorkspaceInitializationInteractionTest, type WorkspaceInitializationInteractionTestState };
|
|
22
|
+
export interface ConfigurationFixtureOptions {
|
|
23
|
+
readonly scope?: WorkspaceScope;
|
|
24
|
+
/** Settings document for the selected scope; written as authored. */
|
|
25
|
+
readonly settings?: Readonly<Record<string, unknown>>;
|
|
26
|
+
/** Lockfile document for the selected scope; `lockfileVersion` is supplied. */
|
|
27
|
+
readonly lockfile?: Readonly<Record<string, unknown>>;
|
|
28
|
+
/** Extra files written into the project root, keyed by relative path. */
|
|
29
|
+
readonly files?: Readonly<Record<string, string>>;
|
|
30
|
+
/** Extra files written into the pinned user home, keyed by relative path. */
|
|
31
|
+
readonly homeFiles?: Readonly<Record<string, string>>;
|
|
32
|
+
/**
|
|
33
|
+
* Agent executables the machine reports as present. Detection consults this
|
|
34
|
+
* alongside the workspace's own marker directories, so a fixture that leaves
|
|
35
|
+
* it empty observes detection from workspace evidence alone.
|
|
36
|
+
*/
|
|
37
|
+
readonly installedExecutables?: ReadonlyArray<string>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A throwaway workspace with every service a configuration use case needs
|
|
41
|
+
* over it, and a pinned user home so a project-scope run can be shown to
|
|
42
|
+
* leave the user scope alone.
|
|
43
|
+
*/
|
|
44
|
+
export declare const makeConfigurationFixture: (options?: ConfigurationFixtureOptions) => {
|
|
45
|
+
root: string;
|
|
46
|
+
home: string;
|
|
47
|
+
workspaceRoot: string;
|
|
48
|
+
writeFile: (relativePath: string, contents: string) => void;
|
|
49
|
+
writeHomeFile: (relativePath: string, contents: string) => void;
|
|
50
|
+
readFile: (relativePath: string) => string;
|
|
51
|
+
exists: (relativePath: string) => boolean;
|
|
52
|
+
/** Every file under the project root. */
|
|
53
|
+
snapshot: () => readonly (readonly [string, string])[];
|
|
54
|
+
/** Every file under the pinned user home. */
|
|
55
|
+
homeSnapshot: () => readonly (readonly [string, string])[];
|
|
56
|
+
/** What the plan interaction port was asked to present and confirm. */
|
|
57
|
+
interactionState: () => ResolvePlanInteractionTestState;
|
|
58
|
+
provide: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E | import("@agentxm/workspace-state").WorkspaceMutationsError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | Exclude<R, import("@agentxm/workspace-projection").CodingAgentRepository | import("@agentxm/workspace-transactions").WorkspaceTransactionScope | import("@agentxm/workspace-state").WorkspaceMutations | import("@agentxm/workspace-state").ConfiguredAgentOutcomesProvider | import("@agentxm/workspace-transactions").FootprintRecorder | import("@agentxm/workspace-operations").OperationJournal | import("@agentxm/workspace-operations").ResolvePlanInteraction | import("@agentxm/extension-materialization").RuleManager | HttpClient.HttpClient | import("@agentxm/agent-integration").NativeWriteAuthority | SourceHostProviders | ProjectionParticipants | AgentExecutableResolver | import("@agentxm/workspace-state/live").WorkspaceStateServices | import("@agentxm/workspace-projection").WorkspaceInvariantFacts | import("@agentxm/extension-sources").WorkspaceCatalog>>;
|
|
59
|
+
cleanup: () => void;
|
|
60
|
+
};
|
|
61
|
+
export type ConfigurationFixture = ReturnType<typeof makeConfigurationFixture>;
|
|
62
|
+
export interface SetupFixtureOptions {
|
|
63
|
+
/** Canned answers for the prompts an interactive setup raises. */
|
|
64
|
+
readonly selectAgents?: ReadonlyArray<string>;
|
|
65
|
+
readonly confirmSetupPlan?: boolean;
|
|
66
|
+
readonly confirmInstructionSync?: boolean;
|
|
67
|
+
/** Files written into the bare project directory before setup runs. */
|
|
68
|
+
readonly files?: Readonly<Record<string, string>>;
|
|
69
|
+
/** Files written into the pinned user home before setup runs. */
|
|
70
|
+
readonly homeFiles?: Readonly<Record<string, string>>;
|
|
71
|
+
/** Agent executables the machine reports as present. */
|
|
72
|
+
readonly installedExecutables?: ReadonlyArray<string>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A bare directory and the services setup needs to turn it into a workspace.
|
|
76
|
+
*
|
|
77
|
+
* Setup builds its own workspace, so this fixture deliberately provides no
|
|
78
|
+
* workspace state: only the platform-adjacent ports it reads — the pinned
|
|
79
|
+
* user home, the coding-agent catalog and executable probe, and a recording
|
|
80
|
+
* initialization-interaction port whose calls are the evidence channel for
|
|
81
|
+
* every claim about what setup asked.
|
|
82
|
+
*/
|
|
83
|
+
export declare const makeSetupFixture: (options?: SetupFixtureOptions) => {
|
|
84
|
+
root: string;
|
|
85
|
+
home: string;
|
|
86
|
+
writeFile: (relativePath: string, contents: string) => void;
|
|
87
|
+
writeHomeFile: (relativePath: string, contents: string) => void;
|
|
88
|
+
readFile: (relativePath: string) => string;
|
|
89
|
+
exists: (relativePath: string) => boolean;
|
|
90
|
+
/** Every file under the bare project directory. */
|
|
91
|
+
snapshot: () => readonly (readonly [string, string])[];
|
|
92
|
+
/** Every file under the pinned user home. */
|
|
93
|
+
homeSnapshot: () => readonly (readonly [string, string])[];
|
|
94
|
+
/** What the initialization interaction port was asked. */
|
|
95
|
+
promptState: () => WorkspaceInitializationInteractionTestState;
|
|
96
|
+
provide: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, import("./setup/initialization-interaction.js").WorkspaceInitializationInteraction | import("@agentxm/workspace-projection").CodingAgentRepository | AgentExecutableResolver>>;
|
|
97
|
+
cleanup: () => void;
|
|
98
|
+
};
|
|
99
|
+
export type SetupFixture = ReturnType<typeof makeSetupFixture>;
|
|
9
100
|
//# sourceMappingURL=testing.d.ts.map
|