@proagentstore/cli 0.4.108 → 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/bootstrap.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  newestPayload,
9
9
  olderThan,
10
10
  payloadAt
11
- } from "./chunk-JOWYSZOL.js";
11
+ } from "./chunk-RGNU4W6B.js";
12
12
 
13
13
  // src/bootstrap.ts
14
14
  import { existsSync } from "fs";
@@ -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-JOWYSZOL.js";
9
+ } from "./chunk-RGNU4W6B.js";
9
10
 
10
11
  // src/index.ts
11
12
  import { createRequire as createRequire3 } from "module";
@@ -688,16 +689,16 @@ function describeCandidate(candidate, nowMs) {
688
689
  const agents = `${candidate.agentCount} agent${candidate.agentCount === 1 ? "" : "s"}`;
689
690
  return `${relativeAge(candidate.lastSeenAt, nowMs)} \xB7 ${agents}`;
690
691
  }
691
- function parseSelection(input, count) {
692
+ function parseSelection(input, count2) {
692
693
  const raw = input.trim().toLowerCase();
693
694
  if (!raw || raw === "n" || raw === "no" || raw === "s" || raw === "skip" || raw === "none") return { kind: "skip" };
694
- if (raw === "a" || raw === "all") return { kind: "pick", indices: Array.from({ length: count }, (_, i) => i) };
695
+ if (raw === "a" || raw === "all") return { kind: "pick", indices: Array.from({ length: count2 }, (_, i) => i) };
695
696
  const parts = raw.split(/[\s,]+/).filter(Boolean);
696
697
  const indices = [];
697
698
  for (const part of parts) {
698
699
  if (!/^\d+$/.test(part)) return { kind: "invalid", message: `"${part}" is not a number.` };
699
700
  const n = Number.parseInt(part, 10);
700
- if (n < 1 || n > count) return { kind: "invalid", message: `${n} is not on the list (1\u2013${count}).` };
701
+ if (n < 1 || n > count2) return { kind: "invalid", message: `${n} is not on the list (1\u2013${count2}).` };
701
702
  if (!indices.includes(n - 1)) indices.push(n - 1);
702
703
  }
703
704
  return indices.length ? { kind: "pick", indices } : { kind: "skip" };
@@ -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) {
@@ -1629,6 +1632,12 @@ function autoUpdateStatusWire(status) {
1629
1632
  failure: "failed"
1630
1633
  }[status] ?? status;
1631
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
+ }
1632
1641
  var statuses = /* @__PURE__ */ new Set([
1633
1642
  "enabled",
1634
1643
  "checking",
@@ -1665,12 +1674,14 @@ function parseAutoUpdatePolicy(value) {
1665
1674
  const latestVersion = raw.latestVersion ?? raw.latest_version;
1666
1675
  const lastAttemptAt = raw.lastAttemptAt ?? raw.last_attempt_at;
1667
1676
  const reason = raw.reason;
1677
+ const recovery = parseRecovery(raw.recovery);
1668
1678
  return {
1669
1679
  autoUpdate,
1670
1680
  ...status ? { status } : {},
1671
1681
  ...typeof latestVersion === "string" ? { latestVersion } : {},
1672
1682
  ...typeof lastAttemptAt === "string" ? { lastAttemptAt } : {},
1673
- ...typeof reason === "string" ? { reason } : {}
1683
+ ...typeof reason === "string" ? { reason } : {},
1684
+ ...recovery ? { recovery } : {}
1674
1685
  };
1675
1686
  }
1676
1687
  function policyFromResponse(value) {
@@ -1726,12 +1737,25 @@ function policyScheduleAction(previous, next, wasAuthoritative) {
1726
1737
  return "keep";
1727
1738
  }
1728
1739
  function acceptPolicyResponse(local, wasAuthoritative, response) {
1729
- const remote = policyFromResponse(response);
1730
- if (!remote) return null;
1740
+ const parsed = policyFromResponse(response);
1741
+ if (!parsed) return null;
1742
+ const { recovery: _fromService, ...remote } = parsed;
1731
1743
  const action = policyScheduleAction(local, remote, wasAuthoritative);
1732
1744
  const ownerTransition = action === "cancel" || !local.autoUpdate;
1733
1745
  return { policy: ownerTransition ? { ...local, ...remote } : { ...local, autoUpdate: remote.autoUpdate }, action };
1734
1746
  }
1747
+ var ResponseFence = class {
1748
+ sent = 0;
1749
+ accepted = 0;
1750
+ next() {
1751
+ return ++this.sent;
1752
+ }
1753
+ accepts(sentAs) {
1754
+ if (sentAs < this.accepted) return false;
1755
+ this.accepted = sentAs;
1756
+ return true;
1757
+ }
1758
+ };
1735
1759
  function mayAutomaticallyRestart(input) {
1736
1760
  return input.authoritative && input.enabled && input.workObserved && input.busy.length === 0;
1737
1761
  }
@@ -1739,6 +1763,50 @@ function mayAutomaticallyRestart(input) {
1739
1763
  // src/commands/runner/auto-update-controller.ts
1740
1764
  var MAX_OBSERVATIONS = 5;
1741
1765
  var IDLE_RETRY_MS = 15e3;
1766
+ var OBSERVATION_TIMEOUT_MS = 3e4;
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 = []) {
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` };
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}` };
1794
+ const observed = facts.observations ?? [];
1795
+ if (!observed.length) return { kind: "unverified", reason: `the runner is on ${facts.current}, but no health look was taken` };
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` };
1808
+ return { kind: "verified" };
1809
+ }
1742
1810
  function blockerClassFor(waitingFor) {
1743
1811
  if (waitingFor.includes("runner-work-observation-unavailable")) return "observation_failure";
1744
1812
  return waitingFor.length ? "active_sessions" : "unknown";
@@ -1748,6 +1816,7 @@ var AutomaticUpdateController = class {
1748
1816
  this.deps = deps;
1749
1817
  this.setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
1750
1818
  this.clearTimer = deps.clearTimer ?? ((timer) => clearTimeout(timer));
1819
+ this.boundTimer = deps.boundTimer ?? ((fn, ms) => setTimeout(fn, ms));
1751
1820
  this.now = deps.now ?? Date.now;
1752
1821
  }
1753
1822
  deps;
@@ -1764,6 +1833,7 @@ var AutomaticUpdateController = class {
1764
1833
  now;
1765
1834
  setTimer;
1766
1835
  clearTimer;
1836
+ boundTimer;
1767
1837
  /** The controller's own record, safe to report: times, a normalized class, and observation outcomes. */
1768
1838
  diagnostics() {
1769
1839
  return { checkAt: this.checkAt, decisionAt: this.decisionAt, nextRetryDueAt: this.nextRetryDueAt, blockerClass: this.blockerClass, observations: [...this.observations] };
@@ -1781,7 +1851,7 @@ var AutomaticUpdateController = class {
1781
1851
  if (this.checking || this.waiting || !this.retryable()) return;
1782
1852
  this.checking = true;
1783
1853
  try {
1784
- if (!await this.deps.refreshPolicy() || !this.enabled()) {
1854
+ if (!await this.bounded((signal) => this.deps.refreshPolicy(signal)) || !this.enabled()) {
1785
1855
  this.offline();
1786
1856
  return;
1787
1857
  }
@@ -1833,6 +1903,7 @@ var AutomaticUpdateController = class {
1833
1903
  */
1834
1904
  offline() {
1835
1905
  if (!this.retryable()) return;
1906
+ this.checkAt = this.now();
1836
1907
  this.failures++;
1837
1908
  this.deps.status("offline", { reason: "The owner policy could not be refreshed; automatic update is deferred." });
1838
1909
  this.schedule();
@@ -1842,17 +1913,41 @@ var AutomaticUpdateController = class {
1842
1913
  this.deps.status("failure", { reason: error instanceof Error ? error.message : String(error) });
1843
1914
  this.schedule();
1844
1915
  }
1916
+ /**
1917
+ * One bounded piece of work (#1020): a policy refresh or a facts look. Past {@link OBSERVATION_TIMEOUT_MS} its
1918
+ * signal is aborted (the relay drops that request's response, so a late answer can never restore authority) and
1919
+ * this resolves null — the race has settled, so the late result cannot overwrite a newer one or reach planning,
1920
+ * install or restart. A late rejection is absorbed rather than left unhandled.
1921
+ */
1922
+ bounded(work) {
1923
+ return boundedWork(work, { timer: this.boundTimer, clear: this.clearTimer });
1924
+ }
1845
1925
  async decide() {
1846
- const facts = await this.deps.facts();
1926
+ const facts = await this.bounded((signal) => this.deps.facts(signal));
1847
1927
  this.checkAt = this.now();
1928
+ if (!facts) {
1929
+ this.failures++;
1930
+ this.deps.status("failure", { reason: `The local work and registry check did not answer within ${OBSERVATION_TIMEOUT_MS / 1e3}s; it will be retried.` });
1931
+ this.schedule();
1932
+ return;
1933
+ }
1848
1934
  if (facts.observations?.length) this.observations = [...this.observations, ...facts.observations].slice(-MAX_OBSERVATIONS);
1849
1935
  if (!this.enabled()) return this.offline();
1850
1936
  const plan = this.deps.plan(facts);
1851
1937
  this.decisionAt = this.now();
1852
1938
  this.blockerClass = plan.action === "wait" ? blockerClassFor(plan.waitingFor) : null;
1853
1939
  if (plan.action === "up-to-date") {
1854
- this.failures = 0;
1855
- this.deps.status("verified-success", { latestVersion: facts.latest ?? void 0, reason: void 0 });
1940
+ const verdict = updateVerdict(this.deps.policy().value, facts, this.deps.attachments?.() ?? []);
1941
+ if (verdict.kind === "unverified") {
1942
+ this.failures++;
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}.` });
1947
+ } else {
1948
+ this.failures = 0;
1949
+ this.deps.status(verdict.kind === "verified" ? "verified-success" : "enabled", { latestVersion: facts.latest ?? void 0, reason: void 0, recovery: void 0 });
1950
+ }
1856
1951
  this.schedule();
1857
1952
  return;
1858
1953
  }
