@webskill/sdk 0.6.0 → 0.7.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/browser.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { A as parseSkillMarkdown, B as unzipWithLimits, H as verifyManifest, L as resolveInsideRoot, M as readResponseWithLimit, N as readSkillSignature, O as messageOf, T as isValidSkillName, U as verifySkillSignature, V as validateSkills, _ as atomicWriteText, f as SkillDiscovery, h as assertRemoteUrlAllowed, j as parseSkillPackManifest, m as WebSkillError, o as SKILLS_LOCKFILE, r as MANIFEST_EXCLUDED_FILES, s as SKILL_MANIFEST_FILE, u as SKILL_PACK_FILE, v as buildCatalog, w as exportSkills, y as buildManifest } from "./dist-8oQRa8Xz.js";
2
- import { H as createWebSkillApi, S as ProgressiveRouter, a as AgentLoop, at as normalizeToolContent, c as CapabilityApproval, et as mergeCatalogEntries, h as FsRunSnapshotStore, m as FsMemoryStore, nt as networkPolicyLibSource, o as AnthropicClient, ot as normalizeToolError, p as FsArtifactStore, st as parseBridgeRequest, x as OpenAiCompatibleClient, y as GoogleGenAiClient, z as bridgeError } from "./dist-DusANsrn.js";
2
+ import { B as bridgeError, C as ProgressiveRouter, S as OpenAiCompatibleClient, U as createWebSkillApi, a as AgentLoop, b as GoogleGenAiClient, c as CapabilityApproval, ct as normalizeToolError, g as FsRunSnapshotStore, h as FsMemoryStore, it as networkPolicyLibSource, lt as parseBridgeRequest, m as FsArtifactStore, nt as mergeCatalogEntries, o as AnthropicClient, st as normalizeToolContent } from "./dist-D0qW6e40.js";
3
3
  import { n as MockLlmClient } from "./testing-CYTFqkDm.js";
4
4
 
5
5
  //#region ../browser/dist/index.js
@@ -1615,8 +1615,20 @@ function describeError(e) {
1615
1615
  if (e instanceof TypeError) return "the request was blocked, most likely by CORS";
1616
1616
  return e instanceof Error ? e.message : String(e);
1617
1617
  }
1618
- /** 值可能是凭据的输入类型:即便宿主忘了写进 exclude 也不读值 */
1618
+ /** 值可能是凭据的输入类型:即便宿主忘了写进 exclude 也不读值。操作侧复用同一份,不另写 */
1619
1619
  const SECRET_INPUT_TYPES = /* @__PURE__ */ new Set(["password", "hidden"]);
1620
+ /** 可被操作的角色:只给这些节点发句柄,其余节点模型连提都提不出来 */
1621
+ const ACTIONABLE_ROLES = /* @__PURE__ */ new Set([
1622
+ "button",
1623
+ "checkbox",
1624
+ "combobox",
1625
+ "form",
1626
+ "link",
1627
+ "radio",
1628
+ "searchbox",
1629
+ "switch",
1630
+ "textbox"
1631
+ ]);
1620
1632
  /** tag → 可访问性角色的最小映射;命中不了就退到 generic,不猜 */
