@webskill/sdk 0.15.0 → 0.16.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 +3 -3
- package/dist/agent.js +466 -66
- package/dist/browser.d.ts +140 -7
- package/dist/browser.js +326 -33
- package/dist/{eventTypes-Y07N8IAC.js → eventTypes-CwciaqCU.js} +23 -0
- package/dist/governance.d.ts +26 -3
- package/dist/governance.js +1 -1
- package/dist/{index-CQYo4tcT.d.ts → index-BLfnO7sz.d.ts} +324 -16
- package/dist/{index-Oc3H89TJ.d.ts → index-DbVqg2V3.d.ts} +1 -1
- package/dist/{index-CU2md8R1.d.ts → index-DeXgdtHa.d.ts} +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/{miniChart-owdCdaw-.js → miniChart-lauNtZu8.js} +2 -7
- package/dist/node.d.ts +3 -3
- package/dist/node.js +1 -1
- package/dist/{openUiLibrary-CY_ADoe0.js → openUiLibrary-BWEBU9P-.js} +1 -1
- package/dist/{openUiSpecLang-DuOCdw9G.js → openUiSpecLang-BIK6CWlW.js} +1 -1
- package/dist/{skillVersionStore-B24Jjjry-C4zomhMZ.d.ts → skillVersionStore-B24Jjjry-CsbzF7Oe.d.ts} +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{types-TUydnxaj.js → types-BuacBov_.js} +14 -3
- package/dist/{types-C3V6lAeO-BIf7UB2e.d.ts → types-C3V6lAeO-BUpcRLER.d.ts} +11 -0
- package/dist/ui-react.d.ts +9 -3
- package/dist/ui-react.js +15 -4
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui-vue.js +2 -2
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +4 -4
- package/dist/{webskillLitCatalog-DSqVFQjK.js → webskillLitCatalog-poL1qhBp.js} +2 -2
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -6,7 +6,7 @@ import { a as networkPolicyLibSource, c as bridgeError, d as FsMemoryStore, g as
|
|
|
6
6
|
import { a as GoogleGenAiClient, o as AnthropicClient, s as OpenAiCompatibleClient, t as MockLlmClient } from "./llm-B7lLH0ZI.js";
|
|
7
7
|
import { n as normalizeToolContent, t as mergeCatalogEntries } from "./external-_ZRQe-V9.js";
|
|
8
8
|
import { t as createWebSkillApi } from "./webSkillApi-DOOyz2G5.js";
|
|
9
|
-
import { a as
|
|
9
|
+
import { a as frameLabel, i as toFrameScopes, o as frameSteps, r as toActionFrameScopes } from "./types-BuacBov_.js";
|
|
10
10
|
|
|
11
11
|
//#region ../browser/src/fs/featureDetection.ts
|
|
12
12
|
/** 检测当前环境是否可用 OPFS(navigator.storage.getDirectory) */
|
|
@@ -2281,7 +2281,7 @@ function promoteRole(input) {
|
|
|
2281
2281
|
//#region ../browser/src/dom/perception.ts
|
|
2282
2282
|
/** 值可能是凭据的输入类型:即便宿主忘了写进 exclude 也不读值。操作侧复用同一份,不另写 */
|
|
2283
2283
|
const SECRET_INPUT_TYPES = /* @__PURE__ */ new Set(["password", "hidden"]);
|
|
2284
|
-
/**
|
|
2284
|
+
/** 可被操作的角色:只有这些节点的句柄能拿去写页面 */
|
|
2285
2285
|
const ACTIONABLE_ROLES = /* @__PURE__ */ new Set([
|
|
2286
2286
|
"button",
|
|
2287
2287
|
"checkbox",
|
|
@@ -2293,9 +2293,34 @@ const ACTIONABLE_ROLES = /* @__PURE__ */ new Set([
|
|
|
2293
2293
|
"switch",
|
|
2294
2294
|
"textbox"
|
|
2295
2295
|
]);
|
|
2296
|
+
/**
|
|
2297
|
+
* 只能做子树锚点的容器角色(分册 14 §6.1)。
|
|
2298
|
+
*
|
|
2299
|
+
* 这些节点拿到的是“读”句柄:`perceive_page(ref)` 能以它为根续读,
|
|
2300
|
+
* `act_on_page` 拿到它则一律被拒。不取 generic:那等于给每个 div 发句柄。
|
|
2301
|
+
*/
|
|
2302
|
+
const ANCHOR_ROLES = /* @__PURE__ */ new Set([
|
|
2303
|
+
"article",
|
|
2304
|
+
"dialog",
|
|
2305
|
+
"feed",
|
|
2306
|
+
"grid",
|
|
2307
|
+
"group",
|
|
2308
|
+
"list",
|
|
2309
|
+
"listitem",
|
|
2310
|
+
"main",
|
|
2311
|
+
"navigation",
|
|
2312
|
+
"region",
|
|
2313
|
+
"row",
|
|
2314
|
+
"rowgroup",
|
|
2315
|
+
"table",
|
|
2316
|
+
"tabpanel",
|
|
2317
|
+
"tree",
|
|
2318
|
+
"treeitem"
|
|
2319
|
+
]);
|
|
2296
2320
|
/** tag → 可访问性角色的最小映射;命中不了就退到 generic,不猜 */
|
|
2297
2321
|
const ROLE_BY_TAG = {
|
|
2298
2322
|
A: "link",
|
|
2323
|
+
ARTICLE: "article",
|
|
2299
2324
|
BUTTON: "button",
|
|
2300
2325
|
CANVAS: "img",
|
|
2301
2326
|
H1: "heading",
|
|
@@ -2469,7 +2494,7 @@ function describe(element, excluded, doc, view, capture, handles, frame, provena
|
|
|
2469
2494
|
element,
|
|
2470
2495
|
role,
|
|
2471
2496
|
doc,
|
|
2472
|
-
hasActionableDescendant: children.some((child) => child.ref !== void 0),
|
|
2497
|
+
hasActionableDescendant: children.some((child) => child.ref !== void 0 && handles?.isAction(child.ref) === true),
|
|
2473
2498
|
context: promotion
|
|
2474
2499
|
});
|
|
2475
2500
|
if (promoted !== void 0) {
|
|
@@ -2602,11 +2627,13 @@ var HandleIssuer = class {
|
|
|
2602
2627
|
}
|
|
2603
2628
|
issue(element, role, frame) {
|
|
2604
2629
|
if (!this.#actionable.has(element) || this.#excluded.has(element)) return void 0;
|
|
2605
|
-
|
|
2630
|
+
const kind = ACTIONABLE_ROLES.has(role) ? "action" : ANCHOR_ROLES.has(role) ? "anchor" : void 0;
|
|
2631
|
+
if (kind === void 0) return void 0;
|
|
2606
2632
|
const known = this.#refByElement.get(element);
|
|
2607
2633
|
if (known !== void 0 && this.#table.has(known)) {
|
|
2608
2634
|
this.#table.set(known, {
|
|
2609
2635
|
element,
|
|
2636
|
+
kind,
|
|
2610
2637
|
...frame !== void 0 ? { frame } : {}
|
|
2611
2638
|
});
|
|
2612
2639
|
return known;
|
|
@@ -2616,11 +2643,16 @@ var HandleIssuer = class {
|
|
|
2616
2643
|
const ref = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2617
2644
|
this.#table.set(ref, {
|
|
2618
2645
|
element,
|
|
2646
|
+
kind,
|
|
2619
2647
|
...frame !== void 0 ? { frame } : {}
|
|
2620
2648
|
});
|
|
2621
2649
|
this.#refByElement.set(element, ref);
|
|
2622
2650
|
return ref;
|
|
2623
2651
|
}
|
|
2652
|
+
/** 角色提升要的是「有可操作后代」,容器锚点不算;混进来会把可点容器判成已有出口 */
|
|
2653
|
+
isAction(ref) {
|
|
2654
|
+
return this.#table.get(ref)?.kind === "action";
|
|
2655
|
+
}
|
|
2624
2656
|
get table() {
|
|
2625
2657
|
return this.#table;
|
|
2626
2658
|
}
|
|
@@ -2721,12 +2753,20 @@ function createDomPerceptionReader(options = {}) {
|
|
|
2721
2753
|
};
|
|
2722
2754
|
/** 该帧的操作范围;没有对应条目就是「这个帧只可读不可操作」。按展示串匹配(FR-18.2) */
|
|
2723
2755
|
const actionScopeFor = (label) => options.actionScope === void 0 ? void 0 : toActionFrameScopes(options.actionScope).find((entry) => frameLabel(entry.frame) === label);
|
|
2724
|
-
|
|
2756
|
+
/**
|
|
2757
|
+
* @param subtree 不为 `undefined` 时只读这一棵子树(分册 14)。
|
|
2758
|
+
* 它**不是一个新的范围来源**:子树必须先落在某条帧授权的 include 根下、
|
|
2759
|
+
* 且未被 exclude 命中,否则遍历**根本不开始**(`anchored: false`)。
|
|
2760
|
+
* 命中时也只是把那一帧的 `roots` 换成这一个元素,遍历器与 exclude 判定一字未改——
|
|
2761
|
+
* 「带 `ref` 的结果是不带它时的子集」因此是个结构事实(FR-14.2)。
|
|
2762
|
+
*/
|
|
2763
|
+
function collect(scope, capture, subtree) {
|
|
2725
2764
|
const root = rootDocument();
|
|
2726
2765
|
if (root === void 0) return {
|
|
2727
2766
|
nodes: [],
|
|
2728
2767
|
frameNotes: [],
|
|
2729
|
-
nestedFrames: []
|
|
2768
|
+
nestedFrames: [],
|
|
2769
|
+
anchored: false
|
|
2730
2770
|
};
|
|
2731
2771
|
handles ??= new HandleIssuer(/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
|
|
2732
2772
|
handles.prune();
|
|
@@ -2818,6 +2858,7 @@ function createDomPerceptionReader(options = {}) {
|
|
|
2818
2858
|
issuer.useSets(actionable, actionExcluded);
|
|
2819
2859
|
return node !== void 0;
|
|
2820
2860
|
}
|
|
2861
|
+
let anchored = subtree === void 0;
|
|
2821
2862
|
for (const { scope: frameScope, label, doc } of resolvedScopes) {
|
|
2822
2863
|
const view = doc.defaultView;
|
|
2823
2864
|
const tag = label === "self" ? void 0 : label;
|
|
@@ -2825,14 +2866,27 @@ function createDomPerceptionReader(options = {}) {
|
|
|
2825
2866
|
issuer.useSets(sets.actionable, sets.excluded);
|
|
2826
2867
|
const excluded = /* @__PURE__ */ new Set();
|
|
2827
2868
|
for (const selector of frameScope.exclude ?? []) for (const element of doc.querySelectorAll(selector)) excluded.add(element);
|
|
2828
|
-
|
|
2869
|
+
let roots = [];
|
|
2829
2870
|
for (const selector of frameScope.include) for (const element of doc.querySelectorAll(selector)) if (!roots.includes(element)) roots.push(element);
|
|
2871
|
+
if (subtree !== void 0) {
|
|
2872
|
+
const inScope = roots.some((element) => element === subtree || element.contains(subtree));
|
|
2873
|
+
const cut = [...excluded].some((element) => element === subtree || element.contains(subtree));
|
|
2874
|
+
if (!inScope || cut) continue;
|
|
2875
|
+
roots = [subtree];
|
|
2876
|
+
anchored = true;
|
|
2877
|
+
}
|
|
2830
2878
|
for (const element of roots) {
|
|
2831
2879
|
const node = describe(element, excluded, doc, view, capture, issuer, tag, void 0, promotionOf(view));
|
|
2832
2880
|
if (node !== void 0) nodes.push(node);
|
|
2833
2881
|
}
|
|
2834
2882
|
descendFrames(roots, doc, excluded, frameScope, label, tag, sets.actionable, sets.excluded);
|
|
2835
2883
|
}
|
|
2884
|
+
if (subtree !== void 0) return {
|
|
2885
|
+
nodes,
|
|
2886
|
+
frameNotes,
|
|
2887
|
+
nestedFrames,
|
|
2888
|
+
anchored
|
|
2889
|
+
};
|
|
2836
2890
|
pruneElevated();
|
|
2837
2891
|
const view = root.defaultView;
|
|
2838
2892
|
const sets = actionSets(root, actionScopeFor("self"));
|
|
@@ -2852,15 +2906,16 @@ function createDomPerceptionReader(options = {}) {
|
|
|
2852
2906
|
return {
|
|
2853
2907
|
nodes,
|
|
2854
2908
|
frameNotes,
|
|
2855
|
-
nestedFrames
|
|
2909
|
+
nestedFrames,
|
|
2910
|
+
anchored
|
|
2856
2911
|
};
|
|
2857
2912
|
}
|
|
2858
|
-
async function readWithImages(scope, capture) {
|
|
2913
|
+
async function readWithImages(scope, capture, subtree) {
|
|
2859
2914
|
const targets = [];
|
|
2860
|
-
const { nodes, frameNotes, nestedFrames } = collect(scope, {
|
|
2915
|
+
const { nodes, frameNotes, nestedFrames, anchored } = collect(scope, {
|
|
2861
2916
|
targets,
|
|
2862
2917
|
minImageArea: capture.minImageArea ?? 0
|
|
2863
|
-
});
|
|
2918
|
+
}, subtree);
|
|
2864
2919
|
const selected = targets.slice(0, capture.maxImages);
|
|
2865
2920
|
const images = [];
|
|
2866
2921
|
let imageFailures = 0;
|
|
@@ -2888,6 +2943,7 @@ function createDomPerceptionReader(options = {}) {
|
|
|
2888
2943
|
images,
|
|
2889
2944
|
imagesOmitted: targets.length - selected.length,
|
|
2890
2945
|
imageFailures,
|
|
2946
|
+
anchored,
|
|
2891
2947
|
...frameNotes.length > 0 ? { frameNotes } : {},
|
|
2892
2948
|
...nestedFrames.length > 0 ? { nestedFrames } : {}
|
|
2893
2949
|
};
|
|
@@ -2904,6 +2960,28 @@ function createDomPerceptionReader(options = {}) {
|
|
|
2904
2960
|
}
|
|
2905
2961
|
return readWithImages(scope, capture);
|
|
2906
2962
|
}
|
|
2963
|
+
/**
|
|
2964
|
+
* 子树续读(分册 14 FR-14.1)。三条判定全在遍历开始**之前**:
|
|
2965
|
+
* 句柄不认识 → `unknown-ref`;解析得到的元素已离开文档、
|
|
2966
|
+
* 或不在任何一条帧授权的 include 根下、或被 exclude 连根剪掉 → `out-of-scope`。
|
|
2967
|
+
*
|
|
2968
|
+
* 拒绝走返回值而不是抛错:跨上下文的 reject 到本地就是一个没有错误码的 `Error`。
|
|
2969
|
+
*/
|
|
2970
|
+
async function readSubtree(scope, ref, capture) {
|
|
2971
|
+
const element = handles?.table.get(ref)?.element;
|
|
2972
|
+
if (element === void 0 || !element.isConnected) return { rejected: "unknown-ref" };
|
|
2973
|
+
if (capture?.images === true && capture.maxImages > 0) {
|
|
2974
|
+
const { anchored, ...result } = await readWithImages(scope, capture, element);
|
|
2975
|
+
return anchored ? result : { rejected: "out-of-scope" };
|
|
2976
|
+
}
|
|
2977
|
+
const { nodes, frameNotes, nestedFrames, anchored } = collect(scope, void 0, element);
|
|
2978
|
+
if (!anchored) return { rejected: "out-of-scope" };
|
|
2979
|
+
return {
|
|
2980
|
+
nodes,
|
|
2981
|
+
...frameNotes.length > 0 ? { frameNotes } : {},
|
|
2982
|
+
...nestedFrames.length > 0 ? { nestedFrames } : {}
|
|
2983
|
+
};
|
|
2984
|
+
}
|
|
2907
2985
|
/** 当前活动模态;跨帧不适用(模态提升只在主文档语义下成立) */
|
|
2908
2986
|
function modalSnapshot() {
|
|
2909
2987
|
const root = rootDocument();
|
|
@@ -2916,7 +2994,9 @@ function createDomPerceptionReader(options = {}) {
|
|
|
2916
2994
|
}
|
|
2917
2995
|
return {
|
|
2918
2996
|
read,
|
|
2997
|
+
readSubtree,
|
|
2919
2998
|
resolve: (ref) => handles?.table.get(ref)?.element,
|
|
2999
|
+
handleKindOf: (ref) => handles?.table.get(ref)?.kind,
|
|
2920
3000
|
frameOf: (ref) => handles?.table.get(ref)?.frame,
|
|
2921
3001
|
modalSnapshot,
|
|
2922
3002
|
elevateNewModals: (before) => {
|
|
@@ -2960,6 +3040,35 @@ function createDomPerceptionReader(options = {}) {
|
|
|
2960
3040
|
}
|
|
2961
3041
|
}
|
|
2962
3042
|
|
|
3043
|
+
//#endregion
|
|
3044
|
+
//#region ../browser/src/remote/documentIdentity.ts
|
|
3045
|
+
/**
|
|
3046
|
+
* 文档标识(0.16.0 分册 11 · FR-11.1)。
|
|
3047
|
+
*
|
|
3048
|
+
* 它有且只有两个消费者,且**必须是同一个函数**:
|
|
3049
|
+
* ① 感知回复里的 `document.key`,决定这批句柄进哪个分片;
|
|
3050
|
+
* ② `PageActionOutcome.navigated`,执行前后各取一次。
|
|
3051
|
+
* 两处各写一套判定,就会出现「操作说导航了、分片说没换」的不一致(AC-11.4 / AC-13.4 验的就是这个)。
|
|
3052
|
+
*/
|
|
3053
|
+
/** 每个文档实例一次。页面重载即换,因此真导航天然换 key */
|
|
3054
|
+
const instance = (() => {
|
|
3055
|
+
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
3056
|
+
crypto.getRandomValues(bytes);
|
|
3057
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3058
|
+
})();
|
|
3059
|
+
/**
|
|
3060
|
+
* `instance` + 当前地址。真导航换 `instance`,帧内路由(SPA)换 `href`,
|
|
3061
|
+
* 两类都被这一个串覆盖——靠 origin 比对识别不出后者。
|
|
3062
|
+
*
|
|
3063
|
+
* `href` 含 hash 是有意的:纯 hash 跳转在很多站点上会换掉整块内容,
|
|
3064
|
+
* 判成换文档是安全侧的选择(代价是多感知一次;漏判没有便宜的补救)。
|
|
3065
|
+
* @experimental
|
|
3066
|
+
*/
|
|
3067
|
+
function documentKey(view) {
|
|
3068
|
+
const href = view?.location.href;
|
|
3069
|
+
return href === void 0 ? void 0 : `${instance}\u0000${href}`;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
2963
3072
|
//#endregion
|
|
2964
3073
|
//#region ../browser/src/dom/pageAction.ts
|
|
2965
3074
|
const FILLABLE_TAGS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA"]);
|
|
@@ -3044,6 +3153,54 @@ function fillValue(element, value) {
|
|
|
3044
3153
|
element.dispatchEvent(new Event("change", { bubbles: true }));
|
|
3045
3154
|
}
|
|
3046
3155
|
const norm = (text) => (text ?? "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
3156
|
+
/**
|
|
3157
|
+
* 一次「像人一样」的点击。
|
|
3158
|
+
*
|
|
3159
|
+
* `HTMLElement.click()` **只**派发 `click`,不派发指针/鼠标序列,也不移动焦点。
|
|
3160
|
+
* 大量自研下拉、弹层、菜单是在 `pointerdown` / `mousedown` 上开的(这样拖拽和
|
|
3161
|
+
* 「点外面关闭」才好写),于是模型「点了但什么都没发生」,人手点却正常——
|
|
3162
|
+
* 差别不在权限,在事件序列。真实用户点击本来就会先后产生 down 与 click 两拨事件,
|
|
3163
|
+
* 因此补齐序列不会造成「点两下」。
|
|
3164
|
+
*
|
|
3165
|
+
* 激活行为仍交给 `.click()`:链接跳转、复选框翻转这些默认动作由它保证。
|
|
3166
|
+
*/
|
|
3167
|
+
function press(element) {
|
|
3168
|
+
const view = element.ownerDocument.defaultView;
|
|
3169
|
+
const rect = element.getBoundingClientRect();
|
|
3170
|
+
const base = {
|
|
3171
|
+
bubbles: true,
|
|
3172
|
+
cancelable: true,
|
|
3173
|
+
composed: true,
|
|
3174
|
+
detail: 1,
|
|
3175
|
+
button: 0,
|
|
3176
|
+
clientX: rect.left + rect.width / 2,
|
|
3177
|
+
clientY: rect.top + rect.height / 2
|
|
3178
|
+
};
|
|
3179
|
+
const pointer = {
|
|
3180
|
+
...base,
|
|
3181
|
+
pointerId: 1,
|
|
3182
|
+
pointerType: "mouse",
|
|
3183
|
+
isPrimary: true
|
|
3184
|
+
};
|
|
3185
|
+
const Pointer = view?.PointerEvent;
|
|
3186
|
+
if (Pointer !== void 0) element.dispatchEvent(new Pointer("pointerdown", {
|
|
3187
|
+
...pointer,
|
|
3188
|
+
buttons: 1
|
|
3189
|
+
}));
|
|
3190
|
+
if (element.dispatchEvent(new MouseEvent("mousedown", {
|
|
3191
|
+
...base,
|
|
3192
|
+
buttons: 1
|
|
3193
|
+
}))) element.focus?.();
|
|
3194
|
+
if (Pointer !== void 0) element.dispatchEvent(new Pointer("pointerup", {
|
|
3195
|
+
...pointer,
|
|
3196
|
+
buttons: 0
|
|
3197
|
+
}));
|
|
3198
|
+
element.dispatchEvent(new MouseEvent("mouseup", {
|
|
3199
|
+
...base,
|
|
3200
|
+
buttons: 0
|
|
3201
|
+
}));
|
|
3202
|
+
element.click();
|
|
3203
|
+
}
|
|
3047
3204
|
/** 轮询等待,上限固定:等不到就失败,不能无限挂着一次工具调用 */
|
|
3048
3205
|
async function waitFor(probe, timeoutMs = 2e3) {
|
|
3049
3206
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -3082,7 +3239,7 @@ async function selectOption(element, value) {
|
|
|
3082
3239
|
} : fail(`The control did not accept "${value}".`);
|
|
3083
3240
|
}
|
|
3084
3241
|
const doc = element.ownerDocument;
|
|
3085
|
-
element
|
|
3242
|
+
press(element);
|
|
3086
3243
|
const listbox = await waitFor(() => doc.querySelector("[role=listbox], [role=grid], [role=menu]") ?? void 0);
|
|
3087
3244
|
if (listbox === null || listbox === void 0) return fail("The options panel did not open.");
|
|
3088
3245
|
const option = [...listbox.querySelectorAll("[role=option], [role=gridcell], [role=menuitem], option")].find((candidate) => norm(accessibleName(candidate)) === norm(value));
|
|
@@ -3120,7 +3277,7 @@ function setToggle(element, value, target) {
|
|
|
3120
3277
|
target,
|
|
3121
3278
|
noop: true
|
|
3122
3279
|
};
|
|
3123
|
-
element
|
|
3280
|
+
press(element);
|
|
3124
3281
|
return {
|
|
3125
3282
|
ok: true,
|
|
3126
3283
|
target
|
|
@@ -3163,7 +3320,44 @@ function createDomPageActionExecutor(options) {
|
|
|
3163
3320
|
elevated: true
|
|
3164
3321
|
} : described;
|
|
3165
3322
|
};
|
|
3323
|
+
/**
|
|
3324
|
+
* 后退(0.16.0 分册 13 / FR-13.6)。
|
|
3325
|
+
*
|
|
3326
|
+
* 两种「没退成」都返回 `ok: true, noop: true` 而不是失败:
|
|
3327
|
+
* 返回失败模型会重试,而重试同样退不动——它需要的是「这条路走不通」的事实,
|
|
3328
|
+
* 不是一个可以再试一次的错误(与 `set` 的 noop 同惯例)。
|
|
3329
|
+
*/
|
|
3330
|
+
const goBack = async () => {
|
|
3331
|
+
const view = (options.document ?? globalThis.document)?.defaultView;
|
|
3332
|
+
const target = { role: "document" };
|
|
3333
|
+
if (view === void 0 || view === null) return {
|
|
3334
|
+
ok: false,
|
|
3335
|
+
target,
|
|
3336
|
+
reason: "No browsing context is available."
|
|
3337
|
+
};
|
|
3338
|
+
if (view.history.length <= 1) return {
|
|
3339
|
+
ok: true,
|
|
3340
|
+
target,
|
|
3341
|
+
noop: true
|
|
3342
|
+
};
|
|
3343
|
+
const keyBefore = documentKey(view);
|
|
3344
|
+
view.history.back();
|
|
3345
|
+
return await waitFor(() => {
|
|
3346
|
+
const key = documentKey(view);
|
|
3347
|
+
return key !== void 0 && key !== keyBefore ? key : void 0;
|
|
3348
|
+
}, 500) === void 0 ? {
|
|
3349
|
+
ok: true,
|
|
3350
|
+
target,
|
|
3351
|
+
noop: true
|
|
3352
|
+
} : {
|
|
3353
|
+
ok: true,
|
|
3354
|
+
target,
|
|
3355
|
+
navigated: true,
|
|
3356
|
+
documentUrl: view.location.href
|
|
3357
|
+
};
|
|
3358
|
+
};
|
|
3166
3359
|
const execute = async (request) => {
|
|
3360
|
+
if (request.action === "back") return await goBack();
|
|
3167
3361
|
const element = reader.resolve(request.ref);
|
|
3168
3362
|
if (element === void 0) return {
|
|
3169
3363
|
ok: false,
|
|
@@ -3176,36 +3370,37 @@ function createDomPageActionExecutor(options) {
|
|
|
3176
3370
|
target,
|
|
3177
3371
|
reason
|
|
3178
3372
|
});
|
|
3373
|
+
if (reader.handleKindOf(request.ref) === "anchor") return fail("That reference points at a container, which cannot be acted on. Use it with perceive_page to read inside it, then act on an element found there.");
|
|
3179
3374
|
if (reader.modalStateOf(element) === "unelevated") return fail("That dialog is not in the authorized scope because it was opened manually. Ask me to open it, or add it to the host allowlist.");
|
|
3180
3375
|
if (reader.modalStateOf(element) !== "elevated" && !reader.inActionScope(element)) return fail("The element is no longer inside the actionable scope.");
|
|
3181
3376
|
if (hidden(element)) return fail("The element is not visible.");
|
|
3182
3377
|
if (element.hasAttribute("disabled")) return fail("The element is disabled.");
|
|
3183
3378
|
const modalsBefore = reader.modalSnapshot();
|
|
3184
3379
|
const frameView = element.ownerDocument.defaultView;
|
|
3185
|
-
const
|
|
3380
|
+
const keyBefore = documentKey(frameView);
|
|
3186
3381
|
const done = async (outcome) => {
|
|
3187
3382
|
if (!outcome.ok) return outcome;
|
|
3188
3383
|
const settled = await waitFor(() => {
|
|
3189
3384
|
const modal = reader.elevateNewModals(modalsBefore);
|
|
3190
3385
|
if (modal !== void 0) return { modal };
|
|
3191
|
-
const
|
|
3192
|
-
return
|
|
3386
|
+
const key = documentKey(frameView);
|
|
3387
|
+
return key !== void 0 && key !== keyBefore ? { key } : void 0;
|
|
3193
3388
|
}, 500);
|
|
3194
3389
|
const elevated = settled !== void 0 && "modal" in settled ? settled.modal : reader.elevateNewModals(modalsBefore);
|
|
3195
3390
|
const name = elevated === void 0 ? void 0 : accessibleName(elevated) ?? "dialog";
|
|
3196
|
-
const
|
|
3197
|
-
const navigated =
|
|
3391
|
+
const keyAfter = documentKey(frameView);
|
|
3392
|
+
const navigated = keyAfter !== void 0 && keyBefore !== void 0 && keyAfter !== keyBefore;
|
|
3198
3393
|
return {
|
|
3199
3394
|
...outcome,
|
|
3200
3395
|
...name !== void 0 ? { elevatedModal: name } : {},
|
|
3201
3396
|
...navigated ? {
|
|
3202
3397
|
navigated: true,
|
|
3203
|
-
documentUrl:
|
|
3398
|
+
documentUrl: frameView?.location.href
|
|
3204
3399
|
} : {}
|
|
3205
3400
|
};
|
|
3206
3401
|
};
|
|
3207
3402
|
if (request.action === "click") {
|
|
3208
|
-
element
|
|
3403
|
+
press(element);
|
|
3209
3404
|
return await done({
|
|
3210
3405
|
ok: true,
|
|
3211
3406
|
target
|
|
@@ -3262,35 +3457,112 @@ function createDomPageActionExecutor(options) {
|
|
|
3262
3457
|
|
|
3263
3458
|
//#endregion
|
|
3264
3459
|
//#region ../browser/src/remote/registry.ts
|
|
3460
|
+
/**
|
|
3461
|
+
* 分片数的防泄漏软上限(分册 11 / DV-2)。
|
|
3462
|
+
*
|
|
3463
|
+
* 它**不是** FR-11.5 那个容量闸门:那一条管的是「同时有几个**标签页**可操作」,
|
|
3464
|
+
* 触顶硬拒绝,落在 `TabWorksetPolicy`。这里管的是同一个标签页里连续跳页攒下的历史分片,
|
|
3465
|
+
* 那些本来就该失效,按最久未使用逐出即可,逐出不构成对模型的拒绝。
|
|
3466
|
+
*/
|
|
3467
|
+
const SHARD_SOFT_LIMIT = 24;
|
|
3468
|
+
/** 不给 `shard` 时用的分片键;单分片宿主全程待在这一个分片里 */
|
|
3469
|
+
const DEFAULT_SHARD = "";
|
|
3470
|
+
/**
|
|
3471
|
+
* 把解析结果翻译成给模型看的补充说明(分册 11 / FR-11.6);
|
|
3472
|
+
* 接到 `PageActionPolicyOptions.diagnose` 上即可:
|
|
3473
|
+
*
|
|
3474
|
+
* ```ts
|
|
3475
|
+
* diagnose: (ref) => explainResolution(targets.resolve(ref))
|
|
3476
|
+
* ```
|
|
3477
|
+
*
|
|
3478
|
+
* `unknown` 返回 `undefined` 而不是自己拼一句:让 policy 落回它的缺省文案,
|
|
3479
|
+
* 于是 AC-11.3 要求的「与 0.15.0 逐字相同」是**构造性**成立的,
|
|
3480
|
+
* 而不是靠两个文件里的字符串抄得一样。
|
|
3481
|
+
* @experimental
|
|
3482
|
+
*/
|
|
3483
|
+
function explainResolution(resolution) {
|
|
3484
|
+
if (resolution.status === "unfocused") return "This reference belongs to a different page. Switch to that page first, then retry.";
|
|
3485
|
+
if (resolution.status === "expired") return "The page this reference belongs to has changed or was closed. Perceive the page again and retry.";
|
|
3486
|
+
}
|
|
3265
3487
|
/** @experimental */
|
|
3266
|
-
function createRemoteTargetRegistry() {
|
|
3267
|
-
|
|
3488
|
+
function createRemoteTargetRegistry(options = {}) {
|
|
3489
|
+
const maxShards = Math.max(1, Math.trunc(options.maxShards ?? 24));
|
|
3490
|
+
let shards = /* @__PURE__ */ new Map();
|
|
3491
|
+
let refIndex = /* @__PURE__ */ new Map();
|
|
3492
|
+
let focused = DEFAULT_SHARD;
|
|
3493
|
+
const touch = (key, shard) => {
|
|
3494
|
+
shards.delete(key);
|
|
3495
|
+
shards.set(key, shard);
|
|
3496
|
+
while (shards.size > maxShards) {
|
|
3497
|
+
const oldest = shards.keys().next();
|
|
3498
|
+
if (oldest.done === true) break;
|
|
3499
|
+
shards.delete(oldest.value);
|
|
3500
|
+
}
|
|
3501
|
+
};
|
|
3268
3502
|
return {
|
|
3269
3503
|
/**
|
|
3270
3504
|
* 整表替换而非合并(FR-13.2):合并会让上一次感知的 ref 在本次感知后仍然可执行——
|
|
3271
3505
|
* 那正是 `PAGE_ACTION_STALE_REF` 要拦的东西。本地形态里旧 ref 天然失效
|
|
3272
3506
|
* (DOM 重新遍历、句柄表 prune 掉离开文档的条目),远端必须手动复现这个失效语义,
|
|
3273
3507
|
* 否则远端比本地更松。
|
|
3508
|
+
*
|
|
3509
|
+
* 0.16.0 起替换的是**该分片**的表,别的分片不受影响——这是相对 0.15.0 的唯一语义变化。
|
|
3274
3510
|
*/
|
|
3275
|
-
replace(entries) {
|
|
3276
|
-
const
|
|
3511
|
+
replace(entries, document) {
|
|
3512
|
+
const shard = document?.key ?? DEFAULT_SHARD;
|
|
3513
|
+
const table = /* @__PURE__ */ new Map();
|
|
3277
3514
|
for (const entry of entries) {
|
|
3278
3515
|
const { ref, role, name, secret, frame, elevated } = entry;
|
|
3279
|
-
|
|
3516
|
+
table.set(ref, {
|
|
3280
3517
|
role,
|
|
3281
3518
|
...name !== void 0 ? { name } : {},
|
|
3282
3519
|
...secret === true ? { secret: true } : {},
|
|
3283
3520
|
...frame !== void 0 ? { frame } : {},
|
|
3284
3521
|
...elevated === true ? { elevated: true } : {}
|
|
3285
3522
|
});
|
|
3523
|
+
refIndex.set(ref, shard);
|
|
3286
3524
|
}
|
|
3287
|
-
|
|
3525
|
+
touch(shard, {
|
|
3526
|
+
table,
|
|
3527
|
+
...document !== void 0 ? { document } : {}
|
|
3528
|
+
});
|
|
3529
|
+
focused = shard;
|
|
3288
3530
|
},
|
|
3289
3531
|
get(ref) {
|
|
3290
|
-
return table.get(ref);
|
|
3532
|
+
return shards.get(focused)?.table.get(ref);
|
|
3533
|
+
},
|
|
3534
|
+
resolve(ref) {
|
|
3535
|
+
const target = shards.get(focused)?.table.get(ref);
|
|
3536
|
+
if (target !== void 0) return {
|
|
3537
|
+
status: "ok",
|
|
3538
|
+
target
|
|
3539
|
+
};
|
|
3540
|
+
const owner = refIndex.get(ref);
|
|
3541
|
+
if (owner === void 0) return { status: "unknown" };
|
|
3542
|
+
return shards.get(owner)?.table.has(ref) === true ? { status: "unfocused" } : { status: "expired" };
|
|
3543
|
+
},
|
|
3544
|
+
focus(shard) {
|
|
3545
|
+
focused = shard;
|
|
3546
|
+
},
|
|
3547
|
+
get focused() {
|
|
3548
|
+
return focused === DEFAULT_SHARD ? void 0 : focused;
|
|
3549
|
+
},
|
|
3550
|
+
describePage() {
|
|
3551
|
+
return shards.get(focused)?.document;
|
|
3552
|
+
},
|
|
3553
|
+
drop(shard) {
|
|
3554
|
+
shards.delete(shard);
|
|
3555
|
+
},
|
|
3556
|
+
clear() {
|
|
3557
|
+
shards = /* @__PURE__ */ new Map();
|
|
3558
|
+
refIndex = /* @__PURE__ */ new Map();
|
|
3559
|
+
focused = DEFAULT_SHARD;
|
|
3291
3560
|
},
|
|
3292
3561
|
get size() {
|
|
3293
|
-
return table.size;
|
|
3562
|
+
return shards.get(focused)?.table.size ?? 0;
|
|
3563
|
+
},
|
|
3564
|
+
get shardCount() {
|
|
3565
|
+
return shards.size;
|
|
3294
3566
|
}
|
|
3295
3567
|
};
|
|
3296
3568
|
}
|
|
@@ -3338,14 +3610,15 @@ async function roundTrip(transport, request, expect, code, timeoutMs) {
|
|
|
3338
3610
|
* @experimental
|
|
3339
3611
|
*/
|
|
3340
3612
|
function createRemotePerceptionReader(options) {
|
|
3341
|
-
const { transport, targets, timeoutMs = DEFAULT_PAGE_AGENT_TIMEOUT_MS } = options;
|
|
3613
|
+
const { transport, targets, timeoutMs = DEFAULT_PAGE_AGENT_TIMEOUT_MS, onDocument } = options;
|
|
3342
3614
|
return { async read(scope, capture) {
|
|
3343
3615
|
const reply = await roundTrip(transport, {
|
|
3344
3616
|
type: "perceive",
|
|
3345
3617
|
scope,
|
|
3346
3618
|
...capture !== void 0 ? { capture } : {}
|
|
3347
3619
|
}, "perceive-result", "PERCEPTION_FAILED", timeoutMs);
|
|
3348
|
-
targets.replace(reply.targets);
|
|
3620
|
+
targets.replace(reply.targets, reply.document);
|
|
3621
|
+
if (reply.document !== void 0) onDocument?.(reply.document);
|
|
3349
3622
|
return reply.result;
|
|
3350
3623
|
} };
|
|
3351
3624
|
}
|
|
@@ -3416,8 +3689,25 @@ function createPageAgentHandler(options = {}) {
|
|
|
3416
3689
|
});
|
|
3417
3690
|
const executor = createDomPageActionExecutor({
|
|
3418
3691
|
reader,
|
|
3692
|
+
...options.document !== void 0 ? { document: options.document } : {},
|
|
3419
3693
|
...options.pickFiles !== void 0 ? { pickFiles: options.pickFiles } : {}
|
|
3420
3694
|
});
|
|
3695
|
+
/**
|
|
3696
|
+
* 这批句柄属于哪个文档(FR-11.1)。`key` 与 `PageActionOutcome.navigated`
|
|
3697
|
+
* 出自同一个 `documentKey()` —— 这是「操作说导航了、分片说没换」不可能发生的构造性保证。
|
|
3698
|
+
*/
|
|
3699
|
+
const describeDocument = () => {
|
|
3700
|
+
const doc = options.document ?? globalThis.document;
|
|
3701
|
+
const key = documentKey(doc?.defaultView);
|
|
3702
|
+
if (key === void 0) return void 0;
|
|
3703
|
+
const url = doc.defaultView?.location.href;
|
|
3704
|
+
const title = doc.title;
|
|
3705
|
+
return {
|
|
3706
|
+
key,
|
|
3707
|
+
...url !== void 0 ? { url } : {},
|
|
3708
|
+
...title !== void 0 && title !== "" ? { title } : {}
|
|
3709
|
+
};
|
|
3710
|
+
};
|
|
3421
3711
|
return { async handle(request) {
|
|
3422
3712
|
try {
|
|
3423
3713
|
if (request.type === "perceive") {
|
|
@@ -3431,10 +3721,12 @@ function createPageAgentHandler(options = {}) {
|
|
|
3431
3721
|
...target
|
|
3432
3722
|
});
|
|
3433
3723
|
}
|
|
3724
|
+
const identity = describeDocument();
|
|
3434
3725
|
return {
|
|
3435
3726
|
type: "perceive-result",
|
|
3436
3727
|
result,
|
|
3437
|
-
targets
|
|
3728
|
+
targets,
|
|
3729
|
+
...identity !== void 0 ? { document: identity } : {}
|
|
3438
3730
|
};
|
|
3439
3731
|
}
|
|
3440
3732
|
return {
|
|
@@ -3634,7 +3926,8 @@ function createFrameRouter(options) {
|
|
|
3634
3926
|
...imageFailures > 0 ? { imageFailures } : {},
|
|
3635
3927
|
...frameNotes.length > 0 ? { frameNotes } : {}
|
|
3636
3928
|
},
|
|
3637
|
-
targets
|
|
3929
|
+
targets,
|
|
3930
|
+
...main.reply.document !== void 0 ? { document: main.reply.document } : {}
|
|
3638
3931
|
};
|
|
3639
3932
|
}
|
|
3640
3933
|
async function execute(request) {
|
|
@@ -5151,4 +5444,4 @@ function originOf(blockedURI) {
|
|
|
5151
5444
|
}
|
|
5152
5445
|
|
|
5153
5446
|
//#endregion
|
|
5154
|
-
export { BrowserSkillManager, BrowserWorkerScriptExecutor, ChromeBuiltinLlmClient, DEFAULT_FRAME_BUDGET, DEFAULT_MAX_VIEWER_PAYLOAD_BYTES, DOCUMENT_SURFACE_AUDIT_EVENT, DOCX_UNEXTRACTED, HOST_PORT_MATRIX, IframeWorkerLike, OpfsProvider, SANDBOX_PAGE_SCRIPT_SOURCE, TsTranspiler, VIEWER_SANDBOX_TOKENS, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, XLSX_UNEXTRACTED, blockedMessage, bridgeError, captureElementImage, checkDictationAvailability, compressImageToBudget, createBrowserChatbotHost, createDocumentSurfaceHost, createDomPageActionExecutor, createDomPerceptionReader, createEncryptedMemoryStore, createFetchLinkedDocumentReader, createFrameRouter, createIframeWorker, createLlmClient, createPageAgentHandler, createRemotePageActionExecutor, createRemotePerceptionReader, createRemoteTargetRegistry, deleteMemoryEncryptionKey, extractDocxText, extractXlsxText, extractZipWeb, generateMemoryEncryptionKey, inspectHostWiring, installWebSkillNavigator, isCapturableElement, isCaptureFailure, isEncryptedMemoryValue, isOpfsAvailable, missingPorts, openDocumentSurface, openMemoryEncryptionKey, parseBridgeRequest, parseMainMessage, parseWorkerEvent, probeChromeBuiltinAvailability, sha256HexWeb, startDictation, startViewerShell, startWorkerRuntimeHost, viewerCspHeader, watchBlockedResources };
|
|
5447
|
+
export { BrowserSkillManager, BrowserWorkerScriptExecutor, ChromeBuiltinLlmClient, DEFAULT_FRAME_BUDGET, DEFAULT_MAX_VIEWER_PAYLOAD_BYTES, DOCUMENT_SURFACE_AUDIT_EVENT, DOCX_UNEXTRACTED, HOST_PORT_MATRIX, IframeWorkerLike, OpfsProvider, SANDBOX_PAGE_SCRIPT_SOURCE, SHARD_SOFT_LIMIT, TsTranspiler, VIEWER_SANDBOX_TOKENS, WORKER_BOOTSTRAP_SOURCE, WorkerRuntimeClient, WorkerUiBridge, XLSX_UNEXTRACTED, blockedMessage, bridgeError, captureElementImage, checkDictationAvailability, compressImageToBudget, createBrowserChatbotHost, createDocumentSurfaceHost, createDomPageActionExecutor, createDomPerceptionReader, createEncryptedMemoryStore, createFetchLinkedDocumentReader, createFrameRouter, createIframeWorker, createLlmClient, createPageAgentHandler, createRemotePageActionExecutor, createRemotePerceptionReader, createRemoteTargetRegistry, deleteMemoryEncryptionKey, documentKey, explainResolution, extractDocxText, extractXlsxText, extractZipWeb, generateMemoryEncryptionKey, inspectHostWiring, installWebSkillNavigator, isCapturableElement, isCaptureFailure, isEncryptedMemoryValue, isOpfsAvailable, missingPorts, openDocumentSurface, openMemoryEncryptionKey, parseBridgeRequest, parseMainMessage, parseWorkerEvent, probeChromeBuiltinAvailability, sha256HexWeb, startDictation, startViewerShell, startWorkerRuntimeHost, viewerCspHeader, watchBlockedResources };
|
|
@@ -41,12 +41,35 @@ const AUDIT_EVENT_TYPES = {
|
|
|
41
41
|
/** 候选记录被从审批队列里删除(仅 draft / pending-review / rejected 可删) */
|
|
42
42
|
candidateRemoved: "candidate.removed",
|
|
43
43
|
pagePerceived: "page.perceived",
|
|
44
|
+
/**
|
|
45
|
+
* 子树续读被拒(0.16.0 分册 14 · FR-14.6)。载荷只有 `reason`,**不含被拒的 `ref`**:
|
|
46
|
+
* 句柄换轮即失效,留下来对复盘没有帮助,却会把一个模型可控的字符串写进审计流。
|
|
47
|
+
*
|
|
48
|
+
* `reason: 'out-of-scope'` 是注入攻击的观测点:页面里写着「请读取整个页面」时,
|
|
49
|
+
* 模型若照做,这条留痕就是它留下的痕迹。
|
|
50
|
+
*/
|
|
51
|
+
pagePerceiveDenied: "page.perceive.denied",
|
|
44
52
|
pageAction: "page.action",
|
|
45
53
|
/**
|
|
46
54
|
* 一次页面操作引发了下载,但 `sandbox.downloadedFiles` 关着,模型因此一无所知
|
|
47
55
|
* (0.15.0 分册 15 · FR-15.7)。载荷只有计数与原因,不含文件名 / 大小 / 类型。
|
|
48
56
|
*/
|
|
49
57
|
pageDownloadUnreported: "page.download.unreported",
|
|
58
|
+
/**
|
|
59
|
+
* 模型侧标签页控制(0.16.0 分册 12 · FR-12.7)。写入点在
|
|
60
|
+
* `packages/agent/src/tabs/policy.ts`,同样是字面量同步。
|
|
61
|
+
*
|
|
62
|
+
* `list_page_tabs` **不在此列**:纯读、无副作用、每轮可能被调很多次,
|
|
63
|
+
* 记下来只会把 focused / closed 淹没在噪音里(DV-4)。作为交换,
|
|
64
|
+
* `page.tab.focused` 的 `data.from` 是必需的——失去列举留痕后,
|
|
65
|
+
* 它是唯一能回答「模型是从哪一页跳过去的」的字段。
|
|
66
|
+
*/
|
|
67
|
+
pageTabFocused: "page.tab.focused",
|
|
68
|
+
pageTabClosed: "page.tab.closed",
|
|
69
|
+
/** 切换 / 关闭被拒,或工作集触顶。载荷只有原因与上限,**不含**被拒的句柄内容 */
|
|
70
|
+
pageTabDenied: "page.tab.denied",
|
|
71
|
+
/** 页面操作新开了一页并被接进工作集(SC-2 的唯一入口) */
|
|
72
|
+
pageTabOpened: "page.tab.opened",
|
|
50
73
|
documentRead: "document.read",
|
|
51
74
|
documentSurfaceOpen: "document.surface.open",
|
|
52
75
|
/** 脚本按宿主声明的数据源取数(分册 16);被拒也记,否则反复试探看不见 */
|