@mearl/client 2.15.0 → 2.15.2
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 +7 -12
- package/dist/cli.js +20 -3
- package/dist/officialPluginCatalog.d.ts +79 -0
- package/dist/officialPluginCatalog.js +80 -0
- package/dist/officialPluginInstaller.d.ts +3 -0
- package/dist/officialPluginInstaller.js +130 -0
- package/dist/plugin.d.ts +4 -7
- package/dist/plugin.js +45 -25
- package/dist/socket.d.ts +3 -2
- package/dist/socket.js +79 -36
- package/package.json +3 -3
- package/dist/buc.d.ts +0 -65
- package/dist/buc.js +0 -533
package/dist/socket.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* 通过 Unix Domain Socket (macOS/Linux) 或 Named Pipe (Windows) 与 native host 进程通信。
|
|
5
5
|
* 每次调用新建连接,收到响应后关闭,简单可靠。
|
|
6
6
|
*
|
|
7
|
-
* 当 Socket
|
|
7
|
+
* 当 Socket 不存在,或 legacy Socket 是无人监听的残留文件时,
|
|
8
|
+
* 自动尝试拉起 native host,稍后重试连接。
|
|
8
9
|
*/
|
|
9
10
|
import net from 'node:net';
|
|
10
11
|
import fs from 'node:fs';
|
|
@@ -138,46 +139,85 @@ function selectBrowserResult(data) {
|
|
|
138
139
|
setDefaultBrowser(match.browserId);
|
|
139
140
|
return { selected: { browserId: match.browserId, name: match.browserName } };
|
|
140
141
|
}
|
|
141
|
-
/**
|
|
142
|
-
let
|
|
142
|
+
/** Deduplicates concurrent recovery attempts in long-lived clients such as the MCP server. */
|
|
143
|
+
let persistentSpawnPromise = null;
|
|
143
144
|
/**
|
|
144
145
|
* Attempt to spawn `mearl-native-host` in the background (detached).
|
|
145
146
|
* Browser discovery hosts probe the default local CDP endpoint for browser_list and only
|
|
146
147
|
* persist when a real browser is available.
|
|
147
148
|
* Returns true if a spawn was attempted (regardless of success).
|
|
148
149
|
*/
|
|
149
|
-
async function trySpawnNativeHost(mode = 'control'
|
|
150
|
+
async function trySpawnNativeHost(mode = 'control') {
|
|
150
151
|
const persistent = mode === 'control';
|
|
151
|
-
if (persistent &&
|
|
152
|
-
return
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
152
|
+
if (persistent && persistentSpawnPromise)
|
|
153
|
+
return persistentSpawnPromise;
|
|
154
|
+
const spawnPromise = (async () => {
|
|
155
|
+
try {
|
|
156
|
+
const { spawn } = await import('node:child_process');
|
|
157
|
+
return await new Promise(resolve => {
|
|
158
|
+
const args = persistent ? ['--daemon'] : ['--daemon', '--discover-cdp'];
|
|
159
|
+
const child = spawn('mearl-native-host', args, {
|
|
160
|
+
detached: true,
|
|
161
|
+
stdio: 'ignore',
|
|
162
|
+
windowsHide: true,
|
|
163
|
+
});
|
|
164
|
+
let settled = false;
|
|
165
|
+
const finish = (ok) => {
|
|
166
|
+
if (settled)
|
|
167
|
+
return;
|
|
168
|
+
settled = true;
|
|
169
|
+
resolve(ok);
|
|
170
|
+
};
|
|
171
|
+
// `spawn ENOENT` 等错误会通过事件异步抛出;必须显式监听避免进程崩溃。
|
|
172
|
+
child.once('error', () => finish(false));
|
|
173
|
+
child.once('spawn', () => {
|
|
174
|
+
child.unref();
|
|
175
|
+
finish(true);
|
|
176
|
+
});
|
|
176
177
|
});
|
|
177
|
-
}
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
})();
|
|
183
|
+
if (!persistent)
|
|
184
|
+
return spawnPromise;
|
|
185
|
+
persistentSpawnPromise = spawnPromise;
|
|
186
|
+
try {
|
|
187
|
+
return await spawnPromise;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
if (persistentSpawnPromise === spawnPromise)
|
|
191
|
+
persistentSpawnPromise = null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function readOwnedLegacySocketIdentity() {
|
|
195
|
+
if (process.platform === 'win32')
|
|
196
|
+
return null;
|
|
197
|
+
try {
|
|
198
|
+
const stat = fs.lstatSync(SOCKET_PATH);
|
|
199
|
+
if (!stat.isSocket())
|
|
200
|
+
return null;
|
|
201
|
+
if (process.getuid && stat.uid !== process.getuid())
|
|
202
|
+
return null;
|
|
203
|
+
return { dev: stat.dev, ino: stat.ino };
|
|
178
204
|
}
|
|
179
205
|
catch {
|
|
180
|
-
return
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/** Remove only the same-user socket inode whose failed connection proved it stale. */
|
|
210
|
+
function removeStaleLegacySocket(identity) {
|
|
211
|
+
if (!identity)
|
|
212
|
+
return;
|
|
213
|
+
try {
|
|
214
|
+
const current = fs.lstatSync(SOCKET_PATH);
|
|
215
|
+
if (!current.isSocket() || current.dev !== identity.dev || current.ino !== identity.ino)
|
|
216
|
+
return;
|
|
217
|
+
fs.unlinkSync(SOCKET_PATH);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// Spawning and the final retry provide the authoritative recovery result.
|
|
181
221
|
}
|
|
182
222
|
}
|
|
183
223
|
/**
|
|
@@ -185,7 +225,7 @@ async function trySpawnNativeHost(mode = 'control', retryPreviousAttempt = false
|
|
|
185
225
|
*
|
|
186
226
|
* 目标解析优先级:显式 options.browser → setDefaultBrowser → 环境变量
|
|
187
227
|
* MEARL_BROWSER → 自动(注册表中最近聚焦的浏览器)。注册表为空时回退到
|
|
188
|
-
* legacy well-known socket,并在 socket
|
|
228
|
+
* legacy well-known socket,并在 socket 不存在或已残留时自动拉起 native host。
|
|
189
229
|
*/
|
|
190
230
|
export function invoke(action, data = {}, options = {}) {
|
|
191
231
|
if (action === 'browser_select_browser') {
|
|
@@ -284,16 +324,19 @@ async function dispatchInvoke(action, data, timeoutSec, transport, selector, con
|
|
|
284
324
|
}
|
|
285
325
|
return invokeViaLegacy(action, data, timeoutSec, transport, controlSource, /* isRetry */ false);
|
|
286
326
|
}
|
|
287
|
-
/** Legacy well-known socket,
|
|
327
|
+
/** Legacy well-known socket, repairing a missing or stale endpoint and retrying once. */
|
|
288
328
|
async function invokeViaLegacy(action, data, timeoutSec, transport, controlSource, isRetry) {
|
|
329
|
+
const legacySocketIdentity = readOwnedLegacySocketIdentity();
|
|
289
330
|
try {
|
|
290
331
|
return await invokeOnce(action, data, timeoutSec, transport, SOCKET_PATH, controlSource);
|
|
291
332
|
}
|
|
292
333
|
catch (error) {
|
|
293
334
|
const err = error;
|
|
294
|
-
const shouldAttemptSpawn = err.code === 'ENOENT' ||
|
|
335
|
+
const shouldAttemptSpawn = err.code === 'ENOENT' || err.code === 'ECONNREFUSED';
|
|
295
336
|
if (shouldAttemptSpawn && !isRetry) {
|
|
296
|
-
|
|
337
|
+
if (err.code === 'ECONNREFUSED')
|
|
338
|
+
removeStaleLegacySocket(legacySocketIdentity);
|
|
339
|
+
const spawned = await trySpawnNativeHost(action === 'browser_list' ? 'discovery' : 'control');
|
|
297
340
|
if (spawned) {
|
|
298
341
|
// Wait for the native host to start listening. Discovery retries the legacy socket directly
|
|
299
342
|
// so the spawned host receives the probe even if another browser registers in the meantime.
|
|
@@ -425,7 +468,7 @@ function formatConnectError(error, socketPath) {
|
|
|
425
468
|
if (error.code === 'ECONNREFUSED') {
|
|
426
469
|
return [
|
|
427
470
|
`Socket 连接被拒绝: ${socketPath}`,
|
|
428
|
-
'native host
|
|
471
|
+
'native host 自动恢复失败。',
|
|
429
472
|
'请确认 Chrome 浏览器正在运行,可尝试在 chrome://extensions 重新加载插件。',
|
|
430
473
|
'',
|
|
431
474
|
installHint,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/client",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.2",
|
|
4
4
|
"description": "Unified Mearl SDK & CLI for local and remote browsers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"commander": "^15.0.0",
|
|
60
60
|
"fastest-levenshtein": "^1.0.16",
|
|
61
61
|
"ws": "^8.18.0",
|
|
62
|
-
"@mearl/cloud-types": "2.15.
|
|
63
|
-
"@mearl/provider": "2.15.
|
|
62
|
+
"@mearl/cloud-types": "2.15.2",
|
|
63
|
+
"@mearl/provider": "2.15.2"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/node": "^24.9.1",
|
package/dist/buc.d.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
interface ProgramResult {
|
|
2
|
-
stdout: string;
|
|
3
|
-
stderr: string;
|
|
4
|
-
}
|
|
5
|
-
export interface BucIdentity {
|
|
6
|
-
agentId?: string;
|
|
7
|
-
empId: string;
|
|
8
|
-
name?: string;
|
|
9
|
-
}
|
|
10
|
-
export interface BucProfile extends BucIdentity {
|
|
11
|
-
bucUserId?: string;
|
|
12
|
-
gid?: string;
|
|
13
|
-
empType?: string;
|
|
14
|
-
photoPath?: string;
|
|
15
|
-
displayName?: string;
|
|
16
|
-
nickName?: string;
|
|
17
|
-
deptId?: string;
|
|
18
|
-
admin?: boolean;
|
|
19
|
-
buName?: string;
|
|
20
|
-
buNo?: string;
|
|
21
|
-
deptDesc?: string;
|
|
22
|
-
lang?: string;
|
|
23
|
-
cid?: string;
|
|
24
|
-
jobDesc?: string;
|
|
25
|
-
loginWorkNo?: string;
|
|
26
|
-
}
|
|
27
|
-
export type BucRequestFormValue = string | number | boolean;
|
|
28
|
-
export type BucRequestAuth = 'id-token' | 'ticket-header' | 'ticket-query';
|
|
29
|
-
export interface BucRequestParams {
|
|
30
|
-
app: string;
|
|
31
|
-
url: string;
|
|
32
|
-
auth?: BucRequestAuth;
|
|
33
|
-
method?: string;
|
|
34
|
-
headers?: Record<string, string>;
|
|
35
|
-
params?: Record<string, string | number | boolean | null | undefined>;
|
|
36
|
-
body?: unknown;
|
|
37
|
-
formData?: Record<string, BucRequestFormValue>;
|
|
38
|
-
responseType?: 'json' | 'text' | 'auto';
|
|
39
|
-
timeoutMs?: number;
|
|
40
|
-
}
|
|
41
|
-
export interface BucRequestResult {
|
|
42
|
-
status: number;
|
|
43
|
-
statusText: string;
|
|
44
|
-
headers: Record<string, string>;
|
|
45
|
-
body: unknown;
|
|
46
|
-
}
|
|
47
|
-
export interface BucApi {
|
|
48
|
-
request(params: BucRequestParams): Promise<BucRequestResult>;
|
|
49
|
-
whoami(): Promise<BucIdentity>;
|
|
50
|
-
profile(): Promise<BucProfile>;
|
|
51
|
-
}
|
|
52
|
-
export type BucCommandRunner = (args: string[]) => Promise<ProgramResult>;
|
|
53
|
-
export interface BucDependencies {
|
|
54
|
-
fetch?: typeof fetch;
|
|
55
|
-
installBuc?: () => Promise<void>;
|
|
56
|
-
runBuc?: BucCommandRunner;
|
|
57
|
-
}
|
|
58
|
-
export declare const runBucCommand: BucCommandRunner;
|
|
59
|
-
export declare function installBucCli(): Promise<void>;
|
|
60
|
-
export declare function bucWhoami(dependencies?: BucDependencies): Promise<BucIdentity>;
|
|
61
|
-
export declare function bucProfile(dependencies?: BucDependencies): Promise<BucProfile>;
|
|
62
|
-
/** Executes one internal request with a fresh credential in the selected BUC authentication mode. */
|
|
63
|
-
export declare function bucRequest(input: BucRequestParams, dependencies?: BucDependencies): Promise<BucRequestResult>;
|
|
64
|
-
export declare const buc: BucApi;
|
|
65
|
-
export {};
|