@proagentstore/cli 0.4.109 → 0.4.111
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 +305 -282
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -689,16 +689,16 @@ function describeCandidate(candidate, nowMs) {
|
|
|
689
689
|
const agents = `${candidate.agentCount} agent${candidate.agentCount === 1 ? "" : "s"}`;
|
|
690
690
|
return `${relativeAge(candidate.lastSeenAt, nowMs)} \xB7 ${agents}`;
|
|
691
691
|
}
|
|
692
|
-
function parseSelection(input,
|
|
692
|
+
function parseSelection(input, count2) {
|
|
693
693
|
const raw = input.trim().toLowerCase();
|
|
694
694
|
if (!raw || raw === "n" || raw === "no" || raw === "s" || raw === "skip" || raw === "none") return { kind: "skip" };
|
|
695
|
-
if (raw === "a" || raw === "all") return { kind: "pick", indices: Array.from({ length:
|
|
695
|
+
if (raw === "a" || raw === "all") return { kind: "pick", indices: Array.from({ length: count2 }, (_, i) => i) };
|
|
696
696
|
const parts = raw.split(/[\s,]+/).filter(Boolean);
|
|
697
697
|
const indices = [];
|
|
698
698
|
for (const part of parts) {
|
|
699
699
|
if (!/^\d+$/.test(part)) return { kind: "invalid", message: `"${part}" is not a number.` };
|
|
700
700
|
const n = Number.parseInt(part, 10);
|
|
701
|
-
if (n < 1 || n >
|
|
701
|
+
if (n < 1 || n > count2) return { kind: "invalid", message: `${n} is not on the list (1\u2013${count2}).` };
|
|
702
702
|
if (!indices.includes(n - 1)) indices.push(n - 1);
|
|
703
703
|
}
|
|
704
704
|
return indices.length ? { kind: "pick", indices } : { kind: "skip" };
|
|
@@ -1632,6 +1632,13 @@ function autoUpdateStatusWire(status) {
|
|
|
1632
1632
|
failure: "failed"
|
|
1633
1633
|
}[status] ?? status;
|
|
1634
1634
|
}
|
|
1635
|
+
function parseRecovery(raw) {
|
|
1636
|
+
const r = raw;
|
|
1637
|
+
if (!r || typeof r.target !== "string" || r.target.length > 80 || typeof r.at !== "string" || !Array.isArray(r.attachments)) return void 0;
|
|
1638
|
+
if (r.attachments.length > 200 || !r.attachments.every((a) => typeof a === "string" && a.length > 0 && a.length <= 128)) return void 0;
|
|
1639
|
+
const verifiedAt = typeof r.verifiedAt === "string" && r.verifiedAt ? r.verifiedAt : void 0;
|
|
1640
|
+
return { target: r.target, attachments: [...r.attachments], at: r.at, ...verifiedAt ? { verifiedAt } : {} };
|
|
1641
|
+
}
|
|
1635
1642
|
var statuses = /* @__PURE__ */ new Set([
|
|
1636
1643
|
"enabled",
|
|
1637
1644
|
"checking",
|
|
@@ -1668,12 +1675,14 @@ function parseAutoUpdatePolicy(value) {
|
|
|
1668
1675
|
const latestVersion = raw.latestVersion ?? raw.latest_version;
|
|
1669
1676
|
const lastAttemptAt = raw.lastAttemptAt ?? raw.last_attempt_at;
|
|
1670
1677
|
const reason = raw.reason;
|
|
1678
|
+
const recovery = parseRecovery(raw.recovery);
|
|
1671
1679
|
return {
|
|
1672
1680
|
autoUpdate,
|
|
1673
1681
|
...status ? { status } : {},
|
|
1674
1682
|
...typeof latestVersion === "string" ? { latestVersion } : {},
|
|
1675
1683
|
...typeof lastAttemptAt === "string" ? { lastAttemptAt } : {},
|
|
1676
|
-
...typeof reason === "string" ? { reason } : {}
|
|
1684
|
+
...typeof reason === "string" ? { reason } : {},
|
|
1685
|
+
...recovery ? { recovery } : {}
|
|
1677
1686
|
};
|
|
1678
1687
|
}
|
|
1679
1688
|
function policyFromResponse(value) {
|
|
@@ -1729,8 +1738,9 @@ function policyScheduleAction(previous, next, wasAuthoritative) {
|
|
|
1729
1738
|
return "keep";
|
|
1730
1739
|
}
|
|
1731
1740
|
function acceptPolicyResponse(local, wasAuthoritative, response) {
|
|
1732
|
-
const
|
|
1733
|
-
if (!
|
|
1741
|
+
const parsed = policyFromResponse(response);
|
|
1742
|
+
if (!parsed) return null;
|
|
1743
|
+
const { recovery: _fromService, ...remote } = parsed;
|
|
1734
1744
|
const action = policyScheduleAction(local, remote, wasAuthoritative);
|
|
1735
1745
|
const ownerTransition = action === "cancel" || !local.autoUpdate;
|
|
1736
1746
|
return { policy: ownerTransition ? { ...local, ...remote } : { ...local, autoUpdate: remote.autoUpdate }, action };
|
|
@@ -1755,18 +1765,48 @@ function mayAutomaticallyRestart(input) {
|
|
|
1755
1765
|
var MAX_OBSERVATIONS = 5;
|
|
1756
1766
|
var IDLE_RETRY_MS = 15e3;
|
|
1757
1767
|
var OBSERVATION_TIMEOUT_MS = 3e4;
|
|
1758
|
-
function
|
|
1759
|
-
const
|
|
1768
|
+
async function boundedWork(work, bound = {}) {
|
|
1769
|
+
const abort = new AbortController();
|
|
1770
|
+
let timer;
|
|
1771
|
+
const expired = new Promise((resolve5) => {
|
|
1772
|
+
timer = (bound.timer ?? ((fn, ms) => setTimeout(fn, ms)))(() => {
|
|
1773
|
+
abort.abort();
|
|
1774
|
+
resolve5(null);
|
|
1775
|
+
}, bound.ms ?? OBSERVATION_TIMEOUT_MS);
|
|
1776
|
+
timer.unref?.();
|
|
1777
|
+
});
|
|
1778
|
+
const running = work(abort.signal);
|
|
1779
|
+
running.catch(() => void 0);
|
|
1780
|
+
try {
|
|
1781
|
+
return await Promise.race([running, expired]);
|
|
1782
|
+
} finally {
|
|
1783
|
+
if (timer) (bound.clear ?? ((t) => clearTimeout(t)))(timer);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
function updateVerdict(attempt, facts, attachedNow = []) {
|
|
1760
1787
|
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
|
-
|
|
1762
|
-
if (
|
|
1763
|
-
if (
|
|
1788
|
+
const cycle = attempt.recovery;
|
|
1789
|
+
if (cycle?.verifiedAt) return { kind: "completed" };
|
|
1790
|
+
if (!cycle) {
|
|
1791
|
+
if (attempt.status === "running" || attempt.status === "restarting") return { kind: "unknown", reason: "an update attempt was recorded by an older runner without the record needed to verify it" };
|
|
1792
|
+
return { kind: "current" };
|
|
1793
|
+
}
|
|
1794
|
+
if (!isVersion(cycle.target)) return { kind: "unverified", reason: `the attempted update's target (${cycle.target}) could not be read` };
|
|
1795
|
+
if (olderThan(facts.current, cycle.target)) return { kind: "unverified", reason: `the runner came back on ${facts.current}, not the attempted ${cycle.target}` };
|
|
1764
1796
|
const observed = facts.observations ?? [];
|
|
1765
|
-
const answered = (source) => observed.some((o) => o.source === source && o.success);
|
|
1766
1797
|
if (!observed.length) return { kind: "unverified", reason: `the runner is on ${facts.current}, but no health look was taken` };
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1798
|
+
const look = (source) => {
|
|
1799
|
+
const o = observed.filter((x) => x.source === source).at(-1);
|
|
1800
|
+
return !o ? "missing" : o.malformed ? "malformed" : o.success ? "ok" : "failed";
|
|
1801
|
+
};
|
|
1802
|
+
const sessions = look("sessions");
|
|
1803
|
+
const health = look("health");
|
|
1804
|
+
if (sessions !== "ok" || health !== "ok" || facts.busy.includes("runner-work-observation-unavailable")) {
|
|
1805
|
+
return { kind: "unverified", reason: `the runner is on ${facts.current}, but its local work look was not readable (sessions ${sessions}, health ${health})` };
|
|
1806
|
+
}
|
|
1807
|
+
const back = new Set(attachedNow);
|
|
1808
|
+
const missing = cycle.attachments.filter((id) => !back.has(id));
|
|
1809
|
+
if (missing.length) return { kind: "unverified", reason: `the runner is on ${facts.current}, but only ${cycle.attachments.length - missing.length} of its ${cycle.attachments.length} original agents are attached again` };
|
|
1770
1810
|
return { kind: "verified" };
|
|
1771
1811
|
}
|
|
1772
1812
|
function blockerClassFor(waitingFor) {
|
|
@@ -1865,6 +1905,7 @@ var AutomaticUpdateController = class {
|
|
|
1865
1905
|
*/
|
|
1866
1906
|
offline() {
|
|
1867
1907
|
if (!this.retryable()) return;
|
|
1908
|
+
this.checkAt = this.now();
|
|
1868
1909
|
this.failures++;
|
|
1869
1910
|
this.deps.status("offline", { reason: "The owner policy could not be refreshed; automatic update is deferred." });
|
|
1870
1911
|
this.schedule();
|
|
@@ -1880,23 +1921,8 @@ var AutomaticUpdateController = class {
|
|
|
1880
1921
|
* this resolves null — the race has settled, so the late result cannot overwrite a newer one or reach planning,
|
|
1881
1922
|
* install or restart. A late rejection is absorbed rather than left unhandled.
|
|
1882
1923
|
*/
|
|
1883
|
-
|
|
1884
|
-
|
|
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
|
-
}
|
|
1924
|
+
bounded(work) {
|
|
1925
|
+
return boundedWork(work, { timer: this.boundTimer, clear: this.clearTimer });
|
|
1900
1926
|
}
|
|
1901
1927
|
async decide() {
|
|
1902
1928
|
const facts = await this.bounded((signal) => this.deps.facts(signal));
|
|
@@ -1912,14 +1938,21 @@ var AutomaticUpdateController = class {
|
|
|
1912
1938
|
const plan = this.deps.plan(facts);
|
|
1913
1939
|
this.decisionAt = this.now();
|
|
1914
1940
|
this.blockerClass = plan.action === "wait" ? blockerClassFor(plan.waitingFor) : null;
|
|
1915
|
-
|
|
1916
|
-
|
|
1941
|
+
const policy = this.deps.policy().value;
|
|
1942
|
+
const open = policy.recovery && !policy.recovery.verifiedAt ? policy.recovery : void 0;
|
|
1943
|
+
const reached = !!open && isVersion(open.target) && isVersion(facts.current) && !olderThan(facts.current, open.target);
|
|
1944
|
+
if (plan.action === "up-to-date" || reached) {
|
|
1945
|
+
const verdict = updateVerdict(policy, facts, this.deps.attachments?.() ?? []);
|
|
1917
1946
|
if (verdict.kind === "unverified") {
|
|
1918
1947
|
this.failures++;
|
|
1919
1948
|
this.deps.status("failure", { reason: `Not verified: ${verdict.reason}.` });
|
|
1949
|
+
} else if (verdict.kind === "unknown") {
|
|
1950
|
+
this.failures = 0;
|
|
1951
|
+
this.deps.status("enabled", { latestVersion: facts.latest ?? void 0, reason: `Not verified: ${verdict.reason}.` });
|
|
1920
1952
|
} else {
|
|
1921
1953
|
this.failures = 0;
|
|
1922
|
-
|
|
1954
|
+
const resolved = verdict.kind === "verified" && policy.recovery ? { recovery: { ...policy.recovery, verifiedAt: new Date(this.now()).toISOString() } } : {};
|
|
1955
|
+
this.deps.status(verdict.kind === "current" ? "enabled" : "verified-success", { latestVersion: facts.latest ?? void 0, reason: void 0, ...resolved });
|
|
1923
1956
|
}
|
|
1924
1957
|
this.schedule();
|
|
1925
1958
|
return;
|
|
@@ -1934,14 +1967,17 @@ var AutomaticUpdateController = class {
|
|
|
1934
1967
|
this.defer();
|
|
1935
1968
|
return;
|
|
1936
1969
|
}
|
|
1937
|
-
|
|
1938
|
-
const
|
|
1970
|
+
const target = open && isVersion(open.target) ? open.target : plan.latest;
|
|
1971
|
+
const attempt = { ...plan, latest: target };
|
|
1972
|
+
const recovery = open ?? { target, attachments: [...this.deps.attachments?.() ?? []], at: new Date(this.now()).toISOString() };
|
|
1973
|
+
this.deps.status("running", { latestVersion: target, reason: void 0, recovery });
|
|
1974
|
+
const restarted = await this.install(attempt, true);
|
|
1939
1975
|
if (restarted) {
|
|
1940
|
-
this.deps.status("restarting", { latestVersion:
|
|
1976
|
+
this.deps.status("restarting", { latestVersion: target, reason: void 0 });
|
|
1941
1977
|
return;
|
|
1942
1978
|
}
|
|
1943
1979
|
this.blockerClass = "idle_wait";
|
|
1944
|
-
this.defer(
|
|
1980
|
+
this.defer(target);
|
|
1945
1981
|
}
|
|
1946
1982
|
defer(latest) {
|
|
1947
1983
|
if (this.waiting) return;
|
|
@@ -2005,14 +2041,17 @@ var RelayMutationAdmission = class {
|
|
|
2005
2041
|
};
|
|
2006
2042
|
|
|
2007
2043
|
// src/commands/runner/runner-work-observation.ts
|
|
2044
|
+
var count = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
|
|
2045
|
+
function isWorkShape(work) {
|
|
2046
|
+
if (!work || typeof work !== "object" || Array.isArray(work)) return false;
|
|
2047
|
+
const w2 = work;
|
|
2048
|
+
return count(w2.codingTurns) && count(w2.localRuns) && Array.isArray(w2.detail) && w2.detail.every((item) => typeof item === "string");
|
|
2049
|
+
}
|
|
2008
2050
|
function observedRunnerWork(observation) {
|
|
2009
2051
|
const sessions = observation.sessions;
|
|
2010
2052
|
const work = observation.work;
|
|
2011
|
-
if (!Array.isArray(sessions) || !work
|
|
2012
|
-
const
|
|
2013
|
-
const localRuns = typeof work.localRuns === "number" && Number.isInteger(work.localRuns) && work.localRuns >= 0 ? work.localRuns : null;
|
|
2014
|
-
const detail = Array.isArray(work.detail) && work.detail.every((item) => typeof item === "string") ? work.detail : null;
|
|
2015
|
-
if (codingTurns === null || localRuns === null || detail === null) return ["runner-work-observation-unavailable"];
|
|
2053
|
+
if (!Array.isArray(sessions) || !isWorkShape(work)) return ["runner-work-observation-unavailable"];
|
|
2054
|
+
const { codingTurns, localRuns, detail } = work;
|
|
2016
2055
|
const activeCoding = sessions.flatMap((session) => {
|
|
2017
2056
|
if (session?.alive === false) return [];
|
|
2018
2057
|
const id = typeof session?.sessionId === "string" && session.sessionId ? session.sessionId : "unidentified-session";
|
|
@@ -2028,6 +2067,155 @@ function observedRunnerWork(observation) {
|
|
|
2028
2067
|
...Array.from({ length: localRuns }, (_v, i) => localDetail[i] || `local-run-${i + 1}`)
|
|
2029
2068
|
];
|
|
2030
2069
|
}
|
|
2070
|
+
function readWorkLook(sessions, health, sampledAt) {
|
|
2071
|
+
const sessionsOk = sessions.status === "fulfilled" && Array.isArray(sessions.value?.sessions);
|
|
2072
|
+
const healthOk = health.status === "fulfilled" && isWorkShape(health.value?.work);
|
|
2073
|
+
const busy = sessions.status === "fulfilled" && health.status === "fulfilled" ? observedRunnerWork({ sessions: sessions.value?.sessions, work: health.value?.work }) : ["runner-work-observation-unavailable"];
|
|
2074
|
+
return {
|
|
2075
|
+
busy,
|
|
2076
|
+
observations: [
|
|
2077
|
+
{ source: "sessions", success: sessionsOk, sampledAt, ...sessions.status === "fulfilled" && !sessionsOk ? { malformed: true } : {} },
|
|
2078
|
+
{ source: "health", success: healthOk, sampledAt, ...health.status === "fulfilled" && !healthOk ? { malformed: true } : {} }
|
|
2079
|
+
]
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// src/commands/runner/update-bridge.ts
|
|
2084
|
+
var PolicyCell = class {
|
|
2085
|
+
constructor(policy, persist, enabled = true) {
|
|
2086
|
+
this.policy = policy;
|
|
2087
|
+
this.persist = persist;
|
|
2088
|
+
this.enabled = enabled;
|
|
2089
|
+
}
|
|
2090
|
+
policy;
|
|
2091
|
+
persist;
|
|
2092
|
+
enabled;
|
|
2093
|
+
authoritative = false;
|
|
2094
|
+
refreshing = false;
|
|
2095
|
+
fence = new ResponseFence();
|
|
2096
|
+
/** The status writer every update path uses. */
|
|
2097
|
+
record(status, extra = {}) {
|
|
2098
|
+
this.policy = { ...this.policy, ...extra, status, lastAttemptAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2099
|
+
if (this.enabled) this.persist(this.policy);
|
|
2100
|
+
}
|
|
2101
|
+
/** The number a registration/heartbeat takes as it is SENT. */
|
|
2102
|
+
next() {
|
|
2103
|
+
return this.fence.next();
|
|
2104
|
+
}
|
|
2105
|
+
/**
|
|
2106
|
+
* Fold one service answer in. It is VALIDATED before it is ordered (#1020): an answer that carries no readable
|
|
2107
|
+
* policy must not advance the fence, or it would suppress a valid older OFF still in flight. The owner's OFF ends
|
|
2108
|
+
* any recovery cycle.
|
|
2109
|
+
*/
|
|
2110
|
+
accept(response, sentAs) {
|
|
2111
|
+
if (!this.enabled) return null;
|
|
2112
|
+
const accepted = acceptPolicyResponse(this.policy, this.authoritative, response);
|
|
2113
|
+
if (!accepted || !this.fence.accepts(sentAs)) return null;
|
|
2114
|
+
this.policy = accepted.action === "cancel" ? { ...accepted.policy, recovery: void 0 } : accepted.policy;
|
|
2115
|
+
this.authoritative = true;
|
|
2116
|
+
this.persist(this.policy);
|
|
2117
|
+
return accepted.action;
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
function createPolicyChannel(deps) {
|
|
2121
|
+
const registered = /* @__PURE__ */ new Set();
|
|
2122
|
+
const act = (action) => {
|
|
2123
|
+
if (action === "cancel") deps.onAction("cancel");
|
|
2124
|
+
else if (action === "start" && !deps.cell.refreshing) deps.onAction("start");
|
|
2125
|
+
};
|
|
2126
|
+
const accept = (response, sentAs) => act(deps.cell.accept(response, sentAs));
|
|
2127
|
+
const register = async (id, force = deps.force, signal) => {
|
|
2128
|
+
const sent = deps.cell.next();
|
|
2129
|
+
try {
|
|
2130
|
+
const result = await deps.post(`/v1/instances/${apiPathSegment(id)}/runtime`, deps.body(id, force), signal);
|
|
2131
|
+
if (signal?.aborted) return false;
|
|
2132
|
+
accept(result, sent);
|
|
2133
|
+
registered.add(id);
|
|
2134
|
+
return true;
|
|
2135
|
+
} catch (e) {
|
|
2136
|
+
if (signal?.aborted) return false;
|
|
2137
|
+
registered.delete(id);
|
|
2138
|
+
deps.onError(id, e instanceof Error ? e.message : String(e));
|
|
2139
|
+
return false;
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
const refresh = async (signal) => {
|
|
2143
|
+
const id = registered.values().next().value ?? [...deps.held()][0];
|
|
2144
|
+
if (!id) return false;
|
|
2145
|
+
deps.cell.authoritative = false;
|
|
2146
|
+
deps.cell.refreshing = true;
|
|
2147
|
+
try {
|
|
2148
|
+
return await register(id, deps.force, signal) && deps.cell.authoritative;
|
|
2149
|
+
} finally {
|
|
2150
|
+
deps.cell.refreshing = false;
|
|
2151
|
+
}
|
|
2152
|
+
};
|
|
2153
|
+
return { registered, register, refresh, accept };
|
|
2154
|
+
}
|
|
2155
|
+
var BoundExpired = class extends Error {
|
|
2156
|
+
boundExpired = true;
|
|
2157
|
+
};
|
|
2158
|
+
function createInstallBridge(deps) {
|
|
2159
|
+
let inFlight = false;
|
|
2160
|
+
let installedPendingRestart = null;
|
|
2161
|
+
const within = (work) => boundedWork(work, deps.bound);
|
|
2162
|
+
const release = (drained) => {
|
|
2163
|
+
if (drained) deps.admission.resume();
|
|
2164
|
+
inFlight = false;
|
|
2165
|
+
};
|
|
2166
|
+
const expired = (drained, what) => {
|
|
2167
|
+
release(drained);
|
|
2168
|
+
throw new BoundExpired(`${what} did not answer within ${(deps.bound?.ms ?? 3e4) / 1e3}s; nothing restarted, and it will be retried.`);
|
|
2169
|
+
};
|
|
2170
|
+
const run2 = async (plan, automatic = false, lease) => {
|
|
2171
|
+
if (inFlight || lease && !deps.leaseActive(lease)) return false;
|
|
2172
|
+
inFlight = true;
|
|
2173
|
+
if (automatic) {
|
|
2174
|
+
const fresh = await within((signal) => deps.refresh(signal));
|
|
2175
|
+
if (fresh === null) expired(false, "The owner policy refresh before installing");
|
|
2176
|
+
if (!fresh || !deps.cell.policy.autoUpdate) {
|
|
2177
|
+
release(false);
|
|
2178
|
+
return false;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
if (installedPendingRestart !== plan.latest) {
|
|
2182
|
+
deps.log(`Updating ${plan.current} \u2192 ${plan.latest} (runner_update)\u2026`);
|
|
2183
|
+
try {
|
|
2184
|
+
await deps.install(plan.latest);
|
|
2185
|
+
installedPendingRestart = plan.latest;
|
|
2186
|
+
} catch (error) {
|
|
2187
|
+
release(false);
|
|
2188
|
+
throw error;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
await deps.admission.drain();
|
|
2192
|
+
await (deps.settle ?? (() => new Promise((resolve5) => setTimeout(resolve5, 500))))();
|
|
2193
|
+
if (lease && !deps.leaseActive(lease)) {
|
|
2194
|
+
release(true);
|
|
2195
|
+
return false;
|
|
2196
|
+
}
|
|
2197
|
+
if (automatic) {
|
|
2198
|
+
const fresh = await within((signal) => deps.refresh(signal));
|
|
2199
|
+
if (fresh === null) expired(true, "The owner policy refresh after installing");
|
|
2200
|
+
if (!fresh || !deps.cell.policy.autoUpdate) {
|
|
2201
|
+
release(true);
|
|
2202
|
+
return false;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
const final = await within((signal) => deps.facts(signal));
|
|
2206
|
+
if (final === null) expired(true, "The final local work look before restarting");
|
|
2207
|
+
const facts = final;
|
|
2208
|
+
const workObserved = !facts.busy.includes("runner-work-observation-unavailable");
|
|
2209
|
+
if (facts.busy.length || automatic && !mayAutomaticallyRestart({ authoritative: deps.cell.authoritative, enabled: deps.cell.policy.autoUpdate, workObserved, busy: facts.busy })) {
|
|
2210
|
+
release(true);
|
|
2211
|
+
return false;
|
|
2212
|
+
}
|
|
2213
|
+
deps.log(`Installed ${plan.latest} \u2014 restarting; every agent re-attaches on the way back up.`);
|
|
2214
|
+
deps.detachAll();
|
|
2215
|
+
return true;
|
|
2216
|
+
};
|
|
2217
|
+
return { run: run2, inFlight: () => inFlight, installedPendingRestart: () => installedPendingRestart };
|
|
2218
|
+
}
|
|
2031
2219
|
|
|
2032
2220
|
// src/commands/runner/relay.ts
|
|
2033
2221
|
var MEMBERSHIP_SYNC_PATH = "/pags/membership/sync";
|
|
@@ -2039,84 +2227,43 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2039
2227
|
const runnerNode = hostname3();
|
|
2040
2228
|
const machine = loadMachineIdentity(runnerNode);
|
|
2041
2229
|
const autoUpdateKey = machine.id ? autoUpdatePolicyKey(machine.id) : "";
|
|
2042
|
-
|
|
2043
|
-
let autoUpdatePolicyAuthoritative = false;
|
|
2044
|
-
let refreshingAutomaticPolicy = false;
|
|
2230
|
+
const cell = new PolicyCell(autoUpdateKey ? loadAutoUpdatePolicy(autoUpdateKey) ?? { autoUpdate: false } : { autoUpdate: false }, (p) => saveAutoUpdatePolicy(autoUpdateKey, p), !!autoUpdateKey);
|
|
2045
2231
|
let scheduleAutomaticUpdate = () => void 0;
|
|
2046
2232
|
let autoUpdateDiagnostics = () => ({});
|
|
2047
|
-
const
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
};
|
|
2054
|
-
if (autoUpdateKey) saveAutoUpdatePolicy(autoUpdateKey, autoUpdatePolicy);
|
|
2055
|
-
};
|
|
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;
|
|
2062
|
-
autoUpdatePolicyAuthoritative = true;
|
|
2063
|
-
if (autoUpdateKey) saveAutoUpdatePolicy(autoUpdateKey, autoUpdatePolicy);
|
|
2064
|
-
if (accepted.action === "cancel") {
|
|
2065
|
-
scheduleAutomaticUpdate(false, true);
|
|
2066
|
-
return;
|
|
2067
|
-
}
|
|
2068
|
-
if (refreshingAutomaticPolicy) return;
|
|
2069
|
-
if (accepted.action === "start") {
|
|
2070
|
-
scheduleAutomaticUpdate(true);
|
|
2071
|
-
}
|
|
2072
|
-
};
|
|
2233
|
+
const autoUpdateStatusBody = () => ({
|
|
2234
|
+
status: autoUpdateStatusWire(cell.policy.status),
|
|
2235
|
+
latestVersion: cell.policy.latestVersion,
|
|
2236
|
+
error: cell.policy.reason,
|
|
2237
|
+
...autoUpdateDiagnostics()
|
|
2238
|
+
});
|
|
2073
2239
|
const capabilities = await requestRunner("GET", "/capabilities", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] });
|
|
2074
2240
|
const caps = Array.isArray(capabilities.capabilities) ? capabilities.capabilities.filter((item) => typeof item === "string") : [];
|
|
2075
|
-
const registered = /* @__PURE__ */ new Set();
|
|
2076
2241
|
let lastRegisterError = "";
|
|
2077
|
-
const
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
}
|
|
2096
|
-
}, signal);
|
|
2097
|
-
if (signal?.aborted) return false;
|
|
2098
|
-
acceptAutoUpdatePolicy(result, sent);
|
|
2099
|
-
registered.add(id);
|
|
2100
|
-
return true;
|
|
2101
|
-
} catch (e) {
|
|
2102
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
2103
|
-
registered.delete(id);
|
|
2242
|
+
const channel = createPolicyChannel({
|
|
2243
|
+
cell,
|
|
2244
|
+
post: (path, body, signal) => requestPags("POST", path, opts, body, signal),
|
|
2245
|
+
body: (_id, forceClaim) => ({
|
|
2246
|
+
endpointUrl: localUrl,
|
|
2247
|
+
token: runnerToken,
|
|
2248
|
+
placement: "local",
|
|
2249
|
+
capabilities: caps,
|
|
2250
|
+
runnerVersion: CLI_VERSION,
|
|
2251
|
+
runnerNode,
|
|
2252
|
+
machineId: machine.id,
|
|
2253
|
+
machineNames: machine.names,
|
|
2254
|
+
force: forceClaim,
|
|
2255
|
+
autoUpdateStatus: autoUpdateStatusBody()
|
|
2256
|
+
}),
|
|
2257
|
+
force,
|
|
2258
|
+
held: () => attached.keys(),
|
|
2259
|
+
onError: (id, msg) => {
|
|
2104
2260
|
lastRegisterError = msg;
|
|
2105
2261
|
writeError(`register ${id.slice(0, 8)}\u2026 failed: ${msg}`);
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
};
|
|
2109
|
-
const
|
|
2110
|
-
|
|
2111
|
-
if (!id) return false;
|
|
2112
|
-
autoUpdatePolicyAuthoritative = false;
|
|
2113
|
-
refreshingAutomaticPolicy = true;
|
|
2114
|
-
try {
|
|
2115
|
-
return await registerRuntime(id, force, signal) && autoUpdatePolicyAuthoritative;
|
|
2116
|
-
} finally {
|
|
2117
|
-
refreshingAutomaticPolicy = false;
|
|
2118
|
-
}
|
|
2119
|
-
};
|
|
2262
|
+
},
|
|
2263
|
+
onAction: (action) => scheduleAutomaticUpdate(action === "start", action === "cancel")
|
|
2264
|
+
});
|
|
2265
|
+
const registered = channel.registered;
|
|
2266
|
+
const registerRuntime = (id, forceClaim = force) => channel.register(id, forceClaim);
|
|
2120
2267
|
const attached = /* @__PURE__ */ new Map();
|
|
2121
2268
|
const processStartedAt = Date.now();
|
|
2122
2269
|
const starts24h = recordRunnerStart(processStartedAt);
|
|
@@ -2138,13 +2285,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2138
2285
|
}, void 0, signal),
|
|
2139
2286
|
requestRunner("GET", "/health", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] }, void 0, signal)
|
|
2140
2287
|
]);
|
|
2141
|
-
const busy
|
|
2142
|
-
const sampledAt = Date.now();
|
|
2143
|
-
const observations = [
|
|
2144
|
-
// An answer is a success only when it has the shape the guard reads; malformed is a failed look.
|
|
2145
|
-
{ source: "sessions", success: sessionsResult.status === "fulfilled" && Array.isArray(sessionsResult.value.sessions), sampledAt },
|
|
2146
|
-
{ source: "health", success: healthResult.status === "fulfilled" && !!healthResult.value.work && typeof healthResult.value.work === "object", sampledAt }
|
|
2147
|
-
];
|
|
2288
|
+
const { busy, observations } = readWorkLook(sessionsResult, healthResult, Date.now());
|
|
2148
2289
|
return {
|
|
2149
2290
|
observations,
|
|
2150
2291
|
current: CLI_VERSION,
|
|
@@ -2156,94 +2297,47 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2156
2297
|
busy
|
|
2157
2298
|
};
|
|
2158
2299
|
};
|
|
2159
|
-
let updateInstallInFlight = false;
|
|
2160
|
-
let installedPendingRestart = null;
|
|
2161
2300
|
const relayMutationAdmission = new RelayMutationAdmission();
|
|
2162
|
-
const
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
} catch (error) {
|
|
2175
|
-
updateInstallInFlight = false;
|
|
2176
|
-
throw error;
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
await relayMutationAdmission.drain();
|
|
2180
|
-
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
2181
|
-
if (lease && !deferredUpdateLeaseActive(lease)) {
|
|
2182
|
-
relayMutationAdmission.resume();
|
|
2183
|
-
updateInstallInFlight = false;
|
|
2184
|
-
return false;
|
|
2185
|
-
}
|
|
2186
|
-
if (automatic) {
|
|
2187
|
-
if (!await refreshAutomaticPolicy() || !autoUpdatePolicy.autoUpdate) {
|
|
2188
|
-
relayMutationAdmission.resume();
|
|
2189
|
-
updateInstallInFlight = false;
|
|
2190
|
-
return false;
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
const finalFacts = await updateFacts();
|
|
2194
|
-
if (finalFacts.busy.length || automatic && !mayAutomaticallyRestart({ authoritative: autoUpdatePolicyAuthoritative, enabled: autoUpdatePolicy.autoUpdate, workObserved: !finalFacts.busy.includes("runner-work-observation-unavailable"), busy: finalFacts.busy })) {
|
|
2195
|
-
relayMutationAdmission.resume();
|
|
2196
|
-
updateInstallInFlight = false;
|
|
2197
|
-
return false;
|
|
2198
|
-
}
|
|
2199
|
-
writeLine(`Installed ${plan.latest} \u2014 restarting; every agent re-attaches on the way back up.`);
|
|
2200
|
-
for (const id of [...attached.keys()]) detach(id);
|
|
2201
|
-
return true;
|
|
2202
|
-
};
|
|
2301
|
+
const bridge = createInstallBridge({
|
|
2302
|
+
cell,
|
|
2303
|
+
refresh: channel.refresh,
|
|
2304
|
+
facts: updateFacts,
|
|
2305
|
+
install: installVersion,
|
|
2306
|
+
admission: relayMutationAdmission,
|
|
2307
|
+
leaseActive: deferredUpdateLeaseActive,
|
|
2308
|
+
detachAll: () => {
|
|
2309
|
+
for (const id of [...attached.keys()]) detach(id);
|
|
2310
|
+
},
|
|
2311
|
+
log: writeLine
|
|
2312
|
+
});
|
|
2203
2313
|
let updateWaiting = false;
|
|
2204
2314
|
let deferredManualLease = null;
|
|
2205
|
-
const updateWhenIdle = (
|
|
2206
|
-
if (updateWaiting
|
|
2315
|
+
const updateWhenIdle = (lease) => {
|
|
2316
|
+
if (updateWaiting) return false;
|
|
2207
2317
|
updateWaiting = true;
|
|
2208
|
-
|
|
2209
|
-
const until = automatic ? Date.now() + 60 * 6e4 : lease.expiresAt;
|
|
2318
|
+
deferredManualLease = lease;
|
|
2210
2319
|
const tick = () => setTimeout(async () => {
|
|
2211
|
-
if (!
|
|
2320
|
+
if (!deferredManualLease || !deferredUpdateLeaseActive(deferredManualLease)) {
|
|
2212
2321
|
writeLine("runner_update: the deferred operation lease expired; no delayed restart was performed.");
|
|
2213
2322
|
deferredManualLease = null;
|
|
2214
2323
|
updateWaiting = false;
|
|
2215
2324
|
return;
|
|
2216
2325
|
}
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
}
|
|
2222
|
-
const plan = planRunnerUpdate(await updateFacts());
|
|
2223
|
-
if (plan.action === "update") {
|
|
2224
|
-
if (automatic && !autoUpdatePolicy.autoUpdate) {
|
|
2225
|
-
updateWaiting = false;
|
|
2226
|
-
return;
|
|
2227
|
-
}
|
|
2228
|
-
if (automatic) saveAutoUpdateStatus("running", { latestVersion: plan.latest, reason: void 0 });
|
|
2229
|
-
const installed = await installAndRestart(plan, automatic, automatic ? void 0 : deferredManualLease ?? void 0).catch((e) => {
|
|
2230
|
-
if (automatic) saveAutoUpdateStatus("failure", { reason: e instanceof Error ? e.message : String(e) });
|
|
2326
|
+
const facts = await boundedWork((signal) => updateFacts(signal));
|
|
2327
|
+
const plan = facts ? planRunnerUpdate(facts) : null;
|
|
2328
|
+
if (plan?.action === "update") {
|
|
2329
|
+
const installed = await bridge.run(plan, false, deferredManualLease).catch((e) => {
|
|
2231
2330
|
writeError(`runner_update: install failed \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
2232
2331
|
return false;
|
|
2233
2332
|
});
|
|
2234
|
-
if (!installed && Date.now() <
|
|
2235
|
-
|
|
2236
|
-
tick();
|
|
2237
|
-
} else {
|
|
2333
|
+
if (!installed && Date.now() < lease.expiresAt) tick();
|
|
2334
|
+
else {
|
|
2238
2335
|
deferredManualLease = null;
|
|
2239
2336
|
updateWaiting = false;
|
|
2240
2337
|
if (installed) leaveForRestart(plan.restarter);
|
|
2241
2338
|
}
|
|
2242
|
-
} else if (plan.action === "wait" && Date.now() <
|
|
2243
|
-
|
|
2244
|
-
tick();
|
|
2245
|
-
} else {
|
|
2246
|
-
if (automatic && plan.action === "refused") saveAutoUpdateStatus("unsupported", { reason: plan.reason });
|
|
2339
|
+
} else if ((!plan || plan.action === "wait") && Date.now() < lease.expiresAt) tick();
|
|
2340
|
+
else {
|
|
2247
2341
|
deferredManualLease = null;
|
|
2248
2342
|
updateWaiting = false;
|
|
2249
2343
|
}
|
|
@@ -2251,107 +2345,41 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2251
2345
|
tick();
|
|
2252
2346
|
return true;
|
|
2253
2347
|
};
|
|
2254
|
-
let automaticTimer = null;
|
|
2255
|
-
let automaticChecking = false;
|
|
2256
|
-
let automaticFailures = 0;
|
|
2257
|
-
const runAutomaticUpdateCheck = async () => {
|
|
2258
|
-
if (!autoUpdatePolicy.autoUpdate || !autoUpdatePolicyAuthoritative || automaticChecking || updateWaiting) return;
|
|
2259
|
-
automaticChecking = true;
|
|
2260
|
-
try {
|
|
2261
|
-
if (!await refreshAutomaticPolicy() || !autoUpdatePolicy.autoUpdate) {
|
|
2262
|
-
saveAutoUpdateStatus("offline", { reason: "The owner policy could not be refreshed; automatic update is deferred." });
|
|
2263
|
-
scheduleAutomaticUpdate();
|
|
2264
|
-
return;
|
|
2265
|
-
}
|
|
2266
|
-
saveAutoUpdateStatus("checking", { reason: void 0 });
|
|
2267
|
-
const facts = await updateFacts();
|
|
2268
|
-
if (!autoUpdatePolicy.autoUpdate || !autoUpdatePolicyAuthoritative) return;
|
|
2269
|
-
if (!facts.latest) {
|
|
2270
|
-
automaticFailures++;
|
|
2271
|
-
saveAutoUpdateStatus("failure", { reason: "Could not check the trusted npm registry for a newer CLI release." });
|
|
2272
|
-
scheduleAutomaticUpdate();
|
|
2273
|
-
return;
|
|
2274
|
-
}
|
|
2275
|
-
automaticFailures = 0;
|
|
2276
|
-
const plan = planRunnerUpdate(facts);
|
|
2277
|
-
if (plan.action === "up-to-date") {
|
|
2278
|
-
saveAutoUpdateStatus("verified-success", { latestVersion: facts.latest, reason: void 0 });
|
|
2279
|
-
scheduleAutomaticUpdate();
|
|
2280
|
-
return;
|
|
2281
|
-
}
|
|
2282
|
-
if (plan.action === "refused") {
|
|
2283
|
-
saveAutoUpdateStatus("unsupported", { latestVersion: facts.latest, reason: plan.reason });
|
|
2284
|
-
scheduleAutomaticUpdate();
|
|
2285
|
-
return;
|
|
2286
|
-
}
|
|
2287
|
-
if (plan.action === "wait") {
|
|
2288
|
-
saveAutoUpdateStatus("waiting-for-idle", { latestVersion: plan.latest, reason: void 0 });
|
|
2289
|
-
updateWhenIdle(true);
|
|
2290
|
-
return;
|
|
2291
|
-
}
|
|
2292
|
-
if (!autoUpdatePolicy.autoUpdate || !autoUpdatePolicyAuthoritative) return;
|
|
2293
|
-
saveAutoUpdateStatus("running", { latestVersion: plan.latest, reason: void 0 });
|
|
2294
|
-
try {
|
|
2295
|
-
const installed = await installAndRestart(plan, true);
|
|
2296
|
-
if (!installed) {
|
|
2297
|
-
saveAutoUpdateStatus("waiting-for-idle", { latestVersion: plan.latest });
|
|
2298
|
-
updateWhenIdle(true);
|
|
2299
|
-
return;
|
|
2300
|
-
}
|
|
2301
|
-
saveAutoUpdateStatus("restarting", { latestVersion: plan.latest, reason: void 0 });
|
|
2302
|
-
} catch (e) {
|
|
2303
|
-
automaticFailures++;
|
|
2304
|
-
saveAutoUpdateStatus("failure", { latestVersion: plan.latest, reason: e instanceof Error ? e.message : String(e) });
|
|
2305
|
-
scheduleAutomaticUpdate();
|
|
2306
|
-
}
|
|
2307
|
-
} finally {
|
|
2308
|
-
automaticChecking = false;
|
|
2309
|
-
}
|
|
2310
|
-
};
|
|
2311
|
-
scheduleAutomaticUpdate = (initial = false, cancel = false) => {
|
|
2312
|
-
if (cancel || !autoUpdatePolicy.autoUpdate || !autoUpdatePolicyAuthoritative) {
|
|
2313
|
-
if (automaticTimer) clearTimeout(automaticTimer);
|
|
2314
|
-
automaticTimer = null;
|
|
2315
|
-
return;
|
|
2316
|
-
}
|
|
2317
|
-
if (automaticTimer || updateWaiting) return;
|
|
2318
|
-
const delay = initial ? Math.round(5e3 + Math.random() * 25e3) : nextAutoUpdateDelayMs(automaticFailures);
|
|
2319
|
-
automaticTimer = setTimeout(() => {
|
|
2320
|
-
automaticTimer = null;
|
|
2321
|
-
void runAutomaticUpdateCheck();
|
|
2322
|
-
}, delay);
|
|
2323
|
-
automaticTimer.unref();
|
|
2324
|
-
};
|
|
2325
2348
|
const automaticUpdateController = new AutomaticUpdateController({
|
|
2326
|
-
policy: () => ({ value:
|
|
2327
|
-
refreshPolicy:
|
|
2349
|
+
policy: () => ({ value: cell.policy, authoritative: cell.authoritative }),
|
|
2350
|
+
refreshPolicy: channel.refresh,
|
|
2328
2351
|
facts: updateFacts,
|
|
2329
2352
|
plan: planRunnerUpdate,
|
|
2330
|
-
//
|
|
2331
|
-
//
|
|
2353
|
+
// The same bridge an explicit runner_update uses: npm, a fresh authoritative policy, the mutation drain, the
|
|
2354
|
+
// final local-work observation and restart — each wait bounded.
|
|
2332
2355
|
installAndRestart: async (plan, automatic) => {
|
|
2333
|
-
const installed = await
|
|
2356
|
+
const installed = await bridge.run(plan, automatic);
|
|
2334
2357
|
if (installed) setTimeout(() => leaveForRestart(plan.restarter), 0).unref();
|
|
2335
2358
|
return installed;
|
|
2336
2359
|
},
|
|
2337
|
-
status:
|
|
2360
|
+
status: (status, extra) => cell.record(status, extra),
|
|
2361
|
+
// The agents held now: attached AND registered — recorded with an install, required back after it.
|
|
2362
|
+
attachments: () => [...attached.keys()].filter((id) => registered.has(id))
|
|
2338
2363
|
});
|
|
2339
2364
|
autoUpdateDiagnostics = () => automaticUpdateController.diagnostics();
|
|
2340
2365
|
scheduleAutomaticUpdate = (initial = false, cancel = false) => automaticUpdateController.onPolicy(cancel ? "cancel" : initial ? "start" : "keep");
|
|
2341
|
-
if (
|
|
2366
|
+
if (cell.authoritative && cell.policy.autoUpdate) scheduleAutomaticUpdate(true);
|
|
2342
2367
|
const answerUpdate = async (body) => {
|
|
2343
2368
|
const dryRun = body?.dryRun === true;
|
|
2344
|
-
const
|
|
2369
|
+
const facts = await boundedWork((signal) => updateFacts(signal));
|
|
2370
|
+
if (!facts) return { status: 504, result: { error: "The local work and registry look did not answer within 30s; nothing was installed or restarted." } };
|
|
2371
|
+
const plan = planRunnerUpdate(facts);
|
|
2345
2372
|
if (dryRun || plan.action === "up-to-date" || plan.action === "refused") return { status: 200, result: { ...plan, dryRun } };
|
|
2346
2373
|
if (plan.action === "wait") {
|
|
2347
2374
|
const lease = deferredUpdateLease(body);
|
|
2348
2375
|
if (!lease) return { status: 400, result: { error: "A busy runner_update needs a current operation lease; no delayed restart was scheduled." } };
|
|
2349
|
-
if (!updateWhenIdle(
|
|
2376
|
+
if (!updateWhenIdle(lease)) return { status: 409, result: { error: "A runner update is already waiting for active work to finish." } };
|
|
2350
2377
|
return { status: 200, result: { ...plan, operationId: lease.operationId, deferredUntil: lease.expiresAt, detail: "This operation may restart only before its recorded lease expires; no delayed restart survives an expired or terminal operation." } };
|
|
2351
2378
|
}
|
|
2352
2379
|
try {
|
|
2353
|
-
if (!await
|
|
2380
|
+
if (!await bridge.run(plan)) return { status: 409, result: { error: "A runner update is already installing or restarting." } };
|
|
2354
2381
|
} catch (e) {
|
|
2382
|
+
if (e instanceof BoundExpired) return { status: 504, result: { error: e.message } };
|
|
2355
2383
|
return { status: 500, result: { error: `npm could not install ${plan.latest}: ${e instanceof Error ? e.message : String(e)}` } };
|
|
2356
2384
|
}
|
|
2357
2385
|
const supervisor = supervisorNote(plan.restarter);
|
|
@@ -2465,19 +2493,14 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2465
2493
|
});
|
|
2466
2494
|
for (const id of [...attached.keys()]) {
|
|
2467
2495
|
try {
|
|
2468
|
-
const sent =
|
|
2496
|
+
const sent = cell.next();
|
|
2469
2497
|
const response = await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime/heartbeat`, opts, {
|
|
2470
2498
|
runnerNode,
|
|
2471
2499
|
machineId: machine.id,
|
|
2472
2500
|
resources,
|
|
2473
|
-
autoUpdateStatus:
|
|
2474
|
-
status: autoUpdateStatusWire(autoUpdatePolicy.status),
|
|
2475
|
-
latestVersion: autoUpdatePolicy.latestVersion,
|
|
2476
|
-
error: autoUpdatePolicy.reason,
|
|
2477
|
-
...autoUpdateDiagnostics()
|
|
2478
|
-
}
|
|
2501
|
+
autoUpdateStatus: autoUpdateStatusBody()
|
|
2479
2502
|
});
|
|
2480
|
-
|
|
2503
|
+
channel.accept(response, sent);
|
|
2481
2504
|
} catch (e) {
|
|
2482
2505
|
failure = e instanceof Error ? e.message : String(e);
|
|
2483
2506
|
}
|