@remcp/remcp 0.2.30 → 0.2.32
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/README.md +29 -1
- package/package.json +1 -1
- package/src/agent.mjs +24 -1
- package/src/cli.mjs +1 -1
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ remcp install Install or repair the user service
|
|
|
27
27
|
remcp uninstall Remove the user service
|
|
28
28
|
remcp uninstall --purge Remove the service and the global packages
|
|
29
29
|
remcp telemetry [status|on|off]
|
|
30
|
+
remcp godmode [status|on|off]
|
|
30
31
|
remcp --version
|
|
31
32
|
```
|
|
32
33
|
|
|
@@ -47,7 +48,8 @@ Two things it deliberately does not do:
|
|
|
47
48
|
or `unrestricted: true` in `~/.config/remcp/runtime.json`) can turn it on. That is what keeps a
|
|
48
49
|
prompt injection from becoming root.
|
|
49
50
|
- **It does not make the agent root.** Commands run as the user the agent runs as. `sudo` is no longer
|
|
50
|
-
blocked, but
|
|
51
|
+
blocked, but the operating system still asks for a password unless your sudoers rules say otherwise;
|
|
52
|
+
a non-interactive command cannot type one. Nothing in this mode grants root by itself.
|
|
51
53
|
|
|
52
54
|
While it is on, `get_runtime_info` reports `policy.unrestricted: true`, so the model can see it and
|
|
53
55
|
say so instead of assuming the guardrails are still there.
|
|
@@ -62,6 +64,32 @@ background service: open **System Settings → Privacy & Security → Full Disk
|
|
|
62
64
|
binary that `remcp doctor` prints, and run `remcp start`. Folders outside those four need no new
|
|
63
65
|
permission, and `remcp doctor` reports the state of each one.
|
|
64
66
|
|
|
67
|
+
## Updates
|
|
68
|
+
|
|
69
|
+
The agent asks the server which versions it should run when it connects and every six hours, installs
|
|
70
|
+
a newer client and runtime in the background, and restarts the service so they take effect. A failed
|
|
71
|
+
install is retried no more often than every thirty minutes, so a broken release cannot turn into an
|
|
72
|
+
install loop. `remcp update` does the same immediately; `remcp auto-update off` turns the automatic
|
|
73
|
+
check off for a machine that must not change on its own.
|
|
74
|
+
|
|
75
|
+
## Screenshots
|
|
76
|
+
|
|
77
|
+
`take_screenshot` returns the screen of this computer as an image, and each desktop keeps its own
|
|
78
|
+
gate — ReMCP names the one that refused instead of printing a generic error:
|
|
79
|
+
|
|
80
|
+
- **macOS** wants Screen Recording for the binary that runs the tools (`remcp doctor` prints its
|
|
81
|
+
path), then `remcp start`.
|
|
82
|
+
- **Windows** needs an unlocked interactive session; a locked or signed-out machine cannot be
|
|
83
|
+
captured.
|
|
84
|
+
- **Linux on Wayland** needs a capture backend: `grim` on wlroots desktops (sway, hyprland), and
|
|
85
|
+
`gnome-screenshot` on GNOME — GNOME refuses the shell's own screenshot API to background processes
|
|
86
|
+
and `grim` cannot read a GNOME session. On X11, `scrot`, ImageMagick `import` or `gnome-screenshot`
|
|
87
|
+
all work.
|
|
88
|
+
- A machine with no graphical session (a server, a container) says so: there is nothing to capture.
|
|
89
|
+
|
|
90
|
+
A screenshot larger than the inline limit is saved on the computer, and the result says where it is
|
|
91
|
+
and how to fetch it in chunks.
|
|
92
|
+
|
|
65
93
|
## What runs on your computer
|
|
66
94
|
|
|
67
95
|
- the agent (`remcp start`), which holds the device credential and dials
|
package/package.json
CHANGED
package/src/agent.mjs
CHANGED
|
@@ -25,6 +25,9 @@ const TELEMETRY_QUEUE_LIMIT = 500;
|
|
|
25
25
|
const TELEMETRY_BATCH_LIMIT = 100;
|
|
26
26
|
const TELEMETRY_SEND_INTERVAL_MS = 5_000;
|
|
27
27
|
const RECONNECT_BASE_MS = 2_000;
|
|
28
|
+
// The first retry after an unexpected drop: fast enough that a blip is invisible, slow enough not to
|
|
29
|
+
// hammer a server that is genuinely down.
|
|
30
|
+
const RECONNECT_FIRST_MS = 500;
|
|
28
31
|
// Must stay above the runtime's own output ceiling (8 MiB), otherwise a large but legal tool result
|
|
29
32
|
// closes the stdio connection and restarts the runtime mid-call.
|
|
30
33
|
const RUNTIME_STDIO_BUFFER_BYTES = 24 * 1024 * 1024;
|
|
@@ -240,6 +243,8 @@ export async function runAgent(options) {
|
|
|
240
243
|
const inFlight = new Map();
|
|
241
244
|
let activeSocket;
|
|
242
245
|
let reconnects = 0;
|
|
246
|
+
// Set when a replica asks this agent to move before it is replaced; the close handler reads it.
|
|
247
|
+
let askedToReconnect = false;
|
|
243
248
|
let pendingRequests = 0;
|
|
244
249
|
let runtimeVersion = 'unknown';
|
|
245
250
|
let runtimeRestarts = 0;
|
|
@@ -444,6 +449,10 @@ export async function runAgent(options) {
|
|
|
444
449
|
ws.on('message', raw => {
|
|
445
450
|
let message;
|
|
446
451
|
try { message = JSON.parse(raw.toString()); } catch { return; }
|
|
452
|
+
if (message?.type === 'reconnect') {
|
|
453
|
+
askedToReconnect = true;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
447
456
|
if (message?.type === 'request') void respond(ws, message);
|
|
448
457
|
if (message?.type === 'cancel' && message.id) {
|
|
449
458
|
const controller = inFlight.get(message.id);
|
|
@@ -453,6 +462,16 @@ export async function runAgent(options) {
|
|
|
453
462
|
ws.on('close', code => {
|
|
454
463
|
for (const controller of inFlight.values()) controller.abort();
|
|
455
464
|
if (stopping) return;
|
|
465
|
+
// 1013 ('reconnect') is what a replica sends before it is replaced by a deployment. The machine
|
|
466
|
+
// is not down, it is moving: reconnect at once and forget the backoff, so the person and the
|
|
467
|
+
// model see nothing at all.
|
|
468
|
+
if (code === 1013 || askedToReconnect) {
|
|
469
|
+
askedToReconnect = false;
|
|
470
|
+
reconnects = 0;
|
|
471
|
+
console.log('ReMCP relay is being redeployed; reconnecting now.');
|
|
472
|
+
setTimeout(connect, 150);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
456
475
|
if (code === 1008 || revoked) {
|
|
457
476
|
// The relay closes with 1008 when the device was revoked. Retrying forever would
|
|
458
477
|
// hide that from the person at the computer.
|
|
@@ -469,7 +488,11 @@ export async function runAgent(options) {
|
|
|
469
488
|
return;
|
|
470
489
|
}
|
|
471
490
|
reconnects += 1;
|
|
472
|
-
|
|
491
|
+
// The first retry after an unexpected drop is quick on purpose: a deploy blip, a proxy restart or
|
|
492
|
+
// a dropped packet should cost a fraction of a second, not the two seconds the backoff starts at.
|
|
493
|
+
const delay = reconnects === 1
|
|
494
|
+
? jitter(RECONNECT_FIRST_MS)
|
|
495
|
+
: jitter(Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * 2 ** Math.min(reconnects, 5)));
|
|
473
496
|
setTimeout(connect, delay);
|
|
474
497
|
});
|
|
475
498
|
ws.on('error', error => console.error(`ReMCP relay: ${error.message}`));
|
package/src/cli.mjs
CHANGED
|
@@ -615,7 +615,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
615
615
|
unrestricted: state,
|
|
616
616
|
source: runtimeFile.unrestricted === true ? runtimeConfigFile : process.env.REMCP_RUNTIME_UNRESTRICTED === '1' ? 'REMCP_RUNTIME_UNRESTRICTED' : 'default',
|
|
617
617
|
meaning: state
|
|
618
|
-
? 'Every path and every command is allowed. Commands run as the user the agent runs as
|
|
618
|
+
? 'Every path and every command is allowed. Commands run as the user the agent runs as, so sudo still needs your own sudoers rules; nothing here grants root by itself.'
|
|
619
619
|
: 'The runtime confines file access to its allowed roots and applies its command guardrails.',
|
|
620
620
|
}, null, 2));
|
|
621
621
|
return;
|