@proagentstore/cli 0.4.100 → 0.4.101
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 +61 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1730,11 +1730,18 @@ function mayAutomaticallyRestart(input) {
|
|
|
1730
1730
|
}
|
|
1731
1731
|
|
|
1732
1732
|
// src/commands/runner/auto-update-controller.ts
|
|
1733
|
+
var MAX_OBSERVATIONS = 5;
|
|
1734
|
+
var IDLE_RETRY_MS = 15e3;
|
|
1735
|
+
function blockerClassFor(waitingFor) {
|
|
1736
|
+
if (waitingFor.includes("runner-work-observation-unavailable")) return "observation_failure";
|
|
1737
|
+
return waitingFor.length ? "active_sessions" : "unknown";
|
|
1738
|
+
}
|
|
1733
1739
|
var AutomaticUpdateController = class {
|
|
1734
1740
|
constructor(deps) {
|
|
1735
1741
|
this.deps = deps;
|
|
1736
1742
|
this.setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
1737
1743
|
this.clearTimer = deps.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
1744
|
+
this.now = deps.now ?? Date.now;
|
|
1738
1745
|
}
|
|
1739
1746
|
deps;
|
|
1740
1747
|
timer = null;
|
|
@@ -1742,8 +1749,18 @@ var AutomaticUpdateController = class {
|
|
|
1742
1749
|
waiting = false;
|
|
1743
1750
|
installing = false;
|
|
1744
1751
|
failures = 0;
|
|
1752
|
+
checkAt = null;
|
|
1753
|
+
decisionAt = null;
|
|
1754
|
+
nextRetryDueAt = null;
|
|
1755
|
+
blockerClass = null;
|
|
1756
|
+
observations = [];
|
|
1757
|
+
now;
|
|
1745
1758
|
setTimer;
|
|
1746
1759
|
clearTimer;
|
|
1760
|
+
/** The controller's own record, safe to report: times, a normalized class, and observation outcomes. */
|
|
1761
|
+
diagnostics() {
|
|
1762
|
+
return { checkAt: this.checkAt, decisionAt: this.decisionAt, nextRetryDueAt: this.nextRetryDueAt, blockerClass: this.blockerClass, observations: [...this.observations] };
|
|
1763
|
+
}
|
|
1747
1764
|
/** Called only for a true policy transition. `keep` deliberately leaves a due heartbeat timer alone. */
|
|
1748
1765
|
onPolicy(action) {
|
|
1749
1766
|
if (action === "cancel") return this.cancel();
|
|
@@ -1756,10 +1773,10 @@ var AutomaticUpdateController = class {
|
|
|
1756
1773
|
this.checking = true;
|
|
1757
1774
|
try {
|
|
1758
1775
|
if (!await this.deps.refreshPolicy() || !this.enabled()) {
|
|
1759
|
-
this.
|
|
1776
|
+
this.offline();
|
|
1760
1777
|
return;
|
|
1761
1778
|
}
|
|
1762
|
-
await this.decide(
|
|
1779
|
+
await this.decide();
|
|
1763
1780
|
} catch (error) {
|
|
1764
1781
|
this.fail(error);
|
|
1765
1782
|
} finally {
|
|
@@ -1779,23 +1796,40 @@ var AutomaticUpdateController = class {
|
|
|
1779
1796
|
const delay = initial ? Math.round(5e3 + (this.deps.random ?? Math.random)() * 25e3) : nextAutoUpdateDelayMs(this.failures, this.deps.random);
|
|
1780
1797
|
this.timer = this.setTimer(() => {
|
|
1781
1798
|
this.timer = null;
|
|
1799
|
+
this.nextRetryDueAt = null;
|
|
1782
1800
|
void this.runNow();
|
|
1783
1801
|
}, delay);
|
|
1784
1802
|
this.timer.unref?.();
|
|
1803
|
+
this.nextRetryDueAt = this.now() + delay;
|
|
1785
1804
|
}
|
|
1786
1805
|
cancel() {
|
|
1787
1806
|
if (this.timer) this.clearTimer(this.timer);
|
|
1788
1807
|
this.timer = null;
|
|
1808
|
+
this.nextRetryDueAt = null;
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* The owner policy could not be re-read, so nothing installs (fail closed) — but the controller keeps
|
|
1812
|
+
* a timer (#1020). It used to report `offline` and stop, leaving an enabled runner with no callback
|
|
1813
|
+
* until the next policy transition. `schedule` itself refuses once the policy is disabled.
|
|
1814
|
+
*/
|
|
1815
|
+
offline() {
|
|
1816
|
+
this.failures++;
|
|
1817
|
+
this.deps.status("offline", { reason: "The owner policy could not be refreshed; automatic update is deferred." });
|
|
1818
|
+
this.schedule();
|
|
1789
1819
|
}
|
|
1790
1820
|
fail(error) {
|
|
1791
1821
|
this.failures++;
|
|
1792
1822
|
this.deps.status("failure", { reason: error instanceof Error ? error.message : String(error) });
|
|
1793
1823
|
this.schedule();
|
|
1794
1824
|
}
|
|
1795
|
-
async decide(
|
|
1825
|
+
async decide() {
|
|
1796
1826
|
const facts = await this.deps.facts();
|
|
1827
|
+
this.checkAt = this.now();
|
|
1828
|
+
if (facts.observations?.length) this.observations = [...this.observations, ...facts.observations].slice(-MAX_OBSERVATIONS);
|
|
1797
1829
|
if (!this.enabled()) return;
|
|
1798
1830
|
const plan = this.deps.plan(facts);
|
|
1831
|
+
this.decisionAt = this.now();
|
|
1832
|
+
this.blockerClass = plan.action === "wait" ? blockerClassFor(plan.waitingFor) : null;
|
|
1799
1833
|
if (plan.action === "up-to-date") {
|
|
1800
1834
|
this.failures = 0;
|
|
1801
1835
|
this.deps.status("verified-success", { latestVersion: facts.latest ?? void 0, reason: void 0 });
|
|
@@ -1814,28 +1848,34 @@ var AutomaticUpdateController = class {
|
|
|
1814
1848
|
}
|
|
1815
1849
|
this.deps.status("running", { latestVersion: plan.latest, reason: void 0 });
|
|
1816
1850
|
const restarted = await this.install(plan, true);
|
|
1817
|
-
if (restarted)
|
|
1818
|
-
|
|
1851
|
+
if (restarted) {
|
|
1852
|
+
this.deps.status("restarting", { latestVersion: plan.latest, reason: void 0 });
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
this.blockerClass = "idle_wait";
|
|
1856
|
+
this.defer(plan.latest);
|
|
1819
1857
|
}
|
|
1820
1858
|
defer(latest) {
|
|
1821
1859
|
if (this.waiting) return;
|
|
1822
1860
|
this.waiting = true;
|
|
1823
1861
|
this.timer = this.setTimer(() => {
|
|
1824
1862
|
this.timer = null;
|
|
1863
|
+
this.nextRetryDueAt = null;
|
|
1825
1864
|
void (async () => {
|
|
1826
1865
|
this.waiting = false;
|
|
1827
1866
|
try {
|
|
1828
1867
|
if (!await this.deps.refreshPolicy() || !this.enabled()) {
|
|
1829
|
-
this.
|
|
1868
|
+
this.offline();
|
|
1830
1869
|
return;
|
|
1831
1870
|
}
|
|
1832
|
-
await this.decide(
|
|
1871
|
+
await this.decide();
|
|
1833
1872
|
} catch (error) {
|
|
1834
1873
|
this.fail(error);
|
|
1835
1874
|
}
|
|
1836
1875
|
})();
|
|
1837
|
-
},
|
|
1876
|
+
}, IDLE_RETRY_MS);
|
|
1838
1877
|
this.timer.unref?.();
|
|
1878
|
+
this.nextRetryDueAt = this.now() + IDLE_RETRY_MS;
|
|
1839
1879
|
this.deps.status("waiting-for-idle", latest ? { latestVersion: latest } : void 0);
|
|
1840
1880
|
}
|
|
1841
1881
|
async install(plan, automatic) {
|
|
@@ -1914,6 +1954,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
1914
1954
|
let autoUpdatePolicyAuthoritative = false;
|
|
1915
1955
|
let refreshingAutomaticPolicy = false;
|
|
1916
1956
|
let scheduleAutomaticUpdate = () => void 0;
|
|
1957
|
+
let autoUpdateDiagnostics = () => ({});
|
|
1917
1958
|
const saveAutoUpdateStatus = (status, extra = {}) => {
|
|
1918
1959
|
autoUpdatePolicy = {
|
|
1919
1960
|
...autoUpdatePolicy,
|
|
@@ -1959,7 +2000,8 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
1959
2000
|
autoUpdateStatus: {
|
|
1960
2001
|
status: autoUpdateStatusWire(autoUpdatePolicy.status),
|
|
1961
2002
|
latestVersion: autoUpdatePolicy.latestVersion,
|
|
1962
|
-
error: autoUpdatePolicy.reason
|
|
2003
|
+
error: autoUpdatePolicy.reason,
|
|
2004
|
+
...autoUpdateDiagnostics()
|
|
1963
2005
|
}
|
|
1964
2006
|
});
|
|
1965
2007
|
acceptAutoUpdatePolicy(result);
|
|
@@ -2006,7 +2048,14 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2006
2048
|
requestRunner("GET", "/health", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] })
|
|
2007
2049
|
]);
|
|
2008
2050
|
const busy = sessionsResult.status === "fulfilled" && healthResult.status === "fulfilled" ? observedRunnerWork({ sessions: sessionsResult.value.sessions, work: healthResult.value.work }) : ["runner-work-observation-unavailable"];
|
|
2051
|
+
const sampledAt = Date.now();
|
|
2052
|
+
const observations = [
|
|
2053
|
+
// An answer is a success only when it has the shape the guard reads; malformed is a failed look.
|
|
2054
|
+
{ source: "sessions", success: sessionsResult.status === "fulfilled" && Array.isArray(sessionsResult.value.sessions), sampledAt },
|
|
2055
|
+
{ source: "health", success: healthResult.status === "fulfilled" && !!healthResult.value.work && typeof healthResult.value.work === "object", sampledAt }
|
|
2056
|
+
];
|
|
2009
2057
|
return {
|
|
2058
|
+
observations,
|
|
2010
2059
|
current: CLI_VERSION,
|
|
2011
2060
|
latest: await latestPublishedVersion(),
|
|
2012
2061
|
fromSource: runsFromSource(),
|
|
@@ -2196,6 +2245,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2196
2245
|
},
|
|
2197
2246
|
status: saveAutoUpdateStatus
|
|
2198
2247
|
});
|
|
2248
|
+
autoUpdateDiagnostics = () => automaticUpdateController.diagnostics();
|
|
2199
2249
|
scheduleAutomaticUpdate = (initial = false, cancel = false) => automaticUpdateController.onPolicy(cancel ? "cancel" : initial ? "start" : "keep");
|
|
2200
2250
|
if (autoUpdatePolicyAuthoritative && autoUpdatePolicy.autoUpdate) scheduleAutomaticUpdate(true);
|
|
2201
2251
|
const answerUpdate = async (body) => {
|
|
@@ -2331,7 +2381,8 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2331
2381
|
autoUpdateStatus: {
|
|
2332
2382
|
status: autoUpdateStatusWire(autoUpdatePolicy.status),
|
|
2333
2383
|
latestVersion: autoUpdatePolicy.latestVersion,
|
|
2334
|
-
error: autoUpdatePolicy.reason
|
|
2384
|
+
error: autoUpdatePolicy.reason,
|
|
2385
|
+
...autoUpdateDiagnostics()
|
|
2335
2386
|
}
|
|
2336
2387
|
});
|
|
2337
2388
|
acceptAutoUpdatePolicy(response);
|