@mearl/client 2.9.6 → 2.10.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/README.md CHANGED
@@ -122,7 +122,7 @@ mearl browser_release
122
122
 
123
123
  `browser_release` 仅在用户明确要求结束调试、退出接管或清理会话资源时使用,不作为普通任务收尾。它会释放当前目标浏览器的 debugger/CDP 控制和会话级临时状态(包括设备/时区模拟),保留浏览器及全部标签页;后续浏览器操作可自动重新建立控制。
124
124
 
125
- 托管浏览器使用独立 Profile。控制浏览器需先登录 TDBank;生成的 SSO 地址在本地内部传递,本地实例可以使用 `headless: true`(默认)完成测试账号登录。AgentBay 固定为非 headless,传 `headless: true` 会被拒绝;`imageId` 可指定镜像别名或具体镜像 ID,省略时使用已验证的 Linux Browser Use 内网镜像。`persistent: true` 会让本地实例保留 Profile,让 AgentBay 同名实例绑定稳定的云端 Browser Context;正常关闭时同步 Cookie、LocalStorage、IndexedDB 等状态,下次启动无需再次复制本地登录态。`deleteProfile: true` 会显式删除对应 Profile 或 Context。AgentBay 的 `reuse` 支持 `never`(默认)、`prefer` 和 `require`:`prefer` 存在运行实例时复用、否则创建,`require` 只复用且不会意外创建计费会话。`copyCookieDomains` 传数组时把控制浏览器指定域的 Cookie 写入目标,传 `"mearl-services"` 时同步 Mearl 依赖平台登录态。Cookie 值不会经过 cloud-server,也不会出现在命令结果或日志中;`userAgentMode: "desktop"` 让本地实例使用匹配本机 Chrome 版本的桌面 UA,AgentBay 则复用当前控制浏览器的桌面 UA。`browser_list` 仅通过 `agentbayImageId`、`agentbayContextId` 和 `agentbayContextName` 返回 AgentBay 实例的稳定配置。无影浏览器串流入口包含临时访问凭据且会过期,因此不会写入实例记录或列表结果;需要时运行 `mearl check --browser <id|名称>` 实时获取,并按敏感信息处理。完整设计见 [托管浏览器与 TDBank 多账号设计](../../docs/managed-browsers.md)。
125
+ 托管浏览器使用独立 Profile。控制浏览器需先登录 TDBank;生成的 SSO 地址在本地内部传递,本地实例可以使用 `headless: true`(默认)完成测试账号登录。AgentBay 固定为非 headless,传 `headless: true` 会被拒绝;`imageId` 可指定镜像别名或具体镜像 ID,省略时使用已验证的 Linux Browser Use 内网镜像。`persistent: true` 会让本地实例保留 Profile,让 AgentBay 同名实例绑定稳定的云端 Browser Context;正常关闭时同步 Cookie、LocalStorage、IndexedDB 等状态,下次启动无需再次复制本地登录态。`deleteProfile: true` 会显式删除对应 Profile 或 Context。AgentBay 的 `reuse` 支持 `never`(默认)、`prefer` 和 `require`:`prefer` 存在运行实例时复用、否则创建,`require` 只复用且不会意外创建计费会话。`copyCookieDomains` 传数组时把控制浏览器指定域的 Cookie 写入目标,传 `"mearl-services"` 时同步 Mearl 依赖平台登录态。Cookie 值不会经过 cloud-server,也不会出现在命令结果或日志中;`userAgentMode: "desktop"` 让本地实例使用匹配本机 Chrome 版本的桌面 UA,AgentBay 则复用当前控制浏览器的桌面 UA。`browser_list` 仅返回浏览器的稳定配置,不携带 AgentBay 串流入口或沙箱 noVNC 地址。需要时显式运行 `mearl check --browser <id|名称>`,由统一的浏览器访问入口能力实时返回 `browserAccess`;AgentBay、OpenSandbox 和 AONE Sandbox 地址都应按敏感信息处理。完整设计见 [托管浏览器与 TDBank 多账号设计](../../docs/managed-browsers.md)。
126
126
 
127
127
  ## 架构
128
128
 
package/dist/check.d.ts CHANGED
@@ -5,6 +5,16 @@ export interface UnifiedCheckVersions {
5
5
  extension?: string | null;
6
6
  browser?: string | null;
7
7
  }
