@tacuchi/agent-workflow-cli 23.0.0 → 25.0.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/README.md +37 -1
- package/dist/adapters/postgres-readonly-tools.js +277 -0
- package/dist/adapters/postgres-readonly-tools.js.map +1 -0
- package/dist/application/checkpoint/markdown.js +85 -5
- package/dist/application/checkpoint/markdown.js.map +1 -1
- package/dist/application/checkpoint-service.js +7 -6
- package/dist/application/checkpoint-service.js.map +1 -1
- package/dist/application/checkpoint-write-service.js +270 -16
- package/dist/application/checkpoint-write-service.js.map +1 -1
- package/dist/application/database-mcp-server.js +168 -0
- package/dist/application/database-mcp-server.js.map +1 -0
- package/dist/application/database-mcp-stdio.js +137 -0
- package/dist/application/database-mcp-stdio.js.map +1 -0
- package/dist/application/database-tool-catalog.js +226 -0
- package/dist/application/database-tool-catalog.js.map +1 -0
- package/dist/application/dsn-reader-service.js +1 -1
- package/dist/application/flow/advance.js +60 -10
- package/dist/application/flow/advance.js.map +1 -1
- package/dist/application/flow/internal-actions.js +1 -1
- package/dist/application/flow/internal-actions.js.map +1 -1
- package/dist/application/flow/submit.js +256 -9
- package/dist/application/flow/submit.js.map +1 -1
- package/dist/application/lifecycle-target.js +16 -19
- package/dist/application/lifecycle-target.js.map +1 -1
- package/dist/application/lock-service.js +96 -31
- package/dist/application/lock-service.js.map +1 -1
- package/dist/application/logging/log-events.js +19 -3
- package/dist/application/logging/log-events.js.map +1 -1
- package/dist/application/logging/logger.js +2 -2
- package/dist/application/logging/logger.js.map +1 -1
- package/dist/application/markdown.js +6 -2
- package/dist/application/markdown.js.map +1 -1
- package/dist/application/mcp-connections-service.js +115 -41
- package/dist/application/mcp-connections-service.js.map +1 -1
- package/dist/application/mcp-doctor-service.js +98 -54
- package/dist/application/mcp-doctor-service.js.map +1 -1
- package/dist/application/mcp-entry-classification.js +60 -0
- package/dist/application/mcp-entry-classification.js.map +1 -0
- package/dist/application/mcp-host-reader.js +102 -86
- package/dist/application/mcp-host-reader.js.map +1 -1
- package/dist/application/mcp-host-receipt-service.js +399 -0
- package/dist/application/mcp-host-receipt-service.js.map +1 -0
- package/dist/application/mcp-host-receipt-store.js +122 -0
- package/dist/application/mcp-host-receipt-store.js.map +1 -0
- package/dist/application/mcp-host-receipts.js +28 -0
- package/dist/application/mcp-host-receipts.js.map +1 -0
- package/dist/application/mcp-host-writer.js +209 -38
- package/dist/application/mcp-host-writer.js.map +1 -1
- package/dist/application/mcp-launch-probe-service.js +142 -0
- package/dist/application/mcp-launch-probe-service.js.map +1 -0
- package/dist/application/mcp-migration-service.js +351 -0
- package/dist/application/mcp-migration-service.js.map +1 -0
- package/dist/application/mcp-native-host-check-service.js +154 -0
- package/dist/application/mcp-native-host-check-service.js.map +1 -0
- package/dist/application/mcp-receipt-registration-service.js +105 -0
- package/dist/application/mcp-receipt-registration-service.js.map +1 -0
- package/dist/application/mcp-receipt-removal-service.js +62 -0
- package/dist/application/mcp-receipt-removal-service.js.map +1 -0
- package/dist/application/mcp-remove-service.js +83 -24
- package/dist/application/mcp-remove-service.js.map +1 -1
- package/dist/application/mcp-scope-common.js +1 -1
- package/dist/application/mcp-scope-common.js.map +1 -1
- package/dist/application/mcp-setup-service.js +59 -24
- package/dist/application/mcp-setup-service.js.map +1 -1
- package/dist/application/mcp-stdio-probe.js +526 -0
- package/dist/application/mcp-stdio-probe.js.map +1 -0
- package/dist/application/mcp-test-connection-service.js +23 -50
- package/dist/application/mcp-test-connection-service.js.map +1 -1
- package/dist/application/parsers/spec-functional.js +220 -0
- package/dist/application/parsers/spec-functional.js.map +1 -0
- package/dist/application/parsers/spec-relation.js +142 -5
- package/dist/application/parsers/spec-relation.js.map +1 -1
- package/dist/application/paths-service.js +13 -0
- package/dist/application/paths-service.js.map +1 -1
- package/dist/application/plan-exec-decision-service.js +57 -4
- package/dist/application/plan-exec-decision-service.js.map +1 -1
- package/dist/application/reseal-service.js +300 -0
- package/dist/application/reseal-service.js.map +1 -0
- package/dist/application/self/host-states.js.map +1 -1
- package/dist/application/self/install-skill.js +4 -34
- package/dist/application/self/install-skill.js.map +1 -1
- package/dist/application/self/mcp-config.js +369 -59
- package/dist/application/self/mcp-config.js.map +1 -1
- package/dist/application/self/mcp-via-state.js +9 -6
- package/dist/application/self/mcp-via-state.js.map +1 -1
- package/dist/application/self/uninstall-skill.js +3 -18
- package/dist/application/self/uninstall-skill.js.map +1 -1
- package/dist/application/self/uninstall.js +4 -23
- package/dist/application/self/uninstall.js.map +1 -1
- package/dist/application/workline-index-service.js +64 -6
- package/dist/application/workline-index-service.js.map +1 -1
- package/dist/application/workspace-init-service.js +4 -6
- package/dist/application/workspace-init-service.js.map +1 -1
- package/dist/cli/commands/checkpoint-write.js +24 -22
- package/dist/cli/commands/checkpoint-write.js.map +1 -1
- package/dist/cli/commands/index.js +7 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/mcp.js +642 -55
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/reseal.js +140 -0
- package/dist/cli/commands/reseal.js.map +1 -0
- package/dist/cli/commands/status.js +5 -1
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/tool.js +141 -0
- package/dist/cli/commands/tool.js.map +1 -0
- package/dist/cli/help-groups.js +7 -1
- package/dist/cli/help-groups.js.map +1 -1
- package/dist/cli/main.js +189 -57
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/parser.js +5 -1
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/render.js +15 -2
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/tui/components/host-admin-section.js +2 -46
- package/dist/cli/tui/components/host-admin-section.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab-helpers.js +71 -0
- package/dist/cli/tui/tabs/mcp-tab-helpers.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab.js +101 -42
- package/dist/cli/tui/tabs/mcp-tab.js.map +1 -1
- package/dist/domain/database-tools.js +445 -0
- package/dist/domain/database-tools.js.map +1 -0
- package/dist/domain/effective-contract.js +10 -2
- package/dist/domain/effective-contract.js.map +1 -1
- package/dist/domain/flow/answer.js +4 -0
- package/dist/domain/flow/answer.js.map +1 -1
- package/dist/domain/flow/authority.js +103 -14
- package/dist/domain/flow/authority.js.map +1 -1
- package/dist/domain/flow/directive.js +2 -0
- package/dist/domain/flow/directive.js.map +1 -1
- package/dist/domain/flow/run-state.js +42 -0
- package/dist/domain/flow/run-state.js.map +1 -1
- package/dist/domain/lineage.js +38 -11
- package/dist/domain/lineage.js.map +1 -1
- package/dist/domain/mcp-entry.js +131 -17
- package/dist/domain/mcp-entry.js.map +1 -1
- package/dist/domain/postgres-object-search.js +320 -0
- package/dist/domain/postgres-object-search.js.map +1 -0
- package/dist/domain/redaction.js +47 -0
- package/dist/domain/redaction.js.map +1 -0
- package/dist/domain/workline-mcp-entry.js +6 -9
- package/dist/domain/workline-mcp-entry.js.map +1 -1
- package/dist/ports/postgres-tools.js +10 -0
- package/dist/ports/postgres-tools.js.map +1 -0
- package/package.json +5 -1
- package/skills/w/commands/plan-exec.md +1 -1
- package/skills/w/commands/spec-new.md +6 -5
- package/skills/w/context/MANIFEST.json +1 -1
- package/skills/w/harness/HARNESS.md +1 -1
- package/skills/w/hooks/README.md +13 -9
- package/skills/w/hooks/hooks.template.json +2 -2
- package/skills/w/loops/CHASSIS.md +1 -1
- package/skills/w/loops/plan-exec-loop/LOOP.md +7 -5
- package/skills/w/loops/plan-new-loop/LOOP.md +46 -41
- package/skills/w/loops/plan-refine-loop/LOOP.md +4 -4
- package/skills/w/loops/quick-loop/LOOP.md +12 -13
- package/skills/w/loops/spec-refine-loop/LOOP.md +7 -3
- package/skills/w/modules/COMPACTION.md +1 -1
- package/skills/w/modules/PLAN-INPUT.md +3 -3
- package/skills/w/modules/PROMPT-CONTINUITY.md +1 -1
- package/dist/application/mcp-dbhub-launcher.js +0 -216
- package/dist/application/mcp-dbhub-launcher.js.map +0 -1
- package/dist/application/self/mcp-offer.js +0 -96
- package/dist/application/self/mcp-offer.js.map +0 -1
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { MCP_FILE_HOSTS, harnessForMcpHost } from "../../domain/harnesses.js";
|
|
3
3
|
import { buildMcpEntry, mcpEntryNameFor, normalizeDsnVarName, validateDsnVarName, validateMcpInstance, } from "../../domain/mcp-entry.js";
|
|
4
4
|
import { dsnKeyForInstance, readDsnFile } from "../dsn-reader-service.js";
|
|
5
|
-
import { deleteMcpConnection, readMcpConnections, upsertMcpConnection, } from "../mcp-connections-service.js";
|
|
5
|
+
import { McpConnectionsError, deleteMcpConnection, readMcpConnections, upsertMcpConnection, } from "../mcp-connections-service.js";
|
|
6
6
|
import { runMcpDoctor } from "../mcp-doctor-service.js";
|
|
7
|
+
import { classifyMcpEntry } from "../mcp-entry-classification.js";
|
|
7
8
|
import { readMcpEntry } from "../mcp-host-reader.js";
|
|
9
|
+
import { McpHostReceiptError, digestMcpReceiptDescriptor, parseMcpHostReceiptBook, } from "../mcp-host-receipt-service.js";
|
|
10
|
+
import { mcpHostReceiptFile } from "../mcp-host-receipt-store.js";
|
|
11
|
+
import { probePersistedMcpSetupEntries } from "../mcp-launch-probe-service.js";
|
|
12
|
+
import { checkNativeMcpHosts, recordNativeMcpHostChecks, } from "../mcp-native-host-check-service.js";
|
|
13
|
+
import { registerMcpSetupReceipts } from "../mcp-receipt-registration-service.js";
|
|
14
|
+
import { removeMcpRemoveReceipts } from "../mcp-receipt-removal-service.js";
|
|
8
15
|
import { runMcpRemove } from "../mcp-remove-service.js";
|
|
9
16
|
import { runMcpSetup } from "../mcp-setup-service.js";
|
|
10
17
|
import { buildWarpPostInstallHint, } from "../mcp-warp-postinstall-hint.js";
|
|
@@ -33,26 +40,39 @@ function isPromptSeparator(choice) {
|
|
|
33
40
|
export async function selfMcpConfig(args, ctx, prompts) {
|
|
34
41
|
const prompt = prompts ?? (await loadPrompts());
|
|
35
42
|
const resolved = await resolveAction(args, prompt);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
try {
|
|
44
|
+
switch (resolved.action) {
|
|
45
|
+
case "list":
|
|
46
|
+
return resolved.fromArgs
|
|
47
|
+
? listConnections(ctx)
|
|
48
|
+
: await listConnectionsMenu(args, ctx, prompt);
|
|
49
|
+
case "use-env":
|
|
50
|
+
return await useExistingDsnVar(args, ctx, prompt);
|
|
51
|
+
case "create-env":
|
|
52
|
+
return await createDsnEnvHelp(args, prompt);
|
|
53
|
+
case "cancel":
|
|
54
|
+
return {
|
|
55
|
+
ok: true,
|
|
56
|
+
data: { action: "cancel", connection: null, summary: "Operación cancelada." },
|
|
57
|
+
exitCode: 0,
|
|
58
|
+
};
|
|
59
|
+
default:
|
|
60
|
+
// install-<host> | doctor | remove — all operate on a registered connection.
|
|
61
|
+
return await runConnectionAction(args, ctx, prompt, resolved.action);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error instanceof McpConnectionsError) {
|
|
66
|
+
return invalidRegistryResult(resolved.action, mcpRegistryIssue(ctx));
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
52
69
|
}
|
|
53
70
|
}
|
|
54
71
|
function listConnections(ctx) {
|
|
55
|
-
const
|
|
72
|
+
const views = connectionViews(ctx);
|
|
73
|
+
if (views.kind === "invalid")
|
|
74
|
+
return invalidRegistryResult("list", views.issue);
|
|
75
|
+
const { connections } = views;
|
|
56
76
|
return {
|
|
57
77
|
ok: true,
|
|
58
78
|
data: {
|
|
@@ -68,7 +88,10 @@ function listConnections(ctx) {
|
|
|
68
88
|
};
|
|
69
89
|
}
|
|
70
90
|
async function listConnectionsMenu(args, ctx, prompts) {
|
|
71
|
-
const
|
|
91
|
+
const views = connectionViews(ctx);
|
|
92
|
+
if (views.kind === "invalid")
|
|
93
|
+
return invalidRegistryResult("list", views.issue);
|
|
94
|
+
const { connections } = views;
|
|
72
95
|
if (connections.length === 0) {
|
|
73
96
|
return {
|
|
74
97
|
ok: true,
|
|
@@ -136,15 +159,19 @@ async function useExistingDsnVar(args, ctx, prompts) {
|
|
|
136
159
|
const connection = connectionView(ctx, {
|
|
137
160
|
name: write.connection.name,
|
|
138
161
|
dsnVar: write.connection.dsnVar,
|
|
162
|
+
provider: write.connection.provider,
|
|
139
163
|
dsnPresent: true,
|
|
140
164
|
});
|
|
165
|
+
const views = connectionViews(ctx);
|
|
166
|
+
if (views.kind === "invalid")
|
|
167
|
+
return invalidRegistryResult("use-env", views.issue);
|
|
141
168
|
return {
|
|
142
169
|
ok: true,
|
|
143
170
|
data: {
|
|
144
171
|
action: "use-env",
|
|
145
172
|
connection,
|
|
146
|
-
connections:
|
|
147
|
-
table: formatConnectionsTable(
|
|
173
|
+
connections: views.connections,
|
|
174
|
+
table: formatConnectionsTable(views.connections),
|
|
148
175
|
registry: { path: write.path, changed: true },
|
|
149
176
|
summary: `Conexión '${connection.nombre}' registrada con ${connection.dsn_var}.`,
|
|
150
177
|
},
|
|
@@ -166,8 +193,10 @@ async function createDsnEnvHelp(args, prompts) {
|
|
|
166
193
|
};
|
|
167
194
|
}
|
|
168
195
|
async function runConnectionAction(args, ctx, prompts, action) {
|
|
169
|
-
const
|
|
170
|
-
if (
|
|
196
|
+
const resolved = await resolveRegisteredConnection(args, ctx, prompts);
|
|
197
|
+
if (resolved.kind === "invalid")
|
|
198
|
+
return invalidRegistryResult(action, resolved.issue);
|
|
199
|
+
if (resolved.kind === "none") {
|
|
171
200
|
return {
|
|
172
201
|
ok: false,
|
|
173
202
|
error: {
|
|
@@ -178,6 +207,7 @@ async function runConnectionAction(args, ctx, prompts, action) {
|
|
|
178
207
|
exitCode: 1,
|
|
179
208
|
};
|
|
180
209
|
}
|
|
210
|
+
const { connection } = resolved;
|
|
181
211
|
switch (action) {
|
|
182
212
|
case "doctor":
|
|
183
213
|
return doctorConnection(ctx, connection);
|
|
@@ -188,20 +218,25 @@ async function runConnectionAction(args, ctx, prompts, action) {
|
|
|
188
218
|
return installConnection(args, ctx, connection, action.slice("install-".length));
|
|
189
219
|
}
|
|
190
220
|
}
|
|
191
|
-
function installConnection(args, ctx, connection, host) {
|
|
221
|
+
async function installConnection(args, ctx, connection, host) {
|
|
192
222
|
// The explicit install action (CLI/TUI) carries its own narrow global consent;
|
|
193
223
|
// it is not the public broad `--force` escape hatch.
|
|
194
|
-
const
|
|
224
|
+
const setupInput = {
|
|
195
225
|
hosts: [host],
|
|
196
226
|
connections: [connection],
|
|
227
|
+
namespace: ctx.paths.namespace,
|
|
197
228
|
scope: "global",
|
|
198
229
|
globalApproval: "explicit-self-action",
|
|
199
230
|
dryRun: args.flags.has("--dry-run"),
|
|
200
|
-
}
|
|
231
|
+
};
|
|
232
|
+
const setup = await runGlobalSetupWithEvidence(ctx, setupInput);
|
|
201
233
|
if ("ok" in setup)
|
|
202
234
|
return refusal(hostAction(host), connectionView(ctx, connection), setup.hint);
|
|
203
235
|
const doctor = runDoctor(ctx, connection, [host]);
|
|
204
236
|
const hasProblems = setup.errors.length > 0 || setup.conflicts.length > 0;
|
|
237
|
+
const views = connectionViews(ctx);
|
|
238
|
+
if (views.kind === "invalid")
|
|
239
|
+
return invalidRegistryResult(hostAction(host), views.issue);
|
|
205
240
|
// The hint cites the file actually written (per-platform global path).
|
|
206
241
|
const warpTarget = [...setup.applied, ...setup.skipped].find((r) => r.host === "warp")?.target;
|
|
207
242
|
const warpHint = host === "warp" && !hasProblems && warpTarget
|
|
@@ -212,8 +247,8 @@ function installConnection(args, ctx, connection, host) {
|
|
|
212
247
|
data: {
|
|
213
248
|
action: hostAction(host),
|
|
214
249
|
connection: connectionView(ctx, connection),
|
|
215
|
-
connections:
|
|
216
|
-
table: formatConnectionsTable(
|
|
250
|
+
connections: views.connections,
|
|
251
|
+
table: formatConnectionsTable(views.connections),
|
|
217
252
|
setup,
|
|
218
253
|
doctor,
|
|
219
254
|
...(warpHint ? { warp_hint: warpHint } : {}),
|
|
@@ -232,6 +267,49 @@ function installConnection(args, ctx, connection, host) {
|
|
|
232
267
|
exitCode: hasProblems ? 1 : 0,
|
|
233
268
|
};
|
|
234
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* The self wizard writes user-scope descriptors, so it must finish the same
|
|
272
|
+
* durable lifecycle as `mcp setup --global`: receipt, exact persisted launch
|
|
273
|
+
* probe, then native host visibility. Keeping that sequence behind one local
|
|
274
|
+
* boundary prevents a future wizard branch from treating a file write as host
|
|
275
|
+
* readiness.
|
|
276
|
+
*/
|
|
277
|
+
async function runGlobalSetupWithEvidence(ctx, input) {
|
|
278
|
+
const setup = runMcpSetup(ctx.env, input);
|
|
279
|
+
if ("ok" in setup)
|
|
280
|
+
return setup;
|
|
281
|
+
const receiptRegistration = await registerMcpSetupReceipts(ctx.paths, input, setup);
|
|
282
|
+
const withReceipts = {
|
|
283
|
+
...setup,
|
|
284
|
+
errors: [...setup.errors, ...receiptRegistration.errors],
|
|
285
|
+
...(receiptRegistration.registered.length === 0
|
|
286
|
+
? {}
|
|
287
|
+
: { receipts: receiptRegistration.registered }),
|
|
288
|
+
};
|
|
289
|
+
const launch = await probePersistedMcpSetupEntries(ctx.paths, withReceipts, receiptRegistration.probeTargets);
|
|
290
|
+
const native = await checkNativeMcpHosts(receiptRegistration.probeTargets);
|
|
291
|
+
await recordNativeMcpHostChecks({
|
|
292
|
+
paths: ctx.paths,
|
|
293
|
+
scope: withReceipts.scope,
|
|
294
|
+
scopeDir: withReceipts.scope_dir,
|
|
295
|
+
targets: receiptRegistration.probeTargets,
|
|
296
|
+
checks: native.checks,
|
|
297
|
+
});
|
|
298
|
+
if (receiptRegistration.registered.length === 0 &&
|
|
299
|
+
receiptRegistration.errors.length === 0 &&
|
|
300
|
+
launch.probes.length === 0 &&
|
|
301
|
+
launch.errors.length === 0 &&
|
|
302
|
+
native.checks.length === 0 &&
|
|
303
|
+
native.errors.length === 0) {
|
|
304
|
+
return setup;
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
...withReceipts,
|
|
308
|
+
errors: [...withReceipts.errors, ...launch.errors, ...native.errors],
|
|
309
|
+
...(launch.probes.length === 0 ? {} : { launch_probes: launch.probes }),
|
|
310
|
+
...(native.checks.length === 0 ? {} : { native_checks: native.checks }),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
235
313
|
function doctorConnection(ctx, connection) {
|
|
236
314
|
const doctor = runDoctor(ctx, connection, [...FILE_HOSTS]);
|
|
237
315
|
const allOk = doctor.summary.ok === doctor.reports.length;
|
|
@@ -254,85 +332,307 @@ function doctorConnection(ctx, connection) {
|
|
|
254
332
|
exitCode: allOk ? 0 : 1,
|
|
255
333
|
};
|
|
256
334
|
}
|
|
257
|
-
function removeConnection(args, ctx, connection) {
|
|
335
|
+
async function removeConnection(args, ctx, connection) {
|
|
258
336
|
const dryRun = args.flags.has("--dry-run");
|
|
259
337
|
// The explicit remove action carries its own narrow global consent.
|
|
260
338
|
// The writer verifies exact ownership per host and returns a conflict for an
|
|
261
339
|
// homonymous foreign entry, so this never has to infer ownership from a word
|
|
262
340
|
// in the command line.
|
|
263
|
-
const
|
|
341
|
+
const removeInput = {
|
|
264
342
|
hosts: [...FILE_HOSTS],
|
|
265
343
|
connections: [connection],
|
|
344
|
+
namespace: ctx.paths.namespace,
|
|
266
345
|
scope: "global",
|
|
267
346
|
globalApproval: "explicit-self-action",
|
|
268
347
|
dryRun,
|
|
269
|
-
}
|
|
348
|
+
};
|
|
349
|
+
const remove = runMcpRemove(ctx.env, removeInput);
|
|
270
350
|
if ("ok" in remove)
|
|
271
351
|
return refusal("remove", connectionView(ctx, connection), remove.hint);
|
|
272
|
-
const
|
|
273
|
-
const
|
|
352
|
+
const receiptErrors = await removeMcpRemoveReceipts(ctx.paths, removeInput, remove);
|
|
353
|
+
const removeWithReceipts = {
|
|
354
|
+
...remove,
|
|
355
|
+
errors: [...remove.errors, ...receiptErrors],
|
|
356
|
+
};
|
|
357
|
+
const hasProblems = removeWithReceipts.errors.length > 0 || removeWithReceipts.conflicts.length > 0;
|
|
358
|
+
const preservedForeign = [
|
|
359
|
+
...new Set(removeWithReceipts.conflicts.map((conflict) => conflict.host)),
|
|
360
|
+
];
|
|
274
361
|
const deleted = !dryRun && !hasProblems ? deleteMcpConnection(ctx.paths, connection) : null;
|
|
275
362
|
const preservedNote = preservedForeign.length > 0
|
|
276
363
|
? ` Se conservó la entrada ajena homónima en: ${preservedForeign.join(", ")}.`
|
|
277
364
|
: "";
|
|
365
|
+
const reloadNotice = removalReloadNotice(removeWithReceipts);
|
|
366
|
+
const views = connectionViews(ctx);
|
|
367
|
+
if (views.kind === "invalid")
|
|
368
|
+
return invalidRegistryResult("remove", views.issue);
|
|
278
369
|
return {
|
|
279
370
|
ok: !hasProblems,
|
|
280
371
|
data: {
|
|
281
372
|
action: "remove",
|
|
282
373
|
connection: connectionView(ctx, connection),
|
|
283
|
-
connections:
|
|
284
|
-
table: formatConnectionsTable(
|
|
285
|
-
remove,
|
|
374
|
+
connections: views.connections,
|
|
375
|
+
table: formatConnectionsTable(views.connections),
|
|
376
|
+
remove: removeWithReceipts,
|
|
286
377
|
...(preservedForeign.length > 0 ? { preserved_foreign: preservedForeign } : {}),
|
|
287
378
|
...(deleted ? { registry: { path: deleted.path, changed: deleted.removed } } : {}),
|
|
288
379
|
summary: dryRun
|
|
289
380
|
? `Previsualización de eliminación para '${connection.name}'.${preservedNote}`
|
|
290
381
|
: hasProblems
|
|
291
|
-
? `Eliminación parcial de '${connection.name}'.${preservedNote}`
|
|
292
|
-
: `Conexión '${connection.name}' eliminada de los hosts con MCP y del registro local.${preservedNote}`,
|
|
382
|
+
? `Eliminación parcial de '${connection.name}'.${preservedNote}${reloadNotice}`
|
|
383
|
+
: `Conexión '${connection.name}' eliminada de los hosts con MCP y del registro local.${preservedNote}${reloadNotice}`,
|
|
293
384
|
},
|
|
294
385
|
...(hasProblems
|
|
295
386
|
? {
|
|
296
387
|
error: {
|
|
297
388
|
code: "MCP_REMOVE_PARTIAL",
|
|
298
|
-
message: `${
|
|
389
|
+
message: `${removeWithReceipts.errors.length} error(es) y ${removeWithReceipts.conflicts.length} conflicto(s) durante remove; ver data.remove.errors y data.remove.conflicts`,
|
|
299
390
|
},
|
|
300
391
|
}
|
|
301
392
|
: {}),
|
|
302
393
|
exitCode: hasProblems ? 1 : 0,
|
|
303
394
|
};
|
|
304
395
|
}
|
|
396
|
+
function removalReloadNotice(remove) {
|
|
397
|
+
const requirements = remove.reload_required ?? [];
|
|
398
|
+
if (requirements.length === 0)
|
|
399
|
+
return "";
|
|
400
|
+
return ` Recarga requerida: ${requirements
|
|
401
|
+
.map((requirement) => `${hostLabel(requirement.host)}: ${requirement.next_step}`)
|
|
402
|
+
.join(" ")}`;
|
|
403
|
+
}
|
|
305
404
|
function runDoctor(ctx, connection, hosts) {
|
|
306
405
|
return runMcpDoctor(ctx.env, ctx.paths, {
|
|
307
406
|
hosts,
|
|
308
407
|
connections: [connection],
|
|
408
|
+
namespace: ctx.paths.namespace,
|
|
309
409
|
scope: "global",
|
|
310
410
|
});
|
|
311
411
|
}
|
|
312
412
|
function connectionViews(ctx) {
|
|
313
|
-
|
|
413
|
+
try {
|
|
414
|
+
const receiptLedger = readHostReceipts(ctx);
|
|
415
|
+
return {
|
|
416
|
+
kind: "ok",
|
|
417
|
+
connections: readMcpConnections(ctx.paths, ctx.env).map((connection) => connectionView(ctx, connection, receiptLedger)),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
if (error instanceof McpConnectionsError) {
|
|
422
|
+
return { kind: "invalid", issue: mcpRegistryIssue(ctx) };
|
|
423
|
+
}
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function mcpRegistryIssue(ctx) {
|
|
428
|
+
return {
|
|
429
|
+
code: "MCP_CONNECTION_INVALID",
|
|
430
|
+
path: ctx.paths.userMcpConnectionsFile(),
|
|
431
|
+
recovery: "Restaurá mcp-connections.json desde una copia válida o corregí su forma antes de volver a operar.",
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function invalidRegistryResult(action, issue) {
|
|
435
|
+
return {
|
|
436
|
+
ok: false,
|
|
437
|
+
error: {
|
|
438
|
+
code: issue.code,
|
|
439
|
+
message: "El registro MCP local no es válido y requiere reparación antes de operar.",
|
|
440
|
+
},
|
|
441
|
+
data: {
|
|
442
|
+
action,
|
|
443
|
+
connection: null,
|
|
444
|
+
connections: [],
|
|
445
|
+
table: formatConnectionsTable([]),
|
|
446
|
+
registry_error: issue,
|
|
447
|
+
summary: issue.recovery,
|
|
448
|
+
},
|
|
449
|
+
exitCode: 2,
|
|
450
|
+
};
|
|
314
451
|
}
|
|
315
|
-
function connectionView(ctx, connection) {
|
|
452
|
+
function connectionView(ctx, connection, receiptLedger = readHostReceipts(ctx)) {
|
|
453
|
+
const hostStatus = Object.fromEntries(FILE_HOSTS.map((host) => [host, installStatus(ctx, connection, host, receiptLedger)]));
|
|
316
454
|
return {
|
|
317
455
|
nombre: connection.name,
|
|
318
456
|
server_name: mcpEntryNameFor(connection.name),
|
|
319
457
|
dsn_var: connection.dsnVar,
|
|
320
458
|
dsn_visible: isDsnVisible(ctx, connection.dsnVar),
|
|
321
|
-
instalado: Object.fromEntries(FILE_HOSTS.map((
|
|
459
|
+
instalado: Object.fromEntries(FILE_HOSTS.map((host) => [host, installMark(hostStatus[host])])),
|
|
460
|
+
host_status: hostStatus,
|
|
322
461
|
};
|
|
323
462
|
}
|
|
324
|
-
|
|
325
|
-
|
|
463
|
+
/**
|
|
464
|
+
* Status must be derived from the exact descriptor that this host would load,
|
|
465
|
+
* not from a host-agnostic default. Global descriptors carry the resolved
|
|
466
|
+
* namespace and host identity in their argv, so both are part of ownership.
|
|
467
|
+
*/
|
|
468
|
+
function installStatus(ctx, connection, host, receiptLedger) {
|
|
469
|
+
const entry = buildMcpEntry(connection.name, connection.dsnVar, {
|
|
470
|
+
host,
|
|
471
|
+
scope: "global",
|
|
472
|
+
namespace: ctx.paths.namespace,
|
|
473
|
+
});
|
|
326
474
|
const snapshot = readMcpEntry(host, ctx.env.homeDir(), entry.name, "global");
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
return
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
475
|
+
const classification = classifyMcpEntry(host, snapshot, entry, connection);
|
|
476
|
+
const ledgerFailure = receiptLedgerFailureStatus(classification.state, receiptLedger);
|
|
477
|
+
if (ledgerFailure !== undefined)
|
|
478
|
+
return ledgerFailure;
|
|
479
|
+
const entryStatus = nonCurrentEntryStatus(classification.state);
|
|
480
|
+
if (entryStatus !== undefined)
|
|
481
|
+
return entryStatus;
|
|
482
|
+
const receipt = receiptLedger.receipts.find((candidate) => candidate.host === host &&
|
|
483
|
+
candidate.scope === "global" &&
|
|
484
|
+
candidate.connection === connection.name);
|
|
485
|
+
if (receipt !== undefined && !receiptMatchesDescriptor(receipt, snapshot)) {
|
|
486
|
+
return staleReceiptStatus(classification.state);
|
|
487
|
+
}
|
|
488
|
+
return currentDescriptorStatus(classification.state, receipt);
|
|
489
|
+
}
|
|
490
|
+
function receiptLedgerFailureStatus(entryState, receiptLedger) {
|
|
491
|
+
if (receiptLedger.failure === undefined)
|
|
492
|
+
return undefined;
|
|
493
|
+
return {
|
|
494
|
+
state: "failed",
|
|
495
|
+
entry_state: entryState,
|
|
496
|
+
launchable: false,
|
|
497
|
+
reload_required: false,
|
|
498
|
+
receipt_failure: { phase: "receipt", code: receiptLedger.failure.code },
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function nonCurrentEntryStatus(entryState) {
|
|
502
|
+
switch (entryState) {
|
|
503
|
+
case "missing":
|
|
504
|
+
return {
|
|
505
|
+
state: "registered",
|
|
506
|
+
entry_state: entryState,
|
|
507
|
+
launchable: false,
|
|
508
|
+
reload_required: false,
|
|
509
|
+
};
|
|
510
|
+
case "known-legacy":
|
|
511
|
+
return {
|
|
512
|
+
state: "legacy",
|
|
513
|
+
entry_state: entryState,
|
|
514
|
+
launchable: false,
|
|
515
|
+
reload_required: true,
|
|
516
|
+
};
|
|
517
|
+
case "foreign":
|
|
518
|
+
return {
|
|
519
|
+
state: "conflict",
|
|
520
|
+
entry_state: entryState,
|
|
521
|
+
launchable: false,
|
|
522
|
+
reload_required: false,
|
|
523
|
+
};
|
|
524
|
+
case "malformed":
|
|
525
|
+
return {
|
|
526
|
+
state: "failed",
|
|
527
|
+
entry_state: entryState,
|
|
528
|
+
launchable: false,
|
|
529
|
+
reload_required: false,
|
|
530
|
+
};
|
|
531
|
+
case "current":
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function staleReceiptStatus(entryState) {
|
|
536
|
+
return {
|
|
537
|
+
state: "failed",
|
|
538
|
+
entry_state: entryState,
|
|
539
|
+
launchable: false,
|
|
540
|
+
reload_required: true,
|
|
541
|
+
receipt_failure: { phase: "descriptor", code: "MCP_RECEIPT_DESCRIPTOR_STALE" },
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function currentDescriptorStatus(entryState, receipt) {
|
|
545
|
+
// A current descriptor without a receipt may be the aftermath of a busy
|
|
546
|
+
// receipt ledger after the host file was already written. It has no durable
|
|
547
|
+
// descriptor digest, lifecycle evidence, or way for a later host reload to
|
|
548
|
+
// clear reload_required, so presenting it as merely configured would hide
|
|
549
|
+
// the required recovery action.
|
|
550
|
+
if (receipt === undefined) {
|
|
551
|
+
return {
|
|
552
|
+
state: "failed",
|
|
553
|
+
entry_state: entryState,
|
|
554
|
+
launchable: false,
|
|
555
|
+
reload_required: true,
|
|
556
|
+
receipt_failure: { phase: "receipt", code: "MCP_RECEIPT_NOT_FOUND" },
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
const lastProbe = receipt?.last_launch_probe;
|
|
560
|
+
const nativeCheckFailure = receipt?.last_native_check_failure;
|
|
561
|
+
const launchable = lastProbe?.outcome === "passed";
|
|
562
|
+
const reloadRequired = receipt?.reload_required ?? false;
|
|
563
|
+
const evidence = {
|
|
564
|
+
entry_state: entryState,
|
|
565
|
+
launchable,
|
|
566
|
+
reload_required: reloadRequired,
|
|
567
|
+
...(lastProbe === undefined
|
|
568
|
+
? {}
|
|
569
|
+
: {
|
|
570
|
+
last_probe: {
|
|
571
|
+
outcome: lastProbe.outcome,
|
|
572
|
+
phase: lastProbe.phase,
|
|
573
|
+
observed_at: lastProbe.observed_at,
|
|
574
|
+
},
|
|
575
|
+
}),
|
|
576
|
+
...(receipt?.last_host_load_observed === undefined
|
|
577
|
+
? {}
|
|
578
|
+
: { last_host_load_observed: receipt.last_host_load_observed.observed_at }),
|
|
579
|
+
...(nativeCheckFailure === undefined
|
|
580
|
+
? {}
|
|
581
|
+
: {
|
|
582
|
+
native_check_failure: {
|
|
583
|
+
code: nativeCheckFailure.code,
|
|
584
|
+
observed_at: nativeCheckFailure.observed_at,
|
|
585
|
+
},
|
|
586
|
+
}),
|
|
587
|
+
};
|
|
588
|
+
if (lastProbe?.outcome === "failed" || nativeCheckFailure !== undefined) {
|
|
589
|
+
return { state: "failed", ...evidence };
|
|
590
|
+
}
|
|
591
|
+
if (receipt?.last_host_load_observed !== undefined && !reloadRequired) {
|
|
592
|
+
return { state: "host-load-observed", ...evidence };
|
|
593
|
+
}
|
|
594
|
+
if (reloadRequired)
|
|
595
|
+
return { state: "reload-required", ...evidence };
|
|
596
|
+
if (launchable)
|
|
597
|
+
return { state: "launchable", ...evidence };
|
|
598
|
+
return { state: "configured", ...evidence };
|
|
599
|
+
}
|
|
600
|
+
function receiptMatchesDescriptor(receipt, snapshot) {
|
|
601
|
+
if (snapshot.command === undefined || snapshot.args === undefined)
|
|
602
|
+
return false;
|
|
603
|
+
try {
|
|
604
|
+
return (receipt.descriptor_digest ===
|
|
605
|
+
digestMcpReceiptDescriptor({ command: snapshot.command, args: snapshot.args }));
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function installMark(status) {
|
|
612
|
+
return status.entry_state === "missing"
|
|
613
|
+
? "no"
|
|
614
|
+
: status.entry_state === "current"
|
|
615
|
+
? "si"
|
|
616
|
+
: "drift";
|
|
617
|
+
}
|
|
618
|
+
function readHostReceipts(ctx) {
|
|
619
|
+
const file = mcpHostReceiptFile(ctx.paths);
|
|
620
|
+
if (!existsSync(file))
|
|
621
|
+
return { receipts: [] };
|
|
622
|
+
try {
|
|
623
|
+
return { receipts: parseMcpHostReceiptBook(readFileSync(file, "utf8")).receipts };
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
// The registry remains readable, but no lifecycle claim survives a corrupt
|
|
627
|
+
// receipt ledger. Every host becomes explicitly actionable rather than
|
|
628
|
+
// quietly falling back to an unproven "configured" state.
|
|
629
|
+
return {
|
|
630
|
+
receipts: [],
|
|
631
|
+
failure: {
|
|
632
|
+
code: error instanceof McpHostReceiptError ? error.code : "MCP_RECEIPT_UNREADABLE",
|
|
633
|
+
},
|
|
634
|
+
};
|
|
635
|
+
}
|
|
336
636
|
}
|
|
337
637
|
export function isDsnVisible(ctx, dsnVar) {
|
|
338
638
|
if (ctx.env.get(dsnVar))
|
|
@@ -383,9 +683,18 @@ function renderBoxTable(headers, rows) {
|
|
|
383
683
|
return out.join("\n");
|
|
384
684
|
}
|
|
385
685
|
async function resolveRegisteredConnection(args, ctx, prompts) {
|
|
386
|
-
|
|
686
|
+
let connections;
|
|
687
|
+
try {
|
|
688
|
+
connections = readMcpConnections(ctx.paths, ctx.env);
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
if (error instanceof McpConnectionsError) {
|
|
692
|
+
return { kind: "invalid", issue: mcpRegistryIssue(ctx) };
|
|
693
|
+
}
|
|
694
|
+
throw error;
|
|
695
|
+
}
|
|
387
696
|
if (connections.length === 0)
|
|
388
|
-
return
|
|
697
|
+
return { kind: "none" };
|
|
389
698
|
const raw = args.values.get("name") ?? args.values.get("instance");
|
|
390
699
|
if (raw !== undefined) {
|
|
391
700
|
const validation = validateMcpInstance(raw);
|
|
@@ -395,7 +704,7 @@ async function resolveRegisteredConnection(args, ctx, prompts) {
|
|
|
395
704
|
if (found === undefined) {
|
|
396
705
|
throw new Error(`conexión MCP no registrada: '${validation.value}'`);
|
|
397
706
|
}
|
|
398
|
-
return found;
|
|
707
|
+
return { kind: "found", connection: found };
|
|
399
708
|
}
|
|
400
709
|
const defaultConnection = connections[0]?.name;
|
|
401
710
|
const selected = await prompts.select({
|
|
@@ -406,7 +715,8 @@ async function resolveRegisteredConnection(args, ctx, prompts) {
|
|
|
406
715
|
value: item.name,
|
|
407
716
|
})),
|
|
408
717
|
});
|
|
409
|
-
|
|
718
|
+
const connection = connections.find((item) => item.name === selected);
|
|
719
|
+
return connection === undefined ? { kind: "none" } : { kind: "found", connection };
|
|
410
720
|
}
|
|
411
721
|
async function resolveConnectionName(args, prompts) {
|
|
412
722
|
const raw = args.values.get("name") ?? args.values.get("instance");
|
|
@@ -504,7 +814,7 @@ async function resolveAction(args, prompts) {
|
|
|
504
814
|
return { action: raw, fromArgs: true };
|
|
505
815
|
return {
|
|
506
816
|
action: await prompts.select({
|
|
507
|
-
message: "Configurar MCP database (
|
|
817
|
+
message: "Configurar MCP database (PostgreSQL Workline)",
|
|
508
818
|
default: "list",
|
|
509
819
|
choices: [
|
|
510
820
|
{ type: "separator", separator: "── Conexiones existentes ──" },
|