@remcp/remcp 0.2.46 → 0.2.47
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/agent.mjs +9 -0
- package/src/cli/env.mjs +1 -0
- package/src/cli/service.mjs +24 -9
package/package.json
CHANGED
package/src/agent.mjs
CHANGED
|
@@ -286,6 +286,15 @@ export async function runAgent(options) {
|
|
|
286
286
|
// Service access is a user-controlled pause, not an outage. Poll slowly enough not to hammer
|
|
287
287
|
// the relay, but cap recovery so an enable action becomes effective within seconds.
|
|
288
288
|
scheduleReconnect(jitter(RECONNECT_BASE_MS));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const status = Number(response.statusCode || 0);
|
|
292
|
+
const transientHandshakeFailure = status === 408 || status === 425 || status === 429 || (status >= 500 && status <= 599);
|
|
293
|
+
if (transientHandshakeFailure && !revoked) {
|
|
294
|
+
// A reverse proxy or relay deploy can refuse the HTTP upgrade before a WebSocket exists.
|
|
295
|
+
// With an unexpected-response listener the client does not reliably emit close afterwards,
|
|
296
|
+
// so this branch must schedule its own retry or a still-running agent can stay offline forever.
|
|
297
|
+
scheduleBackoffReconnect();
|
|
289
298
|
}
|
|
290
299
|
});
|
|
291
300
|
activeSocket = ws;
|
package/src/cli/env.mjs
CHANGED
|
@@ -11,6 +11,7 @@ export const configFile = path.join(configDir, 'config.json');
|
|
|
11
11
|
export const runtimeConfigFile = path.join(configDir, 'runtime.json');
|
|
12
12
|
export const machineIdFile = path.join(configDir, 'machine-id');
|
|
13
13
|
export const linuxServiceFile = path.join(home, '.config', 'systemd', 'user', 'remcp-agent.service');
|
|
14
|
+
export const linuxServiceLauncherFile = path.join(configDir, 'remcp-agent-launcher');
|
|
14
15
|
export const macServiceLabel = 'com.remcp.agent';
|
|
15
16
|
export const macServiceFile = path.join(home, 'Library', 'LaunchAgents', `${macServiceLabel}.plist`);
|
|
16
17
|
export const macLogFile = path.join(home, 'Library', 'Logs', 'remcp-agent.log');
|
package/src/cli/service.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
8
8
|
import { PACKAGE_NAME, VERSION } from '../version.mjs';
|
|
9
9
|
|
|
10
10
|
import { saveConfig } from './config.mjs';
|
|
11
|
-
import { configDir, home, linuxServiceFile, macLogFile, macServiceFile, macServiceLabel, npm, windowsTaskName } from './env.mjs';
|
|
11
|
+
import { configDir, home, linuxServiceFile, linuxServiceLauncherFile, macLogFile, macServiceFile, macServiceLabel, npm, windowsTaskName } from './env.mjs';
|
|
12
12
|
import { output, run } from './shell.mjs';
|
|
13
13
|
|
|
14
14
|
export function servicePlatform() {
|
|
@@ -32,12 +32,28 @@ export function quoteSystemd(value) {
|
|
|
32
32
|
return `"${String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function resolvedCliScript(cliPath) {
|
|
36
|
+
return fs.existsSync(cliPath) ? fs.realpathSync(cliPath) : path.resolve(cliPath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function writeLinuxServiceLauncher(cliPath = globalCliPath()) {
|
|
40
|
+
const cliScript = resolvedCliScript(cliPath);
|
|
41
|
+
const launcher = `#!/bin/sh\nexec ${shellQuote(process.execPath)} ${shellQuote(cliScript)} \"$@\"\n`;
|
|
42
|
+
fs.mkdirSync(path.dirname(linuxServiceLauncherFile), { recursive: true, mode: 0o700 });
|
|
43
|
+
const temporary = `${linuxServiceLauncherFile}.${process.pid}.${Date.now()}.tmp`;
|
|
44
|
+
fs.writeFileSync(temporary, launcher, { mode: 0o700 });
|
|
45
|
+
fs.chmodSync(temporary, 0o700);
|
|
46
|
+
fs.renameSync(temporary, linuxServiceLauncherFile);
|
|
47
|
+
return linuxServiceLauncherFile;
|
|
48
|
+
}
|
|
49
|
+
|
|
35
50
|
export function xmlEscape(value) {
|
|
36
51
|
return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
|
37
52
|
}
|
|
38
53
|
|
|
39
54
|
export function installLinuxService(cliPath = globalCliPath()) {
|
|
40
|
-
const
|
|
55
|
+
const launcherFile = writeLinuxServiceLauncher(cliPath);
|
|
56
|
+
const unit = `[Unit]\nDescription=ReMCP device agent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=${quoteSystemd(launcherFile)} start\nRestart=always\nRestartSec=3\nNoNewPrivileges=true\n\n[Install]\nWantedBy=default.target\n`;
|
|
41
57
|
fs.mkdirSync(path.dirname(linuxServiceFile), { recursive: true });
|
|
42
58
|
fs.writeFileSync(linuxServiceFile, unit);
|
|
43
59
|
run('systemctl', ['--user', 'daemon-reload']);
|
|
@@ -229,17 +245,16 @@ export function ensureServiceIfRecorded(config) {
|
|
|
229
245
|
if (!fs.existsSync(linuxServiceFile)) {
|
|
230
246
|
installLinuxService(cliPath);
|
|
231
247
|
} else {
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
248
|
+
// Keep the systemd unit independent of nvm/Hermes/Homebrew prefixes. Only this small launcher
|
|
249
|
+
// changes when npm or Node moves, so every install converges on one supervisor entrypoint.
|
|
250
|
+
const launcherFile = writeLinuxServiceLauncher(cliPath);
|
|
235
251
|
const unit = fs.readFileSync(linuxServiceFile, 'utf8');
|
|
236
|
-
const expected = `ExecStart=${quoteSystemd(
|
|
252
|
+
const expected = `ExecStart=${quoteSystemd(launcherFile)} start`;
|
|
237
253
|
if (!unit.includes(expected)) {
|
|
238
254
|
const repaired = /^ExecStart=/m.test(unit) ? unit.replace(/^ExecStart=.*$/m, expected) : '';
|
|
239
255
|
if (!repaired) {
|
|
240
|
-
// A unit
|
|
241
|
-
//
|
|
242
|
-
// "old CLI forever" loop, so fall back to a fresh install instead of giving up.
|
|
256
|
+
// A hand-edited unit with no launcher line is replaced wholesale; the stable launcher is
|
|
257
|
+
// still refreshed first so the replacement never points back at a retired Node manager.
|
|
243
258
|
installLinuxService(cliPath);
|
|
244
259
|
} else {
|
|
245
260
|
fs.writeFileSync(linuxServiceFile, repaired);
|