@proagentstore/cli 0.4.107 → 0.4.109
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 +1 -1
- package/dist/{chunk-JOWYSZOL.js → chunk-RGNU4W6B.js} +6 -2
- package/dist/index.js +106 -28
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -12,6 +12,9 @@ var KEEP = 2;
|
|
|
12
12
|
function payloadRoot() {
|
|
13
13
|
return process.env.PAGS_CLI_CACHE || join(homedir(), ".config", "proagentstore", "cli");
|
|
14
14
|
}
|
|
15
|
+
function isVersion(v) {
|
|
16
|
+
return typeof v === "string" && /^(\d+)\.(\d+)\.(\d+)/.test(v.trim());
|
|
17
|
+
}
|
|
15
18
|
function olderThan(a, b) {
|
|
16
19
|
const parse = (v) => /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim())?.slice(1).map(Number);
|
|
17
20
|
const x = parse(a);
|
|
@@ -44,9 +47,9 @@ function cachedPayloads(root = payloadRoot()) {
|
|
|
44
47
|
function newestPayload(payloads) {
|
|
45
48
|
return payloads.reduce((best, p) => !best || olderThan(best.version, p.version) ? p : best, null);
|
|
46
49
|
}
|
|
47
|
-
async function latestPublishedVersion(timeoutMs = 3e4) {
|
|
50
|
+
async function latestPublishedVersion(timeoutMs = 3e4, signal) {
|
|
48
51
|
try {
|
|
49
|
-
const { stdout } = await run("npm", ["view", CLI_PACKAGE, "version"], { timeout: timeoutMs });
|
|
52
|
+
const { stdout } = await run("npm", ["view", CLI_PACKAGE, "version"], { timeout: timeoutMs, signal });
|
|
50
53
|
return stdout.trim() || null;
|
|
51
54
|
} catch {
|
|
52
55
|
return null;
|
|
@@ -88,6 +91,7 @@ export {
|
|
|
88
91
|
CLI_PACKAGE,
|
|
89
92
|
BOOTSTRAP_ENV,
|
|
90
93
|
NO_SELF_UPDATE_ENV,
|
|
94
|
+
isVersion,
|
|
91
95
|
olderThan,
|
|
92
96
|
payloadAt,
|
|
93
97
|
cachedPayloads,
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,10 @@ import {
|
|
|
3
3
|
BOOTSTRAP_ENV,
|
|
4
4
|
CLI_PACKAGE,
|
|
5
5
|
installPayload,
|
|
6
|
+
isVersion,
|
|
6
7
|
latestPublishedVersion,
|
|
7
8
|
olderThan
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-RGNU4W6B.js";
|
|
9
10
|
|
|
10
11
|
// src/index.ts
|
|
11
12
|
import { createRequire as createRequire3 } from "module";
|
|
@@ -832,7 +833,7 @@ function buildRuntimeRegistrationBody(opts, capabilities = []) {
|
|
|
832
833
|
machineNames: machine.names
|
|
833
834
|
};
|
|
834
835
|
}
|
|
835
|
-
async function requestRunner(method, path, opts, body) {
|
|
836
|
+
async function requestRunner(method, path, opts, body, signal) {
|
|
836
837
|
const headers = {
|
|
837
838
|
...runnerRequestHeaders(opts)
|
|
838
839
|
};
|
|
@@ -840,7 +841,8 @@ async function requestRunner(method, path, opts, body) {
|
|
|
840
841
|
const res = await fetch(`${runnerBaseUrl(opts.url)}${path}`, {
|
|
841
842
|
method,
|
|
842
843
|
headers,
|
|
843
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
844
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
845
|
+
signal
|
|
844
846
|
});
|
|
845
847
|
const { text, data } = await readResponse(res);
|
|
846
848
|
if (!res.ok) {
|
|
@@ -849,7 +851,7 @@ async function requestRunner(method, path, opts, body) {
|
|
|
849
851
|
}
|
|
850
852
|
return data;
|
|
851
853
|
}
|
|
852
|
-
async function requestPags(method, path, opts, body) {
|
|
854
|
+
async function requestPags(method, path, opts, body, signal) {
|
|
853
855
|
const headers = {
|
|
854
856
|
...pagsHeaders(opts.pagsToken)
|
|
855
857
|
};
|
|
@@ -860,7 +862,8 @@ async function requestPags(method, path, opts, body) {
|
|
|
860
862
|
const res = await fetch(`${pagsApiBase(opts.apiBase)}${path}`, {
|
|
861
863
|
method,
|
|
862
864
|
headers,
|
|
863
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
865
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
866
|
+
signal
|
|
864
867
|
});
|
|
865
868
|
const { text, data } = await readResponse(res);
|
|
866
869
|
if (!res.ok) {
|
|
@@ -1725,6 +1728,25 @@ function policyScheduleAction(previous, next, wasAuthoritative) {
|
|
|
1725
1728
|
if (!wasAuthoritative || !previous.autoUpdate) return "start";
|
|
1726
1729
|
return "keep";
|
|
1727
1730
|
}
|
|
1731
|
+
function acceptPolicyResponse(local, wasAuthoritative, response) {
|
|
1732
|
+
const remote = policyFromResponse(response);
|
|
1733
|
+
if (!remote) return null;
|
|
1734
|
+
const action = policyScheduleAction(local, remote, wasAuthoritative);
|
|
1735
|
+
const ownerTransition = action === "cancel" || !local.autoUpdate;
|
|
1736
|
+
return { policy: ownerTransition ? { ...local, ...remote } : { ...local, autoUpdate: remote.autoUpdate }, action };
|
|
1737
|
+
}
|
|
1738
|
+
var ResponseFence = class {
|
|
1739
|
+
sent = 0;
|
|
1740
|
+
accepted = 0;
|
|
1741
|
+
next() {
|
|
1742
|
+
return ++this.sent;
|
|
1743
|
+
}
|
|
1744
|
+
accepts(sentAs) {
|
|
1745
|
+
if (sentAs < this.accepted) return false;
|
|
1746
|
+
this.accepted = sentAs;
|
|
1747
|
+
return true;
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1728
1750
|
function mayAutomaticallyRestart(input) {
|
|
1729
1751
|
return input.authoritative && input.enabled && input.workObserved && input.busy.length === 0;
|
|
1730
1752
|
}
|
|
@@ -1732,6 +1754,21 @@ function mayAutomaticallyRestart(input) {
|
|
|
1732
1754
|
// src/commands/runner/auto-update-controller.ts
|
|
1733
1755
|
var MAX_OBSERVATIONS = 5;
|
|
1734
1756
|
var IDLE_RETRY_MS = 15e3;
|
|
1757
|
+
var OBSERVATION_TIMEOUT_MS = 3e4;
|
|
1758
|
+
function updateVerdict(attempt, facts) {
|
|
1759
|
+
const target = attempt.status === "running" || attempt.status === "restarting" ? attempt.latestVersion : void 0;
|
|
1760
|
+
if (!isVersion(facts.current) || !isVersion(facts.latest)) return { kind: "unverified", reason: `the running (${facts.current}) or published (${facts.latest}) version could not be read` };
|
|
1761
|
+
if (target === void 0) return { kind: "current" };
|
|
1762
|
+
if (!isVersion(target)) return { kind: "unverified", reason: `the attempted update's target (${target}) could not be read` };
|
|
1763
|
+
if (olderThan(facts.current, target)) return { kind: "unverified", reason: `the runner came back on ${facts.current}, not the attempted ${target}` };
|
|
1764
|
+
const observed = facts.observations ?? [];
|
|
1765
|
+
const answered = (source) => observed.some((o) => o.source === source && o.success);
|
|
1766
|
+
if (!observed.length) return { kind: "unverified", reason: `the runner is on ${facts.current}, but no health look was taken` };
|
|
1767
|
+
if (!answered("sessions") || !answered("health")) {
|
|
1768
|
+
return { kind: "unverified", reason: `the runner is on ${facts.current}, but its local runtime answered only partly (sessions ${answered("sessions") ? "ok" : "failed"}, health ${answered("health") ? "ok" : "failed"})` };
|
|
1769
|
+
}
|
|
1770
|
+
return { kind: "verified" };
|
|
1771
|
+
}
|
|
1735
1772
|
function blockerClassFor(waitingFor) {
|
|
1736
1773
|
if (waitingFor.includes("runner-work-observation-unavailable")) return "observation_failure";
|
|
1737
1774
|
return waitingFor.length ? "active_sessions" : "unknown";
|
|
@@ -1741,6 +1778,7 @@ var AutomaticUpdateController = class {
|
|
|
1741
1778
|
this.deps = deps;
|
|
1742
1779
|
this.setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
1743
1780
|
this.clearTimer = deps.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
1781
|
+
this.boundTimer = deps.boundTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
1744
1782
|
this.now = deps.now ?? Date.now;
|
|
1745
1783
|
}
|
|
1746
1784
|
deps;
|
|
@@ -1757,6 +1795,7 @@ var AutomaticUpdateController = class {
|
|
|
1757
1795
|
now;
|
|
1758
1796
|
setTimer;
|
|
1759
1797
|
clearTimer;
|
|
1798
|
+
boundTimer;
|
|
1760
1799
|
/** The controller's own record, safe to report: times, a normalized class, and observation outcomes. */
|
|
1761
1800
|
diagnostics() {
|
|
1762
1801
|
return { checkAt: this.checkAt, decisionAt: this.decisionAt, nextRetryDueAt: this.nextRetryDueAt, blockerClass: this.blockerClass, observations: [...this.observations] };
|
|
@@ -1774,7 +1813,7 @@ var AutomaticUpdateController = class {
|
|
|
1774
1813
|
if (this.checking || this.waiting || !this.retryable()) return;
|
|
1775
1814
|
this.checking = true;
|
|
1776
1815
|
try {
|
|
1777
|
-
if (!await this.deps.refreshPolicy() || !this.enabled()) {
|
|
1816
|
+
if (!await this.bounded((signal) => this.deps.refreshPolicy(signal)) || !this.enabled()) {
|
|
1778
1817
|
this.offline();
|
|
1779
1818
|
return;
|
|
1780
1819
|
}
|
|
@@ -1835,17 +1874,53 @@ var AutomaticUpdateController = class {
|
|
|
1835
1874
|
this.deps.status("failure", { reason: error instanceof Error ? error.message : String(error) });
|
|
1836
1875
|
this.schedule();
|
|
1837
1876
|
}
|
|
1877
|
+
/**
|
|
1878
|
+
* One bounded piece of work (#1020): a policy refresh or a facts look. Past {@link OBSERVATION_TIMEOUT_MS} its
|
|
1879
|
+
* signal is aborted (the relay drops that request's response, so a late answer can never restore authority) and
|
|
1880
|
+
* this resolves null — the race has settled, so the late result cannot overwrite a newer one or reach planning,
|
|
1881
|
+
* install or restart. A late rejection is absorbed rather than left unhandled.
|
|
1882
|
+
*/
|
|
1883
|
+
async bounded(work) {
|
|
1884
|
+
const abort = new AbortController();
|
|
1885
|
+
let timer;
|
|
1886
|
+
const expired = new Promise((resolve5) => {
|
|
1887
|
+
timer = this.boundTimer(() => {
|
|
1888
|
+
abort.abort();
|
|
1889
|
+
resolve5(null);
|
|
1890
|
+
}, OBSERVATION_TIMEOUT_MS);
|
|
1891
|
+
timer.unref?.();
|
|
1892
|
+
});
|
|
1893
|
+
const running = work(abort.signal);
|
|
1894
|
+
running.catch(() => void 0);
|
|
1895
|
+
try {
|
|
1896
|
+
return await Promise.race([running, expired]);
|
|
1897
|
+
} finally {
|
|
1898
|
+
if (timer) this.clearTimer(timer);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1838
1901
|
async decide() {
|
|
1839
|
-
const facts = await this.deps.facts();
|
|
1902
|
+
const facts = await this.bounded((signal) => this.deps.facts(signal));
|
|
1840
1903
|
this.checkAt = this.now();
|
|
1904
|
+
if (!facts) {
|
|
1905
|
+
this.failures++;
|
|
1906
|
+
this.deps.status("failure", { reason: `The local work and registry check did not answer within ${OBSERVATION_TIMEOUT_MS / 1e3}s; it will be retried.` });
|
|
1907
|
+
this.schedule();
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1841
1910
|
if (facts.observations?.length) this.observations = [...this.observations, ...facts.observations].slice(-MAX_OBSERVATIONS);
|
|
1842
1911
|
if (!this.enabled()) return this.offline();
|
|
1843
1912
|
const plan = this.deps.plan(facts);
|
|
1844
1913
|
this.decisionAt = this.now();
|
|
1845
1914
|
this.blockerClass = plan.action === "wait" ? blockerClassFor(plan.waitingFor) : null;
|
|
1846
1915
|
if (plan.action === "up-to-date") {
|
|
1847
|
-
this.
|
|
1848
|
-
|
|
1916
|
+
const verdict = updateVerdict(this.deps.policy().value, facts);
|
|
1917
|
+
if (verdict.kind === "unverified") {
|
|
1918
|
+
this.failures++;
|
|
1919
|
+
this.deps.status("failure", { reason: `Not verified: ${verdict.reason}.` });
|
|
1920
|
+
} else {
|
|
1921
|
+
this.failures = 0;
|
|
1922
|
+
this.deps.status(verdict.kind === "verified" ? "verified-success" : "enabled", { latestVersion: facts.latest ?? void 0, reason: void 0 });
|
|
1923
|
+
}
|
|
1849
1924
|
this.schedule();
|
|
1850
1925
|
return;
|
|
1851
1926
|
}
|
|
@@ -1878,7 +1953,7 @@ var AutomaticUpdateController = class {
|
|
|
1878
1953
|
void (async () => {
|
|
1879
1954
|
this.waiting = false;
|
|
1880
1955
|
try {
|
|
1881
|
-
if (!await this.deps.refreshPolicy() || !this.enabled()) {
|
|
1956
|
+
if (!await this.bounded((signal) => this.deps.refreshPolicy(signal)) || !this.enabled()) {
|
|
1882
1957
|
this.offline();
|
|
1883
1958
|
return;
|
|
1884
1959
|
}
|
|
@@ -1978,20 +2053,20 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
1978
2053
|
};
|
|
1979
2054
|
if (autoUpdateKey) saveAutoUpdatePolicy(autoUpdateKey, autoUpdatePolicy);
|
|
1980
2055
|
};
|
|
1981
|
-
const
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
autoUpdatePolicy =
|
|
2056
|
+
const policyFence = new ResponseFence();
|
|
2057
|
+
const acceptAutoUpdatePolicy = (response, sent) => {
|
|
2058
|
+
if (!autoUpdateKey || !policyFence.accepts(sent)) return;
|
|
2059
|
+
const accepted = acceptPolicyResponse(autoUpdatePolicy, autoUpdatePolicyAuthoritative, response);
|
|
2060
|
+
if (!accepted) return;
|
|
2061
|
+
autoUpdatePolicy = accepted.policy;
|
|
1987
2062
|
autoUpdatePolicyAuthoritative = true;
|
|
1988
2063
|
if (autoUpdateKey) saveAutoUpdatePolicy(autoUpdateKey, autoUpdatePolicy);
|
|
1989
|
-
if (action === "cancel") {
|
|
2064
|
+
if (accepted.action === "cancel") {
|
|
1990
2065
|
scheduleAutomaticUpdate(false, true);
|
|
1991
2066
|
return;
|
|
1992
2067
|
}
|
|
1993
2068
|
if (refreshingAutomaticPolicy) return;
|
|
1994
|
-
if (action === "start") {
|
|
2069
|
+
if (accepted.action === "start") {
|
|
1995
2070
|
scheduleAutomaticUpdate(true);
|
|
1996
2071
|
}
|
|
1997
2072
|
};
|
|
@@ -1999,7 +2074,8 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
1999
2074
|
const caps = Array.isArray(capabilities.capabilities) ? capabilities.capabilities.filter((item) => typeof item === "string") : [];
|
|
2000
2075
|
const registered = /* @__PURE__ */ new Set();
|
|
2001
2076
|
let lastRegisterError = "";
|
|
2002
|
-
const registerRuntime = async (id, forceClaim = force) => {
|
|
2077
|
+
const registerRuntime = async (id, forceClaim = force, signal) => {
|
|
2078
|
+
const sent = policyFence.next();
|
|
2003
2079
|
try {
|
|
2004
2080
|
const result = await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime`, opts, {
|
|
2005
2081
|
endpointUrl: localUrl,
|
|
@@ -2017,8 +2093,9 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2017
2093
|
error: autoUpdatePolicy.reason,
|
|
2018
2094
|
...autoUpdateDiagnostics()
|
|
2019
2095
|
}
|
|
2020
|
-
});
|
|
2021
|
-
|
|
2096
|
+
}, signal);
|
|
2097
|
+
if (signal?.aborted) return false;
|
|
2098
|
+
acceptAutoUpdatePolicy(result, sent);
|
|
2022
2099
|
registered.add(id);
|
|
2023
2100
|
return true;
|
|
2024
2101
|
} catch (e) {
|
|
@@ -2029,13 +2106,13 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2029
2106
|
return false;
|
|
2030
2107
|
}
|
|
2031
2108
|
};
|
|
2032
|
-
const refreshAutomaticPolicy = async () => {
|
|
2109
|
+
const refreshAutomaticPolicy = async (signal) => {
|
|
2033
2110
|
const id = registered.values().next().value;
|
|
2034
2111
|
if (!id) return false;
|
|
2035
2112
|
autoUpdatePolicyAuthoritative = false;
|
|
2036
2113
|
refreshingAutomaticPolicy = true;
|
|
2037
2114
|
try {
|
|
2038
|
-
return await registerRuntime(id) && autoUpdatePolicyAuthoritative;
|
|
2115
|
+
return await registerRuntime(id, force, signal) && autoUpdatePolicyAuthoritative;
|
|
2039
2116
|
} finally {
|
|
2040
2117
|
refreshingAutomaticPolicy = false;
|
|
2041
2118
|
}
|
|
@@ -2052,14 +2129,14 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2052
2129
|
writeLine(formatStatusLine({ registration: state, agents, reason: state === "ok" ? void 0 : lastRegisterError }));
|
|
2053
2130
|
};
|
|
2054
2131
|
for (const id of instanceIds) await registerRuntime(id);
|
|
2055
|
-
const updateFacts = async () => {
|
|
2132
|
+
const updateFacts = async (signal) => {
|
|
2056
2133
|
const [sessionsResult, healthResult] = await Promise.allSettled([
|
|
2057
2134
|
requestRunner("GET", "/coding/sessions", {
|
|
2058
2135
|
url: localUrl,
|
|
2059
2136
|
token: runnerToken,
|
|
2060
2137
|
instanceId: instanceIds[0]
|
|
2061
|
-
}),
|
|
2062
|
-
requestRunner("GET", "/health", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] })
|
|
2138
|
+
}, void 0, signal),
|
|
2139
|
+
requestRunner("GET", "/health", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] }, void 0, signal)
|
|
2063
2140
|
]);
|
|
2064
2141
|
const busy = sessionsResult.status === "fulfilled" && healthResult.status === "fulfilled" ? observedRunnerWork({ sessions: sessionsResult.value.sessions, work: healthResult.value.work }) : ["runner-work-observation-unavailable"];
|
|
2065
2142
|
const sampledAt = Date.now();
|
|
@@ -2071,7 +2148,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2071
2148
|
return {
|
|
2072
2149
|
observations,
|
|
2073
2150
|
current: CLI_VERSION,
|
|
2074
|
-
latest: await latestPublishedVersion(),
|
|
2151
|
+
latest: await latestPublishedVersion(void 0, signal),
|
|
2075
2152
|
fromSource: runsFromSource(),
|
|
2076
2153
|
restarter: restarterFrom(process.env),
|
|
2077
2154
|
// A restart ends browser/application work too. Give these durable names to the same
|
|
@@ -2388,6 +2465,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2388
2465
|
});
|
|
2389
2466
|
for (const id of [...attached.keys()]) {
|
|
2390
2467
|
try {
|
|
2468
|
+
const sent = policyFence.next();
|
|
2391
2469
|
const response = await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime/heartbeat`, opts, {
|
|
2392
2470
|
runnerNode,
|
|
2393
2471
|
machineId: machine.id,
|
|
@@ -2399,7 +2477,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2399
2477
|
...autoUpdateDiagnostics()
|
|
2400
2478
|
}
|
|
2401
2479
|
});
|
|
2402
|
-
acceptAutoUpdatePolicy(response);
|
|
2480
|
+
acceptAutoUpdatePolicy(response, sent);
|
|
2403
2481
|
} catch (e) {
|
|
2404
2482
|
failure = e instanceof Error ? e.message : String(e);
|
|
2405
2483
|
}
|