@remcp/runtime 0.2.78 → 0.2.80
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 +12 -4
- package/package.json +3 -3
- package/src/extended/catalog.mjs +3 -3
- package/src/extended/desktop-linux.mjs +78 -7
- package/src/extended/desktop.mjs +38 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
## 0.2.
|
|
5
|
+
## 0.2.80 — 2026-09-21
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
7
|
+
- Wayland `type_text` can replace text in an inactive explicitly targeted native window without compositor focus when exactly one editable accessibility control is present.
|
|
8
|
+
- Ambiguous or missing editables are never guessed; key/clipboard/Enter fallbacks retain verified window-focus requirements.
|
|
9
|
+
- Tool descriptions now explain the semantic window-target path so agents do not request unnecessary focus before accessibility replacement.
|
|
10
|
+
- Lockstep patch release with the ReMCP client/server at `0.2.80`.
|
|
11
|
+
|
|
12
|
+
## 0.2.79 — 2026-09-21
|
|
13
|
+
|
|
14
|
+
- Explicit Wayland keyboard and key-by-key text input now focuses and verifies the requested target window before dispatch, with bounded direct window cycling and verified fallbacks.
|
|
15
|
+
- Native Wayland cursor telemetry fails closed instead of trusting stale XWayland `xdotool` coordinates; `computer_snapshot` keeps that capture non-fatal and structured.
|
|
16
|
+
- Regression coverage is headless-safe while still enforcing the snapshot normalization/error contract.
|
|
17
|
+
- Lockstep patch release with the ReMCP client/server at `0.2.79`.
|
|
10
18
|
|
|
11
19
|
## 0.2.77 — 2026-09-21
|
|
12
20
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remcp/runtime",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.80",
|
|
4
4
|
"description": "First-party ReMCP local device runtime: filesystem, terminal, computer-use, browser, diagnostics and document tools over MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/
|
|
35
|
+
"url": "git+https://github.com/getremcp/remcp.git",
|
|
36
36
|
"directory": "packages/runtime"
|
|
37
37
|
},
|
|
38
38
|
"bugs": {
|
|
39
|
-
"url": "https://github.com/
|
|
39
|
+
"url": "https://github.com/getremcp/remcp/issues"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"access": "public"
|
package/src/extended/catalog.mjs
CHANGED
|
@@ -455,7 +455,7 @@ const inputBackendSelector = {
|
|
|
455
455
|
backend: e(['auto','x11','portal'], 'Input backend preference. auto uses proven local backends and already-authorized portal input; portal explicitly requests XDG RemoteDesktop consent on Wayland; x11 forces XWayland/xdotool only for operations where read-back testing shows it is reliable. GNOME Wayland keyboard/text deliberately reject x11.'),
|
|
456
456
|
};
|
|
457
457
|
const inputWindowSelector = {
|
|
458
|
-
window_id: s('Optional target window id returned by list_windows.
|
|
458
|
+
window_id: s('Optional target window id returned by list_windows. On Wayland the target is focused and verified before keyboard/text input is dispatched.'),
|
|
459
459
|
pid: n('Optional target process id.'),
|
|
460
460
|
app: s('Optional target application/process name substring.'),
|
|
461
461
|
title: s('Optional target window title substring.'),
|
|
@@ -538,8 +538,8 @@ export const extendedToolDefinitions = [
|
|
|
538
538
|
define('ui_snapshot', 'UI accessibility snapshot', 'Read the active desktop accessibility/UI Automation tree with compact labels, semantic hierarchy, bounds, state and suggested actions. Default scope is the active application/window; scope=desktop requests the full desktop tree. Explicit pid/app/window_title scopes override the active default. browser_dom asks the OS accessibility provider for the web-document subtree when available.', o({ scope:e(['active','desktop'],'Default active. Use desktop only when cross-application discovery is required.'), pid:n('Optional owning process id scope.'), app:s('Optional application/process name substring scope.'), window_title:s('Optional top-level accessibility window/frame title scope.'), browser_dom:b('Prefer the native browser document accessibility subtree instead of browser chrome when supported.'), semantic_tree:b('Include compact semantic hierarchy text; default true.'), max_nodes:n('Maximum nodes; default 500, maximum 5000.'), max_depth:n('Maximum traversal depth; default 8, maximum 32.') }), readOnlyLive, desktopHandlers.ui_snapshot, ['ui']),
|
|
539
539
|
define('ui_find', 'Find UI elements', 'Find native accessible desktop elements by label/id/name/role/AutomationId. Reuses the latest five-second semantic snapshot for fast repeated targeting; refresh=true forces a new OS accessibility capture.', o({ ...uiSelector, refresh:b('Force a fresh accessibility capture instead of the short-lived snapshot cache.'), limit:n('Maximum matches; default 20.'), max_nodes:n(), max_depth:n() }), readOnlyLive, desktopHandlers.ui_find, ['ui']),
|
|
540
540
|
define('ui_action', 'Act on UI element', 'Act on a native accessibility/UI Automation element by id, label or semantic selector. Supports invoke/focus/value/toggle/selection, expand-collapse, range values and scroll-into-view before falling back to coordinates.', o({ action:e(['click','invoke','focus','set_value','select','toggle','expand','collapse','scroll_into_view','set_range_value','add_to_selection','remove_from_selection']), ...uiSelector, value:{description:'New text or numeric range value.'} }, ['action']), openMutating, desktopHandlers.ui_action, ['ui']),
|
|
541
|
-
define('type_text', 'Type text', 'Enter Unicode text into a native semantic accessibility target or focused native control. Auto prefers verified accessibility editing; explicit semantic targets fail safely when the platform rejects the write instead of claiming an unverified paste. Use browser_action for Chromium page DOM. Clipboard/keys fallbacks
|
|
542
|
-
define('keyboard', 'Keyboard shortcut', 'Send a keyboard shortcut or key press to the active desktop application or an explicitly selected window. On Wayland
|
|
541
|
+
define('type_text', 'Type text', 'Enter Unicode text into a native semantic accessibility target or focused native control. Auto prefers verified accessibility editing; with clear=true and an explicit window target it can replace the value of the single editable accessibility control in that window without compositor focus. Explicit semantic targets fail safely when the platform rejects the write instead of claiming an unverified paste. Use browser_action for Chromium page DOM. Clipboard/keys/Enter fallbacks still require verified window focus on Wayland.', o({ text:s('Text to enter.'), method:e(['auto','accessibility','clipboard','keys'], 'Default auto prefers verified accessibility editing and falls back to clipboard paste. keys sends physical key events and therefore follows the active keyboard layout.'), clear:b('Replace existing text for semantic/window-scoped accessibility input, or clear it before keyboard/paste fallback.'), caret_position:e(['start','idle','end']), press_enter:b('Press Enter after typing; physical Enter delivery requires verified window focus.'), delay_ms:n('Per-key delay for method=keys.'), ...uiSelector, window_id:s('Optional target window id. With auto/accessibility + clear=true, a unique editable control may be updated semantically without focus; keyboard/clipboard fallbacks verify focus before dispatch.'), title:s('Optional target window title substring. With auto/accessibility + clear=true, a unique editable control may be updated semantically without compositor focus.'), ...inputBackendSelector }, ['text']), openMutating, desktopHandlers.type_text, ['input']),
|
|
542
|
+
define('keyboard', 'Keyboard shortcut', 'Send a keyboard shortcut or key press to the active desktop application or an explicitly selected window. On Wayland, window_id/pid/app/title targets are focused and verified before input is dispatched; backend=portal explicitly requests the consent-backed XDG RemoteDesktop path.', o({ shortcut:s(), key:s(), keys:{ type:'array', items:{type:'string'} }, ...inputWindowSelector, ...inputBackendSelector }), openMutating, desktopHandlers.keyboard, ['input']),
|
|
543
543
|
define('pointer', 'Pointer action', 'Move the pointer or perform click, double-click, right-click, mouse-down, or mouse-up at desktop screen coordinates. Wayland supports XWayland fallback and an explicitly consented XDG RemoteDesktop backend.', o({ action:e(['move','click','double_click','right_click','down','up']), x:n(), y:n(), button:e(['left','right','middle']), ...inputBackendSelector }, ['action']), openMutating, desktopHandlers.pointer, ['pointer']),
|
|
544
544
|
define('drag_drop', 'Drag and drop', 'Drag between desktop coordinates or semantic UI element IDs returned by ui_snapshot/ui_find. Element IDs are resolved to current accessibility bounds before input. GNOME Wayland requires the consent-backed Remote Desktop portal for real drag delivery; unreliable XTEST/xdotool drag is rejected.', o({ from_x:n(), from_y:n(), to_x:n(), to_y:n(), from_id:s('Source UI element id; use instead of from_x/from_y.'), to_id:s('Destination UI element id; use instead of to_x/to_y.'), button:e(['left','right','middle']), hold_ms:n(), duration_ms:n(), ...inputBackendSelector }), openMutating, desktopHandlers.drag_drop, ['drag']),
|
|
545
545
|
define('scroll', 'Scroll', 'Scroll vertically or horizontally using native wheel input. Target a semantic UI element/label or x/y first so the wheel is delivered to the intended region; direction+wheel_times is a convenience over normalized deltas.', o({ delta_x:n('Horizontal delta; positive means right.'), delta_y:n('Vertical 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 when direction is used; 1..50.'), x:n(), y:n(), ...uiSelector, ...inputBackendSelector }), mutatingNonDestructive, desktopHandlers.scroll, ['pointer']),
|
|
@@ -252,6 +252,36 @@ async function focusInputWindow(windowId) {
|
|
|
252
252
|
if (result.code === 0) await new Promise(resolve => setTimeout(resolve, 40));
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
function explicitInputWindowSelector(args = {}) {
|
|
256
|
+
const dedicatedWindowId = optionalString(args.window_id || args.windowId);
|
|
257
|
+
const dedicatedTitle = optionalString(args.title);
|
|
258
|
+
const hasSemanticElementTarget = Boolean(
|
|
259
|
+
optionalString(args.id)
|
|
260
|
+
|| optionalString(args.name)
|
|
261
|
+
|| optionalString(args.role)
|
|
262
|
+
|| optionalString(args.automation_id || args.automationId)
|
|
263
|
+
);
|
|
264
|
+
if (!dedicatedWindowId && !dedicatedTitle && hasSemanticElementTarget) return null;
|
|
265
|
+
const pid = Number.isInteger(Number(args.pid)) ? Number(args.pid) : null;
|
|
266
|
+
const app = optionalString(args.app);
|
|
267
|
+
const scopedTitle = optionalString(args.window_title || args.windowTitle);
|
|
268
|
+
if (!dedicatedWindowId && !dedicatedTitle && pid == null && !app && !scopedTitle) return null;
|
|
269
|
+
return {
|
|
270
|
+
...(dedicatedWindowId ? { id:dedicatedWindowId } : {}),
|
|
271
|
+
...(pid != null ? { pid } : {}),
|
|
272
|
+
...(app ? { app } : {}),
|
|
273
|
+
...((dedicatedTitle || scopedTitle) ? { title:dedicatedTitle || scopedTitle } : {}),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function focusExplicitWaylandInputTarget(args = {}) {
|
|
278
|
+
const selector = explicitInputWindowSelector(args);
|
|
279
|
+
if (!selector) return null;
|
|
280
|
+
const row = await windowMatch(selector);
|
|
281
|
+
await windowAction({ action:'focus', id:row.id, backend:inputBackend(args) });
|
|
282
|
+
return row;
|
|
283
|
+
}
|
|
284
|
+
|
|
255
285
|
async function nativeWaylandWindowAction(row, action, args) {
|
|
256
286
|
const backend = inputBackend(args);
|
|
257
287
|
if (backend === 'x11') unavailable('Native Wayland window action', 'use backend=portal for compositor-managed windows');
|
|
@@ -350,23 +380,31 @@ export async function windowAction(args) {
|
|
|
350
380
|
if (focusState.exact) return text('Window action focus already matched the target window.');
|
|
351
381
|
|
|
352
382
|
const rows = JSON.parse((await listWindows()).content?.[0]?.text || '[]');
|
|
353
|
-
const
|
|
383
|
+
const windowCycles = Math.min(60, Math.max(8, rows.length + 4));
|
|
384
|
+
for (let index = 0; index < windowCycles; index += 1) {
|
|
385
|
+
await portalShortcut('ALT+ESC', portalOptions);
|
|
386
|
+
await new Promise(resolve => setTimeout(resolve, 140));
|
|
387
|
+
focusState = await targetAccessibilityFocus(row);
|
|
388
|
+
if (focusState.exact) return text('Window action focus completed via verified Wayland Alt+Esc.');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const appCycles = Math.min(12, Math.max(3, new Set(rows.map(item => Number(item.pid) || String(item.app || ''))).size + 1));
|
|
354
392
|
for (let index = 0; index < appCycles; index += 1) {
|
|
355
393
|
await portalShortcut('ALT+TAB', portalOptions);
|
|
356
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
394
|
+
await new Promise(resolve => setTimeout(resolve, 160));
|
|
357
395
|
focusState = await targetAccessibilityFocus(row);
|
|
358
|
-
if (focusState.exact) return text('Window action focus completed via verified Wayland Alt+Tab.');
|
|
396
|
+
if (focusState.exact) return text('Window action focus completed via verified Wayland Alt+Tab fallback.');
|
|
359
397
|
if (focusState.active) {
|
|
360
398
|
const sameAppWindows = rows.filter(item => Number(item.pid) === Number(row.pid)).length;
|
|
361
|
-
for (let windowIndex = 0; windowIndex < Math.min(
|
|
399
|
+
for (let windowIndex = 0; windowIndex < Math.min(8, Math.max(2, sameAppWindows + 1)); windowIndex += 1) {
|
|
362
400
|
await portalShortcut('ALT+`', portalOptions);
|
|
363
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
401
|
+
await new Promise(resolve => setTimeout(resolve, 120));
|
|
364
402
|
focusState = await targetAccessibilityFocus(row);
|
|
365
|
-
if (focusState.exact) return text('Window action focus completed via verified Wayland
|
|
403
|
+
if (focusState.exact) return text('Window action focus completed via verified Wayland same-app fallback.');
|
|
366
404
|
}
|
|
367
405
|
}
|
|
368
406
|
}
|
|
369
|
-
throw new Error(`Could not verify focus for ${row.title || row.app || row.id} after
|
|
407
|
+
throw new Error(`Could not verify focus for ${row.title || row.app || row.id} after bounded Wayland window cycling`);
|
|
370
408
|
}
|
|
371
409
|
if (!commandExists('wmctrl')) unavailable('Window focus', 'install wmctrl');
|
|
372
410
|
await runFile('wmctrl', ['-ia', id], { label: 'window focus' });
|
|
@@ -719,6 +757,37 @@ export async function clipboard(args = {}) {
|
|
|
719
757
|
unavailable('Clipboard read', `${failures.join('; ') || 'install wl-clipboard or xclip'}`);
|
|
720
758
|
}
|
|
721
759
|
|
|
760
|
+
export async function typeTextWindowTarget(value, args = {}) {
|
|
761
|
+
const row = await windowMatch(args);
|
|
762
|
+
if (!row?.pid) return null;
|
|
763
|
+
const snapshot = await uiSnapshot({
|
|
764
|
+
pid:row.pid,
|
|
765
|
+
window_title:row.title || undefined,
|
|
766
|
+
max_nodes:2200,
|
|
767
|
+
max_depth:24,
|
|
768
|
+
});
|
|
769
|
+
let payload = null;
|
|
770
|
+
try { payload = JSON.parse(snapshot.content?.[0]?.text || '{}'); } catch {}
|
|
771
|
+
const nodes = Array.isArray(payload) ? payload : payload?.nodes || [];
|
|
772
|
+
const editableRoles = /(^|\b)(text|entry|textbox|edit|text field|search box|password text)(\b|$)/i;
|
|
773
|
+
const candidates = nodes.filter(node => node?.id && editableRoles.test(String(node.role || '')));
|
|
774
|
+
if (candidates.length !== 1) return null;
|
|
775
|
+
|
|
776
|
+
const target = candidates[0];
|
|
777
|
+
const result = await uiAction({ action:'set_value', id:target.id, value:String(value) });
|
|
778
|
+
let action = result.content?.[0]?.text || '';
|
|
779
|
+
try { action = JSON.parse(action); } catch {}
|
|
780
|
+
return {
|
|
781
|
+
id:target.id,
|
|
782
|
+
pid:row.pid,
|
|
783
|
+
app:row.app || target.app || '',
|
|
784
|
+
window:row.title || target.window || '',
|
|
785
|
+
role:target.role || '',
|
|
786
|
+
name:target.name || '',
|
|
787
|
+
action,
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
722
791
|
export async function typeTextFocused(value) {
|
|
723
792
|
if (!commandExists('python3')) return null;
|
|
724
793
|
const script = String.raw`import gi,json,sys
|
|
@@ -805,6 +874,7 @@ export async function keyboard(args = {}) {
|
|
|
805
874
|
return argv;
|
|
806
875
|
};
|
|
807
876
|
if (isWaylandSession()) {
|
|
877
|
+
await focusExplicitWaylandInputTarget(args);
|
|
808
878
|
if (backend !== 'portal' && commandExists('wtype')) {
|
|
809
879
|
const result = await runFile('wtype', wtypeArgs(), { label: 'keyboard', allowFailure: true, timeout: 5000 });
|
|
810
880
|
if (result.code === 0) return text(`Sent ${shortcut} via wtype.`);
|
|
@@ -842,6 +912,7 @@ export async function typeTextKeys(value, args = {}) {
|
|
|
842
912
|
const delay = clamp(args.delay_ms, 1, 0, 1000);
|
|
843
913
|
const backend = inputBackend(args);
|
|
844
914
|
if (isWaylandSession()) {
|
|
915
|
+
await focusExplicitWaylandInputTarget(args);
|
|
845
916
|
if (backend !== 'portal' && commandExists('wtype')) {
|
|
846
917
|
const result = await runFile('wtype', ['-d', String(delay), '--', String(value)], { label: 'type text', timeout: 30_000, allowFailure: true });
|
|
847
918
|
if (result.code === 0) return { ...result, backend: 'wtype' };
|
package/src/extended/desktop.mjs
CHANGED
|
@@ -618,6 +618,23 @@ export async function waitForUi(args = {}) {
|
|
|
618
618
|
return jsonResult({ state:'timeout', wanted:state, matched:nodes.length, attempts, elapsed_ms:Date.now() - startedAt, nodes, ...(state === 'changed' ? { before:baseline } : {}) });
|
|
619
619
|
}
|
|
620
620
|
|
|
621
|
+
function explicitTypeTextWindowTarget(args = {}, hasElementSelector = false) {
|
|
622
|
+
const windowId = optionalString(args.window_id || args.windowId);
|
|
623
|
+
const title = optionalString(args.title);
|
|
624
|
+
if (!windowId && !title && hasElementSelector) return null;
|
|
625
|
+
const pid = Number.isInteger(Number(args.pid)) ? Number(args.pid) : null;
|
|
626
|
+
const app = optionalString(args.app);
|
|
627
|
+
const scopedTitle = optionalString(args.window_title || args.windowTitle);
|
|
628
|
+
if (!windowId && !title && pid == null && !app && !scopedTitle) return null;
|
|
629
|
+
return {
|
|
630
|
+
...(windowId ? { id:windowId } : {}),
|
|
631
|
+
...(pid != null ? { pid } : {}),
|
|
632
|
+
...(app ? { app } : {}),
|
|
633
|
+
...((title || scopedTitle) ? { title:title || scopedTitle } : {}),
|
|
634
|
+
...(args.backend ? { backend:args.backend } : {}),
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
621
638
|
export async function typeText(args = {}) {
|
|
622
639
|
const value = String(args.text ?? '');
|
|
623
640
|
const method = requireEnum(args.method || 'auto', 'method', ['auto','accessibility','clipboard','keys']);
|
|
@@ -626,10 +643,25 @@ export async function typeText(args = {}) {
|
|
|
626
643
|
optionalString(resolvedArgs.id) || optionalString(resolvedArgs.name) || optionalString(resolvedArgs.role) ||
|
|
627
644
|
optionalString(resolvedArgs.automation_id || resolvedArgs.automationId)
|
|
628
645
|
);
|
|
646
|
+
const explicitWindowTarget = explicitTypeTextWindowTarget(resolvedArgs, hasElementSelector);
|
|
629
647
|
const clear = args.clear === true;
|
|
630
648
|
const pressEnter = args.press_enter === true;
|
|
631
649
|
const caret = requireEnum(args.caret_position || 'idle', 'caret_position', ['start','idle','end']);
|
|
632
650
|
|
|
651
|
+
if (
|
|
652
|
+
explicitWindowTarget && !hasElementSelector && clear && !pressEnter
|
|
653
|
+
&& (method === 'auto' || method === 'accessibility')
|
|
654
|
+
&& typeof adapter.typeTextWindowTarget === 'function'
|
|
655
|
+
) {
|
|
656
|
+
const semantic = await adapter.typeTextWindowTarget(value, resolvedArgs);
|
|
657
|
+
if (semantic) {
|
|
658
|
+
return jsonResult({ length:value.length, method:'accessibility', target:semantic, clear:true, press_enter:false });
|
|
659
|
+
}
|
|
660
|
+
if (method === 'accessibility') {
|
|
661
|
+
throw new Error('Accessibility-only text input needs exactly one editable control in the targeted window when no element selector is provided');
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
633
665
|
if ((method === 'auto' || method === 'accessibility') && hasElementSelector) {
|
|
634
666
|
try {
|
|
635
667
|
const result = await uiAction({ ...resolvedArgs, action:'set_value', value });
|
|
@@ -652,7 +684,10 @@ export async function typeText(args = {}) {
|
|
|
652
684
|
throw new Error(`Semantic text target rejected accessibility input. Refresh the UI target or use browser_action for browser page content. ${detail}`);
|
|
653
685
|
}
|
|
654
686
|
}
|
|
655
|
-
if (
|
|
687
|
+
if (
|
|
688
|
+
!explicitWindowTarget && (method === 'auto' || method === 'accessibility') && !hasElementSelector
|
|
689
|
+
&& typeof adapter.typeTextFocused === 'function' && !clear && caret === 'idle'
|
|
690
|
+
) {
|
|
656
691
|
const semantic = await adapter.typeTextFocused(value, resolvedArgs);
|
|
657
692
|
if (semantic) {
|
|
658
693
|
if (pressEnter) await keyboard({ ...resolvedArgs, key:'ENTER' });
|
|
@@ -661,9 +696,10 @@ export async function typeText(args = {}) {
|
|
|
661
696
|
if (method === 'accessibility') throw new Error('No focused editable accessibility element is available');
|
|
662
697
|
}
|
|
663
698
|
if (method === 'accessibility') {
|
|
664
|
-
throw new Error('Accessibility-only text input requires a semantic element target
|
|
699
|
+
throw new Error('Accessibility-only text input requires a semantic element target, one editable control in the targeted window with clear=true, or a focused editable control with clear=false and caret_position=idle');
|
|
665
700
|
}
|
|
666
701
|
|
|
702
|
+
if (explicitWindowTarget) await windowAction({ action:'focus', ...explicitWindowTarget });
|
|
667
703
|
if (hasElementSelector) await uiAction({ ...resolvedArgs, action:'focus' });
|
|
668
704
|
if (clear) {
|
|
669
705
|
await keyboard({ ...resolvedArgs, shortcut:process.platform === 'darwin' ? 'CMD+A' : 'CTRL+A' });
|