@becrafter/prompt-manager 0.1.22 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/package.json +23 -23
  2. package/packages/resources/tools/agent-browser/README.md +640 -0
  3. package/packages/resources/tools/agent-browser/agent-browser.tool.js +1388 -0
  4. package/packages/resources/tools/thinking/README.md +324 -0
  5. package/packages/resources/tools/thinking/thinking.tool.js +911 -0
  6. package/packages/server/README.md +3 -4
  7. package/packages/server/api/admin.routes.js +668 -664
  8. package/packages/server/api/open.routes.js +68 -67
  9. package/packages/server/api/surge.routes.js +5 -6
  10. package/packages/server/api/tool.routes.js +70 -71
  11. package/packages/server/app.js +70 -73
  12. package/packages/server/configs/authors.json +40 -0
  13. package/packages/server/configs/models/built-in/bigmodel.yaml +6 -6
  14. package/packages/server/configs/models/providers.yaml +4 -4
  15. package/packages/server/configs/templates/built-in/general-iteration.yaml +1 -1
  16. package/packages/server/configs/templates/built-in/general-optimize.yaml +1 -1
  17. package/packages/server/configs/templates/built-in/output-format-optimize.yaml +1 -1
  18. package/packages/server/index.js +3 -9
  19. package/packages/server/mcp/heartbeat-patch.js +1 -3
  20. package/packages/server/mcp/mcp.server.js +64 -134
  21. package/packages/server/mcp/prompt.handler.js +101 -95
  22. package/packages/server/middlewares/auth.middleware.js +31 -31
  23. package/packages/server/server.js +60 -45
  24. package/packages/server/services/TerminalService.js +156 -70
  25. package/packages/server/services/WebSocketService.js +35 -34
  26. package/packages/server/services/author-config.service.js +199 -0
  27. package/packages/server/services/manager.js +66 -60
  28. package/packages/server/services/model.service.js +5 -9
  29. package/packages/server/services/optimization.service.js +25 -22
  30. package/packages/server/services/template.service.js +3 -8
  31. package/packages/server/toolm/author-sync.service.js +97 -0
  32. package/packages/server/toolm/index.js +1 -2
  33. package/packages/server/toolm/package-installer.service.js +47 -50
  34. package/packages/server/toolm/tool-context.service.js +64 -62
  35. package/packages/server/toolm/tool-dependency.service.js +28 -30
  36. package/packages/server/toolm/tool-description-generator-optimized.service.js +55 -55
  37. package/packages/server/toolm/tool-description-generator.service.js +20 -23
  38. package/packages/server/toolm/tool-environment.service.js +45 -44
  39. package/packages/server/toolm/tool-execution.service.js +49 -48
  40. package/packages/server/toolm/tool-loader.service.js +13 -18
  41. package/packages/server/toolm/tool-logger.service.js +33 -39
  42. package/packages/server/toolm/tool-manager.handler.js +17 -15
  43. package/packages/server/toolm/tool-manual-generator.service.js +107 -87
  44. package/packages/server/toolm/tool-mode-handlers.service.js +52 -59
  45. package/packages/server/toolm/tool-storage.service.js +11 -12
  46. package/packages/server/toolm/tool-sync.service.js +36 -39
  47. package/packages/server/toolm/tool-utils.js +0 -1
  48. package/packages/server/toolm/tool-yaml-parser.service.js +12 -11
  49. package/packages/server/toolm/validate-system.js +56 -84
  50. package/packages/server/utils/config.js +97 -12
  51. package/packages/server/utils/logger.js +1 -1
  52. package/packages/server/utils/port-checker.js +8 -8
  53. package/packages/server/utils/util.js +470 -467
  54. package/packages/resources/tools/cognitive-thinking/README.md +0 -284
  55. package/packages/resources/tools/cognitive-thinking/cognitive-thinking.tool.js +0 -837
  56. package/packages/server/mcp/sequential-thinking.handler.js +0 -318
  57. package/packages/server/mcp/think-plan.handler.js +0 -274
  58. package/packages/server/mcp/thinking-toolkit.handler.js +0 -380
  59. package/packages/web/0.d1c5a72339dfc32ad86a.js +0 -1
  60. package/packages/web/112.8807b976372b2b0541a8.js +0 -1
  61. package/packages/web/130.584c7e365da413f5d9be.js +0 -1
  62. package/packages/web/142.72c985bc29720f975cca.js +0 -1
  63. package/packages/web/165.a05fc53bf84d18db36b8.js +0 -2
  64. package/packages/web/165.a05fc53bf84d18db36b8.js.LICENSE.txt +0 -9
  65. package/packages/web/203.724ab9f717b80554c397.js +0 -1
  66. package/packages/web/241.bf941d4f02866795f64a.js +0 -1
  67. package/packages/web/249.54cfb224af63f5f5ec55.js +0 -1
  68. package/packages/web/291.6df35042f8f296fca7cd.js +0 -1
  69. package/packages/web/319.2fab900a31b29873f666.js +0 -1
  70. package/packages/web/32.c78d866281995ec33a7b.js +0 -1
  71. package/packages/web/325.9ca297d0f73f38468ce9.js +0 -1
  72. package/packages/web/366.2f9b48fdbf8eee039e57.js +0 -1
  73. package/packages/web/378.6be08c612cd5a3ef97dc.js +0 -1
  74. package/packages/web/393.7a2f817515c5e90623d7.js +0 -1
  75. package/packages/web/412.062df5f732d5ba203415.js +0 -1
  76. package/packages/web/426.08656fef4918b3fb19ad.js +0 -1
  77. package/packages/web/465.2be8018327130a3bd798.js +0 -1
  78. package/packages/web/48.8ca96fc93667a715e67a.js +0 -1
  79. package/packages/web/480.44c1f1a2927486ac3d4f.js +0 -1
  80. package/packages/web/489.e041a8d0db15dc96d607.js +0 -1
  81. package/packages/web/490.9ffb26c907de020d671b.js +0 -1
  82. package/packages/web/492.58781369e348d91fc06a.js +0 -1
  83. package/packages/web/495.ed63e99791a87167c6b3.js +0 -1
  84. package/packages/web/510.4cc07ab7d30d5c1cd17f.js +0 -1
  85. package/packages/web/543.3af155ed4fa237664308.js +0 -1
  86. package/packages/web/567.f04ab60f8e2c2fb0745a.js +0 -1
  87. package/packages/web/592.f3ad085fa9c1849daa06.js +0 -1
  88. package/packages/web/616.b03fb801b3433b17750f.js +0 -1
  89. package/packages/web/617.d88def54921d2c4dc44c.js +0 -1
  90. package/packages/web/641.d30787d674f548928261.js +0 -1
  91. package/packages/web/672.5269c8399fa42a5af95d.js +0 -1
  92. package/packages/web/731.97cab92b71811c502bda.js +0 -1
  93. package/packages/web/746.3947c6f0235407e420fb.js +0 -1
  94. package/packages/web/756.a53233b3f3913900d5ac.js +0 -1
  95. package/packages/web/77.68801af593a28a631fbf.js +0 -1
  96. package/packages/web/802.53b2bff3cf2a69f7b80c.js +0 -1
  97. package/packages/web/815.b6dfab82265f56c7e046.js +0 -1
  98. package/packages/web/821.f5a13e5c735aac244eb9.js +0 -1
  99. package/packages/web/846.b9bf97d5f559270675ce.js +0 -1
  100. package/packages/web/869.7c10403f500e6201407f.js +0 -1
  101. package/packages/web/885.135050364f99e6924fb5.js +0 -1
  102. package/packages/web/901.fd5aeb9df630609a2b43.js +0 -1
  103. package/packages/web/928.f67e590de3caa4daa3ae.js +0 -1
  104. package/packages/web/955.d833403521ba4dd567ee.js +0 -1
  105. package/packages/web/981.a45cb745cf424044c8c8.js +0 -1
  106. package/packages/web/992.645320b60c74c8787482.js +0 -1
  107. package/packages/web/996.ed9a963dc9e7439eca9a.js +0 -1
  108. package/packages/web/css/codemirror-theme_xq-light.css +0 -43
  109. package/packages/web/css/codemirror.css +0 -344
  110. package/packages/web/css/main.196f434e6a88cd448158.css +0 -7278
  111. package/packages/web/css/terminal-fix.css +0 -571
  112. package/packages/web/index.html +0 -3
  113. package/packages/web/main.dceff50c7307dda04873.js +0 -2
  114. package/packages/web/main.dceff50c7307dda04873.js.LICENSE.txt +0 -3
