amalgm 0.1.88 → 0.1.89
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/lib/cli.js +16 -6
- package/lib/service.js +19 -0
- package/lib/supervisor.js +51 -0
- package/package.json +1 -1
- package/runtime/scripts/amalgm-mcp/apps/supervisor.js +0 -25
- package/runtime/scripts/amalgm-mcp/browser/auth-tools.js +179 -0
- package/runtime/scripts/amalgm-mcp/browser/cli.js +217 -0
- package/runtime/scripts/amalgm-mcp/browser/cua-tools.js +147 -0
- package/runtime/scripts/amalgm-mcp/browser/engine.js +454 -0
- package/runtime/scripts/amalgm-mcp/browser/recorder-tools.js +82 -0
- package/runtime/scripts/amalgm-mcp/browser/recorder.js +199 -0
- package/runtime/scripts/amalgm-mcp/browser/rest.js +5 -1
- package/runtime/scripts/amalgm-mcp/browser/tools.js +104 -731
- package/runtime/scripts/amalgm-mcp/fs/rest.js +5 -6
- package/runtime/scripts/amalgm-mcp/index.js +2 -0
- package/runtime/scripts/amalgm-mcp/lib/tool-result.js +32 -66
- package/runtime/scripts/amalgm-mcp/project-context/paths.js +266 -0
- package/runtime/scripts/amalgm-mcp/project-context/prompt.js +232 -0
- package/runtime/scripts/amalgm-mcp/project-context/rest.js +100 -0
- package/runtime/scripts/amalgm-mcp/project-context/store.js +671 -0
- package/runtime/scripts/amalgm-mcp/project-context/tools.js +146 -0
- package/runtime/scripts/amalgm-mcp/server/core-tools.js +35 -2
- package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
- package/runtime/scripts/amalgm-mcp/server/routes/project-context.js +33 -0
- package/runtime/scripts/amalgm-mcp/state/db.js +11 -0
- package/runtime/scripts/amalgm-mcp/state/snapshot.js +3 -3
- package/runtime/scripts/amalgm-mcp/tests/core-tools.test.js +37 -4
- package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +328 -0
- package/runtime/scripts/amalgm-mcp/tests/system-catalog.test.js +9 -9
- package/runtime/scripts/amalgm-mcp/tests/toolbox-service.test.js +2 -2
- package/runtime/scripts/amalgm-mcp/toolbox/loadout-context.js +1 -0
- package/runtime/scripts/amalgm-mcp/toolbox/store.js +0 -15
- package/runtime/scripts/amalgm-mcp/toolbox/system-catalog.js +2 -0
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +11 -7
- package/runtime/scripts/chat-core/adapters/opencode.js +1 -1
- package/runtime/scripts/chat-core/engine.js +16 -2
- package/runtime/scripts/chat-core/input.js +19 -1
- package/runtime/scripts/chat-core/tool-shape.js +6 -4
- package/runtime/scripts/chat-core/tooling/system-instructions.js +51 -0
- package/runtime/scripts/chat-core/tooling/system-prompt.js +34 -8
- package/runtime/scripts/local-gateway.js +1 -0
- package/runtime/scripts/amalgm-mcp/browser/agent-browser.js +0 -569
- package/runtime/scripts/amalgm-mcp/browser/page.js +0 -25
- package/runtime/scripts/chat-core/tooling/active-memory.js +0 -574
- package/runtime/scripts/chat-core/tooling/passive-memory.js +0 -576
|
@@ -1,409 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Browser
|
|
3
|
-
*
|
|
4
|
+
* Browser tools — the thin surface.
|
|
5
|
+
*
|
|
6
|
+
* Nine verbs, built on the agent-browser CLI (vercel-labs/agent-browser).
|
|
7
|
+
* The loop is: open → snapshot (refs) → click/fill (@ref) → re-snapshot.
|
|
8
|
+
* Everything else agent-browser can do (scroll, hover, cookies, network
|
|
9
|
+
* mocking, traces, diffs, React inspection, ...) is reachable through
|
|
10
|
+
* browser_cli without widening this schema.
|
|
11
|
+
*
|
|
12
|
+
* In the Amalgm desktop app these drive the visible in-tab splitscreen
|
|
13
|
+
* browser; elsewhere they drive a standalone chromium.
|
|
4
14
|
*/
|
|
5
15
|
|
|
6
16
|
const { textResult, errorResult } = require('../lib/tool-result');
|
|
7
|
-
const
|
|
8
|
-
clipboardBrowser,
|
|
9
|
-
closeBrowser,
|
|
10
|
-
clickBrowser,
|
|
11
|
-
cuaBrowser,
|
|
12
|
-
evaluateBrowser,
|
|
13
|
-
getTextBrowser,
|
|
14
|
-
locatorCountBrowser,
|
|
15
|
-
locatorTextBrowser,
|
|
16
|
-
loadStateBrowser,
|
|
17
|
-
navigateBrowser,
|
|
18
|
-
pressBrowser,
|
|
19
|
-
screenshotBrowser,
|
|
20
|
-
saveStateBrowser,
|
|
21
|
-
snapshotBrowser,
|
|
22
|
-
startVideoBrowser,
|
|
23
|
-
stateBrowser,
|
|
24
|
-
stopVideoBrowser,
|
|
25
|
-
tabNavigationBrowser,
|
|
26
|
-
tabsGetBrowser,
|
|
27
|
-
tabsListBrowser,
|
|
28
|
-
tabsNewBrowser,
|
|
29
|
-
tabsSelectBrowser,
|
|
30
|
-
tabsSelectedBrowser,
|
|
31
|
-
typeBrowser,
|
|
32
|
-
waitForLoadStateBrowser,
|
|
33
|
-
waitForSelectorBrowser,
|
|
34
|
-
waitForURLBrowser,
|
|
35
|
-
} = require('./page');
|
|
36
|
-
const {
|
|
37
|
-
createBrowserLoginSession,
|
|
38
|
-
listBrowserAuthBundles,
|
|
39
|
-
listBrowserLoginSessions,
|
|
40
|
-
listBrowserProfiles,
|
|
41
|
-
markBrowserAuthBundleImported,
|
|
42
|
-
readEncryptedStateBundle,
|
|
43
|
-
safeId,
|
|
44
|
-
touchBrowserProfile,
|
|
45
|
-
writeEncryptedStateBundle,
|
|
46
|
-
} = require('./store');
|
|
17
|
+
const engine = require('./engine');
|
|
47
18
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
19
|
+
const sessionProperty = {
|
|
20
|
+
session: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Browser session/profile id. Defaults to your chat session — omit it unless you need a second browser.',
|
|
23
|
+
},
|
|
52
24
|
};
|
|
53
25
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
role: { type: 'string', description: 'ARIA role, e.g. button, link, textbox' },
|
|
60
|
-
name: { type: 'string', description: 'Accessible name or visible name used with role/text matching' },
|
|
61
|
-
label: { type: 'string', description: 'Form label text' },
|
|
62
|
-
placeholder: { type: 'string', description: 'Input placeholder text' },
|
|
63
|
-
testId: { type: 'string', description: 'data-testid, data-test-id, or data-test value' },
|
|
64
|
-
exact: { type: 'boolean', description: 'Require exact text/name match' },
|
|
65
|
-
strict: { type: 'boolean', description: 'Require the locator to resolve to exactly one visible element' },
|
|
66
|
-
timeoutMs: { type: 'number', description: 'Maximum wait time in milliseconds' },
|
|
26
|
+
const targetProperty = {
|
|
27
|
+
target: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Element target: a snapshot ref like "@e12" (preferred — run browser_snapshot first), a CSS selector, or "text=Visible label".',
|
|
30
|
+
},
|
|
67
31
|
};
|
|
68
32
|
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
function targetLine(result) {
|
|
74
|
-
const bits = [];
|
|
75
|
-
if (result?.tabId) bits.push(`Tab: ${result.tabId}`);
|
|
76
|
-
if (result?.browserSessionId) bits.push(`Session: ${result.browserSessionId}`);
|
|
77
|
-
return bits.length ? `\n${bits.join('\n')}` : '';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function requireFiniteCoordinate(value, name) {
|
|
81
|
-
const number = Number(value);
|
|
82
|
-
if (!Number.isFinite(number)) throw new Error(`${name} must be a finite number`);
|
|
83
|
-
return number;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function authDomainsFrom(inputDomains, fallbackUrl) {
|
|
87
|
-
if (Array.isArray(inputDomains) && inputDomains.length > 0) return inputDomains;
|
|
88
|
-
try {
|
|
89
|
-
const hostname = new URL(fallbackUrl).hostname;
|
|
90
|
-
return hostname ? [hostname] : [];
|
|
91
|
-
} catch {
|
|
92
|
-
return [];
|
|
93
|
-
}
|
|
33
|
+
function line(result, extra = '') {
|
|
34
|
+
const bits = [extra, result?.url ? `URL: ${result.url}` : '', result?.session ? `Session: ${result.session}` : '']
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
return bits.join('\n');
|
|
94
37
|
}
|
|
95
38
|
|
|
96
39
|
module.exports = [
|
|
97
40
|
{
|
|
98
|
-
name: '
|
|
99
|
-
description: '
|
|
100
|
-
inputSchema: { type: 'object', properties: {} },
|
|
101
|
-
async handler() {
|
|
102
|
-
try {
|
|
103
|
-
return jsonText({ profiles: listBrowserProfiles() });
|
|
104
|
-
} catch (err) {
|
|
105
|
-
return errorResult(`List browser profiles failed: ${err.message}`);
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
name: 'browser_auth_bundles_list',
|
|
111
|
-
description: 'List encrypted browser auth bundles available on this computer. Raw cookies/storage are never returned.',
|
|
112
|
-
inputSchema: { type: 'object', properties: {} },
|
|
113
|
-
async handler() {
|
|
114
|
-
try {
|
|
115
|
-
return jsonText({ bundles: listBrowserAuthBundles() });
|
|
116
|
-
} catch (err) {
|
|
117
|
-
return errorResult(`List browser auth bundles failed: ${err.message}`);
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
name: 'browser_login_sessions_list',
|
|
123
|
-
description: 'List temporary browser login links on this computer. Raw tokens and cookies are never returned.',
|
|
124
|
-
inputSchema: { type: 'object', properties: {} },
|
|
125
|
-
async handler() {
|
|
126
|
-
try {
|
|
127
|
-
return jsonText({ sessions: listBrowserLoginSessions() });
|
|
128
|
-
} catch (err) {
|
|
129
|
-
return errorResult(`List browser login sessions failed: ${err.message}`);
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
name: 'browser_auth_link_create',
|
|
135
|
-
description: 'Create a short-lived browser login link for the user. Use this when an agent needs the user to log into a site before QA or browser work.',
|
|
136
|
-
inputSchema: {
|
|
137
|
-
type: 'object',
|
|
138
|
-
properties: {
|
|
139
|
-
targetUrl: { type: 'string', description: 'The site or app URL the user should log into.' },
|
|
140
|
-
browserProfileId: { type: 'string', description: 'Durable browser profile id that will receive the login.' },
|
|
141
|
-
browserSessionId: { type: 'string', description: 'Alias for browserProfileId.' },
|
|
142
|
-
name: { type: 'string', description: 'Human-readable login/bundle name.' },
|
|
143
|
-
domains: { type: 'array', items: { type: 'string' }, description: 'Domains to save after login, e.g. app.example.com.' },
|
|
144
|
-
transport: { type: 'string', description: 'Optional live-view transport: electron-splitview, novnc, or auto.' },
|
|
145
|
-
liveUrl: { type: 'string', description: 'Optional noVNC/live-view URL for web/mobile login.' },
|
|
146
|
-
appBaseUrl: { type: 'string', description: 'Optional Amalgm app base URL used to build an absolute link.' },
|
|
147
|
-
ttlMs: { type: 'number', description: 'Optional link lifetime in milliseconds. Defaults to 15 minutes, max 1 hour.' },
|
|
148
|
-
},
|
|
149
|
-
required: ['targetUrl'],
|
|
150
|
-
},
|
|
151
|
-
async handler(args, ctx) {
|
|
152
|
-
try {
|
|
153
|
-
const profileId = safeId(
|
|
154
|
-
args.browserProfileId
|
|
155
|
-
|| args.browserSessionId
|
|
156
|
-
|| ctx?.callerSessionId
|
|
157
|
-
|| `login-${Date.now().toString(36)}`,
|
|
158
|
-
);
|
|
159
|
-
const result = createBrowserLoginSession({
|
|
160
|
-
...args,
|
|
161
|
-
browserProfileId: profileId,
|
|
162
|
-
profileId,
|
|
163
|
-
}, { source: 'browser-auth-link-tool' });
|
|
164
|
-
return jsonText({
|
|
165
|
-
session: result.session,
|
|
166
|
-
authLink: result.authLink,
|
|
167
|
-
authLinkPath: result.authLinkPath,
|
|
168
|
-
message: `Open this temporary login link: ${result.authLink}`,
|
|
169
|
-
});
|
|
170
|
-
} catch (err) {
|
|
171
|
-
return errorResult(`Create browser auth link failed: ${err.message}`);
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
name: 'browser_auth_save',
|
|
177
|
-
description: 'Save the current browser profile cookies/storage into an encrypted auth bundle for later agent use.',
|
|
178
|
-
inputSchema: {
|
|
179
|
-
type: 'object',
|
|
180
|
-
properties: {
|
|
181
|
-
browserSessionId: { type: 'string', description: 'Browser session/profile id to save from' },
|
|
182
|
-
browserProfileId: { type: 'string', description: 'Durable browser profile id to save from' },
|
|
183
|
-
name: { type: 'string', description: 'Human-readable bundle name' },
|
|
184
|
-
domains: { type: 'array', items: { type: 'string' }, description: 'Optional domains to include, e.g. example.com' },
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
async handler(args, ctx) {
|
|
188
|
-
try {
|
|
189
|
-
const profileId = safeId(args.browserProfileId || args.browserSessionId || ctx?.callerSessionId || 'default');
|
|
190
|
-
const saved = await saveStateBrowser({ ...args, browserProfileId: profileId, browserSessionId: profileId }, ctx);
|
|
191
|
-
const bundle = writeEncryptedStateBundle({
|
|
192
|
-
name: args.name || saved.title || profileId,
|
|
193
|
-
profileId,
|
|
194
|
-
domains: authDomainsFrom(args.domains, saved.url),
|
|
195
|
-
state: saved.state || {},
|
|
196
|
-
});
|
|
197
|
-
touchBrowserProfile({
|
|
198
|
-
id: profileId,
|
|
199
|
-
name: args.name || profileId,
|
|
200
|
-
source: saved.backend || 'browser-auth-save',
|
|
201
|
-
}, { source: 'browser-auth-save' });
|
|
202
|
-
return jsonText({ bundle });
|
|
203
|
-
} catch (err) {
|
|
204
|
-
return errorResult(`Save browser auth bundle failed: ${err.message}`);
|
|
205
|
-
}
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
name: 'browser_auth_load',
|
|
210
|
-
description: 'Load an encrypted browser auth bundle into a durable browser profile so an agent can start past login.',
|
|
211
|
-
inputSchema: {
|
|
212
|
-
type: 'object',
|
|
213
|
-
properties: {
|
|
214
|
-
id: { type: 'string', description: 'Auth bundle id' },
|
|
215
|
-
browserSessionId: { type: 'string', description: 'Target browser session/profile id' },
|
|
216
|
-
browserProfileId: { type: 'string', description: 'Target durable browser profile id' },
|
|
217
|
-
},
|
|
218
|
-
required: ['id'],
|
|
219
|
-
},
|
|
220
|
-
async handler(args, ctx) {
|
|
221
|
-
try {
|
|
222
|
-
const { bundle, payload } = readEncryptedStateBundle(args.id);
|
|
223
|
-
const profileId = safeId(args.browserProfileId || args.browserSessionId || bundle.profileId);
|
|
224
|
-
const loaded = await loadStateBrowser({
|
|
225
|
-
...args,
|
|
226
|
-
browserProfileId: profileId,
|
|
227
|
-
browserSessionId: profileId,
|
|
228
|
-
state: payload.state,
|
|
229
|
-
}, ctx);
|
|
230
|
-
const updated = markBrowserAuthBundleImported(args.id, profileId);
|
|
231
|
-
touchBrowserProfile({
|
|
232
|
-
id: profileId,
|
|
233
|
-
name: profileId,
|
|
234
|
-
source: loaded.backend || 'browser-auth-load',
|
|
235
|
-
}, { source: 'browser-auth-load' });
|
|
236
|
-
return jsonText({ bundle: updated, loaded });
|
|
237
|
-
} catch (err) {
|
|
238
|
-
return errorResult(`Load browser auth bundle failed: ${err.message}`);
|
|
239
|
-
}
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
name: 'browser_tabs_list',
|
|
244
|
-
description: 'List visible Amalgm browser-use tabs/surfaces.',
|
|
245
|
-
inputSchema: { type: 'object', properties: {} },
|
|
246
|
-
async handler(_args, ctx) {
|
|
247
|
-
try {
|
|
248
|
-
return jsonText(await tabsListBrowser(ctx));
|
|
249
|
-
} catch (err) {
|
|
250
|
-
return errorResult(`List browser tabs failed: ${err.message}`);
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
name: 'browser_tabs_selected',
|
|
256
|
-
description: 'Return the most recently used browser-use tab.',
|
|
257
|
-
inputSchema: { type: 'object', properties: {} },
|
|
258
|
-
async handler(_args, ctx) {
|
|
259
|
-
try {
|
|
260
|
-
return jsonText(await tabsSelectedBrowser(ctx));
|
|
261
|
-
} catch (err) {
|
|
262
|
-
return errorResult(`Get selected browser tab failed: ${err.message}`);
|
|
263
|
-
}
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
name: 'browser_tabs_new',
|
|
268
|
-
description: 'Create a new visible browser-use tab/surface, optionally navigating it to a URL.',
|
|
269
|
-
inputSchema: {
|
|
270
|
-
type: 'object',
|
|
271
|
-
properties: {
|
|
272
|
-
url: { type: 'string', description: 'Optional URL to open' },
|
|
273
|
-
browserSessionId: { type: 'string', description: 'Optional session id; generated if omitted' },
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
async handler(args, ctx) {
|
|
277
|
-
try {
|
|
278
|
-
return jsonText(await tabsNewBrowser(args, ctx));
|
|
279
|
-
} catch (err) {
|
|
280
|
-
return errorResult(`Create browser tab failed: ${err.message}`);
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
name: 'browser_tabs_get',
|
|
286
|
-
description: 'Get state for a browser tab by tabId or browserSessionId.',
|
|
287
|
-
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
288
|
-
async handler(args, ctx) {
|
|
289
|
-
try {
|
|
290
|
-
return jsonText(await tabsGetBrowser(args, ctx));
|
|
291
|
-
} catch (err) {
|
|
292
|
-
return errorResult(`Get browser tab failed: ${err.message}`);
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
name: 'browser_tabs_select',
|
|
298
|
-
description: 'Select/focus a browser-use tab by tabId or browserSessionId.',
|
|
299
|
-
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
300
|
-
async handler(args, ctx) {
|
|
301
|
-
try {
|
|
302
|
-
return jsonText(await tabsSelectBrowser(args, ctx));
|
|
303
|
-
} catch (err) {
|
|
304
|
-
return errorResult(`Select browser tab failed: ${err.message}`);
|
|
305
|
-
}
|
|
306
|
-
},
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
name: 'browser_tabs_close',
|
|
310
|
-
description: 'Close a browser-use tab by tabId or browserSessionId.',
|
|
311
|
-
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
312
|
-
async handler(args, ctx) {
|
|
313
|
-
try {
|
|
314
|
-
await closeBrowser(args, ctx);
|
|
315
|
-
return textResult('Browser tab closed.');
|
|
316
|
-
} catch (err) {
|
|
317
|
-
return errorResult(`Close browser tab failed: ${err.message}`);
|
|
318
|
-
}
|
|
319
|
-
},
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
name: 'browser_state',
|
|
323
|
-
description: 'Return URL/title/loading state for the current or specified browser tab.',
|
|
324
|
-
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
325
|
-
async handler(args, ctx) {
|
|
326
|
-
try {
|
|
327
|
-
return jsonText(await stateBrowser(args, ctx));
|
|
328
|
-
} catch (err) {
|
|
329
|
-
return errorResult(`Browser state failed: ${err.message}`);
|
|
330
|
-
}
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
name: 'browser_navigate',
|
|
335
|
-
description: 'Navigate a visible browser tab to a URL. Returns tabId and browserSessionId for follow-up calls.',
|
|
41
|
+
name: 'browser_open',
|
|
42
|
+
description: 'Open a URL in the browser. In the Amalgm desktop app this appears in the in-tab splitscreen surface. Follow with browser_snapshot to see interactive elements.',
|
|
336
43
|
inputSchema: {
|
|
337
44
|
type: 'object',
|
|
338
|
-
properties: {
|
|
339
|
-
...tabTargetProperties,
|
|
340
|
-
url: { type: 'string', description: 'The URL to navigate to' },
|
|
341
|
-
},
|
|
45
|
+
properties: { url: { type: 'string', description: 'URL to open' }, ...sessionProperty },
|
|
342
46
|
required: ['url'],
|
|
343
47
|
},
|
|
344
48
|
async handler(args, ctx) {
|
|
345
49
|
if (!args.url) return errorResult('url is required');
|
|
346
50
|
try {
|
|
347
|
-
const result = await
|
|
348
|
-
return textResult(`
|
|
51
|
+
const result = await engine.open(args, ctx);
|
|
52
|
+
return textResult(line(result, `Opened: ${result.title || args.url}`)
|
|
53
|
+
+ '\n\nNext: browser_snapshot lists interactive elements as @refs.');
|
|
349
54
|
} catch (err) {
|
|
350
|
-
return errorResult(`
|
|
55
|
+
return errorResult(`Open failed: ${err.message}`);
|
|
351
56
|
}
|
|
352
57
|
},
|
|
353
58
|
},
|
|
354
59
|
{
|
|
355
|
-
name: '
|
|
356
|
-
description: '
|
|
357
|
-
inputSchema: { type: 'object', properties:
|
|
358
|
-
async handler(args, ctx) {
|
|
359
|
-
try {
|
|
360
|
-
return jsonText(await tabNavigationBrowser('back', args, ctx));
|
|
361
|
-
} catch (err) {
|
|
362
|
-
return errorResult(`Browser back failed: ${err.message}`);
|
|
363
|
-
}
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
name: 'browser_forward',
|
|
368
|
-
description: 'Navigate the browser tab forward in history.',
|
|
369
|
-
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
370
|
-
async handler(args, ctx) {
|
|
371
|
-
try {
|
|
372
|
-
return jsonText(await tabNavigationBrowser('forward', args, ctx));
|
|
373
|
-
} catch (err) {
|
|
374
|
-
return errorResult(`Browser forward failed: ${err.message}`);
|
|
375
|
-
}
|
|
376
|
-
},
|
|
377
|
-
},
|
|
378
|
-
{
|
|
379
|
-
name: 'browser_reload',
|
|
380
|
-
description: 'Reload the browser tab.',
|
|
381
|
-
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
60
|
+
name: 'browser_snapshot',
|
|
61
|
+
description: 'Accessibility snapshot of the current page with stable @refs for every interactive element. This is how you see the page; use the refs with browser_click and browser_fill.',
|
|
62
|
+
inputSchema: { type: 'object', properties: sessionProperty },
|
|
382
63
|
async handler(args, ctx) {
|
|
383
64
|
try {
|
|
384
|
-
|
|
65
|
+
const result = await engine.snapshot(args, ctx);
|
|
66
|
+
const header = `Page: ${result.title || ''}\nURL: ${result.url || ''}\n\n`;
|
|
67
|
+
return textResult(header + (result.snapshotText || '(empty page)').slice(0, 12_000));
|
|
385
68
|
} catch (err) {
|
|
386
|
-
return errorResult(`
|
|
69
|
+
return errorResult(`Snapshot failed: ${err.message}`);
|
|
387
70
|
}
|
|
388
71
|
},
|
|
389
72
|
},
|
|
390
73
|
{
|
|
391
74
|
name: 'browser_screenshot',
|
|
392
|
-
description: '
|
|
75
|
+
description: 'Screenshot the current page (PNG). Use browser_snapshot for interaction targets; use this when you need to see pixels.',
|
|
393
76
|
inputSchema: {
|
|
394
77
|
type: 'object',
|
|
395
78
|
properties: {
|
|
396
|
-
|
|
397
|
-
|
|
79
|
+
fullPage: { type: 'boolean', description: 'Capture the full scrollable page instead of the viewport' },
|
|
80
|
+
...sessionProperty,
|
|
398
81
|
},
|
|
399
82
|
},
|
|
400
83
|
async handler(args, ctx) {
|
|
401
84
|
try {
|
|
402
|
-
const capture = await
|
|
85
|
+
const capture = await engine.screenshot(args, ctx);
|
|
403
86
|
return {
|
|
404
87
|
content: [
|
|
405
88
|
{ type: 'image', data: capture.base64, mimeType: 'image/png' },
|
|
406
|
-
{ type: 'text', text: `Screenshot
|
|
89
|
+
{ type: 'text', text: `Screenshot (${capture.bytes} bytes, ${capture.mode || 'viewport'})` },
|
|
407
90
|
],
|
|
408
91
|
};
|
|
409
92
|
} catch (err) {
|
|
@@ -411,449 +94,139 @@ module.exports = [
|
|
|
411
94
|
}
|
|
412
95
|
},
|
|
413
96
|
},
|
|
414
|
-
{
|
|
415
|
-
name: 'browser_snapshot',
|
|
416
|
-
description: 'Get a token-efficient DOM/text snapshot of the current or specified browser tab.',
|
|
417
|
-
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
418
|
-
async handler(args, ctx) {
|
|
419
|
-
try {
|
|
420
|
-
const snapshot = await snapshotBrowser(args, ctx);
|
|
421
|
-
let result = `Page: ${snapshot.title}\nURL: ${snapshot.url}${targetLine(snapshot)}\n\n`;
|
|
422
|
-
result += (snapshot.snapshotText || '(empty)').slice(0, 10_000);
|
|
423
|
-
return textResult(result);
|
|
424
|
-
} catch (err) {
|
|
425
|
-
return errorResult(`Snapshot failed: ${err.message}`);
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
},
|
|
429
97
|
{
|
|
430
98
|
name: 'browser_click',
|
|
431
|
-
description: 'Click an element
|
|
432
|
-
inputSchema: {
|
|
99
|
+
description: 'Click an element. Target with a snapshot @ref, CSS selector, or "text=Label".',
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: { ...targetProperty, ...sessionProperty },
|
|
103
|
+
required: ['target'],
|
|
104
|
+
},
|
|
433
105
|
async handler(args, ctx) {
|
|
434
|
-
if (!args.
|
|
435
|
-
return errorResult('A ref, selector, text, role, label, placeholder, testId, or name is required');
|
|
436
|
-
}
|
|
106
|
+
if (!args.target) return errorResult('target is required (e.g. "@e3", "#submit", "text=Sign in")');
|
|
437
107
|
try {
|
|
438
|
-
const result = await
|
|
439
|
-
return textResult(
|
|
108
|
+
const result = await engine.click(args, ctx);
|
|
109
|
+
return textResult(`Clicked ${args.target}\nSession: ${result.session}\n\nRe-run browser_snapshot if the page changed.`);
|
|
440
110
|
} catch (err) {
|
|
441
111
|
return errorResult(`Click failed: ${err.message}`);
|
|
442
112
|
}
|
|
443
113
|
},
|
|
444
114
|
},
|
|
445
115
|
{
|
|
446
|
-
name: '
|
|
447
|
-
description: '
|
|
116
|
+
name: 'browser_fill',
|
|
117
|
+
description: 'Clear a field and type into it. Optionally press Enter to submit.',
|
|
448
118
|
inputSchema: {
|
|
449
119
|
type: 'object',
|
|
450
120
|
properties: {
|
|
451
|
-
...
|
|
452
|
-
text: { type: 'string', description: '
|
|
453
|
-
|
|
121
|
+
...targetProperty,
|
|
122
|
+
text: { type: 'string', description: 'Text to enter' },
|
|
123
|
+
submit: { type: 'boolean', description: 'Press Enter after typing' },
|
|
124
|
+
...sessionProperty,
|
|
454
125
|
},
|
|
455
|
-
required: ['text'],
|
|
126
|
+
required: ['target', 'text'],
|
|
456
127
|
},
|
|
457
128
|
async handler(args, ctx) {
|
|
458
|
-
if (typeof args.text !== 'string') return errorResult('text
|
|
129
|
+
if (!args.target || typeof args.text !== 'string') return errorResult('target and text are required');
|
|
459
130
|
try {
|
|
460
|
-
const result = await
|
|
461
|
-
return textResult(
|
|
131
|
+
const result = await engine.fill(args, ctx);
|
|
132
|
+
return textResult(`Filled ${args.target}${result.submitted ? ' and pressed Enter' : ''}\nSession: ${result.session}`);
|
|
462
133
|
} catch (err) {
|
|
463
|
-
return errorResult(`
|
|
134
|
+
return errorResult(`Fill failed: ${err.message}`);
|
|
464
135
|
}
|
|
465
136
|
},
|
|
466
137
|
},
|
|
467
138
|
{
|
|
468
139
|
name: 'browser_press',
|
|
469
|
-
description: 'Press a key
|
|
140
|
+
description: 'Press a key or combination at the focused element, e.g. "Enter", "Escape", "Tab", "Control+a".',
|
|
470
141
|
inputSchema: {
|
|
471
142
|
type: 'object',
|
|
472
|
-
properties: {
|
|
473
|
-
...locatorProperties,
|
|
474
|
-
key: { type: 'string', description: 'Key to press, e.g. Enter, Escape, A' },
|
|
475
|
-
},
|
|
143
|
+
properties: { key: { type: 'string', description: 'Key or combination' }, ...sessionProperty },
|
|
476
144
|
required: ['key'],
|
|
477
145
|
},
|
|
478
146
|
async handler(args, ctx) {
|
|
147
|
+
if (!args.key) return errorResult('key is required');
|
|
479
148
|
try {
|
|
480
|
-
|
|
149
|
+
const result = await engine.press(args, ctx);
|
|
150
|
+
return textResult(`Pressed ${args.key}\nSession: ${result.session}`);
|
|
481
151
|
} catch (err) {
|
|
482
152
|
return errorResult(`Press failed: ${err.message}`);
|
|
483
153
|
}
|
|
484
154
|
},
|
|
485
155
|
},
|
|
486
156
|
{
|
|
487
|
-
name: '
|
|
488
|
-
description: '
|
|
489
|
-
inputSchema: {
|
|
490
|
-
type: 'object',
|
|
491
|
-
properties: {
|
|
492
|
-
...tabTargetProperties,
|
|
493
|
-
ref: { type: 'string', description: 'Snapshot ref such as @e1' },
|
|
494
|
-
selector: { type: 'string', description: 'CSS selector to extract (omit for full page)' },
|
|
495
|
-
},
|
|
496
|
-
},
|
|
497
|
-
async handler(args, ctx) {
|
|
498
|
-
try {
|
|
499
|
-
let text = (await getTextBrowser(args, ctx)).text;
|
|
500
|
-
if (text && text.length > 15_000) {
|
|
501
|
-
text = `${text.slice(0, 15_000)}\n\n... (truncated, use a selector for specific content)`;
|
|
502
|
-
}
|
|
503
|
-
return textResult(text || '(empty)');
|
|
504
|
-
} catch (err) {
|
|
505
|
-
return errorResult(`Get text failed: ${err.message}`);
|
|
506
|
-
}
|
|
507
|
-
},
|
|
508
|
-
},
|
|
509
|
-
{
|
|
510
|
-
name: 'browser_locator_count',
|
|
511
|
-
description: 'Count visible elements matching a locator.',
|
|
512
|
-
inputSchema: { type: 'object', properties: locatorProperties },
|
|
513
|
-
async handler(args, ctx) {
|
|
514
|
-
try {
|
|
515
|
-
return jsonText(await locatorCountBrowser(args, ctx));
|
|
516
|
-
} catch (err) {
|
|
517
|
-
return errorResult(`Locator count failed: ${err.message}`);
|
|
518
|
-
}
|
|
519
|
-
},
|
|
520
|
-
},
|
|
521
|
-
{
|
|
522
|
-
name: 'browser_locator_text',
|
|
523
|
-
description: 'Return visible text for the first element matching a locator, plus match count.',
|
|
524
|
-
inputSchema: { type: 'object', properties: locatorProperties },
|
|
525
|
-
async handler(args, ctx) {
|
|
526
|
-
try {
|
|
527
|
-
return jsonText(await locatorTextBrowser(args, ctx));
|
|
528
|
-
} catch (err) {
|
|
529
|
-
return errorResult(`Locator text failed: ${err.message}`);
|
|
530
|
-
}
|
|
531
|
-
},
|
|
532
|
-
},
|
|
533
|
-
{
|
|
534
|
-
name: 'browser_evaluate',
|
|
535
|
-
description: 'Execute JavaScript in the browser page context.',
|
|
157
|
+
name: 'browser_eval',
|
|
158
|
+
description: 'Run JavaScript in the page and return the result. The general-purpose escape hatch for reading or mutating page state.',
|
|
536
159
|
inputSchema: {
|
|
537
160
|
type: 'object',
|
|
538
|
-
properties: {
|
|
539
|
-
...tabTargetProperties,
|
|
540
|
-
script: { type: 'string' },
|
|
541
|
-
},
|
|
161
|
+
properties: { script: { type: 'string', description: 'JavaScript to evaluate' }, ...sessionProperty },
|
|
542
162
|
required: ['script'],
|
|
543
163
|
},
|
|
544
164
|
async handler(args, ctx) {
|
|
545
165
|
if (!args.script) return errorResult('script is required');
|
|
546
166
|
try {
|
|
547
|
-
const { result } = await
|
|
548
|
-
const output = typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result);
|
|
549
|
-
return textResult(output);
|
|
550
|
-
} catch (err) {
|
|
551
|
-
return errorResult(`Evaluate failed: ${err.message}`);
|
|
552
|
-
}
|
|
553
|
-
},
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
name: 'browser_wait_for_load_state',
|
|
557
|
-
description: 'Wait for the browser tab to reach load, domcontentloaded, or networkidle.',
|
|
558
|
-
inputSchema: {
|
|
559
|
-
type: 'object',
|
|
560
|
-
properties: {
|
|
561
|
-
...tabTargetProperties,
|
|
562
|
-
state: { type: 'string', enum: ['load', 'domcontentloaded', 'networkidle'] },
|
|
563
|
-
timeoutMs: { type: 'number' },
|
|
564
|
-
},
|
|
565
|
-
},
|
|
566
|
-
async handler(args, ctx) {
|
|
567
|
-
try {
|
|
568
|
-
return jsonText(await waitForLoadStateBrowser(args, ctx));
|
|
569
|
-
} catch (err) {
|
|
570
|
-
return errorResult(`Wait for load state failed: ${err.message}`);
|
|
571
|
-
}
|
|
572
|
-
},
|
|
573
|
-
},
|
|
574
|
-
{
|
|
575
|
-
name: 'browser_wait_for_url',
|
|
576
|
-
description: 'Wait for the browser tab URL to match a string. Supports * wildcards in Electron mode.',
|
|
577
|
-
inputSchema: {
|
|
578
|
-
type: 'object',
|
|
579
|
-
properties: {
|
|
580
|
-
...tabTargetProperties,
|
|
581
|
-
url: { type: 'string' },
|
|
582
|
-
waitUntil: { type: 'string', enum: ['commit', 'load', 'domcontentloaded', 'networkidle'] },
|
|
583
|
-
timeoutMs: { type: 'number' },
|
|
584
|
-
},
|
|
585
|
-
required: ['url'],
|
|
586
|
-
},
|
|
587
|
-
async handler(args, ctx) {
|
|
588
|
-
try {
|
|
589
|
-
return jsonText(await waitForURLBrowser(args, ctx));
|
|
590
|
-
} catch (err) {
|
|
591
|
-
return errorResult(`Wait for URL failed: ${err.message}`);
|
|
592
|
-
}
|
|
593
|
-
},
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
name: 'browser_wait_for_selector',
|
|
597
|
-
description: 'Wait for a CSS selector to become attached, detached, visible, or hidden.',
|
|
598
|
-
inputSchema: {
|
|
599
|
-
type: 'object',
|
|
600
|
-
properties: {
|
|
601
|
-
...tabTargetProperties,
|
|
602
|
-
selector: { type: 'string' },
|
|
603
|
-
state: { type: 'string', enum: ['attached', 'detached', 'visible', 'hidden'] },
|
|
604
|
-
timeoutMs: { type: 'number' },
|
|
605
|
-
},
|
|
606
|
-
required: ['selector'],
|
|
607
|
-
},
|
|
608
|
-
async handler(args, ctx) {
|
|
609
|
-
try {
|
|
610
|
-
return jsonText(await waitForSelectorBrowser(args, ctx));
|
|
611
|
-
} catch (err) {
|
|
612
|
-
return errorResult(`Wait for selector failed: ${err.message}`);
|
|
613
|
-
}
|
|
614
|
-
},
|
|
615
|
-
},
|
|
616
|
-
{
|
|
617
|
-
name: 'browser_cua_click',
|
|
618
|
-
description: 'Click viewport coordinates in the visible browser tab.',
|
|
619
|
-
inputSchema: {
|
|
620
|
-
type: 'object',
|
|
621
|
-
properties: {
|
|
622
|
-
...tabTargetProperties,
|
|
623
|
-
x: { type: 'number' },
|
|
624
|
-
y: { type: 'number' },
|
|
625
|
-
button: { type: 'number', description: '1 left, 2 middle, 3 right' },
|
|
626
|
-
keypress: { type: 'array', items: { type: 'string' } },
|
|
627
|
-
},
|
|
628
|
-
required: ['x', 'y'],
|
|
629
|
-
},
|
|
630
|
-
async handler(args, ctx) {
|
|
631
|
-
try {
|
|
632
|
-
return jsonText(await cuaBrowser('cua_click', { ...args, x: requireFiniteCoordinate(args.x, 'x'), y: requireFiniteCoordinate(args.y, 'y') }, ctx));
|
|
633
|
-
} catch (err) {
|
|
634
|
-
return errorResult(`CUA click failed: ${err.message}`);
|
|
635
|
-
}
|
|
636
|
-
},
|
|
637
|
-
},
|
|
638
|
-
{
|
|
639
|
-
name: 'browser_cua_double_click',
|
|
640
|
-
description: 'Double-click viewport coordinates in the visible browser tab.',
|
|
641
|
-
inputSchema: {
|
|
642
|
-
type: 'object',
|
|
643
|
-
properties: { ...tabTargetProperties, x: { type: 'number' }, y: { type: 'number' }, keypress: { type: 'array', items: { type: 'string' } } },
|
|
644
|
-
required: ['x', 'y'],
|
|
645
|
-
},
|
|
646
|
-
async handler(args, ctx) {
|
|
647
|
-
try {
|
|
648
|
-
return jsonText(await cuaBrowser('cua_double_click', { ...args, x: requireFiniteCoordinate(args.x, 'x'), y: requireFiniteCoordinate(args.y, 'y') }, ctx));
|
|
649
|
-
} catch (err) {
|
|
650
|
-
return errorResult(`CUA double-click failed: ${err.message}`);
|
|
651
|
-
}
|
|
652
|
-
},
|
|
653
|
-
},
|
|
654
|
-
{
|
|
655
|
-
name: 'browser_cua_move',
|
|
656
|
-
description: 'Move the browser mouse to viewport coordinates.',
|
|
657
|
-
inputSchema: {
|
|
658
|
-
type: 'object',
|
|
659
|
-
properties: { ...tabTargetProperties, x: { type: 'number' }, y: { type: 'number' }, keys: { type: 'array', items: { type: 'string' } } },
|
|
660
|
-
required: ['x', 'y'],
|
|
661
|
-
},
|
|
662
|
-
async handler(args, ctx) {
|
|
663
|
-
try {
|
|
664
|
-
return jsonText(await cuaBrowser('cua_move', { ...args, x: requireFiniteCoordinate(args.x, 'x'), y: requireFiniteCoordinate(args.y, 'y') }, ctx));
|
|
167
|
+
const { result } = await engine.evaluate(args, ctx);
|
|
168
|
+
const output = typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result ?? '');
|
|
169
|
+
return textResult(output.slice(0, 15_000) || '(no result)');
|
|
665
170
|
} catch (err) {
|
|
666
|
-
return errorResult(`
|
|
171
|
+
return errorResult(`Eval failed: ${err.message}`);
|
|
667
172
|
}
|
|
668
173
|
},
|
|
669
174
|
},
|
|
670
175
|
{
|
|
671
|
-
name: '
|
|
672
|
-
description: '
|
|
176
|
+
name: 'browser_wait',
|
|
177
|
+
description: 'Wait for a CSS selector to appear, a URL substring/wildcard to match, or a fixed number of milliseconds.',
|
|
673
178
|
inputSchema: {
|
|
674
179
|
type: 'object',
|
|
675
180
|
properties: {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
keypress: { type: 'array', items: { type: 'string' } },
|
|
181
|
+
selector: { type: 'string', description: 'CSS selector to wait for' },
|
|
182
|
+
url: { type: 'string', description: 'URL substring or *-wildcard to wait for' },
|
|
183
|
+
ms: { type: 'number', description: 'Plain delay in milliseconds (max 60000)' },
|
|
184
|
+
timeoutMs: { type: 'number', description: 'Timeout (default 30000)' },
|
|
185
|
+
...sessionProperty,
|
|
682
186
|
},
|
|
683
|
-
required: ['x', 'y', 'scrollX', 'scrollY'],
|
|
684
|
-
},
|
|
685
|
-
async handler(args, ctx) {
|
|
686
|
-
try {
|
|
687
|
-
return jsonText(await cuaBrowser('cua_scroll', {
|
|
688
|
-
...args,
|
|
689
|
-
x: requireFiniteCoordinate(args.x, 'x'),
|
|
690
|
-
y: requireFiniteCoordinate(args.y, 'y'),
|
|
691
|
-
scrollX: Number(args.scrollX || 0),
|
|
692
|
-
scrollY: Number(args.scrollY || 0),
|
|
693
|
-
}, ctx));
|
|
694
|
-
} catch (err) {
|
|
695
|
-
return errorResult(`CUA scroll failed: ${err.message}`);
|
|
696
|
-
}
|
|
697
|
-
},
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
name: 'browser_cua_type',
|
|
701
|
-
description: 'Type text at the currently focused browser element.',
|
|
702
|
-
inputSchema: {
|
|
703
|
-
type: 'object',
|
|
704
|
-
properties: { ...tabTargetProperties, text: { type: 'string' } },
|
|
705
|
-
required: ['text'],
|
|
706
|
-
},
|
|
707
|
-
async handler(args, ctx) {
|
|
708
|
-
try {
|
|
709
|
-
return jsonText(await cuaBrowser('cua_type', args, ctx));
|
|
710
|
-
} catch (err) {
|
|
711
|
-
return errorResult(`CUA type failed: ${err.message}`);
|
|
712
|
-
}
|
|
713
|
-
},
|
|
714
|
-
},
|
|
715
|
-
{
|
|
716
|
-
name: 'browser_cua_keypress',
|
|
717
|
-
description: 'Press a key combination in the visible browser tab.',
|
|
718
|
-
inputSchema: {
|
|
719
|
-
type: 'object',
|
|
720
|
-
properties: { ...tabTargetProperties, keys: { type: 'array', items: { type: 'string' } } },
|
|
721
|
-
required: ['keys'],
|
|
722
187
|
},
|
|
723
188
|
async handler(args, ctx) {
|
|
724
189
|
try {
|
|
725
|
-
|
|
190
|
+
const result = await engine.wait(args, ctx);
|
|
191
|
+
return textResult(`Done: ${result.matched || result.waited}\nSession: ${result.session}`);
|
|
726
192
|
} catch (err) {
|
|
727
|
-
return errorResult(`
|
|
193
|
+
return errorResult(`Wait failed: ${err.message}`);
|
|
728
194
|
}
|
|
729
195
|
},
|
|
730
196
|
},
|
|
731
197
|
{
|
|
732
|
-
name: '
|
|
733
|
-
description: '
|
|
198
|
+
name: 'browser_cli',
|
|
199
|
+
description: 'Run any agent-browser CLI command against this session — the full surface beyond the core verbs: ["scroll","down"], ["hover","@e3"], ["back"], ["tab"], ["get","html","@e1"], ["cookies","get"], ["network","requests"], ["find","role","button","click","--name","Submit"], and more. Run ["--help"] for the command list.',
|
|
734
200
|
inputSchema: {
|
|
735
201
|
type: 'object',
|
|
736
202
|
properties: {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
203
|
+
args: { type: 'array', items: { type: 'string' }, description: 'agent-browser argv, e.g. ["scroll", "down", "400"]' },
|
|
204
|
+
timeoutMs: { type: 'number', description: 'Timeout (default 60000)' },
|
|
205
|
+
...sessionProperty,
|
|
740
206
|
},
|
|
741
|
-
required: ['
|
|
742
|
-
},
|
|
743
|
-
async handler(args, ctx) {
|
|
744
|
-
try {
|
|
745
|
-
return jsonText(await cuaBrowser('cua_drag', args, ctx));
|
|
746
|
-
} catch (err) {
|
|
747
|
-
return errorResult(`CUA drag failed: ${err.message}`);
|
|
748
|
-
}
|
|
207
|
+
required: ['args'],
|
|
749
208
|
},
|
|
750
|
-
},
|
|
751
|
-
{
|
|
752
|
-
name: 'browser_cua_screenshot',
|
|
753
|
-
description: 'Capture the visible browser tab viewport.',
|
|
754
|
-
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
755
209
|
async handler(args, ctx) {
|
|
756
210
|
try {
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
{ type: 'image', data: capture.base64, mimeType: 'image/png' },
|
|
761
|
-
{ type: 'text', text: `Visible browser screenshot captured (${capture.bytes} bytes)${targetLine(capture)}` },
|
|
762
|
-
],
|
|
763
|
-
};
|
|
211
|
+
const { result } = await engine.passthrough(args, ctx);
|
|
212
|
+
const output = typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result ?? '');
|
|
213
|
+
return textResult(output.slice(0, 15_000) || 'ok');
|
|
764
214
|
} catch (err) {
|
|
765
|
-
return errorResult(`
|
|
215
|
+
return errorResult(`browser_cli failed: ${err.message}`);
|
|
766
216
|
}
|
|
767
217
|
},
|
|
768
218
|
},
|
|
769
219
|
{
|
|
770
|
-
name: '
|
|
771
|
-
description: '
|
|
772
|
-
inputSchema: { type: 'object', properties:
|
|
773
|
-
async handler(args, ctx) {
|
|
774
|
-
try {
|
|
775
|
-
return jsonText(await clipboardBrowser('clipboard_read_text', args, ctx));
|
|
776
|
-
} catch (err) {
|
|
777
|
-
return errorResult(`Clipboard read failed: ${err.message}`);
|
|
778
|
-
}
|
|
779
|
-
},
|
|
780
|
-
},
|
|
781
|
-
{
|
|
782
|
-
name: 'browser_clipboard_write_text',
|
|
783
|
-
description: 'Write plain text through the browser page clipboard API.',
|
|
784
|
-
inputSchema: {
|
|
785
|
-
type: 'object',
|
|
786
|
-
properties: { ...tabTargetProperties, text: { type: 'string' } },
|
|
787
|
-
required: ['text'],
|
|
788
|
-
},
|
|
789
|
-
async handler(args, ctx) {
|
|
790
|
-
try {
|
|
791
|
-
return jsonText(await clipboardBrowser('clipboard_write_text', args, ctx));
|
|
792
|
-
} catch (err) {
|
|
793
|
-
return errorResult(`Clipboard write failed: ${err.message}`);
|
|
794
|
-
}
|
|
795
|
-
},
|
|
796
|
-
},
|
|
797
|
-
{
|
|
798
|
-
name: 'browser_start_video',
|
|
799
|
-
description: 'Start recording the browser session to a local WebM video. Saved when browser_stop_video is called.',
|
|
800
|
-
inputSchema: {
|
|
801
|
-
type: 'object',
|
|
802
|
-
properties: {
|
|
803
|
-
...tabTargetProperties,
|
|
804
|
-
fps: { type: 'number', description: 'Recording frames per second (default: 12, max: 30)' },
|
|
805
|
-
session_id: { type: 'string', description: 'Deprecated alias for recording_id. Optional recording folder name under ~/.amalgm/browser-sessions' },
|
|
806
|
-
recording_id: { type: 'string', description: 'Optional recording folder name under ~/.amalgm/browser-sessions' },
|
|
807
|
-
},
|
|
808
|
-
},
|
|
220
|
+
name: 'browser_close',
|
|
221
|
+
description: 'Close the browser session. A fresh one launches on the next browser call.',
|
|
222
|
+
inputSchema: { type: 'object', properties: sessionProperty },
|
|
809
223
|
async handler(args, ctx) {
|
|
810
224
|
try {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
fps: args.fps,
|
|
814
|
-
sessionId: args.recording_id || args.session_id,
|
|
815
|
-
}, ctx);
|
|
816
|
-
return textResult(`Browser video recording started (${result.fps || 'default'} fps).${targetLine(result)}`);
|
|
225
|
+
await engine.close(args, ctx);
|
|
226
|
+
return textResult('Browser closed.');
|
|
817
227
|
} catch (err) {
|
|
818
|
-
return errorResult(`
|
|
228
|
+
return errorResult(`Close failed: ${err.message}`);
|
|
819
229
|
}
|
|
820
230
|
},
|
|
821
231
|
},
|
|
822
|
-
{
|
|
823
|
-
name: 'browser_stop_video',
|
|
824
|
-
description: 'Stop browser video recording and save it under ~/.amalgm/browser-sessions/.',
|
|
825
|
-
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
826
|
-
async handler(args, ctx) {
|
|
827
|
-
try {
|
|
828
|
-
const result = await stopVideoBrowser(args, ctx);
|
|
829
|
-
if (!result.path) return textResult(`Browser video recording stopped.${targetLine(result)}`);
|
|
830
|
-
const urlLine = result.url ? `\nOpen: ${result.url}` : '';
|
|
831
|
-
return {
|
|
832
|
-
content: [
|
|
833
|
-
{ type: 'text', text: `Browser video saved: ${result.path}\nBytes: ${result.bytes || 0}${urlLine}${targetLine(result)}` },
|
|
834
|
-
],
|
|
835
|
-
metadata: {
|
|
836
|
-
browserVideo: result.browserVideo || {
|
|
837
|
-
path: result.path,
|
|
838
|
-
relativePath: result.relativePath,
|
|
839
|
-
url: result.url,
|
|
840
|
-
bytes: result.bytes,
|
|
841
|
-
mimeType: result.mimeType || 'video/webm',
|
|
842
|
-
},
|
|
843
|
-
},
|
|
844
|
-
};
|
|
845
|
-
} catch (err) {
|
|
846
|
-
return errorResult(`Stop video failed: ${err.message}`);
|
|
847
|
-
}
|
|
848
|
-
},
|
|
849
|
-
},
|
|
850
|
-
{
|
|
851
|
-
name: 'browser_close',
|
|
852
|
-
description: 'Close the current or specified browser tab/surface.',
|
|
853
|
-
inputSchema: { type: 'object', properties: tabTargetProperties },
|
|
854
|
-
async handler(args, ctx) {
|
|
855
|
-
await closeBrowser(args, ctx);
|
|
856
|
-
return textResult('Browser closed. A new instance will launch on the next browser_* call.');
|
|
857
|
-
},
|
|
858
|
-
},
|
|
859
232
|
];
|