@remcp/remcp 0.2.216 → 0.2.218

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.216",
3
+ "version": "0.2.218",
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",
@@ -18,7 +18,7 @@
18
18
  "README.md"
19
19
  ],
20
20
  "scripts": {
21
- "check": "node --check bin/remcp.mjs && node --check src/agent-deploy-reconnect.mjs && node --check src/agent-network-probe-client.mjs && node --check src/agent-relay-connection.mjs && node --check src/agent-responder.mjs && node --check src/agent-runtime-capabilities.mjs && node --check src/agent-update-monitor.mjs && node --check src/agent-update.mjs && node --check src/agent-utilities.mjs && node --check src/agent.mjs && node --check src/cli.mjs && node --check src/cli/config.mjs && node --check src/cli/connect.mjs && node --check src/cli/doctor.mjs && node --check src/cli/env.mjs && node --check src/cli/installation-audit.mjs && node --check src/cli/mac-native-temperature.mjs && node --check src/cli/npm-global.mjs && node --check src/cli/pairing-qr.mjs && node --check src/cli/service-inventory.mjs && node --check src/cli/service-macos.mjs && node --check src/cli/service-paths.mjs && node --check src/cli/service.mjs && node --check src/cli/shell.mjs && node --check src/cli/update.mjs && node --check src/cli/windows-supervisor.mjs && node --check src/cli/windows-task-restart.mjs && node --check src/cli/windows-user-supervisor.mjs && node --check src/fetch-update-manifest.mjs && node --check src/fs-access.mjs && node --check src/npm.mjs && node --check src/runtime.mjs && node --check src/version.mjs",
21
+ "check": "node --check bin/remcp.mjs && node --check src/agent-deploy-reconnect.mjs && node --check src/agent-network-probe-client.mjs && node --check src/agent-relay-connection.mjs && node --check src/agent-responder.mjs && node --check src/agent-runtime-capabilities.mjs && node --check src/agent-update-monitor.mjs && node --check src/agent-update.mjs && node --check src/agent-utilities.mjs && node --check src/agent.mjs && node --check src/cli.mjs && node --check src/cli/config.mjs && node --check src/cli/connect.mjs && node --check src/cli/doctor.mjs && node --check src/cli/env.mjs && node --check src/cli/installation-audit.mjs && node --check src/cli/mac-native-temperature.mjs && node --check src/cli/npm-global.mjs && node --check src/cli/pairing-qr.mjs && node --check src/cli/service-inventory.mjs && node --check src/cli/service-macos.mjs && node --check src/cli/service-paths.mjs && node --check src/cli/service.mjs && node --check src/cli/shell.mjs && node --check src/cli/update.mjs && node --check src/cli/windows-supervisor.mjs && node --check src/cli/windows-task-restart.mjs && node --check src/cli/windows-update-lock.mjs && node --check src/cli/windows-user-supervisor.mjs && node --check src/fetch-update-manifest.mjs && node --check src/fs-access.mjs && node --check src/npm.mjs && node --check src/runtime.mjs && node --check src/version.mjs",
22
22
  "test": "node --test test/*.test.mjs"
23
23
  },
