@melaya/runner 1.0.25 → 1.0.28

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.
@@ -104,6 +104,58 @@ export async function connect(opts) {
104
104
  }
105
105
  // Ensure shared modules are cached
106
106
  await ensureSharedModules(opts.serverUrl, payload.sharedVersion);
107
+ // Ensure the dedicated venv at ~/.melaya-runner/venv/ has
108
+ // agentscope + its transitive deps installed. Without this, the
109
+ // operator's system python3 typically doesn't have shortuuid /
110
+ // anthropic / openai / opentelemetry / etc. pre-installed and
111
+ // every pipeline run crashes during `import agentscope` with a
112
+ // ModuleNotFoundError. The venv is created lazily on first run
113
+ // and reused until sharedVersion changes.
114
+ const { ensurePythonEnv } = await import("./pythonEnv.js");
115
+ const envResult = await ensurePythonEnv(opts.pythonPath, payload.sharedVersion, (msg) => {
116
+ console.log(chalk.gray(` [venv] ${msg}`));
117
+ // Surface bootstrap progress to the FE so the launching
118
+ // animation reflects the real state instead of staying on a
119
+ // generic spinner during the 1-2 min first-time install.
120
+ socket.emit("runner:event", {
121
+ runId: payload.runId,
122
+ payload: {
123
+ event_type: "pipeline_phase",
124
+ run_id: payload.runId,
125
+ project: payload.project,
126
+ step: 1,
127
+ total: 5,
128
+ label: `venv: ${msg}`,
129
+ status: "started",
130
+ },
131
+ });
132
+ });
133
+ if (!envResult.ok) {
134
+ console.log(chalk.red(` ✗ python venv bootstrap failed: ${envResult.reason}`));
135
+ socket.emit("runner:event", {
136
+ runId: payload.runId,
137
+ payload: {
138
+ event_type: "agent_message",
139
+ run_id: payload.runId,
140
+ project: payload.project,
141
+ replyId: `venv-${payload.runId}`,
142
+ replyName: "Runner",
143
+ replyRole: "system",
144
+ msg: {
145
+ id: `venv-${payload.runId}`,
146
+ name: "Runner",
147
+ role: "system",
148
+ content: [{ type: "text", text: `Python env bootstrap failed: ${envResult.reason}` }],
149
+ metadata: {},
150
+ timestamp: new Date().toISOString(),
151
+ },
152
+ },
153
+ });
154
+ socket.emit("runner:runComplete", { runId: payload.runId, status: "failed" });
155
+ return;
156
+ }
157
+ // Replace the system python with the venv python for THIS spawn.
158
+ const pipelinePython = envResult.pythonPath;
107
159
  // Register run in DB + set active project for event relay
108
160
  setActiveProject(payload.project);
109
161
  socket.emit("runner:registerRun", {
@@ -199,8 +251,8 @@ export async function connect(opts) {
199
251
  }
200
252
  : {}),
201
253
  };
