ast-mcp-server 0.7.1 → 0.8.0
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/CHANGELOG.md +44 -1
- package/README.md +36 -14
- package/SECURITY.md +8 -7
- package/dist/cli.js +18 -14
- package/dist/services/agent-setup.js +248 -27
- package/dist/services/agent-targets.js +209 -63
- package/dist/services/checkbox-state.js +31 -0
- package/dist/services/managed-file.js +709 -0
- package/dist/services/managed-guidance.js +229 -0
- package/dist/services/opencode-config.js +135 -0
- package/dist/services/raw-tty.js +75 -0
- package/dist/services/setup-wizard.js +45 -93
- package/dist/services/skill-installer.js +145 -84
- package/docs/support.md +13 -13
- package/package.json +6 -3
- package/skills/structural-code-editing/SKILL.md +9 -3
- package/skills/structural-code-editing/guidance.md +3 -0
- package/skills/structural-code-editing/releases.json +45 -0
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { constants as fsConstants } from "node:fs";
|
|
3
3
|
import { access, realpath, stat } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import process from "node:process";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { planBundledSkillInstallation, SkillConflictError, } from "./skill-installer.js";
|
|
9
|
+
import { planManagedGuidance, } from "./managed-guidance.js";
|
|
10
|
+
import { applyManagedFilePlan, createManagedFileApplyContext, ManagedFileApplyError, ManagedFileRollbackError, revalidateManagedFilePlan, verifyManagedFilePostimage, } from "./managed-file.js";
|
|
11
|
+
import { AGENT_IDS, classifyAgentVersion, getAgentTarget, MCP_SERVER_NAME, } from "./agent-targets.js";
|
|
12
|
+
import { applyOpenCodeConfigPlan, planOpenCodeConfig, resolveOpenCodeConfigPath, withIsolatedOpenCodeConfig, } from "./opencode-config.js";
|
|
8
13
|
const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
|
|
9
14
|
const MAX_COMMAND_OUTPUT_BYTES = 1024 * 1024;
|
|
10
15
|
export class AgentSetupError extends Error {
|
|
@@ -17,6 +22,87 @@ export class AgentSetupError extends Error {
|
|
|
17
22
|
this.details = details;
|
|
18
23
|
}
|
|
19
24
|
}
|
|
25
|
+
export async function applyManagedAssetPlans(skillPlan, guidancePlan, context = createManagedFileApplyContext(), hooks = {}) {
|
|
26
|
+
const physicalWrites = [];
|
|
27
|
+
const possiblyCommitted = [];
|
|
28
|
+
const rolledBack = [];
|
|
29
|
+
const rollbackFailed = [];
|
|
30
|
+
const authenticated = [];
|
|
31
|
+
let activePlan;
|
|
32
|
+
try {
|
|
33
|
+
for (const file of [...skillPlan.files, ...guidancePlan.files]) {
|
|
34
|
+
await revalidateManagedFilePlan(file);
|
|
35
|
+
}
|
|
36
|
+
const plans = [
|
|
37
|
+
...skillPlan.files.map((file) => ({ file, asset: "skill" })),
|
|
38
|
+
...guidancePlan.files.map((file) => ({ file, asset: "guidance" })),
|
|
39
|
+
];
|
|
40
|
+
for (const { file, asset } of plans) {
|
|
41
|
+
activePlan = { file, asset };
|
|
42
|
+
for (const current of authenticated) {
|
|
43
|
+
await verifyManagedFilePostimage(current, context);
|
|
44
|
+
}
|
|
45
|
+
await applyManagedFilePlan(file, context, hooks);
|
|
46
|
+
authenticated.push(file);
|
|
47
|
+
if (file.status !== "unchanged") {
|
|
48
|
+
physicalWrites.push({ path: file.path, asset, status: file.status });
|
|
49
|
+
}
|
|
50
|
+
activePlan = undefined;
|
|
51
|
+
}
|
|
52
|
+
for (const current of authenticated) {
|
|
53
|
+
await verifyManagedFilePostimage(current, context);
|
|
54
|
+
}
|
|
55
|
+
return physicalWrites;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (activePlan !== undefined && activePlan.file.status !== "unchanged") {
|
|
59
|
+
const write = {
|
|
60
|
+
path: activePlan.file.path,
|
|
61
|
+
asset: activePlan.asset,
|
|
62
|
+
status: activePlan.file.status,
|
|
63
|
+
};
|
|
64
|
+
if (error instanceof ManagedFileRollbackError) {
|
|
65
|
+
rolledBack.push(write);
|
|
66
|
+
}
|
|
67
|
+
else if (error instanceof ManagedFileApplyError) {
|
|
68
|
+
if (error.commitState === "committed") {
|
|
69
|
+
physicalWrites.push(write);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
possiblyCommitted.push(write);
|
|
73
|
+
}
|
|
74
|
+
if (error.rollbackState === "failed") {
|
|
75
|
+
rollbackFailed.push(write);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw new AgentSetupError(error instanceof Error ? error.message : "Managed asset apply failed.", "AGENT_ASSET_APPLY_FAILED", {
|
|
80
|
+
completed_writes: physicalWrites,
|
|
81
|
+
possibly_committed: possiblyCommitted,
|
|
82
|
+
rolled_back: rolledBack,
|
|
83
|
+
rollback_failed: rollbackFailed,
|
|
84
|
+
pending: [
|
|
85
|
+
...skillPlan.files
|
|
86
|
+
.filter((file) => file.status !== "unchanged")
|
|
87
|
+
.map((file) => ({ path: file.path, asset: "skill", status: file.status })),
|
|
88
|
+
...guidancePlan.files
|
|
89
|
+
.filter((file) => file.status !== "unchanged")
|
|
90
|
+
.map((file) => ({ path: file.path, asset: "guidance", status: file.status })),
|
|
91
|
+
].filter((pending) => !physicalWrites.some((completed) => completed.path === pending.path && completed.asset === pending.asset) &&
|
|
92
|
+
!possiblyCommitted.some((uncertain) => uncertain.path === pending.path && uncertain.asset === pending.asset)),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function verifyManagedAssetPlans(skillPlan, guidancePlan, context) {
|
|
97
|
+
try {
|
|
98
|
+
for (const file of [...skillPlan.files, ...guidancePlan.files]) {
|
|
99
|
+
await verifyManagedFilePostimage(file, context);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
throw new AgentSetupError(error instanceof Error ? error.message : "Managed asset postimage verification failed.", "AGENT_ASSET_APPLY_FAILED", { cause: error instanceof Error ? error.message : String(error) });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
20
106
|
function environmentPath(environment) {
|
|
21
107
|
return environment.PATH ?? environment.Path ?? environment.path ?? "";
|
|
22
108
|
}
|
|
@@ -76,7 +162,7 @@ async function runCommand(executable, args, options) {
|
|
|
76
162
|
const appendOutput = (target, chunk) => {
|
|
77
163
|
outputBytes += chunk.byteLength;
|
|
78
164
|
if (outputBytes > MAX_COMMAND_OUTPUT_BYTES) {
|
|
79
|
-
finishWithError(new AgentSetupError(`Agent command exceeded ${MAX_COMMAND_OUTPUT_BYTES} output bytes.`, "AGENT_COMMAND_OUTPUT_LIMIT"
|
|
165
|
+
finishWithError(new AgentSetupError(`Agent command exceeded ${MAX_COMMAND_OUTPUT_BYTES} output bytes.`, "AGENT_COMMAND_OUTPUT_LIMIT"));
|
|
80
166
|
return;
|
|
81
167
|
}
|
|
82
168
|
if (target === "stdout") {
|
|
@@ -87,15 +173,12 @@ async function runCommand(executable, args, options) {
|
|
|
87
173
|
}
|
|
88
174
|
};
|
|
89
175
|
const timeout = setTimeout(() => {
|
|
90
|
-
finishWithError(new AgentSetupError(`Agent command timed out after ${options.timeoutMs}ms.`, "AGENT_COMMAND_TIMEOUT"
|
|
176
|
+
finishWithError(new AgentSetupError(`Agent command timed out after ${options.timeoutMs}ms.`, "AGENT_COMMAND_TIMEOUT"));
|
|
91
177
|
}, options.timeoutMs);
|
|
92
178
|
child.stdout.on("data", (chunk) => appendOutput("stdout", chunk));
|
|
93
179
|
child.stderr.on("data", (chunk) => appendOutput("stderr", chunk));
|
|
94
|
-
child.on("error", (
|
|
95
|
-
finishWithError(new AgentSetupError(
|
|
96
|
-
executable,
|
|
97
|
-
args,
|
|
98
|
-
}));
|
|
180
|
+
child.on("error", () => {
|
|
181
|
+
finishWithError(new AgentSetupError("Failed to start the agent command.", "AGENT_COMMAND_START"));
|
|
99
182
|
});
|
|
100
183
|
child.on("close", (exitCode) => {
|
|
101
184
|
if (settled) {
|
|
@@ -109,8 +192,7 @@ async function runCommand(executable, args, options) {
|
|
|
109
192
|
});
|
|
110
193
|
}
|
|
111
194
|
function commandFailure(agent, operation, result) {
|
|
112
|
-
|
|
113
|
-
return new AgentSetupError(`${getAgentTarget(agent).label} ${operation} failed with exit code ${result.exitCode}.`, "AGENT_COMMAND_FAILED", { agent, operation, output });
|
|
195
|
+
return new AgentSetupError(`${getAgentTarget(agent).label} ${operation} failed with exit code ${result.exitCode}.`, "AGENT_COMMAND_FAILED", { agent, operation, reason: `Command exited with code ${result.exitCode}.` });
|
|
114
196
|
}
|
|
115
197
|
function targetRuntime(detection, environment, timeoutMs) {
|
|
116
198
|
return {
|
|
@@ -136,6 +218,33 @@ async function inspectMcp(agent, detection, serverEntryPath, nodeExecutable, env
|
|
|
136
218
|
...(inspection.detail ? { detail: inspection.detail } : {}),
|
|
137
219
|
};
|
|
138
220
|
}
|
|
221
|
+
async function verifyOpenCode(detection, environment, timeoutMs, context) {
|
|
222
|
+
await withIsolatedOpenCodeConfig(environment, environment.HOME ?? os.homedir(), async (isolatedEnvironment) => {
|
|
223
|
+
const runtime = targetRuntime(detection, isolatedEnvironment, timeoutMs);
|
|
224
|
+
const resolved = await runtime.run(["debug", "config", "--pure"]);
|
|
225
|
+
if (resolved.exitCode !== 0)
|
|
226
|
+
throw commandFailure("opencode", "resolved configuration verification", resolved);
|
|
227
|
+
let config;
|
|
228
|
+
try {
|
|
229
|
+
config = JSON.parse(resolved.stdout);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
throw new AgentSetupError("OpenCode resolved configuration output is unknown.", "AGENT_VERIFICATION_FAILED", { agent: "opencode" });
|
|
233
|
+
}
|
|
234
|
+
const command = config?.mcp?.ast?.command;
|
|
235
|
+
if (!Array.isArray(command) ||
|
|
236
|
+
command.length !== 2 ||
|
|
237
|
+
command[0] !== context.nodeExecutable ||
|
|
238
|
+
command[1] !== context.serverEntryPath) {
|
|
239
|
+
throw new AgentSetupError("OpenCode resolved configuration does not contain the expected ast server.", "AGENT_VERIFICATION_FAILED", { agent: "opencode" });
|
|
240
|
+
}
|
|
241
|
+
const listed = await runtime.run(["mcp", "list", "--pure"]);
|
|
242
|
+
const plainList = listed.stdout.replaceAll("\u001b", "");
|
|
243
|
+
if (listed.exitCode !== 0 || !/ast[\s\S]*connected/i.test(plainList)) {
|
|
244
|
+
throw new AgentSetupError("OpenCode did not report the ast server as connected.", "AGENT_VERIFICATION_FAILED", { agent: "opencode" });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
139
248
|
async function configureMcp(agent, detection, serverEntryPath, nodeExecutable, environment, timeoutMs) {
|
|
140
249
|
const target = getAgentTarget(agent);
|
|
141
250
|
const added = await target.mcp.register(targetRuntime(detection, environment, timeoutMs), {
|
|
@@ -175,7 +284,12 @@ export async function detectInstalledAgents(options = {}) {
|
|
|
175
284
|
const definition = getAgentTarget(id);
|
|
176
285
|
const executable = await findExecutable(definition.command, environment);
|
|
177
286
|
if (executable === undefined) {
|
|
178
|
-
return {
|
|
287
|
+
return {
|
|
288
|
+
id,
|
|
289
|
+
label: definition.label,
|
|
290
|
+
installed: false,
|
|
291
|
+
compatibility: { status: "unavailable", reason: "Not found in PATH." },
|
|
292
|
+
};
|
|
179
293
|
}
|
|
180
294
|
let version;
|
|
181
295
|
try {
|
|
@@ -191,10 +305,17 @@ export async function detectInstalledAgents(options = {}) {
|
|
|
191
305
|
catch {
|
|
192
306
|
version = undefined;
|
|
193
307
|
}
|
|
194
|
-
|
|
308
|
+
const compatibility = version === undefined
|
|
309
|
+
? {
|
|
310
|
+
status: "incompatible",
|
|
311
|
+
reason: "Version command did not produce admitted evidence.",
|
|
312
|
+
}
|
|
313
|
+
: classifyAgentVersion(id, version);
|
|
314
|
+
return { id, label: definition.label, installed: true, executable, version, compatibility };
|
|
195
315
|
}));
|
|
196
316
|
}
|
|
197
317
|
export async function runAgentSetup(options) {
|
|
318
|
+
const correlationId = randomUUID();
|
|
198
319
|
const environment = options.environment ?? process.env;
|
|
199
320
|
const nodeExecutable = options.nodeExecutable ?? process.execPath;
|
|
200
321
|
const timeoutMs = options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
|
|
@@ -208,42 +329,137 @@ export async function runAgentSetup(options) {
|
|
|
208
329
|
if (!detection?.installed || detection.executable === undefined) {
|
|
209
330
|
throw new AgentSetupError(`${getAgentTarget(agent).label} is not installed or is not available in PATH.`, "AGENT_NOT_INSTALLED", { agent });
|
|
210
331
|
}
|
|
332
|
+
if (detection.compatibility && detection.compatibility.status !== "compatible") {
|
|
333
|
+
throw new AgentSetupError(`${getAgentTarget(agent).label} is incompatible: ${detection.compatibility.reason}`, detection.compatibility.status === "blocked_untrusted_folder"
|
|
334
|
+
? "AGENT_TRUST_REQUIRED"
|
|
335
|
+
: "AGENT_INCOMPATIBLE", { agent, correlation_id: correlationId });
|
|
336
|
+
}
|
|
211
337
|
}
|
|
212
338
|
await assertRegularFile(options.serverEntryPath, "MCP server entrypoint");
|
|
213
339
|
await assertRegularFile(options.sourceSkillPath, "Bundled skill");
|
|
214
|
-
|
|
340
|
+
let openCodePlan;
|
|
341
|
+
if (requested.includes("opencode")) {
|
|
342
|
+
openCodePlan = await planOpenCodeConfig({
|
|
343
|
+
filePath: resolveOpenCodeConfigPath(environment, environment.HOME ?? os.homedir()),
|
|
344
|
+
nodeExecutable,
|
|
345
|
+
serverEntryPath: options.serverEntryPath,
|
|
346
|
+
}).catch((error) => {
|
|
347
|
+
throw new AgentSetupError(error instanceof Error ? error.message : "OpenCode configuration preflight failed.", "AGENT_MCP_CONFLICT", { agent: "opencode", correlation_id: correlationId });
|
|
348
|
+
});
|
|
349
|
+
const effective = await withIsolatedOpenCodeConfig(environment, environment.HOME ?? os.homedir(), (isolatedEnvironment) => targetRuntime(detectionMap.get("opencode"), isolatedEnvironment, timeoutMs).run([
|
|
350
|
+
"debug",
|
|
351
|
+
"config",
|
|
352
|
+
"--pure",
|
|
353
|
+
]));
|
|
354
|
+
if (effective.exitCode !== 0) {
|
|
355
|
+
throw new AgentSetupError(`OpenCode effective configuration preflight failed with exit code ${effective.exitCode}.`, "AGENT_MCP_CONFLICT", { agent: "opencode", correlation_id: correlationId });
|
|
356
|
+
}
|
|
357
|
+
let effectiveConfig;
|
|
358
|
+
try {
|
|
359
|
+
effectiveConfig = JSON.parse(effective.stdout);
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
throw new AgentSetupError("OpenCode effective configuration output is unknown.", "AGENT_MCP_CONFLICT", { agent: "opencode", correlation_id: correlationId });
|
|
363
|
+
}
|
|
364
|
+
const effectiveAst = effectiveConfig?.mcp?.ast;
|
|
365
|
+
const desiredAst = {
|
|
366
|
+
type: "local",
|
|
367
|
+
command: [nodeExecutable, options.serverEntryPath],
|
|
368
|
+
enabled: true,
|
|
369
|
+
};
|
|
370
|
+
if (effectiveAst !== undefined && JSON.stringify(effectiveAst) !== JSON.stringify(desiredAst)) {
|
|
371
|
+
throw new AgentSetupError("OpenCode effective configuration conflict at mcp.ast.", "AGENT_MCP_CONFLICT", { agent: "opencode", correlation_id: correlationId });
|
|
372
|
+
}
|
|
373
|
+
if (effectiveAst !== undefined && openCodePlan.status === "installed") {
|
|
374
|
+
openCodePlan = { ...openCodePlan, status: "unchanged" };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const inspections = await Promise.all(requested.map((agent) => agent === "opencode"
|
|
378
|
+
? Promise.resolve({
|
|
379
|
+
agent,
|
|
380
|
+
status: openCodePlan?.status === "unchanged" ? "current" : "missing",
|
|
381
|
+
})
|
|
382
|
+
: inspectMcp(agent, detectionMap.get(agent), options.serverEntryPath, nodeExecutable, environment, timeoutMs)));
|
|
215
383
|
const conflict = inspections.find((item) => item.status === "conflict");
|
|
216
384
|
if (conflict !== undefined) {
|
|
217
385
|
throw new AgentSetupError(`Conflicting ${getAgentTarget(conflict.agent).label} MCP registration: ${conflict.detail}`, "AGENT_MCP_CONFLICT", { agent: conflict.agent });
|
|
218
386
|
}
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
387
|
+
const blocked = inspections.find((item) => item.status === "blocked_untrusted_folder");
|
|
388
|
+
if (blocked !== undefined) {
|
|
389
|
+
throw new AgentSetupError(`${getAgentTarget(blocked.agent).label} requires the working folder to be trusted before setup.`, "AGENT_TRUST_REQUIRED", { agent: blocked.agent, correlation_id: correlationId });
|
|
390
|
+
}
|
|
391
|
+
let skillPlan;
|
|
392
|
+
try {
|
|
393
|
+
skillPlan = await planBundledSkillInstallation({
|
|
394
|
+
target: requested,
|
|
395
|
+
scope: "user",
|
|
396
|
+
force: options.forceSkill ?? false,
|
|
397
|
+
sourceSkillPath: options.sourceSkillPath,
|
|
398
|
+
releaseManifestPath: options.releaseManifestPath,
|
|
399
|
+
environment,
|
|
400
|
+
homeDirectory: environment.HOME ?? os.homedir(),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
227
404
|
if (error instanceof SkillConflictError) {
|
|
228
405
|
throw new AgentSetupError(`Skill already exists with different content at ${error.destination}; pass --force-skill to replace it.`, "AGENT_SKILL_CONFLICT", { path: error.destination });
|
|
229
406
|
}
|
|
230
|
-
throw error;
|
|
231
|
-
}
|
|
232
|
-
|
|
407
|
+
throw new AgentSetupError(error instanceof Error ? error.message : "Skill preflight failed.", "SETUP_ASSET_INVALID", { asset: "skill", correlation_id: correlationId });
|
|
408
|
+
}
|
|
409
|
+
let guidancePlan;
|
|
410
|
+
try {
|
|
411
|
+
guidancePlan = await planManagedGuidance({
|
|
412
|
+
agents: requested,
|
|
413
|
+
sourceGuidancePath: options.sourceGuidancePath,
|
|
414
|
+
environment,
|
|
415
|
+
homeDirectory: environment.HOME ?? os.homedir(),
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
throw new AgentSetupError(error instanceof Error ? error.message : "Managed guidance preflight failed.", "AGENT_GUIDANCE_CONFLICT", { asset: "guidance", correlation_id: correlationId });
|
|
420
|
+
}
|
|
421
|
+
const skillStatuses = new Map(skillPlan.installations.map((installation) => [installation.target, installation.status]));
|
|
422
|
+
const guidanceStatuses = new Map(guidancePlan.installations.map((installation) => [installation.agent, installation.status]));
|
|
423
|
+
const applyContext = createManagedFileApplyContext();
|
|
424
|
+
const physicalWrites = await applyManagedAssetPlans(skillPlan, guidancePlan, applyContext);
|
|
233
425
|
const completed = [];
|
|
234
426
|
for (const agent of requested) {
|
|
235
427
|
const detection = detectionMap.get(agent);
|
|
236
428
|
const inspection = inspections.find((item) => item.agent === agent);
|
|
237
429
|
try {
|
|
430
|
+
await verifyManagedAssetPlans(skillPlan, guidancePlan, applyContext);
|
|
238
431
|
if (inspection.status === "missing") {
|
|
239
|
-
|
|
432
|
+
if (agent === "opencode") {
|
|
433
|
+
await applyOpenCodeConfigPlan(openCodePlan);
|
|
434
|
+
if (openCodePlan.status === "installed") {
|
|
435
|
+
physicalWrites.push({
|
|
436
|
+
path: openCodePlan.filePath,
|
|
437
|
+
asset: "mcp_config",
|
|
438
|
+
status: "installed",
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
await verifyOpenCode(detection, environment, timeoutMs, {
|
|
442
|
+
nodeExecutable,
|
|
443
|
+
serverEntryPath: options.serverEntryPath,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
else
|
|
447
|
+
await configureMcp(agent, detection, options.serverEntryPath, nodeExecutable, environment, timeoutMs);
|
|
448
|
+
}
|
|
449
|
+
else if (agent === "opencode") {
|
|
450
|
+
await verifyOpenCode(detection, environment, timeoutMs, {
|
|
451
|
+
nodeExecutable,
|
|
452
|
+
serverEntryPath: options.serverEntryPath,
|
|
453
|
+
});
|
|
240
454
|
}
|
|
455
|
+
await verifyManagedAssetPlans(skillPlan, guidancePlan, applyContext);
|
|
241
456
|
completed.push({
|
|
242
457
|
agent,
|
|
243
458
|
executable: detection.executable,
|
|
244
459
|
version: detection.version,
|
|
245
460
|
mcp: inspection.status === "current" ? "unchanged" : "configured",
|
|
246
461
|
skill: skillStatuses.get(agent),
|
|
462
|
+
guidance: guidanceStatuses.get(agent),
|
|
247
463
|
});
|
|
248
464
|
}
|
|
249
465
|
catch (error) {
|
|
@@ -251,16 +467,21 @@ export async function runAgentSetup(options) {
|
|
|
251
467
|
throw new AgentSetupError(error.message, error.code, {
|
|
252
468
|
cause: error.details,
|
|
253
469
|
completed,
|
|
254
|
-
skills:
|
|
470
|
+
skills: skillPlan.installations,
|
|
471
|
+
guidance: guidancePlan.installations,
|
|
472
|
+
completed_writes: physicalWrites,
|
|
255
473
|
});
|
|
256
474
|
}
|
|
257
475
|
throw error;
|
|
258
476
|
}
|
|
259
477
|
}
|
|
260
478
|
return {
|
|
479
|
+
version: 2,
|
|
261
480
|
status: "ok",
|
|
262
481
|
command: "setup",
|
|
263
482
|
server: { name: MCP_SERVER_NAME, command: nodeExecutable, args: [options.serverEntryPath] },
|
|
264
483
|
agents: completed,
|
|
484
|
+
correlation_id: correlationId,
|
|
485
|
+
physical_writes: physicalWrites,
|
|
265
486
|
};
|
|
266
487
|
}
|