@remcp/runtime 0.2.96 → 0.2.97

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
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.97 — 2026-09-22
4
+
5
+ - Native GNOME Wayland `move`, `resize`, and `move_resize` no longer trust a portal drag as proof of success.
6
+ - Window geometry is re-read after each gesture and must match the requested coordinates/size within a bounded tolerance; otherwise the action fails closed.
7
+ - `move_resize` confirms the move before computing the resize gesture. Tool names and schemas remain unchanged.
8
+
3
9
  ## 0.2.96 — 2026-09-22
4
10
 
5
11
  - Keeps readable search matches when an unreadable descendant is encountered while surfacing a warning.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/runtime",
3
- "version": "0.2.96",
3
+ "version": "0.2.97",
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",
@@ -283,6 +283,34 @@ async function focusExplicitWaylandInputTarget(args = {}) {
283
283
  return row;
284
284
  }
285
285
 
286
+ function nativeWaylandGeometryMatches(row, expected = {}, tolerance = 24) {
287
+ for (const key of ['x','y','width','height']) {
288
+ if (expected[key] == null) continue;
289
+ const actual = Number(row?.[key]);
290
+ const wanted = Number(expected[key]);
291
+ if (!Number.isFinite(actual) || !Number.isFinite(wanted) || Math.abs(actual - wanted) > tolerance) return false;
292
+ }
293
+ return true;
294
+ }
295
+
296
+ async function verifyNativeWaylandGeometry(row, expected, label) {
297
+ let latest = row;
298
+ for (let attempt = 0; attempt < 6; attempt += 1) {
299
+ if (attempt > 0) await new Promise(resolve => setTimeout(resolve, 120));
300
+ try {
301
+ latest = await windowMatch({ id:row.id });
302
+ if (nativeWaylandGeometryMatches(latest, expected)) return latest;
303
+ } catch {}
304
+ }
305
+ const actual = ['x','y','width','height']
306
+ .map(key => `${key}=${Number.isFinite(Number(latest?.[key])) ? Number(latest[key]) : 'unknown'}`)
307
+ .join(' ');
308
+ const wanted = Object.entries(expected)
309
+ .map(([key,value]) => `${key}=${value}`)
310
+ .join(' ');
311
+ throw new Error(`Could not verify native Wayland window ${label} for ${row.title || row.app || row.id}: wanted ${wanted}; observed ${actual}`);
312
+ }
313
+
286
314
  async function nativeWaylandWindowAction(row, action, args) {
287
315
  const backend = inputBackend(args);
288
316
  if (backend === 'x11') unavailable('Native Wayland window action', 'use backend=portal for compositor-managed windows');
@@ -336,8 +364,11 @@ async function nativeWaylandWindowAction(row, action, args) {
336
364
  hold_ms:80,
337
365
  duration_ms:120,
338
366
  });
339
- currentX = x;
340
- currentY = y;
367
+ const verified = await verifyNativeWaylandGeometry(row, { x, y }, 'move');
368
+ currentX = Number(verified.x);
369
+ currentY = Number(verified.y);
370
+ currentWidth = Number(verified.width);
371
+ currentHeight = Number(verified.height);
341
372
  }
342
373
 
343
374
  if (action === 'resize' || action === 'move_resize') {
@@ -352,7 +383,21 @@ async function nativeWaylandWindowAction(row, action, args) {
352
383
  hold_ms:80,
353
384
  duration_ms:120,
354
385
  });
386
+ const verified = await verifyNativeWaylandGeometry(row, { width, height }, 'resize');
387
+ currentX = Number(verified.x);
388
+ currentY = Number(verified.y);
389
+ currentWidth = Number(verified.width);
390
+ currentHeight = Number(verified.height);
355
391
  }
392
+ return jsonResult({
393
+ action,
394
+ backend:'xdg-desktop-portal',
395
+ id:row.id,
396
+ x:currentX,
397
+ y:currentY,
398
+ width:currentWidth,
399
+ height:currentHeight,
400
+ });
356
401
  }
357
402
  return jsonResult({ action, backend:'xdg-desktop-portal', id:row.id });
358
403
  }