@remcp/runtime 0.2.25 → 0.2.26

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools/files.mjs +37 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/runtime",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
4
4
  "description": "First-party ReMCP local device runtime: file, search, terminal and process tools over MCP for computers paired with ReMCP.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,9 +14,10 @@ import { countEvent, recordEvent } from '../telemetry.mjs';
14
14
  import { clampInteger, decodeText, displayPath, fail, globToRegExp, image, looksBinary, multi, pageLines, resolveSafePath, splitLines, text } from '../util.mjs';
15
15
 
16
16
  const MAX_INLINE_FILE_BYTES = 20 * 1024 * 1024;
17
- // An image travels base64-encoded, which costs a third more bytes, and the MCP stdio client drops
18
- // the connection above 10 MB. 4 MiB of image is ~5.4 MiB on the wire, which leaves room for the
19
- // summary text and the frame overhead; anything larger goes through read_binary in chunks instead.
17
+ // An image travels base64-encoded, which costs a third more bytes. The agent's stdio transport holds
18
+ // 24 MiB and the relay's WebSocket frames hold 32 MiB, so 8 MiB of image is ~11 MiB on the wire with
19
+ // room to spare; anything larger goes through read_binary in chunks instead. Binary chunks are 4 MiB
20
+ // for the same reason: four times fewer round trips for the same file.
20
21
  const MAX_IMAGE_BYTES = 4 * 1024 * 1024;
21
22
  const MAX_BINARY_CHUNK_BYTES = 1024 * 1024;
22
23
  const IMAGE_TYPES = new Map([
@@ -921,6 +922,26 @@ const SCREENSHOT_COMMANDS = [
921
922
  { command: 'screencapture', args: file => ['-x', file] },
922
923
  ];
923
924
 
925
+ // What to tell the person when no capture worked. The general "install a screenshot tool" line was
926
+ // wrong on a Mac, where `screencapture` ships with the system and fails only because TCC has not
927
+ // granted Screen Recording — the exact failure behind "the screenshot returned an error" reports.
928
+ function screenshotAdvice(attempts, { platform = process.platform, env = process.env, execPath = process.execPath } = {}) {
929
+ if (platform === 'darwin') {
930
+ return `macOS refused the screen capture (${attempts.join('; ') || 'no capture command ran'}). Grant Screen Recording to the binary that runs the tools — System Settings → Privacy & Security → Screen Recording → + → ${execPath} — then restart the agent with \`remcp start\`. macOS requires it for screencapture even when the file itself is writable.`;
931
+ }
932
+ if (platform === 'win32') {
933
+ return `Windows refused the screen capture (${attempts.join('; ') || 'no capture command ran'}). Screen capture needs an interactive desktop session: a machine where nobody is signed in, or a locked session, cannot be captured. Sign in on that computer and try again.`;
934
+ }
935
+ const wayland = /wayland/i.test(String(env.XDG_SESSION_TYPE || '')) || Boolean(env.WAYLAND_DISPLAY);
936
+ if (!env.DISPLAY && !wayland) {
937
+ return `This computer has no graphical session (no DISPLAY and no Wayland display), so there is nothing to capture — servers and containers usually have none.`;
938
+ }
939
+ if (wayland) {
940
+ return `Could not capture the screen on Wayland (${attempts.join('; ') || 'no capture command ran'}). Install \`grim\` (Wayland's capture tool) — X11 tools such as scrot or ImageMagick import cannot read a Wayland session.`;
941
+ }
942
+ return `Could not capture the screen. Install one of grim, gnome-screenshot, spectacle, scrot, or ImageMagick import (tried: ${attempts.join('; ') || 'none available'}).`;
943
+ }
944
+
924
945
  function windowsScreenshotScript(file) {
925
946
  return [
926
947
  'Add-Type -AssemblyName System.Windows.Forms,System.Drawing',
@@ -949,19 +970,21 @@ export async function takeScreenshotTool(args) {
949
970
  }
950
971
  }
951
972
  if (!await pathExists(file)) {
952
- fail(`Could not capture the screen. Install one of grim, gnome-screenshot, spectacle, scrot, or ImageMagick import (tried: ${attempts.join('; ') || 'none available'}).`);
973
+ fail(screenshotAdvice(attempts));
953
974
  }
954
975
  const info = await stat(file);
955
- if (info.size > MAX_IMAGE_BYTES) {
956
- await rm(file, { force: true });
957
- fail(`Screenshot is ${info.size} bytes, above the ${MAX_IMAGE_BYTES}-byte inline limit`);
958
- }
959
- const buffer = await readFile(file);
960
- if (args.keep !== true) await rm(file, { force: true });
961
- return multi([
962
- { type: 'text', text: `Screenshot of ${os.hostname()} (${info.size} bytes)${args.keep === true ? ` saved at ${displayPath(file)}` : ''}` },
963
- image(buffer.toString('base64'), 'image/png'),
964
- ]);
976
+ if (info.size <= MAX_IMAGE_BYTES) {
977
+ const buffer = await readFile(file);
978
+ if (args.keep !== true) await rm(file, { force: true });
979
+ return multi([
980
+ { type: 'text', text: `Screenshot of ${os.hostname()} (${info.size} bytes)${args.keep === true ? ` saved at ${displayPath(file)}` : ''}` },
981
+ image(buffer.toString('base64'), 'image/png'),
982
+ ]);
983
+ }
984
+ // A big screen is not an error: the PNG stays on the computer and the model is told how to fetch it
985
+ // in chunks, which is the same path every other large file takes. Failing here used to lose the
986
+ // screenshot entirely.
987
+ return text(`Screenshot of ${os.hostname()} captured: ${info.size} bytes, above the ${MAX_IMAGE_BYTES}-byte inline limit, so it is saved at ${displayPath(file)} instead of being returned as an image. Fetch it with read_binary using chunks of up to ${MAX_BINARY_CHUNK_BYTES} bytes (offset_bytes and length_bytes), or ask for a smaller region.`);
965
988
  }
966
989
 
967
990
  export const fileToolHandlers = {