@proagentstore/cli 0.4.108 → 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
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) {
|
|
@@ -1732,6 +1735,18 @@ function acceptPolicyResponse(local, wasAuthoritative, response) {
|
|
|
1732
1735
|
const ownerTransition = action === "cancel" || !local.autoUpdate;
|
|
1733
1736
|
return { policy: ownerTransition ? { ...local, ...remote } : { ...local, autoUpdate: remote.autoUpdate }, action };
|
|
1734
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
|
+
};
|
|
1735
1750
|
function mayAutomaticallyRestart(input) {
|
|
1736
1751
|
return input.authoritative && input.enabled && input.workObserved && input.busy.length === 0;
|
|
1737
1752
|
}
|
|
@@ -1739,6 +1754,21 @@ function mayAutomaticallyRestart(input) {
|
|
|
1739
1754
|
// src/commands/runner/auto-update-controller.ts
|
|
1740
1755
|
var MAX_OBSERVATIONS = 5;
|
|
1741
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
|
+
}
|
|
1742
1772
|
function blockerClassFor(waitingFor) {
|
|
1743
1773
|
if (waitingFor.includes("runner-work-observation-unavailable")) return "observation_failure";
|
|
1744
1774
|
return waitingFor.length ? "active_sessions" : "unknown";
|
|
@@ -1748,6 +1778,7 @@ var AutomaticUpdateController = class {
|
|
|
1748
1778
|
this.deps = deps;
|
|
1749
1779
|
this.setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
1750
1780
|
this.clearTimer = deps.clearTimer ?? ((timer) => clearTimeout(timer));
|
|
1781
|
+
this.boundTimer = deps.boundTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
1751
1782
|
this.now = deps.now ?? Date.now;
|
|
1752
1783
|
}
|
|
1753
1784
|
deps;
|
|
@@ -1764,6 +1795,7 @@ var AutomaticUpdateController = class {
|
|
|
1764
1795
|
now;
|
|
1765
1796
|
setTimer;
|
|
1766
1797
|
clearTimer;
|
|
1798
|
+
boundTimer;
|
|
1767
1799
|
/** The controller's own record, safe to report: times, a normalized class, and observation outcomes. */
|
|
1768
1800
|
diagnostics() {
|
|
1769
1801
|
return { checkAt: this.checkAt, decisionAt: this.decisionAt, nextRetryDueAt: this.nextRetryDueAt, blockerClass: this.blockerClass, observations: [...this.observations] };
|
|
@@ -1781,7 +1813,7 @@ var AutomaticUpdateController = class {
|
|
|
1781
1813
|
if (this.checking || this.waiting || !this.retryable()) return;
|
|
1782
1814
|
this.checking = true;
|
|
1783
1815
|
try {
|
|
1784
|
-
if (!await this.deps.refreshPolicy() || !this.enabled()) {
|
|
1816
|
+
if (!await this.bounded((signal) => this.deps.refreshPolicy(signal)) || !this.enabled()) {
|
|
1785
1817
|
this.offline();
|
|
1786
1818
|
return;
|
|
1787
1819
|
}
|
|
@@ -1842,17 +1874,53 @@ var AutomaticUpdateController = class {
|
|
|
1842
1874
|
this.deps.status("failure", { reason: error instanceof Error ? error.message : String(error) });
|
|
1843
1875
|
this.schedule();
|
|
1844
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
|
+
}
|
|
1845
1901
|
async decide() {
|
|
1846
|
-
const facts = await this.deps.facts();
|
|
1902
|
+
const facts = await this.bounded((signal) => this.deps.facts(signal));
|
|
1847
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
|
+
}
|
|
1848
1910
|
if (facts.observations?.length) this.observations = [...this.observations, ...facts.observations].slice(-MAX_OBSERVATIONS);
|
|
1849
1911
|
if (!this.enabled()) return this.offline();
|
|
1850
1912
|
const plan = this.deps.plan(facts);
|
|
1851
1913
|
this.decisionAt = this.now();
|
|
1852
1914
|
this.blockerClass = plan.action === "wait" ? blockerClassFor(plan.waitingFor) : null;
|
|
1853
1915
|
if (plan.action === "up-to-date") {
|
|
1854
|
-
this.
|
|
1855
|
-
|
|
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
|
+
}
|
|
1856
1924
|
this.schedule();
|
|
1857
1925
|
return;
|
|
1858
1926
|
}
|
|
@@ -1885,7 +1953,7 @@ var AutomaticUpdateController = class {
|
|
|
1885
1953
|
void (async () => {
|
|
1886
1954
|
this.waiting = false;
|
|
1887
1955
|
try {
|
|
1888
|
-
if (!await this.deps.refreshPolicy() || !this.enabled()) {
|
|
1956
|
+
if (!await this.bounded((signal) => this.deps.refreshPolicy(signal)) || !this.enabled()) {
|
|
1889
1957
|
this.offline();
|
|
1890
1958
|
return;
|
|
1891
1959
|
}
|
|
@@ -1985,8 +2053,9 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
1985
2053
|
};
|
|
1986
2054
|
if (autoUpdateKey) saveAutoUpdatePolicy(autoUpdateKey, autoUpdatePolicy);
|
|
1987
2055
|
};
|
|
1988
|
-
const
|
|
1989
|
-
|
|
2056
|
+
const policyFence = new ResponseFence();
|
|
2057
|
+
const acceptAutoUpdatePolicy = (response, sent) => {
|
|
2058
|
+
if (!autoUpdateKey || !policyFence.accepts(sent)) return;
|
|
1990
2059
|
const accepted = acceptPolicyResponse(autoUpdatePolicy, autoUpdatePolicyAuthoritative, response);
|
|
1991
2060
|
if (!accepted) return;
|
|
1992
2061
|
autoUpdatePolicy = accepted.policy;
|
|
@@ -2005,7 +2074,8 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2005
2074
|
const caps = Array.isArray(capabilities.capabilities) ? capabilities.capabilities.filter((item) => typeof item === "string") : [];
|
|
2006
2075
|
const registered = /* @__PURE__ */ new Set();
|
|
2007
2076
|
let lastRegisterError = "";
|
|
2008
|
-
const registerRuntime = async (id, forceClaim = force) => {
|
|
2077
|
+
const registerRuntime = async (id, forceClaim = force, signal) => {
|
|
2078
|
+
const sent = policyFence.next();
|
|
2009
2079
|
try {
|
|
2010
2080
|
const result = await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime`, opts, {
|
|
2011
2081
|
endpointUrl: localUrl,
|
|
@@ -2023,8 +2093,9 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2023
2093
|
error: autoUpdatePolicy.reason,
|
|
2024
2094
|
...autoUpdateDiagnostics()
|
|
2025
2095
|
}
|
|
2026
|
-
});
|
|
2027
|
-
|
|
2096
|
+
}, signal);
|
|
2097
|
+
if (signal?.aborted) return false;
|
|
2098
|
+
acceptAutoUpdatePolicy(result, sent);
|
|
2028
2099
|
registered.add(id);
|
|
2029
2100
|
return true;
|
|
2030
2101
|
} catch (e) {
|
|
@@ -2035,13 +2106,13 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2035
2106
|
return false;
|
|
2036
2107
|
}
|
|
2037
2108
|
};
|
|
2038
|
-
const refreshAutomaticPolicy = async () => {
|
|
2109
|
+
const refreshAutomaticPolicy = async (signal) => {
|
|
2039
2110
|
const id = registered.values().next().value;
|
|
2040
2111
|
if (!id) return false;
|
|
2041
2112
|
autoUpdatePolicyAuthoritative = false;
|
|
2042
2113
|
refreshingAutomaticPolicy = true;
|
|
2043
2114
|
try {
|
|
2044
|
-
return await registerRuntime(id) && autoUpdatePolicyAuthoritative;
|
|
2115
|
+
return await registerRuntime(id, force, signal) && autoUpdatePolicyAuthoritative;
|
|
2045
2116
|
} finally {
|
|
2046
2117
|
refreshingAutomaticPolicy = false;
|
|
2047
2118
|
}
|
|
@@ -2058,14 +2129,14 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2058
2129
|
writeLine(formatStatusLine({ registration: state, agents, reason: state === "ok" ? void 0 : lastRegisterError }));
|
|
2059
2130
|
};
|
|
2060
2131
|
for (const id of instanceIds) await registerRuntime(id);
|
|
2061
|
-
const updateFacts = async () => {
|
|
2132
|
+
const updateFacts = async (signal) => {
|
|
2062
2133
|
const [sessionsResult, healthResult] = await Promise.allSettled([
|
|
2063
2134
|
requestRunner("GET", "/coding/sessions", {
|
|
2064
2135
|
url: localUrl,
|
|
2065
2136
|
token: runnerToken,
|
|
2066
2137
|
instanceId: instanceIds[0]
|
|
2067
|
-
}),
|
|
2068
|
-
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)
|
|
2069
2140
|
]);
|
|
2070
2141
|
const busy = sessionsResult.status === "fulfilled" && healthResult.status === "fulfilled" ? observedRunnerWork({ sessions: sessionsResult.value.sessions, work: healthResult.value.work }) : ["runner-work-observation-unavailable"];
|
|
2071
2142
|
const sampledAt = Date.now();
|
|
@@ -2077,7 +2148,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2077
2148
|
return {
|
|
2078
2149
|
observations,
|
|
2079
2150
|
current: CLI_VERSION,
|
|
2080
|
-
latest: await latestPublishedVersion(),
|
|
2151
|
+
latest: await latestPublishedVersion(void 0, signal),
|
|
2081
2152
|
fromSource: runsFromSource(),
|
|
2082
2153
|
restarter: restarterFrom(process.env),
|
|
2083
2154
|
// A restart ends browser/application work too. Give these durable names to the same
|
|
@@ -2394,6 +2465,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2394
2465
|
});
|
|
2395
2466
|
for (const id of [...attached.keys()]) {
|
|
2396
2467
|
try {
|
|
2468
|
+
const sent = policyFence.next();
|
|
2397
2469
|
const response = await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime/heartbeat`, opts, {
|
|
2398
2470
|
runnerNode,
|
|
2399
2471
|
machineId: machine.id,
|
|
@@ -2405,7 +2477,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2405
2477
|
...autoUpdateDiagnostics()
|
|
2406
2478
|
}
|
|
2407
2479
|
});
|
|
2408
|
-
acceptAutoUpdatePolicy(response);
|
|
2480
|
+
acceptAutoUpdatePolicy(response, sent);
|
|
2409
2481
|
} catch (e) {
|
|
2410
2482
|
failure = e instanceof Error ? e.message : String(e);
|
|
2411
2483
|
}
|