1621
1633
  const ROLE_BY_TAG = {
1622
1634
  A: "link",
@@ -1654,7 +1666,7 @@ const clip = (text) => {
1654
1666
  const normalized = text.replace(/\s+/g, " ").trim();
1655
1667
  return normalized.length > MAX_TEXT ? `${normalized.slice(0, MAX_TEXT)}…` : normalized;
1656
1668
  };
1657
- function roleOf(element) {
1669
+ function roleOf$1(element) {
1658
1670
  const explicit = element.getAttribute("role");
1659
1671
  if (explicit !== null && explicit.trim() !== "") return explicit.trim();
1660
1672
  if (element.tagName === "INPUT") {
@@ -1700,7 +1712,7 @@ function valueOf(element) {
1700
1712
  * 隐藏节点不进结果:屏幕上看不见的东西,用户没有机会发现它被读走了。
1701
1713
  * jsdom 里 `getComputedStyle` 可用但布局是假的,因此只看显式声明,不看尺寸。
1702
1714
  */
1703
- function hidden(element, view) {
1715
+ function hidden$1(element, view) {
1704
1716
  if (element.hasAttribute("hidden")) return true;
1705
1717
  if (element.getAttribute("aria-hidden") === "true") return true;
1706
1718
  const inlineStyle = element.getAttribute("style") ?? "";
@@ -1718,18 +1730,18 @@ const SKIPPED_TAGS = /* @__PURE__ */ new Set([
1718
1730
  "NOSCRIPT",
1719
1731
  "TEMPLATE"
1720
1732
  ]);
1721
- function describe(element, excluded, doc, view, targets) {
1733
+ function describe(element, excluded, doc, view, targets, handles) {
1722
1734
  if (excluded.has(element)) return void 0;
1723
1735
  if (SKIPPED_TAGS.has(element.tagName)) return void 0;
1724
- if (hidden(element, view)) return void 0;
1736
+ if (hidden$1(element, view)) return void 0;
1725
1737
  const children = [];
1726
1738
  for (const child of element.children) {
1727
- const node = describe(child, excluded, doc, view, targets);
1739
+ const node = describe(child, excluded, doc, view, targets, handles);
1728
1740
  if (node !== void 0) children.push(node);
1729
1741
  }
1730
1742
  const name = nameOf(element, doc);
1731
1743
  const value = valueOf(element);
1732
- const role = roleOf(element);
1744
+ const role = roleOf$1(element);
1733
1745
  if (role === "generic" && name === void 0 && value === void 0 && children.length === 0) return void 0;
1734
1746
  const node = {
1735
1747
  role,
@@ -1737,6 +1749,8 @@ function describe(element, excluded, doc, view, targets) {
1737
1749
  ...value !== void 0 ? { value } : {},
1738
1750
  ...children.length > 0 ? { children } : {}
1739
1751
  };
1752
+ const ref = handles?.issue(element, role);
1753
+ if (ref !== void 0) node.ref = ref;
1740
1754
  if (targets !== void 0 && isCapturableElement(element)) targets.push({
1741
1755
  element,
1742
1756
  node
@@ -1744,6 +1758,31 @@ function describe(element, excluded, doc, view, targets) {
1744
1758
  return node;
1745
1759
  }
1746
1760
  /**
1761
+ * 句柄发放与解析。表在 reader 实例内、每次 `read()` 整体替换:
1762
+ * 全局表是第二套状态且会跨会话泄漏;不替换则页面变了还能用旧句柄点到别的东西。
1763
+ */
1764
+ var HandleIssuer = class {
1765
+ #table = /* @__PURE__ */ new Map();
1766
+ #actionable;
1767
+ #excluded;
1768
+ constructor(actionable, excluded) {
1769
+ this.#actionable = actionable;
1770
+ this.#excluded = excluded;
1771
+ }
1772
+ issue(element, role) {
1773
+ if (!this.#actionable.has(element) || this.#excluded.has(element)) return void 0;
1774
+ if (!ACTIONABLE_ROLES.has(role)) return void 0;
1775
+ const bytes = /* @__PURE__ */ new Uint8Array(8);
1776
+ crypto.getRandomValues(bytes);
1777
+ const ref = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
1778
+ this.#table.set(ref, element);
1779
+ return ref;
1780
+ }
1781
+ get table() {
1782
+ return this.#table;
1783
+ }
1784
+ };
1785
+ /**
1747
1786
  * DOM 遍历实现(设计 09 §2 的「实现层」)。产出只有角色 / 名称 / 值,
1748
1787
  * **不含任何 HTML 标记、class、data 属性或注释**(FR-10.4)。
1749
1788
  *
@@ -1752,17 +1791,43 @@ function describe(element, excluded, doc, view, targets) {
1752
1791
  * @experimental
1753
1792
  */
1754
1793
  function createDomPerceptionReader(options = {}) {
1794
+ let handles;
1795
+ const resolveDocument = () => options.document ?? globalThis.document;
1796
+ /** 按当前 DOM 重算一次可操作集合;发句柄与执行前各算一次 */
1797
+ const actionSets = (doc) => {
1798
+ const actionable = /* @__PURE__ */ new Set();
1799
+ const excluded = /* @__PURE__ */ new Set();
1800
+ const scope = options.actionScope;
1801
+ if (scope === void 0 || scope.include.length === 0) return {
1802
+ actionable,
1803
+ excluded
1804
+ };
1805
+ for (const selector of scope.include) for (const root of doc.querySelectorAll(selector)) {
1806
+ actionable.add(root);
1807
+ for (const descendant of root.querySelectorAll("*")) actionable.add(descendant);
1808
+ }
1809
+ for (const selector of scope.exclude ?? []) for (const root of doc.querySelectorAll(selector)) {
1810
+ excluded.add(root);
1811
+ for (const descendant of root.querySelectorAll("*")) excluded.add(descendant);
1812
+ }
1813
+ return {
1814
+ actionable,
1815
+ excluded
1816
+ };
1817
+ };
1755
1818
  function collect(scope, targets) {
1756
- const doc = options.document ?? globalThis.document;
1819
+ const doc = resolveDocument();
1757
1820
  if (doc === void 0 || scope.include.length === 0) return [];
1758
1821
  const view = doc.defaultView;
1822
+ const sets = actionSets(doc);
1823
+ handles = new HandleIssuer(sets.actionable, sets.excluded);
1759
1824
  const excluded = /* @__PURE__ */ new Set();
1760
1825
  for (const selector of scope.exclude ?? []) for (const element of doc.querySelectorAll(selector)) excluded.add(element);
1761
1826
  const roots = [];
1762
1827
  for (const selector of scope.include) for (const element of doc.querySelectorAll(selector)) if (!roots.includes(element)) roots.push(element);
1763
1828
  const nodes = [];
1764
1829
  for (const root of roots) {
1765
- const node = describe(root, excluded, doc, view, targets);
1830
+ const node = describe(root, excluded, doc, view, targets, handles);
1766
1831
  if (node !== void 0) nodes.push(node);
1767
1832
  }
1768
1833
  return nodes;
@@ -1804,7 +1869,126 @@ function createDomPerceptionReader(options = {}) {
1804
1869
  if (!capture.images || capture.maxImages <= 0) return Promise.resolve({ nodes: collect(scope, void 0) });
1805
1870
  return readWithImages(scope, capture);
1806
1871
  }
1807
- return { read };
1872
+ return {
1873
+ read,
1874
+ resolve: (ref) => handles?.table.get(ref),
1875
+ inActionScope: (element) => {
1876
+ const doc = resolveDocument();
1877
+ if (doc === void 0) return false;
1878
+ const sets = actionSets(doc);
1879
+ return sets.actionable.has(element) && !sets.excluded.has(element);
1880
+ }
1881
+ };
1882
+ }
1883
+ const FILLABLE_TAGS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA"]);
1884
+ /** 只看显式声明,不看尺寸:jsdom 有 getComputedStyle 但没有布局 */
1885
+ function hidden(element) {
1886
+ if (element.hasAttribute("hidden")) return true;
1887
+ if (element.getAttribute("aria-hidden") === "true") return true;
1888
+ const inlineStyle = element.getAttribute("style") ?? "";
1889
+ if (/display\s*:\s*none|visibility\s*:\s*hidden/i.test(inlineStyle)) return true;
1890
+ const view = element.ownerDocument.defaultView;
1891
+ if (view !== null) {
1892
+ const computed = view.getComputedStyle(element);
1893
+ if (computed.display === "none" || computed.visibility === "hidden") return true;
1894
+ }
1895
+ return false;
1896
+ }
1897
+ const inputType = (element) => (element.getAttribute("type") ?? "text").toLowerCase();
1898
+ const isSecret = (element) => element.tagName === "INPUT" && SECRET_INPUT_TYPES.has(inputType(element));
1899
+ function accessibleName(element) {
1900
+ const label = element.getAttribute("aria-label");
1901
+ if (label !== null && label.trim() !== "") return label.trim();
1902
+ const text = element.textContent?.replace(/\s+/g, " ").trim() ?? "";
1903
+ if (text !== "") return text;
1904
+ const placeholder = element.getAttribute("placeholder");
1905
+ return placeholder !== null && placeholder.trim() !== "" ? placeholder.trim() : void 0;
1906
+ }
1907
+ function roleOf(element) {
1908
+ const explicit = element.getAttribute("role");
1909
+ if (explicit !== null && explicit.trim() !== "") return explicit.trim();
1910
+ if (element.tagName === "INPUT") {
1911
+ const type = inputType(element);
1912
+ return type === "checkbox" || type === "radio" ? type : type === "button" || type === "submit" ? "button" : "textbox";
1913
+ }
1914
+ if (element.tagName === "BUTTON") return "button";
1915
+ if (element.tagName === "A") return "link";
1916
+ if (element.tagName === "TEXTAREA") return "textbox";
1917
+ if (element.tagName === "SELECT") return "combobox";
1918
+ if (element.tagName === "FORM") return "form";
1919
+ return "generic";
1920
+ }
1921
+ const describeElement = (element) => {
1922
+ const name = accessibleName(element);
1923
+ return {
1924
+ role: roleOf(element),
1925
+ ...name !== void 0 ? { name } : {},
1926
+ ...isSecret(element) ? { secret: true } : {}
1927
+ };
1928
+ };
1929
+ /**
1930
+ * 受控组件必须收到 `input` / `change`:直接写 `.value` 在 React 上会被下一次渲染覆盖,
1931
+ * 表现为「填了又没了」。原生 setter 是绕过 React value 劫持的唯一办法。
1932
+ */
1933
+ function fillValue(element, value) {
1934
+ const prototype = element.tagName === "INPUT" ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
1935
+ const setter = Object.getOwnPropertyDescriptor(prototype, "value")?.set;
1936
+ if (setter) setter.call(element, value);
1937
+ else element.value = value;
1938
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1939
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1940
+ }
1941
+ /** @experimental */
1942
+ function createDomPageActionExecutor(options) {
1943
+ const { reader } = options;
1944
+ const describe = (ref) => {
1945
+ const element = reader.resolve(ref);
1946
+ return element === void 0 ? void 0 : describeElement(element);
1947
+ };
1948
+ const execute = (request) => {
1949
+ const element = reader.resolve(request.ref);
1950
+ if (element === void 0) return {
1951
+ ok: false,
1952
+ target: { role: "generic" },
1953
+ reason: "The element reference is unknown or expired."
1954
+ };
1955
+ const target = describeElement(element);
1956
+ const fail = (reason) => ({
1957
+ ok: false,
1958
+ target,
1959
+ reason
1960
+ });
1961
+ if (!reader.inActionScope(element)) return fail("The element is no longer inside the actionable scope.");
1962
+ if (hidden(element)) return fail("The element is not visible.");
1963
+ if (element.hasAttribute("disabled")) return fail("The element is disabled.");
1964
+ if (request.action === "click") {
1965
+ element.click();
1966
+ return {
1967
+ ok: true,
1968
+ target
1969
+ };
1970
+ }
1971
+ if (request.action === "fill") {
1972
+ if (!FILLABLE_TAGS.has(element.tagName)) return fail("The element is not a text control.");
1973
+ if (element.hasAttribute("readonly")) return fail("The element is read-only.");
1974
+ fillValue(element, request.value ?? "");
1975
+ return {
1976
+ ok: true,
1977
+ target
1978
+ };
1979
+ }
1980
+ const form = element.tagName === "FORM" ? element : element.form;
1981
+ if (!form) return fail("The element does not belong to a form.");
1982
+ form.requestSubmit();
1983
+ return {
1984
+ ok: true,
1985
+ target
1986
+ };
1987
+ };
1988
+ return {
1989
+ execute,
1990
+ describe
1991
+ };
1808
1992
  }
1809
1993
  const isRecord = (v) => typeof v === "object" && v !== null;
1810
1994
  const isNonEmptyString = (v) => typeof v === "string" && v !== "";
@@ -2488,4 +2672,4 @@ async function deleteMemoryEncryptionKey(name = "user-profile") {
2488
2672
  }
2489
2673
 
2490
2674
  //#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 };
2675
+ export { BrowserSkillManager, BrowserWorkerScriptExecutor, ChromeBuiltinLlmClient, IframeWorkerLike, OpfsProvider, TsTranspiler, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, bridgeError, captureElementImage, checkDictationAvailability, compressImageToBudget, createDomPageActionExecutor, createDomPerceptionReader, createEncryptedMemoryStore, createIframeWorker, createLlmClient, deleteMemoryEncryptionKey, extractZipWeb, generateMemoryEncryptionKey, installWebSkillNavigator, isCapturableElement, isCaptureFailure, isEncryptedMemoryValue, isOpfsAvailable, openMemoryEncryptionKey, parseBridgeRequest, parseMainMessage, parseWorkerEvent, probeChromeBuiltinAvailability, sha256HexWeb, startDictation, startWorkerRuntimeHost };