@remcp/runtime 0.2.25 → 0.2.27
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/package.json +1 -1
- package/src/tools/files.mjs +37 -14
package/package.json
CHANGED
package/src/tools/files.mjs
CHANGED
|
@@ -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
|
|
18
|
-
// the
|
|
19
|
-
//
|
|
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(
|
|
973
|
+
fail(screenshotAdvice(attempts));
|
|
953
974
|
}
|
|
954
975
|
const info = await stat(file);
|
|
955
|
-
if (info.size
|
|
956
|
-
await
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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 = {
|