@remcp/remcp 0.2.26 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/remcp",
3
- "version": "0.2.26",
3
+ "version": "0.2.27",
4
4
  "description": "ReMCP device client: pair a computer with ReMCP and run the outbound-only agent that hosts the local MCP runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/agent.mjs CHANGED
@@ -531,10 +531,21 @@ export async function runAgent(options) {
531
531
  child.stderr?.on('data', forward);
532
532
  child.on('exit', code => {
533
533
  updateInFlight = false;
534
- if (code !== 0) {
534
+ // The updater installs the packages and then restarts the service this agent runs in, so it is
535
+ // routinely killed by that restart and exits with a signal (`code` is null). That is success,
536
+ // not failure: the installed version is the proof. Without this check the agent kept
537
+ // reinstalling every few seconds — each cycle taking the device offline and making every tool
538
+ // call in that window fail in 1-3 ms ("Device is offline").
539
+ const targetVersion = String(target).split('@').pop();
540
+ const installedNow = globalInstalledVersion();
541
+ const installedTarget = installedNow && targetVersion && !isNewer(targetVersion, installedNow);
542
+ if (code !== 0 && !installedTarget) {
535
543
  console.error(`remcp update exited with ${code}; keeping ${VERSION} and retrying after the cooldown.`);
536
544
  return;
537
545
  }
546
+ if (code !== 0) {
547
+ console.error(`remcp update was terminated (${code}) after installing ${installedNow}; applying it.`);
548
+ }
538
549
  void restartToApplyUpdate(cli, stop, () => { stopping = true; }, async () => {
539
550
  // The packages are installed now: clear the failure, reset the backoff and start again.
540
551
  runtimeRestartDelay = RUNTIME_RESTART_BASE_MS;
package/src/cli.mjs CHANGED
@@ -255,9 +255,25 @@ function ensureServiceIfRecorded(config) {
255
255
  try {
256
256
  const cliPath = globalCliPath();
257
257
  const platform = servicePlatform();
258
- if (platform === 'linux') { if (!fs.existsSync(linuxServiceFile)) installLinuxService(cliPath); }
259
- else if (platform === 'darwin') { if (!fs.existsSync(macServiceFile)) installMacService(cliPath); }
260
- else if (platform === 'win32') installWindowsService(cliPath);
258
+ if (platform === 'linux') {
259
+ if (!fs.existsSync(linuxServiceFile)) {
260
+ installLinuxService(cliPath);
261
+ } else {
262
+ // Node managers can move the global npm prefix between updates (nvm -> Hermes was observed in
263
+ // production). An existing systemd unit then keeps launching the old CLI forever even though
264
+ // npm successfully installed the new one. Repair the launcher in place before restarting it.
265
+ const unit = fs.readFileSync(linuxServiceFile, 'utf8');
266
+ const expected = `ExecStart=${quoteSystemd(cliPath)} start`;
267
+ if (!unit.includes(expected)) {
268
+ const repaired = unit.replace(/^ExecStart=.*$/m, expected);
269
+ if (repaired === unit) throw new Error('remcp-agent.service has no ExecStart line to repair');
270
+ fs.writeFileSync(linuxServiceFile, repaired);
271
+ run('systemctl', ['--user', 'daemon-reload']);
272
+ }
273
+ }
274
+ } else if (platform === 'darwin') {
275
+ if (!fs.existsSync(macServiceFile)) installMacService(cliPath);
276
+ } else if (platform === 'win32') installWindowsService(cliPath);
261
277
  return true;
262
278
  } catch (error) {
263
279
  console.error(`Could not ensure the background service: ${error instanceof Error ? error.message : String(error)}`);
@@ -646,9 +662,11 @@ export async function main(argv = process.argv.slice(2)) {
646
662
  return;
647
663
  }
648
664
  const before = { cli: VERSION, runtime: installedVersion(cfg.runtime.packageName) };
649
- ensureServiceIfRecorded(cfg);
650
665
  console.log(`Updating ReMCP to the latest published version (${runtimeSpec})…`);
651
666
  npmGlobalInstall(`${PACKAGE_NAME}@latest`, runtimeSpec);
667
+ // The npm prefix can change across Node-manager upgrades. Repair an existing persistent-service
668
+ // launcher only after the install, when globalCliPath() points at the CLI we just installed.
669
+ ensureServiceIfRecorded(cfg);
652
670
  // Only a validated spec is persisted, so a failed update cannot leave the install unable to start.
653
671
  if (requested && runtimeSpec !== cfg.runtime.packageSpec) saveConfig({ ...cfg, runtime: { ...cfg.runtime, packageSpec: runtimeSpec } });
654
672
  const after = { cli: installedVersion(PACKAGE_NAME), runtime: installedVersion(cfg.runtime.packageName) };