@mearl/client 3.0.0-beta.4 → 3.0.0-beta.5

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.
Files changed (46) hide show
  1. package/README.md +94 -15
  2. package/dist/check.d.ts +5 -6
  3. package/dist/check.js +3 -6
  4. package/dist/cli.js +15 -43
  5. package/dist/commandHelp.d.ts +3 -0
  6. package/dist/commandHelp.js +79 -0
  7. package/dist/commands.d.ts +24 -14
  8. package/dist/commands.js +23 -18
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.js +4 -0
  11. package/dist/nativeHost.js +1 -1
  12. package/dist/plugins/clients/pluginHostConnection.d.ts +2 -0
  13. package/dist/plugins/clients/pluginHostConnection.js +3 -1
  14. package/dist/plugins/pluginRegistry.d.ts +1 -1
  15. package/dist/plugins/pluginRegistry.js +12 -4
  16. package/dist/plugins/runtime/pluginCommandWorker.js +8 -6
  17. package/dist/plugins/runtime/pluginExecutor.d.ts +5 -0
  18. package/dist/plugins/runtime/pluginExecutor.js +15 -2
  19. package/dist/plugins/runtime/pluginExecutorProtocol.d.ts +4 -0
  20. package/dist/plugins/runtime/pluginExecutorSupervisor.js +2 -1
  21. package/dist/plugins/runtime/pluginHostServer.js +4 -0
  22. package/dist/plugins/runtime/pluginPageManager.js +0 -4
  23. package/dist/plugins/runtime/pluginRevision.js +0 -51
  24. package/dist/plugins/runtime/pluginStateStore.js +3 -8
  25. package/dist/plugins/runtime/pluginTaskManager.js +3 -7
  26. package/dist/plugins/sdk/plugin.js +1 -1
  27. package/dist/plugins/sdk/pluginModelDefinition.js +0 -6
  28. package/dist/plugins/sdk/pluginPageDefinition.d.ts +0 -1
  29. package/dist/plugins/sdk/pluginPageDefinition.js +2 -18
  30. package/dist/plugins/sdk/pluginRecoveryDefinition.js +0 -3
  31. package/dist/plugins/sdk/pluginStateDefinition.d.ts +0 -1
  32. package/dist/plugins/sdk/pluginStateDefinition.js +0 -11
  33. package/dist/runtimePaths.d.ts +1 -0
  34. package/dist/runtimePaths.js +1 -0
  35. package/dist/script.d.ts +30 -0
  36. package/dist/script.js +260 -0
  37. package/dist/scriptCli.d.ts +1 -0
  38. package/dist/scriptCli.js +94 -0
  39. package/dist/scriptErrors.d.ts +8 -0
  40. package/dist/scriptErrors.js +21 -0
  41. package/dist/scriptWorker.d.ts +1 -0
  42. package/dist/scriptWorker.js +50 -0
  43. package/dist/sdkMethods.d.ts +3 -0
  44. package/dist/sdkMethods.js +9 -0
  45. package/dist/socket.js +2 -1
  46. package/package.json +10 -8
@@ -23,6 +23,16 @@ export interface CommandDef {
23
23
  result?: readonly string[];
24
24
  }
