@melaya/runner 1.0.47 → 1.0.49
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 +21 -0
- package/dist/modelLoader.js +12 -2
- package/dist/pythonEnv.d.ts +1 -0
- package/dist/pythonEnv.js +59 -0
- package/package.json +1 -1
package/dist/connection.js
CHANGED
|
@@ -229,11 +229,24 @@ export async function connect(opts) {
|
|
|
229
229
|
if (k.startsWith("MEL_MODEL_"))
|
|
230
230
|
delete inherited[k];
|
|
231
231
|
}
|
|
232
|
+
// certifi CA-bundle path resolved at venv setup. Set as both
|
|
233
|
+
// SSL_CERT_FILE (urllib / openssl) and REQUESTS_CA_BUNDLE (requests
|
|
234
|
+
// library) so EVERY HTTPS-using tool — gmail_send, slack_post_text,
|
|
235
|
+
// anthropic SDK, openai SDK, web_search, etc. — verifies against
|
|
236
|
+
// certifi's bundle rather than the platform default. Without this,
|
|
237
|
+
// macOS Python crashes with `[SSL: CERTIFICATE_VERIFY_FAILED]` on
|
|
238
|
+
// any cloud call (run eb1b8f67df56491b hit exactly this for
|
|
239
|
+
// gmail_send → api.googleapis.com).
|
|
240
|
+
const certBundle = (await import("./pythonEnv.js")).getCertBundlePath();
|
|
241
|
+
const sslEnv = certBundle
|
|
242
|
+
? { SSL_CERT_FILE: certBundle, REQUESTS_CA_BUNDLE: certBundle }
|
|
243
|
+
: {};
|
|
232
244
|
const env = {
|
|
233
245
|
...inherited,
|
|
234
246
|
PYTHONPATH: sharedDir,
|
|
235
247
|
PYTHONIOENCODING: "utf-8",
|
|
236
248
|
PYTHONUTF8: "1",
|
|
249
|
+
...sslEnv,
|
|
237
250
|
MEL_RUN_ID: payload.runId,
|
|
238
251
|
// Point agentscope hooks at the REAL server (not the browser's Vite URL).
|
|
239
252
|
// Convert wss://api.melaya.org → https://api.melaya.org so pushMessage,
|
|
@@ -542,6 +555,13 @@ export async function connect(opts) {
|
|
|
542
555
|
];
|
|
543
556
|
const { existsSync: _exists2 } = await import("fs");
|
|
544
557
|
const bundledNltkData = candidateNltkPaths.find((p) => _exists2(p)) || "";
|
|
558
|
+
// Resolve certifi bundle for HTTPS verification (same rationale as
|
|
559
|
+
// the runner:run spawn — embedder providers like OpenAI hit
|
|
560
|
+
// api.openai.com over TLS and crash on macOS without certifi).
|
|
561
|
+
const ragCertBundle = (await import("./pythonEnv.js")).getCertBundlePath();
|
|
562
|
+
const ragSslEnv = ragCertBundle
|
|
563
|
+
? { SSL_CERT_FILE: ragCertBundle, REQUESTS_CA_BUNDLE: ragCertBundle }
|
|
564
|
+
: {};
|
|
545
565
|
const proc = spawn(env.pythonPath, [
|
|
546
566
|
"-u", stagedScript,
|
|
547
567
|
"--pipeline-name", payload.pipelineName,
|
|
@@ -552,6 +572,7 @@ export async function connect(opts) {
|
|
|
552
572
|
env: {
|
|
553
573
|
...process.env,
|
|
554
574
|
PYTHONPATH: sharedDir, // so `from agentscope.rag import …` resolves
|
|
575
|
+
...ragSslEnv,
|
|
555
576
|
...(bundledNltkData ? { NLTK_DATA: bundledNltkData } : {}),
|
|
556
577
|
},
|
|
557
578
|
cwd: workDir,
|
package/dist/modelLoader.js
CHANGED
|
@@ -68,7 +68,17 @@ const FAMILY_OVERRIDES = [
|
|
|
68
68
|
{ pattern: /qwen3.*vl.*\b8b\b/i, family: "qwen3", tier: "agentic-capable", thinking: "on", reason: "qwen3-vl-8b: agentic-capable, full thinking" },
|
|
69
69
|
// ── Qwen3 — capable (≥8B) BEFORE the smaller non-vl 4b pattern, so
|
|
70
70
|
// 14b / 32b don't get caught by the 4b regex's stem match. ──
|
|
71
|
-
|
|
71
|
+
// Mainline qwen3 releases ship 8B, 14B, 32B. Custom/finetuned
|
|
72
|
+
// 27B/30B/72B variants exist (e.g. qwen3.6-27b on LM Studio) — we
|
|
73
|
+
// enumerate them so `family="qwen3"` propagates and model.py can
|
|
74
|
+
// apply chat_template_kwargs.enable_thinking=false when needed.
|
|
75
|
+
// Without the explicit 27b entry, qwen3.6-27b falls through to
|
|
76
|
+
// the param-count path with `family=null`, the disable_thinking
|
|
77
|
+
// branch in model.py is skipped, and the model produces
|
|
78
|
+
// thinking-only responses that the OpenAI formatter drops (run
|
|
79
|
+
// 1ecad7cb hit this — Copywriter ended in 11.5KB thinking block,
|
|
80
|
+
// ReportWriter emailed [CONTENT NOT AVAILABLE]).
|
|
81
|
+
{ pattern: /qwen3.*\b(8b|14b|27b|30b|32b|72b)\b/i, family: "qwen3", tier: "agentic-capable", thinking: "off", reason: "qwen3 ≥8b: agentic-capable, thinking disabled (model is large enough to reason without explicit <think> blocks; thinking-only responses get stripped by the OpenAI formatter and lose data to next agent)" },
|
|
72
82
|
// ── Qwen3 — small variants ──
|
|
73
83
|
{ pattern: /qwen3.*\b1\.7b\b/i, family: "qwen3", tier: "text-only", thinking: "off", reason: "qwen3-1.7b: param count below the agentic 7B floor; thinking disabled to prevent <think> loops" },
|
|
74
84
|
{ pattern: /qwen3.*\b0\.5b\b/i, family: "qwen3", tier: "text-only", thinking: "off", reason: "qwen3-0.5b: too small for agentic work" },
|
|
@@ -214,7 +224,7 @@ export function classifyModel(id, lmstudioMeta) {
|
|
|
214
224
|
// Bump whenever FAMILY_OVERRIDES, tier thresholds, or any classifier
|
|
215
225
|
// logic changes in a way that could re-tier an existing cached model.
|
|
216
226
|
// Cached profiles with a mismatched version are ignored and re-classified.
|
|
217
|
-
const PROFILE_SCHEMA_VERSION =
|
|
227
|
+
const PROFILE_SCHEMA_VERSION = 3;
|
|
218
228
|
const LMSTUDIO_BASE = process.env.LMSTUDIO_BASE_URL || "http://127.0.0.1:1234";
|
|
219
229
|
const OLLAMA_BASE = process.env.OLLAMA_BASE_URL || "http://127.0.0.1:11434";
|
|
220
230
|
// JIT-load timeout. 8B-class quantised models on M-series Macs typically
|
package/dist/pythonEnv.d.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* shared-bundle version. Re-bootstraps when the bundle version changes.
|
|
11
11
|
*/
|
|
12
12
|
export declare function venvPython(): string;
|
|
13
|
+
export declare function getCertBundlePath(): string;
|
|
13
14
|
export declare function ensurePythonEnv(systemPython: string, expectedVersion: string, onProgress?: (msg: string) => void): Promise<{
|
|
14
15
|
ok: boolean;
|
|
15
16
|
pythonPath: string;
|
package/dist/pythonEnv.js
CHANGED
|
@@ -119,12 +119,69 @@ const PIP_DEPS = [
|
|
|
119
119
|
// "pandas is not installed". Numpy is already in PIP_DEPS (line 57) so
|
|
120
120
|
// the wheel install is fast.
|
|
121
121
|
"pandas",
|
|
122
|
+
// certifi — provides the CA root bundle that tools like gmail_send /
|
|
123
|
+
// any other HTTPS API rely on for cert verification. On macOS Python
|
|
124
|
+
// 3.x doesn't read the system keychain by default; without certifi +
|
|
125
|
+
// SSL_CERT_FILE pointed at its bundle, every Gmail / Slack / Anthropic
|
|
126
|
+
// / etc. API call raises SSL: CERTIFICATE_VERIFY_FAILED (run
|
|
127
|
+
// eb1b8f67df56491b hit exactly this — gmail_send dispatched but
|
|
128
|
+
// urllib couldn't verify api.googleapis.com). The runner exports
|
|
129
|
+
// SSL_CERT_FILE + REQUESTS_CA_BUNDLE pointing here when spawning
|
|
130
|
+
// pipeline Python — see getCertBundlePath() + spawn env merging in
|
|
131
|
+
// connection.ts.
|
|
132
|
+
"certifi",
|
|
122
133
|
];
|
|
123
134
|
export function venvPython() {
|
|
124
135
|
return platform() === "win32"
|
|
125
136
|
? join(VENV_DIR, "Scripts", "python.exe")
|
|
126
137
|
: join(VENV_DIR, "bin", "python");
|
|
127
138
|
}
|
|
139
|
+
// ── certifi CA-bundle path resolution + cache ────────────────────────────
|
|
140
|
+
// Set as SSL_CERT_FILE + REQUESTS_CA_BUNDLE when spawning pipeline Python
|
|
141
|
+
// so that ANY HTTPS-using tool (gmail_send, slack_post_text, anthropic /
|
|
142
|
+
// openai SDKs, requests, urllib, etc.) verifies certs against certifi's
|
|
143
|
+
// bundle rather than the platform default. On macOS specifically, the
|
|
144
|
+
// system Python doesn't read the keychain — without this, every API call
|
|
145
|
+
// crashes with `[SSL: CERTIFICATE_VERIFY_FAILED]`.
|
|
146
|
+
//
|
|
147
|
+
// We cache the path in ~/.melaya-runner/cert-bundle-path so we don't pay
|
|
148
|
+
// a Python startup roundtrip on every spawn. The cache is invalidated
|
|
149
|
+
// when the venv is rebuilt (the file lives outside VENV_DIR but is
|
|
150
|
+
// re-written every ensurePythonEnv).
|
|
151
|
+
const CERT_PATH_CACHE = join(CACHE_DIR, "cert-bundle-path");
|
|
152
|
+
export function getCertBundlePath() {
|
|
153
|
+
try {
|
|
154
|
+
if (existsSync(CERT_PATH_CACHE)) {
|
|
155
|
+
const cached = readFileSync(CERT_PATH_CACHE, "utf-8").trim();
|
|
156
|
+
if (cached && existsSync(cached))
|
|
157
|
+
return cached;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch { /* fall through to resolution */ }
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
async function _resolveAndCacheCertBundle(onProgress) {
|
|
164
|
+
if (!existsSync(venvPython()))
|
|
165
|
+
return;
|
|
166
|
+
// Resolve certifi.where() — fast subprocess (<300 ms typical).
|
|
167
|
+
let resolved = "";
|
|
168
|
+
await runProc(venvPython(), ["-c", "import certifi, sys; sys.stdout.write(certifi.where())"], (line) => {
|
|
169
|
+
if (line && existsSync(line.trim()))
|
|
170
|
+
resolved = line.trim();
|
|
171
|
+
});
|
|
172
|
+
if (resolved) {
|
|
173
|
+
try {
|
|
174
|
+
writeFileSync(CERT_PATH_CACHE, resolved + "\n", "utf-8");
|
|
175
|
+
onProgress(`cert bundle: ${resolved}`);
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
onProgress(`(warn: failed to cache cert bundle path: ${e?.message})`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
onProgress("(warn: certifi.where() did not resolve a valid path — HTTPS calls may fail with cert verify errors)");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
128
185
|
// The marker is keyed on sharedVersion + a hash of PIP_DEPS so changes
|
|
129
186
|
// to either invalidate the venv. Without the PIP_DEPS half, a runner
|
|
130
187
|
// update that adds a dep (e.g. scrapling[fetchers] in 1.0.32) would
|
|
@@ -226,6 +283,7 @@ export async function ensurePythonEnv(systemPython, expectedVersion, onProgress
|
|
|
226
283
|
// says we're up to date. This is what previously broke for boxes
|
|
227
284
|
// that upgraded the runner without invalidating the marker.
|
|
228
285
|
await ensureNltkData(onProgress);
|
|
286
|
+
await _resolveAndCacheCertBundle(onProgress);
|
|
229
287
|
return { ok: true, pythonPath: venvPython() };
|
|
230
288
|
}
|
|
231
289
|
if (!existsSync(AGENTSCOPE)) {
|
|
@@ -298,6 +356,7 @@ export async function ensurePythonEnv(systemPython, expectedVersion, onProgress
|
|
|
298
356
|
// Centralised in `ensureNltkData()` so the success/failure surface is
|
|
299
357
|
// identical whether the venv is fresh or already valid.
|
|
300
358
|
await ensureNltkData(onProgress);
|
|
359
|
+
await _resolveAndCacheCertBundle(onProgress);
|
|
301
360
|
writeFileSync(VENV_MARK, venvMarkerValue(expectedVersion), "utf-8");
|
|
302
361
|
// sanity: confirm shortuuid + agentscope (via PYTHONPATH) resolve now.
|
|
303
362
|
// Probe must mirror the spawn env so PYTHONPATH=CACHE_DIR points at
|