remotelink 0.4.4 → 0.4.6

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/README.md CHANGED
@@ -53,7 +53,7 @@ npx remotelink --help Show help
53
53
 
54
54
  The package also exposes the aliases `remote-link` and `remote-arc`.
55
55
 
56
- In the 0.4.4 candidate, background recovery and Relay reconnect are separate.
56
+ In 0.4.6, background recovery and Relay reconnect are separate. A normal npx remotelink@latest launch can safely hand off a known older background Agent to the current release without creating two executors.
57
57
  Recovery installs a user service (macOS/Linux) or a hidden login supervisor
58
58
  (Windows), which takes over when the executing Agent ends. Only one updated
59
59
  Agent owns execution; additional terminals follow its operation log and offer
@@ -62,7 +62,7 @@ execution lease. Turning recovery off preserves current execution. Explicit
62
62
  Stop background Agent also ends that Agent's work. Windows recovery needs the
63
63
  supervisor to remain running; killing it requires a local relaunch or login.
64
64
  Sleep/power loss still makes the device unavailable. All concurrent instances
65
- must be updated; pre-0.4.4 Agents do not participate in the execution lease.
65
+ must be updated; pre-0.4.4 Agents do not participate in the execution lease. When the installed version is identified as an older release, 0.4.6 performs a controlled handoff even when that older Agent still owns the execution lease: it stops the known Remote Arc worker and supervisor, waits for the lease to release, preserves pairing and policy, installs the replacement bundle, and verifies that the replacement worker owns execution. Unknown processes still fail closed instead of being killed automatically. Unknown legacy processes still fail closed instead of being killed automatically.
66
66
 
67
67
  ## Native execution core
68
68
 
package/dist/index.js CHANGED
@@ -6959,6 +6959,20 @@ import fs6 from "fs/promises";
6959
6959
  import { spawn as spawn4 } from "child_process";
6960
6960
  var LegacyBackgroundAgentError = class extends Error {
6961
6961
  };
