@remcp/remcp 0.2.26 → 0.2.28
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 +12 -1
- package/src/cli.mjs +36 -5
package/package.json
CHANGED
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
|
-
|
|
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
|
@@ -170,7 +170,10 @@ function macLaunchDomain() {
|
|
|
170
170
|
function installMacService(cliPath = globalCliPath()) {
|
|
171
171
|
const domain = macLaunchDomain();
|
|
172
172
|
const target = `${domain}/${macServiceLabel}`;
|
|
173
|
-
|
|
173
|
+
// launchd wants an absolute path; a symlinked prefix that npm has not materialised yet (or a path
|
|
174
|
+
// that is about to be replaced by the next install) must not abort the repair — a stale plist is
|
|
175
|
+
// exactly the loop this function exists to break.
|
|
176
|
+
const cliScript = fs.existsSync(cliPath) ? fs.realpathSync(cliPath) : path.resolve(cliPath);
|
|
174
177
|
fs.mkdirSync(path.dirname(macServiceFile), { recursive: true });
|
|
175
178
|
fs.mkdirSync(path.dirname(macLogFile), { recursive: true });
|
|
176
179
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n<key>Label</key><string>${macServiceLabel}</string>\n<key>ProgramArguments</key><array><string>${xmlEscape(process.execPath)}</string><string>${xmlEscape(cliScript)}</string><string>start</string></array>\n<key>RunAtLoad</key><true/><key>KeepAlive</key><true/>\n<key>ProcessType</key><string>Background</string>\n<key>StandardOutPath</key><string>${xmlEscape(macLogFile)}</string>\n<key>StandardErrorPath</key><string>${xmlEscape(macLogFile)}</string>\n</dict></plist>\n`;
|
|
@@ -255,9 +258,35 @@ function ensureServiceIfRecorded(config) {
|
|
|
255
258
|
try {
|
|
256
259
|
const cliPath = globalCliPath();
|
|
257
260
|
const platform = servicePlatform();
|
|
258
|
-
if (platform === 'linux') {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
+
if (platform === 'linux') {
|
|
262
|
+
if (!fs.existsSync(linuxServiceFile)) {
|
|
263
|
+
installLinuxService(cliPath);
|
|
264
|
+
} else {
|
|
265
|
+
// Node managers can move the global npm prefix between updates (nvm -> Hermes was observed in
|
|
266
|
+
// production). An existing systemd unit then keeps launching the old CLI forever even though
|
|
267
|
+
// npm successfully installed the new one. Repair the launcher in place before restarting it.
|
|
268
|
+
const unit = fs.readFileSync(linuxServiceFile, 'utf8');
|
|
269
|
+
const expected = `ExecStart=${quoteSystemd(cliPath)} start`;
|
|
270
|
+
if (!unit.includes(expected)) {
|
|
271
|
+
const repaired = /^ExecStart=/m.test(unit) ? unit.replace(/^ExecStart=.*$/m, expected) : '';
|
|
272
|
+
if (!repaired) {
|
|
273
|
+
// A unit that lost its ExecStart line (edited by hand, or written as `ExecStart = …`) cannot
|
|
274
|
+
// be patched by substitution. Rewriting the whole unit is what keeps the machine out of the
|
|
275
|
+
// "old CLI forever" loop, so fall back to a fresh install instead of giving up.
|
|
276
|
+
installLinuxService(cliPath);
|
|
277
|
+
} else {
|
|
278
|
+
fs.writeFileSync(linuxServiceFile, repaired);
|
|
279
|
+
run('systemctl', ['--user', 'daemon-reload']);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} else if (platform === 'darwin') {
|
|
284
|
+
// launchd bakes the interpreter and the CLI path into the plist, so a Node manager that moves
|
|
285
|
+
// its global prefix leaves the agent launching a file that no longer exists — the same loop the
|
|
286
|
+
// Linux unit above is repaired for. Reinstalling is idempotent (bootout, bootstrap, enable,
|
|
287
|
+
// kickstart) and is what the Windows task already does on every update.
|
|
288
|
+
installMacService(cliPath);
|
|
289
|
+
} else if (platform === 'win32') installWindowsService(cliPath);
|
|
261
290
|
return true;
|
|
262
291
|
} catch (error) {
|
|
263
292
|
console.error(`Could not ensure the background service: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -646,9 +675,11 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
646
675
|
return;
|
|
647
676
|
}
|
|
648
677
|
const before = { cli: VERSION, runtime: installedVersion(cfg.runtime.packageName) };
|
|
649
|
-
ensureServiceIfRecorded(cfg);
|
|
650
678
|
console.log(`Updating ReMCP to the latest published version (${runtimeSpec})…`);
|
|
651
679
|
npmGlobalInstall(`${PACKAGE_NAME}@latest`, runtimeSpec);
|
|
680
|
+
// The npm prefix can change across Node-manager upgrades. Repair an existing persistent-service
|
|
681
|
+
// launcher only after the install, when globalCliPath() points at the CLI we just installed.
|
|
682
|
+
ensureServiceIfRecorded(cfg);
|
|
652
683
|
// Only a validated spec is persisted, so a failed update cannot leave the install unable to start.
|
|
653
684
|
if (requested && runtimeSpec !== cfg.runtime.packageSpec) saveConfig({ ...cfg, runtime: { ...cfg.runtime, packageSpec: runtimeSpec } });
|
|
654
685
|
const after = { cli: installedVersion(PACKAGE_NAME), runtime: installedVersion(cfg.runtime.packageName) };
|