24
24
  "dependencies": {
@@ -6,6 +6,7 @@ import { PACKAGE_NAME } from '../version.mjs';
6
6
  import { home, npm } from './env.mjs';
7
7
  import { buildVettedMacTemperatureAddon } from './mac-native-temperature.mjs';
8
8
  import { output, run } from './shell.mjs';
9
+ import { withWindowsUpdateLock } from './windows-update-lock.mjs';
9
10
 
10
11
  export function servicePlatform() {
11
12
  return process.env.NODE_ENV === 'test' && process.env.REMCP_TEST_PLATFORM ? process.env.REMCP_TEST_PLATFORM : process.platform;
@@ -53,8 +54,10 @@ function npmGlobalInstallArgs(specs, { preferOnline = false } = {}) {
53
54
  }
54
55
 
55
56
  export function npmGlobalInstall(...specs) {
56
- assertSafeReMcpNpmPrefix(globalPrefix()); // Reject an unsafe prefix before npm writes anything.
57
- run(npm.command, [...npm.args, ...npmGlobalInstallArgs(specs)]);
57
+ const prefix = assertSafeReMcpNpmPrefix(globalPrefix()); // Reject an unsafe prefix before npm writes anything.
58
+ const install = () => run(npm.command, [...npm.args, ...npmGlobalInstallArgs(specs)]);
59
+ if (servicePlatform() === 'win32') withWindowsUpdateLock(prefix, install);
60
+ else install();
58
61
  if (servicePlatform() === 'darwin' && specs.some(spec => /^@remcp\/runtime@[0-9]/.test(spec))) {
59
62
  try {
60
63
  buildVettedMacTemperatureAddon({
@@ -241,7 +244,7 @@ export function npmGlobalUpdate(packageNames, ...specs) {
241
244
  return;
242
245
  }
243
246
  if (platform === 'win32') {
244
- transactionalWindowsGlobalInstall({ resolved:npm, prefix, packageNames, specs, preferOnline:true });
247
+ withWindowsUpdateLock(prefix, () => transactionalWindowsGlobalInstall({ resolved:npm, prefix, packageNames, specs, preferOnline:true }));
245
248
  return;
246
249
  }
247
250
  run(npm.command, [...npm.args, ...npmGlobalInstallArgs(specs, { preferOnline:true })]);
@@ -256,7 +259,7 @@ export function npmGlobalUpdateForNode(nodePath, packageNames, ...specs) {
256
259
  return;
257
260
  }
258
261
  if (platform === 'win32') {
259
- transactionalWindowsGlobalInstall({ resolved, prefix, packageNames, specs, preferOnline:true });
262
+ withWindowsUpdateLock(prefix, () => transactionalWindowsGlobalInstall({ resolved, prefix, packageNames, specs, preferOnline:true }));
260
263
  return;
261
264
  }
262
265
  run(resolved.command, [...resolved.args, ...npmGlobalInstallArgs(specs, { preferOnline:true })]);
@@ -0,0 +1,77 @@
1
+ // Serializes writes to one Windows npm global prefix across manual and agent updates.
2
+ // The lock exists outside the npm-managed package/shim trees and is held through rollback.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import crypto from 'node:crypto';
6
+ import process from 'node:process';
7
+
8
+ const LOCK_NAME = '.remcp-update-lock';
9
+ const OWNER_NAME = 'owner.json';
10
+
11
+ function lockOwnerFile(directory) {
12
+ return path.join(directory, OWNER_NAME);
13
+ }
14
+
15
+ function ownerIsLive(directory) {
16
+ try {
17
+ const owner = JSON.parse(fs.readFileSync(lockOwnerFile(directory), 'utf8'));
18
+ if (!Number.isSafeInteger(owner.pid) || owner.pid <= 0) return true;
19
+ process.kill(owner.pid, 0);
20
+ return true;
21
+ } catch (error) {
22
+ // On Windows and Unix, only ESRCH is reliable evidence the owner has exited.
23
+ // Permission errors and damaged/incomplete owner metadata must fail closed.
24
+ return error?.code !== 'ESRCH';
25
+ }
26
+ }
27
+
28
+ function removeOwnedLock(directory, token) {
29
+ let owner;
30
+ try {
31
+ if (!fs.lstatSync(directory).isDirectory()) return;
32
+ owner = JSON.parse(fs.readFileSync(lockOwnerFile(directory), 'utf8'));
33
+ } catch { return; }
34
+ if (owner.token !== token || owner.pid !== process.pid) return;
35
+ try {
36
+ fs.unlinkSync(lockOwnerFile(directory));
37
+ fs.rmdirSync(directory);
38
+ } catch (error) {
39
+ // Never force-remove unexpected files created by another process.
40
+ if (error?.code !== 'ENOENT') console.error('ReMCP update lock cleanup failed:', error?.message);
41
+ }
42
+ }
43
+
44
+ export function withWindowsUpdateLock(prefix, operation) {
45
+ if (!path.isAbsolute(prefix)) throw new Error('Windows update lock requires an absolute npm prefix.');
46
+ if (typeof operation !== 'function') throw new TypeError('Windows update operation is required.');
47
+ fs.mkdirSync(prefix, { recursive:true });
48
+ const directory = path.join(prefix, LOCK_NAME);
49
+ try {
50
+ fs.mkdirSync(directory, { mode:0o700 });
51
+ } catch (error) {
52
+ if (error?.code !== 'EEXIST') throw error;
53
+ const stale = !ownerIsLive(directory);
54
+ throw new Error(stale
55
+ ? 'ReMCP previous Windows update exited without releasing its lock; repair the stale update lock before retrying.'
56
+ : 'ReMCP update already in progress for this Windows npm prefix; retry after it finishes.');
57
+ }
58
+
59
+ const token = crypto.randomUUID();
60
+ try {
61
+ fs.writeFileSync(lockOwnerFile(directory), JSON.stringify({
62
+ pid:process.pid, token, startedAt:Date.now(),
63
+ }), { flag:'wx', mode:0o600 });
64
+ } catch (error) {
65
+ try { fs.rmdirSync(directory); } catch {}
66
+ throw error;
67
+ }
68
+
69
+ const onExit = () => removeOwnedLock(directory, token);
70
+ process.once('exit', onExit);
71
+ try {
72
+ return operation();
73
+ } finally {
74
+ process.removeListener('exit', onExit);
75
+ removeOwnedLock(directory, token);
76
+ }
77
+ }