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