@remcp/remcp 0.2.158 → 0.2.159

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.158",
3
+ "version": "0.2.159",
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-responder.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/npm-global.mjs && node --check src/cli/service-inventory.mjs && node --check src/cli/service.mjs && node --check src/cli/shell.mjs && node --check src/cli/update.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-responder.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/npm-global.mjs && node --check src/cli/service-inventory.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/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": {
@@ -15,6 +15,7 @@ import { PACKAGE_NAME, VERSION } from '../version.mjs';
15
15
  import { saveConfig } from './config.mjs';
16
16
  import { configDir, home, linuxServiceFile, linuxServiceLauncherFile, macLogFile, macServiceFile, macServiceLabel, npm, windowsServiceLauncherFile, windowsTaskName } from './env.mjs';
17
17
  import { output, run } from './shell.mjs';
18
+ import { windowsServiceLauncherText, windowsTaskReliabilityCommand } from './windows-supervisor.mjs';
18
19
 
19
20
  export function quoteSystemd(value) {
20
21
  return `"${String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
@@ -233,22 +234,8 @@ export function installMacService(cliPath = globalCliPath(), { restart = true, n
233
234
  return 'bootstrapped';
234
235
  }
235
236
 
236
- function windowsBatchValue(value) {
237
- return String(value).replaceAll('%', '%%');
238
- }
239
-
240
237
  export function writeWindowsServiceLauncher(cliPath = globalCliPath(), nodePath = process.execPath) {
241
- const prefix = path.dirname(cliPath);
242
- const nodeDir = path.dirname(nodePath);
243
- const launcher = [
244
- '@echo off',
245
- 'setlocal DisableDelayedExpansion',
246
- `set "NPM_CONFIG_PREFIX=${windowsBatchValue(prefix)}"`,
247
- `set "PATH=${windowsBatchValue(nodeDir)};%PATH%"`,
248
- `call "${windowsBatchValue(cliPath)}" start --service`,
249
- 'exit /b %ERRORLEVEL%',
250
- '',
251
- ].join('\r\n');
238
+ const launcher = windowsServiceLauncherText(cliPath, nodePath);
252
239
  fs.mkdirSync(configDir, { recursive:true, mode:0o700 });
253
240
  const temporary = `${windowsServiceLauncherFile}.${process.pid}.${Date.now()}.tmp`;
254
241
  fs.writeFileSync(temporary, launcher, { mode:0o700 });
@@ -263,6 +250,9 @@ export function installWindowsService(cliPath = globalCliPath(), { nodePath = pr
263
250
  // instead of changing it afterwards: schtasks /Change can request the account password even when
264
251
  // the task already belongs to the current user, which turns a one-command install into a prompt.
265
252
  run('schtasks.exe', ['/Create', '/TN', windowsTaskName, '/TR', command, '/SC', 'ONLOGON', '/RL', 'HIGHEST', '/IT', '/F']);
253
+ // The default Windows task policy expires long-running tasks and stops them on battery.
254
+ // Apply guarded failure recovery without changing the interactive-user principal/session.
255
+ run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', windowsTaskReliabilityCommand(windowsTaskName)]);
266
256
  run('schtasks.exe', ['/Run', '/TN', windowsTaskName]);
267
257
  }
268
258
 
@@ -0,0 +1,55 @@
1
+ // Recovery for the signed-in user's interactive ReMCP agent on Windows.
2
+ // The desktop UI cannot run in Session 0: this intentionally retains /IT and ONLOGON.
3
+ import path from 'node:path';
4
+
5
+ function windowsBatchValue(value) {
6
+ // cmd.exe expands % even inside quoted strings. Escape user-controlled prefix paths.
7
+ return String(value).replaceAll('%', '%%');
8
+ }
9
+
10
+ export function windowsServiceLauncherText(cliPath, nodePath) {
11
+ // Tests can simulate win32 from a POSIX runner with injected temporary paths.
12
+ // Native Windows paths always use win32 parsing; POSIX-shaped fixtures keep their roots.
13
+ const dirname = file => /^[A-Za-z]:[\\/]/.test(file) || file.includes('\\')
14
+ ? path.win32.dirname(file) : path.dirname(file);
15
+ const prefix = dirname(cliPath);
16
+ const nodeDir = dirname(nodePath);
17
+ return [
18
+ '@echo off',
19
+ 'setlocal DisableDelayedExpansion',
20
+ `set "NPM_CONFIG_PREFIX=${windowsBatchValue(prefix)}"`,
21
+ `set "PATH=${windowsBatchValue(nodeDir)};%PATH%"`,
22
+ ':remcp_restart',
23
+ `call "${windowsBatchValue(cliPath)}" start --service`,
24
+ // Restart even after a clean unexpected exit. A deliberate stop ends the Scheduled Task
25
+ // process tree, so this supervisor cannot resurrect an intentionally stopped service.
26
+ 'rem Reconnect after an interrupted upgrade or a transient crash without logging in again.',
27
+ // timeout.exe may fail when Scheduler redirects stdin; loopback ping supplies the delay
28
+ // without stdin, a helper service, or any public network dependency.
29
+ 'ping.exe -n 6 127.0.0.1 >nul',
30
+ 'goto remcp_restart',
31
+ '',
32
+ ].join('\r\n');
33
+ }
34
+
35
+ export function windowsTaskReliabilityCommand(taskName) {
36
+ if (typeof taskName !== 'string' || !/^[A-Za-z0-9 _-]{1,80}$/.test(taskName)) {
37
+ throw new Error('Invalid Windows task name');
38
+ }
39
+ const safeName = taskName.replaceAll("'", "''");
40
+ return [
41
+ "$ErrorActionPreference = 'Stop'",
42
+ `$taskName = '${safeName}'`,
43
+ // Never default to 72h termination. Keep a single interactive instance and
44
+ // allow running on battery. Scheduler recovers the wrapper if cmd itself fails.
45
+ '$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) ' +
46
+ '-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ' +
47
+ '-MultipleInstances IgnoreNew -RestartCount 99 ' +
48
+ '-RestartInterval (New-TimeSpan -Minutes 1) -StartWhenAvailable',
49
+ 'Set-ScheduledTask -TaskName $taskName -Settings $settings -ErrorAction Stop | Out-Null',
50
+ // Verify the OS accepted the important properties instead of assuming success.
51
+ '$configured = (Get-ScheduledTask -TaskName $taskName -ErrorAction Stop).Settings',
52
+ "if ($configured.ExecutionTimeLimit -ne 'PT0S' -or $configured.RestartCount -lt 1) {" +
53
+ " throw 'Windows Task Scheduler did not persist the ReMCP recovery policy' }",
54
+ ].join('; ');
55
+ }