@proagentstore/cli 0.4.91 → 0.4.92
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/index.js +168 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1716,6 +1716,152 @@ function mayAutomaticallyRestart(input) {
|
|
|
1716
1716
|
return input.authoritative && input.enabled && input.workObserved && input.busy.length === 0;
|
|
1717
1717
|
}
|
|
1718
1718
|
|
|
1719
|
+
// src/commands/runner/auto-update-controller.ts
|
|
1720
|
+
var AutomaticUpdateController = class {
|
|
1721
|
+
constructor(deps) {
|
|
1722
|
+
this.deps = deps;
|
|
1723
|
+
this.setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
1724
|
+
this.clearTimer = deps.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
1725
|
+
}
|
|
1726
|
+
deps;
|
|
1727
|
+
timer = null;
|
|
1728
|
+
checking = false;
|
|
1729
|
+
waiting = false;
|
|
1730
|
+
installing = false;
|
|
1731
|
+
failures = 0;
|
|
1732
|
+
setTimer;
|
|
1733
|
+
clearTimer;
|
|
1734
|
+
/** Called only for a true policy transition. `keep` deliberately leaves a due heartbeat timer alone. */
|
|
1735
|
+
onPolicy(action) {
|
|
1736
|
+
if (action === "cancel") return this.cancel();
|
|
1737
|
+
if (action === "start") this.schedule(true);
|
|
1738
|
+
}
|
|
1739
|
+
/** Testable immediate entry point; production reaches it through the scheduled timer. */
|
|
1740
|
+
async runNow() {
|
|
1741
|
+
const current = this.deps.policy();
|
|
1742
|
+
if (this.checking || this.waiting || !current.authoritative || !current.value.autoUpdate) return;
|
|
1743
|
+
this.checking = true;
|
|
1744
|
+
try {
|
|
1745
|
+
if (!await this.deps.refreshPolicy() || !this.enabled()) {
|
|
1746
|
+
this.deps.status("offline", { reason: "The owner policy could not be refreshed; automatic update is deferred." });
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
await this.decide(false);
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
this.fail(error);
|
|
1752
|
+
} finally {
|
|
1753
|
+
this.checking = false;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
/** Explicit remote updates and unattended checks share this single flight. */
|
|
1757
|
+
async installManually(plan) {
|
|
1758
|
+
return this.install(plan, false);
|
|
1759
|
+
}
|
|
1760
|
+
enabled() {
|
|
1761
|
+
const { value, authoritative } = this.deps.policy();
|
|
1762
|
+
return authoritative && value.autoUpdate;
|
|
1763
|
+
}
|
|
1764
|
+
schedule(initial = false) {
|
|
1765
|
+
if (!this.enabled() || this.timer || this.waiting) return;
|
|
1766
|
+
const delay = initial ? Math.round(5e3 + (this.deps.random ?? Math.random)() * 25e3) : nextAutoUpdateDelayMs(this.failures, this.deps.random);
|
|
1767
|
+
this.timer = this.setTimer(() => {
|
|
1768
|
+
this.timer = null;
|
|
1769
|
+
void this.runNow();
|
|
1770
|
+
}, delay);
|
|
1771
|
+
this.timer.unref?.();
|
|
1772
|
+
}
|
|
1773
|
+
cancel() {
|
|
1774
|
+
if (this.timer) this.clearTimer(this.timer);
|
|
1775
|
+
this.timer = null;
|
|
1776
|
+
}
|
|
1777
|
+
fail(error) {
|
|
1778
|
+
this.failures++;
|
|
1779
|
+
this.deps.status("failure", { reason: error instanceof Error ? error.message : String(error) });
|
|
1780
|
+
this.schedule();
|
|
1781
|
+
}
|
|
1782
|
+
async decide(deferred) {
|
|
1783
|
+
const facts = await this.deps.facts();
|
|
1784
|
+
if (!this.enabled()) return;
|
|
1785
|
+
const plan = this.deps.plan(facts);
|
|
1786
|
+
if (plan.action === "up-to-date") {
|
|
1787
|
+
this.failures = 0;
|
|
1788
|
+
this.deps.status("verified-success", { latestVersion: facts.latest ?? void 0, reason: void 0 });
|
|
1789
|
+
this.schedule();
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
if (plan.action === "refused") {
|
|
1793
|
+
this.deps.status("unsupported", { reason: plan.reason });
|
|
1794
|
+
this.schedule();
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
if (plan.action === "wait") {
|
|
1798
|
+
this.deps.status("waiting-for-idle", { latestVersion: plan.latest, reason: void 0 });
|
|
1799
|
+
this.defer();
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
this.deps.status("running", { latestVersion: plan.latest, reason: void 0 });
|
|
1803
|
+
const restarted = await this.install(plan, true);
|
|
1804
|
+
if (restarted) this.deps.status("restarting", { latestVersion: plan.latest, reason: void 0 });
|
|
1805
|
+
else if (!deferred) this.defer(plan.latest);
|
|
1806
|
+
}
|
|
1807
|
+
defer(latest) {
|
|
1808
|
+
if (this.waiting) return;
|
|
1809
|
+
this.waiting = true;
|
|
1810
|
+
this.timer = this.setTimer(() => {
|
|
1811
|
+
this.timer = null;
|
|
1812
|
+
void (async () => {
|
|
1813
|
+
this.waiting = false;
|
|
1814
|
+
try {
|
|
1815
|
+
if (!await this.deps.refreshPolicy() || !this.enabled()) {
|
|
1816
|
+
this.deps.status("offline", { reason: "The owner policy could not be refreshed; automatic update is deferred." });
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
await this.decide(true);
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
this.fail(error);
|
|
1822
|
+
}
|
|
1823
|
+
})();
|
|
1824
|
+
}, 15e3);
|
|
1825
|
+
this.timer.unref?.();
|
|
1826
|
+
this.deps.status("waiting-for-idle", latest ? { latestVersion: latest } : void 0);
|
|
1827
|
+
}
|
|
1828
|
+
async install(plan, automatic) {
|
|
1829
|
+
if (this.installing) return false;
|
|
1830
|
+
this.installing = true;
|
|
1831
|
+
try {
|
|
1832
|
+
return await this.deps.installAndRestart(plan, automatic);
|
|
1833
|
+
} finally {
|
|
1834
|
+
this.installing = false;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
var RelayMutationAdmission = class {
|
|
1839
|
+
draining = false;
|
|
1840
|
+
inFlight = 0;
|
|
1841
|
+
waiters = [];
|
|
1842
|
+
begin() {
|
|
1843
|
+
if (this.draining) return null;
|
|
1844
|
+
this.inFlight++;
|
|
1845
|
+
let released = false;
|
|
1846
|
+
return () => {
|
|
1847
|
+
if (released) return;
|
|
1848
|
+
released = true;
|
|
1849
|
+
this.inFlight--;
|
|
1850
|
+
if (this.inFlight === 0) {
|
|
1851
|
+
for (const wake of this.waiters.splice(0)) wake();
|
|
1852
|
+
}
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
async drain() {
|
|
1856
|
+
this.draining = true;
|
|
1857
|
+
if (this.inFlight === 0) return;
|
|
1858
|
+
await new Promise((resolve5) => this.waiters.push(resolve5));
|
|
1859
|
+
}
|
|
1860
|
+
resume() {
|
|
1861
|
+
this.draining = false;
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1864
|
+
|
|
1719
1865
|
// src/commands/runner/relay.ts
|
|
1720
1866
|
var MEMBERSHIP_SYNC_PATH = "/pags/membership/sync";
|
|
1721
1867
|
var CLI_CONTROL_PATHS = /* @__PURE__ */ new Set([MEMBERSHIP_SYNC_PATH, RUNNER_UPDATE_PATH]);
|
|
@@ -1839,7 +1985,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
1839
1985
|
};
|
|
1840
1986
|
let updateInstallInFlight = false;
|
|
1841
1987
|
let installedPendingRestart = null;
|
|
1842
|
-
|
|
1988
|
+
const relayMutationAdmission = new RelayMutationAdmission();
|
|
1843
1989
|
const installAndRestart = async (plan, automatic = false) => {
|
|
1844
1990
|
if (updateInstallInFlight) return false;
|
|
1845
1991
|
updateInstallInFlight = true;
|
|
@@ -1858,17 +2004,17 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
1858
2004
|
}
|
|
1859
2005
|
}
|
|
1860
2006
|
if (automatic) {
|
|
1861
|
-
|
|
2007
|
+
await relayMutationAdmission.drain();
|
|
1862
2008
|
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
1863
2009
|
if (!await refreshAutomaticPolicy() || !autoUpdatePolicy.autoUpdate) {
|
|
1864
|
-
|
|
2010
|
+
relayMutationAdmission.resume();
|
|
1865
2011
|
updateInstallInFlight = false;
|
|
1866
2012
|
return false;
|
|
1867
2013
|
}
|
|
1868
2014
|
}
|
|
1869
2015
|
const finalFacts = await updateFacts();
|
|
1870
2016
|
if (finalFacts.busy.length || automatic && !mayAutomaticallyRestart({ authoritative: autoUpdatePolicyAuthoritative, enabled: autoUpdatePolicy.autoUpdate, workObserved: !finalFacts.busy.includes("runner-work-observation-unavailable"), busy: finalFacts.busy })) {
|
|
1871
|
-
|
|
2017
|
+
relayMutationAdmission.resume();
|
|
1872
2018
|
updateInstallInFlight = false;
|
|
1873
2019
|
return false;
|
|
1874
2020
|
}
|
|
@@ -1985,6 +2131,17 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
1985
2131
|
}, delay);
|
|
1986
2132
|
automaticTimer.unref();
|
|
1987
2133
|
};
|
|
2134
|
+
const automaticUpdateController = new AutomaticUpdateController({
|
|
2135
|
+
policy: () => ({ value: autoUpdatePolicy, authoritative: autoUpdatePolicyAuthoritative }),
|
|
2136
|
+
refreshPolicy: refreshAutomaticPolicy,
|
|
2137
|
+
facts: updateFacts,
|
|
2138
|
+
plan: planRunnerUpdate,
|
|
2139
|
+
// This bridge is deliberately the same function used by explicit runner_update. It owns npm,
|
|
2140
|
+
// a fresh authoritative policy, the mutation drain, final local-work observation and restart.
|
|
2141
|
+
installAndRestart,
|
|
2142
|
+
status: saveAutoUpdateStatus
|
|
2143
|
+
});
|
|
2144
|
+
scheduleAutomaticUpdate = (initial = false, cancel = false) => automaticUpdateController.onPolicy(cancel ? "cancel" : initial ? "start" : "keep");
|
|
1988
2145
|
if (autoUpdatePolicyAuthoritative && autoUpdatePolicy.autoUpdate) scheduleAutomaticUpdate(true);
|
|
1989
2146
|
const answerUpdate = async (body) => {
|
|
1990
2147
|
const dryRun = body?.dryRun === true;
|
|
@@ -1995,7 +2152,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
1995
2152
|
return { status: 200, result: { ...plan, detail: "Restarts itself as soon as these engines finish their turns \u2014 no run is cut off." } };
|
|
1996
2153
|
}
|
|
1997
2154
|
try {
|
|
1998
|
-
if (!await
|
|
2155
|
+
if (!await automaticUpdateController.installManually(plan)) return { status: 409, result: { error: "A runner update is already installing or restarting." } };
|
|
1999
2156
|
} catch (e) {
|
|
2000
2157
|
return { status: 500, result: { error: `npm could not install ${plan.latest}: ${e instanceof Error ? e.message : String(e)}` } };
|
|
2001
2158
|
}
|
|
@@ -2064,7 +2221,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2064
2221
|
runnerNode,
|
|
2065
2222
|
// The final automatic-update drain is an admission gate, not merely another poll:
|
|
2066
2223
|
// command work starts only when the old runner is still permitted to serve it.
|
|
2067
|
-
() =>
|
|
2224
|
+
() => relayMutationAdmission.begin()
|
|
2068
2225
|
)
|
|
2069
2226
|
);
|
|
2070
2227
|
if (label) writeLine(`Attached agent: ${label}`);
|
|
@@ -2208,7 +2365,7 @@ async function readSessionResources(localUrl, runnerToken) {
|
|
|
2208
2365
|
}
|
|
2209
2366
|
var RTT_PROBE_PREFIX = "rtt:";
|
|
2210
2367
|
var RTT_ECHO_PREFIX = "rtt-echo:";
|
|
2211
|
-
function openRelaySocket(instanceId, wsBase, mintToken, localUrl, runnerToken, force = false, onConflict, onOpen, onControl, runnerNode = hostname3(),
|
|
2368
|
+
function openRelaySocket(instanceId, wsBase, mintToken, localUrl, runnerToken, force = false, onConflict, onOpen, onControl, runnerNode = hostname3(), beginMutation = () => () => void 0) {
|
|
2212
2369
|
let backoffMs = 1e3;
|
|
2213
2370
|
let reconnecting = false;
|
|
2214
2371
|
let closed = false;
|
|
@@ -2276,7 +2433,8 @@ function openRelaySocket(instanceId, wsBase, mintToken, localUrl, runnerToken, f
|
|
|
2276
2433
|
return;
|
|
2277
2434
|
}
|
|
2278
2435
|
const method = (cmd.method || "POST").toUpperCase();
|
|
2279
|
-
|
|
2436
|
+
const releaseMutation = method !== "GET" && method !== "HEAD" ? beginMutation() : null;
|
|
2437
|
+
if (method !== "GET" && method !== "HEAD" && !releaseMutation) {
|
|
2280
2438
|
try {
|
|
2281
2439
|
ws.send(JSON.stringify({ id: cmd.id, status: 503, result: { error: "Runner is draining for a safe automatic update; retry shortly." } }));
|
|
2282
2440
|
} catch {
|
|
@@ -2310,6 +2468,8 @@ function openRelaySocket(instanceId, wsBase, mintToken, localUrl, runnerToken, f
|
|
|
2310
2468
|
ws.send(JSON.stringify({ id: cmd.id, status: 500, error: err instanceof Error ? err.message : String(err) }));
|
|
2311
2469
|
} catch {
|
|
2312
2470
|
}
|
|
2471
|
+
} finally {
|
|
2472
|
+
releaseMutation?.();
|
|
2313
2473
|
}
|
|
2314
2474
|
};
|
|
2315
2475
|
ws.onclose = (ev) => {
|