@mearl/client 3.0.0-beta.5 → 3.0.0-beta.8
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 +18 -14
- package/dist/actionTimeouts.js +0 -3
- package/dist/autoUpdate.js +2 -1
- package/dist/bin.js +12 -5
- package/dist/capabilityHelp.d.ts +4 -0
- package/dist/capabilityHelp.js +68 -0
- package/dist/check.js +1 -1
- package/dist/cli.js +17 -16
- package/dist/commandHelp.js +3 -3
- package/dist/commandSuggestions.d.ts +3 -0
- package/dist/commandSuggestions.js +16 -5
- package/dist/commands.d.ts +50 -55
- package/dist/commands.js +41 -41
- package/dist/guide.d.ts +46 -0
- package/dist/guide.js +71 -0
- package/dist/guides/api-browser-setup.md +93 -0
- package/dist/guides/api-page-automation.md +140 -0
- package/dist/guides/api-page-debugging.md +39 -0
- package/dist/guides/api-page-environment.md +37 -0
- package/dist/guides/api-request-debugging.md +54 -0
- package/dist/guides/browser-tool-cooperation.md +23 -0
- package/dist/guides/core.md +60 -0
- package/dist/guides/related-skills.md +32 -0
- package/dist/guides/scripting.md +21 -0
- package/dist/guides/troubleshooting.md +170 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -5
- package/dist/plugins/officialPluginInstaller.js +2 -1
- package/dist/plugins/pluginRegistry.js +9 -3
- package/dist/plugins/runtime/pluginDiagnostics.js +1 -1
- package/dist/plugins/runtime/pluginHostServer.js +1 -1
- package/dist/plugins/runtime/pluginPageManager.js +2 -2
- package/dist/plugins/runtime/pluginStateManager.js +1 -1
- package/dist/plugins/runtime/pluginTaskManager.js +2 -2
- package/dist/plugins/sdk/plugin.d.ts +2 -0
- package/dist/plugins/sdk/plugin.js +17 -9
- package/dist/script.js +2 -1
- package/dist/socket.js +32 -8
- package/dist/unified.js +11 -1
- package/dist/unifiedConfig.d.ts +4 -0
- package/dist/unifiedConfig.js +11 -0
- package/package.json +10 -10
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Mearl 环境安装与连接排查
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
|
|
5
|
+
- [环境安装、更新与卸载](#environment-management)
|
|
6
|
+
- [本机控制](#local)
|
|
7
|
+
- [浏览器扩展安装](#local-extension)
|
|
8
|
+
- [命令不可用或连接失败](#local-check)
|
|
9
|
+
- [EACCES](#eacces)
|
|
10
|
+
- [ENOENT](#enoent)
|
|
11
|
+
- [ECONNREFUSED / 连接关闭](#econnrefused)
|
|
12
|
+
- [远程控制](#cloud)
|
|
13
|
+
- [Agent(控制端)](#cloud-agent)
|
|
14
|
+
- [浏览器端](#cloud-browser)
|
|
15
|
+
- [远程控制常见问题](#cloud-issues)
|
|
16
|
+
- [通用问题](#common)
|
|
17
|
+
- [请求超时](#timeout)
|
|
18
|
+
|
|
19
|
+
按 Agent 与浏览器所在机器选择排障流程;命令参数读取对应 CLI 的 `--help`,失败时优先按错误或 `check` 输出的提示处理。
|
|
20
|
+
|
|
21
|
+
- **本机控制**:Agent 与浏览器在同一台机器,`@mearl/client` 通过 native host socket 通信。
|
|
22
|
+
- **远程控制**:Agent 与浏览器不在同一台机器,`@mearl/client` 通过 cloud-server + cloud-connector 中转。
|
|
23
|
+
|
|
24
|
+
<a id="environment-management"></a>
|
|
25
|
+
|
|
26
|
+
## 环境安装、更新与卸载
|
|
27
|
+
|
|
28
|
+
CLI、Native Host、MCP 和云端核心运行包要求 Node.js >= 20.18.0。安装前用 `node --version` 确认版本;
|
|
29
|
+
不满足时先升级 Node。3.0 beta 使用 `npx @mearl/setup@beta --yes`,保留显式的 `@beta` 标签。
|
|
30
|
+
出现 `Unknown operation: plugin` 时,用 `npx @mearl/setup@beta --version` 核对安装器版本;
|
|
31
|
+
未指定版本时,npm 可能因 Node 不兼容而选择历史版本。
|
|
32
|
+
|
|
33
|
+
环境管理使用 `npx @mearl/setup@beta <install|update|uninstall>`,具体选项按子命令 `--help` 查询。Agent 执行 install/uninstall 时附加 `--yes` 或 `--non-interactive`,避免等待交互;人工终端可保留交互选择。
|
|
34
|
+
|
|
35
|
+
主动安装或更新本机环境时,setup 优先使用显式 `--preset <清单 URL 或路径>`,否则保留本机已选 preset;本机未选择时,在运行时初始化后、安装任何 Skill 前检查已连接扩展的受信 preset 并补齐本机组件。首次连接最多等待约一分钟。多个浏览器选择不同、来源不受信或状态不可读时会停止并提示显式选择,不猜测。浏览器未连接时仅安装基础环境,连接后重跑 setup 即可;普通连接和后台自动更新不触发首次本机 preset 安装。
|
|
36
|
+
|
|
37
|
+
卸载只在用户明确要求一并移除全部 CLI 插件和浏览器 Provider 时传 `--full`;该选项不删除 Chrome 扩展。正常浏览器任务不主动安装或重新初始化,下方排障只在连接失败后使用。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
<a id="local"></a>
|
|
42
|
+
|
|
43
|
+
## 本机控制
|
|
44
|
+
|
|
45
|
+
<a id="local-extension"></a>
|
|
46
|
+
|
|
47
|
+
### 浏览器扩展安装
|
|
48
|
+
|
|
49
|
+
优先安装 [Chrome 商店版](https://chromewebstore.google.com/detail/mearl/aoehhjnofngknnjefamjbplchbolghkm),由 Chrome 管理更新。商店不可用或用户明确要求 setup 托管时,执行:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx @mearl/setup@beta extension --full --yes
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
该命令同时准备本地环境。人工在终端执行时可省略 `--yes`。
|
|
56
|
+
|
|
57
|
+
<a id="local-check"></a>
|
|
58
|
+
|
|
59
|
+
### 命令不可用或连接失败
|
|
60
|
+
|
|
61
|
+
`mearl` 或 `mearl-native-host` 命令不存在时,在浏览器所在机器安装:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx @mearl/setup@beta --local --yes
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
命令存在但连接失败时,先检查当前链路:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
mearl check
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
只需隔离检查当前机器时使用 `mearl check --local`;路由参数与配置优先级见 `mearl check --help`。只有检查结果表明本地组件缺失、过期或初始化不完整时,才更新并重新初始化:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx @mearl/setup@beta update --local --yes
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
<a id="eacces"></a>
|
|
80
|
+
|
|
81
|
+
### EACCES(权限不足)
|
|
82
|
+
|
|
83
|
+
仅当 native host 初始化写入系统目录时权限不足,才在浏览器所在机器执行:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
sudo mearl-native-host --init
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Socket 访问被 Agent 沙箱拒绝时,按错误提示配置访问权限,不通过 sudo 或改写 Socket 权限绕过。
|
|
90
|
+
|
|
91
|
+
<a id="enoent"></a>
|
|
92
|
+
|
|
93
|
+
### ENOENT(找不到 Socket)
|
|
94
|
+
|
|
95
|
+
client 会自动拉起 native host 并重试。自动恢复失败时按错误提示检查本机链路;手工启动或开发版扩展初始化的参数见 `mearl-native-host --help`,不要用 `nohup` 手工保活 daemon。
|
|
96
|
+
|
|
97
|
+
如果重试后仍然失败,可按以下步骤排查:
|
|
98
|
+
|
|
99
|
+
**使用浏览器插件模式:**
|
|
100
|
+
|
|
101
|
+
确认 Chrome 已打开并等待扩展建联;仍失败时打开 DevTools 的 Mearl 面板触发连接。开发版扩展使用其实际扩展 ID 初始化 native host,再重新执行命令。
|
|
102
|
+
|
|
103
|
+
**使用 CDP 模式:**
|
|
104
|
+
|
|
105
|
+
按 `mearl-native-host --help` 或连接错误提示在 Chrome 开启调试并允许授权,确认有普通网页已打开后重试。默认在 Extension 不可用时回退 CDP;需要单独验证 CDP 链路时使用 `mearl check --cdp`。
|
|
106
|
+
|
|
107
|
+
<a id="econnrefused"></a>
|
|
108
|
+
|
|
109
|
+
### ECONNREFUSED / 连接关闭
|
|
110
|
+
|
|
111
|
+
client 会处理当前用户的 stale socket 并尝试恢复 native host;恢复失败时运行 `mearl check --local`,按诊断处理,避免盲目删除 Socket 或重复初始化。
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
<a id="cloud"></a>
|
|
116
|
+
|
|
117
|
+
## 远程控制
|
|
118
|
+
|
|
119
|
+
远程控制需要在 **Agent(控制端)** 和 **浏览器端** 两端分别准备。
|
|
120
|
+
|
|
121
|
+
<a id="cloud-agent"></a>
|
|
122
|
+
|
|
123
|
+
### Agent(控制端)
|
|
124
|
+
|
|
125
|
+
1. 安装 cloud-server、统一 client 与当前 Agent 的全局 mearl skill:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npx @mearl/setup@beta --cloud --yes
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
setup 会启动 cloud-server 并输出连接命令。把输出的完整命令交给用户在浏览器所在机器执行,不自行拼接地址或 token。
|
|
132
|
+
|
|
133
|
+
2. 再次获取连接命令使用 `mearl-cloud-server status`;管理与日志命令见 `mearl-cloud-server --help`。
|
|
134
|
+
|
|
135
|
+
3. client 自动读取连接配置。用户完成本地连接后,在 Agent 侧验证:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
mearl check
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
按诊断定位故障所在机器,再在对应机器修复。默认不逐次传 Server 地址;覆盖参数、配置优先级和检查范围见 `mearl check --help`。连接 URL 可能包含 token,不写入日志或转发给无关方。
|
|
142
|
+
|
|
143
|
+
<a id="cloud-browser"></a>
|
|
144
|
+
|
|
145
|
+
### 浏览器端
|
|
146
|
+
|
|
147
|
+
1. 执行 Agent 侧输出的完整连接命令,把浏览器接入远程控制链路。
|
|
148
|
+
|
|
149
|
+
2. 浏览器端仍需满足 [本机控制](#local) 的前置条件(native host 已初始化、浏览器插件或 CDP 已建联),否则 connector 无法连到本地 socket。相关报错(EACCES / ENOENT / ECONNREFUSED)参考上文本机排查步骤。
|
|
150
|
+
|
|
151
|
+
<a id="cloud-issues"></a>
|
|
152
|
+
|
|
153
|
+
### 远程控制常见问题
|
|
154
|
+
|
|
155
|
+
- **Agent 侧无法连接 Server**:运行 `mearl check`,按失败项的提示处理。
|
|
156
|
+
- **需要确认动作会落到哪台机器**:运行 `mearl browser_list`,首次操作传返回的全局 `--browser <browserId>`;控制会话建立后由 `--session <sessionId>` 固定目标,无会话时继续传选定的 `--browser`。需要限制到某台远端机器时,从 `source` 获取 ID 或名称并传 `--connector <id|name>`。
|
|
157
|
+
- **connector 连不上或一直重连**:在浏览器端运行 `mearl-cloud-connector status`,按状态和错误提示处理。
|
|
158
|
+
- **connector 已接入但浏览器不可用或请求无响应**:在浏览器端运行 `mearl check --local`;需要检查 connector 版本与心跳时,Agent 端使用 `mearl connector_list`,字段见该命令 `--help`。
|
|
159
|
+
|
|
160
|
+
### 可选云连接协议
|
|
161
|
+
|
|
162
|
+
Qoder Cloud Agent 需要在浏览器机器先安装 `@mearl/plugin-qoder` 能力,再按该包 README 和 `docs/cloud-pairing.md` 生成配对信息、配置自定义工具。普通远端 Agent 使用上述 WebSocket 模式。核心未安装对应协议时会报告能力缺失,不会自动安装或读取该协议的凭据。
|
|
163
|
+
|
|
164
|
+
### 请求超时
|
|
165
|
+
|
|
166
|
+
超时可能发生在连接、动作执行或结果回传阶段,不能据此判断动作未执行。
|
|
167
|
+
|
|
168
|
+
- 连接超时:使用 `mearl check` 定位链路故障。
|
|
169
|
+
- 点击、提交、上传或写入超时:先读取当前页面或业务状态确认结果,再决定是否重试,避免重复副作用。
|
|
170
|
+
- 只读查询超时:按 CLI 提示缩小查询范围、裁剪结果或增加 `--timeout`;默认时间按 action 推导,不统一设为 30 秒。
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { UnifiedClient, UnifiedClient as MearlClient, decodeBrowserTarget, invok
|
|
|
13
13
|
export type { UnifiedClientOptions, UnifiedInvokeOptions, UnifiedInvokeOptions as InvokeOptions, } from './unified.js';
|
|
14
14
|
export type { PluginInvocation, PluginInvokeOptions, PluginProgress, StructuredPluginProgress, } from './plugins/sdk/plugin.js';
|
|
15
15
|
export { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, BROWSER_ACTIONS, BROWSER_CAPABILITY_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, isBrowserCapabilityAction, isBrowserCommandAction, isExtensionBrowserAction, } from '@mearl/browser-core';
|
|
16
|
-
export type { ActionStep, AppProfileName, BrowserAction, BrowserActionMap, BrowserActionRequest, BrowserActionResponse, BrowserCapabilities, BrowserCapabilityAction, BrowserCloseParams, BrowserCommandAction, BrowserInfo, BrowserInfoBase, BrowserJsonValue, BrowserLaunchParams, BrowserProviderOperation, BrowserListResult, BrowserPairingInfo, BrowserReleaseFailure, BrowserReleaseResult, BrowserProviderDefinition, BrowserProviderIdentity, BrowserProviderInputSchema, BrowserProviderListResult, BrowserReference, BrowserDetails, BrowserSource, BrowserStatus, BrowserScreenshotKind, BrowserTransport, CaptureCheckpointParams, CaptureCheckpointResult, BrowserDevicePresetName, BrowserEmulationOptions, ExtensionBrowserAction, GetCookieParams, GetCookieResult, GetEventsParams, GetLogsParams, GetMocksParams, GetRequestsParams, ManagedBrowserInfo, PageClickParams, PageDragEndpoint, PageDragParams, PageDragPoint, PageActAction, PageDiagnosticsOptions, PageDiagnosticsSetting, PageEvalParams, PageFramesParams, PageHoverParams, PageInspectFilter, PageInspectParams, PageNavigateParams, PageObservationWaitOptions, PagePressParams, PageScreenshotParams, PageScrollParams, PageSnapshotParams, PageSnapshotQuery, PageTypeParams, PageUploadParams, PageWaitParams, RegularBrowserInfo,
|
|
16
|
+
export type { ActionStep, AppProfileName, BrowserAction, BrowserActionMap, BrowserActionRequest, BrowserActionResponse, BrowserCapabilities, BrowserCapabilityAction, BrowserCloseParams, BrowserCommandAction, BrowserInfo, BrowserInfoBase, BrowserJsonValue, BrowserLaunchParams, BrowserProviderOperation, BrowserListResult, BrowserListParams, BrowserPairingInfo, BrowserReleaseFailure, BrowserReleaseResult, BrowserProviderDefinition, BrowserProviderIdentity, BrowserProviderInputSchema, BrowserProviderListResult, BrowserReference, BrowserDetails, BrowserSource, BrowserStatus, BrowserScreenshotKind, BrowserTransport, CaptureCheckpointParams, CaptureCheckpointResult, BrowserDevicePresetName, BrowserEmulationOptions, ExtensionBrowserAction, GetCookieParams, GetCookieResult, GetEventsParams, GetLogsParams, GetMocksParams, GetRequestsParams, ManagedBrowserInfo, PageClickParams, PageDragEndpoint, PageDragParams, PageDragPoint, PageActAction, PageDiagnosticsOptions, PageDiagnosticsSetting, PageEvalParams, PageFramesParams, PageHoverParams, PageInspectFilter, PageInspectParams, PageNavigateParams, PageObservationWaitOptions, PagePressParams, PageScreenshotParams, PageScrollParams, PageSnapshotParams, PageSnapshotQuery, PageTypeParams, PageUploadParams, PageWaitParams, RegularBrowserInfo, RequestPermissionParams, RulesResult, RunActionName, RunActionsParams, SelectBrowserParams, SelectBrowserResult, SendRequestFile, SendRequestBinaryBody, SendRequestFormValue, SendRequestParams, SetAppProfileParams, SetCookieParams, SetCookieResult, SetDeviceEmulationParams, SetMockParams, SetRuleParams, SetTimezoneParams, SetTimezoneResult, TabCloseParams, TabOpenParams, } from '@mearl/browser-core';
|
|
17
17
|
import { type UnifiedInvokeOptions } from './unified.js';
|
|
18
18
|
import type * as Protocol from '@mearl/browser-core';
|
|
19
19
|
export declare function tabCheckpoint(params: Protocol.CaptureCheckpointParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_checkpoint'>>;
|
|
@@ -25,8 +25,8 @@ export declare function getEvents(params: Protocol.GetEventsParams, options?: Un
|
|
|
25
25
|
export declare function setMock(params: Protocol.SetMockParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'set_mock'>>;
|
|
26
26
|
export declare function getMocks(params: Protocol.GetMocksParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'get_mocks'>>;
|
|
27
27
|
export declare function sendRequest(params: Protocol.SendRequestParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'send_request'>>;
|
|
28
|
-
export declare function sendMtopRequest(params: Protocol.SendMtopRequestParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'send_mtop_request'>>;
|
|
29
28
|
export declare function browserList(options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_list'>>;
|
|
29
|
+
export declare function browserList(params: Protocol.BrowserListParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_list'>>;
|
|
30
30
|
export declare function selectBrowser(params?: Protocol.SelectBrowserParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_select_browser'>>;
|
|
31
31
|
export declare function browserRelease(options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_release'>>;
|
|
32
32
|
export declare function browserRelease(params: Protocol.BrowserReleaseParams, options: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'browser_release'>>;
|
package/dist/index.js
CHANGED
|
@@ -36,11 +36,10 @@ export function getMocks(params, options) {
|
|
|
36
36
|
export function sendRequest(params, options) {
|
|
37
37
|
return invoke('send_request', params, options);
|
|
38
38
|
}
|
|
39
|
-
export function
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return invoke('browser_list', {}, options);
|
|
39
|
+
export function browserList(paramsOrOptions = {}, options) {
|
|
40
|
+
return 'provider' in paramsOrOptions || 'launchForm' in paramsOrOptions
|
|
41
|
+
? invoke('browser_list', paramsOrOptions, options)
|
|
42
|
+
: invoke('browser_list', {}, paramsOrOptions);
|
|
44
43
|
}
|
|
45
44
|
export function selectBrowser(params = {}, options) {
|
|
46
45
|
return invoke('browser_select_browser', params, options);
|
|
@@ -2,6 +2,7 @@ import { closeSync, mkdirSync, openSync, rmSync, statSync, writeFileSync } from
|
|
|
2
2
|
import { CLIENT_VERSION } from '../runtimePaths.js';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
|
+
import { npmExecEnvironment } from '@mearl/daemon-core';
|
|
5
6
|
import { findOfficialPlugin, readOfficialPluginCatalog } from './officialPluginCatalog.js';
|
|
6
7
|
import { findPluginRegistration, pluginDirectory } from './pluginRegistry.js';
|
|
7
8
|
const INSTALL_TIMEOUT_MS = 180_000;
|
|
@@ -60,7 +61,7 @@ function appendOutput(current, chunk) {
|
|
|
60
61
|
function installWithSetup(name) {
|
|
61
62
|
return new Promise((resolve, reject) => {
|
|
62
63
|
const command = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
63
|
-
const child = spawn(command, ['-y', '--@mearl:registry=https://registry.npmjs.org', `@mearl/setup@${CLIENT_VERSION}`, 'plugin', 'ensure', name], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
64
|
+
const child = spawn(command, ['-y', '--@mearl:registry=https://registry.npmjs.org', `@mearl/setup@${CLIENT_VERSION}`, 'plugin', 'ensure', name], { env: npmExecEnvironment(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
64
65
|
let stdout = '';
|
|
65
66
|
let stderr = '';
|
|
66
67
|
let settled = false;
|
|
@@ -8,6 +8,7 @@ import { COMMAND_MAP } from '../commands.js';
|
|
|
8
8
|
import { buildPluginPageBundle, PLUGIN_PAGE_CHUNK_BYTES, PLUGIN_PAGE_RUNTIME_HASH, } from './pluginPageBundle.js';
|
|
9
9
|
import { pluginFileVersion } from './pluginSource.js';
|
|
10
10
|
const RESERVED_NAMES = new Set([
|
|
11
|
+
'guide',
|
|
11
12
|
'check',
|
|
12
13
|
'run',
|
|
13
14
|
'help',
|
|
@@ -15,6 +16,11 @@ const RESERVED_NAMES = new Set([
|
|
|
15
16
|
'capture_checkpoint',
|
|
16
17
|
'plugin',
|
|
17
18
|
'plugins',
|
|
19
|
+
'plugin_state',
|
|
20
|
+
'plugin_model',
|
|
21
|
+
'plugin_page',
|
|
22
|
+
'plugin_invoke_child',
|
|
23
|
+
'plugin_task_checkpoint',
|
|
18
24
|
]);
|
|
19
25
|
export class PluginError extends Error {
|
|
20
26
|
code;
|
|
@@ -111,7 +117,7 @@ function persistPluginPageBundle(name, bundle) {
|
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
export function assertPluginName(name) {
|
|
114
|
-
if (!/^[a-z][a-z0-
|
|
120
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(name) || RESERVED_NAMES.has(name) || COMMAND_MAP.has(name)) {
|
|
115
121
|
throw new PluginError('PLUGIN_INVALID_NAME', `不可用的插件名称: ${name}`);
|
|
116
122
|
}
|
|
117
123
|
}
|
|
@@ -346,7 +352,7 @@ export function unregisterPlugin(name) {
|
|
|
346
352
|
}
|
|
347
353
|
}
|
|
348
354
|
export function findPluginRegistration(name) {
|
|
349
|
-
if (!/^[a-z][a-z0-
|
|
355
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(name))
|
|
350
356
|
return undefined;
|
|
351
357
|
const file = join(pluginDirectory(), `${name}.json`);
|
|
352
358
|
let content;
|
|
@@ -470,7 +476,7 @@ export function findPluginEntry(name) {
|
|
|
470
476
|
export function listPluginNames() {
|
|
471
477
|
try {
|
|
472
478
|
return readdirSync(pluginDirectory())
|
|
473
|
-
.filter(file => /^[a-z][a-z0-
|
|
479
|
+
.filter(file => /^[a-z][a-z0-9_-]*\.json$/.test(file))
|
|
474
480
|
.map(file => file.slice(0, -5))
|
|
475
481
|
.sort();
|
|
476
482
|
}
|
|
@@ -26,7 +26,7 @@ export function normalizeDiagnosticOptions(value) {
|
|
|
26
26
|
throw new Error('Invalid diagnostic instance ID');
|
|
27
27
|
if (input.plugin !== undefined &&
|
|
28
28
|
(typeof input.plugin !== 'string' ||
|
|
29
|
-
!/^[a-z][a-z0-
|
|
29
|
+
!/^[a-z][a-z0-9_-]*$/.test(input.plugin) ||
|
|
30
30
|
input.plugin.length > 128))
|
|
31
31
|
throw new Error('Invalid diagnostic plugin');
|
|
32
32
|
for (const key of ['includeErrorMessages', 'includeLocalPaths'])
|
|
@@ -91,7 +91,7 @@ export async function startPluginHostServer(configuration = {}) {
|
|
|
91
91
|
!['service', 'caller'].includes(String(input.bridge)) ||
|
|
92
92
|
!['cli', 'sdk', 'mcp', 'toolbox', 'runtime'].includes(String(input.source)) ||
|
|
93
93
|
(input.plugin !== undefined &&
|
|
94
|
-
(typeof input.plugin !== 'string' || !/^[a-z][a-z0-
|
|
94
|
+
(typeof input.plugin !== 'string' || !/^[a-z][a-z0-9_-]*$/.test(input.plugin))) ||
|
|
95
95
|
(input.scopeId !== undefined &&
|
|
96
96
|
(typeof input.scopeId !== 'string' || input.scopeId.length > 256)) ||
|
|
97
97
|
(input.access !== 'public' && (!input.plugin || input.bridge !== 'caller')))
|
|
@@ -272,7 +272,7 @@ export class PluginPageManager {
|
|
|
272
272
|
execution.signal?.throwIfAborted();
|
|
273
273
|
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
274
274
|
throw new Error('Invalid plugin page request');
|
|
275
|
-
if (!/^[a-z][a-z0-
|
|
275
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(plugin) ||
|
|
276
276
|
(bridge.scope.plugin && bridge.scope.plugin !== plugin))
|
|
277
277
|
throw new Error('Invalid plugin page scope');
|
|
278
278
|
const operation = input.operation;
|
|
@@ -656,7 +656,7 @@ export class PluginPageManager {
|
|
|
656
656
|
async receive(plugin, input, bridge, options = {}) {
|
|
657
657
|
if (input.kind === 'sync') {
|
|
658
658
|
if (this.closed ||
|
|
659
|
-
!/^[a-z][a-z0-
|
|
659
|
+
!/^[a-z][a-z0-9_-]*$/.test(plugin) ||
|
|
660
660
|
(bridge.scope.plugin && bridge.scope.plugin !== plugin) ||
|
|
661
661
|
!Array.isArray(input.instances) ||
|
|
662
662
|
input.instances.length > 32)
|
|
@@ -29,7 +29,7 @@ export class PluginStateManager {
|
|
|
29
29
|
async call(plugin, input, bridge, options = {}, binding) {
|
|
30
30
|
this.controller.signal.throwIfAborted();
|
|
31
31
|
binding?.signal?.throwIfAborted();
|
|
32
|
-
if (!/^[a-z][a-z0-
|
|
32
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(plugin) ||
|
|
33
33
|
(bridge.scope.plugin && bridge.scope.plugin !== plugin) ||
|
|
34
34
|
!input ||
|
|
35
35
|
typeof input !== 'object' ||
|
|
@@ -205,7 +205,7 @@ export class PluginTaskManager {
|
|
|
205
205
|
executionTimeoutMs: request.executionTimeoutMs ?? 120000,
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
|
-
if (!/^[a-z][a-z0-
|
|
208
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(request.plugin) || !/^[a-z][a-z0-9-]*$/.test(request.command))
|
|
209
209
|
throw new Error('Invalid plugin task command');
|
|
210
210
|
if (bridge.scope.plugin && request.plugin !== bridge.scope.plugin)
|
|
211
211
|
throw error('PLUGIN_COMMAND_NOT_ALLOWED', 'Plugin task is outside the caller scope');
|
|
@@ -884,7 +884,7 @@ export class PluginTaskManager {
|
|
|
884
884
|
});
|
|
885
885
|
}
|
|
886
886
|
if (restrictedTarget &&
|
|
887
|
-
!['send_request', '
|
|
887
|
+
!['send_request', 'browser_list', 'connector_list'].includes(action))
|
|
888
888
|
throw error('PLUGIN_PAGE_TARGET_CHANGED', 'Runtime tasks must access the document through context.page');
|
|
889
889
|
this.assertRouting(options, request.options);
|
|
890
890
|
if (client) {
|
|
@@ -72,6 +72,7 @@ export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'opti
|
|
|
72
72
|
}, pluginName?: string): Promise<{
|
|
73
73
|
name: string | undefined;
|
|
74
74
|
description: string | undefined;
|
|
75
|
+
defaultCommand: string | undefined;
|
|
75
76
|
toolbox: PluginToolboxContribution | undefined;
|
|
76
77
|
pageRuntime: PluginPageRuntime | undefined;
|
|
77
78
|
stateStorage: PluginStateDeclaration | undefined;
|
|
@@ -90,6 +91,7 @@ export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'opti
|
|
|
90
91
|
export declare function loadRegisteredPlugin(name: string, context: PluginExecutionContext, options?: PluginInvokeOptions): Promise<{
|
|
91
92
|
name: string | undefined;
|
|
92
93
|
description: string | undefined;
|
|
94
|
+
defaultCommand: string | undefined;
|
|
93
95
|
toolbox: PluginToolboxContribution | undefined;
|
|
94
96
|
pageRuntime: PluginPageRuntime | undefined;
|
|
95
97
|
stateStorage: PluginStateDeclaration | undefined;
|
|
@@ -146,6 +146,7 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
146
146
|
let registering = true;
|
|
147
147
|
let name;
|
|
148
148
|
let description;
|
|
149
|
+
let defaultCommand;
|
|
149
150
|
let toolbox;
|
|
150
151
|
let pageRuntime;
|
|
151
152
|
let stateStorage;
|
|
@@ -312,6 +313,9 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
312
313
|
if (module.description !== undefined && typeof module.description !== 'string')
|
|
313
314
|
throw new Error('入口的 description 导出必须为字符串');
|
|
314
315
|
description = module.description;
|
|
316
|
+
if (module.defaultCommand !== undefined && typeof module.defaultCommand !== 'string')
|
|
317
|
+
throw new Error('入口的 defaultCommand 导出必须为字符串');
|
|
318
|
+
defaultCommand = module.defaultCommand;
|
|
315
319
|
toolbox = normalizePluginToolboxContribution(module.toolbox);
|
|
316
320
|
pageRuntime = normalizePluginPageRuntime(module.pageRuntime);
|
|
317
321
|
stateStorage = normalizePluginState(module.stateStorage);
|
|
@@ -326,6 +330,8 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
326
330
|
}
|
|
327
331
|
if (commands.size === 0 && !appProfiles)
|
|
328
332
|
throw new Error('入口未注册任何命令');
|
|
333
|
+
if (defaultCommand !== undefined && (!commands.has(defaultCommand) || commands.get(defaultCommand)?.toolboxOnly || commands.get(defaultCommand)?.runtimeOnly))
|
|
334
|
+
throw new Error('defaultCommand 必须指向公开命令');
|
|
329
335
|
for (const command of Object.keys(taskRecovery?.commands ?? {})) {
|
|
330
336
|
if (!commands.has(command) ||
|
|
331
337
|
commands.get(command)?.runtimeOnly ||
|
|
@@ -401,12 +407,13 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
401
407
|
if (payload !== undefined && Object.keys(namedInput).length > 0) {
|
|
402
408
|
throw new PluginError('PLUGIN_INVALID_INPUT', '插件业务选项与 --payload 调用不能混用');
|
|
403
409
|
}
|
|
404
|
-
|
|
405
|
-
|
|
410
|
+
const defaultPayload = defaultCommand && toolboxPage === false && isObject(payload) && !Object.hasOwn(payload, 'command');
|
|
411
|
+
if (payload !== undefined && !defaultPayload && !isPluginInvocation(payload)) {
|
|
406
412
|
throw new PluginError('PLUGIN_INVALID_INPUT', '--payload 必须为 {"command":"...","input":{...}}');
|
|
407
413
|
}
|
|
408
|
-
const envelope = payload;
|
|
409
|
-
const
|
|
414
|
+
const envelope = (defaultPayload ? { command: defaultCommand, input: payload } : payload);
|
|
415
|
+
const positionalOffset = defaultCommand && !commands.has(positionals[0]) && toolboxPage === false ? 0 : 1;
|
|
416
|
+
const commandName = envelope?.command ?? (positionalOffset === 0 ? defaultCommand : positionals[0] ?? '');
|
|
410
417
|
const hidden = toolboxPage === 'recovery' || toolboxPage === 'migration';
|
|
411
418
|
const selected = find(commandName, hidden ? false : toolboxPage);
|
|
412
419
|
const command = selected.command;
|
|
@@ -426,7 +433,7 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
426
433
|
: Object.fromEntries(command.registeredArguments
|
|
427
434
|
.map((argument, index) => [
|
|
428
435
|
argument.name(),
|
|
429
|
-
argument.variadic ? positionals.slice(index +
|
|
436
|
+
argument.variadic ? positionals.slice(index + positionalOffset) : positionals[index + positionalOffset],
|
|
430
437
|
])
|
|
431
438
|
.filter(([, value]) => value !== undefined));
|
|
432
439
|
for (const name of Object.keys(namedInput)) {
|
|
@@ -437,7 +444,7 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
437
444
|
const input = { ...positionalInput, ...namedInput };
|
|
438
445
|
if (!envelope &&
|
|
439
446
|
!command.registeredArguments.some(argument => argument.variadic) &&
|
|
440
|
-
positionals.length -
|
|
447
|
+
positionals.length - positionalOffset > command.registeredArguments.length) {
|
|
441
448
|
throw new PluginError('PLUGIN_INVALID_INPUT', '位置参数过多');
|
|
442
449
|
}
|
|
443
450
|
for (const argument of hidden ? [] : command.registeredArguments) {
|
|
@@ -529,6 +536,7 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
529
536
|
return {
|
|
530
537
|
name,
|
|
531
538
|
description,
|
|
539
|
+
defaultCommand,
|
|
532
540
|
toolbox,
|
|
533
541
|
pageRuntime,
|
|
534
542
|
stateStorage,
|
|
@@ -538,11 +546,11 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
538
546
|
version: readPluginVersion(entry),
|
|
539
547
|
commandHelp(namespace) {
|
|
540
548
|
return publicCommands()
|
|
541
|
-
.map(({ command, signature }) => ` ${namespace} ${signature} ${command.description()}`)
|
|
549
|
+
.map(({ command, signature }) => ` ${namespace} ${command.name() === defaultCommand ? signature.slice(defaultCommand.length).trim() : signature} ${command.description()}`)
|
|
542
550
|
.join('\n');
|
|
543
551
|
},
|
|
544
552
|
help(namespace, name) {
|
|
545
|
-
const selected = name ? [find(name)] : publicCommands();
|
|
553
|
+
const selected = name ? [find(defaultCommand && !commands.has(name) ? defaultCommand : name)] : publicCommands();
|
|
546
554
|
if (!selected.length) {
|
|
547
555
|
const location = toolbox
|
|
548
556
|
? `请在 Mearl ${toolbox.displayMode === 'popup' ? 'Popup' : '工具箱'} 中打开「${toolbox.title}」使用。`
|
|
@@ -552,7 +560,7 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
552
560
|
const examples = selected.flatMap(({ examples }) => examples);
|
|
553
561
|
return [
|
|
554
562
|
'用法:',
|
|
555
|
-
...selected.map(({ command, signature }) => ` ${namespace} ${signature} ${command.description()}`),
|
|
563
|
+
...selected.map(({ command, signature }) => ` ${namespace} ${command.name() === defaultCommand ? signature.slice(defaultCommand.length).trim() : signature} ${command.description()}`),
|
|
556
564
|
...(examples.length
|
|
557
565
|
? ['', '示例:', ...examples.map(example => ` ${namespace} ${example}`)]
|
|
558
566
|
: []),
|
package/dist/script.js
CHANGED
|
@@ -208,7 +208,8 @@ export async function executeScript(code, options = {}) {
|
|
|
208
208
|
session: sessionId,
|
|
209
209
|
...args[2],
|
|
210
210
|
});
|
|
211
|
-
const optionsOnly = OPTIONS_ONLY.has(method)
|
|
211
|
+
const optionsOnly = (OPTIONS_ONLY.has(method) && !(method === 'browserList' && args[0] &&
|
|
212
|
+
('provider' in args[0] || 'launchForm' in args[0]))) || (method === 'browserRelease' && args.length < 2);
|
|
212
213
|
return invoke(METHOD_ACTIONS.get(method), optionsOnly ? {} : (args[0] ?? {}), optionsOnly ? args[0] : args[1]);
|
|
213
214
|
})
|
|
214
215
|
.then(result => send({ type: 'response', id, result, session }), error => send({ type: 'response', id, error: serializeError(error), session }))
|
package/dist/socket.js
CHANGED
|
@@ -13,13 +13,13 @@ import os from 'node:os';
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { CLIENT_VERSION } from './runtimePaths.js';
|
|
15
15
|
import { StringDecoder } from 'node:string_decoder';
|
|
16
|
-
import { ensureProviderControlHost, listInstalledProviders } from '@mearl/provider/host';
|
|
17
|
-
import { MEARL_PROVIDER_PROTOCOL_VERSION } from '@mearl/provider';
|
|
18
|
-
import { readLiveProvider } from '@mearl/provider/runtime';
|
|
16
|
+
import { assertProviderControlSessionMode, ensureProviderControlHost, listInstalledProviders } from '@mearl/provider/host';
|
|
17
|
+
import { MEARL_PROVIDER_PROTOCOL_VERSION, assertProviderProtocolVersion, validateProviderLaunchForm } from '@mearl/provider';
|
|
18
|
+
import { readLiveProvider, ensureInstalledProvidersRunning } from '@mearl/provider/runtime';
|
|
19
19
|
import { resolveFileRefs } from './fileRefs.js';
|
|
20
20
|
import { listLiveBrowsers, findBrowser, describeBrowser, } from './browserRegistry.js';
|
|
21
21
|
import { resolveActionTimeoutSec } from './actionTimeouts.js';
|
|
22
|
-
import { isBrowserCapabilityAction } from '@mearl/browser-core';
|
|
22
|
+
import { isBrowserCapabilityAction, parseBrowserListParams } from '@mearl/browser-core';
|
|
23
23
|
export { CLIENT_VERSION };
|
|
24
24
|
const MAX_SOCKET_BUFFER_SIZE = 64 * 1024 * 1024;
|
|
25
25
|
/**
|
|
@@ -376,6 +376,29 @@ export function invoke(action, data = {}, options = {}) {
|
|
|
376
376
|
const resolvedData = resolveFileRefs(data);
|
|
377
377
|
Object.defineProperty(resolvedData, SESSION_OPTION, { value: options.controlSessionId });
|
|
378
378
|
if (action === 'browser_list') {
|
|
379
|
+
const discovery = parseBrowserListParams(resolvedData);
|
|
380
|
+
if (discovery) {
|
|
381
|
+
return (async () => {
|
|
382
|
+
if (transport || options.socketPath || controlSource !== 'local' || options.controlSessionId)
|
|
383
|
+
throw new Error('Provider discovery requires the local source without a transport, socket or session override');
|
|
384
|
+
const provider = listInstalledProviders().find(provider => provider.providerId === discovery.provider);
|
|
385
|
+
if (!provider?.browserType.dynamicLaunchForm)
|
|
386
|
+
throw new Error(`Provider does not support dynamic launch forms: ${discovery.provider}`);
|
|
387
|
+
const warnings = await ensureInstalledProvidersRunning([discovery.provider]);
|
|
388
|
+
if (warnings.length)
|
|
389
|
+
throw new Error(warnings.join('; '));
|
|
390
|
+
const live = readLiveProvider(discovery.provider);
|
|
391
|
+
if (!live)
|
|
392
|
+
throw new Error(`Provider did not start: ${discovery.provider}`);
|
|
393
|
+
assertProviderProtocolVersion(live.providerProtocolVersion, discovery.provider);
|
|
394
|
+
const form = await invokeOnce('browser_providers', { operation: 'launch_form', ...discovery.launchForm }, timeoutSec, undefined, live.socketPath, 'local');
|
|
395
|
+
validateProviderLaunchForm(form);
|
|
396
|
+
return decorateBrowserListResult({
|
|
397
|
+
...browserListFromProviderRecords(listLiveBrowsers().filter(browser => browser.provider?.id === discovery.provider)),
|
|
398
|
+
discovery: { provider: discovery.provider, launchForm: form },
|
|
399
|
+
});
|
|
400
|
+
})();
|
|
401
|
+
}
|
|
379
402
|
return dispatchInvoke(action, resolvedData, timeoutSec, transport, undefined, controlSource, options.socketPath).then(result => decorateBrowserListResult(transport === 'cdp' ? cdpOnlyBrowserList(result) : result));
|
|
380
403
|
}
|
|
381
404
|
const selector = browser ?? defaultBrowser ?? process.env.MEARL_BROWSER ?? undefined;
|
|
@@ -510,7 +533,7 @@ async function dispatchInvoke(action, data, timeoutSec, transport, selector, con
|
|
|
510
533
|
throw new Error(`Browser ${describeBrowser(match)} does not support ${action}`);
|
|
511
534
|
}
|
|
512
535
|
try {
|
|
513
|
-
return await invokeOnce(action, data, timeoutSec, transport, match.socketPath, controlSource, match.transport === 'provider' ? match.browserId : undefined);
|
|
536
|
+
return await invokeOnce(action, data, timeoutSec, transport, match.socketPath, controlSource, match.transport === 'provider' ? match.browserId : undefined, match.capabilities?.controlSessionMode);
|
|
514
537
|
}
|
|
515
538
|
catch (error) {
|
|
516
539
|
if (isConnError(error) || isSocketSandboxError(error)) {
|
|
@@ -527,7 +550,7 @@ async function dispatchInvoke(action, data, timeoutSec, transport, selector, con
|
|
|
527
550
|
if (candidates.length > 0) {
|
|
528
551
|
const target = candidates[0];
|
|
529
552
|
try {
|
|
530
|
-
return await invokeOnce(action, data, timeoutSec, transport, target.socketPath, controlSource, target.transport === 'provider' ? target.browserId : undefined);
|
|
553
|
+
return await invokeOnce(action, data, timeoutSec, transport, target.socketPath, controlSource, target.transport === 'provider' ? target.browserId : undefined, target.capabilities?.controlSessionMode);
|
|
531
554
|
}
|
|
532
555
|
catch (error) {
|
|
533
556
|
if (isSocketSandboxError(error)) {
|
|
@@ -575,7 +598,7 @@ async function invokeViaLegacy(action, data, timeoutSec, transport, controlSourc
|
|
|
575
598
|
!(transport === 'cdp' && browser.transport === 'provider') &&
|
|
576
599
|
supportsAction(browser, action, data));
|
|
577
600
|
if (target) {
|
|
578
|
-
return invokeOnce(action, data, timeoutSec, transport, target.socketPath, controlSource, target.transport === 'provider' ? target.browserId : undefined);
|
|
601
|
+
return invokeOnce(action, data, timeoutSec, transport, target.socketPath, controlSource, target.transport === 'provider' ? target.browserId : undefined, target.capabilities?.controlSessionMode);
|
|
579
602
|
}
|
|
580
603
|
return invokeViaLegacy(action, data, timeoutSec, transport, controlSource,
|
|
581
604
|
/* isRetry */ true, localOnly);
|
|
@@ -586,9 +609,10 @@ async function invokeViaLegacy(action, data, timeoutSec, transport, controlSourc
|
|
|
586
609
|
throw new Error(formatConnectError(err, SOCKET_PATH), { cause: error });
|
|
587
610
|
}
|
|
588
611
|
}
|
|
589
|
-
async function invokeOnce(action, data, timeoutSec, transport, socketPath, controlSource, providerTarget) {
|
|
612
|
+
async function invokeOnce(action, data, timeoutSec, transport, socketPath, controlSource, providerTarget, controlSessionMode) {
|
|
590
613
|
if (providerTarget) {
|
|
591
614
|
const host = await ensureProviderControlHost(Math.min(timeoutSec * 1000, 10_000));
|
|
615
|
+
assertProviderControlSessionMode(host, controlSessionMode);
|
|
592
616
|
socketPath = host.socketPath;
|
|
593
617
|
}
|
|
594
618
|
const id = `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
package/dist/unified.js
CHANGED
|
@@ -3,7 +3,7 @@ import { stageFile, exportArtifact } from './artifacts.js';
|
|
|
3
3
|
import WebSocket from 'ws';
|
|
4
4
|
import { DEFAULT_CONNECT_TIMEOUT, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_REQUEST_TIMEOUT, MAX_BUFFER_SIZE, isHeartbeatMessage, resolveCloudConnectorSelector, } from '@mearl/cloud-types';
|
|
5
5
|
import { resolveActionTimeoutSec } from './actionTimeouts.js';
|
|
6
|
-
import { validateRequiredActionTabId, } from '@mearl/browser-core';
|
|
6
|
+
import { validateRequiredActionTabId, parseBrowserListParams, } from '@mearl/browser-core';
|
|
7
7
|
import { invoke as invokeLocalBrowser, resolveLocalActionTarget } from './socket.js';
|
|
8
8
|
import { loadCloudServerConfig, loadLocalConnectorId } from './unifiedConfig.js';
|
|
9
9
|
const RESPONSE_GRACE_MS = 1_000;
|
|
@@ -146,6 +146,9 @@ export class UnifiedClient {
|
|
|
146
146
|
}, options);
|
|
147
147
|
}
|
|
148
148
|
async invoke(action, data = {}, options = {}) {
|
|
149
|
+
const discovery = action === 'browser_list' ? parseBrowserListParams(data) : undefined;
|
|
150
|
+
if (discovery && (!options.local || options.connector || options.transport || options.session || options.controlSessionId))
|
|
151
|
+
throw new Error('Provider discovery requires explicit --local without connector, transport, socket or session overrides');
|
|
149
152
|
if (action === 'plugin_state') {
|
|
150
153
|
const { PluginStateClient } = await import('./plugins/clients/pluginState.js');
|
|
151
154
|
const state = new PluginStateClient();
|
|
@@ -350,6 +353,12 @@ export class UnifiedClient {
|
|
|
350
353
|
if (action === 'connector_list')
|
|
351
354
|
return this.listConnectors(timeoutSec);
|
|
352
355
|
if (action === 'browser_list') {
|
|
356
|
+
if (parseBrowserListParams(data)) {
|
|
357
|
+
const result = await invokeLocalBrowser('browser_list', data, { timeoutSec, controlSource: 'local' });
|
|
358
|
+
if (!result?.discovery)
|
|
359
|
+
throw new Error('Provider launch-form discovery is not supported by this host');
|
|
360
|
+
return this.applyConfiguredDefault(this.decorateListing(result, { kind: 'local' }));
|
|
361
|
+
}
|
|
353
362
|
if (options.local || options.transport === 'cdp') {
|
|
354
363
|
return this.listLocalBrowsers(timeoutSec, options.transport);
|
|
355
364
|
}
|
|
@@ -543,6 +552,7 @@ export class UnifiedClient {
|
|
|
543
552
|
})
|
|
544
553
|
: null,
|
|
545
554
|
browsers,
|
|
555
|
+
...(result?.discovery ? { discovery: result.discovery } : {}),
|
|
546
556
|
...(resultWarnings.length > 0 ? { warnings: resultWarnings } : {}),
|
|
547
557
|
};
|
|
548
558
|
}
|
package/dist/unifiedConfig.d.ts
CHANGED
|
@@ -6,3 +6,7 @@ export declare function loadCliAutoUpdateIntervalDays(configPath?: string): numb
|
|
|
6
6
|
export declare function getCloudServerConfigPath(): string;
|
|
7
7
|
export declare function loadCloudServerConfig(): CloudServerConfig | null;
|
|
8
8
|
export declare function loadLocalConnectorId(): string | null;
|
|
9
|
+
export declare function resolveServer(explicit?: string): {
|
|
10
|
+
serverUrl?: string;
|
|
11
|
+
serverUrlSource?: string;
|
|
12
|
+
};
|
package/dist/unifiedConfig.js
CHANGED
|
@@ -56,3 +56,14 @@ export function loadLocalConnectorId() {
|
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
export function resolveServer(explicit) {
|
|
60
|
+
if (explicit)
|
|
61
|
+
return { serverUrl: explicit, serverUrlSource: '--server' };
|
|
62
|
+
if (process.env.MEARL_SERVER_URL) {
|
|
63
|
+
return { serverUrl: process.env.MEARL_SERVER_URL, serverUrlSource: 'MEARL_SERVER_URL' };
|
|
64
|
+
}
|
|
65
|
+
const config = loadCloudServerConfig();
|
|
66
|
+
return config?.server
|
|
67
|
+
? { serverUrl: config.server, serverUrlSource: getCloudServerConfigPath() }
|
|
68
|
+
: {};
|
|
69
|
+
}
|