@webskill/sdk 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +218 -28
- package/dist/browser.d.ts +177 -4
- package/dist/browser.js +444 -35
- package/dist/{catalogComponents-Dr5dFMAb-Dacibl1e.js → catalogComponents-DfxxfUvn-D55Gbb2l.js} +4016 -1226
- package/dist/{dist-8oQRa8Xz.js → dist-59XlqDuv.js} +93 -6
- package/dist/{dist-DnYG2-eY.js → dist-CJqQsIm9.js} +498 -118
- package/dist/{dist-DusANsrn.js → dist-DmI5SBBF.js} +437 -55
- package/dist/eventTypes-g1BXL6x5-CibcOftR.js +37 -0
- package/dist/governance.d.ts +91 -11
- package/dist/governance.js +194 -52
- package/dist/{index-C-KFAZoF.d.ts → index-K-eewlGL.d.ts} +176 -75
- package/dist/{index-BMocOEi0.d.ts → index-P9J2LTfU.d.ts} +163 -6
- package/dist/{index-BuTpBMzr.d.ts → index-fLskQfAS.d.ts} +156 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/mcp.d.ts +129 -6
- package/dist/mcp.js +235 -28
- package/dist/{memoryArtifactStore-52Zn9npI-BMPYwvoy.js → memoryArtifactStore-52Zn9npI-upv5OWYf.js} +1 -1
- package/dist/node.d.ts +3 -3
- package/dist/node.js +4 -3
- package/dist/{openUiLibrary-Bdrji9qK-DzAxRlTY.js → openUiLibrary-DURlAxjk-CU6AzfSW.js} +3 -3
- package/dist/{skillVersionStore-BzLbzFOL-CxdAFWO2.d.ts → skillVersionStore-Bl-ElD45-gRfSaAby.d.ts} +8 -2
- package/dist/{testing-CYTFqkDm.js → testing-BCUO5gZR.js} +2 -2
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +2 -2
- package/dist/{types-4pg-qp_I-Gq63X8Oa.d.ts → types-B3n0cMZu-BdcqQ35O.d.ts} +74 -7
- package/dist/ui-react.d.ts +16 -7
- package/dist/ui-react.js +159 -108
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +2 -2
- package/dist/ui.d.ts +4 -4
- package/dist/ui.js +3 -3
- package/dist/{webskillLitCatalog-_mugzRHx-B_54vxum.js → webskillLitCatalog-DwTwSBFt-DiXXpNZA.js} +22 -3
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import {
|
|
3
|
-
import { n as MockLlmClient } from "./testing-
|
|
1
|
+
import { A as messageOf, D as isValidSkillName, E as exportSkills, F as readSkillSignature, G as verifyManifest, K as verifySkillSignature, M as parseSkillMarkdown, N as parseSkillPackManifest, P as readResponseWithLimit, U as unzipWithLimits, W as validateSkills, _ as atomicWriteText, f as SkillDiscovery, h as assertRemoteUrlAllowed, m as WebSkillError, o as SKILLS_LOCKFILE, r as MANIFEST_EXCLUDED_FILES, s as SKILL_MANIFEST_FILE, u as SKILL_PACK_FILE, v as buildCatalog, w as detectSkillArchiveShapeFromFs, y as buildManifest, z as resolveInsideRoot } from "./dist-59XlqDuv.js";
|
|
2
|
+
import { B as bridgeError, C as ProgressiveRouter, S as OpenAiCompatibleClient, U as createWebSkillApi, a as AgentLoop, b as GoogleGenAiClient, c as CapabilityApproval, dt as parseBridgeRequest, g as FsRunSnapshotStore, h as FsMemoryStore, it as mergeCatalogEntries, lt as normalizeToolContent, m as FsArtifactStore, o as AnthropicClient, ot as networkPolicyLibSource, ut as normalizeToolError } from "./dist-DmI5SBBF.js";
|
|
3
|
+
import { n as MockLlmClient } from "./testing-BCUO5gZR.js";
|
|
4
4
|
|
|
5
5
|
//#region ../browser/dist/index.js
|
|
6
6
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
|
@@ -490,7 +490,8 @@ const PRESERVED_INSTALL_CODES = /* @__PURE__ */ new Set([
|
|
|
490
490
|
"SIGNATURE_MALFORMED",
|
|
491
491
|
"SIGNATURE_UNTRUSTED_KEY",
|
|
492
492
|
"SIGNATURE_MISMATCH",
|
|
493
|
-
"SIGNATURE_UNSUPPORTED"
|
|
493
|
+
"SIGNATURE_UNSUPPORTED",
|
|
494
|
+
"SKILL_DUPLICATE_NAME"
|
|
494
495
|
]);
|
|
495
496
|
const asInstallFailed = (e) => e instanceof WebSkillError && (e.code === "INSTALL_FAILED" || e.code === "TOOL_UNSUPPORTED" || PRESERVED_INSTALL_CODES.has(e.code)) ? e : new WebSkillError("INSTALL_FAILED", `Install failed: ${messageOf(e)}`, e);
|
|
496
497
|
const lockfilePath = (root) => `${root}/${SKILLS_LOCKFILE}`;
|
|
@@ -510,6 +511,8 @@ const EMPTY_TRUSTED_KEYS = {
|
|
|
510
511
|
var BrowserSkillManager = class {
|
|
511
512
|
#fs;
|
|
512
513
|
#managedRoot;
|
|
514
|
+
/** 只读技能根(宿主自带的本地技能):参与导出与依赖存在性判定,但不可卸载 */
|
|
515
|
+
#skillRoots;
|
|
513
516
|
#fetchImpl;
|
|
514
517
|
#archiveLimits;
|
|
515
518
|
#onChanged;
|
|
@@ -519,6 +522,7 @@ var BrowserSkillManager = class {
|
|
|
519
522
|
constructor(deps) {
|
|
520
523
|
this.#fs = deps.fs;
|
|
521
524
|
this.#managedRoot = deps.managedRoot.replace(/\/+$/, "");
|
|
525
|
+
this.#skillRoots = (deps.skillRoots ?? []).map((root) => root.replace(/\/+$/, ""));
|
|
522
526
|
this.#fetchImpl = deps.fetchImpl;
|
|
523
527
|
this.#archiveLimits = deps.archiveLimits;
|
|
524
528
|
this.#onChanged = deps.onChanged;
|
|
@@ -572,17 +576,16 @@ var BrowserSkillManager = class {
|
|
|
572
576
|
const contentDir = `${stagingRoot}/content`;
|
|
573
577
|
await fs.mkdir(contentDir);
|
|
574
578
|
await extractZipWeb(fs, data, contentDir, this.#archiveLimits);
|
|
575
|
-
const
|
|
576
|
-
if (
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (dirs.length === 1 && await fs.exists(`${dirs[0]?.path}/SKILL.md`)) skillDir = dirs[0].path;
|
|
584
|
-
else throw new WebSkillError("INSTALL_FAILED", "Archive does not contain a SKILL.md at its root or in a single top-level directory");
|
|
579
|
+
const detection = await detectSkillArchiveShapeFromFs(fs, contentDir);
|
|
580
|
+
if (detection.shape === "invalid") throw new WebSkillError("INSTALL_FAILED", detection.reason ?? "Archive layout is not recognised");
|
|
581
|
+
if (detection.shape === "pack-set") {
|
|
582
|
+
const packRoot = detection.rootDir === void 0 ? contentDir : `${contentDir}/${detection.rootDir}`;
|
|
583
|
+
return (await this.#installPack({
|
|
584
|
+
contentDir: packRoot,
|
|
585
|
+
manifest: parseSkillPackManifest(await fs.readText(`${packRoot}/${SKILL_PACK_FILE}`))
|
|
586
|
+
}, source, stagingRoot))[0];
|
|
585
587
|
}
|
|
588
|
+
const skillDir = detection.rootDir === void 0 ? contentDir : `${contentDir}/${detection.rootDir}`;
|
|
586
589
|
let name;
|
|
587
590
|
let version;
|
|
588
591
|
try {
|
|
@@ -593,13 +596,18 @@ var BrowserSkillManager = class {
|
|
|
593
596
|
throw new WebSkillError("INSTALL_FAILED", `Failed to read skill name from SKILL.md: ${messageOf(e)}`, e);
|
|
594
597
|
}
|
|
595
598
|
if (!isValidSkillName(name)) throw new WebSkillError("INSTALL_FAILED", `Invalid skill name in SKILL.md (path traversal rejected): ${JSON.stringify(name)}`);
|
|
599
|
+
if (options?.replace === "reject" && await fs.exists(`${this.#managedRoot}/${name}`)) throw new WebSkillError("SKILL_DUPLICATE_NAME", `A skill named "${name}" is already installed. Installing it again replaces the existing files; use the packages flow if that is intended.`);
|
|
596
600
|
const finalRoot = `${stagingRoot}/final`;
|
|
597
601
|
const finalDir = `${finalRoot}/${name}`;
|
|
598
602
|
await copyDir(fs, skillDir, finalDir);
|
|
599
|
-
const report = await validateSkills(fs, [finalRoot]);
|
|
603
|
+
const report = await validateSkills(fs, [finalRoot], { extraKnownSkillNames: await this.listSkillNames() });
|
|
600
604
|
if (!report.ok) {
|
|
601
|
-
const errors = report.issues.filter((i) => i.severity === "error");
|
|
602
|
-
|
|
605
|
+
const errors = report.issues.filter((i) => i.severity === "error" && i.code !== "SKILL_UNKNOWN_DEPENDENCY");
|
|
606
|
+
for (const issue of report.issues) if (issue.code === "SKILL_UNKNOWN_DEPENDENCY") this.#onWarning?.({
|
|
607
|
+
skill: name,
|
|
608
|
+
message: issue.message
|
|
609
|
+
});
|
|
610
|
+
if (errors.length > 0) throw new WebSkillError("INSTALL_FAILED", `Skill "${name}" failed validation: ${errors.map((i) => i.message).join("; ")}`, errors);
|
|
603
611
|
}
|
|
604
612
|
const manifest = await this.#createManifest(finalDir, {
|
|
605
613
|
name,
|
|
@@ -714,6 +722,21 @@ var BrowserSkillManager = class {
|
|
|
714
722
|
throw asInstallFailed(e);
|
|
715
723
|
}
|
|
716
724
|
}
|
|
725
|
+
/** 技能的真实根:先 managed,再只读根。都没有则 undefined */
|
|
726
|
+
async #resolveSkillRoot(name) {
|
|
727
|
+
const managed = `${this.#managedRoot}/${name}`;
|
|
728
|
+
if (await this.#fs.exists(managed)) return {
|
|
729
|
+
root: managed,
|
|
730
|
+
managed: true
|
|
731
|
+
};
|
|
732
|
+
for (const base of this.#skillRoots) {
|
|
733
|
+
const candidate = `${base}/${name}`;
|
|
734
|
+
if (await this.#fs.exists(candidate)) return {
|
|
735
|
+
root: candidate,
|
|
736
|
+
managed: false
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
717
740
|
/**
|
|
718
741
|
* 浏览器导出(单技能与包集同构:始终产出含 webskill.skill-pack.json 的 zip 字节,
|
|
719
742
|
* 统一安装路径;保存/下载由应用层处理)。
|
|
@@ -721,11 +744,13 @@ var BrowserSkillManager = class {
|
|
|
721
744
|
async exportArchive(names) {
|
|
722
745
|
const list = Array.isArray(names) ? names : [names];
|
|
723
746
|
if (list.length === 0) throw new WebSkillError("EXPORT_FAILED", "exportArchive requires at least one skill name");
|
|
724
|
-
const roots =
|
|
747
|
+
const roots = [];
|
|
748
|
+
for (const name of list) {
|
|
725
749
|
if (!isValidSkillName(name)) throw new WebSkillError("EXPORT_FAILED", `Invalid skill name (path traversal rejected): ${JSON.stringify(name)}`);
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
750
|
+
const resolved = await this.#resolveSkillRoot(name);
|
|
751
|
+
if (resolved === void 0) throw new WebSkillError("EXPORT_FAILED", `Skill "${name}" is not installed`);
|
|
752
|
+
roots.push(resolved.root);
|
|
753
|
+
}
|
|
729
754
|
return exportSkills(this.#fs, {
|
|
730
755
|
roots,
|
|
731
756
|
manifestBuilder: (root) => this.#readManifest(root)
|
|
@@ -733,8 +758,10 @@ var BrowserSkillManager = class {
|
|
|
733
758
|
}
|
|
734
759
|
async uninstall(name) {
|
|
735
760
|
if (!isValidSkillName(name)) throw new WebSkillError("UNINSTALL_FAILED", `Invalid skill name (path traversal rejected): ${JSON.stringify(name)}`);
|
|
736
|
-
const
|
|
737
|
-
if (
|
|
761
|
+
const resolved = await this.#resolveSkillRoot(name);
|
|
762
|
+
if (resolved === void 0) throw new WebSkillError("UNINSTALL_FAILED", `Skill "${name}" is not installed`);
|
|
763
|
+
if (!resolved.managed) throw new WebSkillError("UNINSTALL_FAILED", `Skill "${name}" ships with the host application and is not managed by the skill manager; remove it from the host's skill root instead.`);
|
|
764
|
+
const targetDir = resolved.root;
|
|
738
765
|
try {
|
|
739
766
|
await this.#fs.remove(targetDir, { recursive: true });
|
|
740
767
|
await this.#removeLockEntry(name);
|
|
@@ -763,6 +790,22 @@ var BrowserSkillManager = class {
|
|
|
763
790
|
throw new WebSkillError("INTEGRITY_FAILED", `Skills lockfile at ${path} is corrupted: ${messageOf(e)}`, e);
|
|
764
791
|
}
|
|
765
792
|
}
|
|
793
|
+
/**
|
|
794
|
+
* 库里已有的技能名:managed 目录 + 宿主声明的只读根。读失败按「库为空」处理,不阻断安装。
|
|
795
|
+
* 与 `listInstalled()` 的区别是**含本地技能**(后者只看安装账本)。
|
|
796
|
+
* @experimental
|
|
797
|
+
*/
|
|
798
|
+
async listSkillNames() {
|
|
799
|
+
const names = [];
|
|
800
|
+
for (const root of [this.#managedRoot, ...this.#skillRoots]) {
|
|
801
|
+
if (!await this.#fs.exists(root).catch(() => false)) continue;
|
|
802
|
+
for (const stat of await this.#fs.list(root).catch(() => [])) {
|
|
803
|
+
const name = stat.path.split("/").pop() ?? "";
|
|
804
|
+
if (stat.type === "directory" && isValidSkillName(name)) names.push(name);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return names;
|
|
808
|
+
}
|
|
766
809
|
async #createManifest(skillRoot, input) {
|
|
767
810
|
const files = [];
|
|
768
811
|
for (const rel of (await listFiles(this.#fs, skillRoot)).sort()) {
|
|
@@ -787,7 +830,16 @@ var BrowserSkillManager = class {
|
|
|
787
830
|
}
|
|
788
831
|
async #readManifest(skillRoot) {
|
|
789
832
|
const path = `${skillRoot}/${SKILL_MANIFEST_FILE}`;
|
|
790
|
-
if (!await this.#fs.exists(path))
|
|
833
|
+
if (!await this.#fs.exists(path)) {
|
|
834
|
+
const name = skillRoot.split("/").pop() ?? skillRoot;
|
|
835
|
+
return this.#createManifest(skillRoot, {
|
|
836
|
+
name,
|
|
837
|
+
source: {
|
|
838
|
+
type: "local",
|
|
839
|
+
path: skillRoot
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
}
|
|
791
843
|
try {
|
|
792
844
|
return JSON.parse(await this.#fs.readText(path));
|
|
793
845
|
} catch (e) {
|
|
@@ -1615,8 +1667,20 @@ function describeError(e) {
|
|
|
1615
1667
|
if (e instanceof TypeError) return "the request was blocked, most likely by CORS";
|
|
1616
1668
|
return e instanceof Error ? e.message : String(e);
|
|
1617
1669
|
}
|
|
1618
|
-
/** 值可能是凭据的输入类型:即便宿主忘了写进 exclude
|
|
1670
|
+
/** 值可能是凭据的输入类型:即便宿主忘了写进 exclude 也不读值。操作侧复用同一份,不另写 */
|
|
1619
1671
|
const SECRET_INPUT_TYPES = /* @__PURE__ */ new Set(["password", "hidden"]);
|
|
1672
|
+
/** 可被操作的角色:只给这些节点发句柄,其余节点模型连提都提不出来 */
|
|
1673
|
+
const ACTIONABLE_ROLES = /* @__PURE__ */ new Set([
|
|
1674
|
+
"button",
|
|
1675
|
+
"checkbox",
|
|
1676
|
+
"combobox",
|
|
1677
|
+
"form",
|
|
1678
|
+
"link",
|
|
1679
|
+
"radio",
|
|
1680
|
+
"searchbox",
|
|
1681
|
+
"switch",
|
|
1682
|
+
"textbox"
|
|
1683
|
+
]);
|
|
1620
1684
|
/** tag → 可访问性角色的最小映射;命中不了就退到 generic,不猜 */
|
|
1621
1685
|
const ROLE_BY_TAG = {
|
|
1622
1686
|
A: "link",
|
|
@@ -1654,7 +1718,7 @@ const clip = (text) => {
|
|
|
1654
1718
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
1655
1719
|
return normalized.length > MAX_TEXT ? `${normalized.slice(0, MAX_TEXT)}…` : normalized;
|
|
1656
1720
|
};
|
|
1657
|
-
function roleOf(element) {
|
|
1721
|
+
function roleOf$1(element) {
|
|
1658
1722
|
const explicit = element.getAttribute("role");
|
|
1659
1723
|
if (explicit !== null && explicit.trim() !== "") return explicit.trim();
|
|
1660
1724
|
if (element.tagName === "INPUT") {
|
|
@@ -1700,7 +1764,7 @@ function valueOf(element) {
|
|
|
1700
1764
|
* 隐藏节点不进结果:屏幕上看不见的东西,用户没有机会发现它被读走了。
|
|
1701
1765
|
* jsdom 里 `getComputedStyle` 可用但布局是假的,因此只看显式声明,不看尺寸。
|
|
1702
1766
|
*/
|
|
1703
|
-
function hidden(element, view) {
|
|
1767
|
+
function hidden$1(element, view) {
|
|
1704
1768
|
if (element.hasAttribute("hidden")) return true;
|
|
1705
1769
|
if (element.getAttribute("aria-hidden") === "true") return true;
|
|
1706
1770
|
const inlineStyle = element.getAttribute("style") ?? "";
|
|
@@ -1718,18 +1782,18 @@ const SKIPPED_TAGS = /* @__PURE__ */ new Set([
|
|
|
1718
1782
|
"NOSCRIPT",
|
|
1719
1783
|
"TEMPLATE"
|
|
1720
1784
|
]);
|
|
1721
|
-
function describe(element, excluded, doc, view, targets) {
|
|
1785
|
+
function describe(element, excluded, doc, view, targets, handles) {
|
|
1722
1786
|
if (excluded.has(element)) return void 0;
|
|
1723
1787
|
if (SKIPPED_TAGS.has(element.tagName)) return void 0;
|
|
1724
|
-
if (hidden(element, view)) return void 0;
|
|
1788
|
+
if (hidden$1(element, view)) return void 0;
|
|
1725
1789
|
const children = [];
|
|
1726
1790
|
for (const child of element.children) {
|
|
1727
|
-
const node = describe(child, excluded, doc, view, targets);
|
|
1791
|
+
const node = describe(child, excluded, doc, view, targets, handles);
|
|
1728
1792
|
if (node !== void 0) children.push(node);
|
|
1729
1793
|
}
|
|
1730
1794
|
const name = nameOf(element, doc);
|
|
1731
1795
|
const value = valueOf(element);
|
|
1732
|
-
const role = roleOf(element);
|
|
1796
|
+
const role = roleOf$1(element);
|
|
1733
1797
|
if (role === "generic" && name === void 0 && value === void 0 && children.length === 0) return void 0;
|
|
1734
1798
|
const node = {
|
|
1735
1799
|
role,
|
|
@@ -1737,6 +1801,8 @@ function describe(element, excluded, doc, view, targets) {
|
|
|
1737
1801
|
...value !== void 0 ? { value } : {},
|
|
1738
1802
|
...children.length > 0 ? { children } : {}
|
|
1739
1803
|
};
|
|
1804
|
+
const ref = handles?.issue(element, role);
|
|
1805
|
+
if (ref !== void 0) node.ref = ref;
|
|
1740
1806
|
if (targets !== void 0 && isCapturableElement(element)) targets.push({
|
|
1741
1807
|
element,
|
|
1742
1808
|
node
|
|
@@ -1744,6 +1810,31 @@ function describe(element, excluded, doc, view, targets) {
|
|
|
1744
1810
|
return node;
|
|
1745
1811
|
}
|
|
1746
1812
|
/**
|
|
1813
|
+
* 句柄发放与解析。表在 reader 实例内、每次 `read()` 整体替换:
|
|
1814
|
+
* 全局表是第二套状态且会跨会话泄漏;不替换则页面变了还能用旧句柄点到别的东西。
|
|
1815
|
+
*/
|
|
1816
|
+
var HandleIssuer = class {
|
|
1817
|
+
#table = /* @__PURE__ */ new Map();
|
|
1818
|
+
#actionable;
|
|
1819
|
+
#excluded;
|
|
1820
|
+
constructor(actionable, excluded) {
|
|
1821
|
+
this.#actionable = actionable;
|
|
1822
|
+
this.#excluded = excluded;
|
|
1823
|
+
}
|
|
1824
|
+
issue(element, role) {
|
|
1825
|
+
if (!this.#actionable.has(element) || this.#excluded.has(element)) return void 0;
|
|
1826
|
+
if (!ACTIONABLE_ROLES.has(role)) return void 0;
|
|
1827
|
+
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
1828
|
+
crypto.getRandomValues(bytes);
|
|
1829
|
+
const ref = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1830
|
+
this.#table.set(ref, element);
|
|
1831
|
+
return ref;
|
|
1832
|
+
}
|
|
1833
|
+
get table() {
|
|
1834
|
+
return this.#table;
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
/**
|
|
1747
1838
|
* DOM 遍历实现(设计 09 §2 的「实现层」)。产出只有角色 / 名称 / 值,
|
|
1748
1839
|
* **不含任何 HTML 标记、class、data 属性或注释**(FR-10.4)。
|
|
1749
1840
|
*
|
|
@@ -1752,17 +1843,43 @@ function describe(element, excluded, doc, view, targets) {
|
|
|
1752
1843
|
* @experimental
|
|
1753
1844
|
*/
|
|
1754
1845
|
function createDomPerceptionReader(options = {}) {
|
|
1846
|
+
let handles;
|
|
1847
|
+
const resolveDocument = () => options.document ?? globalThis.document;
|
|
1848
|
+
/** 按当前 DOM 重算一次可操作集合;发句柄与执行前各算一次 */
|
|
1849
|
+
const actionSets = (doc) => {
|
|
1850
|
+
const actionable = /* @__PURE__ */ new Set();
|
|
1851
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
1852
|
+
const scope = options.actionScope;
|
|
1853
|
+
if (scope === void 0 || scope.include.length === 0) return {
|
|
1854
|
+
actionable,
|
|
1855
|
+
excluded
|
|
1856
|
+
};
|
|
1857
|
+
for (const selector of scope.include) for (const root of doc.querySelectorAll(selector)) {
|
|
1858
|
+
actionable.add(root);
|
|
1859
|
+
for (const descendant of root.querySelectorAll("*")) actionable.add(descendant);
|
|
1860
|
+
}
|
|
1861
|
+
for (const selector of scope.exclude ?? []) for (const root of doc.querySelectorAll(selector)) {
|
|
1862
|
+
excluded.add(root);
|
|
1863
|
+
for (const descendant of root.querySelectorAll("*")) excluded.add(descendant);
|
|
1864
|
+
}
|
|
1865
|
+
return {
|
|
1866
|
+
actionable,
|
|
1867
|
+
excluded
|
|
1868
|
+
};
|
|
1869
|
+
};
|
|
1755
1870
|
function collect(scope, targets) {
|
|
1756
|
-
const doc =
|
|
1871
|
+
const doc = resolveDocument();
|
|
1757
1872
|
if (doc === void 0 || scope.include.length === 0) return [];
|
|
1758
1873
|
const view = doc.defaultView;
|
|
1874
|
+
const sets = actionSets(doc);
|
|
1875
|
+
handles = new HandleIssuer(sets.actionable, sets.excluded);
|
|
1759
1876
|
const excluded = /* @__PURE__ */ new Set();
|
|
1760
1877
|
for (const selector of scope.exclude ?? []) for (const element of doc.querySelectorAll(selector)) excluded.add(element);
|
|
1761
1878
|
const roots = [];
|
|
1762
1879
|
for (const selector of scope.include) for (const element of doc.querySelectorAll(selector)) if (!roots.includes(element)) roots.push(element);
|
|
1763
1880
|
const nodes = [];
|
|
1764
1881
|
for (const root of roots) {
|
|
1765
|
-
const node = describe(root, excluded, doc, view, targets);
|
|
1882
|
+
const node = describe(root, excluded, doc, view, targets, handles);
|
|
1766
1883
|
if (node !== void 0) nodes.push(node);
|
|
1767
1884
|
}
|
|
1768
1885
|
return nodes;
|
|
@@ -1804,7 +1921,126 @@ function createDomPerceptionReader(options = {}) {
|
|
|
1804
1921
|
if (!capture.images || capture.maxImages <= 0) return Promise.resolve({ nodes: collect(scope, void 0) });
|
|
1805
1922
|
return readWithImages(scope, capture);
|
|
1806
1923
|
}
|
|
1807
|
-
return {
|
|
1924
|
+
return {
|
|
1925
|
+
read,
|
|
1926
|
+
resolve: (ref) => handles?.table.get(ref),
|
|
1927
|
+
inActionScope: (element) => {
|
|
1928
|
+
const doc = resolveDocument();
|
|
1929
|
+
if (doc === void 0) return false;
|
|
1930
|
+
const sets = actionSets(doc);
|
|
1931
|
+
return sets.actionable.has(element) && !sets.excluded.has(element);
|
|
1932
|
+
}
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
const FILLABLE_TAGS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA"]);
|
|
1936
|
+
/** 只看显式声明,不看尺寸:jsdom 有 getComputedStyle 但没有布局 */
|
|
1937
|
+
function hidden(element) {
|
|
1938
|
+
if (element.hasAttribute("hidden")) return true;
|
|
1939
|
+
if (element.getAttribute("aria-hidden") === "true") return true;
|
|
1940
|
+
const inlineStyle = element.getAttribute("style") ?? "";
|
|
1941
|
+
if (/display\s*:\s*none|visibility\s*:\s*hidden/i.test(inlineStyle)) return true;
|
|
1942
|
+
const view = element.ownerDocument.defaultView;
|
|
1943
|
+
if (view !== null) {
|
|
1944
|
+
const computed = view.getComputedStyle(element);
|
|
1945
|
+
if (computed.display === "none" || computed.visibility === "hidden") return true;
|
|
1946
|
+
}
|
|
1947
|
+
return false;
|
|
1948
|
+
}
|
|
1949
|
+
const inputType = (element) => (element.getAttribute("type") ?? "text").toLowerCase();
|
|
1950
|
+
const isSecret = (element) => element.tagName === "INPUT" && SECRET_INPUT_TYPES.has(inputType(element));
|
|
1951
|
+
function accessibleName(element) {
|
|
1952
|
+
const label = element.getAttribute("aria-label");
|
|
1953
|
+
if (label !== null && label.trim() !== "") return label.trim();
|
|
1954
|
+
const text = element.textContent?.replace(/\s+/g, " ").trim() ?? "";
|
|
1955
|
+
if (text !== "") return text;
|
|
1956
|
+
const placeholder = element.getAttribute("placeholder");
|
|
1957
|
+
return placeholder !== null && placeholder.trim() !== "" ? placeholder.trim() : void 0;
|
|
1958
|
+
}
|
|
1959
|
+
function roleOf(element) {
|
|
1960
|
+
const explicit = element.getAttribute("role");
|
|
1961
|
+
if (explicit !== null && explicit.trim() !== "") return explicit.trim();
|
|
1962
|
+
if (element.tagName === "INPUT") {
|
|
1963
|
+
const type = inputType(element);
|
|
1964
|
+
return type === "checkbox" || type === "radio" ? type : type === "button" || type === "submit" ? "button" : "textbox";
|
|
1965
|
+
}
|
|
1966
|
+
if (element.tagName === "BUTTON") return "button";
|
|
1967
|
+
if (element.tagName === "A") return "link";
|
|
1968
|
+
if (element.tagName === "TEXTAREA") return "textbox";
|
|
1969
|
+
if (element.tagName === "SELECT") return "combobox";
|
|
1970
|
+
if (element.tagName === "FORM") return "form";
|
|
1971
|
+
return "generic";
|
|
1972
|
+
}
|
|
1973
|
+
const describeElement = (element) => {
|
|
1974
|
+
const name = accessibleName(element);
|
|
1975
|
+
return {
|
|
1976
|
+
role: roleOf(element),
|
|
1977
|
+
...name !== void 0 ? { name } : {},
|
|
1978
|
+
...isSecret(element) ? { secret: true } : {}
|
|
1979
|
+
};
|
|
1980
|
+
};
|
|
1981
|
+
/**
|
|
1982
|
+
* 受控组件必须收到 `input` / `change`:直接写 `.value` 在 React 上会被下一次渲染覆盖,
|
|
1983
|
+
* 表现为「填了又没了」。原生 setter 是绕过 React value 劫持的唯一办法。
|
|
1984
|
+
*/
|
|
1985
|
+
function fillValue(element, value) {
|
|
1986
|
+
const prototype = element.tagName === "INPUT" ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
|
|
1987
|
+
const setter = Object.getOwnPropertyDescriptor(prototype, "value")?.set;
|
|
1988
|
+
if (setter) setter.call(element, value);
|
|
1989
|
+
else element.value = value;
|
|
1990
|
+
element.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1991
|
+
element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1992
|
+
}
|
|
1993
|
+
/** @experimental */
|
|
1994
|
+
function createDomPageActionExecutor(options) {
|
|
1995
|
+
const { reader } = options;
|
|
1996
|
+
const describe = (ref) => {
|
|
1997
|
+
const element = reader.resolve(ref);
|
|
1998
|
+
return element === void 0 ? void 0 : describeElement(element);
|
|
1999
|
+
};
|
|
2000
|
+
const execute = (request) => {
|
|
2001
|
+
const element = reader.resolve(request.ref);
|
|
2002
|
+
if (element === void 0) return {
|
|
2003
|
+
ok: false,
|
|
2004
|
+
target: { role: "generic" },
|
|
2005
|
+
reason: "The element reference is unknown or expired."
|
|
2006
|
+
};
|
|
2007
|
+
const target = describeElement(element);
|
|
2008
|
+
const fail = (reason) => ({
|
|
2009
|
+
ok: false,
|
|
2010
|
+
target,
|
|
2011
|
+
reason
|
|
2012
|
+
});
|
|
2013
|
+
if (!reader.inActionScope(element)) return fail("The element is no longer inside the actionable scope.");
|
|
2014
|
+
if (hidden(element)) return fail("The element is not visible.");
|
|
2015
|
+
if (element.hasAttribute("disabled")) return fail("The element is disabled.");
|
|
2016
|
+
if (request.action === "click") {
|
|
2017
|
+
element.click();
|
|
2018
|
+
return {
|
|
2019
|
+
ok: true,
|
|
2020
|
+
target
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
if (request.action === "fill") {
|
|
2024
|
+
if (!FILLABLE_TAGS.has(element.tagName)) return fail("The element is not a text control.");
|
|
2025
|
+
if (element.hasAttribute("readonly")) return fail("The element is read-only.");
|
|
2026
|
+
fillValue(element, request.value ?? "");
|
|
2027
|
+
return {
|
|
2028
|
+
ok: true,
|
|
2029
|
+
target
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
const form = element.tagName === "FORM" ? element : element.form;
|
|
2033
|
+
if (!form) return fail("The element does not belong to a form.");
|
|
2034
|
+
form.requestSubmit();
|
|
2035
|
+
return {
|
|
2036
|
+
ok: true,
|
|
2037
|
+
target
|
|
2038
|
+
};
|
|
2039
|
+
};
|
|
2040
|
+
return {
|
|
2041
|
+
execute,
|
|
2042
|
+
describe
|
|
2043
|
+
};
|
|
1808
2044
|
}
|
|
1809
2045
|
const isRecord = (v) => typeof v === "object" && v !== null;
|
|
1810
2046
|
const isNonEmptyString = (v) => typeof v === "string" && v !== "";
|
|
@@ -2486,6 +2722,179 @@ async function deleteMemoryEncryptionKey(name = "user-profile") {
|
|
|
2486
2722
|
db.close();
|
|
2487
2723
|
}
|
|
2488
2724
|
}
|
|
2725
|
+
/**
|
|
2726
|
+
* 端口 × 宿主类型的**唯一定义**(AC-17.4)。
|
|
2727
|
+
* About 页与装配工厂都读这一份;护栏 `port-matrix-single-source` 盯着不许再抄。
|
|
2728
|
+
*/
|
|
2729
|
+
const HOST_PORT_MATRIX = [
|
|
2730
|
+
{
|
|
2731
|
+
id: "install / uninstall",
|
|
2732
|
+
surface: "console-backend",
|
|
2733
|
+
key: "install",
|
|
2734
|
+
applicability: {
|
|
2735
|
+
browser: "expected",
|
|
2736
|
+
node: "expected"
|
|
2737
|
+
},
|
|
2738
|
+
note: "Wire ConsoleBackend.install to let the console install and remove skills."
|
|
2739
|
+
},
|
|
2740
|
+
{
|
|
2741
|
+
id: "export (zip)",
|
|
2742
|
+
surface: "console-backend",
|
|
2743
|
+
key: "exportSkill",
|
|
2744
|
+
applicability: {
|
|
2745
|
+
browser: "expected",
|
|
2746
|
+
node: "expected"
|
|
2747
|
+
},
|
|
2748
|
+
note: "Wire ConsoleBackend.exportSkill to enable zip export."
|
|
2749
|
+
},
|
|
2750
|
+
{
|
|
2751
|
+
id: "export (tar)",
|
|
2752
|
+
surface: "console-backend",
|
|
2753
|
+
key: "exportSkillTar",
|
|
2754
|
+
applicability: {
|
|
2755
|
+
browser: "not-applicable",
|
|
2756
|
+
node: "expected"
|
|
2757
|
+
},
|
|
2758
|
+
note: "Browsers have no tar toolchain in the platform; zip export covers the same need."
|
|
2759
|
+
},
|
|
2760
|
+
{
|
|
2761
|
+
id: "integrity",
|
|
2762
|
+
surface: "console-backend",
|
|
2763
|
+
key: "verifyIntegrity",
|
|
2764
|
+
applicability: {
|
|
2765
|
+
browser: "expected",
|
|
2766
|
+
node: "expected"
|
|
2767
|
+
},
|
|
2768
|
+
note: "Wire ConsoleBackend.verifyIntegrity to enable the integrity report."
|
|
2769
|
+
},
|
|
2770
|
+
{
|
|
2771
|
+
id: "editing",
|
|
2772
|
+
surface: "console-backend",
|
|
2773
|
+
key: "writeSkillFile",
|
|
2774
|
+
applicability: {
|
|
2775
|
+
browser: "expected",
|
|
2776
|
+
node: "expected"
|
|
2777
|
+
},
|
|
2778
|
+
note: "Wire ConsoleBackend.writeSkillFile to enable in-place skill editing."
|
|
2779
|
+
},
|
|
2780
|
+
{
|
|
2781
|
+
id: "versions",
|
|
2782
|
+
surface: "console-backend",
|
|
2783
|
+
key: "listVersions",
|
|
2784
|
+
applicability: {
|
|
2785
|
+
browser: "expected",
|
|
2786
|
+
node: "expected"
|
|
2787
|
+
},
|
|
2788
|
+
note: "Wire ConsoleBackend.listVersions to show version history and rollback."
|
|
2789
|
+
},
|
|
2790
|
+
{
|
|
2791
|
+
id: "runs",
|
|
2792
|
+
surface: "console-backend",
|
|
2793
|
+
key: "runs",
|
|
2794
|
+
applicability: {
|
|
2795
|
+
browser: "expected",
|
|
2796
|
+
node: "expected"
|
|
2797
|
+
},
|
|
2798
|
+
note: "Wire ConsoleBackend.runs to read run history; without it the runs pages have no source."
|
|
2799
|
+
},
|
|
2800
|
+
{
|
|
2801
|
+
id: "sessions",
|
|
2802
|
+
surface: "console-backend",
|
|
2803
|
+
key: "sessions",
|
|
2804
|
+
applicability: {
|
|
2805
|
+
browser: "expected",
|
|
2806
|
+
node: "expected"
|
|
2807
|
+
},
|
|
2808
|
+
note: "Wire ConsoleBackend.sessions to list chat sessions."
|
|
2809
|
+
},
|
|
2810
|
+
{
|
|
2811
|
+
id: "page perception",
|
|
2812
|
+
surface: "chatbot-adapter",
|
|
2813
|
+
key: "pagePerception",
|
|
2814
|
+
applicability: {
|
|
2815
|
+
browser: "expected",
|
|
2816
|
+
node: "not-applicable"
|
|
2817
|
+
},
|
|
2818
|
+
note: "Page perception reads the live DOM; there is no page outside a browser."
|
|
2819
|
+
},
|
|
2820
|
+
{
|
|
2821
|
+
id: "skill candidates",
|
|
2822
|
+
surface: "chatbot-config",
|
|
2823
|
+
key: "skillCandidates",
|
|
2824
|
+
applicability: {
|
|
2825
|
+
browser: "expected",
|
|
2826
|
+
node: "expected"
|
|
2827
|
+
},
|
|
2828
|
+
note: "Wire skillCandidates so skill-miss suggestions can be reviewed in the console."
|
|
2829
|
+
},
|
|
2830
|
+
{
|
|
2831
|
+
id: "remote endpoints",
|
|
2832
|
+
surface: "connect-facade",
|
|
2833
|
+
key: "listEndpoints",
|
|
2834
|
+
applicability: {
|
|
2835
|
+
browser: "expected",
|
|
2836
|
+
node: "expected"
|
|
2837
|
+
},
|
|
2838
|
+
note: "Wire ConnectFacade.listEndpoints to manage remote MCP endpoints."
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
id: "page-declared skills",
|
|
2842
|
+
surface: "connect-facade",
|
|
2843
|
+
key: "temporarySkills",
|
|
2844
|
+
applicability: {
|
|
2845
|
+
browser: "expected",
|
|
2846
|
+
node: "not-applicable"
|
|
2847
|
+
},
|
|
2848
|
+
note: "Page-declared temporary skills come from the document; Node hosts have none."
|
|
2849
|
+
}
|
|
2850
|
+
];
|
|
2851
|
+
function inspectHostWiring(input) {
|
|
2852
|
+
const kind = input.kind ?? "browser";
|
|
2853
|
+
return HOST_PORT_MATRIX.map((spec) => {
|
|
2854
|
+
if (!(spec.surface in input.surfaces)) return {
|
|
2855
|
+
...spec,
|
|
2856
|
+
status: "unknown"
|
|
2857
|
+
};
|
|
2858
|
+
if (spec.applicability[kind] === "not-applicable") return {
|
|
2859
|
+
...spec,
|
|
2860
|
+
status: "not-applicable"
|
|
2861
|
+
};
|
|
2862
|
+
const surface = input.surfaces[spec.surface];
|
|
2863
|
+
const wired = surface !== void 0 && surface[spec.key] !== void 0;
|
|
2864
|
+
return {
|
|
2865
|
+
...spec,
|
|
2866
|
+
status: wired ? "wired" : "missing"
|
|
2867
|
+
};
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
/** 缺口 = 该接却没接的项;`unknown` 不算缺口,没看到不等于没接 */
|
|
2871
|
+
function missingPorts(reports) {
|
|
2872
|
+
return reports.filter((report) => report.status === "missing");
|
|
2873
|
+
}
|
|
2874
|
+
function createBrowserChatbotHost(options) {
|
|
2875
|
+
const { fs, managedRoot, skillRoots, navigation, overrides } = options;
|
|
2876
|
+
const chatbotAdapter = {
|
|
2877
|
+
storage: fs,
|
|
2878
|
+
skillRoots,
|
|
2879
|
+
navigation,
|
|
2880
|
+
skillManager: new BrowserSkillManager({
|
|
2881
|
+
fs,
|
|
2882
|
+
managedRoot,
|
|
2883
|
+
skillRoots
|
|
2884
|
+
}),
|
|
2885
|
+
...options.pagePerception !== void 0 ? { pagePerception: options.pagePerception } : {}
|
|
2886
|
+
};
|
|
2887
|
+
const consoleBackend = options.consoleBackend ?? { catalog: async () => ({ entries: [] }) };
|
|
2888
|
+
const connectFacade = options.connectFacade ?? {
|
|
2889
|
+
listEndpoints: async () => [],
|
|
2890
|
+
listTools: async () => []
|
|
2891
|
+
};
|
|
2892
|
+
return {
|
|
2893
|
+
chatbotAdapter: overrides?.chatbotAdapter ?? chatbotAdapter,
|
|
2894
|
+
consoleBackend: overrides?.consoleBackend ?? consoleBackend,
|
|
2895
|
+
connectFacade: overrides?.connectFacade ?? connectFacade
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2489
2898
|
|
|
2490
2899
|
//#endregion
|
|
2491
|
-
export { BrowserSkillManager, BrowserWorkerScriptExecutor, ChromeBuiltinLlmClient, IframeWorkerLike, OpfsProvider, TsTranspiler, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, bridgeError, captureElementImage, checkDictationAvailability, compressImageToBudget, createDomPerceptionReader, createEncryptedMemoryStore, createIframeWorker, createLlmClient, deleteMemoryEncryptionKey, extractZipWeb, generateMemoryEncryptionKey, installWebSkillNavigator, isCapturableElement, isCaptureFailure, isEncryptedMemoryValue, isOpfsAvailable, openMemoryEncryptionKey, parseBridgeRequest, parseMainMessage, parseWorkerEvent, probeChromeBuiltinAvailability, sha256HexWeb, startDictation, startWorkerRuntimeHost };
|
|
2900
|
+
export { BrowserSkillManager, BrowserWorkerScriptExecutor, ChromeBuiltinLlmClient, HOST_PORT_MATRIX, IframeWorkerLike, OpfsProvider, TsTranspiler, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, bridgeError, captureElementImage, checkDictationAvailability, compressImageToBudget, createBrowserChatbotHost, createDomPageActionExecutor, createDomPerceptionReader, createEncryptedMemoryStore, createIframeWorker, createLlmClient, deleteMemoryEncryptionKey, extractZipWeb, generateMemoryEncryptionKey, inspectHostWiring, installWebSkillNavigator, isCapturableElement, isCaptureFailure, isEncryptedMemoryValue, isOpfsAvailable, missingPorts, openMemoryEncryptionKey, parseBridgeRequest, parseMainMessage, parseWorkerEvent, probeChromeBuiltinAvailability, sha256HexWeb, startDictation, startWorkerRuntimeHost };
|