@remcp/runtime 0.2.132 → 0.2.134
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 +13 -0
- package/package.json +1 -1
- package/src/extended/catalog.mjs +1 -1
- package/src/extended/desktop.mjs +21 -2
- package/src/extended/diagnostics.mjs +38 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.134 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- `power_action delay_seconds` is bounded to 0–90 seconds in schema and runtime validation so a delayed destructive call cannot intentionally outlive the hosted ~110-second device-call lifetime.
|
|
6
|
+
- Delayed power actions use the propagated MCP AbortSignal; cancellation clears the timer and stops before any OS lock/sleep/restart/shutdown command is sent.
|
|
7
|
+
- A second cancellation check runs immediately before platform dispatch. Regression tests cover pre-cancelled, mid-delay-cancelled, and schema-bound behavior.
|
|
8
|
+
- Runtime tool names remain unchanged; this is a safety/correctness tightening of the existing power tool.
|
|
9
|
+
|
|
10
|
+
## 0.2.133 — 2026-10-02
|
|
11
|
+
|
|
12
|
+
- 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`.
|
|
13
|
+
- Auto window fallback uses the same sanitized window-only selector shape, preventing stale UI/browser selectors from hijacking window-scoped text entry.
|
|
14
|
+
- 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.
|
|
15
|
+
|
|
3
16
|
## 0.2.132 — 2026-10-02
|
|
4
17
|
|
|
5
18
|
- `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.
|
package/package.json
CHANGED
package/src/extended/catalog.mjs
CHANGED
|
@@ -656,7 +656,7 @@ export const extendedToolDefinitions = [
|
|
|
656
656
|
define('installed_apps', 'Installed applications', 'Inventory installed applications or packages with versions and publishers where available using native package/application inventories.', o({ filter:s('Name substring.'), limit:n('Maximum results; default 1000.') }), readOnly, diagnosticHandlers.installed_apps, ['apps']),
|
|
657
657
|
define('environment', 'Runtime environment', 'Report platform, architecture, Node version, working/home/temp paths, shell, PATH, and optionally environment variables. Environment variables are opt-in and secret-looking or credential-bearing values are masked.', o({ include_env:b('Include sanitized environment variables; default false.') }), readOnly, diagnosticHandlers.environment),
|
|
658
658
|
define('audio', 'Audio control', 'Read or change default output volume or mute state using native operating-system audio controls when available.', o({ action:e(['status','set_volume','mute','unmute']), volume:n('0..100 for set_volume.') }, ['action']), mutatingNonDestructive, diagnosticHandlers.audio, ['audio']),
|
|
659
|
-
define('power_action', 'Power action', 'Lock, sleep, restart, or shut down the paired computer using native operating-system power facilities. The action passes through ReMCP command policy.', o({ action:e(['lock','sleep','restart','shutdown']), delay_seconds:
|
|
659
|
+
define('power_action', 'Power action', 'Lock, sleep, restart, or shut down the paired computer using native operating-system power facilities. The action passes through ReMCP command policy. Delayed actions are cancelled if the client stops waiting.', o({ action:e(['lock','sleep','restart','shutdown']), delay_seconds:{ type:'number', minimum:0, maximum:90, description:'Optional delay in seconds, maximum 90. Longer delays are rejected because hosted calls expire before they can complete safely.' } }, ['action']), mutating, diagnosticHandlers.power_action, ['power']),
|
|
660
660
|
define('record_screen', 'Record screen', 'Record a short bounded desktop video to a permitted local file for debugging and return its path and size. Uses native/available recording helpers.', o({ duration_seconds:n('1..120 seconds.'), fps:n('1..60.'), destination:s('Permitted output path. If an extension is supplied, use .webm for native GNOME Wayland recording and .mp4 for the other recorder backends.') }), mutating, diagnosticHandlers.record_screen, ['record']),
|
|
661
661
|
|
|
662
662
|
define('read_document', 'Read document', 'Read PDF, DOCX, XLSX, TXT, Markdown, CSV, JSON, or XML. PDF/DOCX extraction is built in and XLSX is parsed from its OOXML worksheet cells.', o({ path:s('Document path.'), sheet:s('Worksheet name for XLSX.'), max_cells:n() }, ['path']), readOnly, documentHandlers.read_document, ['documents']),
|
package/src/extended/desktop.mjs
CHANGED
|
@@ -1291,6 +1291,24 @@ function browserComputerAction(args, action) {
|
|
|
1291
1291
|
});
|
|
1292
1292
|
}
|
|
1293
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
|
+
|
|
1294
1312
|
export async function computerAction(args = {}) {
|
|
1295
1313
|
const legacy = ['ui','pointer','window','type','keyboard','scroll','drag','clipboard','launch_app','open_path','reveal_path','notification'];
|
|
1296
1314
|
const universal = [
|
|
@@ -1376,13 +1394,14 @@ export async function computerAction(args = {}) {
|
|
|
1376
1394
|
...(args.window_id ? { id:args.window_id } : {}),
|
|
1377
1395
|
...(args.window_title && !args.title ? { title:args.window_title } : {}),
|
|
1378
1396
|
});
|
|
1397
|
+
const windowTextTarget = () => computerActionWindowTextArgs(args);
|
|
1379
1398
|
|
|
1380
1399
|
if (action === 'type') {
|
|
1381
1400
|
if (target === 'browser') return browserComputerAction(args, 'type');
|
|
1382
1401
|
if (target === 'ocr' || target === 'coordinates') {
|
|
1383
1402
|
throw new Error('computer_action type requires a native UI, browser, or window target');
|
|
1384
1403
|
}
|
|
1385
|
-
if (target === 'window') return typeText(
|
|
1404
|
+
if (target === 'window') return typeText(windowTextTarget());
|
|
1386
1405
|
if (!automatic) return typeText({ ...args, method:args.method || 'accessibility' });
|
|
1387
1406
|
|
|
1388
1407
|
const failures = [];
|
|
@@ -1399,7 +1418,7 @@ export async function computerAction(args = {}) {
|
|
|
1399
1418
|
}
|
|
1400
1419
|
}
|
|
1401
1420
|
if (args.window_id || args.pid != null || args.app || args.window_title || args.title) {
|
|
1402
|
-
try { return await typeText(
|
|
1421
|
+
try { return await typeText(windowTextTarget()); }
|
|
1403
1422
|
catch (error) { failures.push(`window: ${String(error?.message || error).slice(0, 300)}`); }
|
|
1404
1423
|
}
|
|
1405
1424
|
if (failures.length) throw new Error(`computer_action type targeting failed: ${failures.join('; ')}`);
|
|
@@ -9,7 +9,7 @@ import { lstat, mkdtemp, open, rename, rm, stat, unlink, writeFile } from 'node:
|
|
|
9
9
|
import { assertAllowedCommand } from '../policy.mjs';
|
|
10
10
|
import { isWaylandSession } from '../screenshot-portal.mjs';
|
|
11
11
|
import { openDirectoryPath } from '../tools/files.mjs';
|
|
12
|
-
import { resolveSafePath, text } from '../util.mjs';
|
|
12
|
+
import { resolveSafePath, text, throwIfCancelled, ToolError } from '../util.mjs';
|
|
13
13
|
import {
|
|
14
14
|
clamp,
|
|
15
15
|
commandExists,
|
|
@@ -408,11 +408,45 @@ export async function audioTool(args) {
|
|
|
408
408
|
unavailable('Audio control', 'wpctl, pactl or amixer is required on Linux');
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
-
export
|
|
411
|
+
export const POWER_ACTION_MAX_DELAY_SECONDS = 90;
|
|
412
|
+
|
|
413
|
+
export function normalizePowerActionDelay(value) {
|
|
414
|
+
if (value === undefined || value === null || value === '') return 0;
|
|
415
|
+
const delay = Number(value);
|
|
416
|
+
if (!Number.isFinite(delay) || delay < 0 || delay > POWER_ACTION_MAX_DELAY_SECONDS) {
|
|
417
|
+
throw new Error(`delay_seconds must be between 0 and ${POWER_ACTION_MAX_DELAY_SECONDS}`);
|
|
418
|
+
}
|
|
419
|
+
return delay;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export async function waitForPowerActionDelay(delaySeconds, signal) {
|
|
423
|
+
throwIfCancelled(signal);
|
|
424
|
+
if (delaySeconds <= 0) return;
|
|
425
|
+
|
|
426
|
+
await new Promise((resolve, reject) => {
|
|
427
|
+
let settled = false;
|
|
428
|
+
const finish = (fn, value) => {
|
|
429
|
+
if (settled) return;
|
|
430
|
+
settled = true;
|
|
431
|
+
clearTimeout(timer);
|
|
432
|
+
signal?.removeEventListener?.('abort', onAbort);
|
|
433
|
+
fn(value);
|
|
434
|
+
};
|
|
435
|
+
const onAbort = () => finish(reject, new ToolError('Cancelled by the client; power action was not executed.'));
|
|
436
|
+
const timer = setTimeout(() => finish(resolve), delaySeconds * 1000);
|
|
437
|
+
if (signal?.aborted) onAbort();
|
|
438
|
+
else signal?.addEventListener?.('abort', onAbort, { once:true });
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
throwIfCancelled(signal);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export async function powerAction(args, extra = {}) {
|
|
412
445
|
const action = requireEnum(args.action, 'action', ['lock','sleep','restart','shutdown']);
|
|
413
446
|
const policy = assertAllowedCommand(action === 'restart' ? 'reboot' : action === 'shutdown' ? 'shutdown' : action);
|
|
414
|
-
const delay =
|
|
415
|
-
|
|
447
|
+
const delay = normalizePowerActionDelay(args.delay_seconds);
|
|
448
|
+
await waitForPowerActionDelay(delay, extra.signal);
|
|
449
|
+
throwIfCancelled(extra.signal);
|
|
416
450
|
if (process.platform === 'win32') {
|
|
417
451
|
if (action === 'lock') await runPowerShell('rundll32.exe user32.dll,LockWorkStation', { label:'lock workstation' });
|
|
418
452
|
else if (action === 'sleep') await runPowerShell('rundll32.exe powrprof.dll,SetSuspendState 0,1,0', { label:'sleep workstation' });
|