@melaya/runner 1.1.35 → 1.1.37
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/connection.js +30 -4
- package/dist/pythonEnv.d.ts +4 -2
- package/dist/pythonEnv.js +42 -4
- package/package.json +1 -1
package/dist/connection.js
CHANGED
|
@@ -136,14 +136,40 @@ export async function connect(opts) {
|
|
|
136
136
|
_assistantPrewarmed = true;
|
|
137
137
|
try {
|
|
138
138
|
const { getLocalSharedVersion } = await import("./sharedVendor.js");
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
let localVersion = getLocalSharedVersion();
|
|
140
|
+
const freshSetup = !localVersion;
|
|
141
|
+
if (!localVersion) {
|
|
142
|
+
// FIRST LAUNCH: no shared bundle on disk yet. Download it now, on
|
|
143
|
+
// connect, so the venv is built UP-FRONT instead of making the user wait
|
|
144
|
+
// 1-2 min mid-chat on their first assistant/pipeline run. Shown to the
|
|
145
|
+
// user (not verbose-gated) — a silent multi-minute pause reads as "stuck"
|
|
146
|
+
// and users leave; a clear one-time-setup line keeps their trust.
|
|
147
|
+
console.log(chalk.hex("#7C6FF0")("\n ⚙ First-time setup: preparing your local AI runtime (one-time)…"));
|
|
148
|
+
console.log(chalk.gray(" downloading runtime modules…"));
|
|
149
|
+
try {
|
|
150
|
+
await ensureSharedModules(opts.serverUrl, "latest", opts.token);
|
|
151
|
+
localVersion = getLocalSharedVersion();
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
console.log(chalk.gray(` setup deferred (${e?.message || e}) — it will finish on your first run`));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!localVersion)
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
142
160
|
const { ensurePythonEnv, getCertBundlePath } = await import("./pythonEnv.js");
|
|
161
|
+
if (freshSetup)
|
|
162
|
+
console.log(chalk.gray(" installing Python dependencies (~1-2 min)…"));
|
|
143
163
|
const envResult = await ensurePythonEnv(opts.pythonPath, localVersion, (m) => { if (opts.verbose)
|
|
144
164
|
console.log(chalk.gray(` [assistant prewarm] ${m}`)); });
|
|
145
|
-
if (
|
|
165
|
+
if (freshSetup) {
|
|
166
|
+
console.log(envResult.ok
|
|
167
|
+
? chalk.green(" ✓ Local AI runtime ready — your first chat will start instantly\n")
|
|
168
|
+
: chalk.yellow(` ⚠ runtime setup did not finish (${envResult.reason || "unknown"}); it will retry on your first run\n`));
|
|
169
|
+
}
|
|
170
|
+
else if (opts.verbose) {
|
|
146
171
|
console.log(chalk.gray(" [assistant prewarm] ready"));
|
|
172
|
+
}
|
|
147
173
|
// ALSO pre-import the heavy Python stack so the first real run/chat
|
|
148
174
|
// doesn't pay the ~6s cold `import shared.runtime.registry`. Spawn it
|
|
149
175
|
// detached + fire-and-forget with the SAME PYTHONPATH/env a real
|
package/dist/pythonEnv.d.ts
CHANGED
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function venvPython(): string;
|
|
13
13
|
export declare function getCertBundlePath(): string;
|
|
14
|
-
|
|
14
|
+
type EnsureResult = {
|
|
15
15
|
ok: boolean;
|
|
16
16
|
pythonPath: string;
|
|
17
17
|
reason?: string;
|
|
18
|
-
}
|
|
18
|
+
};
|
|
19
|
+
export declare function ensurePythonEnv(systemPython: string, expectedVersion: string, onProgress?: (msg: string) => void): Promise<EnsureResult>;
|
|
20
|
+
export {};
|
package/dist/pythonEnv.js
CHANGED
|
@@ -73,7 +73,15 @@ const PIP_DEPS = [
|
|
|
73
73
|
"filetype",
|
|
74
74
|
"json5",
|
|
75
75
|
"json_repair",
|
|
76
|
-
|
|
76
|
+
// PINNED <2: mcp 2.x renamed `streamablehttp_client` →
|
|
77
|
+
// `streamable_http_client` in mcp.client.streamable_http, which the in-house
|
|
78
|
+
// agentscope imports by the OLD name (agentscope/mcp/_http_state*_client.py).
|
|
79
|
+
// Unpinned `mcp>=1.13` let a FRESH venv pull 2.x → `import agentscope` fails
|
|
80
|
+
// with "cannot import name 'streamablehttp_client'", so the assistant host
|
|
81
|
+
// never boots. Old cached venvs kept 1.x and worked, which is why this only
|
|
82
|
+
// bit fresh installs (e.g. the Mac 3.14→3.12 rebuild). Keep on the 1.x line
|
|
83
|
+
// until agentscope is updated to the 2.x API.
|
|
84
|
+
"mcp>=1.13,<2",
|
|
77
85
|
"numpy",
|
|
78
86
|
"openai",
|
|
79
87
|
"python-datauri",
|
|
@@ -381,7 +389,24 @@ async function ensureNltkData(onProgress) {
|
|
|
381
389
|
}
|
|
382
390
|
// One-time-per-process guard for the cache-hit self-heal (NLTK + cert bundle).
|
|
383
391
|
let _selfHealedThisProcess = false;
|
|
384
|
-
|
|
392
|
+
const _ensureInFlight = new Map();
|
|
393
|
+
export function ensurePythonEnv(systemPython, expectedVersion, onProgress = (m) => console.log(chalk.gray(` [venv] ${m}`))) {
|
|
394
|
+
// A valid venv resolves cheaply and must NOT be blocked behind an in-flight
|
|
395
|
+
// build for a different version — run the impl directly (its own early-return
|
|
396
|
+
// handles the cache hit). Only route through the singleflight when a build is
|
|
397
|
+
// actually needed, so two build requests coalesce.
|
|
398
|
+
if (venvIsValid(expectedVersion) && !_ensureInFlight.has(expectedVersion)) {
|
|
399
|
+
return _ensurePythonEnvImpl(systemPython, expectedVersion, onProgress);
|
|
400
|
+
}
|
|
401
|
+
const existing = _ensureInFlight.get(expectedVersion);
|
|
402
|
+
if (existing)
|
|
403
|
+
return existing;
|
|
404
|
+
const p = _ensurePythonEnvImpl(systemPython, expectedVersion, onProgress)
|
|
405
|
+
.finally(() => { _ensureInFlight.delete(expectedVersion); });
|
|
406
|
+
_ensureInFlight.set(expectedVersion, p);
|
|
407
|
+
return p;
|
|
408
|
+
}
|
|
409
|
+
async function _ensurePythonEnvImpl(systemPython, expectedVersion, onProgress = (m) => console.log(chalk.gray(` [venv] ${m}`))) {
|
|
385
410
|
if (venvIsValid(expectedVersion)) {
|
|
386
411
|
// Self-heal: ensure NLTK data + the cert bundle are present even when the
|
|
387
412
|
// venv marker says we're up to date (this is what previously broke for
|
|
@@ -440,14 +465,27 @@ export async function ensurePythonEnv(systemPython, expectedVersion, onProgress
|
|
|
440
465
|
}
|
|
441
466
|
// Step 2: upgrade pip + wheel so wheel-based deps install cleanly.
|
|
442
467
|
onProgress("upgrading pip / wheel inside the venv");
|
|
443
|
-
|
|
468
|
+
// --use-feature=truststore makes pip verify TLS against the OS trust store
|
|
469
|
+
// instead of its bundled roots — required behind TLS-intercepting proxies /
|
|
470
|
+
// antivirus (else "CERTIFICATE_VERIFY_FAILED: unable to get local issuer
|
|
471
|
+
// certificate" and the whole venv build fails → agentscope import breaks).
|
|
472
|
+
// Same fix as uv's UV_SYSTEM_CERTS. Needs pip>=23.2, which every Python we
|
|
473
|
+
// provision (uv-managed 3.12) or accept (3.11/3.12) bundles.
|
|
474
|
+
await runProc(venvPython(), ["-m", "pip", "install", "--use-feature=truststore", "--quiet", "--upgrade", "pip", "wheel"], onProgress);
|
|
444
475
|
// Step 3: install agentscope's transitive deps + tool extras directly.
|
|
445
476
|
// We do NOT pip-install agentscope itself — the shared bundle ships
|
|
446
477
|
// only *.py files (no pyproject.toml), so PYTHONPATH-based import is
|
|
447
478
|
// the only path. The cached agentscope source under
|
|
448
479
|
// ~/.melaya-runner/agentscope/ resolves via PYTHONPATH at spawn time.
|
|
449
480
|
onProgress(`installing ${PIP_DEPS.length} python deps (anthropic, openai, opentelemetry, …) — first run can take 1-2 min`);
|
|
450
|
-
const acode = await runProc(venvPython(),
|
|
481
|
+
const acode = await runProc(venvPython(),
|
|
482
|
+
// --no-cache-dir: skip pip's on-disk HTTP/wheel cache. It silences the
|
|
483
|
+
// "Cache entry deserialization failed, entry ignored" warning flood (stale
|
|
484
|
+
// entries left by other Python versions in the shared user cache) and, more
|
|
485
|
+
// importantly, guarantees a corrupt cached wheel can never install a subtly
|
|
486
|
+
// broken package into a fresh runner venv. Costs a re-download on rebuild,
|
|
487
|
+
// which is rare (only on a deps change or a manual clear).
|
|
488
|
+
["-m", "pip", "install", "--use-feature=truststore", "--no-cache-dir", "--quiet", "--disable-pip-version-check", ...PIP_DEPS], onProgress);
|
|
451
489
|
if (acode !== 0) {
|
|
452
490
|
return {
|
|
453
491
|
ok: false,
|