@coolkiller007/my-page-agent 0.2.5 → 0.2.7
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 +6 -12
- package/dist/index.js +76 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# My Page Agent
|
|
2
2
|
|
|
3
|
-
面向中台的页面内嵌 GUI Agent
|
|
4
|
-
单一 npm 包交付。
|
|
3
|
+
面向中台的页面内嵌 GUI Agent。项目将自然语言任务转换为受控的页面读取与操作,并以单一 npm 包交付。
|
|
5
4
|
|
|
6
5
|
## Installation
|
|
7
6
|
|
|
@@ -33,23 +32,19 @@ writeResult(result.data)
|
|
|
33
32
|
agent.dispose()
|
|
34
33
|
```
|
|
35
34
|
|
|
36
|
-
生产环境必须通过公司后端代理或 AI 网关访问模型,不得把生产 API Key
|
|
37
|
-
物。页面内容在发送给模型前也应通过 `transformPageContent` 完成业务所需的脱敏。
|
|
35
|
+
生产环境必须通过公司后端代理或 AI 网关访问模型,不得把生产 API Key 打进浏览器产物。页面内容在发送给模型前也应通过 `transformPageContent` 完成业务所需的脱敏。
|
|
38
36
|
|
|
39
37
|
## Public API
|
|
40
38
|
|
|
41
39
|
包根入口是唯一公共入口:
|
|
42
40
|
|
|
43
41
|
- `createAgent(config)`:创建并组装 `MyPageAgent`。
|
|
44
|
-
- `MyPageAgent`:Agent 门面;提供 `execute(task)`、`stop()`、`dispose()`
|
|
45
|
-
`status`。
|
|
42
|
+
- `MyPageAgent`:Agent 门面;提供 `execute(task)`、`stop()`、`dispose()` 和只读`status`。
|
|
46
43
|
- `agent.ui`:提供 `show()`、`hide()`、`expand()` 和 `collapse()`。
|
|
47
44
|
- `tool(options)`:定义自定义 Agent 工具。
|
|
48
|
-
- 类型:`MyPageAgentConfig`、`AgentActivity`、`AgentStatus`、`ExecutionResult`
|
|
49
|
-
和 `HistoricalEvent`。
|
|
45
|
+
- 类型:`MyPageAgentConfig`、`AgentActivity`、`AgentStatus`、`ExecutionResult`和 `HistoricalEvent`。
|
|
50
46
|
|
|
51
|
-
`AgentRuntime`、`BrowserController`、`OpenAICompatibleClient`、UI 内部组件、DOM
|
|
52
|
-
Tree、Prompt 和自动修复器属于内部实现,不从包根入口导出。
|
|
47
|
+
`AgentRuntime`、`BrowserController`、`OpenAICompatibleClient`、UI 内部组件、DOM Tree、Prompt 和自动修复器属于内部实现,不从包根入口导出。
|
|
53
48
|
|
|
54
49
|
## Architecture
|
|
55
50
|
|
|
@@ -60,5 +55,4 @@ Tree、Prompt 和自动修复器属于内部实现,不从包根入口导出。
|
|
|
60
55
|
- `src/shared`:跨能力共享的无状态工具。
|
|
61
56
|
- `src/index.ts`:限定公共 API 的唯一包入口。
|
|
62
57
|
|
|
63
|
-
项目只发布 `@coolkiller007/my-page-agent`,不使用 npm workspaces。Browser、
|
|
64
|
-
me 和 UI 只在 `MyPageAgent` 中组装,内部模块使用相对 import。
|
|
58
|
+
项目只发布 `@coolkiller007/my-page-agent`,不使用 npm workspaces。Browser、Runtime 和 UI 只在 `MyPageAgent` 中组装,内部模块使用相对 import。
|
package/dist/index.js
CHANGED
|
@@ -235,7 +235,8 @@ async function selectOptionElement(selectElement, optionText) {
|
|
|
235
235
|
if (!isSelectElement(selectElement)) throw new Error("Element is not a select element");
|
|
236
236
|
const option = Array.from(selectElement.options).find((opt) => opt.textContent?.trim() === optionText.trim());
|
|
237
237
|
if (!option) throw new Error(`Option with text "${optionText}" not found in select element`);
|
|
238
|
-
selectElement.
|
|
238
|
+
if (selectElement.multiple) option.selected = true;
|
|
239
|
+
else selectElement.value = option.value;
|
|
239
240
|
selectElement.dispatchEvent(new Event("change", { bubbles: true }));
|
|
240
241
|
await waitFor$1(.05);
|
|
241
242
|
}
|
|
@@ -989,6 +990,53 @@ var dom_tree_default = (args = {
|
|
|
989
990
|
return false;
|
|
990
991
|
}
|
|
991
992
|
/**
|
|
993
|
+
* 判断某元素(或其祖先)是否带有 data-browser-use-ignore / data-page-agent-ignore 标记。
|
|
994
|
+
* 这些是我们自己注入的悬浮 UI(agent 面板、模拟遮罩等),命中测试时应视为"透明"。
|
|
995
|
+
*
|
|
996
|
+
* @param {Element | null} el - 待检查元素。
|
|
997
|
+
* @returns {boolean} 是否应在遮挡检测中被忽略。
|
|
998
|
+
*/
|
|
999
|
+
function isIgnoredForHitTest(el) {
|
|
1000
|
+
let current = el;
|
|
1001
|
+
while (current) {
|
|
1002
|
+
if (current.dataset && (current.dataset.browserUseIgnore === "true" || current.dataset.pageAgentIgnore === "true")) return true;
|
|
1003
|
+
current = current.parentElement;
|
|
1004
|
+
}
|
|
1005
|
+
return false;
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* @edit 命中测试时跳过被忽略的悬浮 UI(agent 面板、模拟遮罩等)。
|
|
1009
|
+
* 之前只有 SimulatorMask 会在 DOM 提取期间临时把 pointerEvents 设为 none 来绕开自己,
|
|
1010
|
+
* 但 agent 自身的问答/历史面板(UI.ts)没有做同样处理 —— 当面板展开、在视觉上盖住
|
|
1011
|
+
* 弹窗等页面元素时,elementFromPoint 命中的是面板本身,导致弹窗被误判为"被遮挡"而从
|
|
1012
|
+
* 提交给 LLM 的可交互树中消失,造成"弹窗仍开着,agent 却认为已关闭"的错觉。
|
|
1013
|
+
* 这里统一在遮挡检测层面处理:命中被忽略元素时临时禁用其 pointer-events 再重新探测,
|
|
1014
|
+
* 探测完成后恢复,使得任何打了忽略标记的悬浮 UI 都不会影响真实页面元素的遮挡判断。
|
|
1015
|
+
*
|
|
1016
|
+
* @param {Document | ShadowRoot} doc - 用于命中测试的文档或 shadow root。
|
|
1017
|
+
* @param {number} x - 命中测试点的 x 坐标。
|
|
1018
|
+
* @param {number} y - 命中测试点的 y 坐标。
|
|
1019
|
+
* @returns {Element | null} 忽略悬浮 UI 后,实际命中的最上层元素。
|
|
1020
|
+
*/
|
|
1021
|
+
function elementFromPointSkippingIgnored(doc, x, y) {
|
|
1022
|
+
/** @type {{ el: HTMLElement, prevValue: string }[]} */
|
|
1023
|
+
const restoreList = [];
|
|
1024
|
+
try {
|
|
1025
|
+
let topEl = doc.elementFromPoint(x, y);
|
|
1026
|
+
while (topEl instanceof HTMLElement && isIgnoredForHitTest(topEl)) {
|
|
1027
|
+
restoreList.push({
|
|
1028
|
+
el: topEl,
|
|
1029
|
+
prevValue: topEl.style.pointerEvents
|
|
1030
|
+
});
|
|
1031
|
+
topEl.style.pointerEvents = "none";
|
|
1032
|
+
topEl = doc.elementFromPoint(x, y);
|
|
1033
|
+
}
|
|
1034
|
+
return topEl;
|
|
1035
|
+
} finally {
|
|
1036
|
+
for (const { el, prevValue } of restoreList) el.style.pointerEvents = prevValue;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
992
1040
|
* Checks if an element is the topmost element at its position.
|
|
993
1041
|
*
|
|
994
1042
|
* @param {HTMLElement} element - The element to check.
|
|
@@ -1022,7 +1070,7 @@ var dom_tree_default = (args = {
|
|
|
1022
1070
|
const centerX = rect.left + rect.width / 2;
|
|
1023
1071
|
const centerY = rect.top + rect.height / 2;
|
|
1024
1072
|
try {
|
|
1025
|
-
const topEl = shadowRoot
|
|
1073
|
+
const topEl = elementFromPointSkippingIgnored(shadowRoot, centerX, centerY);
|
|
1026
1074
|
if (!topEl) return false;
|
|
1027
1075
|
let current = topEl;
|
|
1028
1076
|
while (current && current !== shadowRoot) {
|
|
@@ -1050,7 +1098,7 @@ var dom_tree_default = (args = {
|
|
|
1050
1098
|
}
|
|
1051
1099
|
].some(({ x, y }) => {
|
|
1052
1100
|
try {
|
|
1053
|
-
const topEl = document
|
|
1101
|
+
const topEl = elementFromPointSkippingIgnored(document, x, y);
|
|
1054
1102
|
if (!topEl) return false;
|
|
1055
1103
|
let current = topEl;
|
|
1056
1104
|
while (current && current !== document.documentElement) {
|
|
@@ -1365,10 +1413,27 @@ var dom_tree_default = (args = {
|
|
|
1365
1413
|
* 若不同步,选择后重新快照时 LLM 看不到变化,会误判操作未生效而反复执行 select 操作。
|
|
1366
1414
|
*/
|
|
1367
1415
|
if (node.tagName.toLowerCase() === "select") {
|
|
1368
|
-
|
|
1369
|
-
|
|
1416
|
+
if (node.multiple) nodeData.attributes.value = Array.from(node.options).filter((opt) => opt.selected).map((opt) => opt.text).join(", ");
|
|
1417
|
+
else {
|
|
1418
|
+
const selectedOption = node.options[node.selectedIndex];
|
|
1419
|
+
nodeData.attributes.value = selectedOption ? selectedOption.text : node.value;
|
|
1420
|
+
}
|
|
1370
1421
|
}
|
|
1371
1422
|
/**
|
|
1423
|
+
* @edit @workaround input/textarea.value
|
|
1424
|
+
* 文本类输入框(含日期选择器底层常见的 <input type="text">)当前内容同样是 DOM 属性,
|
|
1425
|
+
* 不会反映到 HTML attribute 上,若不同步,输入后重新快照时 LLM 看不到变化,
|
|
1426
|
+
* 会误判输入未生效而反复重试。password/file/hidden 等类型不同步,避免泄露敏感值或无意义路径。
|
|
1427
|
+
*/
|
|
1428
|
+
const NO_VALUE_SYNC_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
1429
|
+
"password",
|
|
1430
|
+
"file",
|
|
1431
|
+
"hidden",
|
|
1432
|
+
"checkbox",
|
|
1433
|
+
"radio"
|
|
1434
|
+
]);
|
|
1435
|
+
if (node.tagName.toLowerCase() === "input" && !NO_VALUE_SYNC_INPUT_TYPES.has(node.type) || node.tagName.toLowerCase() === "textarea") nodeData.attributes.value = node.value;
|
|
1436
|
+
/**
|
|
1372
1437
|
* @edit @workaround label-wrapped checkbox/radio checked
|
|
1373
1438
|
* 常见组件库(如 antd)把 checkbox/radio 包在 <label> 里,视觉上的选中框覆盖在原生
|
|
1374
1439
|
* input 上面,导致 input 不是 isTopElement,不会被收进可交互树、也就打印不出 checked
|
|
@@ -1586,7 +1651,11 @@ function flatTreeToString(flatTree, includeAttributes = [], keepSemanticTags = f
|
|
|
1586
1651
|
"aria-haspopup",
|
|
1587
1652
|
"aria-controls",
|
|
1588
1653
|
"aria-owns",
|
|
1589
|
-
"contenteditable"
|
|
1654
|
+
"contenteditable",
|
|
1655
|
+
"aria-valuenow",
|
|
1656
|
+
"aria-valuetext",
|
|
1657
|
+
"aria-valuemin",
|
|
1658
|
+
"aria-valuemax"
|
|
1590
1659
|
];
|
|
1591
1660
|
const includeAttrs = [...includeAttributes, ...DEFAULT_INCLUDE_ATTRIBUTES];
|
|
1592
1661
|
const capTextLength = (text, maxLength) => {
|
|
@@ -2800,7 +2869,7 @@ var UI = class {
|
|
|
2800
2869
|
if (status === "running") {
|
|
2801
2870
|
this.show();
|
|
2802
2871
|
this.#hideInputArea();
|
|
2803
|
-
if (
|
|
2872
|
+
if (this.#isExpanded) this.#collapse();
|
|
2804
2873
|
}
|
|
2805
2874
|
if (status === "completed" || status === "error" || status === "stopped") {
|
|
2806
2875
|
if (!this.#isExpanded) this.#expand();
|