@glittering0717/ai-browser 1.3.0 → 2.0.0
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 +56 -5
- package/bin/ai-browser.cjs +32 -4
- package/examples/AGENT_INSTRUCTIONS.md +49 -164
- package/llms-install.md +27 -2
- package/package.json +3 -1
- package/scripts/cli_call.cjs +178 -0
- package/scripts/skill.cjs +133 -0
- package/skills/ai-browser/SKILL.md +55 -0
- package/skills/ai-browser/references/hard-pages.md +52 -0
- package/skills/ai-browser/references/network.md +39 -0
- package/skills/ai-browser/references/tasks.md +44 -0
- package/skills/ai-browser/references/troubleshooting.md +31 -0
- package/skills/content-publish/SKILL.md +61 -0
- package/skills/read-webpage/SKILL.md +52 -0
- package/skills/web-network-monitor/SKILL.md +52 -0
- package/src/main/axExtractor.js +50 -6
- package/src/main/capability_probe.js +93 -5
- package/src/main/index.js +99 -6
- package/src/main/mcp_args.js +122 -0
- package/src/main/mcp_server.js +55 -72
- package/src/main/mcp_tools.js +127 -86
- package/src/main/mcp_ws.js +7 -1
- package/src/main/page_manager.js +481 -71
- package/src/main/window_open_shim_source.js +117 -0
- package/src/preload/bridge.cjs +22 -4
- package/src/preload/extractor.cjs +161 -17
- package/src/shared/doc_coverage.js +264 -0
- package/src/shared/effect_state.js +160 -0
- package/src/shared/frame_probe.cjs +91 -1
- package/src/shared/frame_read_source.js +260 -0
- package/src/shared/opened_tab.js +71 -0
- package/src/shared/ref_epoch.js +99 -0
- package/src/shared/tab_hooks.js +33 -0
- package/src/shared/version.js +1 -1
- package/tools/coverage_probe.cjs +111 -9
package/src/main/mcp_tools.js
CHANGED
|
@@ -42,37 +42,81 @@ export const MCP_TOOLS = [
|
|
|
42
42
|
{
|
|
43
43
|
name: 'browse_act',
|
|
44
44
|
title: 'Act on element',
|
|
45
|
-
description: 'Act on
|
|
45
|
+
description: 'Act on a data-ai-id element. type appends at caret, setContent replaces all. Verb-specific params live in the group of the same name (click/read/press/wheel/drag).',
|
|
46
46
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
47
47
|
inputSchema: {
|
|
48
48
|
type: 'object',
|
|
49
49
|
properties: {
|
|
50
|
-
action: { type: 'string', enum: ['click', 'type', 'clear', 'focus', 'hover', 'scroll_to', 'upload', 'get_value', 'drag', 'press', 'wheel', 'paste'], description: '
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
value: { type: 'string', description: '
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
50
|
+
action: { type: 'string', enum: ['click', 'type', 'setContent', 'clear', 'focus', 'hover', 'scroll_to', 'upload', 'get_value', 'drag', 'press', 'wheel', 'paste'], description: 'Verb. Verb-specific params live in the group of the same name.' },
|
|
51
|
+
target: { type: 'string', description: 'data-ai-id from the tree (e.g. e:button-12) — NOT the visible label. Optional for press; drag may use drag.from; upload also takes a CSS selector.' },
|
|
52
|
+
text: { type: 'string', description: 'type: appended at the caret. setContent: replaces all. paste: plain fallback when html is absent.' },
|
|
53
|
+
html: { type: 'string', description: 'paste: HTML, preferred — editors keep headings/lists/bold. Plain text loses formatting.' },
|
|
54
|
+
value: { type: 'string', description: 'Option value for a <select>.' },
|
|
55
|
+
file: { type: 'string', description: 'upload: absolute path. target may be the input, the trigger, or a CSS selector.' },
|
|
56
|
+
timeout_ms: { type: 'integer', description: 'Deadline ms (default 15000, 1s..300s). On timeout: success=false, timed_out=true, may have partially applied.' },
|
|
57
|
+
click: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
description: 'click only.',
|
|
60
|
+
properties: {
|
|
61
|
+
url: { type: 'string', description: 'Hit the link whose href matches this URL (for composite cards with several links).' },
|
|
62
|
+
keep_tab: { type: 'boolean', description: 'Stay on this tab instead of following a newly opened one.' },
|
|
63
|
+
opened_tab_wait_ms: { type: 'integer', description: 'Extra wait ms for a site-opened tab (default 250 on link targets; pass it to force, 0 off).' }
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
read: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
description: 'get_value only — read-only: does not focus the element or fire input events.',
|
|
69
|
+
properties: {
|
|
70
|
+
offset: { type: 'integer', description: 'Start offset in Unicode code points (default 0).' },
|
|
71
|
+
limit: { type: 'integer', description: 'Max code points to return (default 20000, hard cap 1000000).' }
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
press: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
description: 'press only. Keys go to the current focus, so target is optional.',
|
|
77
|
+
properties: {
|
|
78
|
+
key: { type: 'string', description: 'Delete, Escape, ArrowRight, Enter, or a combo like "Meta+a".' },
|
|
79
|
+
keys: { type: 'array', items: { type: 'string' }, description: 'Sequence to press in order.' }
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
wheel: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
description: 'wheel only. Lands on target, or the viewport centre when target is omitted.',
|
|
85
|
+
properties: {
|
|
86
|
+
dx: { type: 'number', description: 'Horizontal delta (positive scrolls right).' },
|
|
87
|
+
dy: { type: 'number', description: 'Vertical delta. Negative scrolls up / zooms out.' },
|
|
88
|
+
hold: { type: 'array', items: { type: 'string' }, description: 'Modifiers held, e.g. ["Control"] to zoom a canvas.' }
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
drag: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
description: 'drag only. Give from/to for absolute points, dx/dy for a relative move, or target_to to drop onto another element.',
|
|
94
|
+
properties: {
|
|
95
|
+
from: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
description: 'Start point. Omit to start from target.',
|
|
98
|
+
properties: {
|
|
99
|
+
x: { type: 'number' },
|
|
100
|
+
y: { type: 'number' },
|
|
101
|
+
anchor: { type: 'string', enum: ['center', 'left', 'right', 'top', 'bottom'], description: 'A side prefers that node\'s connection handle.' }
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
to: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
description: 'End point in viewport coords.',
|
|
107
|
+
properties: {
|
|
108
|
+
x: { type: 'number' },
|
|
109
|
+
y: { type: 'number' },
|
|
110
|
+
anchor: { type: 'string', enum: ['center', 'left', 'right', 'top', 'bottom'] }
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
dx: { type: 'number', description: 'Horizontal distance instead of to.x.' },
|
|
114
|
+
dy: { type: 'number', description: 'Vertical distance instead of to.y.' },
|
|
115
|
+
target_to: { type: 'string', description: 'data-ai-id to drop onto (e.g. connect one node\'s output to another).' },
|
|
116
|
+
hold: { type: 'array', items: { type: 'string' }, description: 'Modifiers held during the drag, e.g. ["Shift"].' },
|
|
117
|
+
button: { type: 'string', enum: ['left', 'middle', 'right'], description: 'Canvas apps often pan on middle/right drag.' }
|
|
118
|
+
}
|
|
119
|
+
},
|
|
76
120
|
tab: { type: 'integer', description: 'Tab id (default: active tab).' }
|
|
77
121
|
},
|
|
78
122
|
required: ['action']
|
|
@@ -164,81 +208,78 @@ export const MCP_TOOLS = [
|
|
|
164
208
|
}
|
|
165
209
|
},
|
|
166
210
|
{
|
|
167
|
-
name: '
|
|
168
|
-
title: '
|
|
169
|
-
description: '
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
{
|
|
174
|
-
name: 'browse_new_tab',
|
|
175
|
-
title: 'New tab',
|
|
176
|
-
description: 'Open a new tab, optionally at a URL.',
|
|
211
|
+
name: 'browse_tabs',
|
|
212
|
+
title: 'Tabs',
|
|
213
|
+
description: 'Tabs: op=list / new / close / switch. switch makes a tab the default target for later calls, so you rarely pass tab explicitly.',
|
|
214
|
+
// 合并前是 4 个工具(list / new / close / set_active),每个只承载一个动词。
|
|
215
|
+
// 合并后 destructiveHint 只能是 false —— 否则 op=list 也会被标成破坏性操作;
|
|
216
|
+
// close 的破坏性写在 description 里。这是合并的唯一语义代价,接受。
|
|
177
217
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
178
218
|
inputSchema: {
|
|
179
219
|
type: 'object',
|
|
180
220
|
properties: {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
name: 'browse_close_tab',
|
|
187
|
-
title: 'Close tab',
|
|
188
|
-
description: 'Close a tab by id.',
|
|
189
|
-
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
190
|
-
inputSchema: {
|
|
191
|
-
type: 'object',
|
|
192
|
-
properties: {
|
|
193
|
-
tab: { type: 'integer', description: 'Tab id.' }
|
|
194
|
-
},
|
|
195
|
-
required: ['tab']
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
name: 'browse_set_active_tab',
|
|
200
|
-
title: 'Switch active tab',
|
|
201
|
-
description: 'Switch the active tab.',
|
|
202
|
-
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
203
|
-
inputSchema: {
|
|
204
|
-
type: 'object',
|
|
205
|
-
properties: {
|
|
206
|
-
tab: { type: 'integer', description: 'Tab id.' }
|
|
221
|
+
op: { type: 'string', enum: ['list', 'new', 'close', 'switch'], description: 'list = id/url/title/active for every tab. switch = make `tab` the default target.' },
|
|
222
|
+
tab: { type: 'integer', description: 'close / switch: the target tab id.' },
|
|
223
|
+
url: { type: 'string', description: 'new: open at this URL (default about:blank).' }
|
|
207
224
|
},
|
|
208
|
-
required: ['
|
|
225
|
+
required: ['op']
|
|
209
226
|
}
|
|
210
227
|
},
|
|
211
228
|
{
|
|
212
229
|
name: 'browse_network',
|
|
213
230
|
title: 'Inspect network',
|
|
214
|
-
description: 'Query captured HTTP requests
|
|
231
|
+
description: 'Query captured HTTP requests: list/get/clear/configure. Captures headers and POST bodies; sensitive headers redacted. Each op reads its own group: filters / include / body_offset / configure.',
|
|
215
232
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
216
233
|
inputSchema: {
|
|
217
234
|
type: 'object',
|
|
218
235
|
properties: {
|
|
219
236
|
operation: { type: 'string', enum: ['list', 'get', 'clear', 'configure'], description: 'Operation.' },
|
|
220
|
-
tab: { type: 'integer', description: 'Tab id (default: active tab).' },
|
|
221
237
|
network_id: { type: 'string', description: 'get: id from list.' },
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
238
|
+
filters: {
|
|
239
|
+
type: 'object',
|
|
240
|
+
description: 'list only.',
|
|
241
|
+
properties: {
|
|
242
|
+
method: { type: 'array', items: { type: 'string' }, description: 'HTTP methods.' },
|
|
243
|
+
url_contains: { type: 'string', description: 'URL substring.' },
|
|
244
|
+
status_min: { type: 'integer' },
|
|
245
|
+
status_max: { type: 'integer' },
|
|
246
|
+
resource_type: { type: 'array', items: { type: 'string' }, description: 'XHR / Fetch / Document / …' },
|
|
247
|
+
state: { type: 'array', items: { type: 'string' }, description: 'pending / finished / failed.' },
|
|
248
|
+
started_after: { type: 'string', description: 'ISO timestamp.' },
|
|
249
|
+
started_before: { type: 'string', description: 'ISO timestamp.' },
|
|
250
|
+
limit: { type: 'integer', description: 'Page size (default 50, max 200).' },
|
|
251
|
+
before_seq: { type: 'integer', description: 'Cursor from pagination.next_before_seq.' }
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
include: {
|
|
255
|
+
type: 'object',
|
|
256
|
+
description: 'get only — what to bring back.',
|
|
257
|
+
properties: {
|
|
258
|
+
request_headers: { type: 'boolean' },
|
|
259
|
+
response_headers: { type: 'boolean' },
|
|
260
|
+
request_body: { type: 'boolean' },
|
|
261
|
+
response_body: { type: 'boolean' },
|
|
262
|
+
sensitive_headers: { type: 'boolean', description: 'Requires AI_BROWSER_NETWORK_SENSITIVE=1.' },
|
|
263
|
+
body_limit: { type: 'integer', description: 'Body page size (default 65536, max 262144).' }
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
body_offset: {
|
|
267
|
+
type: 'object',
|
|
268
|
+
description: 'get only — paginate a large body.',
|
|
269
|
+
properties: {
|
|
270
|
+
request: { type: 'integer' },
|
|
271
|
+
response: { type: 'integer' }
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
configure: {
|
|
275
|
+
type: 'object',
|
|
276
|
+
description: 'configure only.',
|
|
277
|
+
properties: {
|
|
278
|
+
enabled: { type: 'boolean', description: 'Turn capture on/off.' },
|
|
279
|
+
capture_bodies: { type: 'string', enum: ['none', 'api', 'all'] }
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
tab: { type: 'integer', description: 'Tab id (default: active tab).' }
|
|
242
283
|
},
|
|
243
284
|
required: ['operation']
|
|
244
285
|
}
|
package/src/main/mcp_ws.js
CHANGED
|
@@ -102,7 +102,13 @@ export function createWsClient({
|
|
|
102
102
|
|
|
103
103
|
// Attempt failed — release this socket's connect-phase listeners fully so
|
|
104
104
|
// its later error/close can't touch live state (MW-4).
|
|
105
|
-
|
|
105
|
+
// ⚠️ close() 一个还在 CONNECTING 的 socket 会异步再发一次 'error'
|
|
106
|
+
// ("WebSocket was closed before the connection was established")。
|
|
107
|
+
// removeAllListeners 把 connect-phase 的 once('error') 也摘了,这个迟到
|
|
108
|
+
// 的 error 就没有监听器 → EventEmitter 直接 throw → 整个 MCP server 进程
|
|
109
|
+
// 崩掉,lazy launch 重试循环根本走不到(表现为"首次调用必崩")。
|
|
110
|
+
// 挂一个 no-op error 兜底:迟到事件只被吞掉,不影响 MW-4 的状态隔离。
|
|
111
|
+
try { ws.removeAllListeners(); ws.on('error', () => {}); ws.close(); } catch (_e) {}
|
|
106
112
|
const err = connectionErr || new Error('connection refused');
|
|
107
113
|
// eslint-disable-next-line no-console
|
|
108
114
|
if (attempt > 0 && attempt % 4 === 0) console.error('[mcp] ws reconnect attempt', attempt, '-', err.message);
|