@webskill/sdk 0.26.1 → 0.27.1
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/README.md +3 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +46 -16
- package/dist/{approval-DpiP0GVH.js → approval-BSwffz8P.js} +4 -1
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +448 -131
- package/dist/{geometry-CRhuySKV.js → geometry-BXMPNuM6.js} +6 -6
- package/dist/{index-L5JaAkLd.d.ts → index--GvzV6Lq.d.ts} +45 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/node.js +1 -1
- package/dist/{webSkillApi-DLwJmmm-.js → webSkillApi-C_7CTwtB.js} +1 -1
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -3,12 +3,12 @@ import { A as SKILL_MANIFEST_FILE, D as verifySkillSignature, M as buildManifest
|
|
|
3
3
|
import { o as atomicWriteText, r as resolveInsideRoot, s as isAtomicTempPath } from "./pathSecurity-CirkQwWP.js";
|
|
4
4
|
import { t as assertRemoteUrlAllowed } from "./urlSafety-CiSuCJvX.js";
|
|
5
5
|
import { t as ATTACHMENT_TEXT_LIMIT } from "./kind-Dc8x0HWz.js";
|
|
6
|
-
import { _ as FsRunSnapshotStore, a as networkPolicyLibSource, c as bridgeError, ct as ProgressiveRouter, d as FsMemoryStore, l as parseBridgeRequest, m as AgentLoop, r as normalizeToolError, s as UPLOAD_FILES_UNAVAILABLE, t as CapabilityApproval, u as FsArtifactStore } from "./approval-
|
|
6
|
+
import { _ as FsRunSnapshotStore, a as networkPolicyLibSource, c as bridgeError, ct as ProgressiveRouter, d as FsMemoryStore, l as parseBridgeRequest, m as AgentLoop, r as normalizeToolError, s as UPLOAD_FILES_UNAVAILABLE, t as CapabilityApproval, u as FsArtifactStore } from "./approval-BSwffz8P.js";
|
|
7
7
|
import { a as GoogleGenAiClient, o as AnthropicClient, s as OpenAiCompatibleClient, t as MockLlmClient } from "./llm-eIQNO9tr.js";
|
|
8
8
|
import { c as toBase64$1, f as SPREADSHEET_MIME_TYPE } from "./linkedDocument-0iiVPd5t.js";
|
|
9
9
|
import { n as normalizeToolContent, t as mergeCatalogEntries } from "./external-_ZRQe-V9.js";
|
|
10
|
-
import { t as createWebSkillApi } from "./webSkillApi-
|
|
11
|
-
import { A as frameSteps, D as SCROLL_DIRECTIONS, E as toActionFrameScopes, O as toFrameScopes, _ as defersImage, a as columnWidthToPixels, b as isPdfTextTrustworthy, c as isRangeTopLeft, d as rangeContains, f as readImageSize, k as frameLabel, l as parseCellAddress, n as EMU_PER_POINT, o as fitWithin, p as PDF_LARGE_IMAGE_RATIO, r as EMU_PER_TWIP$1, s as formatCellAddress, t as EMU_PER_PIXEL, u as parseCellRange, v as takesImage } from "./geometry-
|
|
10
|
+
import { t as createWebSkillApi } from "./webSkillApi-C_7CTwtB.js";
|
|
11
|
+
import { A as frameSteps, D as SCROLL_DIRECTIONS, E as toActionFrameScopes, O as toFrameScopes, _ as defersImage, a as columnWidthToPixels, b as isPdfTextTrustworthy, c as isRangeTopLeft, d as rangeContains, f as readImageSize, k as frameLabel, l as parseCellAddress, n as EMU_PER_POINT, o as fitWithin, p as PDF_LARGE_IMAGE_RATIO, r as EMU_PER_TWIP$1, s as formatCellAddress, t as EMU_PER_PIXEL, u as parseCellRange, v as takesImage } from "./geometry-BXMPNuM6.js";
|
|
12
12
|
import { zipSync } from "fflate";
|
|
13
13
|
|
|
14
14
|
//#region ../browser/src/fs/featureDetection.ts
|
|
@@ -20,6 +20,8 @@ function isOpfsAvailable(globalScope) {
|
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region ../browser/src/fs/opfsProvider.ts
|
|
22
22
|
const isDomException = (e, name) => e instanceof DOMException && e.name === name;
|
|
23
|
+
/** move 的两种原生签名形状不匹配时,浏览器抛的是这两类;权限/找不到等真实失败不重试。 */
|
|
24
|
+
const isWrongMoveSignature = (e) => e instanceof TypeError || typeof DOMException !== "undefined" && e instanceof DOMException && e.name === "NotSupportedError";
|
|
23
25
|
const mapError = (e, path) => {
|
|
24
26
|
if (isDomException(e, "NotFoundError")) return new WebSkillError("FS_NOT_FOUND", `Path not found: ${path}`);
|
|
25
27
|
if (isDomException(e, "NotAllowedError")) return new WebSkillError("FS_PERMISSION_DENIED", `Permission denied: ${path}`, e);
|
|
@@ -224,11 +226,21 @@ var OpfsProvider = class {
|
|
|
224
226
|
const toSegments = this.#segments(to);
|
|
225
227
|
const toName = toSegments.pop();
|
|
226
228
|
if (!toName) throw new WebSkillError("FS_NOT_FOUND", `Invalid path: ${to}`);
|
|
227
|
-
const
|
|
229
|
+
const fromDir = await this.#walkDir(`/${fromSegments.join("/")}`, false);
|
|
230
|
+
const file = await fromDir.getFileHandle(fromName);
|
|
228
231
|
const mover = file.move;
|
|
229
232
|
if (`/${fromSegments.join("/")}` === `/${toSegments.join("/")}` && typeof mover === "function") {
|
|
230
233
|
await this.#wrap(from, async () => {
|
|
231
|
-
|
|
234
|
+
const attempts = mover.length >= 2 ? [[fromDir, toName], [toName]] : [[toName], [fromDir, toName]];
|
|
235
|
+
let last;
|
|
236
|
+
for (const args of attempts) try {
|
|
237
|
+
await mover.call(file, ...args);
|
|
238
|
+
return;
|
|
239
|
+
} catch (e) {
|
|
240
|
+
if (!isWrongMoveSignature(e)) throw e;
|
|
241
|
+
last = e;
|
|
242
|
+
}
|
|
243
|
+
throw new WebSkillError("FS_PERMISSION_DENIED", `OPFS atomic rename is not supported by this browser's FileSystemFileHandle.move()`, last);
|
|
232
244
|
});
|
|
233
245
|
return;
|
|
234
246
|
}
|
|
@@ -2792,6 +2804,14 @@ function promoteRole(input) {
|
|
|
2792
2804
|
//#region ../browser/src/dom/perception.ts
|
|
2793
2805
|
/** 值可能是凭据的输入类型:即便宿主忘了写进 exclude 也不读值。操作侧复用同一份,不另写 */
|
|
2794
2806
|
const SECRET_INPUT_TYPES = /* @__PURE__ */ new Set(["password", "hidden"]);
|
|
2807
|
+
/**
|
|
2808
|
+
* 当前页地址的长度上限(0.27.0 分册 22 FR-22.4)。
|
|
2809
|
+
*
|
|
2810
|
+
* 地址是给人看的一串字,不是可解析的契约:超长时截断并标记,比让它把整次感知
|
|
2811
|
+
* 的预算吃掉要好。`…[truncated]` 与其它截断标记同源写法。
|
|
2812
|
+
*/
|
|
2813
|
+
const PAGE_URL_LIMIT = 2048;
|
|
2814
|
+
const PAGE_URL_TRUNCATED = "…[truncated]";
|
|
2795
2815
|
/** 可被操作的角色:只有这些节点的句柄能拿去写页面 */
|
|
2796
2816
|
const ACTIONABLE_ROLES = /* @__PURE__ */ new Set([
|
|
2797
2817
|
"button",
|
|
@@ -2937,6 +2957,34 @@ function nameOf(element, doc) {
|
|
|
2937
2957
|
const title = element.getAttribute("title");
|
|
2938
2958
|
return title !== null && title.trim() !== "" ? clip(title) : void 0;
|
|
2939
2959
|
}
|
|
2960
|
+
/**
|
|
2961
|
+
* 稳定化签名(0.27.0 分册 17)。它是**恢复用的保守指纹**,不是选择器:
|
|
2962
|
+
* 角色/名称/链接/表单名/文档内路径/最近行上下文任一不同都不匹配。
|
|
2963
|
+
* 列表重排后同路径可能指向另一行,因此上下文缺失时调用方必须拒绝恢复。
|
|
2964
|
+
*/
|
|
2965
|
+
function handleSignatureOf(element, role) {
|
|
2966
|
+
const doc = element.ownerDocument;
|
|
2967
|
+
const segments = [];
|
|
2968
|
+
let node = element;
|
|
2969
|
+
while (node !== null) {
|
|
2970
|
+
const parent = node.parentElement;
|
|
2971
|
+
const index = parent === null ? 0 : [...parent.children].indexOf(node);
|
|
2972
|
+
segments.unshift(`${node.tagName.toLowerCase()}[${index}]`);
|
|
2973
|
+
node = parent;
|
|
2974
|
+
}
|
|
2975
|
+
const ancestor = element.parentElement?.closest("tr, [role=\"row\"], li, [role=\"listitem\"], article, [role=\"article\"], [role=\"feed\"]");
|
|
2976
|
+
const context = ancestor === null || ancestor === void 0 ? void 0 : nameOf(ancestor, doc) ?? hrefOf(ancestor);
|
|
2977
|
+
const form = element.closest("form");
|
|
2978
|
+
const formName = element.getAttribute("name") ?? form?.getAttribute("name") ?? form?.getAttribute("aria-label") ?? void 0;
|
|
2979
|
+
return JSON.stringify({
|
|
2980
|
+
role,
|
|
2981
|
+
...nameOf(element, doc) !== void 0 ? { name: nameOf(element, doc) } : {},
|
|
2982
|
+
...hrefOf(element) !== void 0 ? { href: hrefOf(element) } : {},
|
|
2983
|
+
...formName !== void 0 ? { formName } : {},
|
|
2984
|
+
path: segments.join("/"),
|
|
2985
|
+
...context !== void 0 ? { context } : {}
|
|
2986
|
+
});
|
|
2987
|
+
}
|
|
2940
2988
|
function valueOf(element) {
|
|
2941
2989
|
if (element.tagName === "INPUT") {
|
|
2942
2990
|
const type = (element.getAttribute("type") ?? "text").toLowerCase();
|
|
@@ -3208,10 +3256,16 @@ function sameOriginDocumentOf(iframe) {
|
|
|
3208
3256
|
* 而安全性本就不由句柄寿命承担——执行前的 `modalStateOf` / `inActionScope` /
|
|
3209
3257
|
* 可见性 / 可用性四道校验才是闸门,它们按**执行那一刻**的 DOM 实时判定。
|
|
3210
3258
|
*/
|
|
3211
|
-
var HandleIssuer = class {
|
|
3259
|
+
var HandleIssuer = class HandleIssuer {
|
|
3212
3260
|
#table = /* @__PURE__ */ new Map();
|
|
3213
3261
|
/** 同一元素复用同一个 ref:模型重读页面后手里的句柄仍然指向同一个东西 */
|
|
3214
3262
|
#refByElement = /* @__PURE__ */ new WeakMap();
|
|
3263
|
+
/**
|
|
3264
|
+
* 失效 ref 的签名(0.27.0 分册 17)。不随 `#table` 删除:重渲染替换实例后,
|
|
3265
|
+
* 旧 ref 仍要能按签名找回来;上限 4096,文档 WeakRef 销毁即视为失效。
|
|
3266
|
+
*/
|
|
3267
|
+
#signatures = /* @__PURE__ */ new Map();
|
|
3268
|
+
static #MAX_SIGNATURES = 4096;
|
|
3215
3269
|
#actionable;
|
|
3216
3270
|
#excluded;
|
|
3217
3271
|
constructor(actionable, excluded) {
|
|
@@ -3229,22 +3283,59 @@ var HandleIssuer = class {
|
|
|
3229
3283
|
*/
|
|
3230
3284
|
prune() {
|
|
3231
3285
|
for (const [ref, entry] of this.#table) if (!entry.element.isConnected) this.#table.delete(ref);
|
|
3286
|
+
for (const [ref, record] of this.#signatures) if (record.document.deref() === void 0) this.#signatures.delete(ref);
|
|
3287
|
+
this.#trimSignatures();
|
|
3288
|
+
}
|
|
3289
|
+
#trimSignatures() {
|
|
3290
|
+
while (this.#signatures.size > HandleIssuer.#MAX_SIGNATURES) {
|
|
3291
|
+
const oldest = this.#signatures.keys().next().value;
|
|
3292
|
+
if (oldest === void 0) return;
|
|
3293
|
+
this.#signatures.delete(oldest);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
#remember(ref, element, signature) {
|
|
3297
|
+
this.#signatures.delete(ref);
|
|
3298
|
+
this.#signatures.set(ref, {
|
|
3299
|
+
signature,
|
|
3300
|
+
document: new WeakRef(element.ownerDocument)
|
|
3301
|
+
});
|
|
3302
|
+
this.#trimSignatures();
|
|
3303
|
+
}
|
|
3304
|
+
signatureOf(ref) {
|
|
3305
|
+
return this.#signatures.get(ref)?.signature ?? this.#table.get(ref)?.signature;
|
|
3306
|
+
}
|
|
3307
|
+
documentOf(ref) {
|
|
3308
|
+
return this.#signatures.get(ref)?.document.deref();
|
|
3309
|
+
}
|
|
3310
|
+
/** 动作恢复:把旧 ref 重新指向实际元素;四道闸仍由执行器在 execute 时判。 */
|
|
3311
|
+
restore(ref, element, kind, frame, signature) {
|
|
3312
|
+
this.#table.set(ref, {
|
|
3313
|
+
element,
|
|
3314
|
+
kind,
|
|
3315
|
+
...frame !== void 0 ? { frame } : {},
|
|
3316
|
+
signature
|
|
3317
|
+
});
|
|
3318
|
+
this.#refByElement.set(element, ref);
|
|
3319
|
+
this.#remember(ref, element, signature);
|
|
3232
3320
|
}
|
|
3233
3321
|
issue(element, role, frame) {
|
|
3234
3322
|
if (!this.#actionable.has(element) || this.#excluded.has(element)) return void 0;
|
|
3235
3323
|
const kind = ACTIONABLE_ROLES.has(role) ? "action" : ANCHOR_ROLES.has(role) ? "anchor" : void 0;
|
|
3236
3324
|
if (kind === void 0) return void 0;
|
|
3237
3325
|
if (kind === "action" && occupiesNoSpace(element)) return void 0;
|
|
3326
|
+
const signature = handleSignatureOf(element, role);
|
|
3238
3327
|
const known = this.#refByElement.get(element);
|
|
3239
3328
|
if (known !== void 0 && this.#table.has(known)) {
|
|
3240
3329
|
this.#table.set(known, {
|
|
3241
3330
|
element,
|
|
3242
3331
|
kind,
|
|
3243
|
-
...frame !== void 0 ? { frame } : {}
|
|
3332
|
+
...frame !== void 0 ? { frame } : {},
|
|
3333
|
+
signature
|
|
3244
3334
|
});
|
|
3335
|
+
this.#remember(known, element, signature);
|
|
3245
3336
|
return known;
|
|
3246
3337
|
}
|
|
3247
|
-
return this.#mint(element, kind, frame);
|
|
3338
|
+
return this.#mint(element, kind, frame, signature);
|
|
3248
3339
|
}
|
|
3249
3340
|
/**
|
|
3250
3341
|
* 取像句柄(0.22.0 FR-12.1)。**不过操作范围那道闸**:取像是读,而该元素已经在
|
|
@@ -3256,7 +3347,7 @@ var HandleIssuer = class {
|
|
|
3256
3347
|
if (this.#excluded.has(element)) return void 0;
|
|
3257
3348
|
const known = this.#refByElement.get(element);
|
|
3258
3349
|
if (known !== void 0 && this.#table.has(known)) return known;
|
|
3259
|
-
return this.#mint(element, "image", frame);
|
|
3350
|
+
return this.#mint(element, "image", frame, handleSignatureOf(element, roleOf$1(element)));
|
|
3260
3351
|
}
|
|
3261
3352
|
/**
|
|
3262
3353
|
* 可滚容器的句柄(0.23.0 分册 22)。角色白名单之外——表格容器往往是个 `generic` div,
|
|
@@ -3269,18 +3360,20 @@ var HandleIssuer = class {
|
|
|
3269
3360
|
if (occupiesNoSpace(element)) return void 0;
|
|
3270
3361
|
const known = this.#refByElement.get(element);
|
|
3271
3362
|
if (known !== void 0 && this.#table.has(known)) return known;
|
|
3272
|
-
return this.#mint(element, "action", frame);
|
|
3363
|
+
return this.#mint(element, "action", frame, handleSignatureOf(element, roleOf$1(element)));
|
|
3273
3364
|
}
|
|
3274
|
-
#mint(element, kind, frame) {
|
|
3365
|
+
#mint(element, kind, frame, signature) {
|
|
3275
3366
|
const bytes = /* @__PURE__ */ new Uint8Array(8);
|
|
3276
3367
|
crypto.getRandomValues(bytes);
|
|
3277
3368
|
const ref = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3278
3369
|
this.#table.set(ref, {
|
|
3279
3370
|
element,
|
|
3280
3371
|
kind,
|
|
3281
|
-
...frame !== void 0 ? { frame } : {}
|
|
3372
|
+
...frame !== void 0 ? { frame } : {},
|
|
3373
|
+
signature
|
|
3282
3374
|
});
|
|
3283
3375
|
this.#refByElement.set(element, ref);
|
|
3376
|
+
this.#remember(ref, element, signature);
|
|
3284
3377
|
return ref;
|
|
3285
3378
|
}
|
|
3286
3379
|
/** 角色提升要的是「有可操作后代」,容器锚点不算;混进来会把可点容器判成已有出口 */
|
|
@@ -3301,6 +3394,8 @@ var HandleIssuer = class {
|
|
|
3301
3394
|
*/
|
|
3302
3395
|
function createDomPerceptionReader(options = {}) {
|
|
3303
3396
|
let handles;
|
|
3397
|
+
/** 最近一次 read/readSubtree 的范围;只给内部 actionChildRefs 用,不进公开接口 */
|
|
3398
|
+
let lastScope;
|
|
3304
3399
|
/** 授权生效时记下各帧的 origin,之后每次感知比对(FR-24.3) */
|
|
3305
3400
|
const frameOrigins = /* @__PURE__ */ new Map();
|
|
3306
3401
|
/**
|
|
@@ -3309,6 +3404,22 @@ function createDomPerceptionReader(options = {}) {
|
|
|
3309
3404
|
*/
|
|
3310
3405
|
const elevatedModals = /* @__PURE__ */ new Set();
|
|
3311
3406
|
const rootDocument = () => options.document ?? globalThis.document;
|
|
3407
|
+
/**
|
|
3408
|
+
* 当前文档的地址与标题(0.27.0 分册 22 FR-22.1)。
|
|
3409
|
+
*
|
|
3410
|
+
* 只回答「你在哪一页」,不是导航入口:没有任何入参能选目的地。
|
|
3411
|
+
* 拿不到(无文档、`about:blank`、`srcdoc`)时返回 `undefined`——结果里就不带这个键,
|
|
3412
|
+
* 而不是塞一个空串让模型以为拿到了地址(FR-22.8)。
|
|
3413
|
+
*/
|
|
3414
|
+
const pageOf = (doc) => {
|
|
3415
|
+
const href = (doc?.defaultView)?.location?.href;
|
|
3416
|
+
if (typeof href !== "string" || href === "" || href === "about:blank") return void 0;
|
|
3417
|
+
const title = typeof doc?.title === "string" ? doc.title.trim() : "";
|
|
3418
|
+
return {
|
|
3419
|
+
url: href.length > PAGE_URL_LIMIT ? `${href.slice(0, PAGE_URL_LIMIT)}${PAGE_URL_TRUNCATED}` : href,
|
|
3420
|
+
...title === "" ? {} : { title }
|
|
3421
|
+
};
|
|
3422
|
+
};
|
|
3312
3423
|
/** 提升未开启时返回 undefined,遍历侧据此整段跳过(AC-G10) */
|
|
3313
3424
|
const promotionOf = (view) => options.promoteRoles === true ? {
|
|
3314
3425
|
view,
|
|
@@ -3402,6 +3513,7 @@ function createDomPerceptionReader(options = {}) {
|
|
|
3402
3513
|
nestedFrames: [],
|
|
3403
3514
|
anchored: false
|
|
3404
3515
|
};
|
|
3516
|
+
const page = pageOf(root);
|
|
3405
3517
|
handles ??= new HandleIssuer(/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
|
|
3406
3518
|
handles.prune();
|
|
3407
3519
|
const issuer = handles;
|
|
@@ -3519,7 +3631,8 @@ function createDomPerceptionReader(options = {}) {
|
|
|
3519
3631
|
nodes,
|
|
3520
3632
|
frameNotes,
|
|
3521
3633
|
nestedFrames,
|
|
3522
|
-
anchored
|
|
3634
|
+
anchored,
|
|
3635
|
+
...page === void 0 ? {} : { page }
|
|
3523
3636
|
};
|
|
3524
3637
|
pruneElevated();
|
|
3525
3638
|
const view = root.defaultView;
|
|
@@ -3541,12 +3654,13 @@ function createDomPerceptionReader(options = {}) {
|
|
|
3541
3654
|
nodes,
|
|
3542
3655
|
frameNotes,
|
|
3543
3656
|
nestedFrames,
|
|
3544
|
-
anchored
|
|
3657
|
+
anchored,
|
|
3658
|
+
...page === void 0 ? {} : { page }
|
|
3545
3659
|
};
|
|
3546
3660
|
}
|
|
3547
3661
|
async function readWithImages(scope, capture, subtree) {
|
|
3548
3662
|
const targets = [];
|
|
3549
|
-
const { nodes, frameNotes, nestedFrames, anchored } = collect(scope, {
|
|
3663
|
+
const { nodes, frameNotes, nestedFrames, anchored, page } = collect(scope, {
|
|
3550
3664
|
targets,
|
|
3551
3665
|
minImageArea: capture.minImageArea ?? 0
|
|
3552
3666
|
}, subtree);
|
|
@@ -3578,16 +3692,19 @@ function createDomPerceptionReader(options = {}) {
|
|
|
3578
3692
|
imagesOmitted: targets.length - selected.length,
|
|
3579
3693
|
imageFailures,
|
|
3580
3694
|
anchored,
|
|
3695
|
+
...page === void 0 ? {} : { page },
|
|
3581
3696
|
...frameNotes.length > 0 ? { frameNotes } : {},
|
|
3582
3697
|
...nestedFrames.length > 0 ? { nestedFrames } : {}
|
|
3583
3698
|
};
|
|
3584
3699
|
}
|
|
3585
3700
|
function read(scope, capture) {
|
|
3701
|
+
lastScope = scope;
|
|
3586
3702
|
if (capture === void 0) return collect(scope, void 0).nodes;
|
|
3587
3703
|
if (!capture.images || capture.maxImages <= 0) {
|
|
3588
|
-
const { nodes, frameNotes, nestedFrames } = collect(scope, void 0);
|
|
3704
|
+
const { nodes, frameNotes, nestedFrames, page } = collect(scope, void 0);
|
|
3589
3705
|
return Promise.resolve({
|
|
3590
3706
|
nodes,
|
|
3707
|
+
...page === void 0 ? {} : { page },
|
|
3591
3708
|
...frameNotes.length > 0 ? { frameNotes } : {},
|
|
3592
3709
|
...nestedFrames.length > 0 ? { nestedFrames } : {}
|
|
3593
3710
|
});
|
|
@@ -3602,20 +3719,113 @@ function createDomPerceptionReader(options = {}) {
|
|
|
3602
3719
|
* 拒绝走返回值而不是抛错:跨上下文的 reject 到本地就是一个没有错误码的 `Error`。
|
|
3603
3720
|
*/
|
|
3604
3721
|
async function readSubtree(scope, ref, capture) {
|
|
3605
|
-
|
|
3606
|
-
|
|
3722
|
+
lastScope = scope;
|
|
3723
|
+
let element = handles?.table.get(ref)?.element;
|
|
3724
|
+
if (element === void 0 || !element.isConnected) element = recoverElement(ref, "perceive");
|
|
3725
|
+
if (element === void 0) return { rejected: "unknown-ref" };
|
|
3607
3726
|
if (capture?.images === true && capture.maxImages > 0) {
|
|
3608
3727
|
const { anchored, ...result } = await readWithImages(scope, capture, element);
|
|
3609
3728
|
return anchored ? result : { rejected: "out-of-scope" };
|
|
3610
3729
|
}
|
|
3611
|
-
const { nodes, frameNotes, nestedFrames, anchored } = collect(scope, void 0, element);
|
|
3730
|
+
const { nodes, frameNotes, nestedFrames, anchored, page } = collect(scope, void 0, element);
|
|
3612
3731
|
if (!anchored) return { rejected: "out-of-scope" };
|
|
3613
3732
|
return {
|
|
3614
3733
|
nodes,
|
|
3734
|
+
...page === void 0 ? {} : { page },
|
|
3615
3735
|
...frameNotes.length > 0 ? { frameNotes } : {},
|
|
3616
3736
|
...nestedFrames.length > 0 ? { nestedFrames } : {}
|
|
3617
3737
|
};
|
|
3618
3738
|
}
|
|
3739
|
+
/**
|
|
3740
|
+
* 内部能力(分册 16):容器 ref 被拒时,给出当前已持有/读一次子树后的可操作子 ref。
|
|
3741
|
+
* 不导出到 `DomPerceptionReader`:公开接口加方法会改 d.ts 与 API 快照。
|
|
3742
|
+
* 返回 `known:false` 表示无法判断,调用方必须退回旧文案,不能声称“没有子节点”。
|
|
3743
|
+
*/
|
|
3744
|
+
async function actionChildRefs(ref, limit) {
|
|
3745
|
+
const entry = handles?.table.get(ref);
|
|
3746
|
+
if (entry === void 0 || entry.kind !== "anchor" || !entry.element.isConnected) return void 0;
|
|
3747
|
+
const container = entry.element;
|
|
3748
|
+
const doc = container.ownerDocument;
|
|
3749
|
+
const collectActionRefs = () => {
|
|
3750
|
+
const out = [];
|
|
3751
|
+
for (const [childRef, child] of handles?.table ?? []) {
|
|
3752
|
+
if (child.kind !== "action" || !child.element.isConnected) continue;
|
|
3753
|
+
if (child.element === container || !container.contains(child.element)) continue;
|
|
3754
|
+
if (child.element.ownerDocument !== doc) continue;
|
|
3755
|
+
out.push({
|
|
3756
|
+
ref: childRef,
|
|
3757
|
+
element: child.element
|
|
3758
|
+
});
|
|
3759
|
+
}
|
|
3760
|
+
out.sort((a, b) => {
|
|
3761
|
+
const position = a.element.compareDocumentPosition(b.element);
|
|
3762
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
|
|
3763
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
|
|
3764
|
+
return 0;
|
|
3765
|
+
});
|
|
3766
|
+
return [...new Set(out.map((item) => item.ref))];
|
|
3767
|
+
};
|
|
3768
|
+
let refs = collectActionRefs();
|
|
3769
|
+
if (refs.length === 0) {
|
|
3770
|
+
if (lastScope === void 0) return {
|
|
3771
|
+
refs: [],
|
|
3772
|
+
known: false,
|
|
3773
|
+
more: 0
|
|
3774
|
+
};
|
|
3775
|
+
if ("rejected" in await readSubtree(lastScope, ref)) return {
|
|
3776
|
+
refs: [],
|
|
3777
|
+
known: false,
|
|
3778
|
+
more: 0
|
|
3779
|
+
};
|
|
3780
|
+
refs = collectActionRefs();
|
|
3781
|
+
}
|
|
3782
|
+
const unique = [...new Set(refs)];
|
|
3783
|
+
return {
|
|
3784
|
+
refs: unique.slice(0, limit),
|
|
3785
|
+
known: true,
|
|
3786
|
+
more: Math.max(0, unique.length - limit)
|
|
3787
|
+
};
|
|
3788
|
+
}
|
|
3789
|
+
/**
|
|
3790
|
+
* 内部恢复(0.27.0 分册 17):旧 ref 的元素断开后,按签名在**当前主文档**内找唯一元素。
|
|
3791
|
+
* `action` 模式会恢复句柄表,`perceive` 只返回元素供本次子树读,不把旧 ref 写回。
|
|
3792
|
+
* 跨帧不恢复:这里只处理主文档,iframe 旧 ref 仍走既有失效路径。
|
|
3793
|
+
*/
|
|
3794
|
+
function recoverElement(ref, mode) {
|
|
3795
|
+
const doc = rootDocument();
|
|
3796
|
+
const signature = handles?.signatureOf(ref);
|
|
3797
|
+
const oldDoc = handles?.documentOf(ref);
|
|
3798
|
+
if (doc === void 0 || signature === void 0 || oldDoc !== doc) return void 0;
|
|
3799
|
+
const scope = lastScope;
|
|
3800
|
+
if (scope === void 0) return void 0;
|
|
3801
|
+
const view = doc.defaultView;
|
|
3802
|
+
const candidates = [...doc.querySelectorAll("*")].filter((element) => {
|
|
3803
|
+
if (!element.isConnected) return false;
|
|
3804
|
+
if (handleSignatureOf(element, roleOf$1(element)) !== signature) return false;
|
|
3805
|
+
if (mode === "action") {
|
|
3806
|
+
const sets = actionSets(doc, actionScopeFor("self"));
|
|
3807
|
+
if (!sets.actionable.has(element) || sets.excluded.has(element)) return false;
|
|
3808
|
+
const modal = element.closest(MODAL_SELECTOR);
|
|
3809
|
+
if (modal !== null && !elevatedModals.has(modal)) return false;
|
|
3810
|
+
if (hidden$1(element, view)) return false;
|
|
3811
|
+
if (occupiesNoSpace(element)) return false;
|
|
3812
|
+
if (element.hasAttribute("disabled")) return false;
|
|
3813
|
+
return true;
|
|
3814
|
+
}
|
|
3815
|
+
const frameScope = "frames" in scope ? scope.frames.find((entry) => frameSteps(entry.frame).length === 0) : scope;
|
|
3816
|
+
if (frameScope === void 0) return false;
|
|
3817
|
+
const sets = actionSets(doc, frameScope);
|
|
3818
|
+
return sets.actionable.has(element) && !sets.excluded.has(element);
|
|
3819
|
+
});
|
|
3820
|
+
if (candidates.length !== 1) return void 0;
|
|
3821
|
+
const [element] = candidates;
|
|
3822
|
+
if (element === void 0) return void 0;
|
|
3823
|
+
if (mode === "action") {
|
|
3824
|
+
const frame = handles?.table.get(ref)?.frame;
|
|
3825
|
+
handles?.restore(ref, element, "action", frame, signature);
|
|
3826
|
+
}
|
|
3827
|
+
return element;
|
|
3828
|
+
}
|
|
3619
3829
|
/** 当前活动模态;跨帧不适用(模态提升只在主文档语义下成立) */
|
|
3620
3830
|
function modalSnapshot() {
|
|
3621
3831
|
const root = rootDocument();
|
|
@@ -3629,6 +3839,8 @@ function createDomPerceptionReader(options = {}) {
|
|
|
3629
3839
|
return {
|
|
3630
3840
|
read,
|
|
3631
3841
|
readSubtree,
|
|
3842
|
+
actionChildRefs,
|
|
3843
|
+
recoverElement,
|
|
3632
3844
|
resolve: (ref) => handles?.table.get(ref)?.element,
|
|
3633
3845
|
handleKindOf: (ref) => handles?.table.get(ref)?.kind,
|
|
3634
3846
|
frameOf: (ref) => handles?.table.get(ref)?.frame,
|
|
@@ -3824,6 +4036,26 @@ function roleOf(element) {
|
|
|
3824
4036
|
if (element.tagName === "FORM") return "form";
|
|
3825
4037
|
return "generic";
|
|
3826
4038
|
}
|
|
4039
|
+
const containerActionText = (reader, inside) => {
|
|
4040
|
+
const base = "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 that carries its own reference. If nothing inside it has one, this part of the page offers no action and retrying here will not help.";
|
|
4041
|
+
if (inside === void 0 || !inside.known) return base;
|
|
4042
|
+
const usable = [];
|
|
4043
|
+
for (const ref of inside.refs) {
|
|
4044
|
+
const element = reader.resolve(ref);
|
|
4045
|
+
if (element === void 0) continue;
|
|
4046
|
+
if (reader.modalStateOf(element) === "unelevated") continue;
|
|
4047
|
+
if (reader.modalStateOf(element) !== "elevated" && !reader.inActionScope(element)) continue;
|
|
4048
|
+
if (hidden(element)) continue;
|
|
4049
|
+
if (occupiesNoSpace(element)) continue;
|
|
4050
|
+
if (element.hasAttribute("disabled")) continue;
|
|
4051
|
+
const target = describeElement(element);
|
|
4052
|
+
usable.push(`[${ref}] ${target.role}${target.name !== void 0 ? ` "${target.name}"` : ""}`);
|
|
4053
|
+
}
|
|
4054
|
+
if (usable.length === 0) return `${base} This container has no actionable elements; retrying here will not help. Perceive the container again after a row or panel has changed.`;
|
|
4055
|
+
const shown = usable.slice(0, 5);
|
|
4056
|
+
const more = Math.max(inside.more, usable.length - shown.length);
|
|
4057
|
+
return `${base} Actionable references inside: ${shown.join("; ")}${more > 0 ? `; and ${more} more` : ""}.`;
|
|
4058
|
+
};
|
|
3827
4059
|
const describeElement = (element) => {
|
|
3828
4060
|
const name = accessibleName(element);
|
|
3829
4061
|
return {
|
|
@@ -3971,24 +4203,28 @@ function hoverOver(element) {
|
|
|
3971
4203
|
}));
|
|
3972
4204
|
element.dispatchEvent(new MouseEvent("mousemove", base));
|
|
3973
4205
|
}
|
|
3974
|
-
/**
|
|
3975
|
-
* 悬停之后页面有没有动(FR-17.3)。
|
|
3976
|
-
*
|
|
3977
|
-
* 用 `MutationObserver` 而不是比对快照:浮层可能挂到 `document.body` 末尾、
|
|
3978
|
-
* 也可能只是把某个节点的 `hidden` 去掉,逐一枚举会漏,而漏判的代价是向模型报假成功。
|
|
3979
|
-
* 观察器在派发**之前**挂上——晚一步就会错过同步执行的那批监听器。
|
|
3980
|
-
*/
|
|
3981
4206
|
function watchMutations(element) {
|
|
3982
4207
|
const doc = element.ownerDocument;
|
|
3983
4208
|
const Observer = doc.defaultView?.MutationObserver;
|
|
3984
4209
|
if (Observer === void 0) return {
|
|
3985
|
-
|
|
4210
|
+
take: () => ({
|
|
4211
|
+
added: 0,
|
|
4212
|
+
removed: 0,
|
|
4213
|
+
changed: true
|
|
4214
|
+
}),
|
|
3986
4215
|
stop: () => void 0
|
|
3987
4216
|
};
|
|
4217
|
+
let added = 0;
|
|
4218
|
+
let removed = 0;
|
|
3988
4219
|
let seen = false;
|
|
3989
|
-
const
|
|
3990
|
-
|
|
3991
|
-
|
|
4220
|
+
const absorb = (records) => {
|
|
4221
|
+
for (const record of records) {
|
|
4222
|
+
seen = true;
|
|
4223
|
+
added += record.addedNodes.length;
|
|
4224
|
+
removed += record.removedNodes.length;
|
|
4225
|
+
}
|
|
4226
|
+
};
|
|
4227
|
+
const observer = new Observer(absorb);
|
|
3992
4228
|
observer.observe(doc, {
|
|
3993
4229
|
subtree: true,
|
|
3994
4230
|
childList: true,
|
|
@@ -3996,13 +4232,50 @@ function watchMutations(element) {
|
|
|
3996
4232
|
characterData: true
|
|
3997
4233
|
});
|
|
3998
4234
|
return {
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
return
|
|
4235
|
+
take: () => {
|
|
4236
|
+
absorb(observer.takeRecords());
|
|
4237
|
+
return {
|
|
4238
|
+
added,
|
|
4239
|
+
removed,
|
|
4240
|
+
changed: seen
|
|
4241
|
+
};
|
|
4002
4242
|
},
|
|
4003
4243
|
stop: () => observer.disconnect()
|
|
4004
4244
|
};
|
|
4005
4245
|
}
|
|
4246
|
+
const STATUS_SELECTORS = [
|
|
4247
|
+
"[role=\"status\"]",
|
|
4248
|
+
"[role=\"alert\"]",
|
|
4249
|
+
".messages .message",
|
|
4250
|
+
".messages li"
|
|
4251
|
+
];
|
|
4252
|
+
const compactText = (element) => {
|
|
4253
|
+
const text = element.textContent?.replace(/\s+/g, " ").trim();
|
|
4254
|
+
return text === void 0 || text === "" ? void 0 : text;
|
|
4255
|
+
};
|
|
4256
|
+
/** 状态文本只在固定状态区取;不读整页、不读 `.messages` 容器,避免把多条消息/正文带进模型上下文。 */
|
|
4257
|
+
function statusTextOf(element, request, target) {
|
|
4258
|
+
if (target.secret === true) return void 0;
|
|
4259
|
+
let text = [...element.ownerDocument.querySelectorAll(STATUS_SELECTORS.join(","))].filter((node) => !hidden(node)).map((node) => compactText(node)).filter((text) => text !== void 0).at(-1);
|
|
4260
|
+
if (text === void 0) return void 0;
|
|
4261
|
+
const value = request.value;
|
|
4262
|
+
if (value !== void 0 && value.length > 0) text = text.split(value).join("[redacted]");
|
|
4263
|
+
return text.length > 200 ? `${text.slice(0, 197)}...` : text;
|
|
4264
|
+
}
|
|
4265
|
+
function withChanges(outcome, mutations, element, request) {
|
|
4266
|
+
if (!outcome.ok || outcome.noop === true || outcome.navigated === true) return outcome;
|
|
4267
|
+
const snapshot = mutations.take();
|
|
4268
|
+
const status = statusTextOf(element, request, outcome.target);
|
|
4269
|
+
if (snapshot.added === 0 && snapshot.removed === 0 && status === void 0) return outcome;
|
|
4270
|
+
return {
|
|
4271
|
+
...outcome,
|
|
4272
|
+
changes: {
|
|
4273
|
+
added: snapshot.added,
|
|
4274
|
+
removed: snapshot.removed,
|
|
4275
|
+
...status !== void 0 ? { status } : {}
|
|
4276
|
+
}
|
|
4277
|
+
};
|
|
4278
|
+
}
|
|
4006
4279
|
/**
|
|
4007
4280
|
* 一次拖拽(FR-17.5)。真实页面有两套互不相通的机制,按起点自动选:
|
|
4008
4281
|
*
|
|
@@ -4279,9 +4552,23 @@ function createDomPageActionExecutor(options) {
|
|
|
4279
4552
|
...frame !== void 0 ? { frame } : {}
|
|
4280
4553
|
};
|
|
4281
4554
|
};
|
|
4555
|
+
const recoveredRefs = /* @__PURE__ */ new Map();
|
|
4556
|
+
const rememberRecovered = (ref, element) => {
|
|
4557
|
+
recoveredRefs.delete(ref);
|
|
4558
|
+
recoveredRefs.set(ref, element);
|
|
4559
|
+
while (recoveredRefs.size > 32) {
|
|
4560
|
+
const oldest = recoveredRefs.keys().next().value;
|
|
4561
|
+
if (oldest === void 0) break;
|
|
4562
|
+
recoveredRefs.delete(oldest);
|
|
4563
|
+
}
|
|
4564
|
+
};
|
|
4565
|
+
const recoverAction = (ref) => reader.recoverElement?.(ref, "action");
|
|
4282
4566
|
const describe = (ref) => {
|
|
4283
|
-
const
|
|
4567
|
+
const resolved = reader.resolve(ref);
|
|
4568
|
+
const live = resolved?.isConnected === true ? resolved : void 0;
|
|
4569
|
+
const element = live ?? recoverAction(ref);
|
|
4284
4570
|
if (element === void 0) return void 0;
|
|
4571
|
+
if (live === void 0) rememberRecovered(ref, element);
|
|
4285
4572
|
const described = withFrame(ref, describeElement(element));
|
|
4286
4573
|
return reader.modalStateOf(element) === "elevated" ? {
|
|
4287
4574
|
...described,
|
|
@@ -4366,7 +4653,15 @@ function createDomPageActionExecutor(options) {
|
|
|
4366
4653
|
};
|
|
4367
4654
|
const execute = async (request) => {
|
|
4368
4655
|
if (request.action === "back") return await goBack();
|
|
4369
|
-
const
|
|
4656
|
+
const cached = recoveredRefs.get(request.ref);
|
|
4657
|
+
const liveResolved = reader.resolve(request.ref);
|
|
4658
|
+
let element = cached ?? (liveResolved?.isConnected === true ? liveResolved : void 0);
|
|
4659
|
+
let recovered = cached !== void 0;
|
|
4660
|
+
if (element === void 0) {
|
|
4661
|
+
element = recoverAction(request.ref);
|
|
4662
|
+
recovered = element !== void 0;
|
|
4663
|
+
if (element !== void 0) rememberRecovered(request.ref, element);
|
|
4664
|
+
}
|
|
4370
4665
|
if (element === void 0) return {
|
|
4371
4666
|
ok: false,
|
|
4372
4667
|
target: { role: "generic" },
|
|
@@ -4378,7 +4673,15 @@ function createDomPageActionExecutor(options) {
|
|
|
4378
4673
|
target,
|
|
4379
4674
|
reason
|
|
4380
4675
|
});
|
|
4381
|
-
if (reader.handleKindOf(request.ref) === "anchor")
|
|
4676
|
+
if (reader.handleKindOf(request.ref) === "anchor") {
|
|
4677
|
+
let inside;
|
|
4678
|
+
try {
|
|
4679
|
+
inside = await reader.actionChildRefs?.(request.ref, 5);
|
|
4680
|
+
} catch {
|
|
4681
|
+
inside = void 0;
|
|
4682
|
+
}
|
|
4683
|
+
return fail(containerActionText(reader, inside));
|
|
4684
|
+
}
|
|
4382
4685
|
if (reader.handleKindOf(request.ref) === "image") return fail("That reference points at an image, which cannot be acted on. Use it with capture_page_image to read the picture itself.");
|
|
4383
4686
|
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.");
|
|
4384
4687
|
if (reader.modalStateOf(element) !== "elevated" && !reader.inActionScope(element)) return fail("The element is no longer inside the actionable scope.");
|
|
@@ -4402,6 +4705,7 @@ function createDomPageActionExecutor(options) {
|
|
|
4402
4705
|
const navigated = keyAfter !== void 0 && keyBefore !== void 0 && keyAfter !== keyBefore;
|
|
4403
4706
|
return {
|
|
4404
4707
|
...outcome,
|
|
4708
|
+
...recovered ? { recovered: true } : {},
|
|
4405
4709
|
...name !== void 0 ? { elevatedModal: name } : {},
|
|
4406
4710
|
...navigated ? {
|
|
4407
4711
|
navigated: true,
|
|
@@ -4409,93 +4713,96 @@ function createDomPageActionExecutor(options) {
|
|
|
4409
4713
|
} : {}
|
|
4410
4714
|
};
|
|
4411
4715
|
};
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
}
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
}
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
}
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4716
|
+
const mutations = watchMutations(element);
|
|
4717
|
+
try {
|
|
4718
|
+
if (request.action === "click") {
|
|
4719
|
+
press(element);
|
|
4720
|
+
const outcome = await done({
|
|
4721
|
+
ok: true,
|
|
4722
|
+
target
|
|
4723
|
+
});
|
|
4724
|
+
return mutations.take().changed || outcome.navigated === true || outcome.elevatedModal !== void 0 || popoverOpenBeside(element) ? withChanges(outcome, mutations, element, request) : {
|
|
4725
|
+
...outcome,
|
|
4726
|
+
noop: true
|
|
4727
|
+
};
|
|
4728
|
+
}
|
|
4729
|
+
if (request.action === "fill") {
|
|
4730
|
+
if (!FILLABLE_TAGS.has(element.tagName)) return fail("The element is not a text control.");
|
|
4731
|
+
if (element.hasAttribute("readonly")) return fail("The element is read-only. Read-only text fields often open a picker when clicked, so try the click action on this same element.");
|
|
4732
|
+
fillValue(element, request.value ?? "");
|
|
4733
|
+
return withChanges(await done({
|
|
4734
|
+
ok: true,
|
|
4735
|
+
target
|
|
4736
|
+
}), mutations, element, request);
|
|
4737
|
+
}
|
|
4738
|
+
if (request.action === "select") {
|
|
4739
|
+
const outcome = await selectOption(element, request.value ?? "");
|
|
4740
|
+
return outcome.ok ? withChanges(await done({
|
|
4741
|
+
...outcome,
|
|
4742
|
+
target
|
|
4743
|
+
}), mutations, element, request) : {
|
|
4744
|
+
...outcome,
|
|
4745
|
+
target
|
|
4746
|
+
};
|
|
4747
|
+
}
|
|
4748
|
+
if (request.action === "set") return withChanges(await done(setToggle(element, request.value ?? "", target)), mutations, element, request);
|
|
4749
|
+
if (request.action === "scroll") {
|
|
4750
|
+
if (!SCROLL_DIRECTIONS.includes(request.value)) return fail(`Use ${SCROLL_DIRECTIONS.map((d) => `"${d}"`).join(" / ")}.`);
|
|
4751
|
+
const scrolled = withChanges(await scrollRegion(element, target, request.value), mutations, element, request);
|
|
4752
|
+
return recovered && scrolled.ok ? {
|
|
4753
|
+
...scrolled,
|
|
4754
|
+
recovered: true
|
|
4755
|
+
} : scrolled;
|
|
4756
|
+
}
|
|
4757
|
+
if (request.action === "hover") {
|
|
4758
|
+
hoverOver(element);
|
|
4759
|
+
await waitFor(() => mutations.take().changed ? true : void 0, 500);
|
|
4760
|
+
return withChanges(await done({
|
|
4761
|
+
ok: true,
|
|
4762
|
+
target,
|
|
4763
|
+
...mutations.take().changed || popoverOpenBeside(element) ? {} : { noop: true }
|
|
4764
|
+
}), mutations, element, request);
|
|
4765
|
+
}
|
|
4766
|
+
if (request.action === "drag") {
|
|
4767
|
+
const destination = request.value ?? "";
|
|
4768
|
+
if (destination === "") return fail("The drag action needs a value holding the destination ref.");
|
|
4769
|
+
const dropTarget = reader.resolve(destination);
|
|
4770
|
+
if (dropTarget === void 0) return fail("The destination reference is unknown or expired.");
|
|
4771
|
+
if (reader.handleKindOf(destination) === "image") return fail("The destination reference points at an image, which cannot receive a drop.");
|
|
4772
|
+
if (reader.modalStateOf(dropTarget) !== "elevated" && !reader.inActionScope(dropTarget)) return fail("The destination is no longer inside the actionable scope.");
|
|
4773
|
+
if (hidden(dropTarget)) return fail("The destination is not visible.");
|
|
4774
|
+
const refused = dragBetween(element, dropTarget);
|
|
4775
|
+
if (refused !== void 0) return fail(refused);
|
|
4776
|
+
return withChanges(await done({
|
|
4777
|
+
ok: true,
|
|
4778
|
+
target
|
|
4779
|
+
}), mutations, element, request);
|
|
4780
|
+
}
|
|
4781
|
+
if (request.action === "attach") {
|
|
4782
|
+
const files = await options.pickFiles?.();
|
|
4783
|
+
if (files === void 0 || files.length === 0) return {
|
|
4784
|
+
ok: false,
|
|
4785
|
+
target,
|
|
4786
|
+
reason: "The user cancelled the file selection."
|
|
4787
|
+
};
|
|
4788
|
+
if (element.tagName !== "INPUT" || element.type !== "file") return fail("The element is not a file input.");
|
|
4789
|
+
const failure = attachFiles(element, files);
|
|
4790
|
+
if (failure !== void 0) return fail(failure);
|
|
4791
|
+
return withChanges(await done({
|
|
4792
|
+
ok: true,
|
|
4793
|
+
target
|
|
4794
|
+
}), mutations, element, request);
|
|
4795
|
+
}
|
|
4796
|
+
const form = element.tagName === "FORM" ? element : element.form;
|
|
4797
|
+
if (!form) return fail("The element does not belong to a form.");
|
|
4798
|
+
form.requestSubmit();
|
|
4799
|
+
return withChanges(await done({
|
|
4488
4800
|
ok: true,
|
|
4489
4801
|
target
|
|
4490
|
-
});
|
|
4802
|
+
}), mutations, element, request);
|
|
4803
|
+
} finally {
|
|
4804
|
+
mutations.stop();
|
|
4491
4805
|
}
|
|
4492
|
-
const form = element.tagName === "FORM" ? element : element.form;
|
|
4493
|
-
if (!form) return fail("The element does not belong to a form.");
|
|
4494
|
-
form.requestSubmit();
|
|
4495
|
-
return await done({
|
|
4496
|
-
ok: true,
|
|
4497
|
-
target
|
|
4498
|
-
});
|
|
4499
4806
|
};
|
|
4500
4807
|
return {
|
|
4501
4808
|
execute,
|
|
@@ -6534,10 +6841,17 @@ async function readDocxBlocks(bytes) {
|
|
|
6534
6841
|
* 图不是浮在表外的附件,它压着某几行几列——按锚点插回行流里,
|
|
6535
6842
|
* 模型才知道「这张图说的是上面那几行」。
|
|
6536
6843
|
*/
|
|
6537
|
-
/**
|
|
6844
|
+
/** @experimental @deprecated 0.27.0 起不再按跨度过滤图片;保留导出仅为兼容既有调用。 */
|
|
6538
6845
|
const XLSX_IMAGE_MIN_COLUMNS = 2;
|
|
6539
|
-
/** @experimental */
|
|
6846
|
+
/** @experimental @deprecated 0.27.0 起不再按跨度过滤图片;保留导出仅为兼容既有调用。 */
|
|
6540
6847
|
const XLSX_IMAGE_MIN_ROWS = 3;
|
|
6848
|
+
const ANCHOR_TAGS = /* @__PURE__ */ new Set([
|
|
6849
|
+
"twoCellAnchor",
|
|
6850
|
+
"oneCellAnchor",
|
|
6851
|
+
"absoluteAnchor"
|
|
6852
|
+
]);
|
|
6853
|
+
/** 绝对锚点没有行号;放在表尾,保证图片不丢。 */
|
|
6854
|
+
const AFTER_LAST_ROW = Number.MAX_SAFE_INTEGER;
|
|
6541
6855
|
const RELATIONSHIPS_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
6542
6856
|
const byTag$2 = (scope, name) => [...scope.getElementsByTagNameNS("*", name)];
|
|
6543
6857
|
function parseXml$1(bytes) {
|
|
@@ -6575,7 +6889,13 @@ const anchorNumber = (anchor, name) => {
|
|
|
6575
6889
|
const value = Number(node?.textContent ?? NaN);
|
|
6576
6890
|
return Number.isInteger(value) && value >= 0 ? value : 0;
|
|
6577
6891
|
};
|
|
6578
|
-
/**
|
|
6892
|
+
/**
|
|
6893
|
+
* 一张表上的图:行号(0 基)→ 该行下面要插的图块。
|
|
6894
|
+
*
|
|
6895
|
+
* 0.27.0 起读所有 DrawingML 锚点、所有尺寸:`twoCellAnchor`、`oneCellAnchor` 都按
|
|
6896
|
+
* `from` 行插回;`absoluteAnchor` 没有单元格坐标,补在表尾。旧版按跨列/跨行阈值过滤,
|
|
6897
|
+
* 会把小图标、单元格小图、以及本仓 `fill_template` 自己写的 `oneCellAnchor` 全部丢掉。
|
|
6898
|
+
*/
|
|
6579
6899
|
function imagesOf(parts, sheetPath) {
|
|
6580
6900
|
const placed = /* @__PURE__ */ new Map();
|
|
6581
6901
|
const sheetRels = readRels(parts, sheetPath);
|
|
@@ -6585,21 +6905,18 @@ function imagesOf(parts, sheetPath) {
|
|
|
6585
6905
|
const drawing = drawingBytes === void 0 ? void 0 : parseXml$1(drawingBytes);
|
|
6586
6906
|
if (drawing === void 0) continue;
|
|
6587
6907
|
const drawingRels = readRels(parts, target);
|
|
6588
|
-
|
|
6908
|
+
const anchors = [...drawing.documentElement?.children ?? []].filter((element) => ANCHOR_TAGS.has(element.localName));
|
|
6909
|
+
for (const anchor of anchors) {
|
|
6589
6910
|
const from = byTag$2(anchor, "from")[0];
|
|
6590
|
-
const
|
|
6591
|
-
const fromRow = anchorNumber(from, "row");
|
|
6592
|
-
const columns = anchorNumber(to, "col") - anchorNumber(from, "col");
|
|
6593
|
-
const rows = anchorNumber(to, "row") - fromRow;
|
|
6594
|
-
if (columns < 2 || rows < 3) continue;
|
|
6911
|
+
const row = anchor.localName === "absoluteAnchor" || from === void 0 ? AFTER_LAST_ROW : anchorNumber(from, "row");
|
|
6595
6912
|
const blip = byTag$2(anchor, "blip")[0];
|
|
6596
6913
|
const relId = blip?.getAttributeNS(RELATIONSHIPS_NS, "embed") ?? blip?.getAttribute("r:embed");
|
|
6597
6914
|
const path = relId === null || relId === void 0 ? void 0 : drawingRels.get(relId);
|
|
6598
6915
|
const data = path === void 0 ? void 0 : parts.get(path);
|
|
6599
6916
|
if (path === void 0 || data === void 0) continue;
|
|
6600
|
-
const list = placed.get(
|
|
6917
|
+
const list = placed.get(row) ?? [];
|
|
6601
6918
|
list.push(imageBlockOf(path, () => toBase64$1(data)));
|
|
6602
|
-
placed.set(
|
|
6919
|
+
placed.set(row, list);
|
|
6603
6920
|
}
|
|
6604
6921
|
}
|
|
6605
6922
|
return placed;
|