@@ -0,0 +1,1388 @@
1
+ /**
2
+ * agent-browser - AI 友好的浏览器自动化工具
3
+ *
4
+ * 战略意义:
5
+ * 1. AI 优先设计 - 专为 Claude Code、Cursor 等智能体优化
6
+ * 2. 上下文效率 - 93% 上下文节省,通过 Snapshot + Refs 机制
7
+ * 3. 直接 API - 使用 BrowserManager 类,避免 CLI 解析开销
8
+ * 4. 确定性选择 - 使用 @e1, @e2 引用代替易变的 CSS 选择器
9
+ *
10
+ * 注意:此工具将在独立沙箱环境中运行,依赖将自动安装到工具目录的 node_modules 中
11
+ * 所有日志将输出到 ~/.prompt-manager/toolbox/agent-browser/run.log 文件中
12
+ */
13
+
14
+ export default {
15
+ /**
16
+ * 获取工具依赖
17
+ */
18
+ getDependencies() {
19
+ return {
20
+ 'agent-browser': '^0.5.0'
21
+ };
22
+ },
23
+
24
+ /**
25
+ * 获取工具元信息
26
+ */
27
+ getMetadata() {
28
+ return {
29
+ id: 'agent-browser',
30
+ name: 'agent-browser',
31
+ description: 'AI 友好的浏览器自动化工具,使用 BrowserManager API 提供基于引用的确定性元素选择',
32
+ version: '1.0.0',
33
+ category: 'browser',
34
+ author: 'Vercel Labs',
35
+ tags: ['browser', 'automation', 'ai', 'playwright', 'headless'],
36
+ scenarios: [
37
+ '网页自动化测试',
38
+ 'AI 智能体浏览器控制',
39
+ '网页数据抓取',
40
+ '表单自动填充',
41
+ '截图和快照'
42
+ ],
43
+ limitations: [
44
+ '需要安装 agent-browser npm 包',
45
+ '浏览器实例会在工具执行期间保持活跃',
46
+ '复杂场景可能需要 headed 模式调试'
47
+ ]
48
+ };
49
+ },
50
+
51
+ /**
52
+ * 获取参数 Schema
53
+ */
54
+ getSchema() {
55
+ return {
56
+ parameters: {
57
+ type: 'object',
58
+ properties: {
59
+ action: {
60
+ type: 'string',
61
+ description: '执行的操作类型',
62
+ enum: [
63
+ // 核心命令
64
+ 'launch', 'navigate', 'click', 'type', 'fill',
65
+ 'check', 'uncheck', 'upload', 'dblclick', 'focus',
66
+ 'drag', 'screenshot', 'snapshot', 'evaluate', 'wait',
67
+ 'scroll', 'select', 'hover', 'content',
68
+ 'close', 'back', 'forward', 'reload',
69
+
70
+ // 查找命令(语义定位器)
71
+ 'getbyrole', 'getbytext', 'getbylabel', 'getbyplaceholder',
72
+ 'getbyalttext', 'getbytitle', 'getbytestid', 'nth',
73
+
74
+ // 获取信息
75
+ 'gettext', 'gethtml', 'getattribute', 'isvisible',
76
+ 'isenabled', 'ischecked', 'count', 'boundingbox',
77
+
78
+ // 导航和控制
79
+ 'url', 'title', 'press', 'pause',
80
+
81
+ // 标签页和窗口
82
+ 'tab_new', 'tab_list', 'tab_switch', 'tab_close',
83
+ 'window_new',
84
+
85
+ // Cookie 和存储
86
+ 'cookies_get', 'cookies_set', 'cookies_clear',
87
+ 'storage_get', 'storage_set', 'storage_clear',
88
+
89
+ // 网络和路由
90
+ 'route', 'unroute', 'requests', 'download',
91
+
92
+ // 设置和模拟
93
+ 'viewport', 'device', 'geolocation', 'permissions',
94
+ 'useragent', 'timezone', 'locale', 'offline', 'headers',
95
+
96
+ // 高级功能
97
+ 'dialog', 'pdf', 'tracing', 'har',
98
+ 'console', 'errors', 'video', 'screencast'
99
+ ]
100
+ },
101
+ // 导航相关
102
+ url: {
103
+ type: 'string',
104
+ description: '要导航的 URL(用于 navigate, tab_new 等操作)'
105
+ },
106
+ waitUntil: {
107
+ type: 'string',
108
+ description: '等待状态',
109
+ enum: ['load', 'domcontentloaded', 'networkidle']
110
+ },
111
+
112
+ // 元素交互相关
113
+ selector: {
114
+ type: 'string',
115
+ description: '元素选择器或引用(如 @e1, @e2)'
116
+ },
117
+ text: {
118
+ type: 'string',
119
+ description: '文本内容(用于 type, fill 等操作)'
120
+ },
121
+ value: {
122
+ type: 'string',
123
+ description: '值(用于 fill, select 等操作)'
124
+ },
125
+ values: {
126
+ type: 'array',
127
+ items: { type: 'string' },
128
+ description: '多个值(用于 multiselect)'
129
+ },
130
+ button: {
131
+ type: 'string',
132
+ description: '鼠标按钮',
133
+ enum: ['left', 'right', 'middle']
134
+ },
135
+ clickCount: {
136
+ type: 'number',
137
+ description: '点击次数'
138
+ },
139
+ delay: {
140
+ type: 'number',
141
+ description: '延迟时间(毫秒)'
142
+ },
143
+ clear: {
144
+ type: 'boolean',
145
+ description: '是否清除现有内容'
146
+ },
147
+
148
+ // 拖拽相关
149
+ source: {
150
+ type: 'string',
151
+ description: '源元素选择器'
152
+ },
153
+ target: {
154
+ type: 'string',
155
+ description: '目标元素选择器'
156
+ },
157
+
158
+ // 上传相关
159
+ files: {
160
+ oneOf: [
161
+ { type: 'string' },
162
+ { type: 'array', items: { type: 'string' } }
163
+ ],
164
+ description: '文件路径或文件路径数组'
165
+ },
166
+
167
+ // 查找命令相关
168
+ role: {
169
+ type: 'string',
170
+ description: 'ARIA 角色(如 button, link, textbox)'
171
+ },
172
+ name: {
173
+ type: 'string',
174
+ description: '元素名称'
175
+ },
176
+ label: {
177
+ type: 'string',
178
+ description: '元素标签'
179
+ },
180
+ placeholder: {
181
+ type: 'string',
182
+ description: '占位符文本'
183
+ },
184
+ altText: {
185
+ type: 'string',
186
+ description: 'alt 文本'
187
+ },
188
+ testId: {
189
+ type: 'string',
190
+ description: 'test-id'
191
+ },
192
+ subaction: {
193
+ type: 'string',
194
+ description: '子操作(click, fill, check, hover)',
195
+ enum: ['click', 'fill', 'check', 'hover', 'text']
196
+ },
197
+ exact: {
198
+ type: 'boolean',
199
+ description: '是否精确匹配'
200
+ },
201
+ index: {
202
+ type: 'number',
203
+ description: '元素索引'
204
+ },
205
+
206
+ // 等待相关
207
+ timeout: {
208
+ type: 'number',
209
+ description: '超时时间(毫秒)'
210
+ },
211
+ state: {
212
+ type: 'string',
213
+ description: '等待状态',
214
+ enum: ['attached', 'detached', 'visible', 'hidden']
215
+ },
216
+
217
+ // 滚动相关
218
+ direction: {
219
+ type: 'string',
220
+ description: '滚动方向',
221
+ enum: ['up', 'down', 'left', 'right']
222
+ },
223
+ amount: {
224
+ type: 'number',
225
+ description: '滚动量(像素)'
226
+ },
227
+ x: {
228
+ type: 'number',
229
+ description: 'X 坐标'
230
+ },
231
+ y: {
232
+ type: 'number',
233
+ description: 'Y 坐标'
234
+ },
235
+
236
+ // Cookie 相关
237
+ cookies: {
238
+ type: 'array',
239
+ description: 'Cookie 数组',
240
+ items: {
241
+ type: 'object',
242
+ properties: {
243
+ name: { type: 'string' },
244
+ value: { type: 'string' },
245
+ url: { type: 'string' },
246
+ domain: { type: 'string' },
247
+ path: { type: 'string' },
248
+ expires: { type: 'number' },
249
+ httpOnly: { type: 'boolean' },
250
+ secure: { type: 'boolean' },
251
+ sameSite: { type: 'string' }
252
+ }
253
+ }
254
+ },
255
+ urls: {
256
+ type: 'array',
257
+ items: { type: 'string' },
258
+ description: 'URL 数组'
259
+ },
260
+ key: {
261
+ type: 'string',
262
+ description: '存储键'
263
+ },
264
+ storageType: {
265
+ type: 'string',
266
+ description: '存储类型',
267
+ enum: ['local', 'session']
268
+ },
269
+
270
+ // 网络相关
271
+ routeUrl: {
272
+ type: 'string',
273
+ description: '路由 URL'
274
+ },
275
+ responseBody: {
276
+ type: 'object',
277
+ description: '响应体配置',
278
+ properties: {
279
+ status: { type: 'number' },
280
+ body: { type: 'string' },
281
+ contentType: { type: 'string' },
282
+ headers: { type: 'object' }
283
+ }
284
+ },
285
+ abort: {
286
+ type: 'boolean',
287
+ description: '是否中止请求'
288
+ },
289
+ filter: {
290
+ type: 'string',
291
+ description: '请求过滤器'
292
+ },
293
+
294
+ // 设置相关
295
+ width: {
296
+ type: 'number',
297
+ description: '宽度'
298
+ },
299
+ height: {
300
+ type: 'number',
301
+ description: '高度'
302
+ },
303
+ viewport: {
304
+ type: 'object',
305
+ description: '视口尺寸',
306
+ properties: {
307
+ width: { type: 'number' },
308
+ height: { type: 'number' }
309
+ }
310
+ },
311
+ device: {
312
+ type: 'string',
313
+ description: '设备名称(如 "iPhone 14")'
314
+ },
315
+ latitude: {
316
+ type: 'number',
317
+ description: '纬度'
318
+ },
319
+ longitude: {
320
+ type: 'number',
321
+ description: '经度'
322
+ },
323
+ accuracy: {
324
+ type: 'number',
325
+ description: '精度(米)'
326
+ },
327
+ permissions: {
328
+ type: 'array',
329
+ items: { type: 'string' },
330
+ description: '权限列表'
331
+ },
332
+ grant: {
333
+ type: 'boolean',
334
+ description: '是否授予权限'
335
+ },
336
+ userAgent: {
337
+ type: 'string',
338
+ description: '用户代理字符串'
339
+ },
340
+ timezone: {
341
+ type: 'string',
342
+ description: '时区(如 "America/New_York")'
343
+ },
344
+ locale: {
345
+ type: 'string',
346
+ description: '区域设置(如 "en-US")'
347
+ },
348
+ offline: {
349
+ type: 'boolean',
350
+ description: '是否离线模式'
351
+ },
352
+ headers: {
353
+ type: 'object',
354
+ description: 'HTTP 头部'
355
+ },
356
+
357
+ // 对话框相关
358
+ dialogResponse: {
359
+ type: 'string',
360
+ description: '对话框响应',
361
+ enum: ['accept', 'dismiss']
362
+ },
363
+ promptText: {
364
+ type: 'string',
365
+ description: '对话框提示文本'
366
+ },
367
+
368
+ // PDF 相关
369
+ pdfPath: {
370
+ type: 'string',
371
+ description: 'PDF 保存路径'
372
+ },
373
+ format: {
374
+ type: 'string',
375
+ description: 'PDF 格式',
376
+ enum: ['Letter', 'Legal', 'Tabloid', 'Ledger', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6']
377
+ },
378
+
379
+ // 录制相关
380
+ screenshotPath: {
381
+ type: 'string',
382
+ description: '截图保存路径'
383
+ },
384
+ fullPage: {
385
+ type: 'boolean',
386
+ description: '是否截取整页'
387
+ },
388
+ screenshotFormat: {
389
+ type: 'string',
390
+ description: '截图格式',
391
+ enum: ['png', 'jpeg']
392
+ },
393
+ quality: {
394
+ type: 'number',
395
+ description: '图片质量(1-100)'
396
+ },
397
+ videoPath: {
398
+ type: 'string',
399
+ description: '视频保存路径'
400
+ },
401
+ tracePath: {
402
+ type: 'string',
403
+ description: '追踪保存路径'
404
+ },
405
+ harPath: {
406
+ type: 'string',
407
+ description: 'HAR 保存路径'
408
+ },
409
+ statePath: {
410
+ type: 'string',
411
+ description: '状态保存路径'
412
+ },
413
+
414
+ // 高级功能
415
+ script: {
416
+ type: 'string',
417
+ description: 'JavaScript 脚本'
418
+ },
419
+ scriptArgs: {
420
+ type: 'array',
421
+ description: '脚本参数数组'
422
+ },
423
+ attribute: {
424
+ type: 'string',
425
+ description: '属性名'
426
+ },
427
+ event: {
428
+ type: 'string',
429
+ description: '事件名称'
430
+ },
431
+ eventInit: {
432
+ type: 'object',
433
+ description: '事件初始化数据'
434
+ },
435
+
436
+ // screencast 相关
437
+ screencastFormat: {
438
+ type: 'string',
439
+ enum: ['jpeg', 'png'],
440
+ description: 'screencast 格式'
441
+ },
442
+ screencastMaxWidth: {
443
+ type: 'number',
444
+ description: 'screencast 最大宽度'
445
+ },
446
+ screencastMaxHeight: {
447
+ type: 'number',
448
+ description: 'screencast 最大高度'
449
+ },
450
+ everyNthFrame: {
451
+ type: 'number',
452
+ description: '每隔 N 帧捕获一次'
453
+ },
454
+
455
+ // 框架相关
456
+ frameSelector: {
457
+ type: 'string',
458
+ description: '框架选择器'
459
+ },
460
+ frameName: {
461
+ type: 'string',
462
+ description: '框架名称'
463
+ },
464
+ frameUrl: {
465
+ type: 'string',
466
+ description: '框架 URL'
467
+ },
468
+
469
+ // 杂项
470
+ clearConsole: {
471
+ type: 'boolean',
472
+ description: '是否清除控制台'
473
+ },
474
+ clearErrors: {
475
+ type: 'boolean',
476
+ description: '是否清除错误'
477
+ },
478
+ clearRequests: {
479
+ type: 'boolean',
480
+ description: '是否清除请求'
481
+ },
482
+ keys: {
483
+ type: 'string',
484
+ description: '按键(如 "Enter", "Ctrl+A")'
485
+ }
486
+ }
487
+ },
488
+ environment: {
489
+ type: 'object',
490
+ properties: {
491
+ AGENT_BROWSER_EXECUTABLE_PATH: {
492
+ type: 'string',
493
+ description: '浏览器可执行文件路径'
494
+ },
495
+ AGENT_BROWSER_HEADLESS: {
496
+ type: 'string',
497
+ description: '是否使用无头模式',
498
+ default: 'true'
499
+ }
500
+ }
501
+ }
502
+ };
503
+ },
504
+
505
+ /**
506
+ * 获取业务错误定义
507
+ */
508
+ getBusinessErrors() {
509
+ return [
510
+ {
511
+ code: 'BROWSER_NOT_LAUNCHED',
512
+ description: '浏览器未启动',
513
+ match: /browser not launched/i,
514
+ solution: '先调用 launch 操作启动浏览器',
515
+ retryable: false
516
+ },
517
+ {
518
+ code: 'ELEMENT_NOT_FOUND',
519
+ description: '元素未找到',
520
+ match: /element.*not found|selector.*invalid/i,
521
+ solution: '使用 snapshot 获取有效的元素引用',
522
+ retryable: false
523
+ },
524
+ {
525
+ code: 'NAVIGATION_FAILED',
526
+ description: '导航失败',
527
+ match: /navigation.*failed|invalid url/i,
528
+ solution: '检查 URL 是否有效',
529
+ retryable: true
530
+ },
531
+ {
532
+ code: 'BROWSER_LAUNCH_FAILED',
533
+ description: '浏览器启动失败',
534
+ match: /browser.*launch|Failed to launch/i,
535
+ solution: '检查浏览器安装或使用 executablePath 指定路径',
536
+ retryable: true
537
+ },
538
+ {
539
+ code: 'TIMEOUT',
540
+ description: '操作超时',
541
+ match: /timeout|timed out/i,
542
+ solution: '增加 timeout 参数或检查网络连接',
543
+ retryable: true
544
+ },
545
+ {
546
+ code: 'FRAME_NOT_FOUND',
547
+ description: '框架未找到',
548
+ match: /frame.*not found/i,
549
+ solution: '检查框架选择器、名称或 URL 是否正确',
550
+ retryable: false
551
+ }
552
+ ];
553
+ },
554
+
555
+ /**
556
+ * 获取或创建浏览器管理器实例
557
+ */
558
+ async getBrowserManager(options = {}) {
559
+ const { api } = this;
560
+
561
+ if (!this.__browserManager) {
562
+ try {
563
+ const { BrowserManager } = await this.importToolModule('agent-browser');
564
+
565
+ this.__browserManager = new BrowserManager();
566
+
567
+ // 启动浏览器
568
+ const launchOptions = {
569
+ headless: options.headless !== false
570
+ };
571
+
572
+ if (options.executablePath) {
573
+ launchOptions.executablePath = options.executablePath;
574
+ }
575
+
576
+ if (options.viewport) {
577
+ launchOptions.viewport = options.viewport;
578
+ }
579
+
580
+ if (options.device) {
581
+ launchOptions.device = options.device;
582
+ }
583
+
584
+ api?.logger?.info('启动浏览器', { launchOptions });
585
+ await this.__browserManager.launch(launchOptions);
586
+
587
+ } catch (error) {
588
+ api?.logger?.error('浏览器启动失败', { error: error.message });
589
+ throw error;
590
+ }
591
+ }
592
+
593
+ return this.__browserManager;
594
+ },
595
+
596
+ /**
597
+ * 关闭浏览器
598
+ */
599
+ async closeBrowser() {
600
+ const { api } = this;
601
+
602
+ if (this.__browserManager) {
603
+ try {
604
+ await this.__browserManager.close();
605
+ this.__browserManager = null;
606
+ api?.logger?.info('浏览器已关闭');
607
+ } catch (error) {
608
+ api?.logger?.error('关闭浏览器失败', { error: error.message });
609
+ throw error;
610
+ }
611
+ }
612
+ },
613
+
614
+ /**
615
+ * 解析通用选项
616
+ */
617
+ parseCommonOptions(params) {
618
+ const { api } = this;
619
+ const options = {};
620
+
621
+ if (params.headless !== undefined) {
622
+ options.headless = params.headless;
623
+ } else {
624
+ const headlessEnv = api?.environment?.get('AGENT_BROWSER_HEADLESS');
625
+ options.headless = headlessEnv !== 'false';
626
+ }
627
+
628
+ if (params.executablePath) {
629
+ options.executablePath = params.executablePath;
630
+ } else {
631
+ options.executablePath = api?.environment?.get('AGENT_BROWSER_EXECUTABLE_PATH');
632
+ }
633
+
634
+ if (params.viewport) {
635
+ options.viewport = params.viewport;
636
+ }
637
+
638
+ if (params.device) {
639
+ options.device = params.device;
640
+ }
641
+
642
+ return options;
643
+ },
644
+
645
+ /**
646
+ * 执行浏览器操作
647
+ */
648
+ async execute(params) {
649
+ const { api } = this;
650
+
651
+ api?.logger?.info('执行操作', { action: params.action });
652
+
653
+ try {
654
+ // 确保 BrowserManager 已初始化
655
+ const options = this.parseCommonOptions(params);
656
+ await this.getBrowserManager(options);
657
+
658
+ // 根据操作类型路由
659
+ const result = await this.executeAction(params);
660
+
661
+ return {
662
+ success: true,
663
+ action: params.action,
664
+ data: result
665
+ };
666
+
667
+ } catch (error) {
668
+ api?.logger?.error('操作执行失败', {
669
+ action: params.action,
670
+ error: error.message
671
+ });
672
+
673
+ // 检查业务错误
674
+ const businessError = this.getBusinessErrors().find(err =>
675
+ err.match.test(error.message)
676
+ );
677
+
678
+ if (businessError) {
679
+ error.code = businessError.code;
680
+ error.solution = businessError.solution;
681
+ error.retryable = businessError.retryable;
682
+ }
683
+
684
+ return {
685
+ success: false,
686
+ action: params.action,
687
+ error: {
688
+ message: error.message,
689
+ code: error.code || 'UNKNOWN_ERROR',
690
+ solution: error.solution,
691
+ retryable: error.retryable || false
692
+ }
693
+ };
694
+ }
695
+ },
696
+
697
+ /**
698
+ * 执行具体操作
699
+ */
700
+ async executeAction(params) {
701
+ const browser = this.__browserManager;
702
+ const { api } = this;
703
+
704
+ switch (params.action) {
705
+ // ========== 核心命令 ==========
706
+
707
+ case 'launch':
708
+ return { message: '浏览器已启动', options: this.parseCommonOptions(params) };
709
+
710
+ case 'navigate':
711
+ await browser.navigate(params.url, { waitUntil: params.waitUntil });
712
+ if (params.headers) {
713
+ await browser.setExtraHeaders(params.headers);
714
+ }
715
+ const page = browser.getPage();
716
+ return {
717
+ url: params.url,
718
+ title: await page.title()
719
+ };
720
+
721
+ case 'click':
722
+ if (params.selector) {
723
+ const locator = browser.getLocator(params.selector);
724
+ if (params.button || params.clickCount || params.delay) {
725
+ await locator.click({
726
+ button: params.button || 'left',
727
+ clickCount: params.clickCount || 1,
728
+ delay: params.delay || 0
729
+ });
730
+ } else {
731
+ await locator.click();
732
+ }
733
+ }
734
+ return { clicked: params.selector };
735
+
736
+ case 'type':
737
+ if (params.selector && params.text) {
738
+ const locator = browser.getLocator(params.selector);
739
+ const options = {};
740
+ if (params.delay !== undefined) options.delay = params.delay;
741
+ if (params.clear !== undefined) options.clear = params.clear;
742
+ await locator.type(params.text, options);
743
+ }
744
+ return { typed: params.text, selector: params.selector };
745
+
746
+ case 'fill':
747
+ if (params.selector && params.value !== undefined) {
748
+ const locator = browser.getLocator(params.selector);
749
+ await locator.fill(params.value);
750
+ }
751
+ return { filled: params.value, selector: params.selector };
752
+
753
+ case 'check':
754
+ if (params.selector) {
755
+ const locator = browser.getLocator(params.selector);
756
+ await locator.setChecked(true);
757
+ }
758
+ return { checked: params.selector };
759
+
760
+ case 'uncheck':
761
+ if (params.selector) {
762
+ const locator = browser.getLocator(params.selector);
763
+ await locator.setChecked(false);
764
+ }
765
+ return { unchecked: params.selector };
766
+
767
+ case 'upload':
768
+ if (params.selector && params.files) {
769
+ const locator = browser.getLocator(params.selector);
770
+ const files = Array.isArray(params.files) ? params.files : [params.files];
771
+ await locator.setInputFiles(files);
772
+ }
773
+ return { uploaded: files, selector: params.selector };
774
+
775
+ case 'dblclick':
776
+ if (params.selector) {
777
+ const locator = browser.getLocator(params.selector);
778
+ await locator.dblclick();
779
+ }
780
+ return { dblclicked: params.selector };
781
+
782
+ case 'focus':
783
+ if (params.selector) {
784
+ const locator = browser.getLocator(params.selector);
785
+ await locator.focus();
786
+ }
787
+ return { focused: params.selector };
788
+
789
+ case 'drag':
790
+ if (params.source && params.target) {
791
+ const sourceLocator = browser.getLocator(params.source);
792
+ const targetLocator = browser.getLocator(params.target);
793
+ await sourceLocator.dragTo(targetLocator);
794
+ }
795
+ return { dragged: { from: params.source, to: params.target } };
796
+
797
+ case 'screenshot':
798
+ const page = browser.getPage();
799
+ const screenshotOptions = {};
800
+ if (params.fullPage) screenshotOptions.fullPage = true;
801
+ if (params.screenshotFormat) screenshotOptions.type = params.screenshotFormat;
802
+ if (params.quality) screenshotOptions.quality = params.quality;
803
+
804
+ if (params.selector) {
805
+ const locator = browser.getLocator(params.selector);
806
+ const buffer = await locator.screenshot(screenshotOptions);
807
+ return { buffer: buffer.toString('base64'), selector: params.selector };
808
+ } else {
809
+ const buffer = await page.screenshot(screenshotOptions);
810
+ if (params.screenshotPath) {
811
+ const fs = await import('fs');
812
+ fs.writeFileSync(params.screenshotPath, buffer);
813
+ return { path: params.screenshotPath, buffer: buffer.toString('base64') };
814
+ }
815
+ return { buffer: buffer.toString('base64') };
816
+ }
817
+
818
+ case 'snapshot':
819
+ const snapshotOptions = {};
820
+ if (params.interactive !== undefined) snapshotOptions.interactive = params.interactive;
821
+ if (params.maxDepth !== undefined) snapshotOptions.maxDepth = params.maxDepth;
822
+ if (params.compact !== undefined) snapshotOptions.compact = params.compact;
823
+ if (params.selector) snapshotOptions.selector = params.selector;
824
+
825
+ const snapshot = await browser.getSnapshot(snapshotOptions);
826
+ return snapshot;
827
+
828
+ case 'evaluate':
829
+ const evalPage = browser.getPage();
830
+ const result = await evalPage.evaluate(
831
+ params.script,
832
+ params.scriptArgs || []
833
+ );
834
+ return { result };
835
+
836
+ case 'wait':
837
+ if (params.selector) {
838
+ const locator = browser.getLocator(params.selector);
839
+ const waitOptions = {};
840
+ if (params.timeout !== undefined) waitOptions.timeout = params.timeout;
841
+ if (params.state) waitOptions.state = params.state;
842
+ await locator.waitFor(waitOptions);
843
+ } else if (params.timeout) {
844
+ await new Promise(resolve => setTimeout(resolve, params.timeout));
845
+ }
846
+ return { waited: params.selector || params.timeout };
847
+
848
+ case 'scroll':
849
+ if (params.selector) {
850
+ const locator = browser.getLocator(params.selector);
851
+ await locator.scrollIntoViewIfNeeded();
852
+ return { scrolled: params.selector };
853
+ } else if (params.direction || params.amount) {
854
+ const scrollPage = browser.getPage();
855
+ await scrollPage.evaluate((direction, amount) => {
856
+ if (direction === 'up') window.scrollBy(0, -amount);
857
+ else if (direction === 'down') window.scrollBy(0, amount);
858
+ else if (direction === 'left') window.scrollBy(-amount, 0);
859
+ else if (direction === 'right') window.scrollBy(amount, 0);
860
+ }, [params.direction, params.amount]);
861
+ return { scrolled: { direction: params.direction, amount: params.amount } };
862
+ }
863
+ return {};
864
+
865
+ case 'select':
866
+ if (params.selector && params.values) {
867
+ const locator = browser.getLocator(params.selector);
868
+ const values = Array.isArray(params.values) ? params.values : [params.values];
869
+ await locator.selectOption(values);
870
+ }
871
+ return { selected: values, selector: params.selector };
872
+
873
+ case 'hover':
874
+ if (params.selector) {
875
+ const locator = browser.getLocator(params.selector);
876
+ await locator.hover();
877
+ }
878
+ return { hovered: params.selector };
879
+
880
+ case 'content':
881
+ if (params.selector) {
882
+ const locator = browser.getLocator(params.selector);
883
+ const content = await locator.content();
884
+ return { selector: params.selector, content };
885
+ } else {
886
+ const contentPage = browser.getPage();
887
+ const content = await contentPage.content();
888
+ return { content };
889
+ }
890
+
891
+ case 'close':
892
+ await this.closeBrowser();
893
+ return { closed: true };
894
+
895
+ case 'back':
896
+ await browser.getPage().goBack();
897
+ return { navigated: 'back' };
898
+
899
+ case 'forward':
900
+ await browser.getPage().goForward();
901
+ return { navigated: 'forward' };
902
+
903
+ case 'reload':
904
+ await browser.getPage().reload();
905
+ return { reloaded: true };
906
+
907
+ // ========== 查找命令(语义定位器) ==========
908
+
909
+ case 'getbyrole':
910
+ if (params.role && params.subaction) {
911
+ const page = browser.getPage();
912
+ const locator = page.getByRole(params.role, { name: params.name });
913
+ await this.executeSubaction(locator, params.subaction, params.value);
914
+ }
915
+ return { role: params.role, name: params.name };
916
+
917
+ case 'getbytext':
918
+ if (params.text && params.subaction) {
919
+ const page = browser.getPage();
920
+ const options = {};
921
+ if (params.exact !== undefined) options.exact = params.exact;
922
+ const locator = page.getByText(params.text, options);
923
+ await this.executeSubaction(locator, params.subaction);
924
+ }
925
+ return { text: params.text };
926
+
927
+ case 'getbylabel':
928
+ if (params.label && params.subaction) {
929
+ const page = browser.getPage();
930
+ const locator = page.getByLabel(params.label);
931
+ await this.executeSubaction(locator, params.subaction, params.value);
932
+ }
933
+ return { label: params.label };
934
+
935
+ case 'getbyplaceholder':
936
+ if (params.placeholder && params.subaction) {
937
+ const page = browser.getPage();
938
+ const locator = page.getByPlaceholder(params.placeholder);
939
+ await this.executeSubaction(locator, params.subaction, params.value);
940
+ }
941
+ return { placeholder: params.placeholder };
942
+
943
+ case 'getbyalttext':
944
+ if (params.altText && params.subaction) {
945
+ const page = browser.getPage();
946
+ const locator = page.getByAltText(params.altText);
947
+ await this.executeSubaction(locator, params.subaction);
948
+ }
949
+ return { altText: params.altText };
950
+
951
+ case 'getbytitle':
952
+ if (params.name && params.subaction) {
953
+ const page = browser.getPage();
954
+ const options = {};
955
+ if (params.exact !== undefined) options.exact = params.exact;
956
+ const locator = page.getByTitle(params.name, options);
957
+ await this.executeSubaction(locator, params.subaction);
958
+ }
959
+ return { title: params.name };
960
+
961
+ case 'getbytestid':
962
+ if (params.testId && params.subaction) {
963
+ const page = browser.getPage();
964
+ const locator = page.getByTestId(params.testId);
965
+ await this.executeSubaction(locator, params.subaction, params.value);
966
+ }
967
+ return { testId: params.testId };
968
+
969
+ case 'nth':
970
+ if (params.selector && params.index !== undefined && params.subaction) {
971
+ const locator = browser.getLocator(params.selector);
972
+ const nthLocator = locator.nth(params.index);
973
+ await this.executeSubaction(nthLocator, params.subaction, params.value);
974
+ }
975
+ return { selector: params.selector, index: params.index };
976
+
977
+ // ========== 获取信息 ==========
978
+
979
+ case 'gettext':
980
+ if (params.selector) {
981
+ const locator = browser.getLocator(params.selector);
982
+ const text = await locator.textContent();
983
+ return { text, selector: params.selector };
984
+ }
985
+ throw new Error('需要提供 selector 参数');
986
+
987
+ case 'gethtml':
988
+ if (params.selector) {
989
+ const locator = browser.getLocator(params.selector);
990
+ const html = await locator.innerHTML();
991
+ return { html, selector: params.selector };
992
+ }
993
+ throw new Error('需要提供 selector 参数');
994
+
995
+ case 'getattribute':
996
+ if (params.selector && params.attribute) {
997
+ const locator = browser.getLocator(params.selector);
998
+ const value = await locator.getAttribute(params.attribute);
999
+ return { attribute: params.attribute, value, selector: params.selector };
1000
+ }
1001
+ throw new Error('需要提供 selector 和 attribute 参数');
1002
+
1003
+ case 'isvisible':
1004
+ if (params.selector) {
1005
+ const locator = browser.getLocator(params.selector);
1006
+ const visible = await locator.isVisible();
1007
+ return { visible, selector: params.selector };
1008
+ }
1009
+ throw new Error('需要提供 selector 参数');
1010
+
1011
+ case 'isenabled':
1012
+ if (params.selector) {
1013
+ const locator = browser.getLocator(params.selector);
1014
+ const enabled = await locator.isEnabled();
1015
+ return { enabled, selector: params.selector };
1016
+ }
1017
+ throw new Error('需要提供 selector 参数');
1018
+
1019
+ case 'ischecked':
1020
+ if (params.selector) {
1021
+ const locator = browser.getLocator(params.selector);
1022
+ const checked = await locator.isChecked();
1023
+ return { checked, selector: params.selector };
1024
+ }
1025
+ throw new Error('需要提供 selector 参数');
1026
+
1027
+ case 'count':
1028
+ if (params.selector) {
1029
+ const locator = browser.getLocator(params.selector);
1030
+ const count = await locator.count();
1031
+ return { count, selector: params.selector };
1032
+ }
1033
+ throw new Error('需要提供 selector 参数');
1034
+
1035
+ case 'boundingbox':
1036
+ if (params.selector) {
1037
+ const locator = browser.getLocator(params.selector);
1038
+ const box = await locator.boundingBox();
1039
+ return { box, selector: params.selector };
1040
+ }
1041
+ throw new Error('需要提供 selector 参数');
1042
+
1043
+ // ========== 导航和控制 ==========
1044
+
1045
+ case 'url':
1046
+ const urlPage = browser.getPage();
1047
+ return { url: urlPage.url() };
1048
+
1049
+ case 'title':
1050
+ const titlePage = browser.getPage();
1051
+ return { title: await titlePage.title() };
1052
+
1053
+ case 'press':
1054
+ const pressPage = browser.getPage();
1055
+ if (params.selector) {
1056
+ const locator = browser.getLocator(params.selector);
1057
+ await locator.press(params.keys);
1058
+ } else {
1059
+ await pressPage.keyboard.press(params.keys);
1060
+ }
1061
+ return { pressed: params.keys };
1062
+
1063
+ case 'pause':
1064
+ await new Promise(resolve => setTimeout(resolve, 1000)); // 默认暂停 1 秒
1065
+ return { paused: true };
1066
+
1067
+ // ========== 标签页和窗口 ==========
1068
+
1069
+ case 'tab_new':
1070
+ const tabResult = await browser.newTab();
1071
+ return tabResult;
1072
+
1073
+ case 'tab_list':
1074
+ const tabs = await browser.listTabs();
1075
+ return tabs;
1076
+
1077
+ case 'tab_switch':
1078
+ const switchedTab = await browser.switchTo(params.index);
1079
+ return switchedTab;
1080
+
1081
+ case 'tab_close':
1082
+ const closedTab = await browser.closeTab(params.index);
1083
+ return closedTab;
1084
+
1085
+ case 'window_new':
1086
+ const windowResult = await browser.newWindow(params.viewport);
1087
+ return windowResult;
1088
+
1089
+ // ========== Cookie 和存储 ==========
1090
+
1091
+ case 'cookies_get':
1092
+ if (params.urls) {
1093
+ browser.getFrame().context().cookies();
1094
+ }
1095
+ const allCookies = await browser.getPage().context().cookies();
1096
+ return { cookies: allCookies };
1097
+
1098
+ case 'cookies_set':
1099
+ if (params.cookies) {
1100
+ await browser.getPage().context().addCookies(params.cookies);
1101
+ }
1102
+ return { cookiesSet: params.cookies };
1103
+
1104
+ case 'cookies_clear':
1105
+ await browser.getPage().context().clearCookies();
1106
+ return { cookiesCleared: true };
1107
+
1108
+ case 'storage_get':
1109
+ const storagePage = browser.getPage();
1110
+ if (params.storageType === 'session') {
1111
+ if (params.key) {
1112
+ const value = await storagePage.evaluate(([key]) => {
1113
+ return sessionStorage.getItem(key);
1114
+ }, [params.key]);
1115
+ return { key: params.key, value };
1116
+ } else {
1117
+ const all = await storagePage.evaluate(() => {
1118
+ const data = {};
1119
+ for (let i = 0; i < sessionStorage.length; i++) {
1120
+ const key = sessionStorage.key(i);
1121
+ data[key] = sessionStorage.getItem(key);
1122
+ }
1123
+ return data;
1124
+ });
1125
+ return { storage: all, type: 'session' };
1126
+ }
1127
+ } else {
1128
+ if (params.key) {
1129
+ const value = await storagePage.evaluate(([key]) => {
1130
+ return localStorage.getItem(key);
1131
+ }, [params.key]);
1132
+ return { key: params.key, value };
1133
+ } else {
1134
+ const all = await storagePage.evaluate(() => {
1135
+ const data = {};
1136
+ for (let i = 0; i < localStorage.length; i++) {
1137
+ const key = localStorage.key(i);
1138
+ data[key] = localStorage.getItem(key);
1139
+ }
1140
+ return data;
1141
+ });
1142
+ return { storage: all, type: 'local' };
1143
+ }
1144
+ }
1145
+
1146
+ case 'storage_set':
1147
+ const setPage = browser.getPage();
1148
+ if (params.storageType === 'session') {
1149
+ await setPage.evaluate(([key, value]) => {
1150
+ sessionStorage.setItem(key, value);
1151
+ }, [params.key, params.value]);
1152
+ } else {
1153
+ await setPage.evaluate(([key, value]) => {
1154
+ localStorage.setItem(key, value);
1155
+ }, [params.key, params.value]);
1156
+ }
1157
+ return { key: params.key, value: params.value, type: params.storageType || 'local' };
1158
+
1159
+ case 'storage_clear':
1160
+ const clearStoragePage = browser.getPage();
1161
+ if (params.storageType === 'session') {
1162
+ await clearStoragePage.evaluate(() => {
1163
+ sessionStorage.clear();
1164
+ });
1165
+ } else {
1166
+ await clearStoragePage.evaluate(() => {
1167
+ localStorage.clear();
1168
+ });
1169
+ }
1170
+ return { cleared: true, type: params.storageType || 'local' };
1171
+
1172
+ // ========== 网络和路由 ==========
1173
+
1174
+ case 'route':
1175
+ if (params.routeUrl) {
1176
+ const routeOptions = {};
1177
+ if (params.responseBody) {
1178
+ routeOptions.response = params.responseBody;
1179
+ }
1180
+ if (params.abort) {
1181
+ routeOptions.abort = true;
1182
+ }
1183
+ await browser.addRoute(params.routeUrl, routeOptions);
1184
+ }
1185
+ return { routed: params.routeUrl };
1186
+
1187
+ case 'unroute':
1188
+ await browser.removeRoute(params.routeUrl);
1189
+ return { unrouted: params.routeUrl };
1190
+
1191
+ case 'requests':
1192
+ if (params.clearRequests) {
1193
+ browser.clearRequests();
1194
+ return { cleared: true };
1195
+ }
1196
+ const requests = browser.getRequests(params.filter);
1197
+ return { requests };
1198
+
1199
+ case 'download':
1200
+ if (params.selector && params.downloadPath) {
1201
+ const locator = browser.getLocator(params.selector);
1202
+ const downloadPromise = page.waitForEvent('download');
1203
+ await locator.click();
1204
+ const download = await downloadPromise;
1205
+ await download.saveAs(params.downloadPath);
1206
+ }
1207
+ return { downloaded: params.downloadPath };
1208
+
1209
+ // ========== 设置和模拟 ==========
1210
+
1211
+ case 'viewport':
1212
+ if (params.width && params.height) {
1213
+ await browser.setViewport(params.width, params.height);
1214
+ }
1215
+ return { viewport: { width: params.width, height: params.height } };
1216
+
1217
+ case 'device':
1218
+ if (params.device) {
1219
+ const deviceInfo = browser.getDevice(params.device);
1220
+ if (deviceInfo) {
1221
+ await browser.setViewport(deviceInfo.viewport.width, deviceInfo.viewport.height);
1222
+ await browser.getPage().setUserAgent(deviceInfo.userAgent);
1223
+ }
1224
+ }
1225
+ return { device: params.device };
1226
+
1227
+ case 'geolocation':
1228
+ if (params.latitude !== undefined && params.longitude !== undefined) {
1229
+ await browser.setGeolocation(params.latitude, params.longitude, params.accuracy);
1230
+ }
1231
+ return { geolocation: { latitude: params.latitude, longitude: params.longitude } };
1232
+
1233
+ case 'permissions':
1234
+ if (params.permissions && params.grant !== undefined) {
1235
+ await browser.setPermissions(params.permissions, params.grant);
1236
+ }
1237
+ return { permissions: params.permissions, granted: params.grant };
1238
+
1239
+ case 'useragent':
1240
+ if (params.userAgent) {
1241
+ await browser.getPage().setUserAgent(params.userAgent);
1242
+ }
1243
+ return { userAgent: params.userAgent };
1244
+
1245
+ case 'timezone':
1246
+ await browser.getPage().emulateTimezone(params.timezone);
1247
+ return { timezone: params.timezone };
1248
+
1249
+ case 'locale':
1250
+ await browser.getPage().setLocale(params.locale);
1251
+ return { locale: params.locale };
1252
+
1253
+ case 'offline':
1254
+ await browser.setOffline(params.offline);
1255
+ return { offline: params.offline };
1256
+
1257
+ case 'headers':
1258
+ await browser.setExtraHeaders(params.headers);
1259
+ return { headers: params.headers };
1260
+
1261
+ // ========== 高级功能 ==========
1262
+
1263
+ case 'dialog':
1264
+ if (params.dialogResponse === 'accept') {
1265
+ browser.setDialogHandler('accept', params.promptText);
1266
+ } else {
1267
+ browser.setDialogHandler('dismiss');
1268
+ }
1269
+ return { handled: params.dialogResponse };
1270
+
1271
+ case 'pdf':
1272
+ const pdfPage = browser.getPage();
1273
+ const pdfBuffer = await pdfPage.pdf({
1274
+ format: params.format || 'A4',
1275
+ path: params.pdfPath
1276
+ });
1277
+ return { saved: params.pdfPath, size: pdfBuffer.length };
1278
+
1279
+ case 'console':
1280
+ if (params.clearConsole) {
1281
+ browser.clearConsoleMessages();
1282
+ } else {
1283
+ const messages = browser.getConsoleMessages();
1284
+ return { consoleMessages: messages };
1285
+ }
1286
+ return { cleared: params.clearConsole };
1287
+
1288
+ case 'errors':
1289
+ if (params.clearErrors) {
1290
+ browser.clearPageErrors();
1291
+ } else {
1292
+ const errors = browser.getPageErrors();
1293
+ return { errors };
1294
+ }
1295
+ return { cleared: params.clearErrors };
1296
+
1297
+ default:
1298
+ throw new Error(`未知操作: ${params.action}`);
1299
+ }
1300
+ },
1301
+
1302
+ /**
1303
+ * 执行子操作(用于语义定位器)
1304
+ */
1305
+ async executeSubaction(locator, subaction, value) {
1306
+ switch (subaction) {
1307
+ case 'click':
1308
+ await locator.click();
1309
+ break;
1310
+ case 'fill':
1311
+ await locator.fill(value);
1312
+ break;
1313
+ case 'check':
1314
+ await locator.setChecked(true);
1315
+ break;
1316
+ case 'hover':
1317
+ await locator.hover();
1318
+ break;
1319
+ case 'text':
1320
+ await locator.textContent();
1321
+ break;
1322
+ default:
1323
+ throw new Error(`未知子操作: ${subaction}`);
1324
+ }
1325
+ },
1326
+
1327
+ /**
1328
+ * 高级方法:获取带引用的快照
1329
+ */
1330
+ async getSnapshotWithRefs(options = {}) {
1331
+ const browser = await this.getBrowserManager(options);
1332
+ const snapshot = await browser.getSnapshot(options);
1333
+ return {
1334
+ success: true,
1335
+ snapshot: snapshot.snapshot,
1336
+ refs: snapshot.refs,
1337
+ metadata: {
1338
+ url: await browser.getPage().url(),
1339
+ title: await browser.getPage().title()
1340
+ }
1341
+ };
1342
+ },
1343
+
1344
+ /**
1345
+ * 高级方法:导航到 URL
1346
+ */
1347
+ async navigate(url, options = {}) {
1348
+ const browser = await this.getBrowserManager(options);
1349
+ await browser.navigate(url);
1350
+ return {
1351
+ success: true,
1352
+ url: url
1353
+ };
1354
+ },
1355
+
1356
+ /**
1357
+ * 高级方法:点击元素
1358
+ */
1359
+ async click(selector, options = {}) {
1360
+ const browser = await this.getBrowserManager(options);
1361
+ const locator = browser.getLocator(selector);
1362
+ await locator.click();
1363
+ return { success: true, clicked: selector };
1364
+ },
1365
+
1366
+ /**
1367
+ * 高级方法:填充表单字段
1368
+ */
1369
+ async fill(selector, value, options = {}) {
1370
+ const browser = await this.getBrowserManager(options);
1371
+ const locator = browser.getLocator(selector);
1372
+ await locator.fill(value);
1373
+ return { success: true, filled: value };
1374
+ },
1375
+
1376
+ /**
1377
+ * 高级方法:截取屏幕截图
1378
+ */
1379
+ async screenshot(path, options = {}) {
1380
+ const browser = await this.getBrowserManager(options);
1381
+ const page = browser.getPage();
1382
+ const buffer = await page.screenshot({
1383
+ path: path,
1384
+ fullPage: options.fullPage
1385
+ });
1386
+ return { success: true, path };
1387
+ }
1388
+ };