@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.
@@ -1,9 +1,10 @@
1
- import { D as normalizeToolContent, G as parseSkillMarkdown, H as isValidSkillName, J as resolveInsideRoot, O as parseBridgeRequest, R as WebSkillError, Y as validateSkills, o as FsArtifactStore, s as FsMemoryStore, x as bridgeError } from "./dist-B_ldNWwT.js";
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(`/webskill.skill-manifest.json`));
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(`/webskill.skill-manifest.json`)) {
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
- integrity: {
878
- algorithm: "sha256",
879
- digest: computeDigest(files)
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 mismatches = [];
1060
+ const actualHashes = /* @__PURE__ */ new Map();
900
1061
  for (const file of manifest.files) {
901
1062
  const path = `${skillRoot}/${file.path}`;
902
- if (!await fs.exists(path)) {
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
- if (res.ok) return {
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, SandboxedScriptExecutor as c, probeLlmCapabilities as d, readArchiveManifest as f, NodeFS as i, SkillManager as l, FileArtifactStore as n, SKILLS_LOCKFILE as o, FileMemoryStore as r, SKILL_MANIFEST_FILE as s, CliUiBridge as t, loadLlmConfigFromEnv as u };
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 = 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 { llm, artifactStore } = this.#deps;
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 this.#deps.executor.loadDefinition(root, match[1]);
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, checkSkillRules as B, createScriptContext as C, normalizeToolContent as D, mergeCatalogEntries as E, SKILL_NAME_PATTERN as F, parseSkillMarkdown as G, isValidSkillName as H, SkillDiscovery as I, resolveInsideRoot as J, renderAvailableSkillsXml as K, SkillReader as L, toVercelToolSpecs as M, MemoryFS as N, parseBridgeRequest as O, SKILL_NAME_MAX_LENGTH as P, WebSkillError as R, buildRenderResult as S, fromVercelStreamPart as T, jsonRenderer as U, escapeXml as V, normalizePath as W, xmlRenderer as X, validateSkills as Y, READ_SKILL_FILE_TOOL as _, EventBus as a, WebSkillRuntime as b, FullDisclosureRouter as c, MemoryArtifactStore as d, 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, renderCatalogJson as q, ASK_USER_TOOL_NAME as r, FsMemoryStore as s, ASK_USER_INPUT_SCHEMA as t, InMemoryStore as u, READ_SKILL_FILE_TOOL_NAME as v, fromVercelResult as w, bridgeError as x, TraceRecorder as y, buildCatalog as z };
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 };
@@ -1,5 +1,5 @@
1
- import { Mt as FileSystemProvider, Rt as SkillCatalogEntry, ht as WebSkillRuntime, j as LlmMessage, k as LlmClient, pt as UiBridge, tt as RuntimeRun } from "./index-88KNOnbr.js";
2
- import { f as SkillManager, p as SkillManifest } from "./index-Bq-bTWh3.js";
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';
@@ -1,5 +1,5 @@
1
- import { H as isValidSkillName, J as resolveInsideRoot, R as WebSkillError, Y as validateSkills } from "./dist-B_ldNWwT.js";
2
- import { i as NodeFS } from "./dist-9fczg9Pa.js";
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, ct as ToolResult, g as FsArtifactStore, it as ScriptExecutor, jt as FileStat, l as BridgeCapabilities, ot as ToolDefinition, pt as UiBridge, rt as ScriptExecutionContext, w as InteractionResponse } from "./index-88KNOnbr.js";
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: SkillSource, options?: {
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 { loadLlmConfigFromEnv as _, LlmEnvConfig as a, SKILLS_LOCKFILE as c, SandboxedScriptExecutor as d, SkillManager as f, VerifyResult as g, SkillsLockfile as h, LlmCapabilities as i, SKILL_MANIFEST_FILE as l, SkillSource as m, FileArtifactStore as n, NodeFS as o, SkillManifest as p, FileMemoryStore as r, NodeScriptExecutor as s, CliUiBridge as t, SandboxOptions as u, probeLlmCapabilities as v, readArchiveManifest as y };
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 };