@skein-code/cli 0.3.22 → 0.3.23
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 +8 -1
- package/dist/cli.js +124 -15
- package/dist/cli.js.map +1 -1
- package/docs/ARCHITECTURE.md +12 -7
- package/docs/NEXT_STEPS.md +23 -15
- package/docs/PRODUCT_BENCHMARK.md +4 -3
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ To build, verify, and install a local package artifact from this checkout:
|
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
98
|
npm run verify:package -- --output-dir artifacts/package
|
|
99
|
-
npm install -g ./artifacts/package/skein-code-cli-0.3.
|
|
99
|
+
npm install -g ./artifacts/package/skein-code-cli-0.3.23.tgz
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
To install the published package from npm:
|
|
@@ -214,6 +214,13 @@ only to the originating session, expire after seven days, and are removed when
|
|
|
214
214
|
that session is deleted. A receipt marked `source-truncated` is honest about
|
|
215
215
|
bytes already omitted by the producing tool; those bytes cannot be recovered.
|
|
216
216
|
|
|
217
|
+
Normal chat runs discover MCP servers lazily. Startup exposes only a compact
|
|
218
|
+
`mcp_activate` catalog of configured servers; selecting one connects that
|
|
219
|
+
server, runs remote discovery, and loads at most eight request-relevant schemas
|
|
220
|
+
into the live tool registry. All activated MCP tools remain `network`
|
|
221
|
+
operations. The explicit `skein mcp status` diagnostic still connects
|
|
222
|
+
configured servers eagerly.
|
|
223
|
+
|
|
217
224
|
At 96 columns and wider, a fresh session uses a two-column welcome surface. The
|
|
218
225
|
left side keeps Skein's brand, workspace path, and next actions close to the
|
|
219
226
|
composer; the right side reports the actual model, mode, local index file/chunk
|
package/dist/cli.js
CHANGED
|
@@ -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.23",
|
|
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,9 @@ var package_default = {
|
|
|
236
236
|
},
|
|
237
237
|
skein: {
|
|
238
238
|
releaseNotes: [
|
|
239
|
+
"Normal chat startup now exposes a compact mcp_activate catalog instead of eagerly connecting MCP servers",
|
|
240
|
+
"MCP activation connects one selected server, discovers remote tools, and loads at most eight relevant schemas",
|
|
241
|
+
"Explicit MCP diagnostics can still connect eagerly while all MCP activation and remote calls remain network operations",
|
|
239
242
|
"OpenAI, Anthropic, and Gemini usage now normalizes cached input, cache-write input, and reasoning tokens when reported",
|
|
240
243
|
"Token Ledger, session totals, and structured usage events preserve provider cache and reasoning counts without prompt content",
|
|
241
244
|
"Streaming, non-streaming, explicit-zero, and legacy-session compatibility paths are covered without claiming cache activation",
|
|
@@ -1938,6 +1941,7 @@ var agentTeamConfigSchema = z2.object({
|
|
|
1938
1941
|
var mcpServerSchema = z2.object({
|
|
1939
1942
|
enabled: z2.boolean().optional(),
|
|
1940
1943
|
transport: z2.enum(["stdio", "http"]).optional(),
|
|
1944
|
+
description: z2.string().min(1).max(500).optional(),
|
|
1941
1945
|
command: z2.string().min(1).max(512).optional(),
|
|
1942
1946
|
args: z2.array(z2.string().max(4e3)).max(64).optional(),
|
|
1943
1947
|
cwd: z2.string().max(4e3).optional(),
|
|
@@ -19940,12 +19944,24 @@ var MAX_TOOLS_PER_SERVER = 256;
|
|
|
19940
19944
|
var MAX_LIST_PAGES = 16;
|
|
19941
19945
|
var DEFAULT_CONNECT_TIMEOUT = 12e3;
|
|
19942
19946
|
var DEFAULT_TOOL_TIMEOUT = 6e4;
|
|
19947
|
+
var LAZY_SCHEMA_LIMIT = 8;
|
|
19943
19948
|
var McpManager = class {
|
|
19944
19949
|
constructor(config, options = {}) {
|
|
19945
19950
|
this.config = config;
|
|
19946
19951
|
this.options = options;
|
|
19947
|
-
|
|
19952
|
+
const entries = Object.entries(config.servers ?? {});
|
|
19953
|
+
for (const [index, [name, server]] of entries.entries()) {
|
|
19948
19954
|
const transport = server.transport ?? "stdio";
|
|
19955
|
+
if (index >= MAX_SERVERS) {
|
|
19956
|
+
this.statuses.set(name, {
|
|
19957
|
+
name,
|
|
19958
|
+
state: "error",
|
|
19959
|
+
transport,
|
|
19960
|
+
toolCount: 0,
|
|
19961
|
+
error: `MCP server limit exceeded (maximum ${MAX_SERVERS})`
|
|
19962
|
+
});
|
|
19963
|
+
continue;
|
|
19964
|
+
}
|
|
19949
19965
|
const state = config.enabled === false || server.enabled === false ? "disabled" : "disconnected";
|
|
19950
19966
|
this.statuses.set(name, { name, state, transport, toolCount: 0 });
|
|
19951
19967
|
}
|
|
@@ -19960,6 +19976,75 @@ var McpManager = class {
|
|
|
19960
19976
|
options;
|
|
19961
19977
|
shutdownController = new AbortController();
|
|
19962
19978
|
closed = false;
|
|
19979
|
+
/** Compact model-visible catalog; transport and remote discovery stay lazy. */
|
|
19980
|
+
activationTool(registry) {
|
|
19981
|
+
const names = this.activatableServerNames();
|
|
19982
|
+
if (!names.length) return;
|
|
19983
|
+
const catalog = names.map((name) => {
|
|
19984
|
+
const server = this.config.servers[name];
|
|
19985
|
+
const description = sanitizeCatalogText(server?.description) || `${server?.transport ?? "stdio"} MCP server`;
|
|
19986
|
+
return `${name}: ${description}`;
|
|
19987
|
+
}).join("; ");
|
|
19988
|
+
return {
|
|
19989
|
+
definition: {
|
|
19990
|
+
name: "mcp_activate",
|
|
19991
|
+
description: `Connect to one configured MCP server only when the current task needs it, discover its tools, and load at most ${LAZY_SCHEMA_LIMIT} relevant schemas. Available servers: ${catalog}`,
|
|
19992
|
+
category: "network",
|
|
19993
|
+
inputSchema: {
|
|
19994
|
+
type: "object",
|
|
19995
|
+
properties: {
|
|
19996
|
+
server: { type: "string", enum: names, description: "Configured MCP server to activate." },
|
|
19997
|
+
query: { type: "string", minLength: 1, maxLength: 500, description: "Capability needed from that server." }
|
|
19998
|
+
},
|
|
19999
|
+
required: ["server", "query"],
|
|
20000
|
+
additionalProperties: false
|
|
20001
|
+
}
|
|
20002
|
+
},
|
|
20003
|
+
permissionCategories: () => ["network"],
|
|
20004
|
+
execute: async (arguments_, context) => {
|
|
20005
|
+
const server = typeof arguments_.server === "string" ? arguments_.server : "";
|
|
20006
|
+
const query = typeof arguments_.query === "string" ? arguments_.query.trim() : "";
|
|
20007
|
+
if (!names.includes(server)) throw new ToolInputError("MCP server is not available for activation");
|
|
20008
|
+
if (!query || query.length > 500) {
|
|
20009
|
+
throw new ToolInputError("MCP activation query must contain 1 to 500 characters");
|
|
20010
|
+
}
|
|
20011
|
+
const result = await this.activate(server, query, registry, context.signal);
|
|
20012
|
+
if (!result.ok) {
|
|
20013
|
+
return {
|
|
20014
|
+
ok: false,
|
|
20015
|
+
content: `MCP server ${server} could not be activated: ${result.status.error ?? result.status.state}`,
|
|
20016
|
+
metadata: activationMetadata(result)
|
|
20017
|
+
};
|
|
20018
|
+
}
|
|
20019
|
+
const loaded = result.registeredTools.length ? result.registeredTools.join(", ") : "no matching tool schemas";
|
|
20020
|
+
const deferred = result.deferredTools ? ` ${result.deferredTools} additional schemas remain deferred; activate again with a narrower query if needed.` : "";
|
|
20021
|
+
return {
|
|
20022
|
+
content: `Activated MCP server ${server}. Loaded: ${loaded}.${deferred}`,
|
|
20023
|
+
metadata: activationMetadata(result)
|
|
20024
|
+
};
|
|
20025
|
+
}
|
|
20026
|
+
};
|
|
20027
|
+
}
|
|
20028
|
+
/** Connect/discover one server, then register only request-relevant schemas. */
|
|
20029
|
+
async activate(name, query, registry, signal) {
|
|
20030
|
+
const connected = await this.connect(name, signal);
|
|
20031
|
+
if (!connected.ok) {
|
|
20032
|
+
return { ...connected, registeredTools: [], availableTools: 0, deferredTools: 0 };
|
|
20033
|
+
}
|
|
20034
|
+
const connection = this.connections.get(name);
|
|
20035
|
+
if (!connection) {
|
|
20036
|
+
return { ...connected, ok: false, registeredTools: [], availableTools: 0, deferredTools: 0 };
|
|
20037
|
+
}
|
|
20038
|
+
const tools = [...connection.tools.values()];
|
|
20039
|
+
const selected = tools.length <= LAZY_SCHEMA_LIMIT ? tools : selectRelevantTools(tools, query, LAZY_SCHEMA_LIMIT);
|
|
20040
|
+
this.registerSelectedTools(registry, selected);
|
|
20041
|
+
return {
|
|
20042
|
+
...connected,
|
|
20043
|
+
registeredTools: selected.map((tool) => tool.definition.name),
|
|
20044
|
+
availableTools: tools.length,
|
|
20045
|
+
deferredTools: Math.max(0, tools.length - selected.length)
|
|
20046
|
+
};
|
|
20047
|
+
}
|
|
19963
20048
|
/** Connect enabled servers with a small concurrency bound. */
|
|
19964
20049
|
async connectAll(signal) {
|
|
19965
20050
|
if (this.closed) throw new Error("MCP manager is closed");
|
|
@@ -19968,15 +20053,6 @@ var McpManager = class {
|
|
|
19968
20053
|
if (this.config.enabled === false) {
|
|
19969
20054
|
return configuredNames.map((name) => this.resultFor(name, false, 0));
|
|
19970
20055
|
}
|
|
19971
|
-
for (const name of configuredNames.slice(MAX_SERVERS)) {
|
|
19972
|
-
const server = this.config.servers[name];
|
|
19973
|
-
this.setStatus(name, {
|
|
19974
|
-
state: "error",
|
|
19975
|
-
transport: server?.transport ?? "stdio",
|
|
19976
|
-
toolCount: 0,
|
|
19977
|
-
error: `MCP server limit exceeded (maximum ${MAX_SERVERS})`
|
|
19978
|
-
});
|
|
19979
|
-
}
|
|
19980
20056
|
const results = [];
|
|
19981
20057
|
let cursor = 0;
|
|
19982
20058
|
const worker = async () => {
|
|
@@ -19992,6 +20068,10 @@ var McpManager = class {
|
|
|
19992
20068
|
/** Connect one configured server. Connection errors are captured in status. */
|
|
19993
20069
|
async connect(name, signal) {
|
|
19994
20070
|
if (this.closed) throw new Error("MCP manager is closed");
|
|
20071
|
+
const status = this.statuses.get(name);
|
|
20072
|
+
if (status?.state === "error" && status.error?.includes("server limit exceeded")) {
|
|
20073
|
+
return this.resultFor(name, false, 0);
|
|
20074
|
+
}
|
|
19995
20075
|
const existing = this.pending.get(name);
|
|
19996
20076
|
if (existing) return existing;
|
|
19997
20077
|
const connectionController = new AbortController();
|
|
@@ -20076,8 +20156,11 @@ var McpManager = class {
|
|
|
20076
20156
|
}
|
|
20077
20157
|
/** Register connected MCP tools, preserving idempotency for the same adapter. */
|
|
20078
20158
|
registerTools(registry) {
|
|
20159
|
+
return this.registerSelectedTools(registry, this.tools());
|
|
20160
|
+
}
|
|
20161
|
+
registerSelectedTools(registry, tools) {
|
|
20079
20162
|
const registered = [];
|
|
20080
|
-
for (const tool of
|
|
20163
|
+
for (const tool of tools) {
|
|
20081
20164
|
const existing = registry.get(tool.definition.name);
|
|
20082
20165
|
if (existing) {
|
|
20083
20166
|
if (existing !== tool) {
|
|
@@ -20090,6 +20173,10 @@ var McpManager = class {
|
|
|
20090
20173
|
}
|
|
20091
20174
|
return registered;
|
|
20092
20175
|
}
|
|
20176
|
+
activatableServerNames() {
|
|
20177
|
+
if (this.config.enabled === false) return [];
|
|
20178
|
+
return [...this.statuses.values()].filter((status) => status.state !== "disabled" && status.state !== "error").map((status) => status.name).sort((left, right) => left.localeCompare(right));
|
|
20179
|
+
}
|
|
20093
20180
|
async connectInternal(name, signal) {
|
|
20094
20181
|
const configured = this.config.servers?.[name];
|
|
20095
20182
|
if (!configured) throw new Error(`Unknown MCP server: ${name}`);
|
|
@@ -20341,6 +20428,28 @@ var McpManager = class {
|
|
|
20341
20428
|
return { name, ok, status: { ...status }, skippedTools };
|
|
20342
20429
|
}
|
|
20343
20430
|
};
|
|
20431
|
+
function activationMetadata(result) {
|
|
20432
|
+
return {
|
|
20433
|
+
mcpServer: result.name,
|
|
20434
|
+
state: result.status.state,
|
|
20435
|
+
availableTools: result.availableTools,
|
|
20436
|
+
loadedTools: result.registeredTools,
|
|
20437
|
+
deferredTools: result.deferredTools,
|
|
20438
|
+
skippedTools: result.skippedTools
|
|
20439
|
+
};
|
|
20440
|
+
}
|
|
20441
|
+
function selectRelevantTools(tools, query, limit) {
|
|
20442
|
+
const terms = new Set(query.toLocaleLowerCase().match(/[\p{L}\p{N}_-]{2,}/gu) ?? []);
|
|
20443
|
+
return tools.map((tool) => {
|
|
20444
|
+
const searchable = `${tool.definition.name.replaceAll("_", " ")} ${tool.definition.description}`.toLocaleLowerCase();
|
|
20445
|
+
let score = 0;
|
|
20446
|
+
for (const term of terms) if (searchable.includes(term)) score += term.length;
|
|
20447
|
+
return { tool, score };
|
|
20448
|
+
}).sort((left, right) => right.score - left.score || left.tool.definition.name.localeCompare(right.tool.definition.name)).slice(0, limit).map(({ tool }) => tool);
|
|
20449
|
+
}
|
|
20450
|
+
function sanitizeCatalogText(value) {
|
|
20451
|
+
return value ? stripAnsi5(value).replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim().slice(0, 200) : "";
|
|
20452
|
+
}
|
|
20344
20453
|
function requestOptions(timeoutMs, signal) {
|
|
20345
20454
|
return {
|
|
20346
20455
|
timeout: timeoutMs,
|
|
@@ -20850,7 +20959,7 @@ var ExtensionRuntime = class _ExtensionRuntime {
|
|
|
20850
20959
|
throw error;
|
|
20851
20960
|
}
|
|
20852
20961
|
}
|
|
20853
|
-
async initialize(registry,
|
|
20962
|
+
async initialize(registry, _signal) {
|
|
20854
20963
|
if (this.initialized) return;
|
|
20855
20964
|
if (this.memory) {
|
|
20856
20965
|
await this.memory.open();
|
|
@@ -20862,8 +20971,8 @@ var ExtensionRuntime = class _ExtensionRuntime {
|
|
|
20862
20971
|
}
|
|
20863
20972
|
await this.skills?.discover();
|
|
20864
20973
|
if (this.mcp) {
|
|
20865
|
-
|
|
20866
|
-
|
|
20974
|
+
const activationTool = this.mcp.activationTool(registry);
|
|
20975
|
+
if (activationTool) registry.register(activationTool);
|
|
20867
20976
|
}
|
|
20868
20977
|
await this.profiles?.discover();
|
|
20869
20978
|
if (this.config.agents?.enabled && this.profiles && this.options.provider && this.options.contextEngine) {
|