@mearl/client 2.1.0 → 2.2.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 CHANGED
@@ -40,6 +40,9 @@ mearl get_requests --payload '{"count":5}'
40
40
  # 代理一个 HTTP 请求
41
41
  mearl send_request --payload '{"url":"https://api.example.com","method":"GET"}'
42
42
 
43
+ # 以 multipart/form-data 上传本地文件
44
+ mearl send_request --payload '{"url":"https://api.example.com/upload","method":"POST","formData":{"folder":"assets"},"files":[{"fieldName":"file","filePath":"/absolute/path/image.png"}]}'
45
+
43
46
  # 截图并保存
44
47
  mearl page_screenshot --output ./screenshot.png
45
48
 
@@ -88,7 +91,7 @@ mearl browser_release
88
91
  | TDBank | `tdbank_account` |
89
92
  | 浏览器 | `browser_list` `browser_release` `browser_launch` `browser_close` |
90
93
 
91
- 页面交互动作(`page_click` / `page_type` / `page_hover` / `page_scroll` / `page_press` / `page_upload`)默认内置观察:同一次调用内执行动作并等待异步稳定,返回 `{ action, observation }`;传 `observe: false` 可关闭观察仅执行裸动作。观察结果不替代完整页面理解:`mode: "delta"` 只返回主文档中的 `effects.notifications`、`effects.interactives` 和 `effects.focus` 等高置信度信号,并明确携带 `scope: "main-document"`;可交互节点会尽量携带真实 backend `node.ref`,后续动作优先使用 ref,缺少 ref 时使用 `node.selector`。`mode: "navigation"` 且 `ready: true` 时,页面已通过网络静默、骨架状态或保守的内容稳定判定,可在新页面重建快照。通常仅在 `fullSnapshotRecommended` 为 true 时根据 `snapshotReasons` 回退;滚动后若下一步需要读取新视口内容,可按需获取 viewport 快照。`page_eval` 默认裸执行,只有显式传入 `observe` 对象时才启用观察。
94
+ 页面交互动作(`page_click` / `page_type` / `page_hover` / `page_scroll` / `page_press` / `page_upload`)默认内置观察:同一次调用内执行动作并等待异步稳定,返回 `{ action, observation }`;传 `observe: false` 可关闭观察仅执行裸动作。观察结果不替代完整页面理解:`mode: "delta"` 只返回主文档中的 `effects.notifications`、`effects.interactives` 和 `effects.focus` 等高置信度信号,并明确携带 `scope: "main-document"`;可交互节点会尽量携带真实 backend `node.ref`,后续动作优先使用 ref,缺少 ref 时使用 `node.selector`。`mode: "navigation"` 且 `ready: true` 时,页面已通过网络静默、骨架状态或保守的内容稳定判定,可在新页面重建快照。动作直接打开新标签页时,`observation.openedTabs` 返回新标签页的 `tabId`、URL、标题和加载状态,可直接把该 `tabId` 用于后续操作,不必调用 `tab_list`。通常仅在 `fullSnapshotRecommended` 为 true 时根据 `snapshotReasons` 回退;滚动后若下一步需要读取新视口内容,可按需获取 viewport 快照。`page_eval` 默认裸执行,只有显式传入 `observe` 对象时才启用观察。
92
95
 
93
96
  `page_snapshot` 默认返回完整 AX Tree;长列表可传 `mode: "viewport"`,只需要当前视口内的控件时传 `mode: "interactive"`,已知 CSS 区域时传 `rootSelector`,已有 ref 时传 `rootRef`(可用 `ancestorDepth` 向上补充上下文),只查找特定文案或角色时传 `query`。视口内缺少 AX 控件语义时,`interactive` 会自动回退到 viewport,并返回 `fallbackMode: "viewport"`。`maxNodes` / `maxChars` 截断会同时保留首尾内容。
94
97
 
@@ -96,7 +99,7 @@ mearl browser_release
96
99
 
97
100
  `browser_list` 统一列出普通浏览器和托管浏览器。`type` 区分 `regular` / `managed`,`status` 区分 `connected` / `running_disconnected` / `stopped`;只有 `connected` 的浏览器可作为操作目标。
98
101
 
99
- `browser_release` 释放当前目标浏览器的 debugger/CDP 控制和会话级临时状态,保留浏览器及全部标签页;后续浏览器操作可自动重新建立控制。
102
+ `browser_release` 仅在用户明确要求结束调试、退出接管或清理会话资源时使用,不作为普通任务收尾。它会释放当前目标浏览器的 debugger/CDP 控制和会话级临时状态(包括设备/时区模拟),保留浏览器及全部标签页;后续浏览器操作可自动重新建立控制。
100
103
 
