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 +2 -2
- package/dist/index.js +82 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
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
|
|
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
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
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
|
-
|
|
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)
|
|
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.
|
|
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("
|
|
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();
|