@webskill/sdk 0.0.5 → 0.0.6
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/dist/browser.d.ts +33 -2
- package/dist/browser.js +106 -2
- package/dist/{dist-Ixnb-hNR.js → dist-33_zuOCm.js} +82 -2
- package/dist/{dist-BM-VcQ90.js → dist-BQruQ9Hv.js} +212 -2
- package/dist/{dist-D405AlPD.js → dist-nXiR40hi.js} +690 -411
- package/dist/governance.d.ts +4 -3
- package/dist/governance.js +13 -4
- package/dist/{index-BQDc-U1x.d.ts → index-DCifjtJx.d.ts} +105 -4
- package/dist/{index-aEple804.d.ts → index-vi7GPemR.d.ts} +5 -1
- 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 +2 -2
- package/dist/node.js +2 -2
- package/dist/ui-react.d.ts +2 -2
- package/dist/ui-react.js +12 -2
- package/dist/ui-vue.d.ts +2 -2
- package/dist/ui-vue.js +9 -2
- package/dist/ui.d.ts +15 -3
- package/dist/ui.js +3 -3
- package/package.json +1 -1
package/dist/browser.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as InteractionPolicy, B as LlmResponse, Et as UiBridge, Ht as parseBridgeRequest, L as LlmClient, M as InteractionResponse, Ot as WebSkillApi, V as LlmStreamEvent, Xt as FileSystemProvider, Yt as FileStat, Z as NetworkPolicy, _t as ScriptExecutor, b as ExternalToolSource, c as ApprovalScope, cn as SkillInstallSource, d as BridgeCapabilities, dn as SkillManifest, gn as SkillsLockfile, gt as ScriptExecutionContext, j as InteractionRequest, jt as bridgeError, m as BridgeResponse, ot as RenderResultRequest, p as BridgeRequest, vn as VerifyResult, xt as ToolResult, y as ExternalSkillProvider, yt as ToolDefinition } from "./index-DCifjtJx.js";
|
|
2
2
|
//#region ../browser/dist/index.d.ts
|
|
3
3
|
//#region src/fs/featureDetection.d.ts
|
|
4
4
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
|
@@ -85,6 +85,11 @@ declare class BrowserSkillManager {
|
|
|
85
85
|
install(source: SkillInstallSource, options?: {
|
|
86
86
|
expectedSha256?: string;
|
|
87
87
|
}): Promise<SkillManifest>;
|
|
88
|
+
/**
|
|
89
|
+
* 浏览器导出(单技能与包集同构:始终产出含 webskill.skill-pack.json 的 zip 字节,
|
|
90
|
+
* 统一安装路径;保存/下载由应用层处理)。
|
|
91
|
+
*/
|
|
92
|
+
exportArchive(names: string | string[]): Promise<Uint8Array>;
|
|
88
93
|
uninstall(name: string): Promise<void>;
|
|
89
94
|
verifyIntegrity(name: string): Promise<VerifyResult>;
|
|
90
95
|
listInstalled(): Promise<SkillsLockfile>;
|
|
@@ -140,6 +145,32 @@ declare class BrowserWorkerScriptExecutor implements ScriptExecutor {
|
|
|
140
145
|
}): Promise<ToolResult>;
|
|
141
146
|
}
|
|
142
147
|
//#endregion
|
|
148
|
+
//#region src/navigator.d.ts
|
|
149
|
+
declare global {
|
|
150
|
+
/** 0.0.6:installWebSkillNavigator 显式装配后可用(不自动挂全局) */
|
|
151
|
+
interface Navigator {
|
|
152
|
+
webskill?: WebSkillApi;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* navigator.webskill 显式装配:OPFS 默认存储 + BrowserWorkerScriptExecutor +
|
|
157
|
+
* BrowserSkillManager → createWebSkillApi;target 提供时挂 target.navigator
|
|
158
|
+
* (缺失则补空对象),否则挂真实 navigator.webskill。
|
|
159
|
+
* target 可注入(测试);返回值即门面本体(挂载只是副作用)。
|
|
160
|
+
*/
|
|
161
|
+
declare function installWebSkillNavigator(config: {
|
|
162
|
+
roots?: string[];
|
|
163
|
+
llm: LlmClient;
|
|
164
|
+
typescript?: TypeScriptSupportOptions;
|
|
165
|
+
/** 测试注入:替换 OPFS 默认 fs(jsdom 无 OPFS) */
|
|
166
|
+
fs?: FileSystemProvider;
|
|
167
|
+
/** 安装目标根(默认与 roots[0] 一致) */
|
|
168
|
+
managedRoot?: string;
|
|
169
|
+
target?: {
|
|
170
|
+
navigator?: Record<string, unknown>;
|
|
171
|
+
};
|
|
172
|
+
}): WebSkillApi;
|
|
173
|
+
//#endregion
|
|
143
174
|
//#region src/host/protocol.d.ts
|
|
144
175
|
/** 主线程 → Worker 的 LLM 配置;mockResponses 供 E2E/测试注入确定性 Mock */
|
|
145
176
|
interface LlmClientConfig {
|
|
@@ -303,4 +334,4 @@ declare class WorkerUiBridge implements UiBridge {
|
|
|
303
334
|
onTextDelta(runId: string, delta: string): Promise<void>;
|
|
304
335
|
}
|
|
305
336
|
//#endregion
|
|
306
|
-
export { type BridgeCapabilities, type BridgeRequest, type BridgeResponse, BrowserSkillManager, BrowserWorkerScriptExecutor, type LlmClientConfig, type MainToWorkerMessage, OpfsProvider, type SerializedError, TsTranspiler, type TypeScriptSupportOptions, WORKER_BOOTSTRAP_SOURCE, type WorkerEvent, type WorkerFactory, type WorkerLike, type WorkerRequest, WorkerRuntimeClient, type WorkerRuntimeClientDeps, type WorkerRuntimeHostOverrides, type WorkerScopeLike, WorkerUiBridge, bridgeError, extractZipWeb, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };
|
|
337
|
+
export { type BridgeCapabilities, type BridgeRequest, type BridgeResponse, BrowserSkillManager, BrowserWorkerScriptExecutor, type LlmClientConfig, type MainToWorkerMessage, OpfsProvider, type SerializedError, TsTranspiler, type TypeScriptSupportOptions, WORKER_BOOTSTRAP_SOURCE, type WorkerEvent, type WorkerFactory, type WorkerLike, type WorkerRequest, WorkerRuntimeClient, type WorkerRuntimeClientDeps, type WorkerRuntimeHostOverrides, type WorkerScopeLike, WorkerUiBridge, bridgeError, extractZipWeb, installWebSkillNavigator, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };
|
package/dist/browser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as createWebSkillApi, F as parseBridgeRequest, G as SKILL_PACK_FILE, H as SKILL_MANIFEST_FILE, J as WebSkillError, K as SkillDiscovery, M as mergeCatalogEntries, N as networkUrlHost, P as normalizeToolContent, V as SKILLS_LOCKFILE, X as buildManifest, Y as buildCatalog, _ as ProgressiveRouter, a as CapabilityApproval, at as parseSkillMarkdown, c as FsMemoryStore, dt as verifyManifest, g as OpenAiCompatibleClient, i as AgentLoop, j as isNetworkAllowed, l as FsRunSnapshotStore, lt as resolveInsideRoot, m as MockLlmClient, nt as isValidSkillName, ot as parseSkillPackManifest, s as FsArtifactStore, tt as exportSkills, ut as validateSkills, w as bridgeError, x as RUN_SNAPSHOT_SCHEMA_VERSION } from "./dist-nXiR40hi.js";
|
|
2
2
|
import { unzipSync } from "fflate";
|
|
3
3
|
|
|
4
4
|
//#region ../browser/dist/index.js
|
|
@@ -476,6 +476,11 @@ var BrowserSkillManager = class {
|
|
|
476
476
|
const contentDir = `${stagingRoot}/content`;
|
|
477
477
|
await fs.mkdir(contentDir);
|
|
478
478
|
await extractZipWeb(fs, data, contentDir);
|
|
479
|
+
const packFilePath = `${contentDir}/${SKILL_PACK_FILE}`;
|
|
480
|
+
if (await fs.exists(packFilePath)) return (await this.#installPack({
|
|
481
|
+
contentDir,
|
|
482
|
+
manifest: parseSkillPackManifest(await fs.readText(packFilePath))
|
|
483
|
+
}, source, stagingRoot))[0];
|
|
479
484
|
let skillDir = contentDir;
|
|
480
485
|
if (!await fs.exists(`${contentDir}/SKILL.md`)) {
|
|
481
486
|
const dirs = (await fs.list(contentDir)).filter((e) => e.type === "directory");
|
|
@@ -521,6 +526,77 @@ var BrowserSkillManager = class {
|
|
|
521
526
|
await removeDirQuiet(fs, stagingRoot);
|
|
522
527
|
}
|
|
523
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* 包集安装:逐技能走单技能管线(归一 → 汇总校验 → 落 managed root → manifest → digest 比对)。
|
|
531
|
+
* 任一失败整体回滚(删除本次已落目录;lockfile 在全部成功后才写入,天然零残留)。
|
|
532
|
+
*/
|
|
533
|
+
async #installPack(pack, source, stagingRoot) {
|
|
534
|
+
const fs = this.#fs;
|
|
535
|
+
const finalRoot = `${stagingRoot}/final`;
|
|
536
|
+
const installed = [];
|
|
537
|
+
try {
|
|
538
|
+
const versions = /* @__PURE__ */ new Map();
|
|
539
|
+
for (const entry of pack.manifest.skills) {
|
|
540
|
+
const skillDir = `${pack.contentDir}/${entry.name}`;
|
|
541
|
+
if (!await fs.exists(`${skillDir}/SKILL.md`)) throw new WebSkillError("INSTALL_FAILED", `Skill pack is missing a directory for skill "${entry.name}"`);
|
|
542
|
+
let name;
|
|
543
|
+
try {
|
|
544
|
+
const { metadata } = parseSkillMarkdown(await fs.readText(`${skillDir}/SKILL.md`));
|
|
545
|
+
name = metadata.name;
|
|
546
|
+
versions.set(name, typeof metadata["version"] === "string" ? metadata["version"] : void 0);
|
|
547
|
+
} catch (e) {
|
|
548
|
+
throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md of pack entry "${entry.name}": ${messageOf$1(e)}`, e);
|
|
549
|
+
}
|
|
550
|
+
if (name !== entry.name) throw new WebSkillError("INSTALL_FAILED", `Skill pack entry "${entry.name}" does not match the SKILL.md name "${name}"`);
|
|
551
|
+
await copyDir(fs, skillDir, `${finalRoot}/${name}`);
|
|
552
|
+
}
|
|
553
|
+
const report = await validateSkills(fs, [finalRoot]);
|
|
554
|
+
if (!report.ok) {
|
|
555
|
+
const errors = report.issues.filter((i) => i.severity === "error");
|
|
556
|
+
throw new WebSkillError("INSTALL_FAILED", `Skill pack failed validation: ${errors.map((i) => i.message).join("; ")}`, errors);
|
|
557
|
+
}
|
|
558
|
+
const manifests = [];
|
|
559
|
+
for (const entry of pack.manifest.skills) {
|
|
560
|
+
const targetDir = `${this.#managedRoot}/${entry.name}`;
|
|
561
|
+
if (await fs.exists(targetDir)) await fs.remove(targetDir, { recursive: true });
|
|
562
|
+
await copyDir(fs, `${finalRoot}/${entry.name}`, targetDir);
|
|
563
|
+
installed.push(targetDir);
|
|
564
|
+
const manifest = await this.#createManifest(targetDir, {
|
|
565
|
+
name: entry.name,
|
|
566
|
+
...versions.get(entry.name) ? { version: versions.get(entry.name) } : {},
|
|
567
|
+
source
|
|
568
|
+
});
|
|
569
|
+
if (manifest.integrity.digest !== entry.digest) throw new WebSkillError("INSTALL_FAILED", `Skill "${entry.name}" digest mismatch: expected ${entry.digest}, got ${manifest.integrity.digest}`);
|
|
570
|
+
manifests.push(manifest);
|
|
571
|
+
}
|
|
572
|
+
for (const manifest of manifests) await this.#upsertLockEntry(manifest.name, {
|
|
573
|
+
digest: manifest.integrity.digest,
|
|
574
|
+
installedAt: manifest.installedAt,
|
|
575
|
+
source
|
|
576
|
+
});
|
|
577
|
+
return manifests;
|
|
578
|
+
} catch (e) {
|
|
579
|
+
for (const targetDir of installed) await removeDirQuiet(fs, targetDir);
|
|
580
|
+
throw asInstallFailed(e);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* 浏览器导出(单技能与包集同构:始终产出含 webskill.skill-pack.json 的 zip 字节,
|
|
585
|
+
* 统一安装路径;保存/下载由应用层处理)。
|
|
586
|
+
*/
|
|
587
|
+
async exportArchive(names) {
|
|
588
|
+
const list = Array.isArray(names) ? names : [names];
|
|
589
|
+
if (list.length === 0) throw new WebSkillError("EXPORT_FAILED", "exportArchive requires at least one skill name");
|
|
590
|
+
const roots = list.map((name) => {
|
|
591
|
+
if (!isValidSkillName(name)) throw new WebSkillError("EXPORT_FAILED", `Invalid skill name (path traversal rejected): ${JSON.stringify(name)}`);
|
|
592
|
+
return `${this.#managedRoot}/${name}`;
|
|
593
|
+
});
|
|
594
|
+
for (const [index, root] of roots.entries()) if (!await this.#fs.exists(root)) throw new WebSkillError("EXPORT_FAILED", `Skill "${list[index]}" is not installed`);
|
|
595
|
+
return exportSkills(this.#fs, {
|
|
596
|
+
roots,
|
|
597
|
+
manifestBuilder: (root) => this.#readManifest(root)
|
|
598
|
+
});
|
|
599
|
+
}
|
|
524
600
|
async uninstall(name) {
|
|
525
601
|
if (!isValidSkillName(name)) throw new WebSkillError("UNINSTALL_FAILED", `Invalid skill name (path traversal rejected): ${JSON.stringify(name)}`);
|
|
526
602
|
const targetDir = `${this.#managedRoot}/${name}`;
|
|
@@ -829,6 +905,34 @@ var BrowserWorkerScriptExecutor = class {
|
|
|
829
905
|
}
|
|
830
906
|
}
|
|
831
907
|
};
|
|
908
|
+
/**
|
|
909
|
+
* navigator.webskill 显式装配:OPFS 默认存储 + BrowserWorkerScriptExecutor +
|
|
910
|
+
* BrowserSkillManager → createWebSkillApi;target 提供时挂 target.navigator
|
|
911
|
+
* (缺失则补空对象),否则挂真实 navigator.webskill。
|
|
912
|
+
* target 可注入(测试);返回值即门面本体(挂载只是副作用)。
|
|
913
|
+
*/
|
|
914
|
+
function installWebSkillNavigator(config) {
|
|
915
|
+
const roots = config.roots ?? ["/skills"];
|
|
916
|
+
const fs = config.fs ?? new OpfsProvider();
|
|
917
|
+
const executor = new BrowserWorkerScriptExecutor({
|
|
918
|
+
fs,
|
|
919
|
+
...config.typescript ? { typescript: config.typescript } : {}
|
|
920
|
+
});
|
|
921
|
+
const skillManager = new BrowserSkillManager({
|
|
922
|
+
fs,
|
|
923
|
+
managedRoot: config.managedRoot ?? roots[0] ?? "/skills"
|
|
924
|
+
});
|
|
925
|
+
const api = createWebSkillApi({
|
|
926
|
+
fs,
|
|
927
|
+
roots,
|
|
928
|
+
llm: config.llm,
|
|
929
|
+
executor,
|
|
930
|
+
skillManager
|
|
931
|
+
});
|
|
932
|
+
const nav = config.target ? config.target.navigator ??= {} : navigator;
|
|
933
|
+
nav["webskill"] = api;
|
|
934
|
+
return api;
|
|
935
|
+
}
|
|
832
936
|
const isRecord = (v) => typeof v === "object" && v !== null;
|
|
833
937
|
const isNonEmptyString = (v) => typeof v === "string" && v !== "";
|
|
834
938
|
/** Worker 侧入站校验:非法消息返回 undefined(忽略) */
|
|
@@ -1337,4 +1441,4 @@ var WorkerRuntimeClient = class {
|
|
|
1337
1441
|
};
|
|
1338
1442
|
|
|
1339
1443
|
//#endregion
|
|
1340
|
-
export { BrowserSkillManager, BrowserWorkerScriptExecutor, OpfsProvider, TsTranspiler, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, bridgeError, extractZipWeb, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };
|
|
1444
|
+
export { BrowserSkillManager, BrowserWorkerScriptExecutor, OpfsProvider, TsTranspiler, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, bridgeError, extractZipWeb, installWebSkillNavigator, isOpfsAvailable, parseBridgeRequest, parseMainMessage, parseWorkerEvent, sha256HexWeb, startWorkerRuntimeHost };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { F as parseBridgeRequest, G as SKILL_PACK_FILE, H as SKILL_MANIFEST_FILE, J as WebSkillError, N as networkUrlHost, P as normalizeToolContent, V as SKILLS_LOCKFILE, X as buildManifest, a as CapabilityApproval, at as parseSkillMarkdown, c as FsMemoryStore, dt as verifyManifest, j as isNetworkAllowed, lt as resolveInsideRoot, nt as isValidSkillName, ot as parseSkillPackManifest, s as FsArtifactStore, tt as exportSkills, ut as validateSkills, w as bridgeError } from "./dist-nXiR40hi.js";
|
|
2
|
+
import { unzipSync, zipSync } from "fflate";
|
|
2
3
|
import { existsSync, promises, readFileSync } from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -8,7 +9,6 @@ import { parseSync } from "oxc-parser";
|
|
|
8
9
|
import { createInterface } from "node:readline/promises";
|
|
9
10
|
import { mkdtemp } from "node:fs/promises";
|
|
10
11
|
import { tmpdir } from "node:os";
|
|
11
|
-
import { unzipSync, zipSync } from "fflate";
|
|
12
12
|
import * as tar from "tar";
|
|
13
13
|
import { execFile } from "node:child_process";
|
|
14
14
|
import { createHash } from "node:crypto";
|
|
@@ -971,6 +971,15 @@ async function stageHttp(source, ctx, expectedSha256) {
|
|
|
971
971
|
await ctx.fs.writeBinary(archivePath, data);
|
|
972
972
|
await extractTarFile(toPlatform(archivePath), toPlatform(contentDir));
|
|
973
973
|
}
|
|
974
|
+
const packFilePath = `${contentDir}/${SKILL_PACK_FILE}`;
|
|
975
|
+
if (await ctx.fs.exists(packFilePath)) return {
|
|
976
|
+
skillDir: "",
|
|
977
|
+
source,
|
|
978
|
+
pack: {
|
|
979
|
+
contentDir,
|
|
980
|
+
manifest: parseSkillPackManifest(await ctx.fs.readText(packFilePath))
|
|
981
|
+
}
|
|
982
|
+
};
|
|
974
983
|
if (await ctx.fs.exists(`${contentDir}/SKILL.md`)) return {
|
|
975
984
|
skillDir: contentDir,
|
|
976
985
|
source
|
|
@@ -1157,6 +1166,7 @@ var SkillManager = class {
|
|
|
1157
1166
|
break;
|
|
1158
1167
|
case "archive": throw new WebSkillError("TOOL_UNSUPPORTED", "The \"archive\" install source is only supported by the browser skill manager");
|
|
1159
1168
|
}
|
|
1169
|
+
if (staged.pack) return (await this.#installPack(staged.pack, staged.source, stagingRoot))[0];
|
|
1160
1170
|
let name;
|
|
1161
1171
|
let version;
|
|
1162
1172
|
try {
|
|
@@ -1198,6 +1208,61 @@ var SkillManager = class {
|
|
|
1198
1208
|
}
|
|
1199
1209
|
}
|
|
1200
1210
|
/**
|
|
1211
|
+
* 包集安装:逐技能走单技能管线(归一 → 汇总校验 → 落 managed root → manifest → digest 比对)。
|
|
1212
|
+
* 任一失败整体回滚(删除本次已落目录;lockfile 在全部成功后才写入,天然零残留)。
|
|
1213
|
+
* 包是封闭产物:跳过安装期 schema 推导(sidecar 已随包携带,保证 digest 逐字节一致)。
|
|
1214
|
+
*/
|
|
1215
|
+
async #installPack(pack, source, stagingRoot) {
|
|
1216
|
+
const fs = this.#fs;
|
|
1217
|
+
const finalRoot = `${stagingRoot}/final`;
|
|
1218
|
+
const installed = [];
|
|
1219
|
+
try {
|
|
1220
|
+
const versions = /* @__PURE__ */ new Map();
|
|
1221
|
+
for (const entry of pack.manifest.skills) {
|
|
1222
|
+
const skillDir = `${pack.contentDir}/${entry.name}`;
|
|
1223
|
+
if (!await fs.exists(`${skillDir}/SKILL.md`)) throw new WebSkillError("INSTALL_FAILED", `Skill pack is missing a directory for skill "${entry.name}"`);
|
|
1224
|
+
let name;
|
|
1225
|
+
try {
|
|
1226
|
+
const { metadata } = parseSkillMarkdown(await fs.readText(`${skillDir}/SKILL.md`));
|
|
1227
|
+
name = metadata.name;
|
|
1228
|
+
versions.set(name, typeof metadata["version"] === "string" ? metadata["version"] : void 0);
|
|
1229
|
+
} catch (e) {
|
|
1230
|
+
throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md of pack entry "${entry.name}": ${messageOf(e)}`, e);
|
|
1231
|
+
}
|
|
1232
|
+
if (name !== entry.name) throw new WebSkillError("INSTALL_FAILED", `Skill pack entry "${entry.name}" does not match the SKILL.md name "${name}"`);
|
|
1233
|
+
await copyDir(fs, skillDir, `${finalRoot}/${name}`);
|
|
1234
|
+
}
|
|
1235
|
+
const report = await validateSkills(fs, [finalRoot]);
|
|
1236
|
+
if (!report.ok) {
|
|
1237
|
+
const errors = report.issues.filter((i) => i.severity === "error");
|
|
1238
|
+
throw new WebSkillError("INSTALL_FAILED", `Skill pack failed validation: ${errors.map((i) => i.message).join("; ")}`, errors);
|
|
1239
|
+
}
|
|
1240
|
+
const manifests = [];
|
|
1241
|
+
for (const entry of pack.manifest.skills) {
|
|
1242
|
+
const targetDir = `${this.#managedRoot}/${entry.name}`;
|
|
1243
|
+
if (await fs.exists(targetDir)) await fs.remove(targetDir, { recursive: true });
|
|
1244
|
+
await copyDir(fs, `${finalRoot}/${entry.name}`, targetDir);
|
|
1245
|
+
installed.push(targetDir);
|
|
1246
|
+
const manifest = await createManifest(fs, targetDir, {
|
|
1247
|
+
name: entry.name,
|
|
1248
|
+
...versions.get(entry.name) ? { version: versions.get(entry.name) } : {},
|
|
1249
|
+
source
|
|
1250
|
+
});
|
|
1251
|
+
if (manifest.integrity.digest !== entry.digest) throw new WebSkillError("INSTALL_FAILED", `Skill "${entry.name}" digest mismatch: expected ${entry.digest}, got ${manifest.integrity.digest}`);
|
|
1252
|
+
manifests.push(manifest);
|
|
1253
|
+
}
|
|
1254
|
+
for (const manifest of manifests) await upsertLockEntry(fs, this.#managedRoot, manifest.name, {
|
|
1255
|
+
digest: manifest.integrity.digest,
|
|
1256
|
+
installedAt: manifest.installedAt,
|
|
1257
|
+
source
|
|
1258
|
+
});
|
|
1259
|
+
return manifests;
|
|
1260
|
+
} catch (e) {
|
|
1261
|
+
for (const targetDir of installed) await removeDirQuiet(fs, targetDir);
|
|
1262
|
+
throw asInstallFailed(e);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1201
1266
|
* D2 安装期预推导:scripts/ 下无显式 inputSchema 导出且无 sidecar 的脚本,
|
|
1202
1267
|
* 经 OXC 推导写 sidecar(计入 manifest.files;重装重新生成)
|
|
1203
1268
|
*/
|
|
@@ -1243,6 +1308,21 @@ var SkillManager = class {
|
|
|
1243
1308
|
if (!await this.#fs.exists(skillRoot)) throw new WebSkillError("EXPORT_FAILED", `Skill "${name}" is not installed`);
|
|
1244
1309
|
return exportArchive(this.#fs, skillRoot, options);
|
|
1245
1310
|
}
|
|
1311
|
+
/** 多技能包集导出(webskill.skill-pack.json + 各技能目录含 manifest),写 outPath 并返回 */
|
|
1312
|
+
async exportPack(names, options) {
|
|
1313
|
+
if (names.length === 0) throw new WebSkillError("EXPORT_FAILED", "exportPack requires at least one skill name");
|
|
1314
|
+
const roots = names.map((name) => {
|
|
1315
|
+
if (!isValidSkillName(name)) throw new WebSkillError("EXPORT_FAILED", `Invalid skill name (path traversal rejected): ${JSON.stringify(name)}`);
|
|
1316
|
+
return `${this.#managedRoot}/${name}`;
|
|
1317
|
+
});
|
|
1318
|
+
for (const [index, root] of roots.entries()) if (!await this.#fs.exists(root)) throw new WebSkillError("EXPORT_FAILED", `Skill "${names[index]}" is not installed`);
|
|
1319
|
+
const bytes = await exportSkills(this.#fs, {
|
|
1320
|
+
roots,
|
|
1321
|
+
manifestBuilder: (root) => readManifest(this.#fs, root)
|
|
1322
|
+
});
|
|
1323
|
+
await this.#fs.writeBinary(options.outPath, bytes);
|
|
1324
|
+
return options.outPath;
|
|
1325
|
+
}
|
|
1246
1326
|
};
|
|
1247
1327
|
/**
|
|
1248
1328
|
* 解析 .env(简单 KEY=VALUE 行解析,不引依赖),
|
|
@@ -190,9 +190,219 @@ function renderMiniMarkdown(text, doc) {
|
|
|
190
190
|
}
|
|
191
191
|
return root;
|
|
192
192
|
}
|
|
193
|
-
/**
|
|
193
|
+
/**
|
|
194
|
+
* 轻量 SVG 图表自绘(bar/line/pie,零依赖,三端复用单一来源)。
|
|
195
|
+
* 全部经 doc.createElementNS 构建(内容来自技能输出,不拼 innerHTML)。
|
|
196
|
+
*/
|
|
197
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
198
|
+
/** 固定 8 色循环调色板 */
|
|
199
|
+
const CHART_PALETTE = [
|
|
200
|
+
"#4e79a7",
|
|
201
|
+
"#f28e2b",
|
|
202
|
+
"#e15759",
|
|
203
|
+
"#76b7b2",
|
|
204
|
+
"#59a14f",
|
|
205
|
+
"#edc948",
|
|
206
|
+
"#b07aa1",
|
|
207
|
+
"#ff9da7"
|
|
208
|
+
];
|
|
209
|
+
const WIDTH = 320;
|
|
210
|
+
const HEIGHT = 200;
|
|
211
|
+
function el(doc, name, attrs) {
|
|
212
|
+
const node = doc.createElementNS(SVG_NS, name);
|
|
213
|
+
for (const [key, value] of Object.entries(attrs)) node.setAttribute(key, value);
|
|
214
|
+
return node;
|
|
215
|
+
}
|
|
216
|
+
function text$1(doc, x, y, content, attrs = {}) {
|
|
217
|
+
const node = el(doc, "text", {
|
|
218
|
+
x: String(x),
|
|
219
|
+
y: String(y),
|
|
220
|
+
"font-size": "9",
|
|
221
|
+
fill: "#555",
|
|
222
|
+
...attrs
|
|
223
|
+
});
|
|
224
|
+
node.textContent = content;
|
|
225
|
+
return node;
|
|
226
|
+
}
|
|
227
|
+
const fmt = (n) => Number.isInteger(n) ? String(n) : n.toFixed(1);
|
|
228
|
+
function maxValue(chart) {
|
|
229
|
+
let max = 0;
|
|
230
|
+
for (const s of chart.series) for (const v of s.data) if (v > max) max = v;
|
|
231
|
+
return max;
|
|
232
|
+
}
|
|
233
|
+
function renderBar(chart, svg, doc) {
|
|
234
|
+
const plot = {
|
|
235
|
+
x0: 30,
|
|
236
|
+
y0: 24,
|
|
237
|
+
x1: 312,
|
|
238
|
+
y1: 164
|
|
239
|
+
};
|
|
240
|
+
const max = maxValue(chart);
|
|
241
|
+
svg.appendChild(el(doc, "line", {
|
|
242
|
+
x1: String(plot.x0),
|
|
243
|
+
y1: String(plot.y0),
|
|
244
|
+
x2: String(plot.x0),
|
|
245
|
+
y2: String(plot.y1),
|
|
246
|
+
stroke: "#999"
|
|
247
|
+
}));
|
|
248
|
+
svg.appendChild(el(doc, "line", {
|
|
249
|
+
x1: String(plot.x0),
|
|
250
|
+
y1: String(plot.y1),
|
|
251
|
+
x2: String(plot.x1),
|
|
252
|
+
y2: String(plot.y1),
|
|
253
|
+
stroke: "#999"
|
|
254
|
+
}));
|
|
255
|
+
if (max <= 0 || chart.labels.length === 0 || chart.series.length === 0) return;
|
|
256
|
+
const groupWidth = (plot.x1 - plot.x0) / chart.labels.length;
|
|
257
|
+
const barWidth = Math.max(1, groupWidth / chart.series.length * .7);
|
|
258
|
+
chart.series.forEach((series, si) => {
|
|
259
|
+
const color = CHART_PALETTE[si % CHART_PALETTE.length];
|
|
260
|
+
series.data.forEach((value, li) => {
|
|
261
|
+
if (li >= chart.labels.length) return;
|
|
262
|
+
const height = (plot.y1 - plot.y0) * value / max;
|
|
263
|
+
const x = plot.x0 + li * groupWidth + (groupWidth - barWidth * chart.series.length) / 2 + si * barWidth;
|
|
264
|
+
const y = plot.y1 - height;
|
|
265
|
+
svg.appendChild(el(doc, "rect", {
|
|
266
|
+
x: String(x),
|
|
267
|
+
y: String(y),
|
|
268
|
+
width: String(barWidth),
|
|
269
|
+
height: String(height),
|
|
270
|
+
fill: color,
|
|
271
|
+
"data-series": series.name ?? String(si)
|
|
272
|
+
}));
|
|
273
|
+
if (chart.series.length === 1) svg.appendChild(text$1(doc, x + barWidth / 2, y - 2, fmt(value), { "text-anchor": "middle" }));
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
chart.labels.forEach((label, li) => {
|
|
277
|
+
svg.appendChild(text$1(doc, plot.x0 + li * groupWidth + groupWidth / 2, plot.y1 + 12, label, { "text-anchor": "middle" }));
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function renderLine(chart, svg, doc) {
|
|
281
|
+
const plot = {
|
|
282
|
+
x0: 30,
|
|
283
|
+
y0: 24,
|
|
284
|
+
x1: 312,
|
|
285
|
+
y1: 164
|
|
286
|
+
};
|
|
287
|
+
const max = maxValue(chart);
|
|
288
|
+
svg.appendChild(el(doc, "line", {
|
|
289
|
+
x1: String(plot.x0),
|
|
290
|
+
y1: String(plot.y0),
|
|
291
|
+
x2: String(plot.x0),
|
|
292
|
+
y2: String(plot.y1),
|
|
293
|
+
stroke: "#999"
|
|
294
|
+
}));
|
|
295
|
+
svg.appendChild(el(doc, "line", {
|
|
296
|
+
x1: String(plot.x0),
|
|
297
|
+
y1: String(plot.y1),
|
|
298
|
+
x2: String(plot.x1),
|
|
299
|
+
y2: String(plot.y1),
|
|
300
|
+
stroke: "#999"
|
|
301
|
+
}));
|
|
302
|
+
if (max <= 0 || chart.labels.length === 0 || chart.series.length === 0) return;
|
|
303
|
+
const step = chart.labels.length > 1 ? (plot.x1 - plot.x0) / (chart.labels.length - 1) : 0;
|
|
304
|
+
const px = (li) => chart.labels.length > 1 ? plot.x0 + li * step : (plot.x0 + plot.x1) / 2;
|
|
305
|
+
const py = (v) => plot.y1 - (plot.y1 - plot.y0) * v / max;
|
|
306
|
+
chart.series.forEach((series, si) => {
|
|
307
|
+
const color = CHART_PALETTE[si % CHART_PALETTE.length];
|
|
308
|
+
const points = series.data.slice(0, chart.labels.length).map((v, li) => `${px(li)},${py(v)}`).join(" ");
|
|
309
|
+
if (points !== "") svg.appendChild(el(doc, "polyline", {
|
|
310
|
+
points,
|
|
311
|
+
fill: "none",
|
|
312
|
+
stroke: color,
|
|
313
|
+
"stroke-width": "1.5"
|
|
314
|
+
}));
|
|
315
|
+
series.data.slice(0, chart.labels.length).forEach((v, li) => {
|
|
316
|
+
svg.appendChild(el(doc, "circle", {
|
|
317
|
+
cx: String(px(li)),
|
|
318
|
+
cy: String(py(v)),
|
|
319
|
+
r: "2",
|
|
320
|
+
fill: color
|
|
321
|
+
}));
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
chart.labels.forEach((label, li) => {
|
|
325
|
+
svg.appendChild(text$1(doc, px(li), plot.y1 + 12, label, { "text-anchor": "middle" }));
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function renderPie(chart, svg, doc) {
|
|
329
|
+
const data = (chart.series[0]?.data ?? []).slice(0, chart.labels.length);
|
|
330
|
+
const total = data.reduce((sum, v) => sum + (v > 0 ? v : 0), 0);
|
|
331
|
+
const cx = 90;
|
|
332
|
+
const cy = 104;
|
|
333
|
+
const r = 72;
|
|
334
|
+
if (total <= 0) {
|
|
335
|
+
svg.appendChild(text$1(doc, cx, cy, "No data", { "text-anchor": "middle" }));
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
let angle = -Math.PI / 2;
|
|
339
|
+
data.forEach((value, i) => {
|
|
340
|
+
const label = chart.labels[i] ?? String(i);
|
|
341
|
+
const color = CHART_PALETTE[i % CHART_PALETTE.length];
|
|
342
|
+
const slice = Math.max(value, 0) / total * Math.PI * 2;
|
|
343
|
+
const x0 = cx + r * Math.cos(angle);
|
|
344
|
+
const y0 = cy + r * Math.sin(angle);
|
|
345
|
+
const x1 = cx + r * Math.cos(angle + slice);
|
|
346
|
+
const y1 = cy + r * Math.sin(angle + slice);
|
|
347
|
+
const path = el(doc, "path", {
|
|
348
|
+
d: `M ${cx} ${cy} L ${x0} ${y0} A ${r} ${r} 0 ${slice > Math.PI ? "1" : "0"} 1 ${x1} ${y1} Z`,
|
|
349
|
+
fill: color,
|
|
350
|
+
"data-label": label
|
|
351
|
+
});
|
|
352
|
+
svg.appendChild(path);
|
|
353
|
+
angle += slice;
|
|
354
|
+
const ly = 40 + i * 14;
|
|
355
|
+
svg.appendChild(el(doc, "rect", {
|
|
356
|
+
x: "190",
|
|
357
|
+
y: String(ly - 7),
|
|
358
|
+
width: "8",
|
|
359
|
+
height: "8",
|
|
360
|
+
fill: color
|
|
361
|
+
}));
|
|
362
|
+
svg.appendChild(text$1(doc, 202, ly, `${label} (${fmt(value)})`));
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
/** ChartSpec → SVG(bar 等宽柱 + 值标签;line 折线 + 点;pie 扇形 + 图例;空数据容错) */
|
|
366
|
+
function renderMiniChart(chart, doc) {
|
|
367
|
+
const svg = el(doc, "svg", {
|
|
368
|
+
viewBox: `0 0 ${WIDTH} ${HEIGHT}`,
|
|
369
|
+
width: String(WIDTH),
|
|
370
|
+
height: String(HEIGHT),
|
|
371
|
+
class: `webskill-chart webskill-chart--${chart.kind}`,
|
|
372
|
+
role: "img"
|
|
373
|
+
});
|
|
374
|
+
if (chart.title) svg.appendChild(text$1(doc, WIDTH / 2, 14, chart.title, {
|
|
375
|
+
"text-anchor": "middle",
|
|
376
|
+
"font-size": "11",
|
|
377
|
+
fill: "#333"
|
|
378
|
+
}));
|
|
379
|
+
switch (chart.kind) {
|
|
380
|
+
case "bar":
|
|
381
|
+
renderBar(chart, svg, doc);
|
|
382
|
+
break;
|
|
383
|
+
case "line":
|
|
384
|
+
renderLine(chart, svg, doc);
|
|
385
|
+
break;
|
|
386
|
+
case "pie":
|
|
387
|
+
renderPie(chart, svg, doc);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
return svg;
|
|
391
|
+
}
|
|
392
|
+
/** A2UI/OpenUI 降级:chart → table(labels 为首列,series 各为一列) */
|
|
393
|
+
function chartToTable(chart) {
|
|
394
|
+
return {
|
|
395
|
+
type: "table",
|
|
396
|
+
columns: ["label", ...chart.series.map((s, i) => s.name ?? `series ${i + 1}`)],
|
|
397
|
+
rows: chart.labels.map((label, li) => [label, ...chart.series.map((s) => s.data[li] ?? null)])
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
/** RenderBlock → DOM(markdown/json/table/image/file/chart 六种) */
|
|
194
401
|
function renderBlocks(container, blocks, doc) {
|
|
195
402
|
for (const block of blocks) switch (block.type) {
|
|
403
|
+
case "chart":
|
|
404
|
+
container.appendChild(renderMiniChart(block.chart, doc));
|
|
405
|
+
break;
|
|
196
406
|
case "markdown":
|
|
197
407
|
container.appendChild(renderMiniMarkdown(block.text, doc));
|
|
198
408
|
break;
|
|
@@ -15403,4 +15613,4 @@ var LitRendererBridge = class {
|
|
|
15403
15613
|
};
|
|
15404
15614
|
|
|
15405
15615
|
//#endregion
|
|
15406
|
-
export {
|
|
15616
|
+
export { renderRenderResult as C, toVercelToolInvocation as D, toOpenUiLang as E, renderMiniMarkdown as S, toA2uiMessages as T, fromOpenUiAction as _, CHART_PALETTE as a, renderBlocks as b, OPENUI_SUBMIT_ACTION as c, WEBSKILL_STYLES_CSS as d, WebFormBridge as f, fromA2uiAction as g, ensureStyles as h, A2UI_VERSION as i, VERCEL_INTERACTION_TOOL_NAME as l, collectValues as m, A2UI_CANCEL_ACTION as n, LitRendererBridge as o, chartToTable as p, A2UI_SUBMIT_ACTION as r, OPENUI_CANCEL_ACTION as s, A2UI_BASIC_CATALOG_ID as t, VercelUiBridge as u, fromVercelToolResult as v, shapeInteractionValue as w, renderMiniChart as x, interactionToFormModel as y };
|