@remcp/remcp 0.2.10 → 0.2.12

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/agent.mjs +32 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/remcp",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
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
@@ -106,11 +106,14 @@ export function updateDecision({ advertised, cliVersion, runtimeVersion, runtime
106
106
  // Applies a freshly installed version. Exiting is what a supervisor needs; without one the new CLI is
107
107
  // started in this process' place. Either way the agent stops holding a stale runtime, which is what
108
108
  // makes an update actually take effect on a machine that no service manager watches.
109
- async function restartToApplyUpdate(cli, stopAgent, markStopping) {
109
+ async function restartToApplyUpdate(cli, stopAgent, markStopping, onRuntimeRepaired) {
110
110
  try {
111
111
  const installed = globalInstalledVersion();
112
112
  if (installed && !isNewer(installed, VERSION)) {
113
- console.log(`ReMCP ${VERSION} is already the installed version; nothing to restart.`);
113
+ // The client is current, so the update was a runtime repair: restart the runtime rather than
114
+ // the whole agent, or a device with no usable runtime would stay broken.
115
+ console.log(`ReMCP ${VERSION} is already the installed version; restarting the local runtime.`);
116
+ await onRuntimeRepaired?.();
114
117
  return;
115
118
  }
116
119
  console.log(`ReMCP ${installed || 'a newer version'} installed; restarting to apply it.`);
@@ -200,6 +203,9 @@ export async function runAgent(options) {
200
203
  let runtimeVersion = 'unknown';
201
204
  let runtimeRestarts = 0;
202
205
  let runtimeDown = false;
206
+ // The reason the runtime is not running, sent to the server so the workspace can show something
207
+ // actionable instead of a machine that merely looks connected.
208
+ let runtimeError = '';
203
209
  const telemetryQueue = [];
204
210
  let telemetryTimer = null;
205
211
  // A device whose runtime is missing or unreadable must still run the agent: the agent is what
@@ -209,7 +215,8 @@ export async function runAgent(options) {
209
215
  runtimeEntry = localRuntimeEntry(options.runtime);
210
216
  } catch (error) {
211
217
  runtimeDown = true;
212
- console.error(`${error instanceof Error ? error.message : String(error)} The agent keeps running and retries; remcp update reinstalls the runtime.`);
218
+ runtimeError = error instanceof Error ? error.message : String(error);
219
+ console.error(`${runtimeError} The agent keeps running and retries; remcp update reinstalls the runtime.`);
213
220
  }
214
221
 
215
222
  // --- local runtime supervision ------------------------------------------------------
@@ -254,11 +261,15 @@ export async function runAgent(options) {
254
261
  runtimeVersion = client.getServerVersion()?.version || runtimeVersion;
255
262
  runtimeRestarts += 1;
256
263
  runtimeRestartDelay = RUNTIME_RESTART_BASE_MS;
264
+ runtimeDown = false;
265
+ runtimeError = '';
257
266
  console.log(`ReMCP local runtime ready (${runtimeVersion})`);
258
267
  send({ type: 'metrics', metrics: deviceMetrics({ reconnects, pendingRequests, runtimeVersion, runtimeRestarts, runtimeDown: false }) });
259
268
  if (runtimeRestarts > 1) queueEvent({ event: 'runtime_restart', at: Date.now(), count: runtimeRestarts, success: true });
260
269
  } catch (error) {
261
- console.error(`ReMCP local runtime failed to start: ${error instanceof Error ? error.message : String(error)}`);
270
+ runtimeDown = true;
271
+ runtimeError = error instanceof Error ? error.message : String(error);
272
+ console.error(`ReMCP local runtime failed to start: ${runtimeError}`);
262
273
  handleRuntimeExit('failed');
263
274
  }
264
275
  }
@@ -305,6 +316,7 @@ export async function runAgent(options) {
305
316
  platform: process.platform,
306
317
  arch: process.arch,
307
318
  installSpec: String(options.installSpec || ''),
319
+ runtimeState: runtimeDown ? 'down' : 'ready',
308
320
  })) {
309
321
  persistState({ installReported: true });
310
322
  console.log('ReMCP reported this installation to your own workspace (disable with `remcp telemetry off`).');
@@ -361,11 +373,13 @@ export async function runAgent(options) {
361
373
  arch: process.arch,
362
374
  agentVersion: VERSION,
363
375
  runtimeVersion,
376
+ runtimeState: runtimeDown ? 'down' : 'ready',
377
+ runtimeError,
364
378
  telemetryEnabled,
365
379
  reconnects,
366
380
  }));
367
381
  console.log(`Connected to ${serverUrl} as ${deviceName}`);
368
- send({ type: 'metrics', metrics: deviceMetrics({ reconnects, pendingRequests, runtimeVersion, runtimeRestarts, runtimeDown }) });
382
+ send({ type: 'metrics', metrics: deviceMetrics({ reconnects, pendingRequests, runtimeVersion, runtimeRestarts, runtimeDown }), runtimeState: runtimeDown ? 'down' : 'ready', runtimeError });
369
383
  reportInstallOnce();
370
384
  flushTelemetry();
371
385
  void checkForUpdate();
@@ -463,7 +477,19 @@ export async function runAgent(options) {
463
477
  console.error(`remcp update exited with ${code}; keeping ${VERSION} and retrying after the cooldown.`);
464
478
  return;
465
479
  }
466
- void restartToApplyUpdate(cli, stop, () => { stopping = true; });
480
+ void restartToApplyUpdate(cli, stop, () => { stopping = true; }, async () => {
481
+ // The packages are installed now: clear the failure, reset the backoff and start again.
482
+ runtimeRestartDelay = RUNTIME_RESTART_BASE_MS;
483
+ runtimeDown = false;
484
+ runtimeError = '';
485
+ try {
486
+ runtimeEntry = localRuntimeEntry(options.runtime);
487
+ } catch (error) {
488
+ runtimeDown = true;
489
+ runtimeError = error instanceof Error ? error.message : String(error);
490
+ }
491
+ if (!runtimeDown && !stopping) await startRuntime();
492
+ });
467
493
  });
468
494
  child.on('error', error => {
469
495
  updateInFlight = false;