@skein-code/cli 0.3.26 → 0.3.27
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 +22 -7
- package/dist/cli.js +991 -117
- package/dist/cli.js.map +1 -1
- package/docs/ARCHITECTURE.md +34 -18
- package/docs/NEXT_STEPS.md +33 -23
- package/docs/PRODUCT_BENCHMARK.md +5 -5
- package/examples/config.yaml +30 -0
- package/package.json +8 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
5
5
|
import { stdin as input, stdout as output } from "node:process";
|
|
6
6
|
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
7
|
-
import { basename as
|
|
7
|
+
import { basename as basename14, resolve as resolve27 } from "node:path";
|
|
8
8
|
import { Command, Option } from "commander";
|
|
9
9
|
import chalk4 from "chalk";
|
|
10
10
|
|
|
@@ -220,7 +220,7 @@ function isSqliteBusy(error) {
|
|
|
220
220
|
// package.json
|
|
221
221
|
var package_default = {
|
|
222
222
|
name: "@skein-code/cli",
|
|
223
|
-
version: "0.3.
|
|
223
|
+
version: "0.3.27",
|
|
224
224
|
description: "A context-first, model-agnostic coding agent with an auditable terminal workspace.",
|
|
225
225
|
type: "module",
|
|
226
226
|
license: "MIT",
|
|
@@ -236,6 +236,13 @@ var package_default = {
|
|
|
236
236
|
},
|
|
237
237
|
skein: {
|
|
238
238
|
releaseNotes: [
|
|
239
|
+
"MCP now follows a no-network search and inspect review before explicit workspace-bound manifest trust and lazy activation",
|
|
240
|
+
"Declarative capability manifests expose redacted source, version, tools, permissions, network, command, path, sensitive-field, process, and completion-evidence effects",
|
|
241
|
+
"Persistent disable and revoke unload active remote schemas while manifest fingerprint changes fail closed to untrusted",
|
|
242
|
+
"Server annotations cannot lower local permissions, named manifests reject injected undeclared schemas, and optional server failures remain isolated",
|
|
243
|
+
"Only explicitly required trusted MCP servers may block startup, while status inspection no longer connects optional servers",
|
|
244
|
+
"Sensitive MCP arguments are redacted from terminal, TUI, JSON, and approval events; unsupported external mutations remain completion-unresolved",
|
|
245
|
+
"Explain and review intents expose smaller built-in schema sets, and MCP activation reports deterministic eager-versus-loaded schema estimates plus top-match evidence",
|
|
239
246
|
"First-run setup now states that the primary agent needs API credentials and that provider subscriptions or signed-in coding CLIs are separate delegated tools",
|
|
240
247
|
"Permission prompts show the policy reason, redacted target, working directory, category risk, and explicit once, session, deny, and stop choices",
|
|
241
248
|
"The Recovery Center joins last-run state, failure repair hints, changed files, checkpoints, diff, audit, rollback, retry, and safe session resume",
|
|
@@ -1955,8 +1962,22 @@ var agentTeamConfigSchema = z2.object({
|
|
|
1955
1962
|
}).partial();
|
|
1956
1963
|
var mcpServerSchema = z2.object({
|
|
1957
1964
|
enabled: z2.boolean().optional(),
|
|
1965
|
+
required: z2.boolean().optional(),
|
|
1958
1966
|
transport: z2.enum(["stdio", "http"]).optional(),
|
|
1959
1967
|
description: z2.string().min(1).max(500).optional(),
|
|
1968
|
+
version: z2.string().min(1).max(128).optional(),
|
|
1969
|
+
tools: z2.array(z2.object({
|
|
1970
|
+
name: z2.string().min(1).max(256),
|
|
1971
|
+
description: z2.string().min(1).max(500).optional(),
|
|
1972
|
+
permissions: z2.array(z2.enum(["read", "write", "shell", "git", "network"])).min(1).max(5),
|
|
1973
|
+
network: z2.array(z2.string().min(1).max(500)).max(32).optional(),
|
|
1974
|
+
commands: z2.array(z2.string().min(1).max(500)).max(32).optional(),
|
|
1975
|
+
paths: z2.array(z2.string().min(1).max(4e3)).max(64).optional(),
|
|
1976
|
+
sensitiveFields: z2.array(z2.string().min(1).max(256)).max(64).optional(),
|
|
1977
|
+
background: z2.boolean().optional(),
|
|
1978
|
+
processTree: z2.boolean().optional(),
|
|
1979
|
+
completionEvidence: z2.enum(["full", "partial", "none"]).optional()
|
|
1980
|
+
}).strict()).max(256).optional(),
|
|
1960
1981
|
command: z2.string().min(1).max(512).optional(),
|
|
1961
1982
|
args: z2.array(z2.string().max(4e3)).max(64).optional(),
|
|
1962
1983
|
cwd: z2.string().max(4e3).optional(),
|
|
@@ -1965,7 +1986,33 @@ var mcpServerSchema = z2.object({
|
|
|
1965
1986
|
headers: z2.record(z2.string(), z2.string().max(2e4)).optional(),
|
|
1966
1987
|
timeoutMs: z2.number().int().positive().max(3e5).optional(),
|
|
1967
1988
|
toolPrefix: z2.string().regex(/^[a-z][a-z0-9_-]{0,24}$/).optional()
|
|
1968
|
-
}).strict()
|
|
1989
|
+
}).strict().superRefine((server, context) => {
|
|
1990
|
+
const names = /* @__PURE__ */ new Set();
|
|
1991
|
+
for (const [index, tool] of (server.tools ?? []).entries()) {
|
|
1992
|
+
if (names.has(tool.name)) {
|
|
1993
|
+
context.addIssue({
|
|
1994
|
+
code: z2.ZodIssueCode.custom,
|
|
1995
|
+
path: ["tools", index, "name"],
|
|
1996
|
+
message: `duplicate MCP capability tool: ${tool.name}`
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
names.add(tool.name);
|
|
2000
|
+
if (tool.permissions.includes("shell") && !tool.commands?.length) {
|
|
2001
|
+
context.addIssue({
|
|
2002
|
+
code: z2.ZodIssueCode.custom,
|
|
2003
|
+
path: ["tools", index, "commands"],
|
|
2004
|
+
message: "MCP shell capability must declare command scopes"
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
if (tool.permissions.includes("write") && !tool.paths?.length) {
|
|
2008
|
+
context.addIssue({
|
|
2009
|
+
code: z2.ZodIssueCode.custom,
|
|
2010
|
+
path: ["tools", index, "paths"],
|
|
2011
|
+
message: "MCP write capability must declare path scopes"
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
1969
2016
|
var mcpConfigSchema = z2.object({
|
|
1970
2017
|
enabled: z2.boolean().optional(),
|
|
1971
2018
|
connectTimeoutMs: z2.number().int().positive().max(3e5).optional(),
|
|
@@ -3142,7 +3189,7 @@ function executableNames(command2) {
|
|
|
3142
3189
|
return [command2, ...extensions.map((extension) => `${command2}${extension}`)];
|
|
3143
3190
|
}
|
|
3144
3191
|
function runProcess(command2, args, options) {
|
|
3145
|
-
return new Promise((
|
|
3192
|
+
return new Promise((resolve28, reject) => {
|
|
3146
3193
|
const started = Date.now();
|
|
3147
3194
|
const environment = options.inheritEnv === false ? {} : { ...process.env };
|
|
3148
3195
|
for (const name of options.unsetEnv ?? []) delete environment[name];
|
|
@@ -3234,7 +3281,7 @@ function runProcess(command2, args, options) {
|
|
|
3234
3281
|
reject(callbackError);
|
|
3235
3282
|
return;
|
|
3236
3283
|
}
|
|
3237
|
-
|
|
3284
|
+
resolve28({
|
|
3238
3285
|
command: [command2, ...args].join(" "),
|
|
3239
3286
|
exitCode: code ?? (timedOut ? 124 : 1),
|
|
3240
3287
|
stdout,
|
|
@@ -5569,12 +5616,12 @@ function rankMentionSuggestions(candidates, partial, limit = 6) {
|
|
|
5569
5616
|
}
|
|
5570
5617
|
function rankMention(path, needle) {
|
|
5571
5618
|
const lower = normalizeForMatch(path);
|
|
5572
|
-
const
|
|
5619
|
+
const basename15 = lower.slice(lower.lastIndexOf("/") + 1);
|
|
5573
5620
|
const segments = lower.split("/");
|
|
5574
5621
|
if (!needle) return segments.length * 100 + lower.length;
|
|
5575
5622
|
if (lower === needle) return 0;
|
|
5576
|
-
if (
|
|
5577
|
-
if (
|
|
5623
|
+
if (basename15 === needle) return 10 + lower.length;
|
|
5624
|
+
if (basename15.startsWith(needle)) return 100 + basename15.length + lower.length / 1e3;
|
|
5578
5625
|
if (segments.some((segment) => segment.startsWith(needle))) {
|
|
5579
5626
|
return 200 + lower.indexOf(needle) + lower.length / 1e3;
|
|
5580
5627
|
}
|
|
@@ -6321,11 +6368,11 @@ var CheckpointStore = class {
|
|
|
6321
6368
|
}
|
|
6322
6369
|
async capture(sessionId, paths, options = {}) {
|
|
6323
6370
|
validateIdentifier(sessionId, "session");
|
|
6324
|
-
const
|
|
6325
|
-
if (!
|
|
6326
|
-
return this.withManagedLease(() => this.captureUnlocked(sessionId,
|
|
6371
|
+
const unique4 = [...new Set(paths)];
|
|
6372
|
+
if (!unique4.length) return void 0;
|
|
6373
|
+
return this.withManagedLease(() => this.captureUnlocked(sessionId, unique4, options));
|
|
6327
6374
|
}
|
|
6328
|
-
async captureUnlocked(sessionId,
|
|
6375
|
+
async captureUnlocked(sessionId, unique4, options) {
|
|
6329
6376
|
const id = `${Date.now().toString(36)}-${randomUUID9()}`;
|
|
6330
6377
|
const target = join9(this.directory, sessionId, id);
|
|
6331
6378
|
const blobDirectory = join9(target, "blobs");
|
|
@@ -6334,7 +6381,7 @@ var CheckpointStore = class {
|
|
|
6334
6381
|
await mkdir7(blobDirectory, { recursive: true });
|
|
6335
6382
|
await this.assertManagedPath(blobDirectory);
|
|
6336
6383
|
const entries = [];
|
|
6337
|
-
for (const input2 of
|
|
6384
|
+
for (const input2 of unique4) {
|
|
6338
6385
|
const path = await this.workspace.resolvePath(input2, { allowMissing: true });
|
|
6339
6386
|
try {
|
|
6340
6387
|
const info = await lstat10(path);
|
|
@@ -8613,6 +8660,16 @@ var ToolRegistry = class {
|
|
|
8613
8660
|
has(name) {
|
|
8614
8661
|
return this.tools.has(name) || this.aliases.has(name);
|
|
8615
8662
|
}
|
|
8663
|
+
/** Remove one exact tool instance without letting extensions evict core tools. */
|
|
8664
|
+
unregister(name, expected) {
|
|
8665
|
+
const current = this.tools.get(name);
|
|
8666
|
+
if (!current || expected && current !== expected) return false;
|
|
8667
|
+
this.tools.delete(name);
|
|
8668
|
+
for (const [alias, target] of this.aliases) {
|
|
8669
|
+
if (target === name) this.aliases.delete(alias);
|
|
8670
|
+
}
|
|
8671
|
+
return true;
|
|
8672
|
+
}
|
|
8616
8673
|
list() {
|
|
8617
8674
|
return [...this.tools.values()];
|
|
8618
8675
|
}
|
|
@@ -11495,6 +11552,7 @@ ${resolvedInput.decision}
|
|
|
11495
11552
|
this.session.audit ?? [],
|
|
11496
11553
|
this.session.duplicationSuppressions ?? []
|
|
11497
11554
|
).size > 0,
|
|
11555
|
+
turnDirective.intent,
|
|
11498
11556
|
request,
|
|
11499
11557
|
loadedProgressiveTools
|
|
11500
11558
|
);
|
|
@@ -11791,7 +11849,8 @@ ${input2}`
|
|
|
11791
11849
|
}
|
|
11792
11850
|
await this.persist();
|
|
11793
11851
|
throwIfAborted(options.signal);
|
|
11794
|
-
|
|
11852
|
+
const displayCall = redactToolCallForDisplay(call, tool.definition.sensitiveFields ?? []);
|
|
11853
|
+
await emit({ type: "tool_start", call: displayCall, category: tool.definition.category });
|
|
11795
11854
|
const executionContext = {
|
|
11796
11855
|
config: this.config,
|
|
11797
11856
|
workspace: this.workspace,
|
|
@@ -11981,13 +12040,17 @@ ${completeContent}`;
|
|
|
11981
12040
|
this.recordPermission(call, category, "allow", "Approved for this session.");
|
|
11982
12041
|
return true;
|
|
11983
12042
|
}
|
|
11984
|
-
|
|
12043
|
+
const displayCall = redactToolCallForDisplay(
|
|
12044
|
+
call,
|
|
12045
|
+
this.tools.get(call.name)?.definition.sensitiveFields ?? []
|
|
12046
|
+
);
|
|
12047
|
+
await emit({ type: "permission", call: displayCall, category, reason: decision.reason });
|
|
11985
12048
|
if (!options.requestPermission) {
|
|
11986
12049
|
this.recordPermission(call, category, "deny", "No permission handler was available.");
|
|
11987
12050
|
return false;
|
|
11988
12051
|
}
|
|
11989
12052
|
try {
|
|
11990
|
-
const grant = await options.requestPermission(
|
|
12053
|
+
const grant = await options.requestPermission(displayCall, category, decision.reason);
|
|
11991
12054
|
const allowed = grant === true || grant === "session";
|
|
11992
12055
|
if (grant === "session") this.sessionApprovals.add(approvalKey);
|
|
11993
12056
|
this.recordPermission(
|
|
@@ -12393,6 +12456,20 @@ function combineMeasurementSources(input2, output2, inputTokens, outputTokens) {
|
|
|
12393
12456
|
function uniqueCategories(categories) {
|
|
12394
12457
|
return [...new Set(categories)];
|
|
12395
12458
|
}
|
|
12459
|
+
function redactToolCallForDisplay(call, sensitiveFields) {
|
|
12460
|
+
if (!sensitiveFields.length) return call;
|
|
12461
|
+
const sensitive = new Set(sensitiveFields.map((field) => field.toLocaleLowerCase()));
|
|
12462
|
+
const redact2 = (value, path = []) => {
|
|
12463
|
+
if (Array.isArray(value)) return value.map((item, index) => redact2(item, [...path, String(index)]));
|
|
12464
|
+
if (!value || typeof value !== "object") return value;
|
|
12465
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => {
|
|
12466
|
+
const fieldPath = [...path, key].join(".").toLocaleLowerCase();
|
|
12467
|
+
const field = key.toLocaleLowerCase();
|
|
12468
|
+
return [key, sensitive.has(field) || sensitive.has(fieldPath) ? "<redacted>" : redact2(item, [...path, key])];
|
|
12469
|
+
}));
|
|
12470
|
+
};
|
|
12471
|
+
return { ...call, arguments: redact2(call.arguments) };
|
|
12472
|
+
}
|
|
12396
12473
|
function failedResult(call, content, failure) {
|
|
12397
12474
|
return {
|
|
12398
12475
|
toolCallId: call.id,
|
|
@@ -12408,9 +12485,9 @@ function failureSignatureFromMetadata(value) {
|
|
|
12408
12485
|
const signature = value.signature;
|
|
12409
12486
|
return typeof signature === "string" && signature ? signature : void 0;
|
|
12410
12487
|
}
|
|
12411
|
-
function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable, duplicationAvailable, request, loadedProgressiveTools) {
|
|
12488
|
+
function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable, duplicationAvailable, intent, request, loadedProgressiveTools) {
|
|
12412
12489
|
const eligible = tools.definitions().filter(
|
|
12413
|
-
(tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact") && (duplicationAvailable || tool.name !== "duplication_audit")
|
|
12490
|
+
(tool) => (!askMode || tool.category === "read") && intentAllowsTool(intent, tool) && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact") && (duplicationAvailable || tool.name !== "duplication_audit")
|
|
12414
12491
|
);
|
|
12415
12492
|
const progressive = eligible.filter((tool) => tool.progressive);
|
|
12416
12493
|
if (progressive.length <= 8) return eligible;
|
|
@@ -12419,6 +12496,26 @@ function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAva
|
|
|
12419
12496
|
}
|
|
12420
12497
|
return eligible.filter((tool) => !tool.progressive || loadedProgressiveTools.has(tool.name));
|
|
12421
12498
|
}
|
|
12499
|
+
function intentAllowsTool(intent, tool) {
|
|
12500
|
+
if (!BUILTIN_TOOL_NAMES.has(tool.name)) return true;
|
|
12501
|
+
if (intent === "explain") return tool.category === "read";
|
|
12502
|
+
if (intent === "review") return tool.category === "read" || tool.name === "git";
|
|
12503
|
+
return true;
|
|
12504
|
+
}
|
|
12505
|
+
var BUILTIN_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
12506
|
+
"read_file",
|
|
12507
|
+
"read_tool_artifact",
|
|
12508
|
+
"list_files",
|
|
12509
|
+
"search_code",
|
|
12510
|
+
"write_file",
|
|
12511
|
+
"apply_patch",
|
|
12512
|
+
"shell",
|
|
12513
|
+
"git",
|
|
12514
|
+
"task",
|
|
12515
|
+
"task_contract",
|
|
12516
|
+
"duplication_audit",
|
|
12517
|
+
"working_memory"
|
|
12518
|
+
]);
|
|
12422
12519
|
function selectProgressiveTools(tools, request, limit) {
|
|
12423
12520
|
const terms = new Set(request.toLocaleLowerCase().match(/[\p{L}\p{N}_-]{2,}/gu) ?? []);
|
|
12424
12521
|
return tools.map((tool) => {
|
|
@@ -13470,6 +13567,8 @@ var DelegationManager = class {
|
|
|
13470
13567
|
return {
|
|
13471
13568
|
definition: {
|
|
13472
13569
|
name: "delegate",
|
|
13570
|
+
source: "agent",
|
|
13571
|
+
activation: "always",
|
|
13473
13572
|
description: "Run independent read-only investigations with specialized isolated agents and return concise evidence-backed summaries.",
|
|
13474
13573
|
category: "read",
|
|
13475
13574
|
inputSchema: jsonSchema({
|
|
@@ -13517,6 +13616,8 @@ var DelegationManager = class {
|
|
|
13517
13616
|
return {
|
|
13518
13617
|
definition: {
|
|
13519
13618
|
name: "team_run",
|
|
13619
|
+
source: "agent",
|
|
13620
|
+
activation: "always",
|
|
13520
13621
|
description: "Run a visible multi-model council: parallel read-only specialists share findings, a reviewer challenges them, and one bounded revision round runs before returning an acceptance report.",
|
|
13521
13622
|
category: "read",
|
|
13522
13623
|
inputSchema: jsonSchema({
|
|
@@ -13561,6 +13662,8 @@ var DelegationManager = class {
|
|
|
13561
13662
|
return {
|
|
13562
13663
|
definition: {
|
|
13563
13664
|
name: "writer_run",
|
|
13665
|
+
source: "agent",
|
|
13666
|
+
activation: "always",
|
|
13564
13667
|
description: "Create one reviewed patch in a disposable Git worktree. This never changes the main workspace; use writer_integrate explicitly after review.",
|
|
13565
13668
|
category: "write",
|
|
13566
13669
|
inputSchema: jsonSchema({
|
|
@@ -13593,6 +13696,8 @@ var DelegationManager = class {
|
|
|
13593
13696
|
return {
|
|
13594
13697
|
definition: {
|
|
13595
13698
|
name: "writer_integrate",
|
|
13699
|
+
source: "agent",
|
|
13700
|
+
activation: "always",
|
|
13596
13701
|
description: "Explicitly apply one accepted writer patch to the main workspace after SHA, HEAD, cleanliness, path, and checkpoint gates pass.",
|
|
13597
13702
|
category: "write",
|
|
13598
13703
|
inputSchema: jsonSchema({
|
|
@@ -15476,7 +15581,7 @@ var commandDefinitions = [
|
|
|
15476
15581
|
command("skills", "List discovered task playbooks"),
|
|
15477
15582
|
command("agents", "List built-in and installed expert profiles"),
|
|
15478
15583
|
command("connections", "Inspect shared model endpoints and setup status", "/connections [setup]"),
|
|
15479
|
-
command("mcp", "
|
|
15584
|
+
command("mcp", "Search, inspect, trust, activate, disable, or revoke MCP capabilities", "/mcp [search|inspect|trust|activate|disable|revoke] [...]"),
|
|
15480
15585
|
command("tools", "List built-in and MCP tools with permission categories"),
|
|
15481
15586
|
command("permissions", "Inspect the active permission policy"),
|
|
15482
15587
|
command("changes", "List files changed in the active session"),
|
|
@@ -15532,6 +15637,21 @@ function commandSuggestions(input2, options = {}) {
|
|
|
15532
15637
|
description: item.description
|
|
15533
15638
|
}));
|
|
15534
15639
|
}
|
|
15640
|
+
if (firstSpace >= 0 && commandName === "mcp") {
|
|
15641
|
+
const query = argument.trim().toLocaleLowerCase();
|
|
15642
|
+
return [
|
|
15643
|
+
{ name: "search", description: "Search redacted local capability manifests" },
|
|
15644
|
+
{ name: "inspect", description: "Review one manifest before trust" },
|
|
15645
|
+
{ name: "trust", description: "Trust the current manifest after confirmation" },
|
|
15646
|
+
{ name: "activate", description: "Connect a trusted server and load relevant schemas" },
|
|
15647
|
+
{ name: "disable", description: "Persistently disable a capability" },
|
|
15648
|
+
{ name: "revoke", description: "Revoke trust after confirmation" }
|
|
15649
|
+
].filter((item) => item.name.includes(query)).map((item) => ({
|
|
15650
|
+
value: `/mcp ${item.name} `,
|
|
15651
|
+
label: item.name,
|
|
15652
|
+
description: item.description
|
|
15653
|
+
}));
|
|
15654
|
+
}
|
|
15535
15655
|
if (firstSpace >= 0 && commandName === "connections") {
|
|
15536
15656
|
return [{
|
|
15537
15657
|
value: "/connections setup",
|
|
@@ -18446,11 +18566,11 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18446
18566
|
return () => clearInterval(timer);
|
|
18447
18567
|
}, [busy]);
|
|
18448
18568
|
const requestPermission = useCallback((call, category, reason) => {
|
|
18449
|
-
return new Promise((
|
|
18569
|
+
return new Promise((resolve28) => setPermission({
|
|
18450
18570
|
call,
|
|
18451
18571
|
category,
|
|
18452
18572
|
...reason ? { reason } : {},
|
|
18453
|
-
resolve:
|
|
18573
|
+
resolve: resolve28
|
|
18454
18574
|
}));
|
|
18455
18575
|
}, []);
|
|
18456
18576
|
const onEvent = useCallback((event) => {
|
|
@@ -18972,19 +19092,103 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18972
19092
|
return true;
|
|
18973
19093
|
}
|
|
18974
19094
|
if (command2 === "mcp") {
|
|
19095
|
+
const [action = "list", server = "", ...tail] = argument.split(/\s+/u).filter(Boolean);
|
|
19096
|
+
if (action === "search") {
|
|
19097
|
+
const results = extensions?.mcpSearch([server, ...tail].filter(Boolean).join(" ")) ?? [];
|
|
19098
|
+
appendList("MCP capability search", results.length ? results.map((result) => ({
|
|
19099
|
+
label: `${result.name} ${result.trust}${result.required ? `${separator}required` : ""}`,
|
|
19100
|
+
detail: `${result.description}${separator}${result.version}${separator}${result.declaredTools || "dynamic"} tools`,
|
|
19101
|
+
tone: result.trust === "trusted" ? "success" : result.trust === "revoked" ? "error" : "warning"
|
|
19102
|
+
})) : [{ label: "No configured MCP capability matched." }]);
|
|
19103
|
+
return true;
|
|
19104
|
+
}
|
|
19105
|
+
if (action === "inspect" || action === "trust") {
|
|
19106
|
+
if (!server) {
|
|
19107
|
+
append({ id: nextId(), kind: "notice", tone: "error", text: `Usage: /mcp ${action} <server>${action === "trust" ? " [--confirm]" : ""}` });
|
|
19108
|
+
return true;
|
|
19109
|
+
}
|
|
19110
|
+
const manifest = extensions?.mcpInspect(server);
|
|
19111
|
+
if (!manifest) {
|
|
19112
|
+
append({ id: nextId(), kind: "notice", tone: "error", text: "MCP is disabled or unavailable." });
|
|
19113
|
+
return true;
|
|
19114
|
+
}
|
|
19115
|
+
appendList(`MCP trust review \xB7 ${manifest.name}`, [
|
|
19116
|
+
{ label: `${manifest.source.kind}:${manifest.name} ${manifest.version}`, detail: `${manifest.transport}${separator}${manifest.target}${manifest.required ? `${separator}required` : ""}` },
|
|
19117
|
+
...manifest.tools.length ? manifest.tools.flatMap((tool) => {
|
|
19118
|
+
const tone = tool.permissions.some((category) => category !== "read" && category !== "network") ? "warning" : "normal";
|
|
19119
|
+
return [
|
|
19120
|
+
{ label: `${tool.name} ${tool.permissions.join("+")}`, detail: `completion evidence ${tool.completionEvidence}`, tone },
|
|
19121
|
+
{ label: "network scopes", detail: tool.network.join(", ") || "unspecified", tone },
|
|
19122
|
+
{ label: "command scopes", detail: tool.commands.join(", ") || "none", tone },
|
|
19123
|
+
{ label: "path scopes", detail: tool.paths.join(", ") || "none", tone },
|
|
19124
|
+
{ label: `sensitive fields ${tool.sensitiveFields.join(", ") || "none"}`, detail: `${tool.background ? "background" : "foreground"}${separator}${tool.processTree ? "process-tree" : "single-process"}`, tone }
|
|
19125
|
+
];
|
|
19126
|
+
}) : [{ label: "Dynamic remote tools", detail: "Undeclared tools stay network-only and do not receive Skein completion-evidence protection.", tone: "warning" }]
|
|
19127
|
+
]);
|
|
19128
|
+
if (action === "trust") {
|
|
19129
|
+
if (manifest.dynamicTools) {
|
|
19130
|
+
append({ id: nextId(), kind: "notice", tone: "error", text: `Declare tools and effects for ${server} in user-owned config before trust can be granted.` });
|
|
19131
|
+
return true;
|
|
19132
|
+
}
|
|
19133
|
+
if (!tail.includes("--confirm")) {
|
|
19134
|
+
append({ id: nextId(), kind: "notice", tone: "warning", text: `Review complete. Run /mcp trust ${server} --confirm to trust this exact manifest fingerprint.` });
|
|
19135
|
+
} else {
|
|
19136
|
+
const status = await extensions?.mcpTrust(server);
|
|
19137
|
+
append({ id: nextId(), kind: "notice", tone: status ? "success" : "error", text: status ? `Trusted MCP capability ${server}. Activation remains explicit.` : "MCP is unavailable." });
|
|
19138
|
+
}
|
|
19139
|
+
}
|
|
19140
|
+
return true;
|
|
19141
|
+
}
|
|
19142
|
+
if (action === "activate") {
|
|
19143
|
+
const query = tail.join(" ").trim();
|
|
19144
|
+
if (!server || !query) {
|
|
19145
|
+
append({ id: nextId(), kind: "notice", tone: "error", text: "Usage: /mcp activate <server> <capability query>" });
|
|
19146
|
+
return true;
|
|
19147
|
+
}
|
|
19148
|
+
const result = await extensions?.mcpActivate(server, query);
|
|
19149
|
+
append({
|
|
19150
|
+
id: nextId(),
|
|
19151
|
+
kind: "notice",
|
|
19152
|
+
tone: result?.ok ? "success" : "error",
|
|
19153
|
+
text: result?.ok ? `Activated ${server}; loaded ${result.registeredTools.length} of ${result.availableTools} schemas.` : `Could not activate ${server}: ${result?.status.error ?? result?.status.trust ?? "MCP unavailable"}`
|
|
19154
|
+
});
|
|
19155
|
+
return true;
|
|
19156
|
+
}
|
|
19157
|
+
if (action === "disable") {
|
|
19158
|
+
if (!server) {
|
|
19159
|
+
append({ id: nextId(), kind: "notice", tone: "error", text: "Usage: /mcp disable <server>" });
|
|
19160
|
+
return true;
|
|
19161
|
+
}
|
|
19162
|
+
const status = await extensions?.mcpDisable(server);
|
|
19163
|
+
append({ id: nextId(), kind: "notice", tone: status ? "success" : "error", text: status ? `Disabled MCP capability ${server}.` : "MCP is unavailable." });
|
|
19164
|
+
return true;
|
|
19165
|
+
}
|
|
19166
|
+
if (action === "revoke") {
|
|
19167
|
+
if (!server || !tail.includes("--confirm")) {
|
|
19168
|
+
append({ id: nextId(), kind: "notice", tone: "warning", text: `Revocation removes persisted trust. Run /mcp revoke ${server || "<server>"} --confirm to continue.` });
|
|
19169
|
+
return true;
|
|
19170
|
+
}
|
|
19171
|
+
const status = await extensions?.mcpRevoke(server);
|
|
19172
|
+
append({ id: nextId(), kind: "notice", tone: status ? "success" : "error", text: status ? `Revoked MCP capability ${server}. Re-inspection and trust are required before reuse.` : "MCP is unavailable." });
|
|
19173
|
+
return true;
|
|
19174
|
+
}
|
|
19175
|
+
if (action !== "list") {
|
|
19176
|
+
append({ id: nextId(), kind: "notice", tone: "error", text: "Usage: /mcp [search|inspect|trust|activate|disable|revoke] [...]" });
|
|
19177
|
+
return true;
|
|
19178
|
+
}
|
|
18975
19179
|
const servers = extensions?.mcpStatus() ?? [];
|
|
18976
|
-
appendList("MCP", servers.length ? servers.map((
|
|
18977
|
-
label: `${
|
|
18978
|
-
detail: `${
|
|
18979
|
-
tone:
|
|
19180
|
+
appendList("MCP", servers.length ? servers.map((item) => ({
|
|
19181
|
+
label: `${item.name} ${item.state}${item.required ? `${separator}required` : ""}${item.serverVersion ? `${separator}v${item.serverVersion}` : ""}`,
|
|
19182
|
+
detail: `${item.transport}${separator}${item.trust}${separator}${item.toolCount} tools${item.connectedAt ? `${separator}connected ${item.connectedAt.slice(11, 19)}` : ""}${item.error ? `${separator}${item.error}` : ""}`,
|
|
19183
|
+
tone: item.state === "connected" ? "success" : item.state === "error" || item.state === "revoked" ? "error" : "warning"
|
|
18980
19184
|
})) : [{ label: "No MCP servers configured." }]);
|
|
18981
19185
|
return true;
|
|
18982
19186
|
}
|
|
18983
19187
|
if (command2 === "tools") {
|
|
18984
19188
|
const definitions = runner.tools.definitions();
|
|
18985
19189
|
appendList("Tools", definitions.map((tool) => ({
|
|
18986
|
-
label: `${tool.name} ${tool.category}`,
|
|
18987
|
-
detail: tool.description
|
|
19190
|
+
label: `${tool.name} ${tool.source ?? "builtin"}${separator}${(tool.permissionCategories ?? [tool.category]).join("+")}`,
|
|
19191
|
+
detail: `${tool.activation ?? "always"}${tool.completionEvidence ? `${separator}evidence ${tool.completionEvidence}` : ""}${separator}${tool.description}`,
|
|
18988
19192
|
tone: tool.category === "read" ? "normal" : tool.category === "network" ? "warning" : "normal"
|
|
18989
19193
|
})));
|
|
18990
19194
|
return true;
|
|
@@ -19409,8 +19613,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
19409
19613
|
}, [append]);
|
|
19410
19614
|
function settlePermission(grant, stop = false) {
|
|
19411
19615
|
if (!permission) return;
|
|
19412
|
-
const { call, category, resolve:
|
|
19413
|
-
|
|
19616
|
+
const { call, category, resolve: resolve28 } = permission;
|
|
19617
|
+
resolve28(grant);
|
|
19414
19618
|
setPermission(void 0);
|
|
19415
19619
|
if (grant === "session") {
|
|
19416
19620
|
append({
|
|
@@ -20802,13 +21006,13 @@ async function runFirstRunOnboarding(initialConfig, options = {}) {
|
|
|
20802
21006
|
}
|
|
20803
21007
|
|
|
20804
21008
|
// src/runtime/extensions.ts
|
|
20805
|
-
import { resolve as
|
|
21009
|
+
import { resolve as resolve26 } from "node:path";
|
|
20806
21010
|
|
|
20807
21011
|
// src/mcp/manager.ts
|
|
20808
21012
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
20809
21013
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
20810
21014
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
20811
|
-
import
|
|
21015
|
+
import stripAnsi6 from "strip-ansi";
|
|
20812
21016
|
|
|
20813
21017
|
// src/mcp/tool.ts
|
|
20814
21018
|
import { createHash as createHash18 } from "node:crypto";
|
|
@@ -20820,36 +21024,61 @@ var MAX_SCHEMA_BYTES = 1e5;
|
|
|
20820
21024
|
function createMcpToolAdapter(options) {
|
|
20821
21025
|
const { remoteTool } = options;
|
|
20822
21026
|
const inputSchema13 = copyInputSchema(remoteTool.inputSchema);
|
|
21027
|
+
const permissionCategories = options.capability?.permissions ?? ["network"];
|
|
21028
|
+
const completionEvidence = options.capability?.completionEvidence ?? "none";
|
|
21029
|
+
const mutating = permissionCategories.some((category) => category === "write" || category === "shell" || category === "git");
|
|
20823
21030
|
return {
|
|
20824
21031
|
definition: {
|
|
20825
21032
|
name: options.exposedName,
|
|
20826
|
-
description: describeTool(options.serverName, remoteTool),
|
|
21033
|
+
description: describeTool(options.serverName, remoteTool, options.capability),
|
|
20827
21034
|
// MCP servers are an external trust boundary. Read-only annotations are
|
|
20828
21035
|
// hints from that server and must not lower the local permission level.
|
|
20829
21036
|
category: "network",
|
|
20830
21037
|
inputSchema: inputSchema13,
|
|
20831
|
-
progressive: true
|
|
21038
|
+
progressive: true,
|
|
21039
|
+
source: "mcp",
|
|
21040
|
+
permissionCategories,
|
|
21041
|
+
activation: "active",
|
|
21042
|
+
completionEvidence,
|
|
21043
|
+
...options.capability?.sensitiveFields.length ? { sensitiveFields: options.capability.sensitiveFields } : {}
|
|
20832
21044
|
},
|
|
20833
|
-
permissionCategories: () =>
|
|
21045
|
+
permissionCategories: () => permissionCategories,
|
|
21046
|
+
...mutating ? {
|
|
21047
|
+
affectedPaths: async (arguments_, context) => resolveDeclaredMutationPaths(
|
|
21048
|
+
arguments_,
|
|
21049
|
+
options.capability?.paths ?? [],
|
|
21050
|
+
context
|
|
21051
|
+
)
|
|
21052
|
+
} : {},
|
|
20834
21053
|
async execute(arguments_, context) {
|
|
20835
21054
|
assertArguments(arguments_);
|
|
21055
|
+
const remoteArguments = mutating && completionEvidence === "full" && context.checkpointId ? { ...arguments_, _skein: { checkpointId: context.checkpointId } } : arguments_;
|
|
20836
21056
|
const result = await options.callTool({
|
|
20837
21057
|
name: remoteTool.name,
|
|
20838
|
-
arguments:
|
|
21058
|
+
arguments: remoteArguments
|
|
20839
21059
|
}, {
|
|
20840
21060
|
timeout: options.timeoutMs,
|
|
20841
21061
|
maxTotalTimeout: options.timeoutMs,
|
|
20842
21062
|
...context.signal ? { signal: context.signal } : {}
|
|
20843
21063
|
});
|
|
20844
21064
|
const normalized = normalizeCallResult(result);
|
|
21065
|
+
const evidence = mutating && completionEvidence === "full" ? await validateCompletionEvidence(normalized.structuredContent, context) : void 0;
|
|
20845
21066
|
return {
|
|
20846
21067
|
ok: !normalized.isError,
|
|
20847
21068
|
content: normalized.content,
|
|
21069
|
+
...evidence ? { changedFiles: evidence.changedFiles } : {},
|
|
20848
21070
|
metadata: {
|
|
20849
21071
|
mcpServer: options.serverName,
|
|
20850
21072
|
mcpTool: remoteTool.name,
|
|
20851
21073
|
sourceBytes: normalized.sourceBytes,
|
|
20852
21074
|
sourceTruncated: normalized.sourceTruncated,
|
|
21075
|
+
capabilityPermissions: permissionCategories,
|
|
21076
|
+
completionEvidence,
|
|
21077
|
+
...mutating ? {
|
|
21078
|
+
changeTracking: evidence ? "complete" : "unresolved",
|
|
21079
|
+
completionEvidenceVerified: Boolean(evidence)
|
|
21080
|
+
} : {},
|
|
21081
|
+
...evidence ? evidence : {},
|
|
20853
21082
|
...normalized.isError ? { mcpError: true } : {}
|
|
20854
21083
|
}
|
|
20855
21084
|
};
|
|
@@ -20878,9 +21107,10 @@ function isUsableRemoteTool(tool) {
|
|
|
20878
21107
|
return false;
|
|
20879
21108
|
}
|
|
20880
21109
|
}
|
|
20881
|
-
function describeTool(serverName, tool) {
|
|
21110
|
+
function describeTool(serverName, tool, capability) {
|
|
20882
21111
|
const label = stripAnsi4(tool.title?.trim() || tool.name).replace(/[\u0000-\u001f\u007f]/g, " ").slice(0, 200);
|
|
20883
|
-
const
|
|
21112
|
+
const sourceDescription = capability?.description ?? tool.description;
|
|
21113
|
+
const description = sourceDescription ? stripAnsi4(sourceDescription).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim().slice(0, MAX_DESCRIPTION_LENGTH) : void 0;
|
|
20884
21114
|
return description ? `[MCP ${serverName}/${label}] ${description}` : `Call the ${label} tool provided by the ${serverName} MCP server.`;
|
|
20885
21115
|
}
|
|
20886
21116
|
function copyInputSchema(schema) {
|
|
@@ -20919,7 +21149,46 @@ ${safeJson(result.structuredContent)}`);
|
|
|
20919
21149
|
${safeJson(result.toolResult)}`);
|
|
20920
21150
|
}
|
|
20921
21151
|
const content = sections.filter(Boolean).join("\n\n") || (isError ? "The MCP tool reported an error." : "The MCP tool completed without output.");
|
|
20922
|
-
return boundResult(content, isError);
|
|
21152
|
+
return { ...boundResult(content, isError), ...result.structuredContent === void 0 ? {} : { structuredContent: result.structuredContent } };
|
|
21153
|
+
}
|
|
21154
|
+
async function validateCompletionEvidence(structuredContent, context) {
|
|
21155
|
+
if (!isRecord2(structuredContent)) return;
|
|
21156
|
+
const receipt = isRecord2(structuredContent.skeinEvidence) ? structuredContent.skeinEvidence : void 0;
|
|
21157
|
+
if (!receipt || !context.checkpointId || !Array.isArray(receipt.changedFiles) || receipt.changedFiles.length > 256 || typeof receipt.checkpointId !== "string" || !receipt.checkpointId.trim() || receipt.checkpointId !== context.checkpointId || !Array.isArray(receipt.artifactReceipts) || !receipt.artifactReceipts.length || !Array.isArray(receipt.completionEvidence) || !receipt.completionEvidence.length) return;
|
|
21158
|
+
const changedFiles = [];
|
|
21159
|
+
for (const path of receipt.changedFiles) {
|
|
21160
|
+
if (typeof path !== "string" || !path.trim()) return;
|
|
21161
|
+
try {
|
|
21162
|
+
changedFiles.push(await context.workspace.resolvePath(path, { allowMissing: true }));
|
|
21163
|
+
} catch {
|
|
21164
|
+
return;
|
|
21165
|
+
}
|
|
21166
|
+
}
|
|
21167
|
+
const artifactReceipts = receipt.artifactReceipts.filter((value) => typeof value === "string" && Boolean(value.trim())).map((value) => sanitizeInlineText(value).slice(0, 256));
|
|
21168
|
+
const completionEvidenceRefs = receipt.completionEvidence.filter((value) => typeof value === "string" && Boolean(value.trim())).map((value) => sanitizeInlineText(value).slice(0, 256));
|
|
21169
|
+
if (artifactReceipts.length !== receipt.artifactReceipts.length || completionEvidenceRefs.length !== receipt.completionEvidence.length) return;
|
|
21170
|
+
return {
|
|
21171
|
+
changedFiles,
|
|
21172
|
+
checkpointId: sanitizeInlineText(receipt.checkpointId).slice(0, 256),
|
|
21173
|
+
artifactReceipts,
|
|
21174
|
+
completionEvidenceRefs
|
|
21175
|
+
};
|
|
21176
|
+
}
|
|
21177
|
+
async function resolveDeclaredMutationPaths(arguments_, declaredPaths, context) {
|
|
21178
|
+
const candidates = [];
|
|
21179
|
+
for (const key of ["path", "file", "cwd"]) {
|
|
21180
|
+
const value = arguments_[key];
|
|
21181
|
+
if (typeof value === "string" && value.trim()) candidates.push(value);
|
|
21182
|
+
}
|
|
21183
|
+
if (Array.isArray(arguments_.paths)) {
|
|
21184
|
+
candidates.push(...arguments_.paths.filter((value) => typeof value === "string" && Boolean(value.trim())));
|
|
21185
|
+
}
|
|
21186
|
+
candidates.push(...declaredPaths.filter((path) => !/[?*{}\[\]]/u.test(path)));
|
|
21187
|
+
const resolved = [];
|
|
21188
|
+
for (const candidate of [...new Set(candidates)].slice(0, 256)) {
|
|
21189
|
+
resolved.push(await context.workspace.resolvePath(candidate, { allowMissing: true }));
|
|
21190
|
+
}
|
|
21191
|
+
return resolved;
|
|
20923
21192
|
}
|
|
20924
21193
|
function boundResult(content, isError) {
|
|
20925
21194
|
const sourceBytes = Buffer.byteLength(content);
|
|
@@ -21147,6 +21416,221 @@ function isLoopbackHost(hostname) {
|
|
|
21147
21416
|
return normalized === "localhost" || normalized === "::1" || normalized === "0:0:0:0:0:0:0:1" || isIpv4Loopback;
|
|
21148
21417
|
}
|
|
21149
21418
|
|
|
21419
|
+
// src/mcp/capabilities.ts
|
|
21420
|
+
import { createHash as createHash19 } from "node:crypto";
|
|
21421
|
+
import { homedir as homedir4 } from "node:os";
|
|
21422
|
+
import { basename as basename12, isAbsolute as isAbsolute9, relative as relative11, resolve as resolve23 } from "node:path";
|
|
21423
|
+
import stripAnsi5 from "strip-ansi";
|
|
21424
|
+
function buildMcpCapabilityManifest(name, config, workspace = process.cwd()) {
|
|
21425
|
+
const tools = (config.tools ?? []).map((tool) => normalizeTool(tool, workspace));
|
|
21426
|
+
return {
|
|
21427
|
+
schemaVersion: 1,
|
|
21428
|
+
id: `mcp:${sanitizeIdentifier(name, "server")}`,
|
|
21429
|
+
source: { kind: "mcp", owner: "user-config" },
|
|
21430
|
+
name: sanitizeText(name, 64),
|
|
21431
|
+
version: sanitizeText(config.version ?? "unversioned", 128),
|
|
21432
|
+
required: config.required === true,
|
|
21433
|
+
transport: config.transport ?? "stdio",
|
|
21434
|
+
target: redactTransportTarget(config),
|
|
21435
|
+
tools,
|
|
21436
|
+
dynamicTools: tools.length === 0
|
|
21437
|
+
};
|
|
21438
|
+
}
|
|
21439
|
+
function capabilityFingerprint(name, config, workspace = process.cwd()) {
|
|
21440
|
+
const manifest = buildMcpCapabilityManifest(name, config, workspace);
|
|
21441
|
+
const privateTransport = {
|
|
21442
|
+
command: config.command ?? null,
|
|
21443
|
+
args: config.args ?? [],
|
|
21444
|
+
cwd: config.cwd ? resolve23(workspace, config.cwd) : null,
|
|
21445
|
+
url: config.url ? redactUrl(config.url) : null,
|
|
21446
|
+
envNames: Object.keys(config.env ?? {}).sort(),
|
|
21447
|
+
headerNames: Object.keys(config.headers ?? {}).map((key) => key.toLocaleLowerCase()).sort()
|
|
21448
|
+
};
|
|
21449
|
+
return createHash19("sha256").update(stableJson2({ manifest, privateTransport })).digest("hex");
|
|
21450
|
+
}
|
|
21451
|
+
function searchMcpCapabilities(config, query) {
|
|
21452
|
+
const terms = new Set(sanitizeText(query, 500).toLocaleLowerCase().match(/[\p{L}\p{N}_-]{2,}/gu) ?? []);
|
|
21453
|
+
return Object.entries(config.servers ?? {}).map(([name, server]) => {
|
|
21454
|
+
const description = sanitizeText(server.description ?? `${server.transport ?? "stdio"} MCP server`, 200);
|
|
21455
|
+
const searchable = [name, description, server.version, ...(server.tools ?? []).flatMap((tool) => [
|
|
21456
|
+
tool.name,
|
|
21457
|
+
tool.description,
|
|
21458
|
+
...tool.permissions,
|
|
21459
|
+
...tool.commands ?? [],
|
|
21460
|
+
...tool.network ?? []
|
|
21461
|
+
])].filter(Boolean).join(" ").toLocaleLowerCase();
|
|
21462
|
+
let score = 0;
|
|
21463
|
+
for (const term of terms) if (searchable.includes(term)) score += term.length;
|
|
21464
|
+
return {
|
|
21465
|
+
name,
|
|
21466
|
+
description,
|
|
21467
|
+
version: sanitizeText(server.version ?? "unversioned", 128),
|
|
21468
|
+
required: server.required === true,
|
|
21469
|
+
transport: server.transport ?? "stdio",
|
|
21470
|
+
declaredTools: server.tools?.length ?? 0,
|
|
21471
|
+
score
|
|
21472
|
+
};
|
|
21473
|
+
}).filter((result) => terms.size === 0 || result.score > 0).sort((left, right) => right.score - left.score || left.name.localeCompare(right.name));
|
|
21474
|
+
}
|
|
21475
|
+
function declaredToolCapability(config, remoteName, workspace = process.cwd()) {
|
|
21476
|
+
const declaration = config.tools?.find((tool) => tool.name === remoteName);
|
|
21477
|
+
return declaration ? normalizeTool(declaration, workspace) : void 0;
|
|
21478
|
+
}
|
|
21479
|
+
function normalizeTool(tool, workspace) {
|
|
21480
|
+
const permissions = uniquePermissions(["network", ...tool.permissions]);
|
|
21481
|
+
return {
|
|
21482
|
+
name: sanitizeText(tool.name, 256),
|
|
21483
|
+
...tool.description ? { description: sanitizeText(tool.description, 500) } : {},
|
|
21484
|
+
permissions,
|
|
21485
|
+
network: unique3(tool.network ?? []).map(redactNetworkTarget),
|
|
21486
|
+
commands: unique3(tool.commands ?? []).map(redactCommand2),
|
|
21487
|
+
paths: unique3(tool.paths ?? []).map((path) => redactPath(path, workspace)),
|
|
21488
|
+
sensitiveFields: unique3(tool.sensitiveFields ?? []).map((field) => sanitizeText(field, 256)),
|
|
21489
|
+
background: tool.background === true,
|
|
21490
|
+
processTree: tool.processTree === true,
|
|
21491
|
+
completionEvidence: tool.completionEvidence ?? "none"
|
|
21492
|
+
};
|
|
21493
|
+
}
|
|
21494
|
+
function redactTransportTarget(config) {
|
|
21495
|
+
if (config.transport === "http") return config.url ? redactUrl(config.url) : "<unconfigured HTTP endpoint>";
|
|
21496
|
+
return config.command ? `<stdio command:${sanitizeText(basename12(config.command), 128)}>` : "<unconfigured stdio command>";
|
|
21497
|
+
}
|
|
21498
|
+
function redactUrl(value) {
|
|
21499
|
+
try {
|
|
21500
|
+
const url = new URL(value);
|
|
21501
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
21502
|
+
} catch {
|
|
21503
|
+
return "<invalid URL>";
|
|
21504
|
+
}
|
|
21505
|
+
}
|
|
21506
|
+
function redactNetworkTarget(value) {
|
|
21507
|
+
if (/^https?:\/\//iu.test(value)) return redactUrl(value);
|
|
21508
|
+
return sanitizeText(value, 500).replace(/([?&](?:key|token|secret|password)=)[^&\s]*/giu, "$1<redacted>");
|
|
21509
|
+
}
|
|
21510
|
+
function redactCommand2(value) {
|
|
21511
|
+
const command2 = sanitizeText(value, 500).split(/\s+/u)[0];
|
|
21512
|
+
return command2 ? basename12(command2) : "<redacted command>";
|
|
21513
|
+
}
|
|
21514
|
+
function redactPath(value, workspace) {
|
|
21515
|
+
const clean2 = sanitizeText(value, 4e3);
|
|
21516
|
+
if (!isAbsolute9(clean2)) return clean2;
|
|
21517
|
+
const absolute = resolve23(clean2);
|
|
21518
|
+
const workspaceRelative = relative11(resolve23(workspace), absolute);
|
|
21519
|
+
if (!workspaceRelative.startsWith("..") && !isAbsolute9(workspaceRelative)) {
|
|
21520
|
+
return `<workspace>/${workspaceRelative || "."}`;
|
|
21521
|
+
}
|
|
21522
|
+
const homeRelative = relative11(homedir4(), absolute);
|
|
21523
|
+
if (!homeRelative.startsWith("..") && !isAbsolute9(homeRelative)) return `~/${homeRelative}`;
|
|
21524
|
+
return `<absolute>/${basename12(absolute)}`;
|
|
21525
|
+
}
|
|
21526
|
+
function uniquePermissions(values) {
|
|
21527
|
+
const order = ["read", "write", "shell", "git", "network"];
|
|
21528
|
+
const present = new Set(values);
|
|
21529
|
+
return order.filter((category) => present.has(category));
|
|
21530
|
+
}
|
|
21531
|
+
function unique3(values) {
|
|
21532
|
+
return [...new Set(values)].slice(0, 256);
|
|
21533
|
+
}
|
|
21534
|
+
function sanitizeIdentifier(value, fallback) {
|
|
21535
|
+
return sanitizeText(value, 64).toLocaleLowerCase().replace(/[^a-z0-9_-]+/gu, "_").replace(/^_+|_+$/gu, "") || fallback;
|
|
21536
|
+
}
|
|
21537
|
+
function sanitizeText(value, maxLength) {
|
|
21538
|
+
return stripAnsi5(value).replace(/[\u0000-\u001f\u007f]/gu, " ").replace(/\s+/gu, " ").trim().slice(0, maxLength);
|
|
21539
|
+
}
|
|
21540
|
+
function stableJson2(value) {
|
|
21541
|
+
if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
|
|
21542
|
+
if (value && typeof value === "object") {
|
|
21543
|
+
const object = value;
|
|
21544
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableJson2(object[key])}`).join(",")}}`;
|
|
21545
|
+
}
|
|
21546
|
+
return JSON.stringify(value);
|
|
21547
|
+
}
|
|
21548
|
+
|
|
21549
|
+
// src/mcp/trust-store.ts
|
|
21550
|
+
import { lstat as lstat21, mkdir as mkdir10, readFile as readFile20, realpath as realpath9 } from "node:fs/promises";
|
|
21551
|
+
import { dirname as dirname10, join as join21, resolve as resolve24 } from "node:path";
|
|
21552
|
+
import { z as z20 } from "zod";
|
|
21553
|
+
var trustRegistrySchema = z20.object({
|
|
21554
|
+
version: z20.literal(1),
|
|
21555
|
+
entries: z20.array(z20.object({
|
|
21556
|
+
workspace: z20.string(),
|
|
21557
|
+
server: z20.string().regex(/^[a-z][a-z0-9_-]{0,63}$/),
|
|
21558
|
+
fingerprint: z20.string().regex(/^[a-f0-9]{64}$/),
|
|
21559
|
+
state: z20.enum(["trusted", "disabled", "revoked"]),
|
|
21560
|
+
updatedAt: z20.string()
|
|
21561
|
+
}).strict()).max(1e3)
|
|
21562
|
+
}).strict();
|
|
21563
|
+
var McpTrustStore = class {
|
|
21564
|
+
path;
|
|
21565
|
+
usesDefaultPath;
|
|
21566
|
+
constructor(options = {}) {
|
|
21567
|
+
this.usesDefaultPath = options.path === void 0;
|
|
21568
|
+
this.path = options.path ?? join21(resolveHomeNamespace(), "mcp-capability-trust.json");
|
|
21569
|
+
}
|
|
21570
|
+
async state(workspace, server, fingerprint) {
|
|
21571
|
+
const resolvedWorkspace = await resolveWorkspace(workspace);
|
|
21572
|
+
const registry = await this.read();
|
|
21573
|
+
const entry = [...registry.entries].reverse().find((candidate) => candidate.workspace === resolvedWorkspace && candidate.server === server);
|
|
21574
|
+
if (!entry) return "untrusted";
|
|
21575
|
+
if (entry.state === "disabled" || entry.state === "revoked") return entry.state;
|
|
21576
|
+
return entry.fingerprint === fingerprint ? "trusted" : "untrusted";
|
|
21577
|
+
}
|
|
21578
|
+
trust(workspace, server, fingerprint) {
|
|
21579
|
+
return this.writeDecision(workspace, server, fingerprint, "trusted");
|
|
21580
|
+
}
|
|
21581
|
+
disable(workspace, server, fingerprint) {
|
|
21582
|
+
return this.writeDecision(workspace, server, fingerprint, "disabled");
|
|
21583
|
+
}
|
|
21584
|
+
revoke(workspace, server, fingerprint) {
|
|
21585
|
+
return this.writeDecision(workspace, server, fingerprint, "revoked");
|
|
21586
|
+
}
|
|
21587
|
+
async writeDecision(workspace, server, fingerprint, state) {
|
|
21588
|
+
const operation = async () => {
|
|
21589
|
+
const resolvedWorkspace = await resolveWorkspace(workspace);
|
|
21590
|
+
const registry = await this.read();
|
|
21591
|
+
const entries = registry.entries.filter((entry) => entry.workspace !== resolvedWorkspace || entry.server !== server);
|
|
21592
|
+
entries.push({
|
|
21593
|
+
workspace: resolvedWorkspace,
|
|
21594
|
+
server,
|
|
21595
|
+
fingerprint,
|
|
21596
|
+
state,
|
|
21597
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
21598
|
+
});
|
|
21599
|
+
if (this.usesDefaultPath) {
|
|
21600
|
+
const home = resolveHomeNamespace();
|
|
21601
|
+
assertActiveHomeNamespacePath(this.path);
|
|
21602
|
+
await assertNoSymlinkPath(dirname10(home), home);
|
|
21603
|
+
}
|
|
21604
|
+
await mkdir10(dirname10(this.path), { recursive: true, mode: 448 });
|
|
21605
|
+
if (this.usesDefaultPath) {
|
|
21606
|
+
const home = resolveHomeNamespace();
|
|
21607
|
+
await assertNoSymlinkPath(dirname10(home), home);
|
|
21608
|
+
}
|
|
21609
|
+
await atomicWrite(
|
|
21610
|
+
this.path,
|
|
21611
|
+
`${JSON.stringify({ version: 1, entries: entries.slice(-1e3) }, null, 2)}
|
|
21612
|
+
`,
|
|
21613
|
+
384
|
|
21614
|
+
);
|
|
21615
|
+
};
|
|
21616
|
+
return this.usesDefaultPath ? withNamespaceLease(homeNamespacePaths().canonical, "shared", operation) : operation();
|
|
21617
|
+
}
|
|
21618
|
+
async read() {
|
|
21619
|
+
try {
|
|
21620
|
+
const info = await lstat21(this.path);
|
|
21621
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 1e6) {
|
|
21622
|
+
return { version: 1, entries: [] };
|
|
21623
|
+
}
|
|
21624
|
+
return trustRegistrySchema.parse(JSON.parse(await readFile20(this.path, "utf8")));
|
|
21625
|
+
} catch {
|
|
21626
|
+
return { version: 1, entries: [] };
|
|
21627
|
+
}
|
|
21628
|
+
}
|
|
21629
|
+
};
|
|
21630
|
+
async function resolveWorkspace(workspace) {
|
|
21631
|
+
return realpath9(resolve24(workspace)).catch(() => resolve24(workspace));
|
|
21632
|
+
}
|
|
21633
|
+
|
|
21150
21634
|
// src/mcp/manager.ts
|
|
21151
21635
|
var MAX_SERVERS = 32;
|
|
21152
21636
|
var MAX_TOOLS_PER_SERVER = 256;
|
|
@@ -21158,6 +21642,9 @@ var McpManager = class {
|
|
|
21158
21642
|
constructor(config, options = {}) {
|
|
21159
21643
|
this.config = config;
|
|
21160
21644
|
this.options = options;
|
|
21645
|
+
this.trustStore = options.trustStore ?? new McpTrustStore();
|
|
21646
|
+
this.workspace = options.cwd ?? process.cwd();
|
|
21647
|
+
this.requireTrust = options.requireTrust !== false;
|
|
21161
21648
|
const entries = Object.entries(config.servers ?? {});
|
|
21162
21649
|
for (const [index, [name, server]] of entries.entries()) {
|
|
21163
21650
|
const transport = server.transport ?? "stdio";
|
|
@@ -21167,12 +21654,22 @@ var McpManager = class {
|
|
|
21167
21654
|
state: "error",
|
|
21168
21655
|
transport,
|
|
21169
21656
|
toolCount: 0,
|
|
21657
|
+
required: server.required === true,
|
|
21658
|
+
trust: "untrusted",
|
|
21170
21659
|
error: `MCP server limit exceeded (maximum ${MAX_SERVERS})`
|
|
21171
21660
|
});
|
|
21172
21661
|
continue;
|
|
21173
21662
|
}
|
|
21174
|
-
const
|
|
21175
|
-
|
|
21663
|
+
const disabled = config.enabled === false || server.enabled === false;
|
|
21664
|
+
const state = disabled ? "disabled" : this.requireTrust ? "untrusted" : "disconnected";
|
|
21665
|
+
this.statuses.set(name, {
|
|
21666
|
+
name,
|
|
21667
|
+
state,
|
|
21668
|
+
transport,
|
|
21669
|
+
toolCount: 0,
|
|
21670
|
+
required: server.required === true,
|
|
21671
|
+
trust: disabled ? "disabled" : this.requireTrust ? "untrusted" : "trusted"
|
|
21672
|
+
});
|
|
21176
21673
|
}
|
|
21177
21674
|
}
|
|
21178
21675
|
config;
|
|
@@ -21182,12 +21679,108 @@ var McpManager = class {
|
|
|
21182
21679
|
statuses = /* @__PURE__ */ new Map();
|
|
21183
21680
|
toolOwners = /* @__PURE__ */ new Map();
|
|
21184
21681
|
stableAdapters = /* @__PURE__ */ new Map();
|
|
21682
|
+
registries = /* @__PURE__ */ new Set();
|
|
21185
21683
|
options;
|
|
21684
|
+
trustStore;
|
|
21685
|
+
workspace;
|
|
21686
|
+
requireTrust;
|
|
21186
21687
|
shutdownController = new AbortController();
|
|
21688
|
+
trustLoaded = false;
|
|
21187
21689
|
closed = false;
|
|
21690
|
+
/** Load persisted trust and prove availability for explicitly required servers. */
|
|
21691
|
+
async initialize(signal) {
|
|
21692
|
+
await this.ensureTrustLoaded();
|
|
21693
|
+
const required = [...this.statuses.values()].filter((status) => status.required && status.state !== "disabled").map((status) => status.name);
|
|
21694
|
+
const failures = [];
|
|
21695
|
+
for (const name of required) {
|
|
21696
|
+
const status = this.statuses.get(name);
|
|
21697
|
+
if (status?.trust !== "trusted") {
|
|
21698
|
+
failures.push(`${name}: capability manifest is ${status?.trust ?? "untrusted"}`);
|
|
21699
|
+
continue;
|
|
21700
|
+
}
|
|
21701
|
+
const result = await this.connect(name, signal);
|
|
21702
|
+
if (!result.ok) failures.push(`${name}: ${result.status.error ?? result.status.state}`);
|
|
21703
|
+
}
|
|
21704
|
+
if (failures.length) {
|
|
21705
|
+
throw new Error(`Required MCP server unavailable: ${failures.join("; ")}`);
|
|
21706
|
+
}
|
|
21707
|
+
}
|
|
21708
|
+
/** Refresh local trust state without connecting; used by status and review UIs. */
|
|
21709
|
+
async loadTrust() {
|
|
21710
|
+
await this.ensureTrustLoaded();
|
|
21711
|
+
}
|
|
21712
|
+
/** Compact, no-network capability controls advertised before remote schemas. */
|
|
21713
|
+
catalogTools(registry) {
|
|
21714
|
+
const names = this.catalogServerNames();
|
|
21715
|
+
if (!names.length) return [];
|
|
21716
|
+
const search = {
|
|
21717
|
+
definition: {
|
|
21718
|
+
name: "mcp_search",
|
|
21719
|
+
description: "Search configured MCP capability manifests without connecting to a server or loading remote schemas.",
|
|
21720
|
+
category: "read",
|
|
21721
|
+
source: "mcp",
|
|
21722
|
+
permissionCategories: ["read"],
|
|
21723
|
+
activation: "catalog",
|
|
21724
|
+
inputSchema: {
|
|
21725
|
+
type: "object",
|
|
21726
|
+
properties: {
|
|
21727
|
+
query: { type: "string", maxLength: 500, description: "Capability, tool, permission, or server to find." }
|
|
21728
|
+
},
|
|
21729
|
+
required: ["query"],
|
|
21730
|
+
additionalProperties: false
|
|
21731
|
+
}
|
|
21732
|
+
},
|
|
21733
|
+
permissionCategories: () => ["read"],
|
|
21734
|
+
execute: async (arguments_) => {
|
|
21735
|
+
const query = typeof arguments_.query === "string" ? arguments_.query.trim() : "";
|
|
21736
|
+
if (query.length > 500) throw new ToolInputError("MCP search query must be at most 500 characters");
|
|
21737
|
+
await this.ensureTrustLoaded();
|
|
21738
|
+
const results = this.search(query);
|
|
21739
|
+
return {
|
|
21740
|
+
content: results.length ? results.map((result) => `${result.name}: ${result.description} (${result.trust}, ${result.declaredTools || "dynamic"} tools)`).join("\n") : "No configured MCP capability matched the query.",
|
|
21741
|
+
metadata: { mcpSearch: results }
|
|
21742
|
+
};
|
|
21743
|
+
}
|
|
21744
|
+
};
|
|
21745
|
+
const inspect = {
|
|
21746
|
+
definition: {
|
|
21747
|
+
name: "mcp_inspect",
|
|
21748
|
+
description: "Inspect one redacted declarative MCP capability manifest and its local trust state without connecting.",
|
|
21749
|
+
category: "read",
|
|
21750
|
+
source: "mcp",
|
|
21751
|
+
permissionCategories: ["read"],
|
|
21752
|
+
activation: "catalog",
|
|
21753
|
+
inputSchema: {
|
|
21754
|
+
type: "object",
|
|
21755
|
+
properties: {
|
|
21756
|
+
server: { type: "string", enum: names, description: "Configured MCP server to inspect." }
|
|
21757
|
+
},
|
|
21758
|
+
required: ["server"],
|
|
21759
|
+
additionalProperties: false
|
|
21760
|
+
}
|
|
21761
|
+
},
|
|
21762
|
+
permissionCategories: () => ["read"],
|
|
21763
|
+
execute: async (arguments_) => {
|
|
21764
|
+
const server = typeof arguments_.server === "string" ? arguments_.server : "";
|
|
21765
|
+
if (!names.includes(server)) throw new ToolInputError("MCP server is not available for inspection");
|
|
21766
|
+
await this.ensureTrustLoaded();
|
|
21767
|
+
const manifest = this.inspect(server);
|
|
21768
|
+
return {
|
|
21769
|
+
content: JSON.stringify({ manifest, trust: this.status(server)?.trust ?? "untrusted" }, null, 2),
|
|
21770
|
+
metadata: {
|
|
21771
|
+
mcpServer: server,
|
|
21772
|
+
trust: this.status(server)?.trust ?? "untrusted",
|
|
21773
|
+
manifestFingerprint: this.fingerprint(server)
|
|
21774
|
+
}
|
|
21775
|
+
};
|
|
21776
|
+
}
|
|
21777
|
+
};
|
|
21778
|
+
const activation = this.activationTool(registry);
|
|
21779
|
+
return activation ? [search, inspect, activation] : [search, inspect];
|
|
21780
|
+
}
|
|
21188
21781
|
/** Compact model-visible catalog; transport and remote discovery stay lazy. */
|
|
21189
21782
|
activationTool(registry) {
|
|
21190
|
-
const names = this.
|
|
21783
|
+
const names = this.catalogServerNames();
|
|
21191
21784
|
if (!names.length) return;
|
|
21192
21785
|
const catalog = names.map((name) => {
|
|
21193
21786
|
const server = this.config.servers[name];
|
|
@@ -21197,8 +21790,11 @@ var McpManager = class {
|
|
|
21197
21790
|
return {
|
|
21198
21791
|
definition: {
|
|
21199
21792
|
name: "mcp_activate",
|
|
21200
|
-
description: `
|
|
21793
|
+
description: `Activate one already trusted MCP capability after mcp_search and mcp_inspect, then load at most ${LAZY_SCHEMA_LIMIT} relevant schemas. This tool cannot grant trust. Available servers: ${catalog}`,
|
|
21201
21794
|
category: "network",
|
|
21795
|
+
source: "mcp",
|
|
21796
|
+
permissionCategories: ["network"],
|
|
21797
|
+
activation: "catalog",
|
|
21202
21798
|
inputSchema: {
|
|
21203
21799
|
type: "object",
|
|
21204
21800
|
properties: {
|
|
@@ -21219,9 +21815,10 @@ var McpManager = class {
|
|
|
21219
21815
|
}
|
|
21220
21816
|
const result = await this.activate(server, query, registry, context.signal);
|
|
21221
21817
|
if (!result.ok) {
|
|
21818
|
+
const trust = result.status.trust;
|
|
21222
21819
|
return {
|
|
21223
21820
|
ok: false,
|
|
21224
|
-
content: `MCP server ${server} could not be activated: ${result.status.error ?? result.status.state}`,
|
|
21821
|
+
content: trust !== "trusted" ? `MCP server ${server} is ${trust}. Review it with mcp_inspect; only the user can trust it with the CLI or /mcp trust confirmation flow.` : `MCP server ${server} could not be activated: ${result.status.error ?? result.status.state}`,
|
|
21225
21822
|
metadata: activationMetadata(result)
|
|
21226
21823
|
};
|
|
21227
21824
|
}
|
|
@@ -21236,6 +21833,7 @@ var McpManager = class {
|
|
|
21236
21833
|
}
|
|
21237
21834
|
/** Connect/discover one server, then register only request-relevant schemas. */
|
|
21238
21835
|
async activate(name, query, registry, signal) {
|
|
21836
|
+
await this.ensureTrustLoaded();
|
|
21239
21837
|
const connected = await this.connect(name, signal);
|
|
21240
21838
|
if (!connected.ok) {
|
|
21241
21839
|
return { ...connected, registeredTools: [], availableTools: 0, deferredTools: 0 };
|
|
@@ -21247,16 +21845,27 @@ var McpManager = class {
|
|
|
21247
21845
|
const tools = [...connection.tools.values()];
|
|
21248
21846
|
const selected = tools.length <= LAZY_SCHEMA_LIMIT ? tools : selectRelevantTools(tools, query, LAZY_SCHEMA_LIMIT);
|
|
21249
21847
|
this.registerSelectedTools(registry, selected);
|
|
21848
|
+
const ranked = rankRelevantTools(tools, query);
|
|
21849
|
+
const eagerTokens = estimateTokens(JSON.stringify(tools.map((tool) => tool.definition)));
|
|
21850
|
+
const loadedTokens = estimateTokens(JSON.stringify(selected.map((tool) => tool.definition)));
|
|
21250
21851
|
return {
|
|
21251
21852
|
...connected,
|
|
21252
21853
|
registeredTools: selected.map((tool) => tool.definition.name),
|
|
21253
21854
|
availableTools: tools.length,
|
|
21254
|
-
deferredTools: Math.max(0, tools.length - selected.length)
|
|
21855
|
+
deferredTools: Math.max(0, tools.length - selected.length),
|
|
21856
|
+
schemaBudget: {
|
|
21857
|
+
eagerTokens,
|
|
21858
|
+
loadedTokens,
|
|
21859
|
+
savedTokens: Math.max(0, eagerTokens - loadedTokens),
|
|
21860
|
+
topMatch: ranked[0]?.tool.definition.name ?? null,
|
|
21861
|
+
queryMatched: (ranked[0]?.score ?? 0) > 0
|
|
21862
|
+
}
|
|
21255
21863
|
};
|
|
21256
21864
|
}
|
|
21257
21865
|
/** Connect enabled servers with a small concurrency bound. */
|
|
21258
21866
|
async connectAll(signal) {
|
|
21259
21867
|
if (this.closed) throw new Error("MCP manager is closed");
|
|
21868
|
+
await this.ensureTrustLoaded();
|
|
21260
21869
|
const configuredNames = Object.keys(this.config.servers ?? {});
|
|
21261
21870
|
const names = configuredNames.slice(0, MAX_SERVERS);
|
|
21262
21871
|
if (this.config.enabled === false) {
|
|
@@ -21277,10 +21886,14 @@ var McpManager = class {
|
|
|
21277
21886
|
/** Connect one configured server. Connection errors are captured in status. */
|
|
21278
21887
|
async connect(name, signal) {
|
|
21279
21888
|
if (this.closed) throw new Error("MCP manager is closed");
|
|
21889
|
+
await this.ensureTrustLoaded();
|
|
21280
21890
|
const status = this.statuses.get(name);
|
|
21281
21891
|
if (status?.state === "error" && status.error?.includes("server limit exceeded")) {
|
|
21282
21892
|
return this.resultFor(name, false, 0);
|
|
21283
21893
|
}
|
|
21894
|
+
if (this.requireTrust && status?.trust !== "trusted") {
|
|
21895
|
+
return this.resultFor(name, false, 0);
|
|
21896
|
+
}
|
|
21284
21897
|
const existing = this.pending.get(name);
|
|
21285
21898
|
if (existing) return existing;
|
|
21286
21899
|
const connectionController = new AbortController();
|
|
@@ -21308,9 +21921,11 @@ var McpManager = class {
|
|
|
21308
21921
|
if (!current) throw new Error(`Unknown MCP server: ${name}`);
|
|
21309
21922
|
const status = {
|
|
21310
21923
|
name,
|
|
21311
|
-
state: current.state === "disabled"
|
|
21924
|
+
state: current.state === "disabled" || current.state === "revoked" || current.state === "untrusted" ? current.state : "disconnected",
|
|
21312
21925
|
transport: current.transport,
|
|
21313
|
-
toolCount: 0
|
|
21926
|
+
toolCount: 0,
|
|
21927
|
+
required: current.required,
|
|
21928
|
+
trust: current.trust
|
|
21314
21929
|
};
|
|
21315
21930
|
this.statuses.set(name, status);
|
|
21316
21931
|
return status;
|
|
@@ -21320,7 +21935,9 @@ var McpManager = class {
|
|
|
21320
21935
|
if (this.closed) throw new Error("MCP manager is closed");
|
|
21321
21936
|
const status = this.statuses.get(name);
|
|
21322
21937
|
if (!status) throw new Error(`Unknown MCP server: ${name}`);
|
|
21323
|
-
if (status.state === "disabled"
|
|
21938
|
+
if (status.state === "disabled" || status.state === "revoked" || status.state === "untrusted") {
|
|
21939
|
+
return this.resultFor(name, false, 0);
|
|
21940
|
+
}
|
|
21324
21941
|
const pending = this.pending.get(name);
|
|
21325
21942
|
if (pending) await pending;
|
|
21326
21943
|
await this.disconnect(name);
|
|
@@ -21344,9 +21961,11 @@ var McpManager = class {
|
|
|
21344
21961
|
for (const [name, status] of this.statuses) {
|
|
21345
21962
|
this.statuses.set(name, {
|
|
21346
21963
|
name,
|
|
21347
|
-
state: status.state === "disabled"
|
|
21964
|
+
state: status.state === "disabled" || status.state === "revoked" || status.state === "untrusted" ? status.state : "closed",
|
|
21348
21965
|
transport: status.transport,
|
|
21349
|
-
toolCount: 0
|
|
21966
|
+
toolCount: 0,
|
|
21967
|
+
required: status.required,
|
|
21968
|
+
trust: status.trust
|
|
21350
21969
|
});
|
|
21351
21970
|
}
|
|
21352
21971
|
}
|
|
@@ -21357,6 +21976,71 @@ var McpManager = class {
|
|
|
21357
21976
|
const status = this.statuses.get(name);
|
|
21358
21977
|
return status ? { ...status } : void 0;
|
|
21359
21978
|
}
|
|
21979
|
+
search(query = "") {
|
|
21980
|
+
return searchMcpCapabilities(this.config, query).map((result) => {
|
|
21981
|
+
const status = this.statuses.get(result.name);
|
|
21982
|
+
return {
|
|
21983
|
+
...result,
|
|
21984
|
+
state: status?.state ?? "error",
|
|
21985
|
+
trust: status?.trust ?? "untrusted"
|
|
21986
|
+
};
|
|
21987
|
+
});
|
|
21988
|
+
}
|
|
21989
|
+
inspect(name) {
|
|
21990
|
+
const server = this.config.servers?.[name];
|
|
21991
|
+
if (!server) throw new Error(`Unknown MCP server: ${name}`);
|
|
21992
|
+
return buildMcpCapabilityManifest(name, server, this.workspace);
|
|
21993
|
+
}
|
|
21994
|
+
fingerprint(name) {
|
|
21995
|
+
const server = this.config.servers?.[name];
|
|
21996
|
+
if (!server) throw new Error(`Unknown MCP server: ${name}`);
|
|
21997
|
+
return capabilityFingerprint(name, server, this.workspace);
|
|
21998
|
+
}
|
|
21999
|
+
async trust(name) {
|
|
22000
|
+
const server = this.config.servers?.[name];
|
|
22001
|
+
if (!server) throw new Error(`Unknown MCP server: ${name}`);
|
|
22002
|
+
if (this.config.enabled === false || server.enabled === false) {
|
|
22003
|
+
throw new Error(`MCP server is disabled by configuration: ${name}`);
|
|
22004
|
+
}
|
|
22005
|
+
if (this.inspect(name).dynamicTools) {
|
|
22006
|
+
throw new Error(`MCP capability ${name} cannot be trusted until its tools and effects are declared.`);
|
|
22007
|
+
}
|
|
22008
|
+
await this.trustStore.trust(this.workspace, name, this.fingerprint(name));
|
|
22009
|
+
this.trustLoaded = true;
|
|
22010
|
+
return this.setStatus(name, {
|
|
22011
|
+
state: "disconnected",
|
|
22012
|
+
trust: "trusted",
|
|
22013
|
+
required: server.required === true,
|
|
22014
|
+
transport: server.transport ?? "stdio",
|
|
22015
|
+
toolCount: 0
|
|
22016
|
+
});
|
|
22017
|
+
}
|
|
22018
|
+
async disable(name) {
|
|
22019
|
+
const server = this.config.servers?.[name];
|
|
22020
|
+
if (!server) throw new Error(`Unknown MCP server: ${name}`);
|
|
22021
|
+
await this.disconnect(name);
|
|
22022
|
+
this.unregisterServerTools(name);
|
|
22023
|
+
await this.trustStore.disable(this.workspace, name, this.fingerprint(name));
|
|
22024
|
+
return this.setStatus(name, {
|
|
22025
|
+
state: "disabled",
|
|
22026
|
+
trust: "disabled",
|
|
22027
|
+
required: server.required === true,
|
|
22028
|
+
toolCount: 0
|
|
22029
|
+
});
|
|
22030
|
+
}
|
|
22031
|
+
async revoke(name) {
|
|
22032
|
+
const server = this.config.servers?.[name];
|
|
22033
|
+
if (!server) throw new Error(`Unknown MCP server: ${name}`);
|
|
22034
|
+
await this.disconnect(name);
|
|
22035
|
+
this.unregisterServerTools(name);
|
|
22036
|
+
await this.trustStore.revoke(this.workspace, name, this.fingerprint(name));
|
|
22037
|
+
return this.setStatus(name, {
|
|
22038
|
+
state: "revoked",
|
|
22039
|
+
trust: "revoked",
|
|
22040
|
+
required: server.required === true,
|
|
22041
|
+
toolCount: 0
|
|
22042
|
+
});
|
|
22043
|
+
}
|
|
21360
22044
|
tools() {
|
|
21361
22045
|
return [...this.connections.values()].flatMap((connection) => [...connection.tools.values()]).sort((a, b) => a.definition.name.localeCompare(b.definition.name));
|
|
21362
22046
|
}
|
|
@@ -21368,6 +22052,7 @@ var McpManager = class {
|
|
|
21368
22052
|
return this.registerSelectedTools(registry, this.tools());
|
|
21369
22053
|
}
|
|
21370
22054
|
registerSelectedTools(registry, tools) {
|
|
22055
|
+
this.registries.add(registry);
|
|
21371
22056
|
const registered = [];
|
|
21372
22057
|
for (const tool of tools) {
|
|
21373
22058
|
const existing = registry.get(tool.definition.name);
|
|
@@ -21382,9 +22067,38 @@ var McpManager = class {
|
|
|
21382
22067
|
}
|
|
21383
22068
|
return registered;
|
|
21384
22069
|
}
|
|
21385
|
-
|
|
21386
|
-
|
|
21387
|
-
|
|
22070
|
+
unregisterServerTools(name) {
|
|
22071
|
+
for (const [identity2, tool] of this.stableAdapters) {
|
|
22072
|
+
if (!identity2.startsWith(`${name}\0`)) continue;
|
|
22073
|
+
for (const registry of this.registries) {
|
|
22074
|
+
registry.unregister(tool.definition.name, tool);
|
|
22075
|
+
}
|
|
22076
|
+
}
|
|
22077
|
+
}
|
|
22078
|
+
catalogServerNames() {
|
|
22079
|
+
return [...this.statuses.values()].filter((status) => !status.error?.includes("server limit exceeded")).map((status) => status.name).sort((left, right) => left.localeCompare(right));
|
|
22080
|
+
}
|
|
22081
|
+
async ensureTrustLoaded() {
|
|
22082
|
+
if (this.trustLoaded) return;
|
|
22083
|
+
for (const [name, server] of Object.entries(this.config.servers ?? {}).slice(0, MAX_SERVERS)) {
|
|
22084
|
+
const current = this.statuses.get(name);
|
|
22085
|
+
if (!current) continue;
|
|
22086
|
+
if (this.config.enabled === false || server.enabled === false) {
|
|
22087
|
+
this.setStatus(name, { state: "disabled", trust: "disabled", toolCount: 0 });
|
|
22088
|
+
continue;
|
|
22089
|
+
}
|
|
22090
|
+
if (!this.requireTrust) {
|
|
22091
|
+
this.setStatus(name, { state: "disconnected", trust: "trusted", toolCount: 0 });
|
|
22092
|
+
continue;
|
|
22093
|
+
}
|
|
22094
|
+
const trust = await this.trustStore.state(this.workspace, name, this.fingerprint(name));
|
|
22095
|
+
this.setStatus(name, {
|
|
22096
|
+
state: trust === "trusted" ? "disconnected" : trust,
|
|
22097
|
+
trust,
|
|
22098
|
+
toolCount: 0
|
|
22099
|
+
});
|
|
22100
|
+
}
|
|
22101
|
+
this.trustLoaded = true;
|
|
21388
22102
|
}
|
|
21389
22103
|
async connectInternal(name, signal) {
|
|
21390
22104
|
const configured = this.config.servers?.[name];
|
|
@@ -21400,7 +22114,12 @@ var McpManager = class {
|
|
|
21400
22114
|
return { name, ok: false, status, skippedTools: 0 };
|
|
21401
22115
|
}
|
|
21402
22116
|
if (this.config.enabled === false || configured.enabled === false) {
|
|
21403
|
-
const status = this.setStatus(name, {
|
|
22117
|
+
const status = this.setStatus(name, {
|
|
22118
|
+
state: "disabled",
|
|
22119
|
+
trust: "disabled",
|
|
22120
|
+
transport: transportKind,
|
|
22121
|
+
toolCount: 0
|
|
22122
|
+
});
|
|
21404
22123
|
return { name, ok: false, status, skippedTools: 0 };
|
|
21405
22124
|
}
|
|
21406
22125
|
if (this.connections.has(name)) {
|
|
@@ -21450,6 +22169,7 @@ var McpManager = class {
|
|
|
21450
22169
|
const listed = await this.listRemoteTools(client, timeoutMs, signal);
|
|
21451
22170
|
if (closedDuringConnect) throw new Error("MCP server closed during connection setup");
|
|
21452
22171
|
const toolMap = this.buildAdapters(name, configured, listed.tools);
|
|
22172
|
+
const undeclaredTools = configured.tools?.length ? listed.tools.filter((tool) => !configured.tools?.some((declared) => declared.name === tool.name)).length : 0;
|
|
21453
22173
|
const connection = {
|
|
21454
22174
|
name,
|
|
21455
22175
|
client,
|
|
@@ -21471,7 +22191,12 @@ var McpManager = class {
|
|
|
21471
22191
|
);
|
|
21472
22192
|
}
|
|
21473
22193
|
const status = this.setStatus(name, statusPatch);
|
|
21474
|
-
return {
|
|
22194
|
+
return {
|
|
22195
|
+
name,
|
|
22196
|
+
ok: true,
|
|
22197
|
+
status,
|
|
22198
|
+
skippedTools: listed.skippedTools + listed.truncatedTools + undeclaredTools
|
|
22199
|
+
};
|
|
21475
22200
|
} catch (error) {
|
|
21476
22201
|
if (client) await closeQuietly(client);
|
|
21477
22202
|
else if (transport) await closeTransportQuietly(transport);
|
|
@@ -21510,7 +22235,7 @@ var McpManager = class {
|
|
|
21510
22235
|
stderr: "pipe"
|
|
21511
22236
|
});
|
|
21512
22237
|
transport.stderr?.on("data", (chunk) => {
|
|
21513
|
-
const text =
|
|
22238
|
+
const text = stripAnsi6(chunk.toString()).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").trim();
|
|
21514
22239
|
if (text) this.options.logger?.(`MCP ${name} stderr`, { text: text.slice(0, 2e3) });
|
|
21515
22240
|
});
|
|
21516
22241
|
return transport;
|
|
@@ -21560,6 +22285,8 @@ var McpManager = class {
|
|
|
21560
22285
|
DEFAULT_TOOL_TIMEOUT
|
|
21561
22286
|
);
|
|
21562
22287
|
for (const remoteTool of remoteTools) {
|
|
22288
|
+
const capability = declaredToolCapability(config, remoteTool.name, this.workspace);
|
|
22289
|
+
if (config.tools?.length && !capability) continue;
|
|
21563
22290
|
let exposedName = makeMcpToolName(namespace, remoteTool.name);
|
|
21564
22291
|
const identity2 = `${serverName}\0${remoteTool.name}`;
|
|
21565
22292
|
if (seen.has(exposedName) || this.isToolNameOwnedByAnother(exposedName, identity2)) {
|
|
@@ -21589,7 +22316,8 @@ var McpManager = class {
|
|
|
21589
22316
|
exposedName,
|
|
21590
22317
|
remoteTool,
|
|
21591
22318
|
timeoutMs,
|
|
21592
|
-
callTool
|
|
22319
|
+
callTool,
|
|
22320
|
+
...capability ? { capability } : {}
|
|
21593
22321
|
});
|
|
21594
22322
|
this.stableAdapters.set(identity2, adapter);
|
|
21595
22323
|
}
|
|
@@ -21624,6 +22352,8 @@ var McpManager = class {
|
|
|
21624
22352
|
state: patch.state ?? current?.state ?? "disconnected",
|
|
21625
22353
|
transport: patch.transport ?? current?.transport ?? "stdio",
|
|
21626
22354
|
toolCount: patch.toolCount ?? current?.toolCount ?? 0,
|
|
22355
|
+
required: patch.required ?? current?.required ?? false,
|
|
22356
|
+
trust: patch.trust ?? current?.trust ?? "untrusted",
|
|
21627
22357
|
...patch.connectedAt !== void 0 ? { connectedAt: patch.connectedAt } : current?.connectedAt !== void 0 ? { connectedAt: current.connectedAt } : {},
|
|
21628
22358
|
...patch.serverVersion !== void 0 ? { serverVersion: patch.serverVersion } : current?.serverVersion !== void 0 ? { serverVersion: current.serverVersion } : {},
|
|
21629
22359
|
...patch.error !== void 0 ? { error: patch.error } : {}
|
|
@@ -21644,20 +22374,24 @@ function activationMetadata(result) {
|
|
|
21644
22374
|
availableTools: result.availableTools,
|
|
21645
22375
|
loadedTools: result.registeredTools,
|
|
21646
22376
|
deferredTools: result.deferredTools,
|
|
21647
|
-
skippedTools: result.skippedTools
|
|
22377
|
+
skippedTools: result.skippedTools,
|
|
22378
|
+
...result.schemaBudget ? { schemaBudget: result.schemaBudget } : {}
|
|
21648
22379
|
};
|
|
21649
22380
|
}
|
|
21650
22381
|
function selectRelevantTools(tools, query, limit) {
|
|
22382
|
+
return rankRelevantTools(tools, query).slice(0, limit).map(({ tool }) => tool);
|
|
22383
|
+
}
|
|
22384
|
+
function rankRelevantTools(tools, query) {
|
|
21651
22385
|
const terms = new Set(query.toLocaleLowerCase().match(/[\p{L}\p{N}_-]{2,}/gu) ?? []);
|
|
21652
22386
|
return tools.map((tool) => {
|
|
21653
22387
|
const searchable = `${tool.definition.name.replaceAll("_", " ")} ${tool.definition.description}`.toLocaleLowerCase();
|
|
21654
22388
|
let score = 0;
|
|
21655
22389
|
for (const term of terms) if (searchable.includes(term)) score += term.length;
|
|
21656
22390
|
return { tool, score };
|
|
21657
|
-
}).sort((left, right) => right.score - left.score || left.tool.definition.name.localeCompare(right.tool.definition.name))
|
|
22391
|
+
}).sort((left, right) => right.score - left.score || left.tool.definition.name.localeCompare(right.tool.definition.name));
|
|
21658
22392
|
}
|
|
21659
22393
|
function sanitizeCatalogText(value) {
|
|
21660
|
-
return value ?
|
|
22394
|
+
return value ? stripAnsi6(value).replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim().slice(0, 200) : "";
|
|
21661
22395
|
}
|
|
21662
22396
|
function requestOptions(timeoutMs, signal) {
|
|
21663
22397
|
return {
|
|
@@ -21691,7 +22425,7 @@ function errorMessage2(error) {
|
|
|
21691
22425
|
return sanitizeStatusText(message2) || "Unknown MCP error";
|
|
21692
22426
|
}
|
|
21693
22427
|
function sanitizeStatusText(value) {
|
|
21694
|
-
return
|
|
22428
|
+
return stripAnsi6(value).replace(/[\u0000-\u001f\u007f]/g, " ").trim().slice(0, 1e3);
|
|
21695
22429
|
}
|
|
21696
22430
|
function timeoutError(timeoutMs) {
|
|
21697
22431
|
const error = new Error(`MCP request timed out after ${timeoutMs} ms`);
|
|
@@ -21717,9 +22451,9 @@ async function closeTransportQuietly(transport) {
|
|
|
21717
22451
|
}
|
|
21718
22452
|
|
|
21719
22453
|
// src/memory/tools.ts
|
|
21720
|
-
import { z as
|
|
21721
|
-
var scopeSchema =
|
|
21722
|
-
var kindSchema =
|
|
22454
|
+
import { z as z21 } from "zod";
|
|
22455
|
+
var scopeSchema = z21.enum(["user", "workspace", "session", "agent"]);
|
|
22456
|
+
var kindSchema = z21.enum(["semantic", "episodic", "procedural"]);
|
|
21723
22457
|
function createMemoryTools(store) {
|
|
21724
22458
|
return [
|
|
21725
22459
|
{
|
|
@@ -21727,6 +22461,8 @@ function createMemoryTools(store) {
|
|
|
21727
22461
|
name: "memory_search",
|
|
21728
22462
|
description: "Search durable user, workspace, session, and agent memories relevant to the task.",
|
|
21729
22463
|
category: "read",
|
|
22464
|
+
source: "memory",
|
|
22465
|
+
activation: "always",
|
|
21730
22466
|
inputSchema: jsonSchema({
|
|
21731
22467
|
query: { type: "string", description: "Natural-language memory query." },
|
|
21732
22468
|
scope: { type: "string", enum: ["user", "workspace", "session", "agent"] },
|
|
@@ -21734,10 +22470,10 @@ function createMemoryTools(store) {
|
|
|
21734
22470
|
}, ["query"])
|
|
21735
22471
|
},
|
|
21736
22472
|
async execute(arguments_, context) {
|
|
21737
|
-
const input2 =
|
|
21738
|
-
query:
|
|
22473
|
+
const input2 = z21.object({
|
|
22474
|
+
query: z21.string().max(4e3),
|
|
21739
22475
|
scope: scopeSchema.optional(),
|
|
21740
|
-
limit:
|
|
22476
|
+
limit: z21.number().int().min(1).max(20).optional()
|
|
21741
22477
|
}).parse(arguments_);
|
|
21742
22478
|
const scopes = input2.scope ? [{ scope: input2.scope, scopeKey: scopeKey(input2.scope, context) }] : availableScopes(context);
|
|
21743
22479
|
const records = store.search(input2.query, { scopes, limit: input2.limit ?? 8 });
|
|
@@ -21755,6 +22491,8 @@ ${record.content}`
|
|
|
21755
22491
|
name: "memory_propose",
|
|
21756
22492
|
description: "Propose a non-secret durable memory for user review. The candidate remains inactive until the user explicitly approves it.",
|
|
21757
22493
|
category: "write",
|
|
22494
|
+
source: "memory",
|
|
22495
|
+
activation: "always",
|
|
21758
22496
|
inputSchema: jsonSchema({
|
|
21759
22497
|
content: { type: "string", description: "Concise fact, preference, experience, or procedure to propose. Never include secrets." },
|
|
21760
22498
|
rationale: { type: "string", description: "Why this is useful beyond the current response and what evidence supports it." },
|
|
@@ -21769,17 +22507,17 @@ ${record.content}`
|
|
|
21769
22507
|
}, ["content", "rationale"])
|
|
21770
22508
|
},
|
|
21771
22509
|
async execute(arguments_, context) {
|
|
21772
|
-
const input2 =
|
|
21773
|
-
content:
|
|
21774
|
-
rationale:
|
|
22510
|
+
const input2 = z21.object({
|
|
22511
|
+
content: z21.string().min(1).max(12e3),
|
|
22512
|
+
rationale: z21.string().min(1).max(1e3),
|
|
21775
22513
|
scope: scopeSchema.optional(),
|
|
21776
22514
|
kind: kindSchema.optional(),
|
|
21777
|
-
tags:
|
|
21778
|
-
importance:
|
|
21779
|
-
confidence:
|
|
21780
|
-
agent:
|
|
21781
|
-
revision:
|
|
21782
|
-
conflictKey:
|
|
22515
|
+
tags: z21.array(z21.string()).max(24).optional(),
|
|
22516
|
+
importance: z21.number().min(0).max(1).optional(),
|
|
22517
|
+
confidence: z21.number().min(0).max(1).optional(),
|
|
22518
|
+
agent: z21.string().max(64).optional(),
|
|
22519
|
+
revision: z21.string().max(240).optional(),
|
|
22520
|
+
conflictKey: z21.string().max(240).optional()
|
|
21783
22521
|
}).strict().parse(arguments_);
|
|
21784
22522
|
const scope = input2.scope ?? "workspace";
|
|
21785
22523
|
const candidate = store.propose({
|
|
@@ -21811,15 +22549,17 @@ ${record.content}`
|
|
|
21811
22549
|
name: "memory_forget",
|
|
21812
22550
|
description: "Archive or permanently delete a memory by id.",
|
|
21813
22551
|
category: "write",
|
|
22552
|
+
source: "memory",
|
|
22553
|
+
activation: "always",
|
|
21814
22554
|
inputSchema: jsonSchema({
|
|
21815
22555
|
id: { type: "string" },
|
|
21816
22556
|
permanent: { type: "boolean" }
|
|
21817
22557
|
}, ["id"])
|
|
21818
22558
|
},
|
|
21819
22559
|
async execute(arguments_) {
|
|
21820
|
-
const input2 =
|
|
21821
|
-
id:
|
|
21822
|
-
permanent:
|
|
22560
|
+
const input2 = z21.object({
|
|
22561
|
+
id: z21.string().uuid(),
|
|
22562
|
+
permanent: z21.boolean().optional()
|
|
21823
22563
|
}).parse(arguments_);
|
|
21824
22564
|
const changed = input2.permanent ? store.remove(input2.id) : store.archive(input2.id);
|
|
21825
22565
|
return {
|
|
@@ -21846,9 +22586,9 @@ function scopeKey(scope, context) {
|
|
|
21846
22586
|
}
|
|
21847
22587
|
|
|
21848
22588
|
// src/skills/catalog.ts
|
|
21849
|
-
import { lstat as
|
|
21850
|
-
import { homedir as
|
|
21851
|
-
import { basename as
|
|
22589
|
+
import { lstat as lstat22, readFile as readFile21, readdir as readdir9, realpath as realpath10 } from "node:fs/promises";
|
|
22590
|
+
import { homedir as homedir5 } from "node:os";
|
|
22591
|
+
import { basename as basename13, join as join22, resolve as resolve25 } from "node:path";
|
|
21852
22592
|
import { parse as parseYaml3 } from "yaml";
|
|
21853
22593
|
var SkillCatalog = class {
|
|
21854
22594
|
constructor(workspace, config) {
|
|
@@ -21868,7 +22608,7 @@ var SkillCatalog = class {
|
|
|
21868
22608
|
for (const location of locations) {
|
|
21869
22609
|
const entries = await safeDirectories(location.path);
|
|
21870
22610
|
for (const entry of entries) {
|
|
21871
|
-
const skillPath =
|
|
22611
|
+
const skillPath = join22(location.path, entry, "SKILL.md");
|
|
21872
22612
|
const metadata = await readMetadata(skillPath);
|
|
21873
22613
|
if (!metadata) continue;
|
|
21874
22614
|
const descriptor = {
|
|
@@ -21916,30 +22656,30 @@ ${skill.content}
|
|
|
21916
22656
|
</active-skills>`;
|
|
21917
22657
|
}
|
|
21918
22658
|
function discoveryLocations(workspace, configured) {
|
|
21919
|
-
const home =
|
|
21920
|
-
const workspaceRoot =
|
|
22659
|
+
const home = homedir5();
|
|
22660
|
+
const workspaceRoot = resolve25(workspace);
|
|
21921
22661
|
return [
|
|
21922
|
-
{ path:
|
|
21923
|
-
{ path:
|
|
21924
|
-
{ path:
|
|
21925
|
-
{ path:
|
|
22662
|
+
{ path: join22(home, ".agents", "skills"), scope: "user", trusted: true },
|
|
22663
|
+
{ path: join22(home, ".claude", "skills"), scope: "user", trusted: true },
|
|
22664
|
+
{ path: join22(home, ".augment", "skills"), scope: "user", trusted: true },
|
|
22665
|
+
{ path: join22(resolveHomeNamespace(), "skills"), scope: "user", trusted: true },
|
|
21926
22666
|
...configured.map((path) => {
|
|
21927
|
-
const resolved =
|
|
22667
|
+
const resolved = resolve25(workspaceRoot, path);
|
|
21928
22668
|
return {
|
|
21929
22669
|
path: resolved,
|
|
21930
22670
|
scope: "configured",
|
|
21931
22671
|
trusted: !isInside(workspaceRoot, resolved)
|
|
21932
22672
|
};
|
|
21933
22673
|
}),
|
|
21934
|
-
{ path:
|
|
21935
|
-
{ path:
|
|
21936
|
-
{ path:
|
|
21937
|
-
{ path:
|
|
22674
|
+
{ path: join22(workspace, ".agents", "skills"), scope: "workspace", trusted: false },
|
|
22675
|
+
{ path: join22(workspace, ".claude", "skills"), scope: "workspace", trusted: false },
|
|
22676
|
+
{ path: join22(workspace, ".augment", "skills"), scope: "workspace", trusted: false },
|
|
22677
|
+
{ path: join22(resolveProjectNamespaceSync(workspaceRoot).active, "skills"), scope: "workspace", trusted: false }
|
|
21938
22678
|
];
|
|
21939
22679
|
}
|
|
21940
22680
|
async function safeDirectories(path) {
|
|
21941
22681
|
try {
|
|
21942
|
-
const info = await
|
|
22682
|
+
const info = await lstat22(path);
|
|
21943
22683
|
if (!info.isDirectory() || info.isSymbolicLink()) return [];
|
|
21944
22684
|
const entries = await readdir9(path, { withFileTypes: true });
|
|
21945
22685
|
return entries.filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).map((entry) => entry.name).slice(0, 500);
|
|
@@ -21953,7 +22693,7 @@ async function readMetadata(path) {
|
|
|
21953
22693
|
const { frontmatter } = splitFrontmatter(raw);
|
|
21954
22694
|
if (!frontmatter) return void 0;
|
|
21955
22695
|
const parsed = parseYaml3(frontmatter);
|
|
21956
|
-
const name = typeof parsed?.name === "string" ? parsed.name.trim() :
|
|
22696
|
+
const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename13(resolve25(path, ".."));
|
|
21957
22697
|
const description = typeof parsed?.description === "string" ? parsed.description.trim() : "";
|
|
21958
22698
|
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || !description || description.length > 1e3) {
|
|
21959
22699
|
return void 0;
|
|
@@ -21972,13 +22712,13 @@ async function readSkill(path, maxChars) {
|
|
|
21972
22712
|
}
|
|
21973
22713
|
async function safeRead(path, maxBytes) {
|
|
21974
22714
|
try {
|
|
21975
|
-
const info = await
|
|
22715
|
+
const info = await lstat22(path);
|
|
21976
22716
|
if (!info.isFile() || info.isSymbolicLink() || info.size > maxBytes) return void 0;
|
|
21977
|
-
const parent =
|
|
21978
|
-
const resolvedParent = await
|
|
21979
|
-
const resolvedPath = await
|
|
22717
|
+
const parent = resolve25(path, "..");
|
|
22718
|
+
const resolvedParent = await realpath10(parent);
|
|
22719
|
+
const resolvedPath = await realpath10(path);
|
|
21980
22720
|
if (!resolvedPath.startsWith(`${resolvedParent}/`)) return void 0;
|
|
21981
|
-
return await
|
|
22721
|
+
return await readFile21(path, "utf8");
|
|
21982
22722
|
} catch {
|
|
21983
22723
|
return void 0;
|
|
21984
22724
|
}
|
|
@@ -22023,7 +22763,7 @@ function escapeAttribute6(value) {
|
|
|
22023
22763
|
}
|
|
22024
22764
|
|
|
22025
22765
|
// src/workflows/catalog.ts
|
|
22026
|
-
import { z as
|
|
22766
|
+
import { z as z22 } from "zod";
|
|
22027
22767
|
var builtInWorkflows = [
|
|
22028
22768
|
{
|
|
22029
22769
|
name: "implement",
|
|
@@ -22108,13 +22848,15 @@ function createWorkflowTool(catalog) {
|
|
|
22108
22848
|
name: "workflow_plan",
|
|
22109
22849
|
description: "Load a built-in typed workflow for implementation, debugging, review, or refactoring.",
|
|
22110
22850
|
category: "read",
|
|
22851
|
+
source: "workflow",
|
|
22852
|
+
activation: "always",
|
|
22111
22853
|
inputSchema: jsonSchema({
|
|
22112
22854
|
name: { type: "string", enum: catalog.list().map((workflow) => workflow.name) },
|
|
22113
22855
|
task: { type: "string" }
|
|
22114
22856
|
}, ["name", "task"])
|
|
22115
22857
|
},
|
|
22116
22858
|
async execute(arguments_) {
|
|
22117
|
-
const input2 =
|
|
22859
|
+
const input2 = z22.object({ name: z22.string(), task: z22.string().min(1).max(2e4) }).parse(arguments_);
|
|
22118
22860
|
const workflow = catalog.get(input2.name);
|
|
22119
22861
|
if (!workflow) return { ok: false, content: `Unknown workflow: ${input2.name}` };
|
|
22120
22862
|
return {
|
|
@@ -22157,9 +22899,10 @@ var ExtensionRuntime = class _ExtensionRuntime {
|
|
|
22157
22899
|
workflows = new WorkflowCatalog();
|
|
22158
22900
|
options;
|
|
22159
22901
|
delegation;
|
|
22902
|
+
registry;
|
|
22160
22903
|
initialized = false;
|
|
22161
22904
|
static async create(config, registry, options = {}) {
|
|
22162
|
-
const runtime = new _ExtensionRuntime(config,
|
|
22905
|
+
const runtime = new _ExtensionRuntime(config, resolve26(config.workspaceRoots[0] ?? process.cwd()), options);
|
|
22163
22906
|
try {
|
|
22164
22907
|
await runtime.initialize(registry, options.signal);
|
|
22165
22908
|
return runtime;
|
|
@@ -22180,8 +22923,8 @@ var ExtensionRuntime = class _ExtensionRuntime {
|
|
|
22180
22923
|
}
|
|
22181
22924
|
await this.skills?.discover();
|
|
22182
22925
|
if (this.mcp) {
|
|
22183
|
-
|
|
22184
|
-
|
|
22926
|
+
await this.mcp.initialize(_signal);
|
|
22927
|
+
for (const tool of this.mcp.catalogTools(registry)) registry.register(tool);
|
|
22185
22928
|
}
|
|
22186
22929
|
await this.profiles?.discover();
|
|
22187
22930
|
if (this.config.agents?.enabled && this.profiles && this.options.provider && this.options.contextEngine) {
|
|
@@ -22202,6 +22945,7 @@ var ExtensionRuntime = class _ExtensionRuntime {
|
|
|
22202
22945
|
}
|
|
22203
22946
|
}
|
|
22204
22947
|
registry.register(createWorkflowTool(this.workflows));
|
|
22948
|
+
this.registry = registry;
|
|
22205
22949
|
this.initialized = true;
|
|
22206
22950
|
}
|
|
22207
22951
|
async prepare(input2, session) {
|
|
@@ -22276,6 +23020,25 @@ var ExtensionRuntime = class _ExtensionRuntime {
|
|
|
22276
23020
|
mcpStatus() {
|
|
22277
23021
|
return this.mcp?.list() ?? [];
|
|
22278
23022
|
}
|
|
23023
|
+
mcpSearch(query = "") {
|
|
23024
|
+
return this.mcp?.search(query) ?? [];
|
|
23025
|
+
}
|
|
23026
|
+
mcpInspect(name) {
|
|
23027
|
+
return this.mcp?.inspect(name);
|
|
23028
|
+
}
|
|
23029
|
+
async mcpTrust(name) {
|
|
23030
|
+
return this.mcp?.trust(name);
|
|
23031
|
+
}
|
|
23032
|
+
async mcpDisable(name) {
|
|
23033
|
+
return this.mcp?.disable(name);
|
|
23034
|
+
}
|
|
23035
|
+
async mcpRevoke(name) {
|
|
23036
|
+
return this.mcp?.revoke(name);
|
|
23037
|
+
}
|
|
23038
|
+
async mcpActivate(name, query, signal) {
|
|
23039
|
+
if (!this.mcp || !this.registry) return;
|
|
23040
|
+
return this.mcp.activate(name, query, this.registry, signal);
|
|
23041
|
+
}
|
|
22279
23042
|
cancelAgent(id) {
|
|
22280
23043
|
return this.delegation?.cancelAgent(id) ?? false;
|
|
22281
23044
|
}
|
|
@@ -22364,15 +23127,15 @@ function upgradeCommandOverride(env = process.env) {
|
|
|
22364
23127
|
return trimmed ? trimmed : void 0;
|
|
22365
23128
|
}
|
|
22366
23129
|
function runUpgrade(plan) {
|
|
22367
|
-
return new Promise((
|
|
23130
|
+
return new Promise((resolve28) => {
|
|
22368
23131
|
const child = spawn2(plan.command, plan.args, {
|
|
22369
23132
|
stdio: "inherit",
|
|
22370
23133
|
shell: plan.shell ?? false
|
|
22371
23134
|
});
|
|
22372
|
-
child.on("error", () =>
|
|
23135
|
+
child.on("error", () => resolve28({ ok: false, exitCode: 127, display: plan.display }));
|
|
22373
23136
|
child.on("close", (code) => {
|
|
22374
23137
|
const exitCode = code ?? 1;
|
|
22375
|
-
|
|
23138
|
+
resolve28({ ok: exitCode === 0, exitCode, display: plan.display });
|
|
22376
23139
|
});
|
|
22377
23140
|
});
|
|
22378
23141
|
}
|
|
@@ -22553,7 +23316,7 @@ program.command("migrate").description("Inspect or migrate legacy .mosaic state
|
|
|
22553
23316
|
process.stdout.write(`${recovery.status === "recovered" ? "Recovered" : "Recovery candidates"}: ${recovery.destination}
|
|
22554
23317
|
`);
|
|
22555
23318
|
for (const candidate of recovery.candidates) {
|
|
22556
|
-
process.stdout.write(` ${
|
|
23319
|
+
process.stdout.write(` ${basename14(candidate.path)} ${candidate.kind} ${candidate.action} ${candidate.detail}
|
|
22557
23320
|
`);
|
|
22558
23321
|
}
|
|
22559
23322
|
if (!options.yes && recovery.status === "ready") {
|
|
@@ -22634,7 +23397,7 @@ sessionCommand.command("export <id>").description("Export a session as Markdown"
|
|
|
22634
23397
|
const store = new SessionStore(workspaceOption(options.workspace));
|
|
22635
23398
|
const session = await requireSessionSelector(store, id);
|
|
22636
23399
|
const markdown = sessionMarkdown(session);
|
|
22637
|
-
if (options.output) await writeFile3(
|
|
23400
|
+
if (options.output) await writeFile3(resolve27(options.output), markdown, "utf8");
|
|
22638
23401
|
else process.stdout.write(markdown);
|
|
22639
23402
|
});
|
|
22640
23403
|
var checkpointCommand = program.command("checkpoint").description("Inspect and restore pre-mutation snapshots");
|
|
@@ -22947,7 +23710,7 @@ memoryCommand.command("reject <id>").description("Reject a memory proposal witho
|
|
|
22947
23710
|
}
|
|
22948
23711
|
});
|
|
22949
23712
|
var mcpCommand = program.command("mcp").description("Inspect configured MCP servers");
|
|
22950
|
-
mcpCommand.command("status").description("
|
|
23713
|
+
mcpCommand.command("status").description("Report redacted MCP trust and connection status without connecting optional servers").option("-w, --workspace <path>", "workspace root").option("--config <path>", "explicit config file").option("--json", "print JSON").action(async (options) => {
|
|
22951
23714
|
const config = await runtimeConfig(workspaceOption(options.workspace), runtimeOptions(options));
|
|
22952
23715
|
if (!config.mcp) return printObject([], options.json === true);
|
|
22953
23716
|
const manager = new McpManager(config.mcp, {
|
|
@@ -22955,18 +23718,99 @@ mcpCommand.command("status").description("Connect and report MCP server/tool sta
|
|
|
22955
23718
|
workspaceRoots: config.workspaceRoots
|
|
22956
23719
|
});
|
|
22957
23720
|
try {
|
|
22958
|
-
await manager.
|
|
23721
|
+
await manager.loadTrust();
|
|
22959
23722
|
const status = manager.list();
|
|
22960
23723
|
if (options.json) printObject(status, true);
|
|
22961
23724
|
else if (!status.length) process.stdout.write("No MCP servers configured.\n");
|
|
22962
23725
|
else for (const server of status) {
|
|
22963
|
-
process.stdout.write(`${server.name.padEnd(18)} ${server.state.padEnd(12)} ${server.toolCount} tools${server.error ? ` ${server.error}` : ""}
|
|
23726
|
+
process.stdout.write(`${server.name.padEnd(18)} ${server.state.padEnd(12)} ${server.trust.padEnd(10)} ${server.required ? "required" : "optional"} ${server.toolCount} tools${server.error ? ` ${server.error}` : ""}
|
|
22964
23727
|
`);
|
|
22965
23728
|
}
|
|
22966
23729
|
} finally {
|
|
22967
23730
|
await manager.close();
|
|
22968
23731
|
}
|
|
22969
23732
|
});
|
|
23733
|
+
mcpCommand.command("search [query...]").description("Search configured capability manifests without network access").option("-w, --workspace <path>", "workspace root").option("--config <path>", "explicit config file").option("--json", "print JSON").action(async (query, options) => {
|
|
23734
|
+
const config = await runtimeConfig(workspaceOption(options.workspace), runtimeOptions(options));
|
|
23735
|
+
if (!config.mcp) return printObject([], options.json === true);
|
|
23736
|
+
const manager = createMcpManager(config);
|
|
23737
|
+
await manager.loadTrust();
|
|
23738
|
+
const results = manager.search(query.join(" "));
|
|
23739
|
+
if (options.json) printObject(results, true);
|
|
23740
|
+
else if (!results.length) process.stdout.write("No configured MCP capability matched.\n");
|
|
23741
|
+
else for (const result of results) {
|
|
23742
|
+
process.stdout.write(`${result.name.padEnd(18)} ${result.trust.padEnd(10)} ${result.version} ${result.description}
|
|
23743
|
+
`);
|
|
23744
|
+
}
|
|
23745
|
+
});
|
|
23746
|
+
mcpCommand.command("inspect <server>").description("Review a redacted declarative capability manifest without connecting").option("-w, --workspace <path>", "workspace root").option("--config <path>", "explicit config file").option("--json", "print JSON").action(async (server, options) => {
|
|
23747
|
+
const config = await runtimeConfig(workspaceOption(options.workspace), runtimeOptions(options));
|
|
23748
|
+
if (!config.mcp) throw new Error("MCP is not configured.");
|
|
23749
|
+
const manager = createMcpManager(config);
|
|
23750
|
+
await manager.loadTrust();
|
|
23751
|
+
const review = {
|
|
23752
|
+
manifest: manager.inspect(server),
|
|
23753
|
+
fingerprint: manager.fingerprint(server),
|
|
23754
|
+
trust: manager.status(server)?.trust ?? "untrusted"
|
|
23755
|
+
};
|
|
23756
|
+
if (options.json) printObject(review, true);
|
|
23757
|
+
else printMcpManifest(review);
|
|
23758
|
+
});
|
|
23759
|
+
mcpCommand.command("trust <server>").description("Trust the current redacted manifest fingerprint after review").option("-w, --workspace <path>", "workspace root").option("--config <path>", "explicit config file").option("--json", "print JSON").option("--yes", "confirm trust non-interactively").action(async (server, options) => {
|
|
23760
|
+
const config = await runtimeConfig(workspaceOption(options.workspace), runtimeOptions(options));
|
|
23761
|
+
if (!config.mcp) throw new Error("MCP is not configured.");
|
|
23762
|
+
const manager = createMcpManager(config);
|
|
23763
|
+
await manager.loadTrust();
|
|
23764
|
+
const review = {
|
|
23765
|
+
manifest: manager.inspect(server),
|
|
23766
|
+
fingerprint: manager.fingerprint(server),
|
|
23767
|
+
trust: manager.status(server)?.trust ?? "untrusted"
|
|
23768
|
+
};
|
|
23769
|
+
if (!options.json) printMcpManifest(review);
|
|
23770
|
+
if (!options.yes && !await confirm(`Trust this exact capability manifest for ${server}?`)) return;
|
|
23771
|
+
const status = await manager.trust(server);
|
|
23772
|
+
if (options.json) printObject({ manifest: review.manifest, fingerprint: review.fingerprint, status }, true);
|
|
23773
|
+
else process.stdout.write(`Trusted ${server}. Activation remains explicit.
|
|
23774
|
+
`);
|
|
23775
|
+
});
|
|
23776
|
+
mcpCommand.command("activate <server> <query...>").description("Connect one trusted server and load only query-relevant schemas").option("-w, --workspace <path>", "workspace root").option("--config <path>", "explicit config file").option("--json", "print JSON").action(async (server, query, options) => {
|
|
23777
|
+
const config = await runtimeConfig(workspaceOption(options.workspace), runtimeOptions(options));
|
|
23778
|
+
if (!config.mcp) throw new Error("MCP is not configured.");
|
|
23779
|
+
const manager = createMcpManager(config);
|
|
23780
|
+
const registry = createDefaultToolRegistry();
|
|
23781
|
+
try {
|
|
23782
|
+
await manager.loadTrust();
|
|
23783
|
+
const result = await manager.activate(server, query.join(" "), registry);
|
|
23784
|
+
if (options.json) printObject(result, true);
|
|
23785
|
+
else process.stdout.write(result.ok ? `Activated ${server}; loaded ${result.registeredTools.length}/${result.availableTools} relevant schemas.
|
|
23786
|
+
` : `Could not activate ${server}: ${result.status.error ?? result.status.trust}.
|
|
23787
|
+
`);
|
|
23788
|
+
if (!result.ok) process.exitCode = 1;
|
|
23789
|
+
} finally {
|
|
23790
|
+
await manager.close();
|
|
23791
|
+
}
|
|
23792
|
+
});
|
|
23793
|
+
mcpCommand.command("disable <server>").description("Persistently disable an MCP capability and unload its schemas").option("-w, --workspace <path>", "workspace root").option("--config <path>", "explicit config file").option("--json", "print JSON").action(async (server, options) => {
|
|
23794
|
+
const config = await runtimeConfig(workspaceOption(options.workspace), runtimeOptions(options));
|
|
23795
|
+
if (!config.mcp) throw new Error("MCP is not configured.");
|
|
23796
|
+
const manager = createMcpManager(config);
|
|
23797
|
+
await manager.loadTrust();
|
|
23798
|
+
const status = await manager.disable(server);
|
|
23799
|
+
if (options.json) printObject(status, true);
|
|
23800
|
+
else process.stdout.write(`Disabled ${server}.
|
|
23801
|
+
`);
|
|
23802
|
+
});
|
|
23803
|
+
mcpCommand.command("revoke <server>").description("Revoke persisted MCP trust and require a fresh review").option("-w, --workspace <path>", "workspace root").option("--config <path>", "explicit config file").option("--json", "print JSON").option("--yes", "confirm revocation non-interactively").action(async (server, options) => {
|
|
23804
|
+
const config = await runtimeConfig(workspaceOption(options.workspace), runtimeOptions(options));
|
|
23805
|
+
if (!config.mcp) throw new Error("MCP is not configured.");
|
|
23806
|
+
if (!options.yes && !await confirm(`Revoke persisted trust for ${server}?`)) return;
|
|
23807
|
+
const manager = createMcpManager(config);
|
|
23808
|
+
await manager.loadTrust();
|
|
23809
|
+
const status = await manager.revoke(server);
|
|
23810
|
+
if (options.json) printObject(status, true);
|
|
23811
|
+
else process.stdout.write(`Revoked ${server}; inspect and trust the manifest before reuse.
|
|
23812
|
+
`);
|
|
23813
|
+
});
|
|
22970
23814
|
program.command("rules").description("List user and workspace rules loaded into the agent").option("-w, --workspace <path>", "workspace root").option("--json", "print JSON").action(async (options) => {
|
|
22971
23815
|
const rules = await discoverWorkspaceRules(workspaceOption(options.workspace));
|
|
22972
23816
|
if (options.json) {
|
|
@@ -23023,7 +23867,7 @@ async function runChat(prompts, options) {
|
|
|
23023
23867
|
const stdinPrompt = !process.stdin.isTTY ? await readStdin() : "";
|
|
23024
23868
|
const firstPrompt = [...prompts, stdinPrompt].filter(Boolean).join("\n\n").trim();
|
|
23025
23869
|
if (shouldPrint && !firstPrompt) throw new Error("Provide a prompt argument or pipe input on stdin.");
|
|
23026
|
-
const workspace =
|
|
23870
|
+
const workspace = resolve27(options.workspace);
|
|
23027
23871
|
let config = await runtimeConfig(workspace, options);
|
|
23028
23872
|
let completedOnboarding = false;
|
|
23029
23873
|
if (!shouldPrint && needsFirstRunOnboarding(config)) {
|
|
@@ -23253,14 +24097,14 @@ async function runAgentSetup(options) {
|
|
|
23253
24097
|
`);
|
|
23254
24098
|
}
|
|
23255
24099
|
async function runtimeConfig(workspaceInput, options) {
|
|
23256
|
-
const workspace =
|
|
24100
|
+
const workspace = resolve27(workspaceInput);
|
|
23257
24101
|
const loaded = await loadConfig(workspace, options.config, {
|
|
23258
24102
|
trustProjectConfig: options.trustProjectConfig === true
|
|
23259
24103
|
});
|
|
23260
24104
|
const roots = [
|
|
23261
24105
|
workspace,
|
|
23262
24106
|
...loaded.workspaceRoots,
|
|
23263
|
-
...(options.addWorkspace ?? []).map((root) =>
|
|
24107
|
+
...(options.addWorkspace ?? []).map((root) => resolve27(workspace, root))
|
|
23264
24108
|
];
|
|
23265
24109
|
const provider = options.provider ? validateProvider(options.provider) : loaded.model.provider;
|
|
23266
24110
|
return {
|
|
@@ -23398,6 +24242,36 @@ function sessionMarkdown(session) {
|
|
|
23398
24242
|
return `${lines.join("\n")}
|
|
23399
24243
|
`;
|
|
23400
24244
|
}
|
|
24245
|
+
function createMcpManager(config) {
|
|
24246
|
+
if (!config.mcp) throw new Error("MCP is not configured.");
|
|
24247
|
+
return new McpManager(config.mcp, {
|
|
24248
|
+
cwd: config.workspaceRoots[0] ?? process.cwd(),
|
|
24249
|
+
workspaceRoots: config.workspaceRoots
|
|
24250
|
+
});
|
|
24251
|
+
}
|
|
24252
|
+
function printMcpManifest(review) {
|
|
24253
|
+
const { manifest } = review;
|
|
24254
|
+
process.stdout.write(`MCP capability ${manifest.name} (${manifest.version})
|
|
24255
|
+
`);
|
|
24256
|
+
process.stdout.write(` Source: ${manifest.source.kind}/${manifest.source.owner}
|
|
24257
|
+
`);
|
|
24258
|
+
process.stdout.write(` Target: ${manifest.transport} ${manifest.target}
|
|
24259
|
+
`);
|
|
24260
|
+
process.stdout.write(` Trust: ${review.trust} ${manifest.required ? "required" : "optional"}
|
|
24261
|
+
`);
|
|
24262
|
+
process.stdout.write(` Fingerprint: ${review.fingerprint}
|
|
24263
|
+
`);
|
|
24264
|
+
if (!manifest.tools.length) {
|
|
24265
|
+
process.stdout.write(" Tools: dynamically discovered; network-only, completion evidence unsupported\n");
|
|
24266
|
+
return;
|
|
24267
|
+
}
|
|
24268
|
+
for (const tool of manifest.tools) {
|
|
24269
|
+
process.stdout.write(` ${tool.name}: ${tool.permissions.join("+")} evidence=${tool.completionEvidence}
|
|
24270
|
+
`);
|
|
24271
|
+
process.stdout.write(` network=${tool.network.join(", ") || "unspecified"} commands=${tool.commands.join(", ") || "none"} paths=${tool.paths.join(", ") || "none"} sensitive=${tool.sensitiveFields.join(", ") || "none"}
|
|
24272
|
+
`);
|
|
24273
|
+
}
|
|
24274
|
+
}
|
|
23401
24275
|
async function confirm(prompt) {
|
|
23402
24276
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
23403
24277
|
const readline = createInterface2({ input, output });
|
|
@@ -23417,7 +24291,7 @@ function collect(value, previous) {
|
|
|
23417
24291
|
}
|
|
23418
24292
|
function workspaceOption(value) {
|
|
23419
24293
|
const rootOptions = program.opts();
|
|
23420
|
-
return
|
|
24294
|
+
return resolve27(value ?? rootOptions.workspace ?? process.cwd());
|
|
23421
24295
|
}
|
|
23422
24296
|
function runtimeOptions(options) {
|
|
23423
24297
|
const root = program.opts();
|