101
104
  托管浏览器使用独立 Profile 和动态 CDP 端口。控制浏览器需先登录 TDBank;生成的 SSO 地址在本地内部传递,新实例可以使用 `headless: true`(默认)完成测试账号登录。`copyCookieDomains` 可把控制浏览器指定域的 Cookie 复制到新实例,Cookie 值不会出现在命令结果或日志中;`userAgentMode: "desktop"` 可让实例使用匹配本机 Chrome 版本的桌面 UA。完整设计见 [托管浏览器与 TDBank 多账号设计](../../docs/managed-browsers.md)。
102
105
 
@@ -1,19 +1,12 @@
1
+ import { PAGE_ACT_ACTIONS } from './generated-browser-action-protocol.js';
2
+ const SEND_REQUEST_FILE_TRANSFER_TIMEOUT_MS = 120_000;
1
3
  function boundedNumber(value, fallback, minimum, maximum) {
2
4
  const numeric = Number(value);
3
5
  if (!Number.isFinite(numeric))
4
6
  return fallback;
5
7
  return Math.min(Math.max(numeric, minimum), maximum);
6
8
  }
7
- // 默认内置观察的页面交互动作。client 作为零依赖包本地维护,
8
- // 需与 browser-core/pageAct.ts 的 PAGE_ACT_ACTIONS 保持一致。
9
- const OBSERVED_PAGE_ACTIONS = new Set([
10
- 'page_click',
11
- 'page_type',
12
- 'page_hover',
13
- 'page_scroll',
14
- 'page_press',
15
- 'page_upload',
16
- ]);
9
+ const OBSERVED_PAGE_ACTIONS = new Set(PAGE_ACT_ACTIONS);
17
10
  export function resolveObservedActionTimeoutSec(data) {
18
11
  const observe = data.observe ?? {};
19
12
  const observationMs = boundedNumber(observe.timeoutMs, 3000, 500, 30000);
@@ -24,7 +17,10 @@ export function resolveObservedActionTimeoutSec(data) {
24
17
  }
25
18
  export function resolveActionTimeoutSec(action, data, fallback = 15) {
26
19
  if (action === 'send_request') {
27
- return Math.max(fallback, Math.ceil(((data.timeout ?? 30000) + 5000) / 1000));
20
+ const transferTimeoutMs = Array.isArray(data.files) && data.files.length > 0
21
+ ? SEND_REQUEST_FILE_TRANSFER_TIMEOUT_MS
22
+ : 0;
23
+ return Math.max(fallback, Math.ceil(((data.timeout ?? 30000) + transferTimeoutMs + 5000) / 1000));
28
24
  }
29
25
  if (action === 'send_mtop_request') {
30
26
  return Math.max(fallback, Math.ceil(((data.timeout ?? 10000) + 5000) / 1000));
@@ -41,13 +37,23 @@ export function resolveActionTimeoutSec(action, data, fallback = 15) {
41
37
  return Math.max(fallback, 90);
42
38
  if (action === 'browser_close' || action === 'tdbank_account')
43
39
  return Math.max(fallback, 20);
40
+ if (action === 'run_actions') {
41
+ const actions = Array.isArray(data.actions) ? data.actions : [{}];
42
+ const timeoutSec = actions.reduce((total, step) => {
43
+ const stepAction = String(step?.action ?? '');
44
+ if (!stepAction || stepAction === 'run_actions')
45
+ return total + 15;
46
+ return total + resolveActionTimeoutSec(stepAction, step?.data ?? {}, 15);
47
+ }, 0);
48
+ return Math.max(fallback, timeoutSec);
49
+ }
44
50
  // 页面交互动作默认内置观察,超时需覆盖观察与导航窗口;observe:false 时是裸动作,用默认超时
45
- if (OBSERVED_PAGE_ACTIONS.has(action) && data.observe !== false) {
51
+ const observedAction = OBSERVED_PAGE_ACTIONS.has(action);
52
+ const observationEnabled = action === 'page_eval'
53
+ ? data.observe !== null && typeof data.observe === 'object' && !Array.isArray(data.observe)
54
+ : data.observe !== false;
55
+ if (observedAction && observationEnabled) {
46
56
  return Math.max(fallback, resolveObservedActionTimeoutSec(data));
47
57
  }
48
- if (action === 'run_actions') {
49
- const steps = Array.isArray(data.actions) ? data.actions.length : 1;
50
- return Math.max(fallback, steps * 15);
51
- }
52
58
  return fallback;
53
59
  }