202
- // Spawn Python subprocess
203
- const proc = spawn(opts.pythonPath, ["-u", join(runDir, "main.py")], {
254
+ // Spawn Python subprocess (uses the bootstrapped venv python).
255
+ const proc = spawn(pipelinePython, ["-u", join(runDir, "main.py")], {
204
256
  cwd: runDir,
205
257
  env,
206
258
  stdio: ["ignore", "pipe", "pipe"],
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Manage a dedicated Python venv at ~/.melaya-runner/venv/ so pipelines
3
+ * don't depend on whatever's globally installed in the operator's
4
+ * `python3`. Without this, every fresh runner install hits import errors
5
+ * (`ModuleNotFoundError: No module named 'shortuuid'`, etc.) on the
6
+ * first pipeline run because agentscope and its 23 transitive deps are
7
+ * not present in the operator's system Python.
8
+ *
9
+ * Idempotent: re-uses the venv if a marker file matches the current
10
+ * shared-bundle version. Re-bootstraps when the bundle version changes.
11
+ */
12
+ export declare function venvPython(): string;
13
+ export declare function ensurePythonEnv(systemPython: string, expectedVersion: string, onProgress?: (msg: string) => void): Promise<{
14
+ ok: boolean;
15
+ pythonPath: string;
16
+ reason?: string;
17
+ }>;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Manage a dedicated Python venv at ~/.melaya-runner/venv/ so pipelines
3
+ * don't depend on whatever's globally installed in the operator's
4
+ * `python3`. Without this, every fresh runner install hits import errors
5
+ * (`ModuleNotFoundError: No module named 'shortuuid'`, etc.) on the
6
+ * first pipeline run because agentscope and its 23 transitive deps are
7
+ * not present in the operator's system Python.
8
+ *
9
+ * Idempotent: re-uses the venv if a marker file matches the current
10
+ * shared-bundle version. Re-bootstraps when the bundle version changes.
11
+ */
12
+ import { spawn } from "child_process";
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
14
+ import { join } from "path";
15
+ import { homedir, platform } from "os";
16
+ import chalk from "chalk";
17
+ const CACHE_DIR = join(homedir(), ".melaya-runner");
18
+ const VENV_DIR = join(CACHE_DIR, "venv");
19
+ const VENV_MARK = join(CACHE_DIR, "venv-version.txt");
20
+ const AGENTSCOPE = join(CACHE_DIR, "agentscope");
21
+ // Explicit dependency list. The shared-bundle endpoint ships only
22
+ // `*.py` files — no pyproject.toml — so `pip install -e
23
+ // ~/.melaya-runner/agentscope` does NOT work (pip can't find a project
24
+ // definition). PYTHONPATH=~/.melaya-runner/ already makes `import
25
+ // agentscope` resolve to the cached source; we only need pip to bring
26
+ // in agentscope's transitive deps + the tool-runtime extras.
27
+ //
28
+ // Mirrors melayaAgents/agentscope/pyproject.toml `dependencies = [...]`
29
+ // PLUS the runner-tool extras (aiohttp, requests, python-dotenv).
30
+ // If you bump the agentscope pyproject deps, bump this list too —
31
+ // or extract from pyproject at server-side and ship via the bundle.
32
+ const PIP_DEPS = [
33
+ // ── agentscope core (mirrors pyproject.toml `dependencies`) ──
34
+ "aioitertools",
35
+ "anthropic",
36
+ "dashscope",
37
+ "docstring_parser",
38
+ "filetype",
39
+ "json5",
40
+ "json_repair",
41
+ "mcp>=1.13",
42
+ "numpy",
43
+ "openai",
44
+ "python-datauri",
45
+ "opentelemetry-api>=1.39.0",
46
+ "opentelemetry-sdk>=1.39.0",
47
+ "opentelemetry-exporter-otlp>=1.39.0",
48
+ "opentelemetry-semantic-conventions>=0.60b0",
49
+ "python-socketio",
50
+ "shortuuid",
51
+ "tiktoken",
52
+ "sqlalchemy",
53
+ "python-frontmatter",
54
+ "aiofiles",
55
+ // ── runner tools (shared/tools/*.py) ──
56
+ "aiohttp",
57
+ "requests",
58
+ "python-dotenv",
59
+ ];
60
+ export function venvPython() {
61
+ return platform() === "win32"
62
+ ? join(VENV_DIR, "Scripts", "python.exe")
63
+ : join(VENV_DIR, "bin", "python");
64
+ }
65
+ function venvIsValid(expectedVersion) {
66
+ if (!existsSync(venvPython()))
67
+ return false;
68
+ if (!existsSync(VENV_MARK))
69
+ return false;
70
+ try {
71
+ const cached = readFileSync(VENV_MARK, "utf-8").trim();
72
+ return cached === expectedVersion;
73
+ }
74
+ catch {
75
+ return false;
76
+ }
77
+ }
78
+ function runProc(cmd, args, onLine, envExtra = {}) {
79
+ return new Promise((resolve) => {
80
+ const child = spawn(cmd, args, {
81
+ stdio: ["ignore", "pipe", "pipe"],
82
+ env: { ...process.env, ...envExtra },
83
+ });
84
+ const chew = (b) => {
85
+ for (const ln of b.toString().split("\n")) {
86
+ const t = ln.trimEnd();
87
+ if (t)
88
+ onLine(t);
89
+ }
90
+ };
91
+ child.stdout?.on("data", chew);
92
+ child.stderr?.on("data", chew);
93
+ child.on("exit", (code) => resolve(code ?? 1));
94
+ child.on("error", () => resolve(1));
95
+ });
96
+ }
97
+ export async function ensurePythonEnv(systemPython, expectedVersion, onProgress = (m) => console.log(chalk.gray(` [venv] ${m}`))) {
98
+ if (venvIsValid(expectedVersion)) {
99
+ return { ok: true, pythonPath: venvPython() };
100
+ }
101
+ if (!existsSync(AGENTSCOPE)) {
102
+ return {
103
+ ok: false,
104
+ pythonPath: systemPython,
105
+ reason: `agentscope cache missing at ${AGENTSCOPE} — restart the runner so the shared bundle downloads first.`,
106
+ };
107
+ }
108
+ mkdirSync(CACHE_DIR, { recursive: true });
109
+ // Step 1: create the venv if missing.
110
+ if (!existsSync(venvPython())) {
111
+ onProgress(`creating venv at ${VENV_DIR} (one-time, ~5s)`);
112
+ const code = await runProc(systemPython, ["-m", "venv", VENV_DIR], onProgress);
113
+ if (code !== 0 || !existsSync(venvPython())) {
114
+ return {
115
+ ok: false,
116
+ pythonPath: systemPython,
117
+ reason: `python -m venv failed (exit ${code}). Confirm the venv module is available: ${systemPython} -m venv --help`,
118
+ };
119
+ }
120
+ }
121
+ // Step 2: upgrade pip + wheel so wheel-based deps install cleanly.
122
+ onProgress("upgrading pip / wheel inside the venv");
123
+ await runProc(venvPython(), ["-m", "pip", "install", "--quiet", "--upgrade", "pip", "wheel"], onProgress);
124
+ // Step 3: install agentscope's transitive deps + tool extras directly.
125
+ // We do NOT pip-install agentscope itself — the shared bundle ships
126
+ // only *.py files (no pyproject.toml), so PYTHONPATH-based import is
127
+ // the only path. The cached agentscope source under
128
+ // ~/.melaya-runner/agentscope/ resolves via PYTHONPATH at spawn time.
129
+ onProgress(`installing ${PIP_DEPS.length} python deps (anthropic, openai, opentelemetry, …) — first run can take 1-2 min`);
130
+ const acode = await runProc(venvPython(), ["-m", "pip", "install", "--quiet", "--disable-pip-version-check", ...PIP_DEPS], onProgress);
131
+ if (acode !== 0) {
132
+ return {
133
+ ok: false,
134
+ pythonPath: systemPython,
135
+ reason: `pip install failed (exit ${acode}). Check the lines above for the actual pip error.`,
136
+ };
137
+ }
138
+ writeFileSync(VENV_MARK, expectedVersion, "utf-8");
139
+ // sanity: confirm shortuuid + agentscope (via PYTHONPATH) resolve now.
140
+ // Probe must mirror the spawn env so PYTHONPATH=CACHE_DIR points at
141
+ // the cached agentscope source — without this the probe ImportError's
142
+ // even though the real spawn would work.
143
+ const probe = await runProc(venvPython(), ["-c", "import shortuuid, agentscope, anthropic, openai"], onProgress, { PYTHONPATH: CACHE_DIR });
144
+ if (probe !== 0) {
145
+ return {
146
+ ok: false,
147
+ pythonPath: systemPython,
148
+ reason: "venv created but agentscope import probe failed. Delete ~/.melaya-runner/venv and restart the runner.",
149
+ };
150
+ }
151
+ onProgress(`✓ venv ready (python=${venvPython()})`);
152
+ return { ok: true, pythonPath: venvPython() };
153
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melaya/runner",
3
- "version": "1.0.25",
3
+ "version": "1.0.28",
4
4
  "description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,