@remcp/runtime 0.2.131 → 0.2.133
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/CHANGELOG.md +16 -0
- package/README.md +3 -1
- package/package.json +1 -1
- package/src/extended/browser.mjs +91 -6
- package/src/extended/catalog.mjs +104 -14
- package/src/extended/desktop.mjs +98 -8
- package/src/extended/diagnostics.mjs +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.133 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- Explicit `computer_action type target=window` now keeps top-level window selectors separate from semantic element selectors; `window_id` is never rewritten into the UI-element `id` before `type_text`.
|
|
6
|
+
- Auto window fallback uses the same sanitized window-only selector shape, preventing stale UI/browser selectors from hijacking window-scoped text entry.
|
|
7
|
+
- Live GNOME/AT-SPI Zenity validation returned the exact Unicode payload after the accessibility edit path. Runtime tool names and schemas are unchanged from 0.2.132.
|
|
8
|
+
|
|
9
|
+
## 0.2.132 — 2026-10-02
|
|
10
|
+
|
|
11
|
+
- `computer_action` now exposes `type`, `scroll`, `drag`, and `target=window` in its MCP schema while keeping specialist tools available when the backend is already known.
|
|
12
|
+
- Auto `type` uses accessibility-only semantic editing before CDP and never falls through to unverified physical text input merely because a native selector failed.
|
|
13
|
+
- Browser DOM typing separates `browser_text` locator semantics from the `text` payload; legacy locator-in-`text` calls remain compatible when `value`/`text_value` carries the payload.
|
|
14
|
+
- `browser_action scroll` performs bounded page or nearest-scrollable-ancestor scrolling with the same delta/direction vocabulary as native `scroll`; `computer_action scroll` can route to it automatically.
|
|
15
|
+
- Action-specific schemas validate type targets/text, scroll deltas/directions, drag endpoints, and explicit backend/target compatibility before handler execution. Window targets require a window selector; UI/coordinate scroll and drag require target-appropriate fields; browser/OCR drag is rejected before dispatch.
|
|
16
|
+
- macOS `service action=list` now uses explicit launchd domains so `scope=user` reads `gui/<uid>` and `scope=system` reads `system`; the old legacy `launchctl list` path could not honor system scope.
|
|
17
|
+
- Public macOS release CI exercises the system launchd domain in addition to document/network backends. Runtime tool names remain unchanged at 83.
|
|
18
|
+
|
|
3
19
|
## 0.2.131 — 2026-10-02
|
|
4
20
|
|
|
5
21
|
- `network action=summary` is now bounded for Docker-heavy hosts: non-container interfaces are prioritized, at most eight interfaces and four addresses per interface are returned, and count/truncation metadata shows what was omitted.
|
package/README.md
CHANGED
|
@@ -60,7 +60,9 @@ If custom widgets are explicitly re-enabled, hosted discovery expands to **15 de
|
|
|
60
60
|
**5 app-only** UI helpers. Cached legacy clients can still call supported granular aliases without inflating
|
|
61
61
|
discovery for new clients.
|
|
62
62
|
|
|
63
|
-
`computer_snapshot` and `computer_action` keep OCR inside the compact high-level surface: when a local `tesseract` binary is available, snapshot can return bounded OCR text/boxes and click targeting can fall back through Accessibility → browser DOM/CDP → OCR → coordinates. OCR is optional and never adds another MCP tool or bundled OCR dependency.
|
|
63
|
+
`computer_snapshot` and `computer_action` keep OCR inside the compact high-level surface: when a local `tesseract` binary is available, snapshot can return bounded OCR text/boxes and click targeting can fall back through Accessibility → browser DOM/CDP → OCR → coordinates. `computer_action` also exposes backend-uncertain `type`, `scroll`, and `drag` plus explicit window targeting; it prefers semantic Accessibility, can route browser selectors through CDP, and keeps native coordinate/UI gestures as the fallback. When the backend is already known, the specialist UI/browser/type/scroll/drag/window tools keep their full controls. OCR is optional and never adds another MCP tool or bundled OCR dependency.
|
|
64
|
+
|
|
65
|
+
For browser typing, `browser_text` is the visible DOM locator while `text` is the Unicode payload. `browser_action(action="scroll")` accepts page/element deltas or direction/wheel steps and scrolls the page or nearest scrollable ancestor; `scroll_into_view` remains the separate semantic reveal operation.
|
|
64
66
|
|
|
65
67
|
For frontend QA, use semantic data to operate the page and rendered pixels to verify it: `browser_action(action="set_viewport")` sets an exact responsive-test viewport, `browser_wait` waits for the real application state, and `browser_snapshot(include_screenshot=true, selector=...)` recenters the component, returns its bounds, and attaches the real CDP-rendered viewport PNG. This intentionally treats screenshots as the visual oracle without making screenshot-driven coordinate guessing the primary control path.
|
|
66
68
|
|
package/package.json
CHANGED
package/src/extended/browser.mjs
CHANGED
|
@@ -1045,6 +1045,47 @@ export async function browserFind(args) {
|
|
|
1045
1045
|
});
|
|
1046
1046
|
}
|
|
1047
1047
|
|
|
1048
|
+
const PAGE_SCROLL_ACTION = `function(selector, needle, deltaX, deltaY) {
|
|
1049
|
+
let target = selector ? document.querySelector(selector) : null;
|
|
1050
|
+
if (!target && needle) {
|
|
1051
|
+
const lower = String(needle).toLowerCase();
|
|
1052
|
+
target = Array.from(document.querySelectorAll('*')).find(node =>
|
|
1053
|
+
String(node.innerText || node.textContent || '').trim().toLowerCase().includes(lower)
|
|
1054
|
+
) || null;
|
|
1055
|
+
}
|
|
1056
|
+
if ((selector || needle) && !target) throw new Error('Element not found');
|
|
1057
|
+
|
|
1058
|
+
let scroller = document.scrollingElement || document.documentElement;
|
|
1059
|
+
if (target) {
|
|
1060
|
+
let current = target;
|
|
1061
|
+
while (current && current !== document.documentElement) {
|
|
1062
|
+
const style = getComputedStyle(current);
|
|
1063
|
+
const scrollX = /(auto|scroll|overlay)/.test(String(style.overflowX || '')) && current.scrollWidth > current.clientWidth;
|
|
1064
|
+
const scrollY = /(auto|scroll|overlay)/.test(String(style.overflowY || '')) && current.scrollHeight > current.clientHeight;
|
|
1065
|
+
if (scrollX || scrollY) {
|
|
1066
|
+
scroller = current;
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
current = current.parentElement;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const beforeX = Number(scroller.scrollLeft || 0);
|
|
1074
|
+
const beforeY = Number(scroller.scrollTop || 0);
|
|
1075
|
+
scroller.scrollLeft = beforeX + Number(deltaX || 0);
|
|
1076
|
+
scroller.scrollTop = beforeY + Number(deltaY || 0);
|
|
1077
|
+
return {
|
|
1078
|
+
target: target ? target.tagName.toLowerCase() : 'page',
|
|
1079
|
+
scroller: scroller === document.scrollingElement || scroller === document.documentElement ? 'page' : scroller.tagName.toLowerCase(),
|
|
1080
|
+
before_x: beforeX,
|
|
1081
|
+
before_y: beforeY,
|
|
1082
|
+
after_x: Number(scroller.scrollLeft || 0),
|
|
1083
|
+
after_y: Number(scroller.scrollTop || 0),
|
|
1084
|
+
delta_x: Number(deltaX || 0),
|
|
1085
|
+
delta_y: Number(deltaY || 0),
|
|
1086
|
+
};
|
|
1087
|
+
}`;
|
|
1088
|
+
|
|
1048
1089
|
const PAGE_ELEMENT_ACTION = `function(selector, needle, action, value) {
|
|
1049
1090
|
let el = selector ? document.querySelector(selector) : null;
|
|
1050
1091
|
if (!el && needle) {
|
|
@@ -1081,14 +1122,46 @@ const PAGE_ELEMENT_ACTION = `function(selector, needle, action, value) {
|
|
|
1081
1122
|
};
|
|
1082
1123
|
}`;
|
|
1083
1124
|
|
|
1125
|
+
export function browserActionLocatorText(args = {}, action = '') {
|
|
1126
|
+
const explicit = optionalString(args.browser_text || args.match_text);
|
|
1127
|
+
if (explicit) return explicit;
|
|
1128
|
+
if (action === 'type') {
|
|
1129
|
+
// Legacy type calls used text as the locator and value/text_value as the payload.
|
|
1130
|
+
if (!optionalString(args.selector) && (args.value != null || args.text_value != null)) return optionalString(args.text);
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
return optionalString(args.text);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1084
1136
|
export function browserActionInputValue(args = {}, action = '') {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1137
|
+
if (action === 'type') {
|
|
1138
|
+
if (!optionalString(args.selector) && !optionalString(args.browser_text || args.match_text) && (args.value != null || args.text_value != null)) {
|
|
1139
|
+
return String(args.text_value ?? args.value ?? '');
|
|
1140
|
+
}
|
|
1141
|
+
return String(args.text ?? args.text_value ?? args.value ?? '');
|
|
1142
|
+
}
|
|
1143
|
+
return String(args.value ?? args.text_value ?? args.text ?? '');
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
export function browserScrollDeltas(args = {}) {
|
|
1147
|
+
const direction = optionalString(args.direction);
|
|
1148
|
+
const hasDelta = [args.delta_x,args.delta_y,args.delta].some(value => value != null && Number.isFinite(Number(value)));
|
|
1149
|
+
if (!direction && !hasDelta) return null;
|
|
1150
|
+
const times = clamp(args.wheel_times, 1, 1, 50);
|
|
1151
|
+
let deltaX = Number(args.delta_x || 0);
|
|
1152
|
+
let deltaY = Number(args.delta_y ?? args.delta ?? 0);
|
|
1153
|
+
if (direction) {
|
|
1154
|
+
const normalized = requireEnum(direction, 'direction', ['up','down','left','right']);
|
|
1155
|
+
if (normalized === 'up') deltaY = -120 * times;
|
|
1156
|
+
if (normalized === 'down') deltaY = 120 * times;
|
|
1157
|
+
if (normalized === 'left') deltaX = -120 * times;
|
|
1158
|
+
if (normalized === 'right') deltaX = 120 * times;
|
|
1159
|
+
}
|
|
1160
|
+
return { delta_x:deltaX, delta_y:deltaY };
|
|
1088
1161
|
}
|
|
1089
1162
|
|
|
1090
1163
|
export async function browserAction(args) {
|
|
1091
|
-
const action = requireEnum(args.action, 'action', ['click', 'focus', 'type', 'set_value', 'select', 'scroll_into_view', 'upload', 'press', 'set_viewport']);
|
|
1164
|
+
const action = requireEnum(args.action, 'action', ['click', 'focus', 'type', 'set_value', 'select', 'scroll', 'scroll_into_view', 'upload', 'press', 'set_viewport']);
|
|
1092
1165
|
return withTarget(args, async (session, target) => {
|
|
1093
1166
|
if (action === 'set_viewport') {
|
|
1094
1167
|
const width = clamp(args.width, 1280, 200, 8192);
|
|
@@ -1126,11 +1199,22 @@ export async function browserAction(args) {
|
|
|
1126
1199
|
return jsonResult({ target_id: target.id, action, key });
|
|
1127
1200
|
}
|
|
1128
1201
|
const selector = optionalString(args.selector);
|
|
1129
|
-
const needle =
|
|
1130
|
-
if (!selector && !needle) throw new Error('selector or text is required');
|
|
1202
|
+
const needle = browserActionLocatorText(args, action);
|
|
1131
1203
|
const value = browserActionInputValue(args, action);
|
|
1132
1204
|
const option = String(args.option ?? value);
|
|
1133
1205
|
|
|
1206
|
+
if (action === 'scroll') {
|
|
1207
|
+
const deltas = browserScrollDeltas(args);
|
|
1208
|
+
if (!deltas) throw new Error('browser_action scroll requires direction or a delta');
|
|
1209
|
+
return jsonResult({
|
|
1210
|
+
target_id: target.id,
|
|
1211
|
+
action,
|
|
1212
|
+
result: await callPageFunction(session, PAGE_SCROLL_ACTION, [selector, needle, deltas.delta_x, deltas.delta_y]),
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (!selector && !needle) throw new Error('selector or browser_text/text locator is required');
|
|
1217
|
+
|
|
1134
1218
|
if (action === 'click') {
|
|
1135
1219
|
const rect = await callPageFunction(session, PAGE_ELEMENT_ACTION, [selector, needle, 'click', '']);
|
|
1136
1220
|
const x = Number(rect.x) + Number(rect.width) / 2;
|
|
@@ -1143,6 +1227,7 @@ export async function browserAction(args) {
|
|
|
1143
1227
|
|
|
1144
1228
|
if (action === 'type') {
|
|
1145
1229
|
const focused = await callPageFunction(session, PAGE_ELEMENT_ACTION, [selector, needle, 'type', '']);
|
|
1230
|
+
if (args.clear === true) await callPageFunction(session, PAGE_ELEMENT_ACTION, [selector, needle, 'set_value', '']);
|
|
1146
1231
|
await session.send('Input.insertText', { text:value });
|
|
1147
1232
|
const current = await callPageFunction(session, PAGE_ELEMENT_ACTION, [selector, needle, 'read_value', '']);
|
|
1148
1233
|
return jsonResult({ target_id:target.id, action, result:{ ...focused, value:current.value } });
|
package/src/extended/catalog.mjs
CHANGED
|
@@ -30,7 +30,7 @@ const o = (properties = {}, required = []) => ({ type: 'object', properties, ...
|
|
|
30
30
|
|
|
31
31
|
const AI_TOOL_DESCRIPTIONS = Object.freeze({
|
|
32
32
|
computer_snapshot: 'Use this for the first look at an unfamiliar desktop state when you need one compact bundle of windows, semantic UI, displays, clipboard metadata and optional pixels/browser/OCR. Prefer it over repeated screenshots; narrow later with ui_find, browser_find or screenshot_region.',
|
|
33
|
-
computer_action: 'Use this only when one semantic click/
|
|
33
|
+
computer_action: 'Use this only when one semantic click/type/invoke/select/focus/scroll/drag action must work across an uncertain target backend and may need Accessibility → browser DOM/CDP → OCR → coordinate fallback, or when multi_select/multi_edit avoids many identical native UI calls. When the backend is already known, prefer the specialist tool (ui_action, browser_action, type_text, drag_drop, scroll or window_action) for its full controls. Keep keyboard, clipboard, launch/open/reveal/notification and raw pointer gestures in their dedicated tools.',
|
|
34
34
|
list_windows: 'Use this to discover top-level desktop windows or obtain a stable window id/PID/bounds before window_action, screenshot_region or targeted input. Use ui_snapshot instead when you need controls inside a window; prefer list_windows over screenshot-based window guessing.',
|
|
35
35
|
window_action: 'Use this to focus, minimize, maximize, restore, move, resize or close one known top-level window. Select it by id/PID/app/title from list_windows. Use ui_action for controls inside the window; do not use pointer coordinates for window management when this semantic tool can express the action.',
|
|
36
36
|
launch_app: 'Use this to start a desktop application by executable/path or friendly application name without a shell. Prefer it over start_process for GUI application launch. If the user wants to open a specific file or directory in its associated application, use open_path instead; use start_process for terminal commands, build tools and shell pipelines.',
|
|
@@ -247,8 +247,18 @@ function requiredAny(...groups) {
|
|
|
247
247
|
function applySchemaRules(name, schema) {
|
|
248
248
|
const next = structuredClone(schema);
|
|
249
249
|
if (name === 'computer_action') {
|
|
250
|
-
const
|
|
250
|
+
const nativeTarget = requiredAny(['id'], ['label'], ['name'], ['role'], ['automation_id']);
|
|
251
|
+
const browserElementTarget = requiredAny(['selector'], ['browser_text'], ['name']);
|
|
252
|
+
const windowTarget = requiredAny(['window_id'], ['pid'], ['app'], ['window_title'], ['title']);
|
|
253
|
+
const semanticTarget = [...nativeTarget, ...browserElementTarget];
|
|
251
254
|
next.allOf = [
|
|
255
|
+
{
|
|
256
|
+
if:{ properties:{ target:{ const:'window' } }, required:['target'] },
|
|
257
|
+
then:{
|
|
258
|
+
properties:{ action:{ enum:['type','focus','scroll','drag'] } },
|
|
259
|
+
anyOf:windowTarget,
|
|
260
|
+
},
|
|
261
|
+
},
|
|
252
262
|
{
|
|
253
263
|
if:{ properties:{ action:{ const:'click' } }, required:['action'] },
|
|
254
264
|
then:{ anyOf:[...semanticTarget, { required:['ocr_text'] }, { required:['x','y'] }] },
|
|
@@ -259,7 +269,60 @@ function applySchemaRules(name, schema) {
|
|
|
259
269
|
},
|
|
260
270
|
{
|
|
261
271
|
if:{ properties:{ action:{ const:'focus' } }, required:['action'] },
|
|
262
|
-
then:{ anyOf:[...semanticTarget, ...requiredAny(['pid'], ['app'], ['window_title'], ['title'])] },
|
|
272
|
+
then:{ anyOf:[...semanticTarget, ...requiredAny(['window_id'], ['pid'], ['app'], ['window_title'], ['title'])] },
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
if:{ properties:{ action:{ const:'type' } }, required:['action'] },
|
|
276
|
+
then:{
|
|
277
|
+
required:['text'],
|
|
278
|
+
properties:{ target:{ enum:['auto','ui','ui_element','browser','window'] } },
|
|
279
|
+
allOf:[
|
|
280
|
+
{ anyOf:[...semanticTarget, ...windowTarget] },
|
|
281
|
+
{
|
|
282
|
+
if:{ properties:{ target:{ enum:['ui','ui_element'] } }, required:['target'] },
|
|
283
|
+
then:{ anyOf:nativeTarget },
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
if:{ properties:{ target:{ const:'browser' } }, required:['target'] },
|
|
287
|
+
then:{ anyOf:browserElementTarget },
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
if:{ properties:{ action:{ const:'scroll' } }, required:['action'] },
|
|
294
|
+
then:{
|
|
295
|
+
properties:{ target:{ enum:['auto','ui','ui_element','browser','coordinates','window'] } },
|
|
296
|
+
allOf:[
|
|
297
|
+
{ anyOf:requiredAny(['delta_x'], ['delta_y'], ['delta'], ['direction']) },
|
|
298
|
+
{
|
|
299
|
+
if:{ properties:{ target:{ enum:['ui','ui_element'] } }, required:['target'] },
|
|
300
|
+
then:{ anyOf:nativeTarget },
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
if:{ properties:{ target:{ const:'coordinates' } }, required:['target'] },
|
|
304
|
+
then:{ required:['x','y'] },
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
if:{ properties:{ action:{ const:'drag' } }, required:['action'] },
|
|
311
|
+
then:{
|
|
312
|
+
properties:{ target:{ enum:['auto','ui','ui_element','coordinates','window'] } },
|
|
313
|
+
allOf:[
|
|
314
|
+
{ anyOf:requiredAny(['from_id'], ['from_x','from_y']) },
|
|
315
|
+
{ anyOf:requiredAny(['to_id'], ['to_x','to_y']) },
|
|
316
|
+
{
|
|
317
|
+
if:{ properties:{ target:{ enum:['ui','ui_element'] } }, required:['target'] },
|
|
318
|
+
then:{ required:['from_id','to_id'] },
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
if:{ properties:{ target:{ const:'coordinates' } }, required:['target'] },
|
|
322
|
+
then:{ required:['from_x','from_y','to_x','to_y'] },
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
},
|
|
263
326
|
},
|
|
264
327
|
{
|
|
265
328
|
if:{ properties:{ action:{ enum:['set_value','set_range_value'] } }, required:['action'] },
|
|
@@ -357,8 +420,19 @@ function applySchemaRules(name, schema) {
|
|
|
357
420
|
} else if (name === 'browser_action') {
|
|
358
421
|
next.allOf = [
|
|
359
422
|
{
|
|
360
|
-
if:{ properties:{ action:{ enum:['click','focus','
|
|
361
|
-
then:{ anyOf:requiredAny(['selector'], ['text']) },
|
|
423
|
+
if:{ properties:{ action:{ enum:['click','focus','set_value','select','scroll_into_view'] } }, required:['action'] },
|
|
424
|
+
then:{ anyOf:requiredAny(['selector'], ['text'], ['browser_text']) },
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
if:{ properties:{ action:{ const:'type' } }, required:['action'] },
|
|
428
|
+
then:{
|
|
429
|
+
required:['text'],
|
|
430
|
+
anyOf:requiredAny(['selector'], ['browser_text'], ['value'], ['text_value']),
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
if:{ properties:{ action:{ const:'scroll' } }, required:['action'] },
|
|
435
|
+
then:{ anyOf:requiredAny(['delta_x'], ['delta_y'], ['delta'], ['direction']) },
|
|
362
436
|
},
|
|
363
437
|
{
|
|
364
438
|
if:{ properties:{ action:{ const:'upload' } }, required:['action'] },
|
|
@@ -477,22 +551,38 @@ export const extendedToolDefinitions = [
|
|
|
477
551
|
'Perform one semantic cross-backend action when the target may require Accessibility → browser DOM/CDP → OCR → coordinate fallback.',
|
|
478
552
|
o({
|
|
479
553
|
action:e([
|
|
480
|
-
'click','invoke','focus','
|
|
554
|
+
'click','type','invoke','focus','select','scroll','drag','set_value','toggle','expand','collapse',
|
|
481
555
|
'scroll_into_view','set_range_value','add_to_selection','remove_from_selection',
|
|
482
556
|
'multi_select','multi_edit',
|
|
483
557
|
]),
|
|
484
558
|
target:e(
|
|
485
|
-
['auto','ui','ui_element','browser','ocr','coordinates'],
|
|
486
|
-
'Optional target type. auto
|
|
559
|
+
['auto','ui','ui_element','browser','ocr','coordinates','window'],
|
|
560
|
+
'Optional target type. auto prefers native accessibility, then browser DOM/CDP, then OCR/coordinates where supported. Use window for explicit top-level window focus/text/scroll targeting.',
|
|
487
561
|
),
|
|
488
562
|
...uiSelector,
|
|
489
|
-
title:s('Optional browser page title substring when target=
|
|
490
|
-
|
|
563
|
+
title:s('Optional browser page title substring or top-level window title when target=window.'),
|
|
564
|
+
window_id:s('Top-level window id returned by list_windows when target=window.'),
|
|
565
|
+
text:s('Unicode text to enter for action=type, or text used by multi_edit.'),
|
|
566
|
+
clear:b('For action=type, clear the current semantic/browser value before inserting text when supported.'),
|
|
491
567
|
value:s('New value for set_value, set_range_value, or select.'),
|
|
492
|
-
x:n('Virtual-desktop X coordinate used
|
|
493
|
-
y:n('Virtual-desktop Y coordinate used
|
|
568
|
+
x:n('Virtual-desktop X coordinate used by click/scroll coordinate targeting.'),
|
|
569
|
+
y:n('Virtual-desktop Y coordinate used by click/scroll coordinate targeting.'),
|
|
494
570
|
selector:s('CSS selector when the fallback target is a browser element.'),
|
|
495
|
-
browser_text:s('Visible browser text used to locate a DOM
|
|
571
|
+
browser_text:s('Visible browser text used only to locate a DOM target; for action=type, text remains the value to enter.'),
|
|
572
|
+
delta_x:n('Horizontal scroll delta; positive means right.'),
|
|
573
|
+
delta_y:n('Vertical scroll delta; positive means down.'),
|
|
574
|
+
delta:n('Alias for delta_y.'),
|
|
575
|
+
direction:e(['up','down','left','right']),
|
|
576
|
+
wheel_times:n('Number of 120-unit wheel steps for direction; 1..50.'),
|
|
577
|
+
from_x:n('Drag source X coordinate.'),
|
|
578
|
+
from_y:n('Drag source Y coordinate.'),
|
|
579
|
+
to_x:n('Drag destination X coordinate.'),
|
|
580
|
+
to_y:n('Drag destination Y coordinate.'),
|
|
581
|
+
from_id:s('Source semantic UI element id for action=drag.'),
|
|
582
|
+
to_id:s('Destination semantic UI element id for action=drag.'),
|
|
583
|
+
button:e(['left','right','middle']),
|
|
584
|
+
hold_ms:n('Delay after pointer down before moving during action=drag.'),
|
|
585
|
+
duration_ms:n('Drag movement duration.'),
|
|
496
586
|
ocr_text:s('Visible text to find in rendered pixels for the OCR click fallback.'),
|
|
497
587
|
ocr_region:{
|
|
498
588
|
type:'array',
|
|
@@ -556,7 +646,7 @@ export const extendedToolDefinitions = [
|
|
|
556
646
|
define('browser_navigate', 'Browser navigate', 'Open a new debuggable tab or navigate a selected browser page by URL, history back/forward, or reload through Chrome DevTools Protocol.', o({ ...browserTarget, action:e(['url','new_tab','back','forward','reload'], 'Default url when url is provided, otherwise reload. new_tab creates the first page target when needed.'), url:s('Destination URL for action=url or action=new_tab.'), wait:b('Wait for the page load event; default true.'), ignore_cache:b('Reload without cache when action=reload.') }), openMutatingNonDestructive, browserHandlers.browser_navigate, ['browser_cdp']),
|
|
557
647
|
define('browser_snapshot', 'Browser snapshot', 'Return the selected browser page accessibility tree through Chrome DevTools Protocol and, when requested, attach a real rendered viewport PNG for visual verification. A selector is temporarily recentered for capture, its exact viewport bounds are returned, and the prior scroll position is restored before return.', o({ ...browserTarget, max_nodes:n('Maximum AX nodes; default 1500.'), include_screenshot:b('Attach a real CDP-rendered viewport PNG screenshot; default false.'), selector:s('When include_screenshot=true, center this CSS-selected element before capture and report its viewport bounds.') }), readOnlyLive, browserHandlers.browser_snapshot, ['browser_cdp']),
|
|
558
648
|
define('browser_find', 'Find browser element', 'Find visible DOM elements by CSS selector, text, or ARIA role and return reusable selectors plus text, value, and bounding boxes.', o({ ...browserTarget, selector:s('CSS selector.'), text:s('Visible text substring.'), role:s('ARIA role.'), limit:n() }), readOnlyLive, browserHandlers.browser_find, ['browser_cdp']),
|
|
559
|
-
define('browser_action', 'Browser element action', 'Click, focus, type/set a value, select, scroll
|
|
649
|
+
define('browser_action', 'Browser element action', 'Click, focus, type/set a value, select, scroll a page or scrollable element, scroll an element into view, upload files, press a key, or set an exact responsive-test viewport in a debuggable browser page using DOM/CDP semantics.', o({ ...browserTarget, action:e(['click','focus','type','set_value','select','scroll','scroll_into_view','upload','press','set_viewport']), selector:s('CSS selector for the target element; prefer the reusable selector returned by browser_find.'), browser_text:s('Visible text used only to locate a DOM element; use this instead of overloading text for action=type.'), text:s('For action=type, Unicode text to insert. For non-type element actions without selector/browser_text, visible text may identify the target.'), clear:b('For action=type, clear the current value before inserting text.'), value:s('Value for action=set_value; also accepted as a legacy type payload when text is used as the locator.'), text_value:s('Compatibility alias for the type/set_value text value.'), option:s(), delta_x:n('Horizontal scroll delta; positive means right.'), delta_y:n('Vertical scroll delta; positive means down.'), delta:n('Alias for delta_y.'), direction:e(['up','down','left','right']), wheel_times:n('Number of 120-unit wheel steps for direction; 1..50.'), path:s(), paths:{type:'array',items:{type:'string'}}, key:s(), width:n('CSS viewport width for set_viewport.'), height:n('CSS viewport height for set_viewport.'), device_scale_factor:n('Device scale factor for set_viewport; default 1.'), mobile:b('Enable mobile emulation for set_viewport.') }, ['action']), openMutating, browserHandlers.browser_action, ['browser_cdp']),
|
|
560
650
|
define('browser_wait', 'Wait for browser', 'Wait for DOM state, page text, URL, completed loading, a navigation away from the current URL, or a short network-idle period in a debuggable browser page. Use browser_evaluate separately for JavaScript predicates or page-specific inspection.', o({ ...browserTarget, condition:e(['selector','text','url_contains','load','navigation','network_idle']), selector:s(), text:s(), value:s(), poll_ms:n(), idle_ms:n('Required zero-in-flight network quiet window for network_idle; default 500 ms.') }), readOnlyLive, browserHandlers.browser_wait, ['browser_cdp']),
|
|
561
651
|
define('browser_evaluate', 'Evaluate browser JavaScript', 'Evaluate JavaScript in a selected debuggable browser page and return its serializable value. This is a powerful escape hatch for page-specific automation.', o({ ...browserTarget, expression:s('JavaScript expression.'), await_promise:b('Await a returned Promise; default true.') }, ['expression']), openMutating, browserHandlers.browser_evaluate, ['browser_evaluate']),
|
|
562
652
|
|
package/src/extended/desktop.mjs
CHANGED
|
@@ -1263,6 +1263,7 @@ function browserComputerAction(args, action) {
|
|
|
1263
1263
|
set_value:'set_value',
|
|
1264
1264
|
select:'select',
|
|
1265
1265
|
type:'type',
|
|
1266
|
+
scroll:'scroll',
|
|
1266
1267
|
}[action];
|
|
1267
1268
|
if (!mapped) throw new Error(`Browser target does not support computer_action action: ${action}`);
|
|
1268
1269
|
const visibleText = optionalString(args.browser_text)
|
|
@@ -1276,13 +1277,38 @@ function browserComputerAction(args, action) {
|
|
|
1276
1277
|
timeout_ms: args.timeout_ms,
|
|
1277
1278
|
action: mapped,
|
|
1278
1279
|
selector: args.selector,
|
|
1279
|
-
...(visibleText ? {
|
|
1280
|
+
...(visibleText ? { browser_text: visibleText } : {}),
|
|
1280
1281
|
...(args.value != null ? { value: args.value } : {}),
|
|
1281
1282
|
...(args.option != null ? { option: args.option } : {}),
|
|
1282
|
-
...(action === 'type' && args.text != null ? {
|
|
1283
|
+
...(action === 'type' && args.text != null ? { text: args.text, clear:args.clear === true } : {}),
|
|
1284
|
+
...(action === 'scroll' ? {
|
|
1285
|
+
delta_x:args.delta_x,
|
|
1286
|
+
delta_y:args.delta_y,
|
|
1287
|
+
delta:args.delta,
|
|
1288
|
+
direction:args.direction,
|
|
1289
|
+
wheel_times:args.wheel_times,
|
|
1290
|
+
} : {}),
|
|
1283
1291
|
});
|
|
1284
1292
|
}
|
|
1285
1293
|
|
|
1294
|
+
export function computerActionWindowTextArgs(args = {}) {
|
|
1295
|
+
const {
|
|
1296
|
+
id: _id,
|
|
1297
|
+
label: _label,
|
|
1298
|
+
name: _name,
|
|
1299
|
+
role: _role,
|
|
1300
|
+
automation_id: _automationId,
|
|
1301
|
+
automationId: _automationIdAlias,
|
|
1302
|
+
selector: _selector,
|
|
1303
|
+
browser_text: _browserText,
|
|
1304
|
+
...windowArgs
|
|
1305
|
+
} = args;
|
|
1306
|
+
return {
|
|
1307
|
+
...windowArgs,
|
|
1308
|
+
...(args.window_title && !args.title ? { title:args.window_title } : {}),
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1286
1312
|
export async function computerAction(args = {}) {
|
|
1287
1313
|
const legacy = ['ui','pointer','window','type','keyboard','scroll','drag','clipboard','launch_app','open_path','reveal_path','notification'];
|
|
1288
1314
|
const universal = [
|
|
@@ -1362,21 +1388,85 @@ export async function computerAction(args = {}) {
|
|
|
1362
1388
|
if (action === 'ui') return uiAction(nested);
|
|
1363
1389
|
if (action === 'pointer') return pointer(nested);
|
|
1364
1390
|
if (action === 'window') return windowAction(nested);
|
|
1365
|
-
|
|
1391
|
+
|
|
1392
|
+
const windowTarget = () => ({
|
|
1393
|
+
...args,
|
|
1394
|
+
...(args.window_id ? { id:args.window_id } : {}),
|
|
1395
|
+
...(args.window_title && !args.title ? { title:args.window_title } : {}),
|
|
1396
|
+
});
|
|
1397
|
+
const windowTextTarget = () => computerActionWindowTextArgs(args);
|
|
1398
|
+
|
|
1399
|
+
if (action === 'type') {
|
|
1400
|
+
if (target === 'browser') return browserComputerAction(args, 'type');
|
|
1401
|
+
if (target === 'ocr' || target === 'coordinates') {
|
|
1402
|
+
throw new Error('computer_action type requires a native UI, browser, or window target');
|
|
1403
|
+
}
|
|
1404
|
+
if (target === 'window') return typeText(windowTextTarget());
|
|
1405
|
+
if (!automatic) return typeText({ ...args, method:args.method || 'accessibility' });
|
|
1406
|
+
|
|
1407
|
+
const failures = [];
|
|
1408
|
+
if (hasUi) {
|
|
1409
|
+
try { return await typeText({ ...args, method:'accessibility' }); }
|
|
1410
|
+
catch (error) { failures.push(`accessibility: ${String(error?.message || error).slice(0, 300)}`); }
|
|
1411
|
+
}
|
|
1412
|
+
if (hasBrowser) {
|
|
1413
|
+
if (await browserCapabilityAvailable(args.endpoint, 300)) {
|
|
1414
|
+
try { return await browserComputerAction(args, 'type'); }
|
|
1415
|
+
catch (error) { failures.push(`browser DOM: ${String(error?.message || error).slice(0, 300)}`); }
|
|
1416
|
+
} else {
|
|
1417
|
+
failures.push('browser DOM: no local CDP endpoint is available');
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
if (args.window_id || args.pid != null || args.app || args.window_title || args.title) {
|
|
1421
|
+
try { return await typeText(windowTextTarget()); }
|
|
1422
|
+
catch (error) { failures.push(`window: ${String(error?.message || error).slice(0, 300)}`); }
|
|
1423
|
+
}
|
|
1424
|
+
if (failures.length) throw new Error(`computer_action type targeting failed: ${failures.join('; ')}`);
|
|
1425
|
+
throw new Error('computer_action type requires a native UI, browser, or window target');
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1366
1428
|
if (action === 'keyboard') {
|
|
1367
1429
|
if (target === 'browser' && args.key && !args.shortcut && !Array.isArray(args.keys)) {
|
|
1368
1430
|
return browserAction({ ...args, action:'press' });
|
|
1369
1431
|
}
|
|
1370
1432
|
return keyboard(args);
|
|
1371
1433
|
}
|
|
1434
|
+
|
|
1372
1435
|
if (action === 'scroll') {
|
|
1373
|
-
if (target === 'browser'
|
|
1374
|
-
|
|
1436
|
+
if (target === 'browser') return browserComputerAction(args, 'scroll');
|
|
1437
|
+
if (target === 'ocr') throw new Error('computer_action scroll does not support OCR targets; use browser/UI/window/coordinates');
|
|
1438
|
+
|
|
1439
|
+
if (automatic) {
|
|
1440
|
+
let uiError = null;
|
|
1441
|
+
if (hasUi) {
|
|
1442
|
+
try { return await scroll(args); }
|
|
1443
|
+
catch (error) { uiError = error; }
|
|
1444
|
+
}
|
|
1445
|
+
if (hasBrowser) {
|
|
1446
|
+
if (await browserCapabilityAvailable(args.endpoint, 300)) return browserComputerAction(args, 'scroll');
|
|
1447
|
+
if (uiError) throw new Error(`computer_action scroll targeting failed: accessibility: ${String(uiError?.message || uiError).slice(0, 300)}; browser DOM: no local CDP endpoint is available`);
|
|
1448
|
+
throw new Error('computer_action scroll browser target is unavailable because no local CDP endpoint is available');
|
|
1449
|
+
}
|
|
1450
|
+
if (args.window_id || args.pid != null || args.app || args.window_title || args.title) {
|
|
1451
|
+
await windowAction({ ...windowTarget(), action:'focus' });
|
|
1452
|
+
}
|
|
1453
|
+
return scroll(args);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
if (target === 'window') {
|
|
1457
|
+
await windowAction({ ...windowTarget(), action:'focus' });
|
|
1458
|
+
return scroll(args);
|
|
1375
1459
|
}
|
|
1376
|
-
if (args.id || args.name || args.role || args.automation_id) await uiAction({ ...args, action:'focus' }).catch(() => null);
|
|
1377
1460
|
return scroll(args);
|
|
1378
1461
|
}
|
|
1379
|
-
|
|
1462
|
+
|
|
1463
|
+
if (action === 'drag') {
|
|
1464
|
+
if (target === 'browser' || target === 'ocr') {
|
|
1465
|
+
throw new Error('computer_action drag currently requires native UI element ids or desktop coordinates; browser/OCR drag is not advertised');
|
|
1466
|
+
}
|
|
1467
|
+
if (target === 'window') await windowAction({ ...windowTarget(), action:'focus' });
|
|
1468
|
+
return dragDrop(args);
|
|
1469
|
+
}
|
|
1380
1470
|
if (action === 'clipboard') return clipboard(nested);
|
|
1381
1471
|
if (action === 'launch_app') return launchApp(args);
|
|
1382
1472
|
if (action === 'open_path') return openPath(args);
|
|
@@ -1423,7 +1513,7 @@ export async function computerAction(args = {}) {
|
|
|
1423
1513
|
}
|
|
1424
1514
|
return uiAction({ ...args, action:'focus' });
|
|
1425
1515
|
}
|
|
1426
|
-
return windowAction({ ...
|
|
1516
|
+
return windowAction({ ...windowTarget(), action:'focus' });
|
|
1427
1517
|
}
|
|
1428
1518
|
|
|
1429
1519
|
if (['minimize','maximize','restore','move_resize','resize','close'].includes(action)) {
|
|
@@ -32,6 +32,11 @@ function durationSpec(value) {
|
|
|
32
32
|
return raw;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export function macosLaunchctlDomain(scope = 'user', uid = process.getuid?.() ?? 0) {
|
|
36
|
+
const normalized = requireEnum(scope, 'scope', ['user','system']);
|
|
37
|
+
return normalized === 'system' ? 'system' : `gui/${uid}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
export async function serviceTool(args) {
|
|
36
41
|
const action = requireEnum(args.action || 'list', 'action', ['list','status','start','stop','restart']);
|
|
37
42
|
const name = optionalString(args.name);
|
|
@@ -55,8 +60,8 @@ export async function serviceTool(args) {
|
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
if (process.platform === 'darwin') {
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
const domain = macosLaunchctlDomain(scope);
|
|
64
|
+
if (action === 'list') return text((await runFile('/bin/launchctl', ['print', domain], { label: `launchctl print ${domain}` })).stdout);
|
|
60
65
|
const target = `${domain}/${name}`;
|
|
61
66
|
if (action === 'status') {
|
|
62
67
|
const result = await runFile('/bin/launchctl', ['print', target], { label: 'launchctl print', allowFailure: true });
|