6962
+ function parseReleaseVersion(value) {
6963
+ if (!value) return null;
6964
+ const match = value.trim().match(/^(\d+)\.(\d+)\.(\d+)$/);
6965
+ if (!match) return null;
6966
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
6967
+ }
6968
+ function isOlderRelease(current, target) {
6969
+ const a = parseReleaseVersion(current);
6970
+ const b = parseReleaseVersion(target);
6971
+ if (!a || !b) return false;
6972
+ if (a[0] !== b[0]) return a[0] < b[0];
6973
+ if (a[1] !== b[1]) return a[1] < b[1];
6974
+ return a[2] < b[2];
6975
+ }
6962
6976
  function windowsSupervisorLaunchScript(node, bundle) {
6963
6977
  const quote = (value) => "'" + value.replaceAll("'", "''") + "'";
6964
6978
  return "Start-Process -FilePath " + quote(node) + " -ArgumentList " + quote('"' + bundle + '" --supervise') + " -WindowStyle Hidden | Out-Null";
@@ -7325,16 +7339,55 @@ WantedBy=default.target
7325
7339
  async function enableBackgroundAgent2(sourcePath, options2 = {}) {
7326
7340
  const preserveCurrent = options2.preserveCurrent ?? false;
7327
7341
  const before = await backgroundAgentStatus2();
7342
+ let upgradeHandoff = false;
7328
7343
  if (before.workerPid && before.workerPid !== process.pid) {
7329
- let participates = await leaseActive(AGENT_DIR);
7330
- for (let attempt = 0; !participates && attempt < 6; attempt++) {
7331
- await wait(500);
7332
- participates = await leaseActive(AGENT_DIR);
7333
- }
7334
- if (!participates)
7335
- throw new LegacyBackgroundAgentError(
7336
- "An existing background Agent does not hold the execution lease. Stop the older Agent locally before upgrading; this launch will not create a competing executor."
7337
- );
7344
+ const knownOlderRelease = before.enabled && isOlderRelease(before.version, options2.version);
7345
+ if (knownOlderRelease) {
7346
+ upgradeHandoff = true;
7347
+ if (platform === "darwin") await disableMac(true);
7348
+ else if (platform === "win32") await disableWindows(true);
7349
+ else if (platform === "linux") await disableLinux(true);
7350
+ else {
7351
+ throw new LegacyBackgroundAgentError(
7352
+ "The existing background Agent is older, but automatic handoff is not supported on this operating system."
7353
+ );
7354
+ }
7355
+ let stopped = false;
7356
+ for (let attempt = 0; attempt < 40; attempt++) {
7357
+ const status2 = await backgroundAgentStatus2();
7358
+ if (!status2.workerPid) {
7359
+ stopped = true;
7360
+ break;
7361
+ }
7362
+ await wait(250);
7363
+ }
7364
+ if (!stopped) {
7365
+ throw new LegacyBackgroundAgentError(
7366
+ "The older background Agent did not stop cleanly. Automatic upgrade was aborted before installing the new executor."
7367
+ );
7368
+ }
7369
+ let leaseReleased = !await leaseActive(AGENT_DIR);
7370
+ for (let attempt = 0; !leaseReleased && attempt < 60; attempt++) {
7371
+ await wait(250);
7372
+ leaseReleased = !await leaseActive(AGENT_DIR);
7373
+ }
7374
+ if (!leaseReleased) {
7375
+ throw new LegacyBackgroundAgentError(
7376
+ "The older background Agent stopped, but its execution lease did not release in time. Automatic upgrade was aborted before starting the replacement executor."
7377
+ );
7378
+ }
7379
+ } else {
7380
+ let participates = await leaseActive(AGENT_DIR);
7381
+ for (let attempt = 0; !participates && attempt < 6; attempt++) {
7382
+ await wait(500);
7383
+ participates = await leaseActive(AGENT_DIR);
7384
+ }
7385
+ if (!participates) {
7386
+ throw new LegacyBackgroundAgentError(
7387
+ "An existing background Agent does not hold the execution lease and cannot be identified as a safe older release. Stop it locally before retrying; this launch will not create a competing executor."
7388
+ );
7389
+ }
7390
+ }
7338
7391
  }
7339
7392
  if (platform === "darwin")
7340
7393
  await enableMac(sourcePath, preserveCurrent, options2.version);
@@ -7343,11 +7396,17 @@ WantedBy=default.target
7343
7396
  else if (platform === "linux")
7344
7397
  await enableLinux(sourcePath, preserveCurrent, options2.version);
7345
7398
  else return backgroundAgentStatus2();
7346
- for (let attempt = 0; attempt < 20; attempt++) {
7399
+ const verifyAttempts = upgradeHandoff ? 80 : 20;
7400
+ for (let attempt = 0; attempt < verifyAttempts; attempt++) {
7347
7401
  const status2 = await backgroundAgentStatus2();
7348
- if (status2.enabled && status2.active) return status2;
7402
+ if (status2.enabled && status2.active) {
7403
+ if (!upgradeHandoff) return status2;
7404
+ const versionMatches = !options2.version || status2.version === options2.version;
7405
+ const workerOwnsLease = Boolean(status2.workerPid) && await leaseActive(AGENT_DIR);
7406
+ if (versionMatches && workerOwnsLease) return status2;
7407
+ }
7349
7408
  if (!status2.supported) return status2;
7350
- await wait(200);
7409
+ await wait(upgradeHandoff ? 250 : 200);
7351
7410
  }
7352
7411
  const status = await backgroundAgentStatus2();
7353
7412
  throw new Error(
@@ -7374,7 +7433,7 @@ var {
7374
7433
  } = createBackgroundController();
7375
7434
 
7376
7435
  // src/index.ts
7377
- var VERSION = "0.4.4";
7436
+ var VERSION = "0.4.6";
7378
7437
  var DEFAULT_ORIGIN = "https://mcp.remotearc.app";
7379
7438
  var CONFIG_DIR = path7.join(os4.homedir(), ".remotearc");
7380
7439
  var CONFIG_PATH = path7.join(CONFIG_DIR, "config.json");
@@ -7499,7 +7558,7 @@ function logLine(level, message) {
7499
7558
  function banner() {
7500
7559
  process3.stdout.write("\n");
7501
7560
  process3.stdout.write(bold("Remote Arc") + " " + dim(`v${VERSION}`) + "\n");
7502
- process3.stdout.write(dim("Secure remote MCP bridge") + "\n\n");
7561
+ process3.stdout.write(dim("Controlled remote access for AI") + "\n\n");
7503
7562
  }
7504
7563
  function argFlag(name) {
7505
7564
  return process3.argv.includes(name);
@@ -7747,7 +7806,8 @@ async function connectAgent(config) {
7747
7806
  recoveryEnabled: recovery?.enabled ?? false,
7748
7807
  supervisorActive: recovery?.active,
7749
7808
  supervisorPid: recovery?.pid,
7750
- supervisorService: recovery?.service
7809
+ supervisorService: recovery?.service,
7810
+ recoveryVersion: recovery?.version ?? null
7751
7811
  },
7752
7812
  tools: [...tools.map((tool) => tool.name), ...internalTools],
7753
7813
  capabilities: [
@@ -8032,6 +8092,13 @@ async function main() {
8032
8092
  if (agentMode && config.backgroundEnabled !== true) return;
8033
8093
  if (!agentMode && !foregroundMode && config.backgroundEnabled === true) {
8034
8094
  try {
8095
+ const existing = await backgroundAgentStatus().catch(() => null);
8096
+ if (existing?.active && existing.version && existing.version !== VERSION) {
8097
+ logLine(
8098
+ "info",
8099
+ `Background Agent v${existing.version} detected \xB7 checking safe handoff to v${VERSION}\u2026`
8100
+ );
8101
+ }
8035
8102
  const status = await enableBackgroundAgent(SELF_PATH, { version: VERSION });
8036
8103
  if (status.supported && status.enabled && status.active) {
8037
8104
  banner();