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