8
+ export type BrowserAccessCapability = {
9
+ kind: 'agentbay';
10
+ } | {
11
+ kind: 'sandbox-novnc';
12
+ environment: 'opensandbox' | 'aone';
13
+ port: number;
14
+ };
15
+ export type BrowserAccess = BrowserAccessCapability & {
16
+ url: string;
17
+ };
8
18
  interface BrowserInfo {
9
19
  browserId: string;
10
20
  extensionVersion?: string;
@@ -27,7 +37,7 @@ export interface ExtensionInstallationInfo {
27
37
  setupManagedPath: string | null;
28
38
  }
29
39
  export interface UnifiedCheckBrowserResult extends BrowserInfo {
30
- agentbayAccessUrl?: string;
40
+ browserAccess?: BrowserAccess;
31
41
  check?: {
32
42
  errors?: string[];
33
43
  label: string;
package/dist/check.js CHANGED
@@ -2,6 +2,20 @@ import { UnifiedClient, normalizeBrowserSelector, } from './unified.js';
2
2
  function formatError(error) {
3
3
  return error instanceof Error ? error.message : String(error);
4
4
  }
5
+ function browserAccessLabel(access) {
6
+ if (access.kind === 'agentbay')
7
+ return 'AgentBay, temporary';
8
+ return access.environment === 'opensandbox' ? 'OpenSandbox noVNC' : 'AONE Sandbox noVNC';
9
+ }
10
+ async function getBrowserAccess(client, browser, capability, timeoutSec) {
11
+ const result = (await client.invoke('get_browser_access_url', {}, { timeoutSec, browser: browser.browserId }));
12
+ if (!result.browserAccess?.url)
13
+ throw new Error('Browser host did not return an access URL');
14
+ if (result.browserAccess.kind !== capability.kind) {
15
+ throw new Error('Browser host returned an unexpected access URL type');
16
+ }
17
+ return result.browserAccess;
18
+ }
5
19
  function redactServerUrl(serverUrl) {
6
20
  try {
7
21
  const parsed = new URL(serverUrl);
@@ -223,33 +237,27 @@ export async function runUnifiedCheck(options, dependencies = {}) {
223
237
  setupManagedPath: versions.setupManagedPath,
224
238
  };
225
239
  }
226
- let agentbayAccessUrl;
240
+ let browserAccess;
227
241
  let accessUrlWarning;
228
- if (browser.provider === 'agentbay' && options.browser) {
242
+ const browserAccessCapability = versions.browserAccess;
243
+ if (browserAccessCapability && options.browser) {
229
244
  try {
230
- const access = (await client.invoke('get_agentbay_access_url', {}, {
231
- timeoutSec,
232
- browser: browser.browserId,
233
- }));
234
- if (!access.agentbayAccessUrl) {
235
- throw new Error('AgentBay did not return a browser access URL');
236
- }
237
- agentbayAccessUrl = access.agentbayAccessUrl;
238
- details.push(`AgentBay access URL (temporary, sensitive): ${access.agentbayAccessUrl}`);
245
+ browserAccess = await getBrowserAccess(client, browser, browserAccessCapability, timeoutSec);
246
+ details.push(`Browser access URL (${browserAccessLabel(browserAccess)}; sensitive): ${browserAccess.url}`);
239
247
  }
240
248
  catch (error) {
241
249
  accessUrlWarning = formatError(error);
242
- details.push(`AgentBay access URL unavailable: ${accessUrlWarning}`);
250
+ details.push(`Browser access URL unavailable: ${accessUrlWarning}`);
243
251
  }
244
252
  }
245
253
  browserChecks.push({
246
- ...(agentbayAccessUrl ? { agentbayAccessUrl } : {}),
254
+ ...(browserAccess ? { browserAccess } : {}),
247
255
  browser,
248
256
  ...(extensionInstallation ? { extensionInstallation } : {}),
249
257
  item: {
250
258
  status: accessUrlWarning ? 'warning' : 'ok',
251
259
  label: accessUrlWarning
252
- ? 'Browser command path ready; AgentBay access URL unavailable'
260
+ ? 'Browser command path ready; browser access URL unavailable'
253
261
  : options.transport === 'cdp'
254
262
  ? 'CDP command path'
255
263
  : 'Browser command path',
@@ -293,7 +301,7 @@ export async function runUnifiedCheck(options, dependencies = {}) {
293
301
  return {
294
302
  ...browserIdentity,
295
303
  selected: targets.some(target => target.browserId === browser.browserId),
296
- ...(check?.agentbayAccessUrl ? { agentbayAccessUrl: check.agentbayAccessUrl } : {}),
304
+ ...(check?.browserAccess ? { browserAccess: check.browserAccess } : {}),
297
305
  ...(check?.item
298
306
  ? {
299
307
  check: {
@@ -509,25 +509,21 @@ export declare const COMMANDS: readonly [{
509
509
  }, {
510
510
  readonly name: "emulation";
511
511
  readonly type: "object";
512
- readonly description: "开 tab 即进入移动端模拟态(首屏即移动态、无需 reload)。字段同 set_device_emulation 去掉 enabled,如 {\"preset\":\"iphone-15-pro\"}";
512
+ readonly description: "开 tab 即进入移动端模拟态(首屏即移动态、无需 reload)。字段同 set_device_emulation 去掉 enabled/reload;deviceShell=true 时页面从首屏起在 iframe WebView 中渲染设备系统外壳";
513
513
  }, {
514
514
  readonly name: "appProfile";
515
515
  readonly type: "string";
516
- readonly description: "开 tab 即进入 App 容器模拟态(客户端 UA + JSBridge 托管 + iframe 容器栈 + 自适应系统标题栏,首屏即容器态)。系统栏由 iframe 外层统一渲染,并根据页面协议与桥调用切换普通、沉浸或隐藏状态;档案名同 set_app_profile,如 \"fliggy\"";
516
+ readonly description: "开 tab 即注入 App 客户端环境与 JSBridge;档案名同 set_app_profile,如 \"fliggy\"。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true";
517
517
  }, {
518
518
  readonly name: "os";
519
519
  readonly type: "\"ios\" | \"android\"";
520
520
  readonly description: "与 appProfile 配合;容器系统,默认 ios";
521
- }, {
522
- readonly name: "simulateSystemBars";
523
- readonly type: "boolean";
524
- readonly description: "与 appProfile 配合;是否模拟顶部状态/导航栏与底部手势栏,默认 true";
525
521
  }, {
526
522
  readonly name: "bridgeConfig";
527
523
  readonly type: "object";
528
524
  readonly description: "与 appProfile 配合;声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}}";
529
525
  }];
530
- readonly examples: ["tab_open --payload '{\"url\":\"https://example.com\"}'", "tab_open --payload '{\"url\":\"https://example.com/page\",\"reuse\":\"prefer\",\"match\":{\"ignoreSearch\":true}}'", "tab_open --payload '{\"url\":\"https://m.example.com\",\"emulation\":{\"preset\":\"iphone-15-pro\"}}'", "tab_open --payload '{\"url\":\"https://market.m.taobao.com/app/trip/rx-home/pages/home\",\"appProfile\":\"fliggy\"}'", "tab_open --payload '{\"url\":\"https://example.com\",\"appProfile\":\"fliggy\",\"os\":\"android\"}'", "tab_open --payload '{\"url\":\"https://example.com\",\"group\":\"验证任务\"}'"];
526
+ readonly examples: ["tab_open --payload '{\"url\":\"https://example.com\"}'", "tab_open --payload '{\"url\":\"https://example.com/page\",\"reuse\":\"prefer\",\"match\":{\"ignoreSearch\":true}}'", "tab_open --payload '{\"url\":\"https://m.example.com\",\"emulation\":{\"preset\":\"iphone-17-pro\"}}'", "tab_open --payload '{\"url\":\"https://market.m.taobao.com/app/trip/rx-home/pages/home\",\"appProfile\":\"fliggy\"}'", "tab_open --payload '{\"url\":\"https://example.com\",\"appProfile\":\"fliggy\",\"os\":\"android\"}'", "tab_open --payload '{\"url\":\"https://example.com\",\"group\":\"验证任务\"}'"];
531
527
  }, {
532
528
  readonly name: "tab_close";
533
529
  readonly description: "关闭指定标签页";
@@ -912,7 +908,7 @@ export declare const COMMANDS: readonly [{
912
908
  readonly examples: ["record_stop"];
913
909
  }, {
914
910
  readonly name: "set_device_emulation";
915
- readonly description: "把目标 tab 切换为移动端模拟(视口/UA/触摸),等价 DevTools 设备工具栏,作用域 per-tab;需保持调试控制才持续生效";
911
+ readonly description: "把目标 tab 切换为移动端模拟(视口/DPR/UA/触摸/视觉比例),等价 DevTools 设备工具栏,作用域 per-tab;需保持调试控制才持续生效";
916
912
  readonly summary: "设置或清除目标标签页的设备模拟";
917
913
  readonly params: [{
918
914
  readonly name: "enabled";
@@ -921,8 +917,8 @@ export declare const COMMANDS: readonly [{
921
917
  readonly description: "true=启用,false=清除";
922
918
  }, {
923
919
  readonly name: "preset";
924
- readonly type: "\"iphone-15-pro\" | \"iphone-se\" | \"pixel-8\" | \"ipad-mini\" | \"galaxy-s23\"";
925
- readonly description: "内置设备预设,提供 width/height/DPR/UA 默认值";
920
+ readonly type: string;
921
+ readonly description: "内置设备预设,提供 width/height/DPR/UA 默认值;旧机型 ID 继续兼容";
926
922
  }, {
927
923
  readonly name: "width";
928
924
  readonly type: "number";
@@ -935,6 +931,10 @@ export declare const COMMANDS: readonly [{
935
931
  readonly name: "deviceScaleFactor";
936
932
  readonly type: "number";
937
933
  readonly description: "DPR(覆盖 preset)";
934
+ }, {
935
+ readonly name: "scale";
936
+ readonly type: "number";
937
+ readonly description: "画面视觉比例,不改变视口尺寸或 DPR;必须为有限正数,默认 1";
938
938
  }, {
939
939
  readonly name: "mobile";
940
940
  readonly type: "boolean";
@@ -950,7 +950,11 @@ export declare const COMMANDS: readonly [{
950
950
  }, {
951
951
  readonly name: "touch";
952
952
  readonly type: "boolean";
953
- readonly description: "是否启用触摸事件,默认 mobile 为真时启用";
953
+ readonly description: "是否启用触摸事件,默认从 preset 推断;无 preset 时跟随 mobile";
954
+ }, {
955
+ readonly name: "deviceShell";
956
+ readonly type: "boolean";
957
+ readonly description: "是否使用 iframe WebView 并渲染顶部状态栏与底部操作区,默认 false";
954
958
  }, {
955
959
  readonly name: "reload";
956
960
  readonly type: "boolean";
@@ -961,10 +965,10 @@ export declare const COMMANDS: readonly [{
961
965
  readonly required: true;
962
966
  readonly description: "目标标签页 ID";
963
967
  }];
964
- readonly examples: ["set_device_emulation --payload '{\"enabled\":true,\"preset\":\"iphone-15-pro\"}'", "set_device_emulation --payload '{\"enabled\":true,\"preset\":\"pixel-8\",\"orientation\":\"landscape\"}'", "set_device_emulation --payload '{\"enabled\":false}'"];
968
+ readonly examples: ["set_device_emulation --payload '{\"enabled\":true,\"preset\":\"iphone-17-pro\"}'", "set_device_emulation --payload '{\"enabled\":true,\"preset\":\"pixel-11\",\"orientation\":\"landscape\"}'", "set_device_emulation --payload '{\"enabled\":false}'"];
965
969
  }, {
966
970
  readonly name: "set_app_profile";
967
- readonly description: "把目标 tab 切换为 App 容器模拟态(客户端 UA + JSBridge 托管 + 页面跳转 iframe 容器栈 + scheme 唤端映射 + 自适应系统标题栏),系统栏由 iframe 外层统一渲染,并根据页面协议与桥调用切换普通、沉浸或隐藏状态;用于桌面浏览器近似实机验证 App 内 H5,需保持调试控制才持续生效";
971
+ readonly description: "设置 App 客户端环境与 JSBridge;传 emulation 时由同一个命令联动设备模拟,省略时不改变设备状态";
968
972
  readonly summary: "设置或清除目标标签页的 App 容器模拟";
969
973
  readonly params: [{
970
974
  readonly name: "enabled";
@@ -987,18 +991,14 @@ export declare const COMMANDS: readonly [{
987
991
  readonly name: "deviceModel";
988
992
  readonly type: "string";
989
993
  readonly description: "Android 机型名(拼入 UA 与 __DEVICE_MODEL__)";
990
- }, {
991
- readonly name: "simulateSystemBars";
992
- readonly type: "boolean";
993
- readonly description: "是否模拟顶部状态/导航栏与底部手势栏,默认 true";
994
- }, {
995
- readonly name: "emulation";
996
- readonly type: "object";
997
- readonly description: "覆盖档案推荐的设备模拟参数,字段同 set_device_emulation 去掉 enabled";
998
994
  }, {
999
995
  readonly name: "bridgeConfig";
1000
996
  readonly type: "object";
1001
997
  readonly description: "声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本";
998
+ }, {
999
+ readonly name: "emulation";
1000
+ readonly type: "object";
1001
+ readonly description: "可选设备联动参数,字段同 set_device_emulation 去掉 enabled/reload;字段覆盖档案推荐设备,deviceShell 默认 true。省略时不设置或清除设备";
1002
1002
  }, {
1003
1003
  readonly name: "reload";
1004
1004
  readonly type: "boolean";
@@ -1009,7 +1009,7 @@ export declare const COMMANDS: readonly [{
1009
1009
  readonly required: true;
1010
1010
  readonly description: "目标标签页 ID";
1011
1011
  }];
1012
- readonly examples: ["set_app_profile --payload '{\"enabled\":true,\"profile\":\"fliggy\"}'", "set_app_profile --payload '{\"enabled\":true,\"profile\":\"fliggy\",\"simulateSystemBars\":false}'", "set_app_profile --payload '{\"enabled\":true,\"profile\":\"alipay\",\"os\":\"android\",\"deviceModel\":\"Pixel 8 Pro\"}'", "set_app_profile --payload '{\"enabled\":false}'"];
1012
+ readonly examples: ["set_app_profile --payload '{\"enabled\":true,\"profile\":\"fliggy\"}'", "set_app_profile --payload '{\"enabled\":true,\"profile\":\"fliggy\",\"emulation\":{}}'", "set_app_profile --payload '{\"enabled\":true,\"profile\":\"alipay\",\"emulation\":{\"preset\":\"pixel-11\",\"deviceShell\":true}}'", "set_app_profile --payload '{\"enabled\":false}'", "set_app_profile --payload '{\"enabled\":false,\"emulation\":{}}'"];
1013
1013
  }, {
1014
1014
  readonly name: "set_timezone";
1015
1015
  readonly description: "覆盖目标 tab 的时区,传空字符串恢复浏览器默认时区";
package/dist/commands.js CHANGED
@@ -1,6 +1,7 @@
1
- import { APP_PROFILE_NAMES, } from './generated-browser-action-protocol.js';
1
+ import { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, } from './generated-browser-action-protocol.js';
2
2
  /** 容器档案名列表(拼进参数说明,新增档案无需改本文件) */
3
3
  const APP_PROFILE_NAME_LIST = APP_PROFILE_NAMES.join('/');
4
+ const DEVICE_PRESET_TYPE = BROWSER_DEVICE_PRESET_NAMES.map(name => JSON.stringify(name)).join(' | ');
4
5
  const PAGE_DIAGNOSTICS_PARAM = {
5
6
  name: 'diagnostics',
6
7
  type: 'object | boolean',
@@ -424,23 +425,18 @@ export const COMMANDS = [
424
425
  {
425
426
  name: 'emulation',
426
427
  type: 'object',
427
- description: '开 tab 即进入移动端模拟态(首屏即移动态、无需 reload)。字段同 set_device_emulation 去掉 enabled,如 {"preset":"iphone-15-pro"}',
428
+ description: '开 tab 即进入移动端模拟态(首屏即移动态、无需 reload)。字段同 set_device_emulation 去掉 enabled/reload;deviceShell=true 时页面从首屏起在 iframe WebView 中渲染设备系统外壳',
428
429
  },
429
430
  {
430
431
  name: 'appProfile',
431
432
  type: 'string',
432
- description: '开 tab 即进入 App 容器模拟态(客户端 UA + JSBridge 托管 + iframe 容器栈 + 自适应系统标题栏,首屏即容器态)。系统栏由 iframe 外层统一渲染,并根据页面协议与桥调用切换普通、沉浸或隐藏状态;档案名同 set_app_profile,如 "fliggy"',
433
+ description: '开 tab 即注入 App 客户端环境与 JSBridge;档案名同 set_app_profile,如 "fliggy"。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true',
433
434
  },
434
435
  {
435
436
  name: 'os',
436
437
  type: '"ios" | "android"',
437
438
  description: '与 appProfile 配合;容器系统,默认 ios',
438
439
  },
439
- {
440
- name: 'simulateSystemBars',
441
- type: 'boolean',
442
- description: '与 appProfile 配合;是否模拟顶部状态/导航栏与底部手势栏,默认 true',
443
- },
444
440
  {
445
441
  name: 'bridgeConfig',
446
442
  type: 'object',
@@ -450,7 +446,7 @@ export const COMMANDS = [
450
446
  examples: [
451
447
  `tab_open --payload '{"url":"https://example.com"}'`,
452
448
  `tab_open --payload '{"url":"https://example.com/page","reuse":"prefer","match":{"ignoreSearch":true}}'`,
453
- `tab_open --payload '{"url":"https://m.example.com","emulation":{"preset":"iphone-15-pro"}}'`,
449
+ `tab_open --payload '{"url":"https://m.example.com","emulation":{"preset":"iphone-17-pro"}}'`,
454
450
  `tab_open --payload '{"url":"https://market.m.taobao.com/app/trip/rx-home/pages/home","appProfile":"fliggy"}'`,
455
451
  `tab_open --payload '{"url":"https://example.com","appProfile":"fliggy","os":"android"}'`,
456
452
  `tab_open --payload '{"url":"https://example.com","group":"验证任务"}'`,
@@ -853,18 +849,23 @@ export const COMMANDS = [
853
849
  // ── 移动端模拟 ────────────────────────────────────────────────────────────
854
850
  {
855
851
  name: 'set_device_emulation',
856
- description: '把目标 tab 切换为移动端模拟(视口/UA/触摸),等价 DevTools 设备工具栏,作用域 per-tab;需保持调试控制才持续生效',
852
+ description: '把目标 tab 切换为移动端模拟(视口/DPR/UA/触摸/视觉比例),等价 DevTools 设备工具栏,作用域 per-tab;需保持调试控制才持续生效',
857
853
  summary: '设置或清除目标标签页的设备模拟',
858
854
  params: [
859
855
  { name: 'enabled', type: 'boolean', required: true, description: 'true=启用,false=清除' },
860
856
  {
861
857
  name: 'preset',
862
- type: '"iphone-15-pro" | "iphone-se" | "pixel-8" | "ipad-mini" | "galaxy-s23"',
863
- description: '内置设备预设,提供 width/height/DPR/UA 默认值',
858
+ type: DEVICE_PRESET_TYPE,
859
+ description: '内置设备预设,提供 width/height/DPR/UA 默认值;旧机型 ID 继续兼容',
864
860
  },
865
861
  { name: 'width', type: 'number', description: '视口宽度(覆盖 preset)' },
866
862
  { name: 'height', type: 'number', description: '视口高度(覆盖 preset)' },
867
863
  { name: 'deviceScaleFactor', type: 'number', description: 'DPR(覆盖 preset)' },
864
+ {
865
+ name: 'scale',
866
+ type: 'number',
867
+ description: '画面视觉比例,不改变视口尺寸或 DPR;必须为有限正数,默认 1',
868
+ },
868
869
  { name: 'mobile', type: 'boolean', description: '是否走移动渲染模式,默认从 preset 推断' },
869
870
  { name: 'userAgent', type: 'string', description: '自定义 UA(覆盖 preset)' },
870
871
  {
@@ -872,19 +873,28 @@ export const COMMANDS = [
872
873
  type: '"portrait" | "landscape"',
873
874
  description: '横竖屏,默认 portrait',
874
875
  },
875
- { name: 'touch', type: 'boolean', description: '是否启用触摸事件,默认 mobile 为真时启用' },
876
+ {
877
+ name: 'touch',
878
+ type: 'boolean',
879
+ description: '是否启用触摸事件,默认从 preset 推断;无 preset 时跟随 mobile',
880
+ },
881
+ {
882
+ name: 'deviceShell',
883
+ type: 'boolean',
884
+ description: '是否使用 iframe WebView 并渲染顶部状态栏与底部操作区,默认 false',
885
+ },
876
886
  { name: 'reload', type: 'boolean', description: '启用后是否重载页面,默认 true' },
877
887
  { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' },
878
888
  ],
879
889
  examples: [
880
- `set_device_emulation --payload '{"enabled":true,"preset":"iphone-15-pro"}'`,
881
- `set_device_emulation --payload '{"enabled":true,"preset":"pixel-8","orientation":"landscape"}'`,
890
+ `set_device_emulation --payload '{"enabled":true,"preset":"iphone-17-pro"}'`,
891
+ `set_device_emulation --payload '{"enabled":true,"preset":"pixel-11","orientation":"landscape"}'`,
882
892
  `set_device_emulation --payload '{"enabled":false}'`,
883
893
  ],
884
894
  },
885
895
  {
886
896
  name: 'set_app_profile',
887
- description: '把目标 tab 切换为 App 容器模拟态(客户端 UA + JSBridge 托管 + 页面跳转 iframe 容器栈 + scheme 唤端映射 + 自适应系统标题栏),系统栏由 iframe 外层统一渲染,并根据页面协议与桥调用切换普通、沉浸或隐藏状态;用于桌面浏览器近似实机验证 App 内 H5,需保持调试控制才持续生效',
897
+ description: '设置 App 客户端环境与 JSBridge;传 emulation 时由同一个命令联动设备模拟,省略时不改变设备状态',
888
898
  summary: '设置或清除目标标签页的 App 容器模拟',
889
899
  params: [
890
900
  { name: 'enabled', type: 'boolean', required: true, description: 'true=启用,false=清除' },
@@ -901,28 +911,24 @@ export const COMMANDS = [
901
911
  description: 'Android 机型名(拼入 UA 与 __DEVICE_MODEL__)',
902
912
  },
903
913
  {
904
- name: 'simulateSystemBars',
905
- type: 'boolean',
906
- description: '是否模拟顶部状态/导航栏与底部手势栏,默认 true',
907
- },
908
- {
909
- name: 'emulation',
914
+ name: 'bridgeConfig',
910
915
  type: 'object',
911
- description: '覆盖档案推荐的设备模拟参数,字段同 set_device_emulation 去掉 enabled',
916
+ description: '声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本',
912
917
  },
913
918
  {
914
- name: 'bridgeConfig',
919
+ name: 'emulation',
915
920
  type: 'object',
916
- description: '声明式覆盖桥方法,形如 {methods:{api:{behavior|response|noop,silent?}}};不执行配置脚本',
921
+ description: '可选设备联动参数,字段同 set_device_emulation 去掉 enabled/reload;字段覆盖档案推荐设备,deviceShell 默认 true。省略时不设置或清除设备',
917
922
  },
918
923
  { name: 'reload', type: 'boolean', description: '应用/清除后是否重载页面,默认 true' },
919
924
  { name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' },
920
925
  ],
921
926
  examples: [
922
927
  `set_app_profile --payload '{"enabled":true,"profile":"fliggy"}'`,
923
- `set_app_profile --payload '{"enabled":true,"profile":"fliggy","simulateSystemBars":false}'`,
924
- `set_app_profile --payload '{"enabled":true,"profile":"alipay","os":"android","deviceModel":"Pixel 8 Pro"}'`,
928
+ `set_app_profile --payload '{"enabled":true,"profile":"fliggy","emulation":{}}'`,
929
+ `set_app_profile --payload '{"enabled":true,"profile":"alipay","emulation":{"preset":"pixel-11","deviceShell":true}}'`,
925
930
  `set_app_profile --payload '{"enabled":false}'`,
931
+ `set_app_profile --payload '{"enabled":false,"emulation":{}}'`,
926
932
  ],
927
933
  },
928
934
  {
@@ -262,16 +262,20 @@ export interface PageSelectedElementParams {
262
262
  includeOuterHTML?: boolean;
263
263
  includeScreenshot?: boolean;
264
264
  }
265
- export type BrowserDevicePresetName = 'iphone-15-pro' | 'iphone-se' | 'pixel-8' | 'ipad-mini' | 'galaxy-s23';
265
+ export declare const BROWSER_DEVICE_PRESET_NAMES: readonly ["iphone-17-pro", "iphone-17e", "pixel-11", "ipad-mini-a17-pro", "galaxy-s26", "pura-x-max", "iphone-15-pro", "iphone-se", "pixel-8", "ipad-mini", "galaxy-s23"];
266
+ export type BrowserDevicePresetName = (typeof BROWSER_DEVICE_PRESET_NAMES)[number];
266
267
  export interface BrowserEmulationOptions {
267
268
  preset?: BrowserDevicePresetName;
268
269
  width?: number;
269
270
  height?: number;
270
271
  deviceScaleFactor?: number;
272
+ scale?: number;
271
273
  mobile?: boolean;
272
274
  userAgent?: string;
273
275
  orientation?: 'portrait' | 'landscape';
274
276
  touch?: boolean;
277
+ /** 在 iframe WebView 外渲染设备系统栏;默认 false,保持纯 CDP 设备模拟。 */
278
+ deviceShell?: boolean;
275
279
  }
276
280
  export type AppBridgeConfigBehavior = 'navigate' | 'closeAll' | 'closeBackData' | 'postMessage' | 'notifySubscribe' | 'notifyUnsubscribe' | 'notifyPost' | 'sharedFileRead' | 'sharedFileWrite' | 'sharedDataGet' | 'sharedDataSet' | 'legacyBroadcastCreate' | 'legacyBroadcastPost' | 'legacyBroadcastClose' | 'memoryGet' | 'memorySet' | 'memoryDelete' | 'availabilityMap' | 'userInfo' | 'networkInfo' | 'geolocation' | 'permission' | 'prefetch' | 'fptTrack' | 'mtopStream' | 'previewFile' | 'toast' | 'unsupportedNativeInput' | 'payment' | 'userTrack' | 'calendar' | 'networkQualityMonitor' | 'beaconRequest';
277
281
  /** 单个桥方法的声明式覆盖;行为、固定回包、成功空实现三选一。 */
@@ -302,15 +306,12 @@ export interface TabOpenParams {
302
306
  emulation?: BrowserEmulationOptions;
303
307
  group?: string;
304
308
  /**
305
- * 客户端容器模拟档案名(如 'fliggy')。打开后即按该 App 的 WebView 环境渲染:
306
- * 移动视口 + 容器 UA + JSBridge 托管 + 自适应系统标题栏。系统栏由 iframe 外层统一渲染,
307
- * 并根据页面协议与桥调用切换普通、沉浸或隐藏状态。可用档案见 APP_PROFILE_NAMES。
309
+ * 客户端容器模拟档案名(如 'fliggy')。只注入容器 UA、环境变量、JSBridge 与桥协议;
310
+ * 设备视口和 iframe 系统外壳由 emulation.deviceShell 独立控制。可用档案见 APP_PROFILE_NAMES。
308
311
  */
309
312
  appProfile?: AppProfileName;
310
313
  /** 与 appProfile 配合使用;容器系统,默认 ios */
311
314
  os?: 'ios' | 'android';
312
- /** 与 appProfile 配合使用;是否模拟顶部状态/导航栏与底部手势栏,默认 true */
313
- simulateSystemBars?: boolean;
314
315
  /** 与 appProfile 配合使用;按方法覆盖档案内置桥配置 */
315
316
  bridgeConfig?: AppBridgeConfig;
316
317
  }
@@ -333,11 +334,12 @@ export interface SetAppProfileParams {
333
334
  os?: 'ios' | 'android';
334
335
  appVersion?: string;
335
336
  deviceModel?: string;
336
- /** 是否模拟顶部状态/导航栏与底部手势栏,默认 true */
337
- simulateSystemBars?: boolean;
338
337
  /** 按方法覆盖档案内置桥配置,仅支持受控行为、固定 JSON 回包和成功空实现 */
339
338
  bridgeConfig?: AppBridgeConfig;
340
- /** 覆盖档案推荐的设备模拟参数(视口/UA 默认取档案值) */
339
+ /**
340
+ * 可选设备联动参数。传入时显式字段覆盖档案推荐设备,空对象使用推荐设备且默认开启
341
+ * deviceShell;省略时不设置或清除该 tab 的设备状态。
342
+ */
341
343
  emulation?: BrowserEmulationOptions;
342
344
  /** 默认 true:应用/清除后刷新页面使注入生效 */
343
345
  reload?: boolean;
@@ -6,6 +6,20 @@ export const SEND_REQUEST_MAX_TOTAL_FILE_BYTES = 40 * 1024 * 1024;
6
6
  // Base64 expands by 4/3; 7 MiB leaves roughly 680 KiB for JSON framing under
7
7
  // the 10 MiB cloud message limit.
8
8
  export const SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES = 7 * 1024 * 1024;
9
+ export const BROWSER_DEVICE_PRESET_NAMES = [
10
+ 'iphone-17-pro',
11
+ 'iphone-17e',
12
+ 'pixel-11',
13
+ 'ipad-mini-a17-pro',
14
+ 'galaxy-s26',
15
+ 'pura-x-max',
16
+ // 旧名称保留具体机型语义,供已有调用与历史回归基线继续使用。
17
+ 'iphone-15-pro',
18
+ 'iphone-se',
19
+ 'pixel-8',
20
+ 'ipad-mini',
21
+ 'galaxy-s23',
22
+ ];
9
23
  /**
10
24
  * 客户端容器模拟可用档案名。
11
25
  *
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export { UnifiedClient, UnifiedClient as MearlClient, decodeBrowserTarget, invoke, listCloudConnectors, } from './unified.js';
8
8
  export type { UnifiedClientOptions, UnifiedInvokeOptions, UnifiedInvokeOptions as InvokeOptions, } from './unified.js';
9
- export { APP_PROFILE_NAMES, BROWSER_ACTIONS, BROWSER_COMMAND_ACTIONS, EXTENSION_BROWSER_ACTIONS, PAGE_ACT_ACTIONS, SEND_REQUEST_MAX_FILES, SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES, SEND_REQUEST_MAX_TOTAL_FILE_BYTES, isBrowserAction, isBrowserCommandAction, isExtensionBrowserAction, } from './generated-browser-action-protocol.js';
9
+ export { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, BROWSER_ACTIONS, BROWSER_COMMAND_ACTIONS, EXTENSION_BROWSER_ACTIONS, PAGE_ACT_ACTIONS, SEND_REQUEST_MAX_FILES, SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES, SEND_REQUEST_MAX_TOTAL_FILE_BYTES, isBrowserAction, isBrowserCommandAction, isExtensionBrowserAction, } from './generated-browser-action-protocol.js';
10
10
  export type { ActionStep, AppProfileName, BrowserAction, BrowserActionMap, BrowserActionRequest, BrowserActionResponse, BrowserCloseParams, BrowserCommandAction, BrowserInfo, BrowserInfoBase, BrowserLaunchParams, BrowserListResult, BrowserReleaseFailure, BrowserReleaseResult, BrowserSource, BrowserStatus, CaptureCheckpointParams, CaptureCheckpointResult, BrowserDevicePresetName, BrowserEmulationOptions, ExtensionBrowserAction, GetApiSchemaParams, GetCookieParams, GetCookieResult, GetEventsParams, GetLogsParams, GetMocksParams, GetRequestsParams, GetUserInfoParams, ManagedBrowserAccount, ManagedBrowserInfo, PageClickParams, PageDragEndpoint, PageDragParams, PageDragPoint, PageActAction, PageDiagnosticsOptions, PageDiagnosticsSetting, PageEvalParams, PageFramesParams, PageHoverParams, PageNavigateParams, PageObservationWaitOptions, PagePressParams, PageScreenshotParams, PageScrollParams, PageSelectedElementParams, PageSnapshotParams, PageSnapshotQuery, PageTypeParams, PageUploadParams, PageWaitParams, RecordParams, RegularBrowserInfo, RequestDomainPermissionParams, RulesResult, RunActionName, RunActionsParams, SelectBrowserParams, SelectBrowserResult, SendRequestFile, SendRequestFormValue, SendMtopRequestParams, SendRequestParams, SetAppProfileParams, SetCookieParams, SetCookieResult, SetDeviceEmulationParams, SetMockParams, SetRuleParams, SetTimezoneParams, SetTimezoneResult, TabCloseParams, TabOpenParams, TdbankAccountParams, } from './generated-browser-action-protocol.js';
11
11
  import { type UnifiedInvokeOptions } from './unified.js';
12
12
  import type * as Protocol from './generated-browser-action-protocol.js';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * then routes actions using the global browser ids returned by browser_list.
6
6
  */
7
7
  export { UnifiedClient, UnifiedClient as MearlClient, decodeBrowserTarget, invoke, listCloudConnectors, } from './unified.js';
8
- export { APP_PROFILE_NAMES, BROWSER_ACTIONS, BROWSER_COMMAND_ACTIONS, EXTENSION_BROWSER_ACTIONS, PAGE_ACT_ACTIONS, SEND_REQUEST_MAX_FILES, SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES, SEND_REQUEST_MAX_TOTAL_FILE_BYTES, isBrowserAction, isBrowserCommandAction, isExtensionBrowserAction, } from './generated-browser-action-protocol.js';
8
+ export { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, BROWSER_ACTIONS, BROWSER_COMMAND_ACTIONS, EXTENSION_BROWSER_ACTIONS, PAGE_ACT_ACTIONS, SEND_REQUEST_MAX_FILES, SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES, SEND_REQUEST_MAX_TOTAL_FILE_BYTES, isBrowserAction, isBrowserCommandAction, isExtensionBrowserAction, } from './generated-browser-action-protocol.js';
9
9
  import { invoke } from './unified.js';
10
10
  export function tabCheckpoint(params = {}, options) {
11
11
  return invoke('tab_checkpoint', params, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/client",
3
- "version": "2.9.6",
3
+ "version": "2.10.0",
4
4
  "description": "Unified Mearl SDK & CLI for local and remote browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "commander": "^15.0.0",
56
56
  "fastest-levenshtein": "^1.0.16",
57
57
  "ws": "^8.18.0",
58
- "@mearl/cloud-types": "2.9.6"
58
+ "@mearl/cloud-types": "2.10.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "^24.9.1",