@melaya/runner 1.1.78 → 1.1.79

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/pythonEnv.js CHANGED
@@ -583,12 +583,26 @@ async function _ensurePythonEnvImpl(systemPython, expectedVersion, onProgress =
583
583
  // blocked by a uv edge case.
584
584
  const _uvBin = _resolveUvBin();
585
585
  let acode;
586
+ // Always in the runner log (onProgress is silent outside verbose mode): a deps
587
+ // change reinstalls every runner's venv once, and nobody could tell it happened,
588
+ // how long it took or why it failed (Mac, 2026-10-30).
589
+ const installStart = Date.now();
590
+ const installTail = [];
591
+ const installOut = (line) => {
592
+ installTail.push(line);
593
+ if (installTail.length > 12)
594
+ installTail.shift();
595
+ onProgress(line);
596
+ };
597
+ console.log(` python venv: installing ${PIP_DEPS.length} deps with ${_uvBin ? "uv" : "pip"} (deps changed)`);
586
598
  if (_uvBin) {
587
599
  onProgress(`installing ${PIP_DEPS.length} python deps via uv (fast; cached reinstalls take seconds)`);
588
- acode = await runProc(_uvBin, ["pip", "install", "--python", venvPython(), "--quiet", ...PIP_DEPS], onProgress, { UV_SYSTEM_CERTS: "1" });
600
+ acode = await runProc(_uvBin, ["pip", "install", "--python", venvPython(), "--quiet", ...PIP_DEPS], installOut, { UV_SYSTEM_CERTS: "1" });
589
601
  if (acode !== 0) {
590
602
  onProgress("uv install failed — falling back to pip (--no-cache-dir)");
591
- acode = await runProc(venvPython(), ["-m", "pip", "install", "--use-feature=truststore", "--no-cache-dir", "--quiet", "--disable-pip-version-check", ...PIP_DEPS], onProgress);
603
+ console.warn(` python venv: uv failed (exit ${acode}), falling back to pip:\n ${installTail.join("\n ")}`);
604
+ installTail.length = 0;
605
+ acode = await runProc(venvPython(), ["-m", "pip", "install", "--use-feature=truststore", "--no-cache-dir", "--quiet", "--disable-pip-version-check", ...PIP_DEPS], installOut);
592
606
  }
593
607
  }
594
608
  else {
@@ -599,9 +613,10 @@ async function _ensurePythonEnvImpl(systemPython, expectedVersion, onProgress =
599
613
  // importantly, guarantees a corrupt cached wheel can never install a subtly
600
614
  // broken package into a fresh runner venv. Costs a re-download on rebuild,
601
615
  // which is rare (only on a deps change or a manual clear).
602
- acode = await runProc(venvPython(), ["-m", "pip", "install", "--use-feature=truststore", "--no-cache-dir", "--quiet", "--disable-pip-version-check", ...PIP_DEPS], onProgress);
616
+ acode = await runProc(venvPython(), ["-m", "pip", "install", "--use-feature=truststore", "--no-cache-dir", "--quiet", "--disable-pip-version-check", ...PIP_DEPS], installOut);
603
617
  }
604
618
  if (acode !== 0) {
619
+ console.warn(` python venv: install failed (exit ${acode}) after ${Math.round((Date.now() - installStart) / 1000)} s:\n ${installTail.join("\n ")}`);
605
620
  return {
606
621
  ok: false,
607
622
  pythonPath: systemPython,
@@ -685,5 +700,6 @@ async function _ensurePythonEnvImpl(systemPython, expectedVersion, onProgress =
685
700
  }
686
701
  writeFileSync(VENV_MARK, venvMarkerValue(expectedVersion), "utf-8");
687
702
  onProgress(`✓ venv ready (python=${venvPython()})`);
703
+ console.log(` python venv: ready in ${Math.round((Date.now() - installStart) / 1000)} s (python=${venvPython()})`);
688
704
  return { ok: true, pythonPath: venvPython() };
689
705
  }
@@ -16,3 +16,5 @@
16
16
  export declare function getLocalSharedVersion(): string | null;
17
17
  export declare function getSharedDir(): string;
18
18
  export declare function ensureSharedModules(serverUrl: string, expectedVersion: string, runnerToken: string): Promise<void>;
19
+ /** No data for this long aborts the download (a slow one keeps going). */
20
+ export declare const SHARED_STALL_MS = 30000;
@@ -68,6 +68,48 @@ export function ensureSharedModules(serverUrl, expectedVersion, runnerToken) {
68
68
  _sharedInFlight.set(expectedVersion, p);
69
69
  return p;
70
70
  }
71
+ /** No data for this long aborts the download (a slow one keeps going). */
72
+ export const SHARED_STALL_MS = 30_000;
73
+ /** Hard ceiling for one download, however slowly it trickles. */
74
+ const SHARED_MAX_MS = 5 * 60_000;
75
+ /** GET the bundle (gzip accepted: about 4 MB instead of 24), aborting only on a stall. */
76
+ async function fetchSharedBundle(url, runnerToken) {
77
+ const ac = new AbortController();
78
+ let stall;
79
+ const arm = () => {
80
+ if (stall)
81
+ clearTimeout(stall);
82
+ stall = setTimeout(() => ac.abort(new Error(`no data for ${SHARED_STALL_MS / 1000} s`)), SHARED_STALL_MS);
83
+ };
84
+ const ceiling = setTimeout(() => ac.abort(new Error(`not done after ${SHARED_MAX_MS / 1000} s`)), SHARED_MAX_MS);
85
+ arm();
86
+ try {
87
+ const res = await fetch(url, {
88
+ headers: { Authorization: `Bearer ${runnerToken}`, "Accept-Encoding": "gzip" },
89
+ signal: ac.signal,
90
+ });
91
+ if (!res.ok)
92
+ throw new Error(`Failed to fetch shared modules: HTTP ${res.status}`);
93
+ if (!res.body)
94
+ throw new Error("Failed to fetch shared modules: empty response");
95
+ const reader = res.body.getReader();
96
+ const chunks = [];
97
+ for (;;) {
98
+ const { done, value } = await reader.read();
99
+ if (done)
100
+ break;
101
+ if (value)
102
+ chunks.push(value);
103
+ arm();
104
+ }
105
+ return JSON.parse(Buffer.concat(chunks).toString("utf-8"));
106
+ }
107
+ finally {
108
+ if (stall)
109
+ clearTimeout(stall);
110
+ clearTimeout(ceiling);
111
+ }
112
+ }
71
113
  async function _ensureSharedModulesImpl(serverUrl, expectedVersion, runnerToken) {
72
114
  mkdirSync(CACHE_DIR, { recursive: true });
73
115
  // Check cached version. We require BOTH a version match AND a valid nested
@@ -90,14 +132,26 @@ async function _ensureSharedModulesImpl(serverUrl, expectedVersion, runnerToken)
90
132
  // Fetch from server
91
133
  const httpUrl = serverUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:");
92
134
  const url = `${httpUrl}/api/v1/runner/shared-bundle?v=${expectedVersion}`;
93
- const res = await fetch(url, {
94
- headers: { Authorization: `Bearer ${runnerToken}` },
95
- signal: AbortSignal.timeout(30_000),
96
- });
97
- if (!res.ok) {
98
- throw new Error(`Failed to fetch shared modules: HTTP ${res.status}`);
135
+ // Right after a deploy every runner downloads the new bundle at once: a slow but
136
+ // live download must not fail the run (run 3751301796504315, 2026-10-30, hit the
137
+ // old flat 30 s limit). Abort only when no data arrives for SHARED_STALL_MS, retry
138
+ // once, and if both fail while a previous bundle is still valid on disk, run with
139
+ // it and say so (the next run fetches the new one).
140
+ let body;
141
+ try {
142
+ body = await fetchSharedBundle(url, runnerToken).catch(async (e) => {
143
+ console.warn(` ⚠ shared modules ${expectedVersion}: download failed (${e?.message}), retrying once`);
144
+ return fetchSharedBundle(url, runnerToken);
145
+ });
146
+ }
147
+ catch (e) {
148
+ if (existsSync(VERSION_FILE) && cacheLooksValid()) {
149
+ const cached = readFileSync(VERSION_FILE, "utf-8").trim();
150
+ console.warn(` ⚠ shared modules ${expectedVersion}: download failed again (${e?.message}); running with the cached ${cached}`);
151
+ return;
152
+ }
153
+ throw e;
99
154
  }
100
- const body = await res.json();
101
155
  // Verify integrity
102
156
  const computed = createHash("sha256").update(JSON.stringify(body.files)).digest("hex");
103
157
  if (computed !== body.hash) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melaya/runner",
3
- "version": "1.1.78",
3
+ "version": "1.1.79",
4
4
  "description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Melaya Labs LLC",