25
25
  export declare const COMMANDS: readonly [{
26
+ readonly name: "get_capabilities";
27
+ readonly description: "只读查询当前目标浏览器的动作、设备和已安装 App 能力、限制与 Skill/帮助入口;不打开页面或启用模拟";
28
+ readonly params: [{
29
+ readonly name: "app";
30
+ readonly type: "string";
31
+ readonly description: "可选,使用目录中的完整 App ID 查询桥方法、可调设置和默认版本详情";
32
+ }];
33
+ readonly notes: readonly ["沿用 --browser / --session 路由;不传 app 返回摘要。apps.status=partial 表示目录未完整取得,unsupported 表示目标未提供目录;不能把两者当作未安装。", "available=false 的 App 当前不可启用;支持某个 action 不代表已有域名权限或真实设备授权。按需读取 usage.skills / usage.documentation,查询不会自动安装或加载 Skill。"];
34
+ readonly examples: ["get_capabilities", "get_capabilities --payload '{\"app\":\"example/app\"}'"];
35
+ }, {
26
36
  readonly name: "browser_control";
27
37
  readonly description: "把控制会话交给用户;用户在 Mearl 浏览器管理页交还后重新观察页面";
28
38
  readonly params: [{
@@ -389,7 +399,7 @@ export declare const COMMANDS: readonly [{
389
399
  readonly type: "number";
390
400
  readonly description: "超时毫秒数,默认 10000";
391
401
  }];
392
- readonly notes: readonly ["需要目标浏览器有对应登录态;Mearl 自动处理签名,不依赖页面脚本。api 以 mopen. 开头时使用 mopen 协议。", "env=prod 使用 h5api.m.taobao.com,pre 使用 h5api.wapa.taobao.com。"];
402
+ readonly notes: readonly ["需要目标浏览器有对应登录态;Mearl 自动处理签名,不依赖页面脚本。api 以 mopen. 开头时使用 mopen 协议。", "env=prod/pre 的网关由已安装的请求协议能力提供。"];
393
403
  readonly result: readonly ["返回 success、response、requestData;业务成功需检查 response.ret。"];
394
404
  readonly examples: ["send_mtop_request --payload '{\"api\":\"mtop.trade.order.detail\",\"data\":{\"orderId\":\"123\"}}'"];
395
405
  }, {
@@ -519,7 +529,7 @@ export declare const COMMANDS: readonly [{
519
529
  }, {
520
530
  readonly name: "appProfile";
521
531
  readonly type: "string";
522
- readonly description: "按已安装档案应用 App 环境;内置基础档案只提供 UA/版本,完整桥由能力包提供。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true";
532
+ readonly description: "按已安装档案应用 App 环境,ID 与限制通过 get_capabilities 查询。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true";
523
533
  }, {
524
534
  readonly name: "os";
525
535
  readonly type: "\"ios\" | \"android\"";
@@ -531,7 +541,7 @@ export declare const COMMANDS: readonly [{
531
541
  }];
532
542
  readonly notes: readonly ["emulation / appProfile 只应用到新建 Tab;复用 Tab 要切换状态时用对应设置 action。", "closeExisting 不能与复用或 openerTabId 组合;多匹配默认报错,match.onMultiple=first 可显式选择第一个。"];
533
543
  readonly result: readonly ["返回 tabId、url、title、opened/reused;reused=true 的既有页面应保留。", "active=true 时 activation 包含 tabActive、windowFocused、windowState、visibilityState;emulation 返回实际生效配置。", "capture.cursor 是导航前诊断边界,可分别用于 get_logs/get_requests/get_events 的 after。"];
534
- 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://example.com/app/home\",\"appProfile\":\"fliggy\"}'", "tab_open --payload '{\"url\":\"https://example.com/app/home\",\"appProfile\":\"fliggy\",\"emulation\":{\"preset\":\"ipad-mini-a17-pro\",\"orientation\":\"landscape\",\"deviceShell\":true,\"splitScreen\":true}}'", "tab_open --payload '{\"url\":\"https://example.com\",\"appProfile\":\"fliggy\",\"os\":\"android\"}'", "tab_open --payload '{\"url\":\"https://example.com\",\"group\":\"验证任务\"}'"];
544
+ 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://example.com/app/home\",\"appProfile\":\"reader\"}'", "tab_open --payload '{\"url\":\"https://example.com/app/home\",\"appProfile\":\"reader\",\"emulation\":{\"preset\":\"ipad-mini-a17-pro\",\"orientation\":\"landscape\",\"deviceShell\":true,\"splitScreen\":true}}'", "tab_open --payload '{\"url\":\"https://example.com\",\"appProfile\":\"reader\",\"os\":\"android\"}'", "tab_open --payload '{\"url\":\"https://example.com\",\"group\":\"验证任务\"}'"];
535
545
  }, {
536
546
  readonly name: "tab_close";
537
547
  readonly description: "关闭指定标签页";
@@ -613,7 +623,7 @@ export declare const COMMANDS: readonly [{
613
623
  readonly required: true;
614
624
  readonly description: "目标标签页 ID";
615
625
  }];
616
- readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。失败以退出码 1 结束。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
626
+ readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
617
627
  readonly notes: readonly ["selector / text / point 必须且只能提供一个。隐藏页 auto 使用 DOM;只有明确依赖真实用户手势时才激活页面。", "原生 select 可按 option 文本点击;checkbox/radio 返回 controlState.checkedBefore/checkedAfter。"];
618
628
  readonly examples: ["page_click --payload '{\"tabId\":12345,\"text\":\"提交\"}'", "page_click --payload '{\"tabId\":12345,\"text\":\"退款明细\",\"role\":\"button\"}'", "page_click --payload '{\"tabId\":12345,\"text\":\"确定\",\"scope\":\"@e12\"}'", "page_click --payload '{\"tabId\":12345,\"selector\":\"@e3\"}'", "page_click --payload '{\"tabId\":12345,\"selector\":\".submit-btn\"}'", "page_click --payload '{\"tabId\":12345,\"selector\":\".gesture-btn\",\"clickMode\":\"touch\"}'", "page_click --payload '{\"tabId\":12345,\"point\":{\"x\":336,\"y\":117}}'", "page_click --payload '{\"tabId\":12345,\"point\":{\"x\":196,\"y\":240},\"coordinateSpace\":\"active-frame\"}'"];
619
629
  }, {
@@ -643,7 +653,7 @@ export declare const COMMANDS: readonly [{
643
653
  readonly required: true;
644
654
  readonly description: "目标标签页 ID";
645
655
  }];
646
- readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。失败以退出码 1 结束。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
656
+ readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
647
657
  readonly notes: readonly ["起点会自动滚入视口,终点必须同时位于当前视口;否则先滚动页面。隐藏页 auto 使用 DOM,不切换活动页。"];
648
658
  readonly examples: ["page_drag --payload '{\"tabId\":12345,\"from\":{\"x\":350,\"y\":80},\"to\":{\"x\":80,\"y\":80}}'", "page_drag --payload '{\"tabId\":12345,\"from\":\"@e3\",\"to\":\"#drop-zone\",\"dragMode\":\"mouse\"}'"];
649
659
  }, {
@@ -669,7 +679,7 @@ export declare const COMMANDS: readonly [{
669
679
  readonly required: true;
670
680
  readonly description: "目标标签页 ID";
671
681
  }];
672
- readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。失败以退出码 1 结束。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
682
+ readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
673
683
  readonly examples: ["page_type --payload '{\"tabId\":12345,\"selector\":\"@e6\",\"text\":\"搜索关键词\"}'"];
674
684
  }, {
675
685
  readonly name: "page_hover";
@@ -685,7 +695,7 @@ export declare const COMMANDS: readonly [{
685
695
  readonly required: true;
686
696
  readonly description: "目标标签页 ID";
687
697
  }];
688
- readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。失败以退出码 1 结束。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
698
+ readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
689
699
  readonly examples: ["page_hover --payload '{\"tabId\":12345,\"selector\":\".nav-menu\"}'"];
690
700
  }, {
691
701
  readonly name: "page_scroll";
@@ -713,7 +723,7 @@ export declare const COMMANDS: readonly [{
713
723
  readonly required: true;
714
724
  readonly description: "目标标签页 ID";
715
725
  }];
716
- readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。失败以退出码 1 结束。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。", "action.data 返回 moved、atTop/atBottom、scrollYBefore/After、maxScrollY、resolvedContainer 和 ancestorDistance;自动发现的内部容器提供可复用 containerSelector。"];
726
+ readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。", "action.data 返回 moved、atTop/atBottom、scrollYBefore/After、maxScrollY、resolvedContainer 和 ancestorDistance;自动发现的内部容器提供可复用 containerSelector。"];
717
727
  readonly examples: ["page_scroll --payload '{\"tabId\":12345,\"direction\":\"down\"}'", "page_scroll --payload '{\"tabId\":12345,\"direction\":\"bottom\"}'", "page_scroll --payload '{\"tabId\":12345,\"selector\":\".virtual-list\",\"direction\":\"down\",\"distance\":800}'", "page_scroll --payload '{\"tabId\":12345,\"selector\":\"@e8\",\"containerPolicy\":\"nearest\",\"direction\":\"bottom\"}'"];
718
728
  }, {
719
729
  readonly name: "page_runtime";
@@ -821,7 +831,7 @@ export declare const COMMANDS: readonly [{
821
831
  readonly description: "目标标签页 ID";
822
832
  }];
823
833
  readonly notes: readonly ["隐藏页 DOM 模式支持 Tab 焦点移动、原生控件 Enter/Space 激活和输入框 Enter 提交;编辑、选择、方向键和页面滚动等原生行为需可见页与 pressMode=keyboard。"];
824
- readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。失败以退出码 1 结束。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。", "action.data.dispatchMode / fallbackReason / defaultAction 说明实际输入路径。"];
834
+ readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。", "action.data.dispatchMode / fallbackReason / defaultAction 说明实际输入路径。"];
825
835
  readonly examples: ["page_press --payload '{\"tabId\":12345,\"key\":\"Enter\"}'"];
826
836
  }, {
827
837
  readonly name: "page_wait";
@@ -911,7 +921,7 @@ export declare const COMMANDS: readonly [{
911
921
  readonly required: true;
912
922
  readonly description: "目标标签页 ID";
913
923
  }];
914
- readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。失败以退出码 1 结束。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
924
+ readonly result: readonly ["默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。", "observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。", "包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。", "observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。", "effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。", "fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。", "显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。"];
915
925
  readonly notes: readonly ["直接写入 input[type=file],不依赖 file chooser;隐藏的文件节点也可上传。外部 Provider 的文件中转能力查询 mearl <providerId>。"];
916
926
  readonly examples: ["page_upload --payload '{\"tabId\":12345,\"selector\":\"#file-input\",\"filePaths\":[\"/Users/me/photo.jpg\"]}'"];
917
927
  }, {
@@ -1065,7 +1075,7 @@ export declare const COMMANDS: readonly [{
1065
1075
  }, {
1066
1076
  readonly name: "profile";
1067
1077
  readonly type: "string";
1068
- readonly description: `\u5BB9\u5668\u6863\u6848\u540D\uFF08enabled=true \u65F6\u5FC5\u586B\uFF09\uFF1B\u5185\u7F6E ${string}`;
1078
+ readonly description: "已安装的容器档案 ID(enabled=true 时必填);通过 get_capabilities 查询当前浏览器目录";
1069
1079
  }, {
1070
1080
  readonly name: "os";
1071
1081
  readonly type: "\"ios\" | \"android\"";
@@ -1110,7 +1120,7 @@ export declare const COMMANDS: readonly [{
1110
1120
  }];
1111
1121
  readonly notes: readonly ["只有用户明确要求 App/客户端/容器/JSBridge 模拟时启用。移动视口或普通 H5 调试使用 set_device_emulation。", "emulation={} 使用档案推荐设备;enabled=false 时省略 emulation 只清除 App 注入,传 {} 同时清除设备模拟。", "bridgeConfig.methods 按方法声明 behavior / response / noop 三选一,可传 silent;只支持受控行为、固定 JSON 回包或成功空实现,不执行配置脚本。"];
1112
1122
  readonly result: readonly ["返回 enabled、profile、label、userAgent、os、mtopEnv、reloaded;联动设备时额外返回实际 emulation。"];
1113
- readonly examples: ["set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"fliggy\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"fliggy\",\"mtopEnv\":\"pre\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false,\"mtopEnv\":\"prod\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"fliggy\",\"emulation\":{}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"alipay\",\"emulation\":{\"preset\":\"pixel-11\",\"deviceShell\":true}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"fliggy\",\"emulation\":{\"preset\":\"ipad-mini-a17-pro\",\"orientation\":\"landscape\",\"deviceShell\":true,\"splitScreen\":true}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false,\"emulation\":{}}'"];
1123
+ readonly examples: ["set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"mtopEnv\":\"pre\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false,\"mtopEnv\":\"prod\"}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"emulation\":{}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"wallet\",\"emulation\":{\"preset\":\"pixel-11\",\"deviceShell\":true}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":true,\"profile\":\"reader\",\"emulation\":{\"preset\":\"ipad-mini-a17-pro\",\"orientation\":\"landscape\",\"deviceShell\":true,\"splitScreen\":true}}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false}'", "set_app_profile --payload '{\"tabId\":12345,\"enabled\":false,\"emulation\":{}}'"];
1114
1124
  }, {
1115
1125
  readonly name: "set_timezone";
1116
1126
  readonly description: "覆盖目标 tab 的时区,传空字符串恢复浏览器默认时区";
@@ -1153,7 +1163,7 @@ export declare const COMMANDS: readonly [{
1153
1163
  }];
1154
1164
  readonly notes: readonly ["只读取指定非分区 Cookie,不列举全部、不回退分区 Cookie,拒绝 HttpOnly;禁止读取认证 Token。指定 url 不导航,指定 tabId 仅选择存储区时不启动采集。"];
1155
1165
  readonly result: readonly ["找到时 found=true 并返回 value/domain/path;不存在时 found=false。"];
1156
- readonly examples: ["get_cookie --payload '{\"tabId\":12345,\"name\":\"feature_flag\"}'", "get_cookie --payload '{\"url\":\"https://www.taobao.com/\",\"name\":\"tracknick\"}'", "get_cookie --payload '{\"tabId\":12345,\"name\":\"feature_channel\",\"domain\":\"example.com\"}'", "get_cookie --payload '{\"tabId\":12345,\"name\":\"tracknick\",\"url\":\"https://www.taobao.com/\",\"domain\":\"example.com\",\"path\":\"/\"}'"];
1166
+ readonly examples: ["get_cookie --payload '{\"tabId\":12345,\"name\":\"feature_flag\"}'", "get_cookie --payload '{\"url\":\"https://www.shop.com/\",\"name\":\"account_name\"}'", "get_cookie --payload '{\"tabId\":12345,\"name\":\"feature_channel\",\"domain\":\"example.com\"}'", "get_cookie --payload '{\"tabId\":12345,\"name\":\"account_name\",\"url\":\"https://www.shop.com/\",\"domain\":\"example.com\",\"path\":\"/\"}'"];
1157
1167
  }, {
1158
1168
  readonly name: "set_cookie";
1159
1169
  readonly description: "在目标浏览器设置一个指定非分区 session Cookie,可选刷新指定页面";
@@ -1190,7 +1200,7 @@ export declare const COMMANDS: readonly [{
1190
1200
  }];
1191
1201
  readonly notes: readonly ["仅写非分区 session Cookie,不支持删除。reload=true 时校验目标域与刷新页域的授权。"];
1192
1202
  readonly result: readonly ["返回 url/name/domain/path/reloaded,指定标签页时包含 tabId;不回显 Cookie 值。"];
1193
- readonly examples: ["set_cookie --payload '{\"tabId\":12345,\"name\":\"feature_flag\",\"value\":\"enabled\"}'", "set_cookie --payload '{\"url\":\"https://www.taobao.com/\",\"name\":\"feature_flag\",\"value\":\"enabled\"}'", "set_cookie --payload '{\"tabId\":12345,\"name\":\"feature_channel\",\"value\":\"3.6.63\",\"domain\":\"example.com\",\"reload\":true}'"];
1203
+ readonly examples: ["set_cookie --payload '{\"tabId\":12345,\"name\":\"feature_flag\",\"value\":\"enabled\"}'", "set_cookie --payload '{\"url\":\"https://www.shop.com/\",\"name\":\"feature_flag\",\"value\":\"enabled\"}'", "set_cookie --payload '{\"tabId\":12345,\"name\":\"feature_channel\",\"value\":\"3.6.63\",\"domain\":\"example.com\",\"reload\":true}'"];
1194
1204
  }, {
1195
1205
  readonly name: "page_frames";
1196
1206
  readonly description: "获取页面主 frame 与所有 iframe 列表";
package/dist/commands.js CHANGED
@@ -1,6 +1,4 @@
1
- import { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, } from '@mearl/browser-core';
2
- /** 容器档案名列表(拼进参数说明,新增档案无需改本文件) */
3
- const APP_PROFILE_NAME_LIST = APP_PROFILE_NAMES.join('/');
1
+ import { BROWSER_DEVICE_PRESET_NAMES, } from '@mearl/browser-core';
4
2
  const DEVICE_PRESET_TYPE = BROWSER_DEVICE_PRESET_NAMES.map(name => JSON.stringify(name)).join(' | ');
5
3
  const PAGE_DIAGNOSTICS_PARAM = {
6
4
  name: 'diagnostics',
@@ -30,13 +28,20 @@ const PAGE_OBSERVE_PARAM = {
30
28
  const PAGE_ACTION_RESULT = [
31
29
  '默认返回 { action, observation };action.success 只表示动作成功,业务效果需结合 observation 和目标状态判断。',
32
30
  'observe=false 且未开启 diagnostics 时返回裸动作结果(如 { clicked: true }),没有 action/observation;开启 diagnostics 时返回 { action, diagnostics }。',
33
- '包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。失败以退出码 1 结束。',
31
+ '包装结果中的 action.stage=precondition 表示未派发,省略 observation/diagnostics;dispatch-uncertain 表示可能已派发,先查看 action.attempt 与可用观察或最小快照核验,避免重复提交。CLI 单次调用失败以退出码 1 结束;脚本需检查 action.success。',
34
32
  'observation.effects 返回通知、控件和焦点增量,可直接复用 node.ref/selector;changed=true 且 effects 为空不表示失败。',
35
33
  'effects.texts 按需返回普通可见文本的 selector/text,不代表可点击控件或业务成功;超限或覆盖不完整时仍提示补快照。',
36
34
  'fullSnapshotRecommended/snapshotReasons 提示是否补充快照;openedTabs 返回可继续操作的 tabId 和 ready。navigation 模式的 ready 表示新页面就绪。',
37
35
  '显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。',
38
36
  ];
39
37
  export const COMMANDS = [
38
+ {
39
+ name: 'get_capabilities',
40
+ description: '只读查询当前目标浏览器的动作、设备和已安装 App 能力、限制与 Skill/帮助入口;不打开页面或启用模拟',
41
+ params: [{ name: 'app', type: 'string', description: '可选,使用目录中的完整 App ID 查询桥方法、可调设置和默认版本详情' }],
42
+ notes: ['沿用 --browser / --session 路由;不传 app 返回摘要。apps.status=partial 表示目录未完整取得,unsupported 表示目标未提供目录;不能把两者当作未安装。', 'available=false 的 App 当前不可启用;支持某个 action 不代表已有域名权限或真实设备授权。按需读取 usage.skills / usage.documentation,查询不会自动安装或加载 Skill。'],
43
+ examples: ['get_capabilities', `get_capabilities --payload '{"app":"example/app"}'`],
44
+ },
40
45
  {
41
46
  name: 'browser_control',
42
47
  description: '把控制会话交给用户;用户在 Mearl 浏览器管理页交还后重新观察页面',
@@ -377,7 +382,7 @@ export const COMMANDS = [
377
382
  ],
378
383
  notes: [
379
384
  '需要目标浏览器有对应登录态;Mearl 自动处理签名,不依赖页面脚本。api 以 mopen. 开头时使用 mopen 协议。',
380
- 'env=prod 使用 h5api.m.taobao.com,pre 使用 h5api.wapa.taobao.com。',
385
+ 'env=prod/pre 的网关由已安装的请求协议能力提供。',
381
386
  ],
382
387
  result: ['返回 success、response、requestData;业务成功需检查 response.ret。'],
383
388
  examples: [
@@ -541,7 +546,7 @@ export const COMMANDS = [
541
546
  {
542
547
  name: 'appProfile',
543
548
  type: 'string',
544
- description: '按已安装档案应用 App 环境;内置基础档案只提供 UA/版本,完整桥由能力包提供。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true',
549
+ description: '按已安装档案应用 App 环境,ID 与限制通过 get_capabilities 查询。需要 iframe WebView 和系统栏时同时传 emulation.deviceShell=true',
545
550
  },
546
551
  {
547
552
  name: 'os',
@@ -567,9 +572,9 @@ export const COMMANDS = [
567
572
  `tab_open --payload '{"url":"https://example.com"}'`,
568
573
  `tab_open --payload '{"url":"https://example.com/page","reuse":"prefer","match":{"ignoreSearch":true}}'`,
569
574
  `tab_open --payload '{"url":"https://m.example.com","emulation":{"preset":"iphone-17-pro"}}'`,
570
- `tab_open --payload '{"url":"https://example.com/app/home","appProfile":"fliggy"}'`,
571
- `tab_open --payload '{"url":"https://example.com/app/home","appProfile":"fliggy","emulation":{"preset":"ipad-mini-a17-pro","orientation":"landscape","deviceShell":true,"splitScreen":true}}'`,
572
- `tab_open --payload '{"url":"https://example.com","appProfile":"fliggy","os":"android"}'`,
575
+ `tab_open --payload '{"url":"https://example.com/app/home","appProfile":"reader"}'`,
576
+ `tab_open --payload '{"url":"https://example.com/app/home","appProfile":"reader","emulation":{"preset":"ipad-mini-a17-pro","orientation":"landscape","deviceShell":true,"splitScreen":true}}'`,
577
+ `tab_open --payload '{"url":"https://example.com","appProfile":"reader","os":"android"}'`,
573
578
  `tab_open --payload '{"url":"https://example.com","group":"验证任务"}'`,
574
579
  ],
575
580
  },
@@ -1174,7 +1179,7 @@ export const COMMANDS = [
1174
1179
  {
1175
1180
  name: 'profile',
1176
1181
  type: 'string',
1177
- description: `容器档案名(enabled=true 时必填);内置 ${APP_PROFILE_NAME_LIST}`,
1182
+ description: '已安装的容器档案 ID(enabled=true 时必填);通过 get_capabilities 查询当前浏览器目录',
1178
1183
  },
1179
1184
  { name: 'os', type: '"ios" | "android"', description: '容器系统,默认 ios' },
1180
1185
  {
@@ -1220,12 +1225,12 @@ export const COMMANDS = [
1220
1225
  '返回 enabled、profile、label、userAgent、os、mtopEnv、reloaded;联动设备时额外返回实际 emulation。',
1221
1226
  ],
1222
1227
  examples: [
1223
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"fliggy"}'`,
1224
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"fliggy","mtopEnv":"pre"}'`,
1228
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader"}'`,
1229
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader","mtopEnv":"pre"}'`,
1225
1230
  `set_app_profile --payload '{"tabId":12345,"enabled":false,"mtopEnv":"prod"}'`,
1226
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"fliggy","emulation":{}}'`,
1227
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"alipay","emulation":{"preset":"pixel-11","deviceShell":true}}'`,
1228
- `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"fliggy","emulation":{"preset":"ipad-mini-a17-pro","orientation":"landscape","deviceShell":true,"splitScreen":true}}'`,
1231
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader","emulation":{}}'`,
1232
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"wallet","emulation":{"preset":"pixel-11","deviceShell":true}}'`,
1233
+ `set_app_profile --payload '{"tabId":12345,"enabled":true,"profile":"reader","emulation":{"preset":"ipad-mini-a17-pro","orientation":"landscape","deviceShell":true,"splitScreen":true}}'`,
1229
1234
  `set_app_profile --payload '{"tabId":12345,"enabled":false}'`,
1230
1235
  `set_app_profile --payload '{"tabId":12345,"enabled":false,"emulation":{}}'`,
1231
1236
  ],
@@ -1280,9 +1285,9 @@ export const COMMANDS = [
1280
1285
  result: ['找到时 found=true 并返回 value/domain/path;不存在时 found=false。'],
1281
1286
  examples: [
1282
1287
  `get_cookie --payload '{"tabId":12345,"name":"feature_flag"}'`,
1283
- `get_cookie --payload '{"url":"https://www.taobao.com/","name":"tracknick"}'`,
1288
+ `get_cookie --payload '{"url":"https://www.shop.com/","name":"account_name"}'`,
1284
1289
  `get_cookie --payload '{"tabId":12345,"name":"feature_channel","domain":"example.com"}'`,
1285
- `get_cookie --payload '{"tabId":12345,"name":"tracknick","url":"https://www.taobao.com/","domain":"example.com","path":"/"}'`,
1290
+ `get_cookie --payload '{"tabId":12345,"name":"account_name","url":"https://www.shop.com/","domain":"example.com","path":"/"}'`,
1286
1291
  ],
1287
1292
  },
1288
1293
  {
@@ -1327,7 +1332,7 @@ export const COMMANDS = [
1327
1332
  result: ['返回 url/name/domain/path/reloaded,指定标签页时包含 tabId;不回显 Cookie 值。'],
1328
1333
  examples: [
1329
1334
  `set_cookie --payload '{"tabId":12345,"name":"feature_flag","value":"enabled"}'`,
1330
- `set_cookie --payload '{"url":"https://www.taobao.com/","name":"feature_flag","value":"enabled"}'`,
1335
+ `set_cookie --payload '{"url":"https://www.shop.com/","name":"feature_flag","value":"enabled"}'`,
1331
1336
  `set_cookie --payload '{"tabId":12345,"name":"feature_channel","value":"3.6.63","domain":"example.com","reload":true}'`,
1332
1337
  ],
1333
1338
  },
package/dist/index.d.ts CHANGED
@@ -37,6 +37,7 @@ export declare function getRules(options?: UnifiedInvokeOptions): Promise<Protoc
37
37
  export declare function pageScreenshot(params: Protocol.PageScreenshotParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'page_screenshot'>>;
38
38
  export declare function tabOpen(params: Protocol.TabOpenParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_open'>>;
39
39
  export declare function tabClose(params: Protocol.TabCloseParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_close'>>;
40
+ export declare function getCapabilities(params?: Protocol.GetCapabilitiesParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'get_capabilities'>>;
40
41
  export declare function tabList(options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_list'>>;
41
42
  export declare function tabGroup(params: Protocol.TabGroupParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_group'>>;
42
43
  export declare function pageClick(params: Protocol.PageClickParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'page_click'>>;
@@ -69,3 +70,5 @@ export declare function saveArtifact(artifact: Protocol.BrowserArtifact & {
69
70
  export { PluginStateClient } from './plugins/clients/pluginState.js';
70
71
  export { PluginModelClient } from './plugins/clients/pluginModels.js';
71
72
  export type * from '@mearl/browser-core';
73
+ export { getStructure as structure, type StructureOptions } from '@mearl/browser-core';
74
+ export type { GetCapabilitiesParams, BrowserCapabilityCatalog, AppCapability, AppProfileUsage } from '@mearl/browser-core';
package/dist/index.js CHANGED
@@ -71,6 +71,9 @@ export function tabOpen(params, options) {
71
71
  export function tabClose(params, options) {
72
72
  return invoke('tab_close', params, options);
73
73
  }
74
+ export function getCapabilities(params = {}, options) {
75
+ return invoke('get_capabilities', params, options);
76
+ }
74
77
  export function tabList(options) {
75
78
  return invoke('tab_list', {}, options);
76
79
  }
@@ -146,3 +149,4 @@ export async function saveArtifact(artifact, outputPath, options = {}) {
146
149
  }
147
150
  export { PluginStateClient } from './plugins/clients/pluginState.js';
148
151
  export { PluginModelClient } from './plugins/clients/pluginModels.js';
152
+ export { getStructure as structure } from '@mearl/browser-core';
@@ -96,7 +96,7 @@ export function inspectNativeHostInstallation(options = {}) {
96
96
  details: [
97
97
  ...details,
98
98
  `Registration: ${manifestPath}`,
99
- 'Run `mearl-native-host init` with a working Node.js, then reopen the extension. If the runtime package is missing, run `npx --@mearl:registry=https://registry.npmjs.org @mearl/setup update --local`.',
99
+ 'Run `mearl-native-host init` with a working Node.js, then reopen the extension. If the runtime package is missing, run `npx @mearl/setup update --local`.',
100
100
  ],
101
101
  });
102
102
  }
@@ -16,6 +16,8 @@ export interface PluginHostClientOptions extends PluginHostConnectionOptions {
16
16
  }
17
17
  export interface HostStatus {
18
18
  protocolVersion: number;
19
+ version?: string;
20
+ buildId?: string;
19
21
  host: PluginExecutionHost;
20
22
  pid: number;
21
23
  }
@@ -243,7 +243,9 @@ export class PluginHostConnection {
243
243
  this.connection = undefined;
244
244
  });
245
245
  }
246
- return (await this.connection).request(method, input, timeoutMs);
246
+ const connection = await this.connection;
247
+ // The handshake protocol defines compatibility; build identity is diagnostic metadata.
248
+ return connection.request(method, input, timeoutMs);
247
249
  }
248
250
  disconnect() {
249
251
  this.generation++;
@@ -1,7 +1,7 @@
1
1
  import { type AppProfileContribution } from '@mearl/browser-core';
2
2
  export declare class PluginError extends Error {
3
3
  readonly code: string;
4
- constructor(code: string, message: string);
4
+ constructor(code: string, message: string, options?: ErrorOptions);
5
5
  }
6
6
  export interface PluginToolboxSandboxPage {
7
7
  type: 'sandbox';
@@ -9,6 +9,7 @@ import { buildPluginPageBundle, PLUGIN_PAGE_CHUNK_BYTES, PLUGIN_PAGE_RUNTIME_HAS
9
9
  import { pluginFileVersion } from './pluginSource.js';
10
10
  const RESERVED_NAMES = new Set([
11
11
  'check',
12
+ 'run',
12
13
  'help',
13
14
  'connector_list',
14
15
  'capture_checkpoint',
@@ -17,8 +18,8 @@ const RESERVED_NAMES = new Set([
17
18
  ]);
18
19
  export class PluginError extends Error {
19
20
  code;
20
- constructor(code, message) {
21
- super(`${code}: ${message}`);
21
+ constructor(code, message, options) {
22
+ super(`${code}: ${message}`, options);
22
23
  this.code = code;
23
24
  }
24
25
  }
@@ -405,8 +406,15 @@ export function findPluginRegistration(name) {
405
406
  ...(source ? { source } : {}),
406
407
  };
407
408
  }
408
- catch {
409
- throw new PluginError('PLUGIN_REGISTRY_INVALID', `无效的插件记录: ${file}`);
409
+ catch (cause) {
410
+ const reason = cause instanceof SyntaxError
411
+ ? 'JSON 格式错误'
412
+ : cause instanceof Error
413
+ ? cause.message
414
+ : '未知校验错误';
415
+ throw new PluginError('PLUGIN_REGISTRY_INVALID', `无效的插件记录: ${file} (${reason})`, {
416
+ cause,
417
+ });
410
418
  }
411
419
  }
412
420
  export function readPluginPageBundleChunk(name, hash, index) {
@@ -31,7 +31,7 @@ async function run(message) {
31
31
  : message.access === 'toolbox'
32
32
  ? invokeRegisteredToolboxPlugin
33
33
  : invokeRegisteredPlugin;
34
- const declaration = message.describe
34
+ const declaration = message.describe || message.help
35
35
  ? await loadPlugin(message.entry, {
36
36
  options: {},
37
37
  progress: () => { },
@@ -63,11 +63,13 @@ async function run(message) {
63
63
  const value = migration
64
64
  ? await migration.runMigration(message.invocation)
65
65
  : declaration
66
- ? {
67
- pageRuntime: declaration.pageRuntime,
68
- stateStorage: declaration.stateStorage,
69
- taskRecovery: declaration.taskRecovery,
70
- }
66
+ ? message.help
67
+ ? declaration.help(message.help.namespace, message.help.command)
68
+ : {
69
+ pageRuntime: declaration.pageRuntime,
70
+ stateStorage: declaration.stateStorage,
71
+ taskRecovery: declaration.taskRecovery,
72
+ }
71
73
  : message.model
72
74
  ? await (await import('@mearl/agent-runtime/codex')).runCodexModel(message.model, {
73
75
  signal: controller.signal,
@@ -6,6 +6,11 @@ export interface PluginExecutorDependencies {
6
6
  registration?: PluginRegistration;
7
7
  }
8
8
  export interface PluginExecutorOptions extends PluginInvokeOptions {
9
+ help?: {
10
+ namespace: string;
11
+ command?: string;
12
+ };
13
+ onOutput?: (stream: 'stdout' | 'stderr', chunk: Buffer) => void;
9
14
  describe?: boolean;
10
15
  model?: PluginModelRequest;
11
16
  recovery?: boolean;
@@ -8,7 +8,7 @@ import { stopPluginProcessTree } from './pluginProcessTree.js';
8
8
  import { processIdentity } from '@mearl/daemon-core';
9
9
  /** Executes registered trusted code in a process whose lifetime belongs to this invocation. */
10
10
  export async function executeRegisteredPlugin(plugin, invocation, context, options = {}, dependencies = {}) {
11
- const { signal, onProgress, source, access = 'public', executionTimeoutMs, cancellationGraceMs = 1000, workerPath, cwd, environment, describe, model, recovery, migration, expectedRecovery, onExecutorStarted, onExecutorStopped, onDiagnostic, ...invokeOptions } = options;
11
+ const { signal, onProgress, source, access = 'public', executionTimeoutMs, cancellationGraceMs = 1000, workerPath, cwd, environment, describe, help, onOutput, model, recovery, migration, expectedRecovery, onExecutorStarted, onExecutorStopped, onDiagnostic, ...invokeOptions } = options;
12
12
  for (const [name, value] of [
13
13
  ['executionTimeoutMs', executionTimeoutMs],
14
14
  ['cancellationGraceMs', cancellationGraceMs],
@@ -25,7 +25,7 @@ export async function executeRegisteredPlugin(plugin, invocation, context, optio
25
25
  const entryVersion = pluginFileVersion(registration.entry);
26
26
  return new Promise((resolve, reject) => {
27
27
  const child = fork(workerPath ?? PLUGIN_WORKER_PATH, [], {
28
- stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
28
+ stdio: ['ignore', onOutput ? 'pipe' : 'ignore', 'pipe', 'ipc'],
29
29
  serialization: 'advanced',
30
30
  execArgv: [],
31
31
  detached: process.platform !== 'win32',
@@ -46,6 +46,18 @@ export async function executeRegisteredPlugin(plugin, invocation, context, optio
46
46
  child.stderr?.on('data', chunk => {
47
47
  stderr = `${stderr}${chunk}`.slice(-65536);
48
48
  });
49
+ if (onOutput) {
50
+ for (const stream of ['stdout', 'stderr']) {
51
+ child[stream]?.on('data', chunk => {
52
+ try {
53
+ onOutput(stream, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
54
+ }
55
+ catch (error) {
56
+ stop(error instanceof Error ? error : new Error(String(error)));
57
+ }
58
+ });
59
+ }
60
+ }
49
61
  const send = (message) => {
50
62
  if (!child.connected || exited)
51
63
  return;
@@ -209,6 +221,7 @@ export async function executeRegisteredPlugin(plugin, invocation, context, optio
209
221
  entryVersion,
210
222
  access,
211
223
  describe,
224
+ help,
212
225
  model,
213
226
  recovery,
214
227
  migration,
@@ -13,6 +13,10 @@ export type PluginWorkerRequest = {
13
13
  access: 'public' | 'toolbox' | 'runtime';
14
14
  registration?: PluginRegistration;
15
15
  describe?: boolean;
16
+ help?: {
17
+ namespace: string;
18
+ command?: string;
19
+ };
16
20
  model?: PluginModelRequest;
17
21
  recovery?: boolean;
18
22
  migration?: boolean;
@@ -3,13 +3,14 @@ import { fileURLToPath } from 'node:url';
3
3
  /** Ownership monitoring runs outside the event loop that executes plugin code. */
4
4
  export function supervisePluginExecutor(workerUrl) {
5
5
  const child = fork(fileURLToPath(workerUrl), ['--command'], {
6
- stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
6
+ stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
7
7
  serialization: 'advanced',
8
8
  execArgv: [],
9
9
  });
10
10
  let ending = false;
11
11
  let forwarded = Promise.resolve();
12
12
  child.stderr?.on('data', chunk => process.stderr.write(chunk));
13
+ child.stdout?.on('data', chunk => process.stdout.write(chunk));
13
14
  const terminate = () => {
14
15
  if (process.platform === 'win32') {
15
16
  try {
@@ -1,3 +1,4 @@
1
+ import { CLIENT_VERSION } from '../../runtimePaths.js';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { createServer } from 'node:net';
3
4
  import { chmodSync, rmSync } from 'node:fs';
@@ -17,6 +18,7 @@ export async function startPluginHostServer(configuration = {}) {
17
18
  const owner = {
18
19
  pid: process.pid,
19
20
  protocolVersion: PLUGIN_TASK_PROTOCOL_VERSION,
21
+ version: CLIENT_VERSION,
20
22
  processIdentity: processIdentity(process.pid),
21
23
  };
22
24
  if (!claimRecord(paths.lease, owner))
@@ -103,6 +105,7 @@ export async function startPluginHostServer(configuration = {}) {
103
105
  };
104
106
  return {
105
107
  protocolVersion: PLUGIN_TASK_PROTOCOL_VERSION,
108
+ version: CLIENT_VERSION,
106
109
  host: manager.host,
107
110
  pid: process.pid,
108
111
  };
@@ -112,6 +115,7 @@ export async function startPluginHostServer(configuration = {}) {
112
115
  if (method === 'status')
113
116
  return {
114
117
  protocolVersion: PLUGIN_TASK_PROTOCOL_VERSION,
118
+ version: CLIENT_VERSION,
115
119
  host: manager.host,
116
120
  pid: process.pid,
117
121
  };
@@ -5,7 +5,6 @@ import { readFileSync, statSync } from 'node:fs';
5
5
  import { isAbsolute, join } from 'node:path';
6
6
  import { capturePluginRevision } from './pluginRevision.js';
7
7
  import { findPluginRegistration } from '../sdk/plugin.js';
8
- import { compilePluginPageSchema } from '../sdk/pluginPageDefinition.js';
9
8
  import { UnifiedClient, decodeBrowserTarget } from '../../unified.js';
10
9
  import { pluginHostPaths } from './pluginHostPaths.js';
11
10
  import { serializePluginExecutionError, pluginExecutionError } from './pluginExecutorProtocol.js';
@@ -726,9 +725,6 @@ export class PluginPageManager {
726
725
  const binding = record.runtime.events?.[name];
727
726
  if (!binding || Buffer.byteLength(JSON.stringify(input.payload ?? null)) > 32 * 1024)
728
727
  throw new Error('Page event is not declared or exceeds 32 KiB');
729
- const validate = compilePluginPageSchema(binding.schema);
730
- if (!validate(input.payload))
731
- throw failure('PLUGIN_PAGE_EVENT_INVALID', 'Page event payload does not match its schema');
732
728
  const waiting = record.deliveries.filter(item => item.name === name && item.state === 'queued');
733
729
  if (binding.policy === 'latest') {
734
730
  for (const previous of waiting) {