@proagentstore/cli 0.4.149 → 0.4.152
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/dist/bootstrap.js +74 -9
- package/dist/chunk-PKMXQJEJ.js +281 -0
- package/dist/index.js +519 -120
- package/package.json +1 -1
- package/dist/chunk-NNA7VZ6W.js +0 -138
package/dist/index.js
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
import {
|
|
3
3
|
BOOTSTRAP_ENV,
|
|
4
4
|
CLI_PACKAGE,
|
|
5
|
+
badVersions,
|
|
6
|
+
beginTrial,
|
|
5
7
|
installPayload,
|
|
6
8
|
isVersion,
|
|
7
9
|
latestPublishedRelease,
|
|
10
|
+
logUpdateStep,
|
|
11
|
+
markPayloadHealthy,
|
|
8
12
|
olderThan,
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
payloadRoot,
|
|
14
|
+
runNpm,
|
|
15
|
+
takeRollbackNotice,
|
|
16
|
+
updateLogUpload
|
|
17
|
+
} from "./chunk-PKMXQJEJ.js";
|
|
11
18
|
|
|
12
19
|
// src/index.ts
|
|
13
20
|
import { createRequire as createRequire3 } from "module";
|
|
@@ -374,6 +381,7 @@ jobs:
|
|
|
374
381
|
});
|
|
375
382
|
|
|
376
383
|
// src/commands/login.ts
|
|
384
|
+
import { spawn } from "child_process";
|
|
377
385
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
378
386
|
import { createServer } from "http";
|
|
379
387
|
import { homedir } from "os";
|
|
@@ -407,9 +415,21 @@ function requireSession() {
|
|
|
407
415
|
}
|
|
408
416
|
return session;
|
|
409
417
|
}
|
|
418
|
+
function browserCommand(url, platform = process.platform) {
|
|
419
|
+
if (platform === "win32") return { file: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
|
|
420
|
+
return { file: platform === "darwin" ? "open" : "xdg-open", args: [url] };
|
|
421
|
+
}
|
|
422
|
+
function openBrowser(url) {
|
|
423
|
+
const { file, args } = browserCommand(url);
|
|
424
|
+
try {
|
|
425
|
+
const child = spawn(file, args, { stdio: "ignore", detached: true, windowsHide: true });
|
|
426
|
+
child.on("error", () => void 0);
|
|
427
|
+
child.unref();
|
|
428
|
+
} catch {
|
|
429
|
+
}
|
|
430
|
+
}
|
|
410
431
|
var loginCommand = new Command3("login").description("Sign in with Google or GitHub (opens browser)").option("--github", "Use GitHub instead of Google").action(async (opts) => {
|
|
411
432
|
const provider = opts.github ? "github" : "google";
|
|
412
|
-
writeLine(`Opening browser for ${provider} sign-in...`);
|
|
413
433
|
const port = await findFreePort();
|
|
414
434
|
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
415
435
|
const tokenPromise = new Promise((resolve5, reject) => {
|
|
@@ -462,9 +482,9 @@ var loginCommand = new Command3("login").description("Sign in with Google or Git
|
|
|
462
482
|
});
|
|
463
483
|
const oauthBase = provider === "google" ? `${API_BASE}/v1/auth/google/start` : `${API_BASE}/v1/auth/github/start`;
|
|
464
484
|
const oauthUrl = `${oauthBase}?return_to=${encodeURIComponent(callbackUrl)}`;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
485
|
+
writeLine(`Opening your browser for ${provider} sign-in. If it does not open, paste this URL into a browser on this machine:`);
|
|
486
|
+
writeLine(` ${oauthUrl}`);
|
|
487
|
+
openBrowser(oauthUrl);
|
|
468
488
|
try {
|
|
469
489
|
await tokenPromise;
|
|
470
490
|
const session = loadSession();
|
|
@@ -983,14 +1003,14 @@ var unclaimCommand = new Command4("unclaim").description("Remove a mistaken mach
|
|
|
983
1003
|
var machinesCommand = new Command4("machines").description("Show and claim the machine names ProAgentStore has for this account").addCommand(listCommand, { isDefault: true }).addCommand(claimCommand).addCommand(unclaimCommand);
|
|
984
1004
|
|
|
985
1005
|
// src/commands/mcp.ts
|
|
986
|
-
import { spawn } from "child_process";
|
|
1006
|
+
import { spawn as spawn2 } from "child_process";
|
|
987
1007
|
import { Command as Command5 } from "commander";
|
|
988
1008
|
var DEFAULT_MCP_URL = "https://mcp.proagentstore.online/mcp";
|
|
989
1009
|
function buildMcpRemoteArgs(opts, extraArgs = []) {
|
|
990
1010
|
return ["-y", "mcp-remote", opts.url || DEFAULT_MCP_URL, ...extraArgs];
|
|
991
1011
|
}
|
|
992
1012
|
async function runMcpProxy(opts, extraArgs = []) {
|
|
993
|
-
const child =
|
|
1013
|
+
const child = spawn2("npx", buildMcpRemoteArgs(opts, extraArgs), {
|
|
994
1014
|
stdio: "inherit",
|
|
995
1015
|
env: process.env
|
|
996
1016
|
});
|
|
@@ -1173,12 +1193,12 @@ var publishCommand = new Command7("publish").description("Publish an agent to Pr
|
|
|
1173
1193
|
});
|
|
1174
1194
|
|
|
1175
1195
|
// src/commands/runner/command.ts
|
|
1176
|
-
import { spawn as
|
|
1196
|
+
import { spawn as spawn5 } from "child_process";
|
|
1177
1197
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
1178
1198
|
import { Command as Command8 } from "commander";
|
|
1179
1199
|
|
|
1180
1200
|
// src/commands/runner/process.ts
|
|
1181
|
-
import { spawn as
|
|
1201
|
+
import { spawn as spawn3 } from "child_process";
|
|
1182
1202
|
import { existsSync as existsSync6 } from "fs";
|
|
1183
1203
|
import { resolve as resolve4, win32 } from "path";
|
|
1184
1204
|
import { createServer as createServer2 } from "net";
|
|
@@ -1253,7 +1273,7 @@ function runnerSpawnSpec(opts) {
|
|
|
1253
1273
|
function startRunnerForeground(opts) {
|
|
1254
1274
|
const spec = runnerSpawnSpec(opts);
|
|
1255
1275
|
return new Promise((resolvePromise, reject) => {
|
|
1256
|
-
const child =
|
|
1276
|
+
const child = spawn3(spec.command, spec.args, {
|
|
1257
1277
|
cwd: spec.cwd,
|
|
1258
1278
|
stdio: "inherit",
|
|
1259
1279
|
shell: spec.shell
|
|
@@ -1329,7 +1349,7 @@ function sampleResources(read = os, now = Date.now(), extra = {}) {
|
|
|
1329
1349
|
}
|
|
1330
1350
|
|
|
1331
1351
|
// src/commands/runner/self-update.ts
|
|
1332
|
-
import { spawn as
|
|
1352
|
+
import { spawn as spawn4 } from "child_process";
|
|
1333
1353
|
var RUNNER_UPDATE_PATH = "/pags/runner/update";
|
|
1334
1354
|
var RUNNER_RESTART_PATH = "/pags/runner/restart";
|
|
1335
1355
|
var RUNNER_RESTART_EXIT_CODE = 75;
|
|
@@ -1360,7 +1380,8 @@ function deferredUpdateLease(value, now = Date.now()) {
|
|
|
1360
1380
|
function deferredUpdateLeaseActive(lease, now = Date.now()) {
|
|
1361
1381
|
return now < lease.expiresAt;
|
|
1362
1382
|
}
|
|
1363
|
-
function leaveForRestart(restarter, deps = { spawn:
|
|
1383
|
+
function leaveForRestart(restarter, deps = { spawn: spawn4, exit: (code) => process.exit(code) }) {
|
|
1384
|
+
logUpdateStep(`restart: leaving for the ${restarter} restarter (${restarter === "command" ? "restart command started, exit 0" : `exit ${RUNNER_RESTART_EXIT_CODE}`})`);
|
|
1364
1385
|
if (restarter === "command") {
|
|
1365
1386
|
deps.spawn(process.env[RESTART_COMMAND_ENV], { shell: true, detached: true, stdio: "ignore", env: process.env }).unref();
|
|
1366
1387
|
deps.exit(0);
|
|
@@ -1379,6 +1400,8 @@ function planRunnerUpdate(f) {
|
|
|
1379
1400
|
if (f.fromSource) return { action: "refused", current: f.current, reason: "This runner runs from a source checkout \u2014 update it with `git pull` there, not npm." };
|
|
1380
1401
|
if (!f.latest) return { action: "refused", current: f.current, reason: `npm could not be asked for the latest ${CLI_PACKAGE} from this machine${f.latestError ? `: ${f.latestError}` : "."}`, retryable: true };
|
|
1381
1402
|
if (!olderThan(f.current, f.latest)) return { action: "up-to-date", current: f.current };
|
|
1403
|
+
const rolledBack = f.rolledBack?.[f.latest];
|
|
1404
|
+
if (rolledBack) return { action: "refused", current: f.current, reason: `${f.latest} was installed here and rolled back: ${rolledBack}. This machine stays on ${f.current} until a newer release is published.`, retryable: true };
|
|
1382
1405
|
if (!f.restarter) {
|
|
1383
1406
|
return {
|
|
1384
1407
|
action: "refused",
|
|
@@ -1411,17 +1434,24 @@ function planRunnerRestart(f) {
|
|
|
1411
1434
|
}
|
|
1412
1435
|
return { action: "restart", current: f.current, restarter: f.restarter };
|
|
1413
1436
|
}
|
|
1414
|
-
async function installVersion(version2) {
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1437
|
+
async function installVersion(version2, current = null) {
|
|
1438
|
+
const bootstrapped = !!process.env[BOOTSTRAP_ENV];
|
|
1439
|
+
logUpdateStep(`install: ${current ?? "?"} \u2192 ${version2} starting (${bootstrapped ? "payload cache" : "npm i -g"})`);
|
|
1419
1440
|
try {
|
|
1420
|
-
|
|
1441
|
+
if (bootstrapped) {
|
|
1442
|
+
await installPayload(version2);
|
|
1443
|
+
beginTrial(payloadRoot(), version2, current);
|
|
1444
|
+
} else {
|
|
1445
|
+
await runNpm(["i", "-g", `${CLI_PACKAGE}@${version2}`], { timeout: 5 * 6e4 }).catch((e) => {
|
|
1446
|
+
const stderr = String(e.stderr ?? "").trim();
|
|
1447
|
+
throw new Error((stderr || (e instanceof Error ? e.message : String(e))).slice(-400));
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1421
1450
|
} catch (e) {
|
|
1422
|
-
|
|
1423
|
-
throw
|
|
1451
|
+
logUpdateStep(`install: ${version2} FAILED \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
1452
|
+
throw e;
|
|
1424
1453
|
}
|
|
1454
|
+
logUpdateStep(`install: ${version2} installed`);
|
|
1425
1455
|
}
|
|
1426
1456
|
|
|
1427
1457
|
// src/commands/runner/membership.ts
|
|
@@ -1776,13 +1806,15 @@ function parseAutoUpdatePolicy(value) {
|
|
|
1776
1806
|
const lastAttemptAt = raw.lastAttemptAt ?? raw.last_attempt_at;
|
|
1777
1807
|
const reason = raw.reason;
|
|
1778
1808
|
const recovery = parseRecovery(raw.recovery);
|
|
1809
|
+
const fleet = raw.fleetUpdating ?? raw.fleet_updating;
|
|
1779
1810
|
return {
|
|
1780
1811
|
autoUpdate,
|
|
1781
1812
|
...status ? { status } : {},
|
|
1782
1813
|
...typeof latestVersion === "string" ? { latestVersion } : {},
|
|
1783
1814
|
...typeof lastAttemptAt === "string" ? { lastAttemptAt } : {},
|
|
1784
1815
|
...typeof reason === "string" ? { reason } : {},
|
|
1785
|
-
...recovery ? { recovery } : {}
|
|
1816
|
+
...recovery ? { recovery } : {},
|
|
1817
|
+
...typeof fleet === "number" && Number.isInteger(fleet) && fleet >= 0 ? { fleetUpdating: fleet } : {}
|
|
1786
1818
|
};
|
|
1787
1819
|
}
|
|
1788
1820
|
function policyFromResponse(value) {
|
|
@@ -1843,7 +1875,9 @@ function acceptPolicyResponse(local, wasAuthoritative, response) {
|
|
|
1843
1875
|
const { recovery: _fromService, ...remote } = parsed;
|
|
1844
1876
|
const action = policyScheduleAction(local, remote, wasAuthoritative);
|
|
1845
1877
|
const ownerTransition = action === "cancel" || !local.autoUpdate;
|
|
1846
|
-
|
|
1878
|
+
const { fleetUpdating: _stale, ...base } = local;
|
|
1879
|
+
const fleet = remote.fleetUpdating === void 0 ? {} : { fleetUpdating: remote.fleetUpdating };
|
|
1880
|
+
return { policy: ownerTransition ? { ...base, ...remote, ...fleet } : { ...base, autoUpdate: remote.autoUpdate, ...fleet }, action };
|
|
1847
1881
|
}
|
|
1848
1882
|
var ResponseFence = class {
|
|
1849
1883
|
sent = 0;
|
|
@@ -2068,6 +2102,12 @@ var AutomaticUpdateController = class {
|
|
|
2068
2102
|
this.defer();
|
|
2069
2103
|
return;
|
|
2070
2104
|
}
|
|
2105
|
+
if (policy.fleetUpdating) {
|
|
2106
|
+
this.blockerClass = "fleet_stagger";
|
|
2107
|
+
this.deps.status("waiting-for-idle", { latestVersion: plan.latest, reason: `${policy.fleetUpdating} other machine(s) on this account are mid-update; this one updates after them.` });
|
|
2108
|
+
this.defer(plan.latest);
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2071
2111
|
const target = open && isVersion(open.target) ? open.target : plan.latest;
|
|
2072
2112
|
const attempt = { ...plan, latest: target };
|
|
2073
2113
|
const recovery = open ?? { target, attachments: [...this.deps.attachments?.() ?? []], at: new Date(this.now()).toISOString() };
|
|
@@ -2077,7 +2117,7 @@ var AutomaticUpdateController = class {
|
|
|
2077
2117
|
this.deps.status("restarting", { latestVersion: target, reason: void 0 });
|
|
2078
2118
|
return;
|
|
2079
2119
|
}
|
|
2080
|
-
this.blockerClass = "idle_wait";
|
|
2120
|
+
this.blockerClass = this.deps.policy().value.fleetUpdating ? "fleet_stagger" : "idle_wait";
|
|
2081
2121
|
this.defer(target);
|
|
2082
2122
|
}
|
|
2083
2123
|
defer(latest) {
|
|
@@ -2250,7 +2290,10 @@ function createPolicyChannel(deps) {
|
|
|
2250
2290
|
if (action === "cancel") deps.onAction("cancel");
|
|
2251
2291
|
else if (action === "start" && !deps.cell.refreshing) deps.onAction("start");
|
|
2252
2292
|
};
|
|
2253
|
-
const accept = (response, sentAs) =>
|
|
2293
|
+
const accept = (response, sentAs) => {
|
|
2294
|
+
act(deps.cell.accept(response, sentAs));
|
|
2295
|
+
deps.onAnswer?.();
|
|
2296
|
+
};
|
|
2254
2297
|
const register = async (id, force = deps.force, signal) => {
|
|
2255
2298
|
const sent = deps.cell.next();
|
|
2256
2299
|
try {
|
|
@@ -2300,7 +2343,7 @@ function createInstallBridge(deps) {
|
|
|
2300
2343
|
if (automatic) {
|
|
2301
2344
|
const fresh = await within((signal) => deps.refresh(signal));
|
|
2302
2345
|
if (fresh === null) expired(false, "The owner policy refresh before installing");
|
|
2303
|
-
if (!fresh || !deps.cell.policy.autoUpdate) {
|
|
2346
|
+
if (!fresh || !deps.cell.policy.autoUpdate || deps.cell.policy.fleetUpdating) {
|
|
2304
2347
|
release(false);
|
|
2305
2348
|
return false;
|
|
2306
2349
|
}
|
|
@@ -2344,6 +2387,122 @@ function createInstallBridge(deps) {
|
|
|
2344
2387
|
return { run, inFlight: () => inFlight, installedPendingRestart: () => installedPendingRestart };
|
|
2345
2388
|
}
|
|
2346
2389
|
|
|
2390
|
+
// src/commands/runner/release-trial.ts
|
|
2391
|
+
function releaseTrial(version2, cell, log, env = process.env, root = payloadRoot) {
|
|
2392
|
+
if (!env[BOOTSTRAP_ENV]) return { rollbackReason: null, proven: () => void 0, rolledBack: () => ({}) };
|
|
2393
|
+
const notice = takeRollbackNotice(root());
|
|
2394
|
+
const rollbackReason = notice ? `Rolled back from ${notice.from} to ${version2}: ${notice.reason}.` : null;
|
|
2395
|
+
if (rollbackReason) {
|
|
2396
|
+
log(`auto-update: ${rollbackReason}`);
|
|
2397
|
+
cell.record("failure", { reason: rollbackReason, recovery: void 0 });
|
|
2398
|
+
}
|
|
2399
|
+
let done = false;
|
|
2400
|
+
return {
|
|
2401
|
+
rollbackReason,
|
|
2402
|
+
proven: () => {
|
|
2403
|
+
if (done) return;
|
|
2404
|
+
done = true;
|
|
2405
|
+
if (markPayloadHealthy(root(), version2)) log(`auto-update: ${version2} registered \u2014 it is kept.`);
|
|
2406
|
+
},
|
|
2407
|
+
rolledBack: () => badVersions(root())
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
// src/commands/up-restart.ts
|
|
2412
|
+
import { spawn as nodeSpawn, spawnSync as nodeSpawnSync } from "child_process";
|
|
2413
|
+
var UP_LAUNCHER_ENV = "PAGS_UP_LAUNCHER";
|
|
2414
|
+
var UP_LAUNCHER_PID_ENV = "PAGS_UP_LAUNCHER_PID";
|
|
2415
|
+
var RUNNER_DIED_EXIT_CODE = 76;
|
|
2416
|
+
function upRestartMode(env, parent = { pid: process.ppid, alive: pidAlive }) {
|
|
2417
|
+
if (env[SERVICE_ENV] === "1") return "service";
|
|
2418
|
+
if (env[RESTART_COMMAND_ENV]?.trim()) return "command";
|
|
2419
|
+
const launcher = Number(env[UP_LAUNCHER_PID_ENV]);
|
|
2420
|
+
if (env[UP_LAUNCHER_ENV] === "1" && launcher === parent.pid && parent.alive(launcher)) return "launcher-child";
|
|
2421
|
+
return "launcher";
|
|
2422
|
+
}
|
|
2423
|
+
function runnerExitAction(code, stopping) {
|
|
2424
|
+
if (code === RUNNER_RESTART_EXIT_CODE) return "restart";
|
|
2425
|
+
if (stopping) return "ignore";
|
|
2426
|
+
return code === 0 ? "stop" : "died";
|
|
2427
|
+
}
|
|
2428
|
+
function restartOwner(env = process.env, alive = pidAlive) {
|
|
2429
|
+
if (env[SERVICE_ENV] === "1") return "service";
|
|
2430
|
+
return env[UP_LAUNCHER_ENV] === "1" && alive(Number(env[UP_LAUNCHER_PID_ENV])) ? "launcher" : "none";
|
|
2431
|
+
}
|
|
2432
|
+
function relaunchDelayMs(failures) {
|
|
2433
|
+
return Math.min(5 * 6e4, 1e4 * 2 ** Math.max(0, failures - 1));
|
|
2434
|
+
}
|
|
2435
|
+
var RAN_MS = 6e4;
|
|
2436
|
+
var RESET_MS = 10 * 6e4;
|
|
2437
|
+
function processRestartDeps() {
|
|
2438
|
+
return {
|
|
2439
|
+
env: process.env,
|
|
2440
|
+
execPath: process.execPath,
|
|
2441
|
+
execArgv: process.execArgv,
|
|
2442
|
+
script: process.argv[1],
|
|
2443
|
+
spawn: nodeSpawn,
|
|
2444
|
+
spawnSync: nodeSpawnSync,
|
|
2445
|
+
exit: (code) => process.exit(code),
|
|
2446
|
+
log: () => {
|
|
2447
|
+
},
|
|
2448
|
+
sleep: (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms),
|
|
2449
|
+
step: (line) => logUpdateStep(line),
|
|
2450
|
+
parent: { pid: process.ppid, alive: pidAlive },
|
|
2451
|
+
pid: process.pid,
|
|
2452
|
+
now: Date.now
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
function restartPagsUp(args, extraEnv, deps, opts = {}) {
|
|
2456
|
+
const env = { ...deps.env, ...extraEnv };
|
|
2457
|
+
const argv = [...deps.execArgv, deps.script, ...args];
|
|
2458
|
+
const exitCode = opts.exitCode ?? RUNNER_RESTART_EXIT_CODE;
|
|
2459
|
+
const mode = upRestartMode(env, deps.parent);
|
|
2460
|
+
deps.step(`${opts.initial ? "start" : "restart"}: pags ${args.join(" ")} \u2014 ${opts.initial ? "as" : "leaving via"} ${mode}${exitCode === RUNNER_DIED_EXIT_CODE ? " (the runner died)" : ""}`);
|
|
2461
|
+
if (mode === "service") {
|
|
2462
|
+
deps.exit(exitCode);
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
if (mode === "command") {
|
|
2466
|
+
leaveForRestart(mode, { spawn: deps.spawn, exit: deps.exit });
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
if (mode === "launcher-child") {
|
|
2470
|
+
deps.exit(exitCode);
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
const childEnv = { ...env, [UP_LAUNCHER_ENV]: "1", [UP_LAUNCHER_PID_ENV]: String(deps.pid) };
|
|
2474
|
+
let failures = 0;
|
|
2475
|
+
for (let first = true; ; first = false) {
|
|
2476
|
+
const unattended = !first || env.PAGS_NO_PROMPT === "1";
|
|
2477
|
+
deps.step(`launcher: starting pags up${unattended ? " (unattended)" : ""}`);
|
|
2478
|
+
const startedAt = deps.now();
|
|
2479
|
+
const r = deps.spawnSync(deps.execPath, argv, { stdio: "inherit", env: first ? childEnv : { ...childEnv, PAGS_NO_PROMPT: "1" } });
|
|
2480
|
+
const ran = deps.now() - startedAt;
|
|
2481
|
+
const how = r.error ? `could not start (${r.error.message})` : r.signal ? `was killed by ${r.signal}` : `exited ${r.status}`;
|
|
2482
|
+
deps.step(`launcher: pags up ${how} after ${Math.round(ran / 1e3)}s`);
|
|
2483
|
+
if (ran >= RESET_MS) failures = 0;
|
|
2484
|
+
if (r.status === RUNNER_RESTART_EXIT_CODE) {
|
|
2485
|
+
failures = 0;
|
|
2486
|
+
continue;
|
|
2487
|
+
}
|
|
2488
|
+
if (!r.error && (r.status === 0 || r.signal === "SIGINT")) {
|
|
2489
|
+
deps.exit(0);
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
if (unattended || r.status === RUNNER_DIED_EXIT_CODE || ran >= RAN_MS) {
|
|
2493
|
+
failures++;
|
|
2494
|
+
const wait = relaunchDelayMs(failures);
|
|
2495
|
+
deps.log(`pags up ${how} \u2014 starting it again in ${Math.round(wait / 1e3)}s (attempt ${failures + 1}).`);
|
|
2496
|
+
deps.step(`launcher: retrying in ${Math.round(wait / 1e3)}s (failure ${failures})`);
|
|
2497
|
+
deps.sleep(wait);
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
2500
|
+
if (!opts.initial) deps.log(`Restart failed${typeof r.status === "number" ? ` (exit ${r.status})` : ""} \u2014 the runner is NOT running. Run 'pags up' again.`);
|
|
2501
|
+
deps.exit(typeof r.status === "number" && r.status !== 0 ? r.status : 1);
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2347
2506
|
// src/commands/runner/relay.ts
|
|
2348
2507
|
var MEMBERSHIP_SYNC_PATH = "/pags/membership/sync";
|
|
2349
2508
|
var CLI_CONTROL_PATHS = /* @__PURE__ */ new Set([MEMBERSHIP_SYNC_PATH, RUNNER_UPDATE_PATH, RUNNER_RESTART_PATH]);
|
|
@@ -2355,12 +2514,15 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2355
2514
|
const machine = loadMachineIdentity(runnerNode);
|
|
2356
2515
|
const autoUpdateKey = machine.id ? autoUpdatePolicyKey(machine.id) : "";
|
|
2357
2516
|
const cell = new PolicyCell(autoUpdateKey ? loadAutoUpdatePolicy(autoUpdateKey) ?? { autoUpdate: false } : { autoUpdate: false }, (p) => saveAutoUpdatePolicy(autoUpdateKey, p), !!autoUpdateKey);
|
|
2517
|
+
const trial = releaseTrial(CLI_VERSION, cell, writeLine);
|
|
2358
2518
|
let scheduleAutomaticUpdate = () => void 0;
|
|
2359
2519
|
let autoUpdateDiagnostics = () => ({});
|
|
2520
|
+
const updateLog = updateLogUpload(CLI_VERSION);
|
|
2360
2521
|
const autoUpdateStatusBody = () => ({
|
|
2361
2522
|
status: autoUpdateStatusWire(cell.policy.status),
|
|
2362
2523
|
latestVersion: cell.policy.latestVersion,
|
|
2363
2524
|
error: cell.policy.reason,
|
|
2525
|
+
...updateLog.body(),
|
|
2364
2526
|
...autoUpdateDiagnostics()
|
|
2365
2527
|
});
|
|
2366
2528
|
const capabilities = await requestRunner("GET", "/capabilities", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] });
|
|
@@ -2387,7 +2549,11 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2387
2549
|
lastRegisterError = msg;
|
|
2388
2550
|
writeError(`register ${id.slice(0, 8)}\u2026 failed: ${msg}`);
|
|
2389
2551
|
},
|
|
2390
|
-
onAction: (action) => scheduleAutomaticUpdate(action === "start", action === "cancel")
|
|
2552
|
+
onAction: (action) => scheduleAutomaticUpdate(action === "start", action === "cancel"),
|
|
2553
|
+
onAnswer: () => {
|
|
2554
|
+
trial.proven();
|
|
2555
|
+
updateLog.answered();
|
|
2556
|
+
}
|
|
2391
2557
|
});
|
|
2392
2558
|
const registered = channel.registered;
|
|
2393
2559
|
const registerRuntime = (id, forceClaim = force) => channel.register(id, forceClaim);
|
|
@@ -2424,6 +2590,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2424
2590
|
..."error" in release ? { latestError: release.error } : {},
|
|
2425
2591
|
fromSource: runsFromSource(),
|
|
2426
2592
|
restarter: restarterFrom(process.env),
|
|
2593
|
+
rolledBack: trial.rolledBack(),
|
|
2427
2594
|
// A restart ends browser/application work too. Give these durable names to the same
|
|
2428
2595
|
// planner so neither manual nor automatic update can mistake them for an idle machine.
|
|
2429
2596
|
busy
|
|
@@ -2434,7 +2601,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2434
2601
|
cell,
|
|
2435
2602
|
refresh: channel.refresh,
|
|
2436
2603
|
facts: updateFacts,
|
|
2437
|
-
install: installVersion,
|
|
2604
|
+
install: (version2) => installVersion(version2, CLI_VERSION),
|
|
2438
2605
|
admission: relayMutationAdmission,
|
|
2439
2606
|
leaseActive: deferredUpdateLeaseActive,
|
|
2440
2607
|
detachAll: () => {
|
|
@@ -2631,7 +2798,9 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2631
2798
|
// #896: who is heartbeating. Set by `runner connect` from the lock it took.
|
|
2632
2799
|
...process.env.PAGS_RUNNER_RSID ? { rsid: process.env.PAGS_RUNNER_RSID } : {},
|
|
2633
2800
|
pid: process.pid,
|
|
2634
|
-
...process.env.PAGS_RUNNER_LAUNCH ? { launch: process.env.PAGS_RUNNER_LAUNCH } : {}
|
|
2801
|
+
...process.env.PAGS_RUNNER_LAUNCH ? { launch: process.env.PAGS_RUNNER_LAUNCH } : {},
|
|
2802
|
+
supervisor: restartOwner()
|
|
2803
|
+
// #1154: what starts it again if it dies
|
|
2635
2804
|
},
|
|
2636
2805
|
relayRttMs,
|
|
2637
2806
|
sessions: sessions ?? void 0
|
|
@@ -3198,6 +3367,8 @@ function createRunnerCommand() {
|
|
|
3198
3367
|
process.exit(1);
|
|
3199
3368
|
}
|
|
3200
3369
|
lockHeld = { path: taken.path, rsid: taken.lock.rsid, nonce: taken.lock.nonce, launch: taken.lock.launch };
|
|
3370
|
+
process.env.PAGS_RUNNER_RSID = taken.lock.rsid;
|
|
3371
|
+
process.env.PAGS_RUNNER_LAUNCH = taken.lock.launch;
|
|
3201
3372
|
writeLine(`STATUS runner-session=${taken.lock.rsid}`);
|
|
3202
3373
|
}
|
|
3203
3374
|
const releaseHeldLock = () => {
|
|
@@ -3209,7 +3380,7 @@ function createRunnerCommand() {
|
|
|
3209
3380
|
const spec = runnerSpawnSpec(runnerOpts);
|
|
3210
3381
|
const crashAccount = lockAccount || loadSession()?.user.login || "";
|
|
3211
3382
|
const crashes = crashAccount ? crashPaths(configDir(), crashAccount, opts.dataDir) : null;
|
|
3212
|
-
const runner =
|
|
3383
|
+
const runner = spawn5(spec.command, spec.args, {
|
|
3213
3384
|
cwd: spec.cwd,
|
|
3214
3385
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3215
3386
|
shell: spec.shell,
|
|
@@ -3394,7 +3565,7 @@ import { readFileSync as readFileSync11, statSync as statSync2, watchFile } from
|
|
|
3394
3565
|
import { Command as Command9 } from "commander";
|
|
3395
3566
|
|
|
3396
3567
|
// src/background.ts
|
|
3397
|
-
import { spawn as
|
|
3568
|
+
import { spawn as nodeSpawn2 } from "child_process";
|
|
3398
3569
|
import { closeSync as closeSync3, existsSync as existsSync10, mkdirSync as mkdirSync8, openSync as openSync3, readFileSync as readFileSync10, renameSync as renameSync4, statSync, writeFileSync as writeFileSync6 } from "fs";
|
|
3399
3570
|
import { dirname as dirname5, join as join11 } from "path";
|
|
3400
3571
|
var BACKGROUND_ENV = "PAGS_BACKGROUND";
|
|
@@ -3433,12 +3604,12 @@ function backgroundRestartCommand(execPath, script, upArgs, platform = process.p
|
|
|
3433
3604
|
const argv = [execPath, script, ...upArgs, "--background"].map((a) => shellQuote(a, platform)).join(" ");
|
|
3434
3605
|
return platform === "win32" ? `set ${BACKGROUND_RESTART_ENV}=1&& ${argv}` : `${BACKGROUND_RESTART_ENV}=1 ${argv}`;
|
|
3435
3606
|
}
|
|
3436
|
-
function spawnDetached(command, args, env, logPath,
|
|
3607
|
+
function spawnDetached(command, args, env, logPath, spawn6 = nodeSpawn2) {
|
|
3437
3608
|
mkdirSync8(dirname5(logPath), { recursive: true });
|
|
3438
3609
|
rotateLog(logPath);
|
|
3439
3610
|
const fd = openSync3(logPath, "a", 384);
|
|
3440
3611
|
try {
|
|
3441
|
-
const child =
|
|
3612
|
+
const child = spawn6(command, args, { detached: true, stdio: ["ignore", fd, fd], env, windowsHide: true });
|
|
3442
3613
|
child.unref();
|
|
3443
3614
|
if (!child.pid) throw new Error("the runner process did not start");
|
|
3444
3615
|
return child.pid;
|
|
@@ -3531,7 +3702,7 @@ var logsCommand = new Command9("logs").description("Show the background runner's
|
|
|
3531
3702
|
// src/commands/up.ts
|
|
3532
3703
|
import { createRequire as createRequire2 } from "module";
|
|
3533
3704
|
import { Command as Command10 } from "commander";
|
|
3534
|
-
import { hostname as hostname7 } from "os";
|
|
3705
|
+
import { homedir as homedir5, hostname as hostname7, userInfo } from "os";
|
|
3535
3706
|
|
|
3536
3707
|
// src/tui.ts
|
|
3537
3708
|
import chalk from "chalk";
|
|
@@ -3650,65 +3821,6 @@ async function waitForKey(keys, onInterrupt) {
|
|
|
3650
3821
|
});
|
|
3651
3822
|
}
|
|
3652
3823
|
|
|
3653
|
-
// src/commands/up-restart.ts
|
|
3654
|
-
import { spawn as nodeSpawn2, spawnSync as nodeSpawnSync } from "child_process";
|
|
3655
|
-
var UP_LAUNCHER_ENV = "PAGS_UP_LAUNCHER";
|
|
3656
|
-
function upRestartMode(env, platform, canExecve) {
|
|
3657
|
-
if (env[SERVICE_ENV] === "1") return "service";
|
|
3658
|
-
if (env[RESTART_COMMAND_ENV]?.trim()) return "command";
|
|
3659
|
-
if (env[UP_LAUNCHER_ENV] === "1") return "launcher-child";
|
|
3660
|
-
return platform !== "win32" && canExecve ? "exec" : "launcher";
|
|
3661
|
-
}
|
|
3662
|
-
function processRestartDeps() {
|
|
3663
|
-
const execve = process.execve;
|
|
3664
|
-
return {
|
|
3665
|
-
env: process.env,
|
|
3666
|
-
platform: process.platform,
|
|
3667
|
-
execPath: process.execPath,
|
|
3668
|
-
execArgv: process.execArgv,
|
|
3669
|
-
script: process.argv[1],
|
|
3670
|
-
execve: typeof execve === "function" ? execve.bind(process) : void 0,
|
|
3671
|
-
spawn: nodeSpawn2,
|
|
3672
|
-
spawnSync: nodeSpawnSync,
|
|
3673
|
-
exit: (code) => process.exit(code),
|
|
3674
|
-
log: () => {
|
|
3675
|
-
}
|
|
3676
|
-
};
|
|
3677
|
-
}
|
|
3678
|
-
function restartPagsUp(args, extraEnv, deps) {
|
|
3679
|
-
const env = { ...deps.env, ...extraEnv };
|
|
3680
|
-
const argv = [...deps.execArgv, deps.script, ...args];
|
|
3681
|
-
const mode = upRestartMode(env, deps.platform, !!deps.execve);
|
|
3682
|
-
if (mode === "service" || mode === "command") {
|
|
3683
|
-
leaveForRestart(mode, { spawn: deps.spawn, exit: deps.exit });
|
|
3684
|
-
return;
|
|
3685
|
-
}
|
|
3686
|
-
if (mode === "launcher-child") {
|
|
3687
|
-
deps.exit(RUNNER_RESTART_EXIT_CODE);
|
|
3688
|
-
return;
|
|
3689
|
-
}
|
|
3690
|
-
if (mode === "exec") {
|
|
3691
|
-
try {
|
|
3692
|
-
deps.execve(deps.execPath, [deps.execPath, ...argv], env);
|
|
3693
|
-
} catch (e) {
|
|
3694
|
-
deps.log(`Restart failed (${e instanceof Error ? e.message : String(e)}) \u2014 the runner is NOT running. Run 'pags up' again.`);
|
|
3695
|
-
deps.exit(1);
|
|
3696
|
-
}
|
|
3697
|
-
return;
|
|
3698
|
-
}
|
|
3699
|
-
for (let first = true; ; first = false) {
|
|
3700
|
-
const r = deps.spawnSync(deps.execPath, argv, { stdio: "inherit", env: { ...env, [UP_LAUNCHER_ENV]: "1", ...first ? {} : { PAGS_NO_PROMPT: "1" } } });
|
|
3701
|
-
if (r.status === RUNNER_RESTART_EXIT_CODE) continue;
|
|
3702
|
-
if (r.error || r.status !== 0) {
|
|
3703
|
-
deps.log(`Restart failed${typeof r.status === "number" ? ` (exit ${r.status})` : ""} \u2014 the runner is NOT running. Run 'pags up' again.`);
|
|
3704
|
-
deps.exit(typeof r.status === "number" && r.status !== 0 ? r.status : 1);
|
|
3705
|
-
return;
|
|
3706
|
-
}
|
|
3707
|
-
deps.exit(0);
|
|
3708
|
-
return;
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
|
|
3712
3824
|
// src/runner-replace.ts
|
|
3713
3825
|
var REPLACE_EXIT_TIMEOUT_MS = 3e4;
|
|
3714
3826
|
function describeWork(work) {
|
|
@@ -3850,11 +3962,263 @@ async function startInBackground(start, deps = processDeps()) {
|
|
|
3850
3962
|
return 1;
|
|
3851
3963
|
}
|
|
3852
3964
|
|
|
3965
|
+
// src/service-install.ts
|
|
3966
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
3967
|
+
import { mkdirSync as mkdirSync9, rmSync, writeFileSync as writeFileSync7 } from "fs";
|
|
3968
|
+
import { posix, win32 as win322 } from "path";
|
|
3969
|
+
var SERVICE_LABEL = "online.proagentstore.pags-up";
|
|
3970
|
+
var SYSTEMD_UNIT = "pags-up.service";
|
|
3971
|
+
var WINDOWS_TASK = "pags up";
|
|
3972
|
+
var pathFor = (ctx) => ctx.platform === "win32" ? win322 : posix;
|
|
3973
|
+
var supervisorPath = (ctx) => pathFor(ctx).join(ctx.configDir, "pags-up-supervisor.mjs");
|
|
3974
|
+
var serviceLogPath = (ctx) => pathFor(ctx).join(ctx.configDir, "pags-up.log");
|
|
3975
|
+
function supervisorScript(ctx) {
|
|
3976
|
+
const config = JSON.stringify({ node: ctx.nodePath, script: ctx.script, log: serviceLogPath(ctx), path: ctx.pathEnv });
|
|
3977
|
+
return `// Written by \`pags up --install-service\` (#1141). Keeps \`pags up\` running; see packages/cli/src/service-install.ts.
|
|
3978
|
+
import { spawn } from "node:child_process";
|
|
3979
|
+
import { appendFileSync, closeSync, openSync } from "node:fs";
|
|
3980
|
+
|
|
3981
|
+
const config = ${config};
|
|
3982
|
+
const num = (name, fallback) => { const n = Number(process.env[name]); return Number.isFinite(n) && n > 0 ? n : fallback; };
|
|
3983
|
+
const MIN = num("PAGS_SUPERVISOR_MIN_MS", 10_000);
|
|
3984
|
+
const MAX = num("PAGS_SUPERVISOR_MAX_MS", 300_000);
|
|
3985
|
+
const RESET = num("PAGS_SUPERVISOR_RESET_MS", 600_000);
|
|
3986
|
+
const RESTART_CODE = 75; // \`pags up\` asking to be started again: an update, or runner_restart
|
|
3987
|
+
|
|
3988
|
+
const say = (line) => { try { appendFileSync(config.log, \`[\${new Date().toISOString()}] supervisor: \${line}\\n\`); } catch { /* a log that cannot be written must not stop the runner */ } };
|
|
3989
|
+
let delay = MIN;
|
|
3990
|
+
let child = null;
|
|
3991
|
+
let stopping = false;
|
|
3992
|
+
|
|
3993
|
+
function start() {
|
|
3994
|
+
if (stopping) return;
|
|
3995
|
+
const startedAt = Date.now();
|
|
3996
|
+
let out = "ignore";
|
|
3997
|
+
try { out = openSync(config.log, "a"); } catch { /* run without a log rather than not at all */ }
|
|
3998
|
+
say("starting pags up");
|
|
3999
|
+
child = spawn(config.node, [config.script, "up", "--headless"], {
|
|
4000
|
+
stdio: ["ignore", out, out],
|
|
4001
|
+
env: { ...process.env, PATH: config.path || process.env.PATH, PAGS_SERVICE: "1", PAGS_SERVICE_NAME: "${SERVICE_LABEL}", PAGS_NO_PROMPT: "1" },
|
|
4002
|
+
windowsHide: true,
|
|
4003
|
+
});
|
|
4004
|
+
if (typeof out === "number") closeSync(out);
|
|
4005
|
+
child.on("error", (e) => say(\`could not start pags up: \${e.message}\`));
|
|
4006
|
+
child.on("exit", (code, signal) => {
|
|
4007
|
+
child = null;
|
|
4008
|
+
if (stopping) return;
|
|
4009
|
+
if (Date.now() - startedAt >= RESET) delay = MIN;
|
|
4010
|
+
const wait = code === RESTART_CODE ? 1_000 : delay;
|
|
4011
|
+
say(\`pags up exited (\${signal ?? code}); starting it again in \${Math.round(wait / 1000)}s\`);
|
|
4012
|
+
if (code !== RESTART_CODE) delay = Math.min(delay * 2, MAX);
|
|
4013
|
+
setTimeout(start, wait);
|
|
4014
|
+
});
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
for (const sig of ["SIGTERM", "SIGINT"]) {
|
|
4018
|
+
process.on(sig, () => {
|
|
4019
|
+
stopping = true;
|
|
4020
|
+
say(\`stopping (\${sig})\`);
|
|
4021
|
+
if (child) child.kill("SIGTERM");
|
|
4022
|
+
setTimeout(() => process.exit(0), 5_000).unref();
|
|
4023
|
+
if (!child) process.exit(0);
|
|
4024
|
+
else child.once("exit", () => process.exit(0));
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
4027
|
+
start();
|
|
4028
|
+
`;
|
|
4029
|
+
}
|
|
4030
|
+
var xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4031
|
+
var unitWord = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
4032
|
+
var ps = (s) => `'${s.replace(/'/g, "''")}'`;
|
|
4033
|
+
function versionManagedNode(nodePath) {
|
|
4034
|
+
if (/[\\/]n[\\/]versions[\\/]/.test(nodePath)) return "n";
|
|
4035
|
+
const m = /[\\/]\.?(nvm|fnm|volta|asdf|nodenv)[\\/]/i.exec(nodePath);
|
|
4036
|
+
return m ? m[1].toLowerCase() : null;
|
|
4037
|
+
}
|
|
4038
|
+
var pinnedNodeNote = (ctx) => {
|
|
4039
|
+
const manager = versionManagedNode(ctx.nodePath);
|
|
4040
|
+
return manager ? [`It runs Node ${ctx.nodePath}, installed by ${manager}. Removing or switching away from that Node version stops the service; after changing Node, run \`pags up --install-service\` again.`] : [];
|
|
4041
|
+
};
|
|
4042
|
+
function servicePlan(ctx) {
|
|
4043
|
+
const supervisor = supervisorPath(ctx);
|
|
4044
|
+
const log = serviceLogPath(ctx);
|
|
4045
|
+
const files = [{ path: supervisor, content: supervisorScript(ctx) }];
|
|
4046
|
+
if (ctx.platform === "darwin") {
|
|
4047
|
+
const plist = posix.join(ctx.home, "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
4048
|
+
const domain = `gui/${ctx.uid ?? 501}`;
|
|
4049
|
+
files.push({
|
|
4050
|
+
path: plist,
|
|
4051
|
+
content: `<?xml version="1.0" encoding="UTF-8"?>
|
|
4052
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4053
|
+
<plist version="1.0">
|
|
4054
|
+
<dict>
|
|
4055
|
+
<key>Label</key><string>${SERVICE_LABEL}</string>
|
|
4056
|
+
<key>ProgramArguments</key>
|
|
4057
|
+
<array>
|
|
4058
|
+
<string>${xml(ctx.nodePath)}</string>
|
|
4059
|
+
<string>${xml(supervisor)}</string>
|
|
4060
|
+
</array>
|
|
4061
|
+
<key>RunAtLoad</key><true/>
|
|
4062
|
+
<key>KeepAlive</key><true/>
|
|
4063
|
+
<key>ThrottleInterval</key><integer>30</integer>
|
|
4064
|
+
<key>ProcessType</key><string>Background</string>
|
|
4065
|
+
<key>WorkingDirectory</key><string>${xml(ctx.home)}</string>
|
|
4066
|
+
<key>StandardOutPath</key><string>${xml(log)}</string>
|
|
4067
|
+
<key>StandardErrorPath</key><string>${xml(log)}</string>
|
|
4068
|
+
</dict>
|
|
4069
|
+
</plist>
|
|
4070
|
+
`
|
|
4071
|
+
});
|
|
4072
|
+
return {
|
|
4073
|
+
kind: "launchd",
|
|
4074
|
+
files,
|
|
4075
|
+
commands: [
|
|
4076
|
+
{ file: "launchctl", args: ["bootout", `${domain}/${SERVICE_LABEL}`], optional: true, why: "unload an earlier copy" },
|
|
4077
|
+
{ file: "launchctl", args: ["bootstrap", domain, plist], why: "load the LaunchAgent" },
|
|
4078
|
+
{ file: "launchctl", args: ["kickstart", "-k", `${domain}/${SERVICE_LABEL}`], why: "start it now" }
|
|
4079
|
+
],
|
|
4080
|
+
notes: [
|
|
4081
|
+
...pinnedNodeNote(ctx),
|
|
4082
|
+
`LaunchAgent ${plist} \u2014 runs while this user is logged in. On a machine that must survive a reboot unattended, turn on automatic login for this user.`,
|
|
4083
|
+
`Remove it with: pags up --uninstall-service`
|
|
4084
|
+
]
|
|
4085
|
+
};
|
|
4086
|
+
}
|
|
4087
|
+
if (ctx.platform === "win32") {
|
|
4088
|
+
const task = win322.join(ctx.configDir, "pags-up-task.ps1");
|
|
4089
|
+
const principal = `${ctx.domain ? `${ctx.domain}\\` : ""}${ctx.user ?? ""}`;
|
|
4090
|
+
files.push({
|
|
4091
|
+
path: task,
|
|
4092
|
+
content: `# Written by \`pags up --install-service\` (#1141). Registers the "${WINDOWS_TASK}" Scheduled Task; needs an elevated PowerShell.
|
|
4093
|
+
$ErrorActionPreference = 'Stop'
|
|
4094
|
+
$action = New-ScheduledTaskAction -Execute ${ps(ctx.nodePath)} -Argument ${ps(`"${supervisor}"`)}
|
|
4095
|
+
$trigger = New-ScheduledTaskTrigger -AtStartup
|
|
4096
|
+
# Task Scheduler restarts a task that failed to START; a supervisor killed later just ends. Re-trigger it every five
|
|
4097
|
+
# minutes, forever \u2014 MultipleInstances IgnoreNew makes a trigger that finds it running do nothing (#1154).
|
|
4098
|
+
$trigger.Repetition = (New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5)).Repetition
|
|
4099
|
+
$principal = New-ScheduledTaskPrincipal -UserId ${ps(principal)} -LogonType S4U -RunLevel Limited
|
|
4100
|
+
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -MultipleInstances IgnoreNew
|
|
4101
|
+
Unregister-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -Confirm:$false -ErrorAction SilentlyContinue
|
|
4102
|
+
Register-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -Action $action -Trigger $trigger -Principal $principal -Settings $settings | Out-Null
|
|
4103
|
+
Start-ScheduledTask -TaskName ${ps(WINDOWS_TASK)}
|
|
4104
|
+
`
|
|
4105
|
+
});
|
|
4106
|
+
return {
|
|
4107
|
+
kind: "scheduled-task",
|
|
4108
|
+
files,
|
|
4109
|
+
commands: [{ file: "powershell.exe", args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", task], why: "register and start the Scheduled Task" }],
|
|
4110
|
+
notes: [
|
|
4111
|
+
...pinnedNodeNote(ctx),
|
|
4112
|
+
`Scheduled Task "${WINDOWS_TASK}" \u2014 starts at boot as ${principal || "this user"}, with nobody logged in. Registering it needs an elevated PowerShell: if it was refused, run ${task} from one.`,
|
|
4113
|
+
`Remove it with: pags up --uninstall-service`
|
|
4114
|
+
]
|
|
4115
|
+
};
|
|
4116
|
+
}
|
|
4117
|
+
const unit = posix.join(ctx.home, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
4118
|
+
files.push({
|
|
4119
|
+
path: unit,
|
|
4120
|
+
content: `# Written by \`pags up --install-service\` (#1141).
|
|
4121
|
+
[Unit]
|
|
4122
|
+
Description=ProAgentStore runner (pags up)
|
|
4123
|
+
After=network-online.target
|
|
4124
|
+
Wants=network-online.target
|
|
4125
|
+
StartLimitIntervalSec=0
|
|
4126
|
+
|
|
4127
|
+
[Service]
|
|
4128
|
+
ExecStart=${unitWord(ctx.nodePath)} ${unitWord(supervisor)}
|
|
4129
|
+
Environment=${unitWord(`PATH=${ctx.pathEnv}`)}
|
|
4130
|
+
Restart=always
|
|
4131
|
+
RestartSec=10
|
|
4132
|
+
KillMode=mixed
|
|
4133
|
+
|
|
4134
|
+
[Install]
|
|
4135
|
+
WantedBy=default.target
|
|
4136
|
+
`
|
|
4137
|
+
});
|
|
4138
|
+
return {
|
|
4139
|
+
kind: "systemd",
|
|
4140
|
+
files,
|
|
4141
|
+
commands: [
|
|
4142
|
+
{ file: "systemctl", args: ["--user", "daemon-reload"], why: "read the unit" },
|
|
4143
|
+
{ file: "systemctl", args: ["--user", "enable", "--now", SYSTEMD_UNIT], why: "enable and start it" },
|
|
4144
|
+
{ file: "loginctl", args: ["enable-linger", ...ctx.user ? [ctx.user] : []], optional: true, why: "keep it running with nobody logged in" }
|
|
4145
|
+
],
|
|
4146
|
+
notes: [...pinnedNodeNote(ctx), `systemd user unit ${unit}. Logs: ${log} (and journalctl --user -u ${SYSTEMD_UNIT}).`, `Remove it with: pags up --uninstall-service`]
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
function serviceRemovalPlan(ctx) {
|
|
4150
|
+
const plan = servicePlan(ctx);
|
|
4151
|
+
const files = plan.files.map((f) => f.path);
|
|
4152
|
+
if (plan.kind === "launchd") return { files, commands: [{ file: "launchctl", args: ["bootout", `gui/${ctx.uid ?? 501}/${SERVICE_LABEL}`], optional: true, why: "unload the LaunchAgent" }] };
|
|
4153
|
+
if (plan.kind === "systemd") return { files, commands: [{ file: "systemctl", args: ["--user", "disable", "--now", SYSTEMD_UNIT], optional: true, why: "stop and disable the unit" }] };
|
|
4154
|
+
return {
|
|
4155
|
+
files,
|
|
4156
|
+
commands: [{ file: "powershell.exe", args: ["-NoProfile", "-Command", `Stop-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -ErrorAction SilentlyContinue; Unregister-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -Confirm:$false`], optional: true, why: "remove the Scheduled Task" }]
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4159
|
+
var defaultWrite = (path, content) => {
|
|
4160
|
+
mkdirSync9(path.replace(/[\\/][^\\/]*$/, ""), { recursive: true });
|
|
4161
|
+
writeFileSync7(path, content);
|
|
4162
|
+
};
|
|
4163
|
+
var defaultRun = (file, args) => void execFileSync3(file, args, { stdio: "pipe", timeout: 6e4 });
|
|
4164
|
+
function installService(ctx, deps = {}) {
|
|
4165
|
+
const plan = servicePlan(ctx);
|
|
4166
|
+
const lines = [];
|
|
4167
|
+
for (const f of plan.files) {
|
|
4168
|
+
(deps.write ?? defaultWrite)(f.path, f.content);
|
|
4169
|
+
lines.push(`wrote ${f.path}`);
|
|
4170
|
+
}
|
|
4171
|
+
let ok = true;
|
|
4172
|
+
for (const c2 of plan.commands) {
|
|
4173
|
+
try {
|
|
4174
|
+
(deps.run ?? defaultRun)(c2.file, c2.args);
|
|
4175
|
+
lines.push(`${c2.why}: ok`);
|
|
4176
|
+
} catch (e) {
|
|
4177
|
+
const why = String(e.stderr ?? "").trim() || (e instanceof Error ? e.message : String(e));
|
|
4178
|
+
lines.push(`${c2.why}: ${c2.optional ? "skipped" : "FAILED"} \u2014 ${why.split(/\r?\n/)[0].slice(0, 200)}`);
|
|
4179
|
+
if (!c2.optional) ok = false;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
return { ok, lines: [...lines, ...plan.notes] };
|
|
4183
|
+
}
|
|
4184
|
+
function uninstallService(ctx, deps = {}) {
|
|
4185
|
+
const plan = serviceRemovalPlan(ctx);
|
|
4186
|
+
const lines = [];
|
|
4187
|
+
for (const c2 of plan.commands) {
|
|
4188
|
+
try {
|
|
4189
|
+
(deps.run ?? defaultRun)(c2.file, c2.args);
|
|
4190
|
+
lines.push(`${c2.why}: ok`);
|
|
4191
|
+
} catch (e) {
|
|
4192
|
+
lines.push(`${c2.why}: skipped \u2014 ${(e instanceof Error ? e.message : String(e)).split(/\r?\n/)[0].slice(0, 200)}`);
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
for (const f of plan.files) {
|
|
4196
|
+
(deps.remove ?? ((p) => rmSync(p, { force: true })))(f);
|
|
4197
|
+
lines.push(`removed ${f}`);
|
|
4198
|
+
}
|
|
4199
|
+
return { lines };
|
|
4200
|
+
}
|
|
4201
|
+
|
|
3853
4202
|
// src/commands/up.ts
|
|
3854
4203
|
var API_BASE3 = "https://api.proagentstore.online";
|
|
3855
4204
|
var CLI_VERSION2 = createRequire2(import.meta.url)("../package.json").version;
|
|
3856
|
-
var upCommand = new Command10("up").description("Start the browser runner for all your agent instances").option("--headless", "Run browser in headless mode").option("--instance <id>", "Connect to a specific instance only").option("--force", "Take over from another connected machine").option("--replace", "Stop the `pags up` already running here for this account, then take over").option("--now", "With --replace: stop it even though work is running on it (that work is lost)").option("--background", "Run the runner in the background, with no terminal open (stop it with `pags down`)").action(async (opts) => {
|
|
4205
|
+
var upCommand = new Command10("up").description("Start the browser runner for all your agent instances").option("--headless", "Run browser in headless mode").option("--instance <id>", "Connect to a specific instance only").option("--force", "Take over from another connected machine").option("--replace", "Stop the `pags up` already running here for this account, then take over").option("--now", "With --replace: stop it even though work is running on it (that work is lost)").option("--background", "Run the runner in the background, with no terminal open (stop it with `pags down`)").option("--install-service", "Install an OS service that keeps `pags up` running (launchd, systemd or a Scheduled Task), then exit").option("--uninstall-service", "Remove the service --install-service installed, then exit").action(async (opts) => {
|
|
4206
|
+
if (opts.uninstallService) {
|
|
4207
|
+
for (const line of uninstallService(serviceContext()).lines) writeLine(line);
|
|
4208
|
+
return;
|
|
4209
|
+
}
|
|
4210
|
+
if (!opts.background && !opts.installService && upRestartMode(process.env) === "launcher") {
|
|
4211
|
+
restartPagsUp(process.argv.slice(2), {}, { ...processRestartDeps(), log: (line) => writeLine(` ${line}`) }, { initial: true });
|
|
4212
|
+
return;
|
|
4213
|
+
}
|
|
3857
4214
|
const session = requireSession();
|
|
4215
|
+
if (opts.installService) {
|
|
4216
|
+
const result = installService(serviceContext());
|
|
4217
|
+
for (const line of result.lines) writeLine(line);
|
|
4218
|
+
writeLine(result.ok ? "The service starts `pags up` now and whenever it exits. A `pags up` already running here keeps the lock; the service takes over once it stops." : "The service was not fully installed \u2014 see the FAILED line above.");
|
|
4219
|
+
if (!result.ok) process.exitCode = 1;
|
|
4220
|
+
return;
|
|
4221
|
+
}
|
|
3858
4222
|
const state = {
|
|
3859
4223
|
user: session.user.login,
|
|
3860
4224
|
instances: [],
|
|
@@ -3871,18 +4235,34 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
3871
4235
|
// as taking a few seconds, indefinitely, because nothing counted (#497).
|
|
3872
4236
|
startedAt: Date.now()
|
|
3873
4237
|
};
|
|
4238
|
+
const unattended = process.env.PAGS_NO_PROMPT === "1";
|
|
4239
|
+
logUpdateStep(`start: pags up ${CLI_VERSION2}${unattended ? " (unattended)" : ""}`);
|
|
3874
4240
|
clearScreen();
|
|
3875
4241
|
printLogo(CLI_VERSION2);
|
|
3876
4242
|
printStep("Signed in as " + session.user.login, "ok");
|
|
3877
4243
|
printStep("Fetching instances...", "wait");
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
4244
|
+
let data;
|
|
4245
|
+
for (let failures = 1; ; failures++) {
|
|
4246
|
+
let failure;
|
|
4247
|
+
try {
|
|
4248
|
+
const res = await fetch(`${API_BASE3}/v1/instances/my/instances`, { headers: { Authorization: `Bearer ${session.token}` } });
|
|
4249
|
+
if (res.ok) {
|
|
4250
|
+
data = await res.json();
|
|
4251
|
+
break;
|
|
4252
|
+
}
|
|
4253
|
+
failure = `HTTP ${res.status}`;
|
|
4254
|
+
} catch (e) {
|
|
4255
|
+
failure = e instanceof Error ? e.message : String(e);
|
|
4256
|
+
}
|
|
4257
|
+
logUpdateStep(`start: fetching instances failed (${failure})${unattended ? "" : " \u2014 exiting"}`);
|
|
4258
|
+
if (!unattended) {
|
|
4259
|
+
printStep("Failed to fetch instances: " + failure, "fail");
|
|
4260
|
+
process.exit(1);
|
|
4261
|
+
}
|
|
4262
|
+
const wait = Math.min(5 * 6e4, 5e3 * 2 ** (failures - 1));
|
|
4263
|
+
printStep(`Failed to fetch instances (${failure}) \u2014 retrying in ${Math.round(wait / 1e3)}s`, "wait");
|
|
4264
|
+
await new Promise((resolve5) => setTimeout(resolve5, wait));
|
|
3884
4265
|
}
|
|
3885
|
-
const data = await res.json();
|
|
3886
4266
|
const active = (data.instances || []).filter((i) => i.status === "active");
|
|
3887
4267
|
let instances = active;
|
|
3888
4268
|
let elsewhere = [];
|
|
@@ -3930,9 +4310,10 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
3930
4310
|
state.activeInstance = instances.length === 1 ? instances[0].name || instances[0].slug || instances[0].id.slice(0, 8) : `${instances.length} agents`;
|
|
3931
4311
|
printStep(`Connecting ${state.activeInstance}\u2026`, "wait");
|
|
3932
4312
|
const lockFile = lockPath(session.user.login);
|
|
3933
|
-
if (opts.background && process.env[BACKGROUND_RESTART_ENV] === "1") await waitUntilTakeable(() => inspectHolder(lockFile));
|
|
4313
|
+
if (unattended || opts.background && process.env[BACKGROUND_RESTART_ENV] === "1") await waitUntilTakeable(() => inspectHolder(lockFile), { timeoutMs: 6e4 });
|
|
3934
4314
|
const held = await inspectHolder(lockFile);
|
|
3935
4315
|
if (held.lock && !isTakeable(held.verdict)) {
|
|
4316
|
+
logUpdateStep(`start: the lock is held by pid ${held.lock.pid} (${held.verdict})${opts.replace ? " \u2014 replacing it" : " \u2014 exiting"}`);
|
|
3936
4317
|
if (!opts.replace) {
|
|
3937
4318
|
clearScreen();
|
|
3938
4319
|
printLogo(CLI_VERSION2);
|
|
@@ -3950,7 +4331,7 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
3950
4331
|
}
|
|
3951
4332
|
for (const line of replaced.lines) printStep(line, "ok");
|
|
3952
4333
|
}
|
|
3953
|
-
const { spawn:
|
|
4334
|
+
const { spawn: spawn6 } = await import("child_process");
|
|
3954
4335
|
const cliPath = process.argv[1];
|
|
3955
4336
|
const args = [cliPath, "runner", "connect", ...instances.map((i) => i.id)];
|
|
3956
4337
|
if (opts.headless) args.push("--headless");
|
|
@@ -3960,7 +4341,7 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
3960
4341
|
const code = await startInBackground({ session, opts, runnerArgs: args, instances: state.instances, lockFile, watching: !opts.instance });
|
|
3961
4342
|
process.exit(code);
|
|
3962
4343
|
}
|
|
3963
|
-
const child =
|
|
4344
|
+
const child = spawn6(process.execPath, args, {
|
|
3964
4345
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3965
4346
|
// The child is the lock HOLDER (#896): it owns the relay sockets, so it is what a
|
|
3966
4347
|
// contender must find and what `--replace` must be able to stop.
|
|
@@ -4036,31 +4417,29 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
4036
4417
|
void deliverCrashReports({ apiBase: API_BASE3, token: session.token, account: session.user.login, paths: crashes }).catch(() => void 0);
|
|
4037
4418
|
};
|
|
4038
4419
|
child.on("exit", (code, signal) => {
|
|
4039
|
-
|
|
4420
|
+
const action = runnerExitAction(code, stopping);
|
|
4421
|
+
if (action === "restart") {
|
|
4422
|
+
logUpdateStep("restart: runner connect asked for a restart (exit 75)");
|
|
4040
4423
|
restartUp("Runner updated remotely \u2014 restarting pags up on the new version\u2026", { PAGS_NO_PROMPT: "1" });
|
|
4041
4424
|
return;
|
|
4042
4425
|
}
|
|
4043
4426
|
childDead = true;
|
|
4044
|
-
|
|
4045
|
-
if (
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
}
|
|
4051
|
-
if (code !== 0) {
|
|
4052
|
-
state.runner = "error";
|
|
4053
|
-
state.lastEvent = `Runner exited (code ${code})`;
|
|
4054
|
-
const recent = logs.slice(-5);
|
|
4055
|
-
if (recent.length) state.lastEvent += ": " + recent[recent.length - 1].slice(0, 60);
|
|
4056
|
-
printStatus(state);
|
|
4427
|
+
logUpdateStep(`runner connect ${signal ? `was killed by ${signal}` : `exited ${code}`}${stopping ? " (stopping)" : ""}`);
|
|
4428
|
+
if (action === "ignore") return;
|
|
4429
|
+
if (action === "stop") {
|
|
4430
|
+
clearScreen();
|
|
4431
|
+
writeLine(" Runner stopped (pags down, or replaced by another pags up).");
|
|
4432
|
+
process.exit(0);
|
|
4057
4433
|
}
|
|
4434
|
+
recordConnectExit(code, signal ?? null, logs.slice(-20));
|
|
4435
|
+
const recent = logs.slice(-5).at(-1);
|
|
4436
|
+
restartUp(`Runner ${signal ? `was killed by ${signal}` : `exited (code ${code})`}${recent ? `: ${recent.slice(0, 60)}` : ""} \u2014 starting it again\u2026`, { PAGS_NO_PROMPT: "1" }, RUNNER_DIED_EXIT_CODE);
|
|
4058
4437
|
});
|
|
4059
|
-
function restartUp(why, env = {}) {
|
|
4438
|
+
function restartUp(why, env = {}, exitCode) {
|
|
4060
4439
|
writeLine(` ${why}`);
|
|
4061
4440
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
4062
4441
|
process.stdin.pause();
|
|
4063
|
-
restartPagsUp(restartUpArgs(opts), env, { ...processRestartDeps(), log: (line) => writeLine(` ${line}`) });
|
|
4442
|
+
restartPagsUp(restartUpArgs(opts), env, { ...processRestartDeps(), log: (line) => writeLine(` ${line}`) }, { exitCode });
|
|
4064
4443
|
process.exit(1);
|
|
4065
4444
|
}
|
|
4066
4445
|
const shutdown = () => {
|
|
@@ -4125,6 +4504,26 @@ var downCommand = new Command10("down").description("Stop the browser runner and
|
|
|
4125
4504
|
}
|
|
4126
4505
|
writeLine("");
|
|
4127
4506
|
});
|
|
4507
|
+
function serviceContext() {
|
|
4508
|
+
const user = (() => {
|
|
4509
|
+
try {
|
|
4510
|
+
return userInfo().username;
|
|
4511
|
+
} catch {
|
|
4512
|
+
return process.env.USER || process.env.USERNAME || void 0;
|
|
4513
|
+
}
|
|
4514
|
+
})();
|
|
4515
|
+
return {
|
|
4516
|
+
platform: process.platform,
|
|
4517
|
+
home: homedir5(),
|
|
4518
|
+
nodePath: process.execPath,
|
|
4519
|
+
script: process.argv[1],
|
|
4520
|
+
configDir: configDir(),
|
|
4521
|
+
pathEnv: process.env.PATH ?? "",
|
|
4522
|
+
uid: process.getuid?.(),
|
|
4523
|
+
user,
|
|
4524
|
+
domain: process.env.USERDOMAIN
|
|
4525
|
+
};
|
|
4526
|
+
}
|
|
4128
4527
|
|
|
4129
4528
|
// src/index.ts
|
|
4130
4529
|
var require2 = createRequire3(import.meta.url);
|