@mearl/client 2.19.3 → 2.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/actionTimeouts.js +2 -0
- package/dist/artifacts.d.ts +17 -0
- package/dist/artifacts.js +80 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +27 -2
- package/dist/commands.d.ts +65 -2
- package/dist/commands.js +78 -1
- package/dist/generated-browser-action-protocol.d.ts +142 -6
- package/dist/generated-browser-action-protocol.js +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +15 -3
- package/dist/pluginPageBundle.js +78 -23
- package/dist/socket.d.ts +1 -0
- package/dist/socket.js +28 -8
- package/dist/unified.d.ts +17 -0
- package/dist/unified.js +117 -8
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -72,6 +72,29 @@ mearl browser_list
|
|
|
72
72
|
mearl browser_release
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
### 控制会话与文件
|
|
76
|
+
|
|
77
|
+
多步骤任务首次 `tab_open` 或 `page_snapshot` 使用 `--session new`,响应在原结果旁附带 `session`,后续用其 `sessionId` 自动路由,无需额外创建/查询 action。Session 绑定一个浏览器与传输;页面首次使用自动关联,复用页面与新建页面分别记录。同一页面只允许一个会话控制,旧调用不能绕过占用;Session 不隔离 Cookie 或存储。
|
|
78
|
+
|
|
79
|
+
旧调用不必传 `--session`,现有动作、`run_actions` 批次和上传 `filePaths` 参数继续可用。活动会话存在时,旧调用不能操作其占用页面、执行浏览器级释放/关闭或修改共享设置;旧批次中的开页/关闭浏览器需改为独立调用。CDP Mock 按标签页生效,跨页使用同一 Mock 时需分别配置。
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
mearl tab_open --session new --payload '{"url":"https://example.com"}'
|
|
83
|
+
mearl page_snapshot --session '<sessionId>' --payload '{"tabId":12345}'
|
|
84
|
+
mearl browser_control --session '<sessionId>' --payload '{"operation":"handoff","reason":"请完成扫码"}'
|
|
85
|
+
mearl page_download --session '<sessionId>' --payload '{"tabId":12345,"trigger":{"selector":"#export"}}' --output /absolute/path/report.csv
|
|
86
|
+
mearl page_upload --session '<sessionId>' --payload '{"tabId":12345,"selector":"input[type=file]","files":[{"filePath":"/absolute/path/report.csv"}]}'
|
|
87
|
+
mearl browser_release --session '<sessionId>' --payload '{"closeCreatedTabs":true,"keepTabIds":[12345]}'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
用户在 Popup 的浏览器管理页接管、交还或结束会话。交接后排队旧请求和批次后续步骤停止;已派发动作可能仍然生效。交还后重新取快照。CDP-only 环境可在用户明确确认后用同一 `browser_control` 的 `operation:"resume"` 交还,无需额外 action。会话跨 CLI 连接保留,宿主进程退出后失效;不会根据空闲时间清理用户正在操作的会话。
|
|
91
|
+
|
|
92
|
+
带 Session 的释放只影响该会话,默认保留页面及环境;`closeCreatedTabs` 只关闭新建页,`keepTabIds` 保留结果页,复用的用户页面始终保留。`preserveEnvironment:false` 清理这些页面的环境。活动会话存在时,浏览器级释放与关闭会被拒绝。
|
|
93
|
+
|
|
94
|
+
`page_download` 先监听再点击,返回浏览器文件产物;CLI `--output` 或 SDK `saveArtifact(artifact, absolutePath, { session })` 以 256 KiB 分块导出到调用方。`page_upload.files` 接受调用方绝对路径 `{filePath}` 或浏览器 `{artifactId}`,`filePaths` 仍指浏览器宿主路径。单文件最多 256 MiB;保存到调用方的结果独立于临时产物。会话释放清理临时产物,`keepArtifactIds` 可保留指定产物;无会话产物空闲一小时回收,宿主退出清理暂存。
|
|
95
|
+
|
|
96
|
+
扩展捕获下载需要匹配来源页 referrer 或明确的预期 `url`,无法归属时不会认领最近下载。CDP 只在托管浏览器中使用临时下载目录,共享浏览器保持默认目录,Chrome 未返回完成文件路径时明确报错。下载超时可能已经触发,需检查现场再决定下一步。当前会话与下载适配 Extension/CDP;直接处理页面动作的外部 Provider 对不支持的能力明确报错。
|
|
97
|
+
|
|
75
98
|
### 外部浏览器 Provider
|
|
76
99
|
|
|
77
100
|
Provider 浏览器与扩展、CDP 浏览器一起出现在 `browser_list` 中,并使用同一个
|
package/dist/actionTimeouts.js
CHANGED
|
@@ -29,6 +29,8 @@ export function resolveActionTimeoutSec(action, data, fallback = 15) {
|
|
|
29
29
|
if (action === 'send_mtop_request') {
|
|
30
30
|
return Math.max(fallback, Math.ceil(((data.timeout ?? 10000) + 5000) / 1000));
|
|
31
31
|
}
|
|
32
|
+
if (action === 'page_download')
|
|
33
|
+
return Math.max(fallback, Math.ceil((Number(data.timeout ?? 30000) + 10000) / 1000));
|
|
32
34
|
if (action === 'page_wait') {
|
|
33
35
|
const waitMs = data.time ?? data.timeout ?? 10000;
|
|
34
36
|
return Math.max(fallback, Math.ceil((Number(waitMs) + 5000) / 1000));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BrowserArtifact } from './generated-browser-action-protocol.js';
|
|
2
|
+
import type { UnifiedInvokeOptions } from './unified.js';
|
|
3
|
+
interface Invoker {
|
|
4
|
+
invoke(action: string, data?: Record<string, any>, options?: UnifiedInvokeOptions): Promise<any>;
|
|
5
|
+
}
|
|
6
|
+
export declare function stageFile(client: Invoker, filePath: string, options: UnifiedInvokeOptions): Promise<{
|
|
7
|
+
artifactId: string;
|
|
8
|
+
options: UnifiedInvokeOptions;
|
|
9
|
+
}>;
|
|
10
|
+
/** Atomically export browser-host data to the calling machine without large JSON responses. */
|
|
11
|
+
export declare function exportArtifact(client: Invoker, artifact: BrowserArtifact & {
|
|
12
|
+
browserId?: string;
|
|
13
|
+
}, outputPath: string, options?: UnifiedInvokeOptions): Promise<{
|
|
14
|
+
outputPath: string;
|
|
15
|
+
bytes: number;
|
|
16
|
+
}>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { open, mkdir, rename, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
const CHUNK_BYTES = 256 * 1024;
|
|
5
|
+
export async function stageFile(client, filePath, options) {
|
|
6
|
+
if (!path.isAbsolute(filePath))
|
|
7
|
+
throw new Error('Caller-local file paths must be absolute');
|
|
8
|
+
const source = await open(filePath, 'r');
|
|
9
|
+
let id;
|
|
10
|
+
let pinned = options;
|
|
11
|
+
try {
|
|
12
|
+
const metadata = await source.stat();
|
|
13
|
+
if (!metadata.isFile() || metadata.size > 256 * 1024 * 1024)
|
|
14
|
+
throw new Error('Upload requires a regular file of at most 256 MiB');
|
|
15
|
+
const created = await client.invoke('__artifact_init', { filename: path.basename(filePath) }, options);
|
|
16
|
+
pinned = { ...options, browser: created.browserId };
|
|
17
|
+
id = created.artifact.artifactId;
|
|
18
|
+
let offset = 0;
|
|
19
|
+
for await (const chunk of source.createReadStream({
|
|
20
|
+
highWaterMark: CHUNK_BYTES,
|
|
21
|
+
autoClose: false,
|
|
22
|
+
})) {
|
|
23
|
+
await client.invoke('__artifact_write', { artifactId: id, offset, base64: chunk.toString('base64') }, pinned);
|
|
24
|
+
offset += chunk.length;
|
|
25
|
+
}
|
|
26
|
+
await client.invoke('__artifact_complete', { artifactId: id, bytes: offset }, pinned);
|
|
27
|
+
return { artifactId: id, options: pinned };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (id)
|
|
31
|
+
await client.invoke('__artifact_remove', { artifactId: id }, pinned).catch(() => { });
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
await source.close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Atomically export browser-host data to the calling machine without large JSON responses. */
|
|
39
|
+
export async function exportArtifact(client, artifact, outputPath, options = {}) {
|
|
40
|
+
if (!Number.isSafeInteger(artifact.bytes) || artifact.bytes < 0)
|
|
41
|
+
throw new Error('Invalid artifact size');
|
|
42
|
+
if (!path.isAbsolute(outputPath))
|
|
43
|
+
throw new Error('Artifact output paths must be absolute');
|
|
44
|
+
if (!options.session && !artifact.browserId && !options.browser)
|
|
45
|
+
throw new Error('An artifact requires its originating browser');
|
|
46
|
+
const pinned = { ...options, ...(artifact.browserId ? { browser: artifact.browserId } : {}) };
|
|
47
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
48
|
+
const temporary = `${outputPath}.${randomUUID()}.part`;
|
|
49
|
+
const target = await open(temporary, 'wx', 0o600);
|
|
50
|
+
try {
|
|
51
|
+
let offset = 0;
|
|
52
|
+
if (artifact.bytes === 0) {
|
|
53
|
+
const empty = await client.invoke('__artifact_read', { artifactId: artifact.artifactId, offset: 0 }, pinned);
|
|
54
|
+
if (empty.totalBytes !== 0 || empty.bytes !== 0 || empty.base64 !== '')
|
|
55
|
+
throw new Error('Artifact export size mismatch');
|
|
56
|
+
}
|
|
57
|
+
while (offset < artifact.bytes) {
|
|
58
|
+
const chunk = await client.invoke('__artifact_read', { artifactId: artifact.artifactId, offset }, pinned);
|
|
59
|
+
const bytes = Buffer.from(chunk.base64, 'base64');
|
|
60
|
+
if (!bytes.length ||
|
|
61
|
+
bytes.length !== chunk.bytes ||
|
|
62
|
+
chunk.totalBytes !== artifact.bytes ||
|
|
63
|
+
offset + bytes.length > artifact.bytes)
|
|
64
|
+
throw new Error('Invalid artifact chunk or truncated transfer');
|
|
65
|
+
await target.write(bytes);
|
|
66
|
+
offset += bytes.length;
|
|
67
|
+
}
|
|
68
|
+
await target.sync();
|
|
69
|
+
await target.close();
|
|
70
|
+
if ((await stat(temporary)).size !== artifact.bytes)
|
|
71
|
+
throw new Error('Artifact export size mismatch');
|
|
72
|
+
await rename(temporary, outputPath);
|
|
73
|
+
return { outputPath, bytes: artifact.bytes };
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
await target.close().catch(() => { });
|
|
77
|
+
await rm(temporary, { force: true });
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -38,6 +38,7 @@ const CATEGORIES = [
|
|
|
38
38
|
'page_wait',
|
|
39
39
|
'page_navigate',
|
|
40
40
|
'page_upload',
|
|
41
|
+
'page_download',
|
|
41
42
|
'page_eval',
|
|
42
43
|
],
|
|
43
44
|
},
|
|
@@ -48,7 +49,13 @@ const CATEGORIES = [
|
|
|
48
49
|
},
|
|
49
50
|
{
|
|
50
51
|
title: '浏览器',
|
|
51
|
-
names: [
|
|
52
|
+
names: [
|
|
53
|
+
'browser_list',
|
|
54
|
+
'browser_launch',
|
|
55
|
+
'browser_release',
|
|
56
|
+
'browser_close',
|
|
57
|
+
'browser_control',
|
|
58
|
+
],
|
|
52
59
|
},
|
|
53
60
|
];
|
|
54
61
|
const BUILTIN_COMMANDS = [
|
|
@@ -119,6 +126,7 @@ function printUsage(commandName, version) {
|
|
|
119
126
|
' --timeout <seconds> 超时时间(默认按 action 推导)',
|
|
120
127
|
' --compact stdout 输出紧凑格式 JSON',
|
|
121
128
|
' --output <path> 保存输出或截图',
|
|
129
|
+
' --session <new|id> 首次建立或复用控制会话',
|
|
122
130
|
' --browser <id|名称> 指定 browser_list 返回的浏览器',
|
|
123
131
|
'',
|
|
124
132
|
'其他选项:',
|
|
@@ -422,6 +430,7 @@ export function parseClientArgs(args, plugin = false) {
|
|
|
422
430
|
.option('--json')
|
|
423
431
|
.option('--output <path>')
|
|
424
432
|
.option('--browser <id-or-name>')
|
|
433
|
+
.option('--session <new-or-id>')
|
|
425
434
|
.option('--connector <id-or-name>')
|
|
426
435
|
.option('--local')
|
|
427
436
|
.option('--updates')
|
|
@@ -484,6 +493,7 @@ export function parseClientArgs(args, plugin = false) {
|
|
|
484
493
|
updates: options.updates === true,
|
|
485
494
|
...(outputPath ? { outputPath } : {}),
|
|
486
495
|
...(options.browser ? { browser: options.browser } : {}),
|
|
496
|
+
...(options.session ? { session: options.session } : {}),
|
|
487
497
|
...(connector ? { connector } : {}),
|
|
488
498
|
...resolveServer(options.server),
|
|
489
499
|
...(plugin
|
|
@@ -512,6 +522,7 @@ async function runPluginCli(args, commandName) {
|
|
|
512
522
|
const invokeOptions = {
|
|
513
523
|
...(parsed.timeoutOption ? { timeoutSec: parsed.timeoutSec } : {}),
|
|
514
524
|
...(parsed.browser ? { browser: parsed.browser } : {}),
|
|
525
|
+
...(parsed.session ? { session: parsed.session } : {}),
|
|
515
526
|
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
516
527
|
...(parsed.local ? { local: true } : {}),
|
|
517
528
|
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
@@ -665,6 +676,7 @@ export async function runMearlCli(args, options = {}) {
|
|
|
665
676
|
...(parsed.serverUrl ? { serverUrl: parsed.serverUrl } : {}),
|
|
666
677
|
...(parsed.serverUrlSource ? { serverUrlSource: parsed.serverUrlSource } : {}),
|
|
667
678
|
...(parsed.browser ? { browser: parsed.browser } : {}),
|
|
679
|
+
...(parsed.session ? { session: parsed.session } : {}),
|
|
668
680
|
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
669
681
|
...(parsed.local ? { local: true } : {}),
|
|
670
682
|
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
@@ -684,12 +696,25 @@ export async function runMearlCli(args, options = {}) {
|
|
|
684
696
|
const result = await client.invoke(parsed.action, parsed.payload, {
|
|
685
697
|
timeoutSec: parsed.timeoutSec,
|
|
686
698
|
...(parsed.browser ? { browser: parsed.browser } : {}),
|
|
699
|
+
...(parsed.session ? { session: parsed.session } : {}),
|
|
687
700
|
...(parsed.connector ? { connector: parsed.connector } : {}),
|
|
688
701
|
...(parsed.local ? { local: true } : {}),
|
|
689
702
|
...(parsed.forceCdp ? { transport: 'cdp' } : {}),
|
|
690
703
|
});
|
|
691
|
-
if (parsed.outputPath) {
|
|
704
|
+
if (parsed.outputPath && parsed.action === 'page_download') {
|
|
705
|
+
await client.saveArtifact(result.download, resolve(parsed.outputPath), {
|
|
706
|
+
...(parsed.session ? { session: parsed.session } : {}),
|
|
707
|
+
});
|
|
708
|
+
process.stdout.write(JSON.stringify({ ...result, outputPath: resolve(parsed.outputPath) }) + '\n');
|
|
709
|
+
if (!parsed.session)
|
|
710
|
+
await client.invoke('__artifact_remove', { artifactId: result.download.artifactId }, { browser: result.download.browserId });
|
|
711
|
+
}
|
|
712
|
+
else if (parsed.outputPath) {
|
|
692
713
|
writeOutput(parsed.action, result, parsed.outputPath);
|
|
714
|
+
if (parsed.action === 'page_screenshot' && result?.session) {
|
|
715
|
+
const { data: _image, ...metadata } = result;
|
|
716
|
+
process.stdout.write(JSON.stringify({ ...metadata, outputPath: resolve(parsed.outputPath) }) + '\n');
|
|
717
|
+
}
|
|
693
718
|
}
|
|
694
719
|
else {
|
|
695
720
|
process.stdout.write(`${parsed.compact ? JSON.stringify(result) : JSON.stringify(result, null, 2)}\n`);
|
package/dist/commands.d.ts
CHANGED
|
@@ -23,6 +23,48 @@ export interface CommandDef {
|
|
|
23
23
|
result?: readonly string[];
|
|
24
24
|
}
|
|
25
25
|
export declare const COMMANDS: readonly [{
|
|
26
|
+
readonly name: "browser_control";
|
|
27
|
+
readonly description: "把控制会话交给用户;用户在 Mearl 浏览器管理页交还后重新观察页面";
|
|
28
|
+
readonly params: [{
|
|
29
|
+
readonly name: "operation";
|
|
30
|
+
readonly type: "\"handoff\" | \"resume\"";
|
|
31
|
+
readonly required: true;
|
|
32
|
+
readonly description: "交给用户操作;resume 仅在用户明确交还后使用";
|
|
33
|
+
}, {
|
|
34
|
+
readonly name: "tabId";
|
|
35
|
+
readonly type: "number";
|
|
36
|
+
readonly description: "可选的目标页面";
|
|
37
|
+
}, {
|
|
38
|
+
readonly name: "reason";
|
|
39
|
+
readonly type: "string";
|
|
40
|
+
readonly description: "用户需要处理的事项";
|
|
41
|
+
}];
|
|
42
|
+
readonly notes: readonly ["必须携带 --session;接管停止后续派发,已经发出的操作可能仍然生效。"];
|
|
43
|
+
}, {
|
|
44
|
+
readonly name: "page_download";
|
|
45
|
+
readonly description: "先监听再点击,等待下载完成并创建浏览器文件产物";
|
|
46
|
+
readonly outputFlag: true;
|
|
47
|
+
readonly params: [{
|
|
48
|
+
readonly name: "tabId";
|
|
49
|
+
readonly type: "number";
|
|
50
|
+
readonly required: true;
|
|
51
|
+
readonly description: "下载来源页面";
|
|
52
|
+
}, {
|
|
53
|
+
readonly name: "trigger";
|
|
54
|
+
readonly type: "object";
|
|
55
|
+
readonly required: true;
|
|
56
|
+
readonly description: "page_click 参数,不含 tabId";
|
|
57
|
+
}, {
|
|
58
|
+
readonly name: "url";
|
|
59
|
+
readonly type: "string";
|
|
60
|
+
readonly description: "预期下载 URL;扩展无法通过 referrer 归属时需显式指定";
|
|
61
|
+
}, {
|
|
62
|
+
readonly name: "timeout";
|
|
63
|
+
readonly type: "number";
|
|
64
|
+
readonly description: "下载等待毫秒,默认 30000,范围 1000–300000";
|
|
65
|
+
}];
|
|
66
|
+
readonly notes: readonly ["--output 把文件分块保存到调用方机器;未指定时返回 artifactId,可供 page_upload 复用。", "共享 CDP 浏览器保持原下载目录;Chrome 未返回文件路径时明确报错。超时不能直接重复点击。"];
|
|
67
|
+
}, {
|
|
26
68
|
readonly name: "tab_checkpoint";
|
|
27
69
|
readonly description: "为目标标签页建立日志、请求和埋点增量边界";
|
|
28
70
|
readonly params: [{
|
|
@@ -354,6 +396,24 @@ export declare const COMMANDS: readonly [{
|
|
|
354
396
|
}, {
|
|
355
397
|
readonly name: "browser_release";
|
|
356
398
|
readonly description: "释放当前浏览器的调试控制和会话级临时效果(含设备/时区模拟);不用于普通任务收尾,Provider 实例的行为以 mearl <providerId> 为准";
|
|
399
|
+
readonly params: [{
|
|
400
|
+
readonly name: "closeCreatedTabs";
|
|
401
|
+
readonly type: "boolean";
|
|
402
|
+
readonly description: "带 --session 时关闭会话创建的过程页,默认 false";
|
|
403
|
+
}, {
|
|
404
|
+
readonly name: "keepTabIds";
|
|
405
|
+
readonly type: "number[]";
|
|
406
|
+
readonly description: "保留的结果页面;复用的用户页面始终保留";
|
|
407
|
+
}, {
|
|
408
|
+
readonly name: "preserveEnvironment";
|
|
409
|
+
readonly type: "boolean";
|
|
410
|
+
readonly description: "带 --session 时默认 true,保留 Mock 与模拟现场;false 清理这些页面的环境";
|
|
411
|
+
}, {
|
|
412
|
+
readonly name: "keepArtifactIds";
|
|
413
|
+
readonly type: "string[]";
|
|
414
|
+
readonly description: "保留在浏览器宿主的文件产物;其余会话临时文件删除";
|
|
415
|
+
}];
|
|
416
|
+
readonly notes: readonly ["带 --session 时可用于任务收尾,只释放该会话;未带会话时保留原浏览器级释放行为,活动会话存在时拒绝全局释放。"];
|
|
357
417
|
readonly summary: "释放浏览器调试控制和会话级临时效果";
|
|
358
418
|
readonly examples: ["browser_release", "browser_release --browser chrome-a"];
|
|
359
419
|
}, {
|
|
@@ -808,6 +868,10 @@ export declare const COMMANDS: readonly [{
|
|
|
808
868
|
readonly name: "page_upload";
|
|
809
869
|
readonly description: "向 <input type=\"file\"> 元素上传一个或多个文件";
|
|
810
870
|
readonly params: [{
|
|
871
|
+
readonly name: "files";
|
|
872
|
+
readonly type: "object[]";
|
|
873
|
+
readonly description: "调用方绝对路径 {filePath} 或浏览器产物 {artifactId};与 filePaths 二选一";
|
|
874
|
+
}, {
|
|
811
875
|
readonly name: "selector";
|
|
812
876
|
readonly type: "string";
|
|
813
877
|
readonly required: true;
|
|
@@ -815,8 +879,7 @@ export declare const COMMANDS: readonly [{
|
|
|
815
879
|
}, {
|
|
816
880
|
readonly name: "filePaths";
|
|
817
881
|
readonly type: "string[]";
|
|
818
|
-
readonly
|
|
819
|
-
readonly description: "本地文件路径数组";
|
|
882
|
+
readonly description: "浏览器宿主上的绝对路径数组;与 files 二选一";
|
|
820
883
|
}, ParamDef, ParamDef, {
|
|
821
884
|
readonly name: "tabId";
|
|
822
885
|
readonly type: "number";
|
package/dist/commands.js
CHANGED
|
@@ -36,6 +36,49 @@ const PAGE_ACTION_RESULT = [
|
|
|
36
36
|
'显式开启的诊断通道在 diagnostics 中返回动作期间增量,与 observe 独立。',
|
|
37
37
|
];
|
|
38
38
|
export const COMMANDS = [
|
|
39
|
+
{
|
|
40
|
+
name: 'browser_control',
|
|
41
|
+
description: '把控制会话交给用户;用户在 Mearl 浏览器管理页交还后重新观察页面',
|
|
42
|
+
params: [
|
|
43
|
+
{
|
|
44
|
+
name: 'operation',
|
|
45
|
+
type: '"handoff" | "resume"',
|
|
46
|
+
required: true,
|
|
47
|
+
description: '交给用户操作;resume 仅在用户明确交还后使用',
|
|
48
|
+
},
|
|
49
|
+
{ name: 'tabId', type: 'number', description: '可选的目标页面' },
|
|
50
|
+
{ name: 'reason', type: 'string', description: '用户需要处理的事项' },
|
|
51
|
+
],
|
|
52
|
+
notes: ['必须携带 --session;接管停止后续派发,已经发出的操作可能仍然生效。'],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'page_download',
|
|
56
|
+
description: '先监听再点击,等待下载完成并创建浏览器文件产物',
|
|
57
|
+
outputFlag: true,
|
|
58
|
+
params: [
|
|
59
|
+
{ name: 'tabId', type: 'number', required: true, description: '下载来源页面' },
|
|
60
|
+
{
|
|
61
|
+
name: 'trigger',
|
|
62
|
+
type: 'object',
|
|
63
|
+
required: true,
|
|
64
|
+
description: 'page_click 参数,不含 tabId',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'url',
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: '预期下载 URL;扩展无法通过 referrer 归属时需显式指定',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'timeout',
|
|
73
|
+
type: 'number',
|
|
74
|
+
description: '下载等待毫秒,默认 30000,范围 1000–300000',
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
notes: [
|
|
78
|
+
'--output 把文件分块保存到调用方机器;未指定时返回 artifactId,可供 page_upload 复用。',
|
|
79
|
+
'共享 CDP 浏览器保持原下载目录;Chrome 未返回文件路径时明确报错。超时不能直接重复点击。',
|
|
80
|
+
],
|
|
81
|
+
},
|
|
39
82
|
// ── API 调试 ──────────────────────────────────────────────────────────────
|
|
40
83
|
{
|
|
41
84
|
name: 'tab_checkpoint',
|
|
@@ -337,6 +380,31 @@ export const COMMANDS = [
|
|
|
337
380
|
{
|
|
338
381
|
name: 'browser_release',
|
|
339
382
|
description: '释放当前浏览器的调试控制和会话级临时效果(含设备/时区模拟);不用于普通任务收尾,Provider 实例的行为以 mearl <providerId> 为准',
|
|
383
|
+
params: [
|
|
384
|
+
{
|
|
385
|
+
name: 'closeCreatedTabs',
|
|
386
|
+
type: 'boolean',
|
|
387
|
+
description: '带 --session 时关闭会话创建的过程页,默认 false',
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
name: 'keepTabIds',
|
|
391
|
+
type: 'number[]',
|
|
392
|
+
description: '保留的结果页面;复用的用户页面始终保留',
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: 'preserveEnvironment',
|
|
396
|
+
type: 'boolean',
|
|
397
|
+
description: '带 --session 时默认 true,保留 Mock 与模拟现场;false 清理这些页面的环境',
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: 'keepArtifactIds',
|
|
401
|
+
type: 'string[]',
|
|
402
|
+
description: '保留在浏览器宿主的文件产物;其余会话临时文件删除',
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
notes: [
|
|
406
|
+
'带 --session 时可用于任务收尾,只释放该会话;未带会话时保留原浏览器级释放行为,活动会话存在时拒绝全局释放。',
|
|
407
|
+
],
|
|
340
408
|
summary: '释放浏览器调试控制和会话级临时效果',
|
|
341
409
|
examples: ['browser_release', 'browser_release --browser chrome-a'],
|
|
342
410
|
},
|
|
@@ -882,13 +950,22 @@ export const COMMANDS = [
|
|
|
882
950
|
name: 'page_upload',
|
|
883
951
|
description: '向 <input type="file"> 元素上传一个或多个文件',
|
|
884
952
|
params: [
|
|
953
|
+
{
|
|
954
|
+
name: 'files',
|
|
955
|
+
type: 'object[]',
|
|
956
|
+
description: '调用方绝对路径 {filePath} 或浏览器产物 {artifactId};与 filePaths 二选一',
|
|
957
|
+
},
|
|
885
958
|
{
|
|
886
959
|
name: 'selector',
|
|
887
960
|
type: 'string',
|
|
888
961
|
required: true,
|
|
889
962
|
description: '原生 CSS 选择器,或 page_snapshot / 页面动作 observation 返回的 @eN ref(必须带 @ 前缀),必须指向 input[type=file]',
|
|
890
963
|
},
|
|
891
|
-
{
|
|
964
|
+
{
|
|
965
|
+
name: 'filePaths',
|
|
966
|
+
type: 'string[]',
|
|
967
|
+
description: '浏览器宿主上的绝对路径数组;与 files 二选一',
|
|
968
|
+
},
|
|
892
969
|
PAGE_OBSERVE_PARAM,
|
|
893
970
|
PAGE_DIAGNOSTICS_PARAM,
|
|
894
971
|
{ name: 'tabId', type: 'number', required: true, description: '目标标签页 ID' },
|
|
@@ -173,7 +173,7 @@ export interface BrowserInfoBase {
|
|
|
173
173
|
}
|
|
174
174
|
export type BrowserScreenshotKind = 'native' | 'reconstructed' | 'none';
|
|
175
175
|
/** Browser-targeted actions that an external provider may implement. */
|
|
176
|
-
export type BrowserCapabilityAction = ExtensionBrowserAction;
|
|
176
|
+
export type BrowserCapabilityAction = ExtensionBrowserAction | 'page_download';
|
|
177
177
|
/** Capabilities advertised by an external browser-control provider. */
|
|
178
178
|
export interface BrowserCapabilities {
|
|
179
179
|
actions: BrowserCapabilityAction[];
|
|
@@ -222,6 +222,52 @@ export interface BrowserReleaseResult {
|
|
|
222
222
|
affectedTabIds: number[];
|
|
223
223
|
failures?: BrowserReleaseFailure[];
|
|
224
224
|
}
|
|
225
|
+
export interface ControlSessionInfo {
|
|
226
|
+
sessionId: string;
|
|
227
|
+
browserId: string;
|
|
228
|
+
transport: BrowserTransport;
|
|
229
|
+
status: 'active' | 'user' | 'releasing' | 'released';
|
|
230
|
+
generation: number;
|
|
231
|
+
tabs: Array<{
|
|
232
|
+
tabId: number;
|
|
233
|
+
source: 'created' | 'attached';
|
|
234
|
+
title?: string;
|
|
235
|
+
url?: string;
|
|
236
|
+
}>;
|
|
237
|
+
reason?: string;
|
|
238
|
+
}
|
|
239
|
+
export interface BrowserReleaseParams {
|
|
240
|
+
/** Close only session-created pages; attached user pages are protected. */
|
|
241
|
+
closeCreatedTabs?: boolean;
|
|
242
|
+
keepTabIds?: number[];
|
|
243
|
+
/** Preserve the browser environment by default for a scoped release. */
|
|
244
|
+
preserveEnvironment?: boolean;
|
|
245
|
+
keepArtifactIds?: string[];
|
|
246
|
+
}
|
|
247
|
+
export interface BrowserControlParams {
|
|
248
|
+
operation: 'handoff' | 'resume';
|
|
249
|
+
tabId?: number;
|
|
250
|
+
reason?: string;
|
|
251
|
+
}
|
|
252
|
+
export interface BrowserArtifact {
|
|
253
|
+
artifactId: string;
|
|
254
|
+
filename: string;
|
|
255
|
+
bytes: number;
|
|
256
|
+
location: 'browser';
|
|
257
|
+
/** Global browser route is attached by the unified client. */
|
|
258
|
+
browserId?: string;
|
|
259
|
+
}
|
|
260
|
+
export interface PageDownloadParams {
|
|
261
|
+
tabId: number;
|
|
262
|
+
trigger: Omit<PageClickParams, 'tabId'>;
|
|
263
|
+
/** Restrict the expected download URL; useful when the browser cannot identify its source. */
|
|
264
|
+
url?: string;
|
|
265
|
+
timeout?: number;
|
|
266
|
+
}
|
|
267
|
+
export interface PageDownloadResult {
|
|
268
|
+
download: BrowserArtifact;
|
|
269
|
+
action: unknown;
|
|
270
|
+
}
|
|
225
271
|
export interface CookieCopyScope {
|
|
226
272
|
/** Maintained authentication-domain presets to include. */
|
|
227
273
|
presets?: Array<'mearl-services'>;
|
|
@@ -277,6 +323,12 @@ export type BrowserProviderInputSchema = (BrowserProviderSchemaBase & {
|
|
|
277
323
|
maxLength?: number;
|
|
278
324
|
pattern?: string;
|
|
279
325
|
placeholder?: string;
|
|
326
|
+
options?: readonly {
|
|
327
|
+
value: string;
|
|
328
|
+
label: string;
|
|
329
|
+
description?: string;
|
|
330
|
+
disabled?: boolean;
|
|
331
|
+
}[];
|
|
280
332
|
}) | (BrowserProviderSchemaBase & {
|
|
281
333
|
type: 'number';
|
|
282
334
|
integer?: boolean;
|
|
@@ -307,8 +359,29 @@ export interface BrowserProviderDefinition {
|
|
|
307
359
|
configuration?: {
|
|
308
360
|
inputSchema: BrowserProviderObjectSchema;
|
|
309
361
|
};
|
|
362
|
+
dynamicLaunchForm?: boolean;
|
|
363
|
+
launchForm?: BrowserProviderLaunchForm;
|
|
310
364
|
};
|
|
311
365
|
}
|
|
366
|
+
export interface BrowserProviderLaunchFormVariant {
|
|
367
|
+
id: string;
|
|
368
|
+
title: string;
|
|
369
|
+
description?: string;
|
|
370
|
+
inputSchema?: BrowserProviderObjectSchema;
|
|
371
|
+
values?: Record<string, BrowserJsonValue>;
|
|
372
|
+
refreshOn?: readonly string[];
|
|
373
|
+
dynamicFields?: readonly string[];
|
|
374
|
+
}
|
|
375
|
+
export interface BrowserProviderLaunchForm {
|
|
376
|
+
inputSchema: BrowserProviderObjectSchema;
|
|
377
|
+
variants?: readonly BrowserProviderLaunchFormVariant[];
|
|
378
|
+
variant?: string;
|
|
379
|
+
values?: Record<string, BrowserJsonValue>;
|
|
380
|
+
refreshOn?: readonly string[];
|
|
381
|
+
dynamicFields?: readonly string[];
|
|
382
|
+
message?: string;
|
|
383
|
+
blocked?: boolean;
|
|
384
|
+
}
|
|
312
385
|
export interface BrowserProviderListResult {
|
|
313
386
|
providers: BrowserProviderDefinition[];
|
|
314
387
|
}
|
|
@@ -595,7 +668,14 @@ export interface PageNavigateParams {
|
|
|
595
668
|
export interface PageUploadParams {
|
|
596
669
|
tabId: number;
|
|
597
670
|
selector: string;
|
|
598
|
-
|
|
671
|
+
/** Absolute paths available to the browser host. */
|
|
672
|
+
filePaths?: string[];
|
|
673
|
+
/** SDK/CLI stages caller-local files before dispatch. Mutually exclusive with filePaths. */
|
|
674
|
+
files?: Array<{
|
|
675
|
+
filePath: string;
|
|
676
|
+
} | {
|
|
677
|
+
artifactId: string;
|
|
678
|
+
}>;
|
|
599
679
|
observe?: PageObservationWaitOptions | false;
|
|
600
680
|
diagnostics?: PageDiagnosticsSetting;
|
|
601
681
|
}
|
|
@@ -719,9 +799,19 @@ export interface BrowserActionMap {
|
|
|
719
799
|
response: SelectBrowserResult;
|
|
720
800
|
};
|
|
721
801
|
browser_release: {
|
|
722
|
-
request:
|
|
802
|
+
request: BrowserReleaseParams;
|
|
723
803
|
response: BrowserReleaseResult;
|
|
724
804
|
};
|
|
805
|
+
browser_control: {
|
|
806
|
+
request: BrowserControlParams;
|
|
807
|
+
response: {
|
|
808
|
+
session: ControlSessionInfo;
|
|
809
|
+
};
|
|
810
|
+
};
|
|
811
|
+
page_download: {
|
|
812
|
+
request: PageDownloadParams;
|
|
813
|
+
response: PageDownloadResult;
|
|
814
|
+
};
|
|
725
815
|
browser_launch: {
|
|
726
816
|
request: BrowserLaunchParams;
|
|
727
817
|
response: ManagedBrowserInfo;
|
|
@@ -833,10 +923,12 @@ export interface BrowserActionMap {
|
|
|
833
923
|
}
|
|
834
924
|
export type BrowserAction = keyof BrowserActionMap;
|
|
835
925
|
export type BrowserActionRequest<A extends BrowserAction> = BrowserActionMap[A]['request'];
|
|
836
|
-
export type BrowserActionResponse<A extends BrowserAction> = BrowserActionMap[A]['response']
|
|
926
|
+
export type BrowserActionResponse<A extends BrowserAction> = BrowserActionMap[A]['response'] & {
|
|
927
|
+
session?: ControlSessionInfo;
|
|
928
|
+
};
|
|
837
929
|
export type BrowserCommandAction = Exclude<BrowserAction, 'browser_select_browser'>;
|
|
838
|
-
export type ExtensionBrowserAction = Exclude<BrowserCommandAction, 'browser_list' | 'browser_launch' | 'browser_close'>;
|
|
839
|
-
export declare const TAB_ID_REQUIRED_ACTIONS: readonly ["tab_checkpoint", "capture_checkpoint", "get_requests", "get_logs", "get_events", "set_mock", "get_mocks", "page_screenshot", "tab_close", "page_click", "page_drag", "page_type", "page_scroll", "page_hover", "page_eval", "page_inspect", "page_snapshot", "page_press", "page_wait", "page_navigate", "page_upload", "page_frames", "set_device_emulation", "set_app_profile", "set_timezone", "run_actions"];
|
|
930
|
+
export type ExtensionBrowserAction = Exclude<BrowserCommandAction, 'browser_list' | 'browser_launch' | 'browser_close' | 'browser_control' | 'page_download'>;
|
|
931
|
+
export declare const TAB_ID_REQUIRED_ACTIONS: readonly ["tab_checkpoint", "capture_checkpoint", "get_requests", "get_logs", "get_events", "set_mock", "get_mocks", "page_screenshot", "tab_close", "page_click", "page_drag", "page_type", "page_scroll", "page_hover", "page_eval", "page_inspect", "page_snapshot", "page_press", "page_wait", "page_navigate", "page_upload", "page_download", "page_frames", "set_device_emulation", "set_app_profile", "set_timezone", "run_actions"];
|
|
840
932
|
export type TabIdRequiredAction = (typeof TAB_ID_REQUIRED_ACTIONS)[number];
|
|
841
933
|
export declare function actionRequiresTabId(action: unknown): action is TabIdRequiredAction;
|
|
842
934
|
export declare function validateRequiredActionTabId(action: string, data: unknown): void;
|
|
@@ -857,6 +949,8 @@ export declare const BROWSER_ACTIONS: {
|
|
|
857
949
|
readonly browser_list: true;
|
|
858
950
|
readonly browser_select_browser: true;
|
|
859
951
|
readonly browser_release: true;
|
|
952
|
+
readonly browser_control: true;
|
|
953
|
+
readonly page_download: true;
|
|
860
954
|
readonly browser_launch: true;
|
|
861
955
|
readonly browser_close: true;
|
|
862
956
|
readonly page_screenshot: true;
|
|
@@ -899,6 +993,8 @@ export declare const BROWSER_COMMAND_ACTIONS: {
|
|
|
899
993
|
readonly send_mtop_request: true;
|
|
900
994
|
readonly browser_list: true;
|
|
901
995
|
readonly browser_release: true;
|
|
996
|
+
readonly browser_control: true;
|
|
997
|
+
readonly page_download: true;
|
|
902
998
|
readonly browser_launch: true;
|
|
903
999
|
readonly browser_close: true;
|
|
904
1000
|
readonly page_screenshot: true;
|
|
@@ -966,6 +1062,46 @@ export declare const EXTENSION_BROWSER_ACTIONS: {
|
|
|
966
1062
|
readonly request_domain_permission: true;
|
|
967
1063
|
readonly run_actions: true;
|
|
968
1064
|
};
|
|
1065
|
+
export declare const BROWSER_CAPABILITY_ACTIONS: {
|
|
1066
|
+
readonly page_download: true;
|
|
1067
|
+
readonly tab_checkpoint: true;
|
|
1068
|
+
readonly capture_checkpoint: true;
|
|
1069
|
+
readonly get_requests: true;
|
|
1070
|
+
readonly get_logs: true;
|
|
1071
|
+
readonly get_events: true;
|
|
1072
|
+
readonly set_mock: true;
|
|
1073
|
+
readonly get_mocks: true;
|
|
1074
|
+
readonly set_rule: true;
|
|
1075
|
+
readonly get_rules: true;
|
|
1076
|
+
readonly send_request: true;
|
|
1077
|
+
readonly send_mtop_request: true;
|
|
1078
|
+
readonly browser_release: true;
|
|
1079
|
+
readonly page_screenshot: true;
|
|
1080
|
+
readonly tab_open: true;
|
|
1081
|
+
readonly tab_close: true;
|
|
1082
|
+
readonly tab_list: true;
|
|
1083
|
+
readonly tab_group: true;
|
|
1084
|
+
readonly page_click: true;
|
|
1085
|
+
readonly page_drag: true;
|
|
1086
|
+
readonly page_type: true;
|
|
1087
|
+
readonly page_scroll: true;
|
|
1088
|
+
readonly page_hover: true;
|
|
1089
|
+
readonly page_eval: true;
|
|
1090
|
+
readonly page_inspect: true;
|
|
1091
|
+
readonly page_snapshot: true;
|
|
1092
|
+
readonly page_press: true;
|
|
1093
|
+
readonly page_wait: true;
|
|
1094
|
+
readonly page_navigate: true;
|
|
1095
|
+
readonly page_upload: true;
|
|
1096
|
+
readonly page_frames: true;
|
|
1097
|
+
readonly set_device_emulation: true;
|
|
1098
|
+
readonly set_app_profile: true;
|
|
1099
|
+
readonly set_timezone: true;
|
|
1100
|
+
readonly get_cookie: true;
|
|
1101
|
+
readonly set_cookie: true;
|
|
1102
|
+
readonly request_domain_permission: true;
|
|
1103
|
+
readonly run_actions: true;
|
|
1104
|
+
};
|
|
969
1105
|
export declare function isBrowserAction(value: unknown): value is BrowserAction;
|
|
970
1106
|
export declare function isBrowserCommandAction(value: unknown): value is BrowserCommandAction;
|
|
971
1107
|
export declare function isBrowserCapabilityAction(value: unknown): value is BrowserCapabilityAction;
|