@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 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` 中,并使用同一个
@@ -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
@@ -3,6 +3,7 @@ export interface MearlCliOptions {
3
3
  clientVersion?: string;
4
4
  }
5
5
  export interface ParsedArgs {
6
+ session?: string;
6
7
  action: string;
7
8
  browser?: string;
8
9
  compact: boolean;
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: ['browser_list', 'browser_launch', 'browser_release', 'browser_close'],
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`);
@@ -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 required: true;
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
- { name: 'filePaths', type: 'string[]', required: true, description: '本地文件路径数组' },
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
- filePaths: string[];
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: undefined;
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;