@webskill/sdk 0.0.1 → 0.0.3
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 +16 -13
- package/dist/browser.d.ts +67 -2
- package/dist/browser.js +275 -4
- package/dist/{dist-9fczg9Pa.js → dist-C1p26Ap9.js} +214 -32
- package/dist/{dist-B_ldNWwT.js → dist-hMMxARXK.js} +74 -6
- package/dist/governance.d.ts +2 -2
- package/dist/governance.js +2 -2
- package/dist/{index-Bq-bTWh3.d.ts → index-D65Jk0ju.d.ts} +16 -53
- package/dist/{index-88KNOnbr.d.ts → index-DnI0BTEp.d.ts} +89 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/node.d.ts +3 -3
- package/dist/node.js +3 -3
- package/dist/ui-react.d.ts +1 -1
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +1 -1
- package/package.json +5 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { D as normalizeToolContent,
|
|
1
|
+
import { $ as validateSkills, B as WebSkillError, D as normalizeToolContent, F as SKILL_MANIFEST_FILE, H as buildManifest, K as isValidSkillName, O as parseBridgeRequest, P as SKILLS_LOCKFILE, Q as resolveInsideRoot, Y as parseSkillMarkdown, et as verifyManifest, o as FsArtifactStore, s as FsMemoryStore, x as bridgeError } from "./dist-hMMxARXK.js";
|
|
2
2
|
import { existsSync, promises, readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { format, promisify } from "node:util";
|
|
6
6
|
import { Worker } from "node:worker_threads";
|
|
7
|
+
import { parseSync } from "oxc-parser";
|
|
7
8
|
import { createInterface } from "node:readline/promises";
|
|
8
9
|
import { mkdtemp } from "node:fs/promises";
|
|
9
10
|
import { tmpdir } from "node:os";
|
|
@@ -416,6 +417,178 @@ var SandboxedScriptExecutor = class {
|
|
|
416
417
|
}
|
|
417
418
|
}
|
|
418
419
|
};
|
|
420
|
+
const PLACEHOLDER_PREFIX = "Inferred placeholder (unsupported type: ";
|
|
421
|
+
const placeholder = (text) => ({
|
|
422
|
+
type: "string",
|
|
423
|
+
description: `${PLACEHOLDER_PREFIX}${text})`,
|
|
424
|
+
"x-inferred": true
|
|
425
|
+
});
|
|
426
|
+
/** 主路径映射:基础类型/数组/嵌套对象/可选/字面量 union;其余降级 placeholder */
|
|
427
|
+
var TypeMapper = class {
|
|
428
|
+
#decls = /* @__PURE__ */ new Map();
|
|
429
|
+
#source;
|
|
430
|
+
constructor(body, source) {
|
|
431
|
+
this.#source = source;
|
|
432
|
+
for (const node of body) {
|
|
433
|
+
if (node.type === "TSInterfaceDeclaration" || node.type === "TSTypeAliasDeclaration") this.#decls.set(node.id?.name, node);
|
|
434
|
+
if (node.type === "ExportNamedDeclaration" && (node.declaration?.type === "TSInterfaceDeclaration" || node.declaration?.type === "TSTypeAliasDeclaration")) this.#decls.set(node.declaration.id?.name, node.declaration);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
objectSchema(t, seen) {
|
|
438
|
+
const mapped = this.map(t, seen);
|
|
439
|
+
return mapped?.type === "object" ? mapped : void 0;
|
|
440
|
+
}
|
|
441
|
+
map(t, seen) {
|
|
442
|
+
if (!t) return void 0;
|
|
443
|
+
switch (t.type) {
|
|
444
|
+
case "TSStringKeyword": return { type: "string" };
|
|
445
|
+
case "TSNumberKeyword": return { type: "number" };
|
|
446
|
+
case "TSBooleanKeyword": return { type: "boolean" };
|
|
447
|
+
case "TSAnyKeyword":
|
|
448
|
+
case "TSUnknownKeyword": return {};
|
|
449
|
+
case "TSArrayType": return {
|
|
450
|
+
type: "array",
|
|
451
|
+
items: this.map(t.elementType, seen) ?? {}
|
|
452
|
+
};
|
|
453
|
+
case "TSTypeLiteral": return this.#literalObject(t, seen);
|
|
454
|
+
case "TSLiteralType": return this.#literal(t);
|
|
455
|
+
case "TSUnionType": return this.#union(t, seen);
|
|
456
|
+
case "TSTypeReference": return this.#reference(t, seen);
|
|
457
|
+
case "TSParenthesizedType": return this.map(t.typeAnnotation, seen);
|
|
458
|
+
default: return placeholder(this.#text(t));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
#literal(t) {
|
|
462
|
+
const value = t.literal?.value ?? t.value;
|
|
463
|
+
if (typeof value === "string") return {
|
|
464
|
+
type: "string",
|
|
465
|
+
enum: [value]
|
|
466
|
+
};
|
|
467
|
+
return placeholder(this.#text(t));
|
|
468
|
+
}
|
|
469
|
+
#union(t, seen) {
|
|
470
|
+
const values = [];
|
|
471
|
+
for (const member of t.types ?? []) {
|
|
472
|
+
const value = member.literal?.value ?? member.value;
|
|
473
|
+
if (member.type === "TSLiteralType" && typeof value === "string") values.push(value);
|
|
474
|
+
else return placeholder(this.#text(t));
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
type: "string",
|
|
478
|
+
enum: values
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
#literalObject(t, seen) {
|
|
482
|
+
const properties = {};
|
|
483
|
+
const required = [];
|
|
484
|
+
for (const member of t.members ?? []) {
|
|
485
|
+
if (member.type !== "TSPropertySignature") continue;
|
|
486
|
+
const name = member.key?.name ?? member.key?.value;
|
|
487
|
+
if (typeof name !== "string") continue;
|
|
488
|
+
const annotation = member.typeAnnotation?.typeAnnotation;
|
|
489
|
+
properties[name] = annotation ? this.map(annotation, seen) ?? {} : { type: "string" };
|
|
490
|
+
if (!member.optional) required.push(name);
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
type: "object",
|
|
494
|
+
properties,
|
|
495
|
+
...required.length ? { required } : {}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
#reference(t, seen) {
|
|
499
|
+
const name = t.typeName?.name ?? t.typeName?.left?.name;
|
|
500
|
+
const params = t.typeArguments?.params ?? t.typeParameters?.params ?? [];
|
|
501
|
+
if (name === "Array" && params.length === 1) return {
|
|
502
|
+
type: "array",
|
|
503
|
+
items: this.map(params[0], seen) ?? {}
|
|
504
|
+
};
|
|
505
|
+
if (name === "string" || name === "number" || name === "boolean") return { type: name };
|
|
506
|
+
if (!name || params.length > 0) return placeholder(this.#text(t));
|
|
507
|
+
if (seen.has(name)) return placeholder(`recursive reference ${name}`);
|
|
508
|
+
const decl = this.#decls.get(name);
|
|
509
|
+
if (!decl) return placeholder(this.#text(t));
|
|
510
|
+
const nextSeen = new Set(seen).add(name);
|
|
511
|
+
if (decl.type === "TSInterfaceDeclaration") return this.#literalObject({ members: decl.body?.body ?? [] }, nextSeen);
|
|
512
|
+
return this.map(decl.typeAnnotation, nextSeen) ?? placeholder(this.#text(t));
|
|
513
|
+
}
|
|
514
|
+
#text(t) {
|
|
515
|
+
if (typeof t.start === "number" && typeof t.end === "number") return this.#source.slice(t.start, t.end);
|
|
516
|
+
return String(t.type);
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
const findRunFunction = (body) => {
|
|
520
|
+
for (const node of body) {
|
|
521
|
+
if (node.type !== "ExportNamedDeclaration") continue;
|
|
522
|
+
const decl = node.declaration;
|
|
523
|
+
if (!decl) continue;
|
|
524
|
+
if (decl.type === "FunctionDeclaration" && decl.id?.name === "run") return decl;
|
|
525
|
+
if (decl.type === "VariableDeclaration") {
|
|
526
|
+
for (const d of decl.declarations ?? []) if (d.id?.name === "run" && (d.init?.type === "ArrowFunctionExpression" || d.init?.type === "FunctionExpression")) return d.init;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
/** JSDoc 辅路径:@param {type} input.name - desc(点形式)与 @param {{...}} input(对象形式) */
|
|
531
|
+
function inferFromJsDoc(jsdoc, mapper, reparse) {
|
|
532
|
+
const properties = {};
|
|
533
|
+
const required = [];
|
|
534
|
+
const objectForm = /@param\s+\{\{([\s\S]+?)\}\}\s+input(?:\s+-\s+(.*))?/.exec(jsdoc);
|
|
535
|
+
if (objectForm) {
|
|
536
|
+
const alias = reparse(`{${objectForm[1]}}`);
|
|
537
|
+
if (!alias) return void 0;
|
|
538
|
+
const schema = mapper.map(alias, /* @__PURE__ */ new Set());
|
|
539
|
+
return schema?.type === "object" ? schema : void 0;
|
|
540
|
+
}
|
|
541
|
+
const dotForm = /@param\s+\{([^}]+)\}\s+input\.(\w+)(?:\s+-\s+([^\n*]+))?/g;
|
|
542
|
+
let matched = false;
|
|
543
|
+
for (const m of jsdoc.matchAll(dotForm)) {
|
|
544
|
+
const [, typeText, name, desc] = m;
|
|
545
|
+
if (!typeText || !name) continue;
|
|
546
|
+
matched = true;
|
|
547
|
+
const alias = reparse(typeText.trim());
|
|
548
|
+
const schema = alias ? mapper.map(alias, /* @__PURE__ */ new Set()) ?? {} : { type: "string" };
|
|
549
|
+
if (desc?.trim()) schema.description = desc.trim();
|
|
550
|
+
properties[name] = schema;
|
|
551
|
+
required.push(name);
|
|
552
|
+
}
|
|
553
|
+
if (!matched) return void 0;
|
|
554
|
+
return {
|
|
555
|
+
type: "object",
|
|
556
|
+
properties,
|
|
557
|
+
required
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* D2 Schema 推导(OXC 静态文本分析,宿主侧执行,不进沙箱)。
|
|
562
|
+
* TS 类型标注为主路径,JSDoc @param 为辅路径;不支持类型降级 string 并标 'x-inferred'。
|
|
563
|
+
*/
|
|
564
|
+
var OxcSchemaInferer = class {
|
|
565
|
+
inferSchemaFromSource(source, options) {
|
|
566
|
+
let program;
|
|
567
|
+
let comments;
|
|
568
|
+
try {
|
|
569
|
+
const result = parseSync(options?.fileName ?? "script.ts", source);
|
|
570
|
+
if (result.errors?.length > 0) return void 0;
|
|
571
|
+
program = result.program;
|
|
572
|
+
comments = result.comments ?? [];
|
|
573
|
+
} catch {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const runFn = findRunFunction(program.body ?? []);
|
|
577
|
+
if (!runFn) return void 0;
|
|
578
|
+
const annotation = (runFn.params?.[0] ?? runFn.params?.items?.[0])?.typeAnnotation?.typeAnnotation;
|
|
579
|
+
const mapper = new TypeMapper(program.body ?? [], source);
|
|
580
|
+
if (annotation) return mapper.objectSchema(annotation, /* @__PURE__ */ new Set());
|
|
581
|
+
const runStart = runFn.start ?? Number.MAX_SAFE_INTEGER;
|
|
582
|
+
const jsdoc = comments.filter((c) => c.type === "Block" && typeof c.value === "string" && c.value.includes("@param") && typeof c.end === "number" && c.end <= runStart).sort((a, b) => b.end - a.end)[0];
|
|
583
|
+
if (!jsdoc) return void 0;
|
|
584
|
+
const reparse = (typeText) => {
|
|
585
|
+
const r = parseSync("__t.ts", `type __T = ${typeText};`);
|
|
586
|
+
if (r.errors?.length > 0) return void 0;
|
|
587
|
+
return r.program.body.find((n) => n.type === "TSTypeAliasDeclaration")?.typeAnnotation;
|
|
588
|
+
};
|
|
589
|
+
return inferFromJsDoc(jsdoc.value, mapper, reparse);
|
|
590
|
+
}
|
|
591
|
+
};
|
|
419
592
|
/**
|
|
420
593
|
* FileArtifactStore:兼容别名,语义同阶段 2-4。
|
|
421
594
|
* 实现已上移到 runtime 的 FsArtifactStore;node 侧保留 NodeFS 默认值。
|
|
@@ -623,10 +796,6 @@ async function removeDirQuiet(fs, dir) {
|
|
|
623
796
|
if (await fs.exists(dir)) await fs.remove(dir, { recursive: true });
|
|
624
797
|
} catch {}
|
|
625
798
|
}
|
|
626
|
-
/** 技能目录内的安装清单文件名(不参与 files 列表与 digest) */
|
|
627
|
-
const SKILL_MANIFEST_FILE = "webskill.skill-manifest.json";
|
|
628
|
-
/** managed root 下的锁定文件名(不参与 files 列表与 digest) */
|
|
629
|
-
const SKILLS_LOCKFILE = "skills.lock.json";
|
|
630
799
|
const messageOf$3 = (e) => e instanceof Error ? e.message : String(e);
|
|
631
800
|
/** 导出技能目录全部文件(含 manifest)为 zip/tar 归档,返回 outPath */
|
|
632
801
|
async function exportArchive(fs, skillRoot, options) {
|
|
@@ -651,7 +820,7 @@ async function readArchiveManifest(fs, archivePath) {
|
|
|
651
820
|
const data = await fs.readBinary(archivePath);
|
|
652
821
|
if (isZipArchive(data)) {
|
|
653
822
|
const entries = unzipSync(data);
|
|
654
|
-
const key = Object.keys(entries).find((k) => k === "webskill.skill-manifest.json" || k.endsWith(
|
|
823
|
+
const key = Object.keys(entries).find((k) => k === "webskill.skill-manifest.json" || k.endsWith(`/${"webskill.skill-manifest.json"}`));
|
|
655
824
|
if (!key) throw notFound();
|
|
656
825
|
return JSON.parse(new TextDecoder().decode(entries[key]));
|
|
657
826
|
}
|
|
@@ -660,7 +829,7 @@ async function readArchiveManifest(fs, archivePath) {
|
|
|
660
829
|
await tar.t({
|
|
661
830
|
file: toPlatform(archivePath),
|
|
662
831
|
onentry: (entry) => {
|
|
663
|
-
if (entry.path === "webskill.skill-manifest.json" || entry.path.endsWith(
|
|
832
|
+
if (entry.path === "webskill.skill-manifest.json" || entry.path.endsWith(`/${"webskill.skill-manifest.json"}`)) {
|
|
664
833
|
const chunks = [];
|
|
665
834
|
entry.on("data", (chunk) => chunks.push(chunk));
|
|
666
835
|
entry.on("end", () => {
|
|
@@ -730,10 +899,6 @@ async function stageGit(source, ctx) {
|
|
|
730
899
|
function sha256Hex(data) {
|
|
731
900
|
return createHash("sha256").update(data).digest("hex");
|
|
732
901
|
}
|
|
733
|
-
/** 整体 digest:逐文件 "<path>:<sha256>\n" 拼接串(文件按 path 排序)的 sha256 */
|
|
734
|
-
function computeDigest(files) {
|
|
735
|
-
return sha256Hex([...files].sort((a, b) => a.path.localeCompare(b.path)).map((f) => `${f.path}:${f.sha256}\n`).join(""));
|
|
736
|
-
}
|
|
737
902
|
const messageOf$1 = (e) => e instanceof Error ? e.message : String(e);
|
|
738
903
|
/** http 源:下载归档(可选 expectedSha256 校验包体)→ 解包 → 定位技能根 */
|
|
739
904
|
async function stageHttp(source, ctx, expectedSha256) {
|
|
@@ -868,18 +1033,14 @@ async function createManifest(fs, skillRoot, input) {
|
|
|
868
1033
|
sha256: sha256Hex(content)
|
|
869
1034
|
});
|
|
870
1035
|
}
|
|
871
|
-
const manifest = {
|
|
872
|
-
schemaVersion: 1,
|
|
1036
|
+
const manifest = await buildManifest({
|
|
873
1037
|
name: input.name,
|
|
874
1038
|
...input.version !== void 0 ? { version: input.version } : {},
|
|
875
1039
|
source: input.source,
|
|
876
1040
|
installedAt: input.installedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
},
|
|
881
|
-
files
|
|
882
|
-
};
|
|
1041
|
+
files,
|
|
1042
|
+
sha256: sha256Hex
|
|
1043
|
+
});
|
|
883
1044
|
await fs.writeText(`${skillRoot}/${SKILL_MANIFEST_FILE}`, JSON.stringify(manifest, null, 2));
|
|
884
1045
|
return manifest;
|
|
885
1046
|
}
|
|
@@ -893,22 +1054,15 @@ async function readManifest(fs, skillRoot) {
|
|
|
893
1054
|
throw new WebSkillError("INTEGRITY_FAILED", `Skill manifest at ${path} is corrupted: ${e instanceof Error ? e.message : String(e)}`, e);
|
|
894
1055
|
}
|
|
895
1056
|
}
|
|
896
|
-
/** 按 manifest.files 逐文件重算 sha256
|
|
1057
|
+
/** 按 manifest.files 逐文件重算 sha256 比对(core 纯逻辑校验) */
|
|
897
1058
|
async function verifyIntegrity(fs, skillRoot) {
|
|
898
1059
|
const manifest = await readManifest(fs, skillRoot);
|
|
899
|
-
const
|
|
1060
|
+
const actualHashes = /* @__PURE__ */ new Map();
|
|
900
1061
|
for (const file of manifest.files) {
|
|
901
1062
|
const path = `${skillRoot}/${file.path}`;
|
|
902
|
-
if (
|
|
903
|
-
mismatches.push(file.path);
|
|
904
|
-
continue;
|
|
905
|
-
}
|
|
906
|
-
if (sha256Hex(await fs.readBinary(path)) !== file.sha256) mismatches.push(file.path);
|
|
1063
|
+
if (await fs.exists(path)) actualHashes.set(file.path, sha256Hex(await fs.readBinary(path)));
|
|
907
1064
|
}
|
|
908
|
-
return
|
|
909
|
-
ok: mismatches.length === 0,
|
|
910
|
-
mismatches
|
|
911
|
-
};
|
|
1065
|
+
return verifyManifest(manifest, actualHashes);
|
|
912
1066
|
}
|
|
913
1067
|
const messageOf = (e) => e instanceof Error ? e.message : String(e);
|
|
914
1068
|
const asInstallFailed = (e) => e instanceof WebSkillError && e.code === "INSTALL_FAILED" ? e : new WebSkillError("INSTALL_FAILED", `Install failed: ${messageOf(e)}`, e);
|
|
@@ -920,10 +1074,13 @@ var SkillManager = class {
|
|
|
920
1074
|
#managedRoot;
|
|
921
1075
|
#fs;
|
|
922
1076
|
#fetchImpl;
|
|
1077
|
+
#schemaInference;
|
|
1078
|
+
#schemaInferer = new OxcSchemaInferer();
|
|
923
1079
|
constructor(deps) {
|
|
924
1080
|
this.#managedRoot = deps.managedRoot.replace(/\/+$/, "");
|
|
925
1081
|
this.#fs = deps.fs ?? new NodeFS();
|
|
926
1082
|
this.#fetchImpl = deps.fetchImpl;
|
|
1083
|
+
this.#schemaInference = deps.schemaInference ?? true;
|
|
927
1084
|
}
|
|
928
1085
|
async install(source, options) {
|
|
929
1086
|
const fs = this.#fs;
|
|
@@ -950,6 +1107,7 @@ var SkillManager = class {
|
|
|
950
1107
|
case "npm":
|
|
951
1108
|
staged = await stageNpm(source, ctx);
|
|
952
1109
|
break;
|
|
1110
|
+
case "archive": throw new WebSkillError("TOOL_UNSUPPORTED", "The \"archive\" install source is only supported by the browser skill manager");
|
|
953
1111
|
}
|
|
954
1112
|
let name;
|
|
955
1113
|
let version;
|
|
@@ -971,6 +1129,7 @@ var SkillManager = class {
|
|
|
971
1129
|
targetDir = `${this.#managedRoot}/${name}`;
|
|
972
1130
|
if (await fs.exists(targetDir)) await fs.remove(targetDir, { recursive: true });
|
|
973
1131
|
await copyDir(fs, finalDir, targetDir);
|
|
1132
|
+
await this.#inferSkillSchemas(targetDir);
|
|
974
1133
|
const manifest = await createManifest(fs, targetDir, {
|
|
975
1134
|
name,
|
|
976
1135
|
...version ? { version } : {},
|
|
@@ -990,6 +1149,28 @@ var SkillManager = class {
|
|
|
990
1149
|
await removeDirQuiet(fs, stagingRoot);
|
|
991
1150
|
}
|
|
992
1151
|
}
|
|
1152
|
+
/**
|
|
1153
|
+
* D2 安装期预推导:scripts/ 下无显式 inputSchema 导出且无 sidecar 的脚本,
|
|
1154
|
+
* 经 OXC 推导写 sidecar(计入 manifest.files;重装重新生成)
|
|
1155
|
+
*/
|
|
1156
|
+
async #inferSkillSchemas(skillRoot) {
|
|
1157
|
+
if (!this.#schemaInference) return;
|
|
1158
|
+
const fs = this.#fs;
|
|
1159
|
+
const scriptsDir = `${skillRoot}/scripts`;
|
|
1160
|
+
if (!await fs.exists(scriptsDir)) return;
|
|
1161
|
+
for (const entry of await fs.list(scriptsDir)) {
|
|
1162
|
+
if (entry.type !== "file") continue;
|
|
1163
|
+
const match = /^(.*)\.(ts|js)$/.exec(entry.path.split("/").pop() ?? "");
|
|
1164
|
+
if (!match?.[1]) continue;
|
|
1165
|
+
const scriptName = match[1];
|
|
1166
|
+
const sidecar = `${scriptsDir}/${scriptName}.schema.json`;
|
|
1167
|
+
if (await fs.exists(sidecar)) continue;
|
|
1168
|
+
const source = await fs.readText(entry.path);
|
|
1169
|
+
if (/export\s+(?:const|let|var)\s+inputSchema/.test(source)) continue;
|
|
1170
|
+
const inferred = this.#schemaInferer.inferSchemaFromSource(source, { fileName: `${scriptName}.${match[2]}` });
|
|
1171
|
+
if (inferred) await fs.writeText(sidecar, JSON.stringify(inferred, null, 2));
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
993
1174
|
async uninstall(name) {
|
|
994
1175
|
if (!isValidSkillName(name)) throw new WebSkillError("UNINSTALL_FAILED", `Invalid skill name (path traversal rejected): ${JSON.stringify(name)}`);
|
|
995
1176
|
const targetDir = `${this.#managedRoot}/${name}`;
|
|
@@ -1099,7 +1280,8 @@ async function doProbeLlmCapabilities(config) {
|
|
|
1099
1280
|
}),
|
|
1100
1281
|
signal: AbortSignal.timeout(15e3)
|
|
1101
1282
|
});
|
|
1102
|
-
|
|
1283
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
1284
|
+
if (res.ok && !contentType.includes("text/event-stream")) return {
|
|
1103
1285
|
available: true,
|
|
1104
1286
|
nonStreaming: true
|
|
1105
1287
|
};
|
|
@@ -1119,4 +1301,4 @@ async function doProbeLlmCapabilities(config) {
|
|
|
1119
1301
|
}
|
|
1120
1302
|
|
|
1121
1303
|
//#endregion
|
|
1122
|
-
export { NodeScriptExecutor as a,
|
|
1304
|
+
export { NodeScriptExecutor as a, SkillManager as c, readArchiveManifest as d, NodeFS as i, loadLlmConfigFromEnv as l, FileArtifactStore as n, OxcSchemaInferer as o, FileMemoryStore as r, SandboxedScriptExecutor as s, CliUiBridge as t, probeLlmCapabilities as u };
|
|
@@ -12,6 +12,41 @@ var WebSkillError = class extends Error {
|
|
|
12
12
|
this.details = details;
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
|
+
/** 技能目录内的安装清单文件名(不参与 files 列表与 digest) */
|
|
16
|
+
const SKILL_MANIFEST_FILE = "webskill.skill-manifest.json";
|
|
17
|
+
/** managed root 下的锁定文件名(不参与 files 列表与 digest) */
|
|
18
|
+
const SKILLS_LOCKFILE = "skills.lock.json";
|
|
19
|
+
/** 整体 digest:逐文件 "<path>:<sha256>\n" 拼接串(文件按 path 排序)的 sha256(hash 由调用方提供,可为 WebCrypto 异步实现) */
|
|
20
|
+
async function computeDigest(files, sha256) {
|
|
21
|
+
return sha256([...files].sort((a, b) => a.path.localeCompare(b.path)).map((f) => `${f.path}:${f.sha256}\n`).join(""));
|
|
22
|
+
}
|
|
23
|
+
/** 由逐文件清单构建 manifest(digest 由 computeDigest 计算) */
|
|
24
|
+
async function buildManifest(input) {
|
|
25
|
+
return {
|
|
26
|
+
schemaVersion: 1,
|
|
27
|
+
name: input.name,
|
|
28
|
+
...input.version !== void 0 ? { version: input.version } : {},
|
|
29
|
+
source: input.source,
|
|
30
|
+
installedAt: input.installedAt,
|
|
31
|
+
integrity: {
|
|
32
|
+
algorithm: "sha256",
|
|
33
|
+
digest: await computeDigest(input.files, input.sha256)
|
|
34
|
+
},
|
|
35
|
+
files: input.files
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** 按 manifest.files 比对实际 hash(调用方负责计算 actualHashes) */
|
|
39
|
+
function verifyManifest(manifest, actualHashes) {
|
|
40
|
+
const mismatches = [];
|
|
41
|
+
for (const file of manifest.files) {
|
|
42
|
+
const actual = actualHashes.get(file.path);
|
|
43
|
+
if (actual === void 0 || actual !== file.sha256) mismatches.push(file.path);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ok: mismatches.length === 0,
|
|
47
|
+
mismatches
|
|
48
|
+
};
|
|
49
|
+
}
|
|
15
50
|
const ROOT = "/";
|
|
16
51
|
/** 内存文件系统:路径一律规范化为 `/` 分隔,写入自动创建父目录 */
|
|
17
52
|
var MemoryFS = class {
|
|
@@ -182,6 +217,8 @@ function isValidSkillName(name) {
|
|
|
182
217
|
return name.length <= 64 && SKILL_NAME_PATTERN.test(name);
|
|
183
218
|
}
|
|
184
219
|
const SCRIPT_EXTENSION_RE = /\.(ts|js)$/;
|
|
220
|
+
/** D2 schema sidecar(scripts/<name>.schema.json)属于协议内文件,不算非法脚本 */
|
|
221
|
+
const SCHEMA_SIDECAR_RE = /\.schema\.json$/;
|
|
185
222
|
/**
|
|
186
223
|
* 技能合规规则的唯一实现。SkillDiscovery 与 validateSkills 都必须调用本函数,
|
|
187
224
|
* 禁止各自重复实现规则(避免两套规则漂移)。
|
|
@@ -217,10 +254,10 @@ function checkSkillRules(input) {
|
|
|
217
254
|
message: `Duplicate skill name ${JSON.stringify(metadata.name)}`
|
|
218
255
|
});
|
|
219
256
|
}
|
|
220
|
-
for (const fileName of input.scriptFileNames ?? []) if (!SCRIPT_EXTENSION_RE.test(fileName)) issues.push({
|
|
257
|
+
for (const fileName of input.scriptFileNames ?? []) if (!SCRIPT_EXTENSION_RE.test(fileName) && !SCHEMA_SIDECAR_RE.test(fileName)) issues.push({
|
|
221
258
|
code: "SKILL_UNSUPPORTED_SCRIPT",
|
|
222
259
|
severity: "error",
|
|
223
|
-
message: `Unsupported script file in scripts/ of directory ${dirName}: ${fileName} (only .ts/.js allowed)`
|
|
260
|
+
message: `Unsupported script file in scripts/ of directory ${dirName}: ${fileName} (only .ts/.js and .schema.json sidecars allowed)`
|
|
224
261
|
});
|
|
225
262
|
return issues;
|
|
226
263
|
}
|
|
@@ -1329,7 +1366,10 @@ var AgentLoop = class {
|
|
|
1329
1366
|
#config;
|
|
1330
1367
|
#policy;
|
|
1331
1368
|
constructor(deps, config = {}) {
|
|
1332
|
-
this.#deps =
|
|
1369
|
+
this.#deps = {
|
|
1370
|
+
...deps,
|
|
1371
|
+
artifactStore: deps.artifactStore ?? new MemoryArtifactStore()
|
|
1372
|
+
};
|
|
1333
1373
|
this.#config = {
|
|
1334
1374
|
maxTurns: config.maxTurns ?? 10,
|
|
1335
1375
|
totalTimeoutMs: config.totalTimeoutMs ?? 12e4,
|
|
@@ -1344,7 +1384,8 @@ var AgentLoop = class {
|
|
|
1344
1384
|
};
|
|
1345
1385
|
}
|
|
1346
1386
|
async run(input) {
|
|
1347
|
-
const
|
|
1387
|
+
const llm = this.#deps.llm;
|
|
1388
|
+
const artifactStore = this.#deps.artifactStore;
|
|
1348
1389
|
const now = this.#deps.clock?.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
1349
1390
|
const runId = input.runId ?? `run-${Math.random().toString(36).slice(2, 10)}`;
|
|
1350
1391
|
const trace = new TraceRecorder(runId, this.#deps.clock);
|
|
@@ -1734,6 +1775,8 @@ var AgentLoop = class {
|
|
|
1734
1775
|
await this.#writeActivationMemory(skillName, state);
|
|
1735
1776
|
const root = this.#deps.skillIndex.get(skillName);
|
|
1736
1777
|
if (!root) return "";
|
|
1778
|
+
const executor = this.#deps.executor;
|
|
1779
|
+
if (!executor) return "";
|
|
1737
1780
|
const loaded = [];
|
|
1738
1781
|
let scriptFiles = [];
|
|
1739
1782
|
try {
|
|
@@ -1745,13 +1788,36 @@ var AgentLoop = class {
|
|
|
1745
1788
|
const match = /^(.*)\.(ts|js)$/.exec(file);
|
|
1746
1789
|
if (!match?.[1]) continue;
|
|
1747
1790
|
try {
|
|
1748
|
-
const def = await
|
|
1791
|
+
const def = await executor.loadDefinition(root, match[1]);
|
|
1792
|
+
await this.#enrichDefinition(root, match[1], def, state);
|
|
1749
1793
|
state.activatedTools.set(def.name, def);
|
|
1750
1794
|
loaded.push(def.name);
|
|
1751
1795
|
} catch {}
|
|
1752
1796
|
}
|
|
1753
1797
|
return loaded.length ? `\n\nActivated tools: ${loaded.join(", ")}` : "";
|
|
1754
1798
|
}
|
|
1799
|
+
/**
|
|
1800
|
+
* D2 Schema 兜底链(同一 run 内激活时只算一次):
|
|
1801
|
+
* 显式 inputSchema > sidecar scripts/<name>.schema.json > schemaInferer 推导 > schemaUnavailable
|
|
1802
|
+
*/
|
|
1803
|
+
async #enrichDefinition(skillRoot, scriptName, def, state) {
|
|
1804
|
+
if (def.inputSchema) return;
|
|
1805
|
+
const sidecar = `${skillRoot}/scripts/${scriptName}.schema.json`;
|
|
1806
|
+
if (await this.#deps.fs.exists(sidecar)) try {
|
|
1807
|
+
def.inputSchema = JSON.parse(await this.#deps.fs.readText(sidecar));
|
|
1808
|
+
return;
|
|
1809
|
+
} catch (e) {
|
|
1810
|
+
state.trace.record("run.warning", { message: `Failed to parse schema sidecar ${sidecar}: ${messageOf(e)}` });
|
|
1811
|
+
}
|
|
1812
|
+
if (!this.#deps.schemaInferer) return;
|
|
1813
|
+
for (const ext of ["ts", "js"]) {
|
|
1814
|
+
const scriptPath = `${skillRoot}/scripts/${scriptName}.${ext}`;
|
|
1815
|
+
if (!await this.#deps.fs.exists(scriptPath)) continue;
|
|
1816
|
+
const inferred = this.#deps.schemaInferer.inferSchemaFromSource(await this.#deps.fs.readText(scriptPath), { fileName: `${scriptName}.${ext}` });
|
|
1817
|
+
if (inferred) def.inputSchema = inferred;
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1755
1821
|
async #handleScriptTool(call, skillName, scriptName, state) {
|
|
1756
1822
|
const root = this.#deps.skillIndex.get(skillName);
|
|
1757
1823
|
if (!root) return toolError("TOOL_NOT_FOUND", `Skill "${skillName}" is not in the catalog`);
|
|
@@ -1776,6 +1842,7 @@ var AgentLoop = class {
|
|
|
1776
1842
|
if (e instanceof BridgeRequestError) return toolError("UI_UNAVAILABLE", `Parameter form failed: ${e.message}`);
|
|
1777
1843
|
throw e;
|
|
1778
1844
|
}
|
|
1845
|
+
if (!this.#deps.executor) return toolError("TOOL_UNSUPPORTED", `No script executor is configured; script tool "${call.name}" cannot run`);
|
|
1779
1846
|
const context = createScriptContext({
|
|
1780
1847
|
fs: this.#deps.fs,
|
|
1781
1848
|
artifactStore: this.#deps.artifactStore,
|
|
@@ -1937,6 +2004,7 @@ var WebSkillRuntime = class {
|
|
|
1937
2004
|
const result = await new AgentLoop({
|
|
1938
2005
|
llm: this.#deps.llm,
|
|
1939
2006
|
executor: this.#deps.executor,
|
|
2007
|
+
schemaInferer: this.#deps.schemaInferer,
|
|
1940
2008
|
artifactStore: this.#deps.artifactStore,
|
|
1941
2009
|
fs: this.#deps.fs,
|
|
1942
2010
|
skillIndex: cache.index,
|
|
@@ -1972,4 +2040,4 @@ var WebSkillRuntime = class {
|
|
|
1972
2040
|
};
|
|
1973
2041
|
|
|
1974
2042
|
//#endregion
|
|
1975
|
-
export { schemaToForm as A,
|
|
2043
|
+
export { validateSkills as $, schemaToForm as A, WebSkillError as B, createScriptContext as C, normalizeToolContent as D, mergeCatalogEntries as E, SKILL_MANIFEST_FILE as F, escapeXml as G, buildManifest as H, SKILL_NAME_MAX_LENGTH as I, normalizePath as J, isValidSkillName as K, SKILL_NAME_PATTERN as L, toVercelToolSpecs as M, MemoryFS as N, parseBridgeRequest as O, SKILLS_LOCKFILE as P, resolveInsideRoot as Q, SkillDiscovery as R, buildRenderResult as S, fromVercelStreamPart as T, checkSkillRules as U, buildCatalog as V, computeDigest as W, renderAvailableSkillsXml as X, parseSkillMarkdown as Y, renderCatalogJson as Z, READ_SKILL_FILE_TOOL as _, EventBus as a, WebSkillRuntime as b, FullDisclosureRouter as c, MemoryArtifactStore as d, verifyManifest as et, MockLlmClient as f, READ_SKILL_FILE_INPUT_SCHEMA as g, ProgressiveRouter as h, AgentLoop as i, toLlmToolSpec as j, resolveToolName as k, HookRunner as l, OpenAiCompatibleClient as m, ASK_USER_TOOL as n, FsArtifactStore as o, MockUiBridge as p, jsonRenderer as q, ASK_USER_TOOL_NAME as r, FsMemoryStore as s, ASK_USER_INPUT_SCHEMA as t, xmlRenderer as tt, InMemoryStore as u, READ_SKILL_FILE_TOOL_NAME as v, fromVercelResult as w, bridgeError as x, TraceRecorder as y, SkillReader as z };
|
package/dist/governance.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Nt as FileSystemProvider, Vt as SkillCatalogEntry, gt as WebSkillRuntime, j as LlmMessage, k as LlmClient, mt as UiBridge, qt as SkillManifest, tt as RuntimeRun } from "./index-DnI0BTEp.js";
|
|
2
|
+
import { d as SkillManager } from "./index-D65Jk0ju.js";
|
|
3
3
|
//#region ../governance/dist/index.d.ts
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
type CandidateStatus = 'draft' | 'pending-review' | 'approved' | 'published' | 'rejected';
|
package/dist/governance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as NodeFS } from "./dist-
|
|
1
|
+
import { $ as validateSkills, B as WebSkillError, K as isValidSkillName, Q as resolveInsideRoot } from "./dist-hMMxARXK.js";
|
|
2
|
+
import { i as NodeFS } from "./dist-C1p26Ap9.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { mkdtemp } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as InteractionRequest, Mt as FileSystemProvider, X as RenderResultRequest, _ as FsMemoryStore,
|
|
1
|
+
import { $t as VerifyResult, C as InteractionRequest, Mt as FileStat, Nt as FileSystemProvider, Pt as JsonSchema, Wt as SkillInstallSource, X as RenderResultRequest, Zt as SkillsLockfile, _ as FsMemoryStore, at as ScriptExecutor, g as FsArtifactStore, it as ScriptExecutionContext, l as BridgeCapabilities, lt as ToolResult, mt as UiBridge, qt as SkillManifest, rt as SchemaInferer, st as ToolDefinition, w as InteractionResponse } from "./index-DnI0BTEp.js";
|
|
2
2
|
import { Readable, Writable } from "node:stream";
|
|
3
3
|
//#region ../node/dist/index.d.ts
|
|
4
4
|
//#region src/fs/nodeFs.d.ts
|
|
@@ -67,6 +67,17 @@ declare class SandboxedScriptExecutor implements ScriptExecutor {
|
|
|
67
67
|
}): Promise<ToolResult>;
|
|
68
68
|
}
|
|
69
69
|
//#endregion
|
|
70
|
+
//#region src/schema/oxcSchemaInferer.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* D2 Schema 推导(OXC 静态文本分析,宿主侧执行,不进沙箱)。
|
|
73
|
+
* TS 类型标注为主路径,JSDoc @param 为辅路径;不支持类型降级 string 并标 'x-inferred'。
|
|
74
|
+
*/
|
|
75
|
+
declare class OxcSchemaInferer implements SchemaInferer {
|
|
76
|
+
inferSchemaFromSource(source: string, options?: {
|
|
77
|
+
fileName?: string;
|
|
78
|
+
}): JsonSchema | undefined;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
70
81
|
//#region src/artifacts/fileArtifactStore.d.ts
|
|
71
82
|
/**
|
|
72
83
|
* FileArtifactStore:兼容别名,语义同阶段 2-4。
|
|
@@ -112,56 +123,6 @@ declare class CliUiBridge implements UiBridge {
|
|
|
112
123
|
renderResult(input: RenderResultRequest): Promise<void>;
|
|
113
124
|
}
|
|
114
125
|
//#endregion
|
|
115
|
-
//#region src/skillManagement/types.d.ts
|
|
116
|
-
type SkillSource = {
|
|
117
|
-
type: 'local';
|
|
118
|
-
path: string;
|
|
119
|
-
} | {
|
|
120
|
-
type: 'http';
|
|
121
|
-
url: string;
|
|
122
|
-
} | {
|
|
123
|
-
type: 'git';
|
|
124
|
-
url: string;
|
|
125
|
-
ref?: string;
|
|
126
|
-
commit?: string;
|
|
127
|
-
} | {
|
|
128
|
-
type: 'npm';
|
|
129
|
-
packageName: string;
|
|
130
|
-
version?: string;
|
|
131
|
-
};
|
|
132
|
-
interface SkillManifest {
|
|
133
|
-
schemaVersion: 1;
|
|
134
|
-
name: string;
|
|
135
|
-
version?: string;
|
|
136
|
-
source: SkillSource;
|
|
137
|
-
installedAt: string;
|
|
138
|
-
integrity: {
|
|
139
|
-
algorithm: 'sha256';
|
|
140
|
-
digest: string;
|
|
141
|
-
};
|
|
142
|
-
files: Array<{
|
|
143
|
-
path: string;
|
|
144
|
-
size: number;
|
|
145
|
-
sha256: string;
|
|
146
|
-
}>;
|
|
147
|
-
}
|
|
148
|
-
interface SkillsLockfile {
|
|
149
|
-
schemaVersion: 1;
|
|
150
|
-
skills: Record<string, {
|
|
151
|
-
digest: string;
|
|
152
|
-
installedAt: string;
|
|
153
|
-
source: SkillSource;
|
|
154
|
-
}>;
|
|
155
|
-
}
|
|
156
|
-
interface VerifyResult {
|
|
157
|
-
ok: boolean;
|
|
158
|
-
mismatches: string[];
|
|
159
|
-
}
|
|
160
|
-
/** 技能目录内的安装清单文件名(不参与 files 列表与 digest) */
|
|
161
|
-
declare const SKILL_MANIFEST_FILE = "webskill.skill-manifest.json";
|
|
162
|
-
/** managed root 下的锁定文件名(不参与 files 列表与 digest) */
|
|
163
|
-
declare const SKILLS_LOCKFILE = "skills.lock.json";
|
|
164
|
-
//#endregion
|
|
165
126
|
//#region src/skillManagement/skillManager.d.ts
|
|
166
127
|
/**
|
|
167
128
|
* 技能管理门面:统一安装管线(staging → 解析 name → 校验 → 拷贝 → manifest → lockfile),
|
|
@@ -173,8 +134,10 @@ declare class SkillManager {
|
|
|
173
134
|
managedRoot: string;
|
|
174
135
|
fs?: FileSystemProvider;
|
|
175
136
|
fetchImpl?: typeof fetch;
|
|
137
|
+
/** D2 安装期 schema 预推导(默认 true,可关) */
|
|
138
|
+
schemaInference?: boolean;
|
|
176
139
|
});
|
|
177
|
-
install(source:
|
|
140
|
+
install(source: SkillInstallSource, options?: {
|
|
178
141
|
expectedSha256?: string;
|
|
179
142
|
}): Promise<SkillManifest>;
|
|
180
143
|
uninstall(name: string): Promise<void>;
|
|
@@ -216,4 +179,4 @@ interface LlmCapabilities {
|
|
|
216
179
|
*/
|
|
217
180
|
declare function probeLlmCapabilities(config: LlmEnvConfig): Promise<LlmCapabilities>;
|
|
218
181
|
//#endregion
|
|
219
|
-
export {
|
|
182
|
+
export { LlmEnvConfig as a, OxcSchemaInferer as c, SkillManager as d, loadLlmConfigFromEnv as f, LlmCapabilities as i, SandboxOptions as l, readArchiveManifest as m, FileArtifactStore as n, NodeFS as o, probeLlmCapabilities as p, FileMemoryStore as r, NodeScriptExecutor as s, CliUiBridge as t, SandboxedScriptExecutor as u };
|