@remcp/runtime 0.2.79 → 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 CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.80 — 2026-09-21
6
+
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
+
5
12
  ## 0.2.79 — 2026-09-21
6
13
 
7
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/runtime",
3
- "version": "0.2.79",
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/antonbaider/remcp.git",
35
+ "url": "git+https://github.com/getremcp/remcp.git",
36
36
  "directory": "packages/runtime"
37
37
  },
38
38
  "bugs": {
39
- "url": "https://github.com/antonbaider/remcp/issues"
39
+ "url": "https://github.com/getremcp/remcp/issues"
40
40
  },
41
41
  "publishConfig": {
42
42
  "access": "public"
@@ -538,7 +538,7 @@ 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 remain available for focused/window-level input when appropriate, and explicit Wayland window targets are focused and verified before dispatch.', 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('Clear existing text before input when keyboard/paste fallback is used.'), caret_position:e(['start','idle','end']), press_enter:b('Press Enter after typing.'), delay_ms:n('Per-key delay for method=keys.'), ...uiSelector, window_id:s('Optional target window id; the window is focused before auto/accessibility/clipboard/keys input and Wayland verifies focus before dispatch.'), title:s('Optional target window title substring; focused before input when no semantic element target is supplied.'), ...inputBackendSelector }, ['text']), openMutating, desktopHandlers.type_text, ['input']),
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
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']),
@@ -757,6 +757,37 @@ export async function clipboard(args = {}) {
757
757
  unavailable('Clipboard read', `${failures.join('; ') || 'install wl-clipboard or xclip'}`);
758
758
  }
759
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
+
760
791
  export async function typeTextFocused(value) {
761
792
  if (!commandExists('python3')) return null;
762
793
  const script = String.raw`import gi,json,sys
@@ -644,11 +644,24 @@ export async function typeText(args = {}) {
644
644
  optionalString(resolvedArgs.automation_id || resolvedArgs.automationId)
645
645
  );
646
646
  const explicitWindowTarget = explicitTypeTextWindowTarget(resolvedArgs, hasElementSelector);
647
- if (explicitWindowTarget) await windowAction({ action:'focus', ...explicitWindowTarget });
648
647
  const clear = args.clear === true;
649
648
  const pressEnter = args.press_enter === true;
650
649
  const caret = requireEnum(args.caret_position || 'idle', 'caret_position', ['start','idle','end']);
651
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
+
652
665
  if ((method === 'auto' || method === 'accessibility') && hasElementSelector) {
653
666
  try {
654
667
  const result = await uiAction({ ...resolvedArgs, action:'set_value', value });
@@ -671,7 +684,10 @@ export async function typeText(args = {}) {
671
684
  throw new Error(`Semantic text target rejected accessibility input. Refresh the UI target or use browser_action for browser page content. ${detail}`);
672
685
  }
673
686
  }
674
- if ((method === 'auto' || method === 'accessibility') && !hasElementSelector && typeof adapter.typeTextFocused === 'function' && !clear && caret === 'idle') {
687
+ if (
688
+ !explicitWindowTarget && (method === 'auto' || method === 'accessibility') && !hasElementSelector
689
+ && typeof adapter.typeTextFocused === 'function' && !clear && caret === 'idle'
690
+ ) {
675
691
  const semantic = await adapter.typeTextFocused(value, resolvedArgs);
676
692
  if (semantic) {
677
693
  if (pressEnter) await keyboard({ ...resolvedArgs, key:'ENTER' });
@@ -680,9 +696,10 @@ export async function typeText(args = {}) {
680
696
  if (method === 'accessibility') throw new Error('No focused editable accessibility element is available');
681
697
  }
682
698
  if (method === 'accessibility') {
683
- throw new Error('Accessibility-only text input requires a semantic element target for replacement, or a focused editable control with clear=false and caret_position=idle');
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');
684
700
  }
685
701
 
702
+ if (explicitWindowTarget) await windowAction({ action:'focus', ...explicitWindowTarget });
686
703
  if (hasElementSelector) await uiAction({ ...resolvedArgs, action:'focus' });
687
704
  if (clear) {
688
705
  await keyboard({ ...resolvedArgs, shortcut:process.platform === 'darwin' ? 'CMD+A' : 'CTRL+A' });