@webskill/sdk 0.2.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/browser.d.ts +2 -2
- package/dist/browser.js +4 -9
- package/dist/{catalogComponents-C_V39rbF-BOHveMWa.js → catalogComponents-KsujmL4b-Clx1kCnU.js} +278 -122
- package/dist/client-BCM6Z3yq-qUQNkKrK.js +7787 -0
- package/dist/{dist-BQzncxXg.js → dist-8oQRa8Xz.js} +212 -13
- package/dist/{dist-B9VLwOME.js → dist-C-Sh0MDU.js} +1019 -317
- package/dist/{dist-CtBLBbEz.js → dist-D9Lcn5Pp.js} +528 -838
- package/dist/governance.d.ts +46 -11
- package/dist/governance.js +152 -25
- package/dist/{index-7DVaZJU7.d.ts → index-CHXxDccV.d.ts} +62 -144
- package/dist/{index-QrHtAudz.d.ts → index-DLfR2Y6I.d.ts} +412 -21
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -3
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +2 -2
- package/dist/memoryArtifactStore-BtOeB_hm-tj3fC5ip.js +78 -0
- package/dist/node.d.ts +297 -4
- package/dist/node.js +281 -7
- package/dist/{openUiLibrary-B8-Cvou9-D3RsU2EB.js → openUiLibrary-YLS-cxyT-C96jWDQq.js} +6 -5
- package/dist/skillVersionStore-uyefLPR1-DXOzbksv.d.ts +158 -0
- package/dist/stdio-CFMoANJJ-BxrTeXh7.js +31 -0
- package/dist/{testing-BUoXvm1u.js → testing-DDCJWvgA.js} +8 -6
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +2 -2
- package/dist/{types-AmKCKJn_-BogJPQHU.d.ts → types-7Wcg--Vh-1YlQ4jF9.d.ts} +219 -70
- package/dist/types-WovEf4ED-CZSDiiBU.js +6215 -0
- package/dist/ui-react.d.ts +329 -18
- package/dist/ui-react.js +3715 -3464
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +1 -1
- package/dist/ui.d.ts +4 -3
- package/dist/ui.js +3 -3
- package/dist/{webskillLitCatalog-CNaUpasU-CfSRvqCZ.js → webskillLitCatalog-CSTbhBe_-CYIs5BX8.js} +312 -122
- package/package.json +4 -7
- package/dist/jsonRenderRegistry-9GrWP_hE-CQY8bT9w.js +0 -2468
- package/dist/memoryArtifactStore-C9lFVqPF-yFz6yJj0.js +0 -48
- package/dist/skillVersionStore-B7rGjtMi-BgnQho9v.d.ts +0 -365
package/dist/node.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import {
|
|
1
|
+
import { A as parseSkillMarkdown, B as unzipWithLimits, H as verifyManifest, I as resolveArchiveLimits, L as resolveInsideRoot, M as readResponseWithLimit, N as readSkillSignature, O as messageOf, T as isValidSkillName, U as verifySkillSignature, V as validateSkills, _ as atomicWriteText, g as assertSafePathSegment, h as assertRemoteUrlAllowed, j as parseSkillPackManifest, m as WebSkillError, o as SKILLS_LOCKFILE, r as MANIFEST_EXCLUDED_FILES, s as SKILL_MANIFEST_FILE, u as SKILL_PACK_FILE, w as exportSkills, y as buildManifest } from "./dist-8oQRa8Xz.js";
|
|
2
|
+
import { A as createScriptContext, D as WebSkillRuntime, H as normalizeToolContent, O as bridgeError, U as normalizeToolError, W as parseBridgeRequest, l as FsArtifactStore, o as CapabilityApproval, u as FsMemoryStore, z as networkPolicyLibSource } from "./dist-C-Sh0MDU.js";
|
|
3
3
|
import { i as probeLlmCapabilities } from "./env--jJB-TSX-04klhTYi.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { unzipSync, zipSync } from "fflate";
|
|
@@ -10,7 +10,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
10
10
|
import { format, promisify } from "node:util";
|
|
11
11
|
import { Worker } from "node:worker_threads";
|
|
12
12
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
13
|
-
import { execFile, fork } from "node:child_process";
|
|
13
|
+
import { execFile, fork, spawn } from "node:child_process";
|
|
14
14
|
import { createInterface } from "node:readline/promises";
|
|
15
15
|
import { createHash } from "node:crypto";
|
|
16
16
|
|
|
@@ -1597,7 +1597,6 @@ async function removeLockEntry(fs, managedRoot, name) {
|
|
|
1597
1597
|
async function writeLockfileAtomic(fs, managedRoot, lockfile) {
|
|
1598
1598
|
await atomicWriteText(fs, `${managedRoot}/${SKILLS_LOCKFILE}`, JSON.stringify(lockfile, null, 2));
|
|
1599
1599
|
}
|
|
1600
|
-
const EXCLUDED_FILES = /* @__PURE__ */ new Set([SKILL_MANIFEST_FILE, SKILLS_LOCKFILE]);
|
|
1601
1600
|
/** 递归收集技能目录内的文件(相对路径,posix 分隔) */
|
|
1602
1601
|
async function walkFiles(fs, root, prefix = "") {
|
|
1603
1602
|
const out = [];
|
|
@@ -1605,7 +1604,7 @@ async function walkFiles(fs, root, prefix = "") {
|
|
|
1605
1604
|
const name = entry.path.split("/").pop() ?? "";
|
|
1606
1605
|
const rel = prefix === "" ? name : `${prefix}/${name}`;
|
|
1607
1606
|
if (entry.type === "directory") out.push(...await walkFiles(fs, entry.path, rel));
|
|
1608
|
-
else if (!
|
|
1607
|
+
else if (!MANIFEST_EXCLUDED_FILES.has(name)) out.push(rel);
|
|
1609
1608
|
}
|
|
1610
1609
|
return out;
|
|
1611
1610
|
}
|
|
@@ -1649,7 +1648,29 @@ async function verifyIntegrity(fs, skillRoot) {
|
|
|
1649
1648
|
for (const rel of actualFiles) actualHashes.set(rel, sha256Hex(await fs.readBinary(`${skillRoot}/${rel}`)));
|
|
1650
1649
|
return verifyManifest(manifest, actualHashes, actualFiles);
|
|
1651
1650
|
}
|
|
1652
|
-
|
|
1651
|
+
/**
|
|
1652
|
+
* 验签失败不能被折叠成 INSTALL_FAILED:四种失败的宿主处置完全不同
|
|
1653
|
+
* (引导选策略 / 提示重下 / 引导添加密钥 / 安全事件),见 design 03 §3.1。
|
|
1654
|
+
*/
|
|
1655
|
+
const PRESERVED_INSTALL_CODES = /* @__PURE__ */ new Set([
|
|
1656
|
+
"SIGNATURE_MISSING",
|
|
1657
|
+
"SIGNATURE_MALFORMED",
|
|
1658
|
+
"SIGNATURE_UNTRUSTED_KEY",
|
|
1659
|
+
"SIGNATURE_MISMATCH",
|
|
1660
|
+
"SIGNATURE_UNSUPPORTED"
|
|
1661
|
+
]);
|
|
1662
|
+
const asInstallFailed = (e) => e instanceof WebSkillError && (e.code === "INSTALL_FAILED" || PRESERVED_INSTALL_CODES.has(e.code)) ? e : new WebSkillError("INSTALL_FAILED", `Install failed: ${messageOf(e)}`, e);
|
|
1663
|
+
/** 未注入信任库时的空库:任何签名都不受信,比「验不了就放行」安全 */
|
|
1664
|
+
const EMPTY_TRUSTED_KEYS = {
|
|
1665
|
+
list: async () => [],
|
|
1666
|
+
get: async () => void 0,
|
|
1667
|
+
add: async () => {
|
|
1668
|
+
throw new WebSkillError("SIGNATURE_UNTRUSTED_KEY", "This SkillManager has no trusted key store configured");
|
|
1669
|
+
},
|
|
1670
|
+
remove: async () => {
|
|
1671
|
+
throw new WebSkillError("SIGNATURE_UNTRUSTED_KEY", "This SkillManager has no trusted key store configured");
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1653
1674
|
/**
|
|
1654
1675
|
* 技能管理门面:统一安装管线(staging → 解析 name → 校验 → 拷贝 → manifest → lockfile),
|
|
1655
1676
|
* 任何失败清理现场抛 INSTALL_FAILED。
|
|
@@ -1662,6 +1683,10 @@ var SkillManager = class {
|
|
|
1662
1683
|
#schemaInference;
|
|
1663
1684
|
#archiveLimits;
|
|
1664
1685
|
#onChanged;
|
|
1686
|
+
#trustedKeys;
|
|
1687
|
+
#unsignedPolicy;
|
|
1688
|
+
#signatureAudit;
|
|
1689
|
+
#onWarning;
|
|
1665
1690
|
#schemaInferer = new OxcSchemaInferer();
|
|
1666
1691
|
constructor(deps) {
|
|
1667
1692
|
this.#managedRoot = deps.managedRoot.replace(/\/+$/, "");
|
|
@@ -1670,6 +1695,10 @@ var SkillManager = class {
|
|
|
1670
1695
|
this.#schemaInference = deps.schemaInference ?? true;
|
|
1671
1696
|
this.#archiveLimits = deps.archiveLimits;
|
|
1672
1697
|
this.#onChanged = deps.onChanged;
|
|
1698
|
+
this.#trustedKeys = deps.signature?.trustedKeys ?? EMPTY_TRUSTED_KEYS;
|
|
1699
|
+
this.#unsignedPolicy = deps.signature?.unsigned ?? "warn";
|
|
1700
|
+
this.#signatureAudit = deps.signature?.audit;
|
|
1701
|
+
this.#onWarning = deps.onWarning;
|
|
1673
1702
|
}
|
|
1674
1703
|
/** 托管根目录(治理发布归档捕获等只读场景) */
|
|
1675
1704
|
get managedRoot() {
|
|
@@ -1728,6 +1757,7 @@ var SkillManager = class {
|
|
|
1728
1757
|
...version ? { version } : {},
|
|
1729
1758
|
source: staged.source
|
|
1730
1759
|
});
|
|
1760
|
+
await this.#verifySignature(finalDir, manifest);
|
|
1731
1761
|
targetDir = `${this.#managedRoot}/${name}`;
|
|
1732
1762
|
const backupDir = `${stagingRoot}/backup`;
|
|
1733
1763
|
const hadPrevious = await fs.exists(targetDir);
|
|
@@ -1797,6 +1827,7 @@ var SkillManager = class {
|
|
|
1797
1827
|
source
|
|
1798
1828
|
});
|
|
1799
1829
|
if (manifest.integrity.digest !== entry.digest) throw new WebSkillError("INSTALL_FAILED", `Skill "${entry.name}" digest mismatch: expected ${entry.digest}, got ${manifest.integrity.digest}`);
|
|
1830
|
+
await this.#verifySignature(`${finalRoot}/${entry.name}`, manifest);
|
|
1800
1831
|
manifests.push(manifest);
|
|
1801
1832
|
}
|
|
1802
1833
|
const swapped = [];
|
|
@@ -1836,6 +1867,33 @@ var SkillManager = class {
|
|
|
1836
1867
|
}
|
|
1837
1868
|
}
|
|
1838
1869
|
/**
|
|
1870
|
+
* 安装期验签(design 03 §5.1):manifest 生成之后、lockfile 提交之前。
|
|
1871
|
+
* 失败直接抛出,暂存目录由 install 的 finally 清理,不落盘。
|
|
1872
|
+
* 未签名且策略放行时也记一条审计——「当时是 warn 放过去的」事后必须查得到。
|
|
1873
|
+
*/
|
|
1874
|
+
async #verifySignature(skillDir, manifest) {
|
|
1875
|
+
const verdict = await verifySkillSignature({
|
|
1876
|
+
manifest,
|
|
1877
|
+
signature: await readSkillSignature(this.#fs, skillDir),
|
|
1878
|
+
trustedKeys: this.#trustedKeys,
|
|
1879
|
+
policy: { unsigned: this.#unsignedPolicy }
|
|
1880
|
+
});
|
|
1881
|
+
if (verdict.warning !== void 0) this.#onWarning?.({
|
|
1882
|
+
skill: manifest.name,
|
|
1883
|
+
message: verdict.warning
|
|
1884
|
+
});
|
|
1885
|
+
await this.#signatureAudit?.append({
|
|
1886
|
+
type: "skill.signature-verified",
|
|
1887
|
+
target: manifest.name,
|
|
1888
|
+
data: {
|
|
1889
|
+
...verdict.keyId !== void 0 ? { keyId: verdict.keyId } : {},
|
|
1890
|
+
verdict: verdict.unsigned ? "unsigned-allowed" : "signed",
|
|
1891
|
+
policy: this.#unsignedPolicy,
|
|
1892
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1893
|
+
}
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
/**
|
|
1839
1897
|
* D2 安装期预推导:scripts/ 下无显式 inputSchema 导出且无 sidecar 的脚本,
|
|
1840
1898
|
* 经 OXC 推导写 sidecar(计入 manifest.files;重装重新生成)
|
|
1841
1899
|
*/
|
|
@@ -1898,6 +1956,222 @@ var SkillManager = class {
|
|
|
1898
1956
|
return options.outPath;
|
|
1899
1957
|
}
|
|
1900
1958
|
};
|
|
1959
|
+
async function loadMcpSdk() {
|
|
1960
|
+
try {
|
|
1961
|
+
const [client, stdio] = await Promise.all([import("./client-BCM6Z3yq-qUQNkKrK.js"), import("./stdio-CFMoANJJ-BxrTeXh7.js")]);
|
|
1962
|
+
return {
|
|
1963
|
+
Client: client.Client,
|
|
1964
|
+
ReadBuffer: stdio.ReadBuffer,
|
|
1965
|
+
serializeMessage: stdio.serializeMessage
|
|
1966
|
+
};
|
|
1967
|
+
} catch (e) {
|
|
1968
|
+
throw new WebSkillError("MCP_ENDPOINT_UNAVAILABLE", "The \"@modelcontextprotocol/sdk\" package is required for stdio MCP endpoints; install it first (npm i @modelcontextprotocol/sdk)", e);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
/** SIGTERM 后等待自行退出的宽限期,超时改 SIGKILL */
|
|
1972
|
+
const KILL_GRACE_MS = 2e3;
|
|
1973
|
+
function describeExit(exit) {
|
|
1974
|
+
return exit.signal !== null ? `signal ${exit.signal}` : `exit code ${exit.code}`;
|
|
1975
|
+
}
|
|
1976
|
+
async function spawnServer(config) {
|
|
1977
|
+
const child = spawn(config.command, [...config.args ?? []], {
|
|
1978
|
+
...config.cwd !== void 0 ? { cwd: config.cwd } : {},
|
|
1979
|
+
env: { ...config.env },
|
|
1980
|
+
stdio: [
|
|
1981
|
+
"pipe",
|
|
1982
|
+
"pipe",
|
|
1983
|
+
"pipe"
|
|
1984
|
+
],
|
|
1985
|
+
shell: false,
|
|
1986
|
+
windowsHide: true
|
|
1987
|
+
});
|
|
1988
|
+
let tail = "";
|
|
1989
|
+
child.stderr?.on("data", (chunk) => {
|
|
1990
|
+
const text = chunk.toString("utf8");
|
|
1991
|
+
config.onStderr?.(text);
|
|
1992
|
+
tail = (tail + text).slice(-4096);
|
|
1993
|
+
});
|
|
1994
|
+
child.stdin?.on("error", () => {});
|
|
1995
|
+
child.stderr?.on("error", () => {});
|
|
1996
|
+
let exit;
|
|
1997
|
+
const exited = new Promise((resolve) => {
|
|
1998
|
+
child.once("exit", (code, signal) => {
|
|
1999
|
+
exit = {
|
|
2000
|
+
code,
|
|
2001
|
+
signal
|
|
2002
|
+
};
|
|
2003
|
+
resolve();
|
|
2004
|
+
});
|
|
2005
|
+
});
|
|
2006
|
+
try {
|
|
2007
|
+
await new Promise((resolve, reject) => {
|
|
2008
|
+
child.once("spawn", resolve);
|
|
2009
|
+
child.once("error", reject);
|
|
2010
|
+
});
|
|
2011
|
+
} catch (e) {
|
|
2012
|
+
throw new WebSkillError("MCP_STDIO_SPAWN_FAILED", `Failed to spawn stdio MCP endpoint "${config.endpoint}" (${config.command}): ${messageOf(e)}`, e);
|
|
2013
|
+
}
|
|
2014
|
+
child.on("error", () => {});
|
|
2015
|
+
return {
|
|
2016
|
+
child,
|
|
2017
|
+
stderrTail: () => tail,
|
|
2018
|
+
exit: () => exit,
|
|
2019
|
+
terminate: async () => {
|
|
2020
|
+
if (exit !== void 0) return;
|
|
2021
|
+
child.kill("SIGTERM");
|
|
2022
|
+
const killer = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
2023
|
+
try {
|
|
2024
|
+
await exited;
|
|
2025
|
+
} finally {
|
|
2026
|
+
clearTimeout(killer);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* 子进程 stdio 上的 MCP transport。帧解析复用 SDK 的 ReadBuffer / serializeMessage,
|
|
2033
|
+
* 不自己写协议解析;进程由 connectStdioEndpoint 先行 spawn,
|
|
2034
|
+
* 好把「命令起不来」和「起来后立刻退出」分成两个错误码。
|
|
2035
|
+
*/
|
|
2036
|
+
var ChildStdioTransport = class {
|
|
2037
|
+
onclose;
|
|
2038
|
+
onerror;
|
|
2039
|
+
onmessage;
|
|
2040
|
+
#child;
|
|
2041
|
+
#sdk;
|
|
2042
|
+
#readBuffer;
|
|
2043
|
+
constructor(child, sdk) {
|
|
2044
|
+
this.#child = child;
|
|
2045
|
+
this.#sdk = sdk;
|
|
2046
|
+
this.#readBuffer = new sdk.ReadBuffer();
|
|
2047
|
+
}
|
|
2048
|
+
async start() {
|
|
2049
|
+
if (this.#child.exitCode !== null || this.#child.signalCode !== null) {
|
|
2050
|
+
this.onclose?.();
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
this.#child.stdout?.on("data", (chunk) => {
|
|
2054
|
+
this.#readBuffer.append(chunk);
|
|
2055
|
+
for (;;) {
|
|
2056
|
+
let message;
|
|
2057
|
+
try {
|
|
2058
|
+
message = this.#readBuffer.readMessage();
|
|
2059
|
+
} catch (e) {
|
|
2060
|
+
this.onerror?.(e instanceof Error ? e : new Error(String(e)));
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
if (message === null) return;
|
|
2064
|
+
this.onmessage?.(message);
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
this.#child.stdout?.on("error", (e) => this.onerror?.(e));
|
|
2068
|
+
this.#child.once("exit", () => this.onclose?.());
|
|
2069
|
+
}
|
|
2070
|
+
async send(message) {
|
|
2071
|
+
const stdin = this.#child.stdin;
|
|
2072
|
+
if (!stdin || stdin.destroyed) throw new WebSkillError("MCP_STDIO_EXITED", "The stdio MCP server process is no longer writable");
|
|
2073
|
+
await new Promise((resolve, reject) => {
|
|
2074
|
+
stdin.write(this.#sdk.serializeMessage(message), (e) => e ? reject(e) : resolve());
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
async close() {
|
|
2078
|
+
this.#child.stdin?.end();
|
|
2079
|
+
this.#readBuffer.clear();
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
function withTimeout(work, timeoutMs, onTimeout) {
|
|
2083
|
+
if (timeoutMs <= 0) return work;
|
|
2084
|
+
let timer;
|
|
2085
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
2086
|
+
timer = setTimeout(() => reject(onTimeout()), timeoutMs);
|
|
2087
|
+
});
|
|
2088
|
+
return Promise.race([work, timeout]).finally(() => clearTimeout(timer));
|
|
2089
|
+
}
|
|
2090
|
+
const CLIENT_METHODS = [
|
|
2091
|
+
"listTools",
|
|
2092
|
+
"callTool",
|
|
2093
|
+
"listPrompts",
|
|
2094
|
+
"getPrompt",
|
|
2095
|
+
"listResources",
|
|
2096
|
+
"readResource"
|
|
2097
|
+
];
|
|
2098
|
+
/**
|
|
2099
|
+
* 给每次调用套上 timeoutMs。超时按「server 挂住了」处理:先终止进程再上抛,
|
|
2100
|
+
* 免得留下一个谁都不再理会的子进程。
|
|
2101
|
+
*/
|
|
2102
|
+
function guardCalls(client, config, abort) {
|
|
2103
|
+
const timeoutMs = config.timeoutMs ?? 0;
|
|
2104
|
+
if (timeoutMs <= 0) return client;
|
|
2105
|
+
const source = client;
|
|
2106
|
+
const guarded = {};
|
|
2107
|
+
for (const method of CLIENT_METHODS) guarded[method] = async (input) => {
|
|
2108
|
+
try {
|
|
2109
|
+
return await withTimeout(source[method](input), timeoutMs, () => new WebSkillError("MCP_STDIO_TIMEOUT", `Call "${method}" on stdio MCP endpoint "${config.endpoint}" timed out after ${timeoutMs}ms`));
|
|
2110
|
+
} catch (e) {
|
|
2111
|
+
if (e instanceof WebSkillError && e.code === "MCP_STDIO_TIMEOUT") await abort();
|
|
2112
|
+
throw e;
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2115
|
+
return guarded;
|
|
2116
|
+
}
|
|
2117
|
+
/**
|
|
2118
|
+
* stdio MCP endpoint 装配:spawn 宿主配置的可执行程序,经 stdin/stdout 说 MCP,
|
|
2119
|
+
* 注册进 EndpointRegistry——endpoint:tool 解析、TTL+版本缓存、临时技能消费自动生效。
|
|
2120
|
+
* 返回 close 句柄:断开后 unregister 并确保子进程真的退出(不留僵尸)。
|
|
2121
|
+
*
|
|
2122
|
+
* registry 参数写成结构型而非 `EndpointRegistry<McpClientLike>`:只要从 `@webskill/mcp`
|
|
2123
|
+
* `import type`,tsdown 就会把整个 mcp 的 d.ts chunk 拖进 `@webskill/sdk/node`,
|
|
2124
|
+
* 把 optional peer `@modelcontextprotocol/sdk` 变成 node 子路径的类型硬依赖
|
|
2125
|
+
* (install-smoke 的消费方 typecheck 探针会因此变红)。结构类型保证真实的
|
|
2126
|
+
* `EndpointRegistry<McpClientLike>` 仍可直接传入。
|
|
2127
|
+
*/
|
|
2128
|
+
async function connectStdioEndpoint(registry, config) {
|
|
2129
|
+
const sdk = await loadMcpSdk();
|
|
2130
|
+
const proc = await spawnServer(config);
|
|
2131
|
+
const transport = new ChildStdioTransport(proc.child, sdk);
|
|
2132
|
+
const client = new sdk.Client({
|
|
2133
|
+
name: "webskill-stdio-client",
|
|
2134
|
+
version: "0.1.0"
|
|
2135
|
+
});
|
|
2136
|
+
let timedOut = false;
|
|
2137
|
+
try {
|
|
2138
|
+
await withTimeout(client.connect(transport), config.timeoutMs ?? 0, () => {
|
|
2139
|
+
timedOut = true;
|
|
2140
|
+
return new WebSkillError("MCP_STDIO_TIMEOUT", `Timed out after ${config.timeoutMs}ms connecting stdio MCP endpoint "${config.endpoint}"`);
|
|
2141
|
+
});
|
|
2142
|
+
} catch (e) {
|
|
2143
|
+
await proc.terminate();
|
|
2144
|
+
if (timedOut) throw e;
|
|
2145
|
+
const exit = proc.exit();
|
|
2146
|
+
if (exit !== void 0) {
|
|
2147
|
+
const tail = proc.stderrTail().trim();
|
|
2148
|
+
throw new WebSkillError("MCP_STDIO_EXITED", `Stdio MCP endpoint "${config.endpoint}" exited before it was ready (${describeExit(exit)})` + (tail === "" ? "" : `; stderr: ${tail}`), e);
|
|
2149
|
+
}
|
|
2150
|
+
throw new WebSkillError("MCP_ENDPOINT_UNAVAILABLE", `Failed to connect stdio MCP endpoint "${config.endpoint}": ${messageOf(e)}`, e);
|
|
2151
|
+
}
|
|
2152
|
+
let closed = false;
|
|
2153
|
+
const close = async () => {
|
|
2154
|
+
if (closed) return;
|
|
2155
|
+
closed = true;
|
|
2156
|
+
registry.unregister(config.endpoint);
|
|
2157
|
+
try {
|
|
2158
|
+
await client.close();
|
|
2159
|
+
} finally {
|
|
2160
|
+
await proc.terminate();
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
try {
|
|
2164
|
+
registry.register(config.endpoint, guardCalls(client, config, close));
|
|
2165
|
+
} catch (e) {
|
|
2166
|
+
try {
|
|
2167
|
+
await client.close();
|
|
2168
|
+
} finally {
|
|
2169
|
+
await proc.terminate();
|
|
2170
|
+
}
|
|
2171
|
+
throw new WebSkillError("MCP_ENDPOINT_UNAVAILABLE", `Failed to register stdio MCP endpoint "${config.endpoint}": ${messageOf(e)}`, e);
|
|
2172
|
+
}
|
|
2173
|
+
return { close };
|
|
2174
|
+
}
|
|
1901
2175
|
|
|
1902
2176
|
//#endregion
|
|
1903
2177
|
//#region ../governance/dist/node.js
|
|
@@ -2063,4 +2337,4 @@ function createEvaluationRuntime(deps) {
|
|
|
2063
2337
|
}
|
|
2064
2338
|
|
|
2065
2339
|
//#endregion
|
|
2066
|
-
export { ApprovalWorkflow, CliUiBridge, FileArtifactStore, FileMemoryStore, NodeFS, NodeScriptExecutor, OxcSchemaInferer, ProcessSandboxExecutor, SKILLS_LOCKFILE, SKILL_MANIFEST_FILE, SandboxedScriptExecutor, SkillManager, createEvaluationRuntime, createScriptContext, exportArchive, probeLlmCapabilities, readArchiveManifest };
|
|
2340
|
+
export { ApprovalWorkflow, CliUiBridge, FileArtifactStore, FileMemoryStore, NodeFS, NodeScriptExecutor, OxcSchemaInferer, ProcessSandboxExecutor, SKILLS_LOCKFILE, SKILL_MANIFEST_FILE, SandboxedScriptExecutor, SkillManager, connectStdioEndpoint, createEvaluationRuntime, createScriptContext, exportArchive, probeLlmCapabilities, readArchiveManifest };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { q as uiCatalog } from "./dist-D9Lcn5Pp.js";
|
|
2
|
+
import { t as CatalogNode } from "./catalogComponents-KsujmL4b-Clx1kCnU.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { Component, Fragment, createContext, useCallback, useContext, useEffect, useInsertionEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -3761,7 +3761,7 @@ function Renderer({ response, library, isStreaming = false, onAction, onStateUpd
|
|
|
3761
3761
|
const FormValidationContext = createContext(null);
|
|
3762
3762
|
|
|
3763
3763
|
//#endregion
|
|
3764
|
-
//#region ../ui-react/dist/openUiLibrary-
|
|
3764
|
+
//#region ../ui-react/dist/openUiLibrary-YLS-cxyT.js
|
|
3765
3765
|
const propsFor = (props, container) => container ? props.extend({ children: z.array(z.any()).optional() }) : props;
|
|
3766
3766
|
/**
|
|
3767
3767
|
* catalog 的 OpenUI 投影:`defineComponent` 复用同一份 zod schema 与描述,
|
|
@@ -3773,10 +3773,11 @@ const webskillOpenUiLibrary = createLibrary({
|
|
|
3773
3773
|
name: def.name,
|
|
3774
3774
|
description: def.description,
|
|
3775
3775
|
props: propsFor(def.props, def.children !== void 0),
|
|
3776
|
-
component: ((input) =>
|
|
3776
|
+
component: ((input) => /* @__PURE__ */ jsx(CatalogNode, {
|
|
3777
|
+
name: def.name,
|
|
3777
3778
|
props: input.props,
|
|
3778
3779
|
children: input.renderNode(input.props["children"])
|
|
3779
|
-
})
|
|
3780
|
+
}))
|
|
3780
3781
|
}))
|
|
3781
3782
|
});
|
|
3782
3783
|
const OPEN_UI_COMPONENTS = Object.keys(webskillOpenUiLibrary.components);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { N as FileSystemProvider, R as Page, Z as SkillCatalogEntry, it as SkillManifest, z as PageQuery } from "./types-7Wcg--Vh-1YlQ4jF9.js";
|
|
2
|
+
//#region ../governance/dist/skillVersionStore-uyefLPR1.d.ts
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
type CandidateStatus = 'draft' | 'pending-review' | 'approved' | 'published' | 'rejected';
|
|
5
|
+
type CandidateSource = 'runtime-miss' | 'document' | 'manual';
|
|
6
|
+
type CandidateRisk = 'low' | 'medium' | 'high';
|
|
7
|
+
interface CandidateFile {
|
|
8
|
+
path: string;
|
|
9
|
+
kind: 'skill-md' | 'script' | 'reference' | 'asset';
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
interface CandidateSkill {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
status: CandidateStatus;
|
|
17
|
+
source: CandidateSource;
|
|
18
|
+
risk: CandidateRisk;
|
|
19
|
+
riskReasons: string[];
|
|
20
|
+
files: CandidateFile[];
|
|
21
|
+
suggestedTests?: string[];
|
|
22
|
+
createdAt: string;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
interface AuditEvent {
|
|
27
|
+
id: string;
|
|
28
|
+
type: string;
|
|
29
|
+
target: string;
|
|
30
|
+
actor?: string;
|
|
31
|
+
ts: string;
|
|
32
|
+
data?: Record<string, unknown>;
|
|
33
|
+
/** hash 链:上一条事件的 hash(首条为 GENESIS) */
|
|
34
|
+
prevHash?: string;
|
|
35
|
+
/** 本事件规范化载荷 + prevHash 的 sha256 */
|
|
36
|
+
hash?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 审计查询的筛选条件。刻意是**具名字段**而不是谓词函数:
|
|
40
|
+
* 谓词无法被存储层用索引优化,下推一个谓词等于把 `.filter()` 换了个位置。
|
|
41
|
+
* @stable
|
|
42
|
+
*/
|
|
43
|
+
interface AuditQueryFilter {
|
|
44
|
+
target?: string;
|
|
45
|
+
type?: string;
|
|
46
|
+
actor?: string;
|
|
47
|
+
/** 含端点(`ts >= since`) */
|
|
48
|
+
since?: string;
|
|
49
|
+
/** 含端点(`ts <= until`) */
|
|
50
|
+
until?: string;
|
|
51
|
+
}
|
|
52
|
+
interface AuditLog {
|
|
53
|
+
append(event: Omit<AuditEvent, 'id' | 'ts'> & {
|
|
54
|
+
id?: string;
|
|
55
|
+
ts?: string;
|
|
56
|
+
}): Promise<AuditEvent>;
|
|
57
|
+
/**
|
|
58
|
+
* 分页查询。方向与会话消息一致:无游标给**最新**一页,`nextCursor` 指向更早的历史。
|
|
59
|
+
*
|
|
60
|
+
* 分页是性能优化,不是绕过完整性检查的通道:本页范围内的 hash 链断裂
|
|
61
|
+
* 必须以 `GOVERNANCE_FAILED` 报出,不得安静地把那一页返回。
|
|
62
|
+
*/
|
|
63
|
+
query(filter?: AuditQueryFilter & PageQuery): Promise<Page<AuditEvent>>;
|
|
64
|
+
}
|
|
65
|
+
interface SkillVersion {
|
|
66
|
+
versionId: string;
|
|
67
|
+
parentVersionId?: string;
|
|
68
|
+
createdAt: string;
|
|
69
|
+
reason: string;
|
|
70
|
+
manifest: SkillManifest;
|
|
71
|
+
auditEventId?: string;
|
|
72
|
+
/** 发布时捕获的技能归档(zip 相对路径,applyRollback 恢复用;0.2.0 起记录) */
|
|
73
|
+
archivePath?: string;
|
|
74
|
+
}
|
|
75
|
+
type SkillState = 'active' | 'quarantined' | 'deprecated' | 'disabled';
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/candidate/candidateStore.d.ts
|
|
78
|
+
/** 候选列表的缺省页长。缺省属于实现,不属于调用方 */
|
|
79
|
+
declare const CANDIDATE_PAGE_SIZE = 50;
|
|
80
|
+
/** 逐文件持久化的候选存储:<managedRoot>/.webskill/candidates/<id>.json */
|
|
81
|
+
declare class CandidateStore {
|
|
82
|
+
#private;
|
|
83
|
+
constructor(deps: {
|
|
84
|
+
root: string;
|
|
85
|
+
fs: FileSystemProvider;
|
|
86
|
+
});
|
|
87
|
+
save(candidate: CandidateSkill): Promise<void>;
|
|
88
|
+
get(id: string): Promise<CandidateSkill>;
|
|
89
|
+
/**
|
|
90
|
+
* 候选(= 审批队列)分页。`status` 是端口参数而不是上层 `.filter()`:
|
|
91
|
+
* 审批页面只看 `pending-review`,没理由把已发布的候选全部拉过端口。
|
|
92
|
+
* 目录扫描仍是逐文件读(备案 D25),但返回量已由 `limit` 封顶。
|
|
93
|
+
*/
|
|
94
|
+
list(options?: {
|
|
95
|
+
status?: CandidateStatus;
|
|
96
|
+
} & PageQuery): Promise<Page<CandidateSkill>>;
|
|
97
|
+
updateStatus(id: string, status: CandidateStatus, now?: string): Promise<CandidateSkill>;
|
|
98
|
+
}
|
|
99
|
+
/** 硬门禁:仅 published 可转换为 Catalog 条目,否则 APPROVAL_REQUIRED */
|
|
100
|
+
declare function candidateToCatalogEntry(candidate: CandidateSkill): SkillCatalogEntry;
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/approval/policies.d.ts
|
|
103
|
+
interface ApprovalDecision {
|
|
104
|
+
needsHuman: boolean;
|
|
105
|
+
reason: string;
|
|
106
|
+
}
|
|
107
|
+
interface ApprovalPolicy {
|
|
108
|
+
evaluate(candidate: CandidateSkill): ApprovalDecision;
|
|
109
|
+
}
|
|
110
|
+
/** 默认策略:任何候选都必须人工审批 */
|
|
111
|
+
declare class AlwaysHumanApprovalPolicy implements ApprovalPolicy {
|
|
112
|
+
evaluate(candidate: CandidateSkill): ApprovalDecision;
|
|
113
|
+
}
|
|
114
|
+
/** 规则组合策略:首个命中的规则胜出,全部未命中走 fallback(默认 AlwaysHuman) */
|
|
115
|
+
declare class CompositeApprovalPolicy implements ApprovalPolicy {
|
|
116
|
+
#private;
|
|
117
|
+
constructor(rules: Array<(candidate: CandidateSkill) => ApprovalDecision | undefined>, fallback?: ApprovalPolicy);
|
|
118
|
+
evaluate(candidate: CandidateSkill): ApprovalDecision;
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/versioning/skillVersionStore.d.ts
|
|
122
|
+
/** 版本列表的缺省页长。缺省属于实现,不属于调用方 */
|
|
123
|
+
declare const SKILL_VERSION_PAGE_SIZE = 20;
|
|
124
|
+
/** 版本存储:manifest 快照 + parentVersionId 链;回滚 = 追加新版本(谱系不断)。
|
|
125
|
+
* 保留策略:maxArchivesPerSkill(默认 5)超出时清理最旧版本(json + zip 归档一并删除)。 */
|
|
126
|
+
declare class SkillVersionStore {
|
|
127
|
+
#private;
|
|
128
|
+
constructor(deps: {
|
|
129
|
+
root: string;
|
|
130
|
+
fs: FileSystemProvider;
|
|
131
|
+
now?: () => string;
|
|
132
|
+
createId?: () => string;
|
|
133
|
+
audit?: AuditLog;
|
|
134
|
+
/** 每技能保留的版本/归档上限(默认 5,超出清理最旧) */
|
|
135
|
+
maxArchivesPerSkill?: number;
|
|
136
|
+
});
|
|
137
|
+
add(skillName: string, input: {
|
|
138
|
+
reason: string;
|
|
139
|
+
manifest: SkillManifest;
|
|
140
|
+
parentVersionId?: string;
|
|
141
|
+
archive?: Uint8Array;
|
|
142
|
+
}): Promise<SkillVersion>;
|
|
143
|
+
/** 读取版本归档字节(applyRollback 用;未捕获归档的旧版本 → GOVERNANCE_FAILED) */
|
|
144
|
+
readArchive(skillName: string, versionId: string): Promise<Uint8Array>;
|
|
145
|
+
/**
|
|
146
|
+
* 版本列表分页(方向同 C1:无游标给**最新**一页)。
|
|
147
|
+
* 保留策略与谱系构建走 `#readAll`:它们必须看到全量,不能被分页截断。
|
|
148
|
+
*/
|
|
149
|
+
list(skillName: string, options?: PageQuery): Promise<Page<SkillVersion>>;
|
|
150
|
+
get(skillName: string, versionId: string): Promise<SkillVersion>;
|
|
151
|
+
/** 回滚:基于旧 manifest 追加新版本 + skill.rolled_back 审计 */
|
|
152
|
+
rollback(skillName: string, targetVersionId: string, input: {
|
|
153
|
+
actor?: string;
|
|
154
|
+
reason?: string;
|
|
155
|
+
}): Promise<SkillVersion>;
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
export { SkillVersion as _, AuditLog as a, CandidateFile as c, CandidateSource as d, CandidateStatus as f, SkillState as g, SKILL_VERSION_PAGE_SIZE as h, AuditEvent as i, CandidateRisk as l, CompositeApprovalPolicy as m, ApprovalDecision as n, AuditQueryFilter as o, CandidateStore as p, ApprovalPolicy as r, CANDIDATE_PAGE_SIZE as s, AlwaysHumanApprovalPolicy as t, CandidateSkill as u, SkillVersionStore as v, candidateToCatalogEntry as y };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { y as JSONRPCMessageSchema } from "./types-WovEf4ED-CZSDiiBU.js";
|
|
2
|
+
|
|
3
|
+
//#region ../node/dist/stdio-CFMoANJJ.js
|
|
4
|
+
/**
|
|
5
|
+
* Buffers a continuous stdio stream into discrete JSON-RPC messages.
|
|
6
|
+
*/
|
|
7
|
+
var ReadBuffer = class {
|
|
8
|
+
append(chunk) {
|
|
9
|
+
this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
|
|
10
|
+
}
|
|
11
|
+
readMessage() {
|
|
12
|
+
if (!this._buffer) return null;
|
|
13
|
+
const index = this._buffer.indexOf("\n");
|
|
14
|
+
if (index === -1) return null;
|
|
15
|
+
const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
|
|
16
|
+
this._buffer = this._buffer.subarray(index + 1);
|
|
17
|
+
return deserializeMessage(line);
|
|
18
|
+
}
|
|
19
|
+
clear() {
|
|
20
|
+
this._buffer = void 0;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function deserializeMessage(line) {
|
|
24
|
+
return JSONRPCMessageSchema.parse(JSON.parse(line));
|
|
25
|
+
}
|
|
26
|
+
function serializeMessage(message) {
|
|
27
|
+
return JSON.stringify(message) + "\n";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
export { ReadBuffer, deserializeMessage, serializeMessage };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { m as WebSkillError } from "./dist-8oQRa8Xz.js";
|
|
2
|
+
import { i as textParts, n as partsToText } from "./memoryArtifactStore-BtOeB_hm-tj3fC5ip.js";
|
|
2
3
|
|
|
3
4
|
//#region ../runtime/dist/testing.js
|
|
4
5
|
/**
|
|
@@ -27,7 +28,7 @@ var MockLlmClient = class {
|
|
|
27
28
|
else if (event.type === "tool-calls") toolCalls = event.toolCalls;
|
|
28
29
|
else if (event.type === "done" && event.content !== void 0) content = event.content;
|
|
29
30
|
return {
|
|
30
|
-
content,
|
|
31
|
+
content: content === "" ? void 0 : textParts(content),
|
|
31
32
|
toolCalls
|
|
32
33
|
};
|
|
33
34
|
}
|
|
@@ -43,11 +44,12 @@ var MockLlmClient = class {
|
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
46
|
const response = typeof next === "function" ? await next(input) : next;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const text = partsToText(response.content);
|
|
48
|
+
if (text !== "") {
|
|
49
|
+
const chunkSize = Math.max(1, Math.ceil(text.length / 3));
|
|
50
|
+
for (let i = 0; i < text.length; i += chunkSize) yield {
|
|
49
51
|
type: "text-delta",
|
|
50
|
-
delta:
|
|
52
|
+
delta: text.slice(i, i + chunkSize)
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
55
|
if (response.toolCalls?.length) yield {
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as LlmClient,
|
|
1
|
+
import { c as LlmClient, f as LlmResponse, g as MemoryStore, l as LlmCompleteInput, n as ArtifactStore, o as InteractionRequest, p as LlmStreamEvent, s as InteractionResponse, t as Artifact, y as UiBridge } from "./types-7Wcg--Vh-1YlQ4jF9.js";
|
|
2
2
|
import { a as loadGoogleConfigFromEnv, i as loadAnthropicConfigFromEnv, n as LlmEnvConfig, o as loadLlmConfigFromEnv, r as ProviderEnvConfig } from "./env-BPUBZCwJ-4jat_SVG.js";
|
|
3
3
|
//#region ../runtime/dist/testing.d.ts
|
|
4
4
|
//#region src/llm/mockLlmClient.d.ts
|
package/dist/testing.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as MemoryArtifactStore } from "./memoryArtifactStore-
|
|
1
|
+
import { t as MemoryArtifactStore } from "./memoryArtifactStore-BtOeB_hm-tj3fC5ip.js";
|
|
2
2
|
import { n as loadGoogleConfigFromEnv, r as loadLlmConfigFromEnv, t as loadAnthropicConfigFromEnv } from "./env--jJB-TSX-04klhTYi.js";
|
|
3
|
-
import { n as MockLlmClient, r as MockUiBridge, t as InMemoryStore } from "./testing-
|
|
3
|
+
import { n as MockLlmClient, r as MockUiBridge, t as InMemoryStore } from "./testing-DDCJWvgA.js";
|
|
4
4
|
|
|
5
5
|
export { InMemoryStore, MemoryArtifactStore, MockLlmClient, MockUiBridge, loadAnthropicConfigFromEnv, loadGoogleConfigFromEnv, loadLlmConfigFromEnv };
|