@@ -1866,7 +1961,7 @@ var AutomaticUpdateController = class {
1866
1961
  this.defer();
1867
1962
  return;
1868
1963
  }
1869
- 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() } });
1870
1965
  const restarted = await this.install(plan, true);
1871
1966
  if (restarted) {
1872
1967
  this.deps.status("restarting", { latestVersion: plan.latest, reason: void 0 });
@@ -1885,7 +1980,7 @@ var AutomaticUpdateController = class {
1885
1980
  void (async () => {
1886
1981
  this.waiting = false;
1887
1982
  try {
1888
- if (!await this.deps.refreshPolicy() || !this.enabled()) {
1983
+ if (!await this.bounded((signal) => this.deps.refreshPolicy(signal)) || !this.enabled()) {
1889
1984
  this.offline();
1890
1985
  return;
1891
1986
  }
@@ -1937,14 +2032,17 @@ var RelayMutationAdmission = class {
1937
2032
  };
1938
2033
 
1939
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
+ }
1940
2041
  function observedRunnerWork(observation) {
1941
2042
  const sessions = observation.sessions;
1942
2043
  const work = observation.work;
1943
- if (!Array.isArray(sessions) || !work || typeof work !== "object") return ["runner-work-observation-unavailable"];
1944
- const codingTurns = typeof work.codingTurns === "number" && Number.isInteger(work.codingTurns) && work.codingTurns >= 0 ? work.codingTurns : null;
1945
- const localRuns = typeof work.localRuns === "number" && Number.isInteger(work.localRuns) && work.localRuns >= 0 ? work.localRuns : null;
1946
- const detail = Array.isArray(work.detail) && work.detail.every((item) => typeof item === "string") ? work.detail : null;
1947
- 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;
1948
2046
  const activeCoding = sessions.flatMap((session) => {
1949
2047
  if (session?.alive === false) return [];
1950
2048
  const id = typeof session?.sessionId === "string" && session.sessionId ? session.sessionId : "unidentified-session";
@@ -1960,6 +2058,155 @@ function observedRunnerWork(observation) {
1960
2058
  ...Array.from({ length: localRuns }, (_v, i) => localDetail[i] || `local-run-${i + 1}`)
1961
2059
  ];
1962
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
+ }
1963
2210
 
1964
2211
  // src/commands/runner/relay.ts
1965
2212
  var MEMBERSHIP_SYNC_PATH = "/pags/membership/sync";
@@ -1971,81 +2218,43 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
1971
2218
  const runnerNode = hostname3();
1972
2219
  const machine = loadMachineIdentity(runnerNode);
1973
2220
  const autoUpdateKey = machine.id ? autoUpdatePolicyKey(machine.id) : "";
1974
- let autoUpdatePolicy = autoUpdateKey ? loadAutoUpdatePolicy(autoUpdateKey) ?? { autoUpdate: false } : { autoUpdate: false };
1975
- let autoUpdatePolicyAuthoritative = false;
1976
- let refreshingAutomaticPolicy = false;
2221
+ const cell = new PolicyCell(autoUpdateKey ? loadAutoUpdatePolicy(autoUpdateKey) ?? { autoUpdate: false } : { autoUpdate: false }, (p) => saveAutoUpdatePolicy(autoUpdateKey, p), !!autoUpdateKey);
1977
2222
  let scheduleAutomaticUpdate = () => void 0;
1978
2223
  let autoUpdateDiagnostics = () => ({});
1979
- const saveAutoUpdateStatus = (status, extra = {}) => {
1980
- autoUpdatePolicy = {
1981
- ...autoUpdatePolicy,
1982
- ...extra,
1983
- status,
1984
- lastAttemptAt: (/* @__PURE__ */ new Date()).toISOString()
1985
- };
1986
- if (autoUpdateKey) saveAutoUpdatePolicy(autoUpdateKey, autoUpdatePolicy);
1987
- };
1988
- const acceptAutoUpdatePolicy = (response) => {
1989
- if (!autoUpdateKey) return;
1990
- const accepted = acceptPolicyResponse(autoUpdatePolicy, autoUpdatePolicyAuthoritative, response);
1991
- if (!accepted) return;
1992
- autoUpdatePolicy = accepted.policy;
1993
- autoUpdatePolicyAuthoritative = true;
1994
- if (autoUpdateKey) saveAutoUpdatePolicy(autoUpdateKey, autoUpdatePolicy);
1995
- if (accepted.action === "cancel") {
1996
- scheduleAutomaticUpdate(false, true);
1997
- return;
1998
- }
1999
- if (refreshingAutomaticPolicy) return;
2000
- if (accepted.action === "start") {
2001
- scheduleAutomaticUpdate(true);
2002
- }
2003
- };
2224
+ const autoUpdateStatusBody = () => ({
2225
+ status: autoUpdateStatusWire(cell.policy.status),
2226
+ latestVersion: cell.policy.latestVersion,
2227
+ error: cell.policy.reason,
2228
+ ...autoUpdateDiagnostics()
2229
+ });
2004
2230
  const capabilities = await requestRunner("GET", "/capabilities", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] });
2005
2231
  const caps = Array.isArray(capabilities.capabilities) ? capabilities.capabilities.filter((item) => typeof item === "string") : [];
2006
- const registered = /* @__PURE__ */ new Set();
2007
2232
  let lastRegisterError = "";
2008
- const registerRuntime = async (id, forceClaim = force) => {
2009
- try {
2010
- const result = await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime`, opts, {
2011
- endpointUrl: localUrl,
2012
- token: runnerToken,
2013
- placement: "local",
2014
- capabilities: caps,
2015
- runnerVersion: CLI_VERSION,
2016
- runnerNode,
2017
- machineId: machine.id,
2018
- machineNames: machine.names,
2019
- force: forceClaim,
2020
- autoUpdateStatus: {
2021
- status: autoUpdateStatusWire(autoUpdatePolicy.status),
2022
- latestVersion: autoUpdatePolicy.latestVersion,
2023
- error: autoUpdatePolicy.reason,
2024
- ...autoUpdateDiagnostics()
2025
- }
2026
- });
2027
- acceptAutoUpdatePolicy(result);
2028
- registered.add(id);
2029
- return true;
2030
- } catch (e) {
2031
- const msg = e instanceof Error ? e.message : String(e);
2032
- 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) => {
2033
2251
  lastRegisterError = msg;
2034
2252
  writeError(`register ${id.slice(0, 8)}\u2026 failed: ${msg}`);
2035
- return false;
2036
- }
2037
- };
2038
- const refreshAutomaticPolicy = async () => {
2039
- const id = registered.values().next().value;
2040
- if (!id) return false;
2041
- autoUpdatePolicyAuthoritative = false;
2042
- refreshingAutomaticPolicy = true;
2043
- try {
2044
- return await registerRuntime(id) && autoUpdatePolicyAuthoritative;
2045
- } finally {
2046
- refreshingAutomaticPolicy = false;
2047
- }
2048
- };
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);
2049
2258
  const attached = /* @__PURE__ */ new Map();
2050
2259
  const processStartedAt = Date.now();
2051
2260
  const starts24h = recordRunnerStart(processStartedAt);
@@ -2058,26 +2267,20 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
2058
2267
  writeLine(formatStatusLine({ registration: state, agents, reason: state === "ok" ? void 0 : lastRegisterError }));
2059
2268
  };
2060
2269
  for (const id of instanceIds) await registerRuntime(id);
2061
- const updateFacts = async () => {
2270
+ const updateFacts = async (signal) => {
2062
2271
  const [sessionsResult, healthResult] = await Promise.allSettled([
2063
2272
  requestRunner("GET", "/coding/sessions", {
2064
2273
  url: localUrl,
2065
2274
  token: runnerToken,
2066
2275
  instanceId: instanceIds[0]
2067
- }),
2068
- requestRunner("GET", "/health", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] })
2276
+ }, void 0, signal),
2277
+ requestRunner("GET", "/health", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] }, void 0, signal)
2069
2278
  ]);
2070
- const busy = sessionsResult.status === "fulfilled" && healthResult.status === "fulfilled" ? observedRunnerWork({ sessions: sessionsResult.value.sessions, work: healthResult.value.work }) : ["runner-work-observation-unavailable"];
2071
- const sampledAt = Date.now();
2072
- const observations = [
2073
- // An answer is a success only when it has the shape the guard reads; malformed is a failed look.
2074
- { source: "sessions", success: sessionsResult.status === "fulfilled" && Array.isArray(sessionsResult.value.sessions), sampledAt },
2075
- { source: "health", success: healthResult.status === "fulfilled" && !!healthResult.value.work && typeof healthResult.value.work === "object", sampledAt }
2076
- ];
2279
+ const { busy, observations } = readWorkLook(sessionsResult, healthResult, Date.now());
2077
2280
  return {
2078
2281
  observations,
2079
2282
  current: CLI_VERSION,
2080
- latest: await latestPublishedVersion(),
2283
+ latest: await latestPublishedVersion(void 0, signal),
2081
2284
  fromSource: runsFromSource(),
2082
2285
  restarter: restarterFrom(process.env),
2083
2286
  // A restart ends browser/application work too. Give these durable names to the same
@@ -2085,94 +2288,47 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
2085
2288
  busy
2086
2289
  };
2087
2290
  };
2088
- let updateInstallInFlight = false;
2089
- let installedPendingRestart = null;
2090
2291
  const relayMutationAdmission = new RelayMutationAdmission();
2091
- const installAndRestart = async (plan, automatic = false, lease) => {
2092
- if (updateInstallInFlight || lease && !deferredUpdateLeaseActive(lease)) return false;
2093
- updateInstallInFlight = true;
2094
- if (automatic && (!await refreshAutomaticPolicy() || !autoUpdatePolicy.autoUpdate)) {
2095
- updateInstallInFlight = false;
2096
- return false;
2097
- }
2098
- if (installedPendingRestart !== plan.latest) {
2099
- writeLine(`Updating ${plan.current} \u2192 ${plan.latest} (runner_update)\u2026`);
2100
- try {
2101
- await installVersion(plan.latest);
2102
- installedPendingRestart = plan.latest;
2103
- } catch (error) {
2104
- updateInstallInFlight = false;
2105
- throw error;
2106
- }
2107
- }
2108
- await relayMutationAdmission.drain();
2109
- await new Promise((resolve5) => setTimeout(resolve5, 500));
2110
- if (lease && !deferredUpdateLeaseActive(lease)) {
2111
- relayMutationAdmission.resume();
2112
- updateInstallInFlight = false;
2113
- return false;
2114
- }
2115
- if (automatic) {
2116
- if (!await refreshAutomaticPolicy() || !autoUpdatePolicy.autoUpdate) {
2117
- relayMutationAdmission.resume();
2118
- updateInstallInFlight = false;
2119
- return false;
2120
- }
2121
- }
2122
- const finalFacts = await updateFacts();
2123
- if (finalFacts.busy.length || automatic && !mayAutomaticallyRestart({ authoritative: autoUpdatePolicyAuthoritative, enabled: autoUpdatePolicy.autoUpdate, workObserved: !finalFacts.busy.includes("runner-work-observation-unavailable"), busy: finalFacts.busy })) {
2124
- relayMutationAdmission.resume();
2125
- updateInstallInFlight = false;
2126
- return false;
2127
- }
2128
- writeLine(`Installed ${plan.latest} \u2014 restarting; every agent re-attaches on the way back up.`);
2129
- for (const id of [...attached.keys()]) detach(id);
2130
- return true;
2131
- };
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
+ });
2132
2304
  let updateWaiting = false;
2133
2305
  let deferredManualLease = null;
2134
- const updateWhenIdle = (automatic = false, lease) => {
2135
- if (updateWaiting || !automatic && !lease) return false;
2306
+ const updateWhenIdle = (lease) => {
2307
+ if (updateWaiting) return false;
2136
2308
  updateWaiting = true;
2137
- if (!automatic) deferredManualLease = lease;
2138
- const until = automatic ? Date.now() + 60 * 6e4 : lease.expiresAt;
2309
+ deferredManualLease = lease;
2139
2310
  const tick = () => setTimeout(async () => {
2140
- if (!automatic && (!deferredManualLease || !deferredUpdateLeaseActive(deferredManualLease))) {
2311
+ if (!deferredManualLease || !deferredUpdateLeaseActive(deferredManualLease)) {
2141
2312
  writeLine("runner_update: the deferred operation lease expired; no delayed restart was performed.");
2142
2313
  deferredManualLease = null;
2143
2314
  updateWaiting = false;
2144
2315
  return;
2145
2316
  }
2146
- if (automatic && (!await refreshAutomaticPolicy() || !autoUpdatePolicy.autoUpdate)) {
2147
- saveAutoUpdateStatus("offline", { reason: "The owner policy could not be refreshed; automatic update is deferred." });
2148
- updateWaiting = false;
2149
- return;
2150
- }
2151
- const plan = planRunnerUpdate(await updateFacts());
2152
- if (plan.action === "update") {
2153
- if (automatic && !autoUpdatePolicy.autoUpdate) {
2154
- updateWaiting = false;
2155
- return;
2156
- }
2157
- if (automatic) saveAutoUpdateStatus("running", { latestVersion: plan.latest, reason: void 0 });
2158
- const installed = await installAndRestart(plan, automatic, automatic ? void 0 : deferredManualLease ?? void 0).catch((e) => {
2159
- 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) => {
2160
2321
  writeError(`runner_update: install failed \u2014 ${e instanceof Error ? e.message : String(e)}`);
2161
2322
  return false;
2162
2323
  });
2163
- if (!installed && Date.now() < until) {
2164
- saveAutoUpdateStatus("waiting-for-idle", { latestVersion: plan.latest });
2165
- tick();
2166
- } else {
2324
+ if (!installed && Date.now() < lease.expiresAt) tick();
2325
+ else {
2167
2326
  deferredManualLease = null;
2168
2327
  updateWaiting = false;
2169
2328
  if (installed) leaveForRestart(plan.restarter);
2170
2329
  }
2171
- } else if (plan.action === "wait" && Date.now() < until) {
2172
- if (automatic) saveAutoUpdateStatus("waiting-for-idle", { latestVersion: plan.latest, reason: void 0 });
2173
- tick();
2174
- } else {
2175
- if (automatic && plan.action === "refused") saveAutoUpdateStatus("unsupported", { reason: plan.reason });
2330
+ } else if ((!plan || plan.action === "wait") && Date.now() < lease.expiresAt) tick();
2331
+ else {
2176
2332
  deferredManualLease = null;
2177
2333
  updateWaiting = false;
2178
2334
  }
@@ -2180,107 +2336,41 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
2180
2336
  tick();
2181
2337
  return true;
2182
2338
  };
2183
- let automaticTimer = null;
2184
- let automaticChecking = false;
2185
- let automaticFailures = 0;
2186
- const runAutomaticUpdateCheck = async () => {
2187
- if (!autoUpdatePolicy.autoUpdate || !autoUpdatePolicyAuthoritative || automaticChecking || updateWaiting) return;
2188
- automaticChecking = true;
2189
- try {
2190
- if (!await refreshAutomaticPolicy() || !autoUpdatePolicy.autoUpdate) {
2191
- saveAutoUpdateStatus("offline", { reason: "The owner policy could not be refreshed; automatic update is deferred." });
2192
- scheduleAutomaticUpdate();
2193
- return;
2194
- }
2195
- saveAutoUpdateStatus("checking", { reason: void 0 });
2196
- const facts = await updateFacts();
2197
- if (!autoUpdatePolicy.autoUpdate || !autoUpdatePolicyAuthoritative) return;
2198
- if (!facts.latest) {
2199
- automaticFailures++;
2200
- saveAutoUpdateStatus("failure", { reason: "Could not check the trusted npm registry for a newer CLI release." });
2201
- scheduleAutomaticUpdate();
2202
- return;
2203
- }
2204
- automaticFailures = 0;
2205
- const plan = planRunnerUpdate(facts);
2206
- if (plan.action === "up-to-date") {
2207
- saveAutoUpdateStatus("verified-success", { latestVersion: facts.latest, reason: void 0 });
2208
- scheduleAutomaticUpdate();
2209
- return;
2210
- }
2211
- if (plan.action === "refused") {
2212
- saveAutoUpdateStatus("unsupported", { latestVersion: facts.latest, reason: plan.reason });
2213
- scheduleAutomaticUpdate();
2214
- return;
2215
- }
2216
- if (plan.action === "wait") {
2217
- saveAutoUpdateStatus("waiting-for-idle", { latestVersion: plan.latest, reason: void 0 });
2218
- updateWhenIdle(true);
2219
- return;
2220
- }
2221
- if (!autoUpdatePolicy.autoUpdate || !autoUpdatePolicyAuthoritative) return;
2222
- saveAutoUpdateStatus("running", { latestVersion: plan.latest, reason: void 0 });
2223
- try {
2224
- const installed = await installAndRestart(plan, true);
2225
- if (!installed) {
2226
- saveAutoUpdateStatus("waiting-for-idle", { latestVersion: plan.latest });
2227
- updateWhenIdle(true);
2228
- return;
2229
- }
2230
- saveAutoUpdateStatus("restarting", { latestVersion: plan.latest, reason: void 0 });
2231
- } catch (e) {
2232
- automaticFailures++;
2233
- saveAutoUpdateStatus("failure", { latestVersion: plan.latest, reason: e instanceof Error ? e.message : String(e) });
2234
- scheduleAutomaticUpdate();
2235
- }
2236
- } finally {
2237
- automaticChecking = false;
2238
- }
2239
- };
2240
- scheduleAutomaticUpdate = (initial = false, cancel = false) => {
2241
- if (cancel || !autoUpdatePolicy.autoUpdate || !autoUpdatePolicyAuthoritative) {
2242
- if (automaticTimer) clearTimeout(automaticTimer);
2243
- automaticTimer = null;
2244
- return;
2245
- }
2246
- if (automaticTimer || updateWaiting) return;
2247
- const delay = initial ? Math.round(5e3 + Math.random() * 25e3) : nextAutoUpdateDelayMs(automaticFailures);
2248
- automaticTimer = setTimeout(() => {
2249
- automaticTimer = null;
2250
- void runAutomaticUpdateCheck();
2251
- }, delay);
2252
- automaticTimer.unref();
2253
- };
2254
2339
  const automaticUpdateController = new AutomaticUpdateController({
2255
- policy: () => ({ value: autoUpdatePolicy, authoritative: autoUpdatePolicyAuthoritative }),
2256
- refreshPolicy: refreshAutomaticPolicy,
2340
+ policy: () => ({ value: cell.policy, authoritative: cell.authoritative }),
2341
+ refreshPolicy: channel.refresh,
2257
2342
  facts: updateFacts,
2258
2343
  plan: planRunnerUpdate,
2259
- // This bridge is deliberately the same function used by explicit runner_update. It owns npm,
2260
- // a fresh authoritative policy, the mutation drain, final local-work observation and restart.
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.
2261
2346
  installAndRestart: async (plan, automatic) => {
2262
- const installed = await installAndRestart(plan, automatic);
2347
+ const installed = await bridge.run(plan, automatic);
2263
2348
  if (installed) setTimeout(() => leaveForRestart(plan.restarter), 0).unref();
2264
2349
  return installed;
2265
2350
  },
2266
- status: saveAutoUpdateStatus
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))
2267
2354
  });
2268
2355
  autoUpdateDiagnostics = () => automaticUpdateController.diagnostics();
2269
2356
  scheduleAutomaticUpdate = (initial = false, cancel = false) => automaticUpdateController.onPolicy(cancel ? "cancel" : initial ? "start" : "keep");
2270
- if (autoUpdatePolicyAuthoritative && autoUpdatePolicy.autoUpdate) scheduleAutomaticUpdate(true);
2357
+ if (cell.authoritative && cell.policy.autoUpdate) scheduleAutomaticUpdate(true);
2271
2358
  const answerUpdate = async (body) => {
2272
2359
  const dryRun = body?.dryRun === true;
2273
- const plan = planRunnerUpdate(await updateFacts());
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);
2274
2363
  if (dryRun || plan.action === "up-to-date" || plan.action === "refused") return { status: 200, result: { ...plan, dryRun } };
2275
2364
  if (plan.action === "wait") {
2276
2365
  const lease = deferredUpdateLease(body);
2277
2366
  if (!lease) return { status: 400, result: { error: "A busy runner_update needs a current operation lease; no delayed restart was scheduled." } };
2278
- if (!updateWhenIdle(false, lease)) return { status: 409, result: { error: "A runner update is already waiting for active work to finish." } };
2367
+ if (!updateWhenIdle(lease)) return { status: 409, result: { error: "A runner update is already waiting for active work to finish." } };
2279
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." } };
2280
2369
  }
2281
2370
  try {
2282
- if (!await installAndRestart(plan)) return { status: 409, result: { error: "A runner update is already installing or restarting." } };
2371
+ if (!await bridge.run(plan)) return { status: 409, result: { error: "A runner update is already installing or restarting." } };
2283
2372
  } catch (e) {
2373
+ if (e instanceof BoundExpired) return { status: 504, result: { error: e.message } };
2284
2374
  return { status: 500, result: { error: `npm could not install ${plan.latest}: ${e instanceof Error ? e.message : String(e)}` } };
2285
2375
  }
2286
2376
  const supervisor = supervisorNote(plan.restarter);
@@ -2394,18 +2484,14 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
2394
2484
  });
2395
2485
  for (const id of [...attached.keys()]) {
2396
2486
  try {
2487
+ const sent = cell.next();
2397
2488
  const response = await requestPags("POST", `/v1/instances/${apiPathSegment(id)}/runtime/heartbeat`, opts, {
2398
2489
  runnerNode,
2399
2490
  machineId: machine.id,
2400
2491
  resources,
2401
- autoUpdateStatus: {
2402
- status: autoUpdateStatusWire(autoUpdatePolicy.status),
2403
- latestVersion: autoUpdatePolicy.latestVersion,
2404
- error: autoUpdatePolicy.reason,
2405
- ...autoUpdateDiagnostics()
2406
- }
2492
+ autoUpdateStatus: autoUpdateStatusBody()
2407
2493
  });
2408
- acceptAutoUpdatePolicy(response);
2494
+ channel.accept(response, sent);
2409
2495
  } catch (e) {
2410
2496
  failure = e instanceof Error ? e.message : String(e);
2411
2497
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.108",
3
+ "version": "0.4.110",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",