@melaya/runner 1.0.25 → 1.0.27

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,112 @@
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
+ // Extra deps the cached `shared/tools/*.py` import that aren't in
22
+ // agentscope's pyproject. Keep this list narrow — full tool-specific
23
+ // SDKs (linkedin_api, twilio, stripe, etc.) install lazily on first
24
+ // use via tool-specific scripts. The minimum to GET A PIPELINE RUNNING
25
+ // is agentscope + aiohttp + requests + redis (used by some tools).
26
+ const EXTRA_DEPS = ["aiohttp", "requests", "python-dotenv"];
27
+ export function venvPython() {
28
+ return platform() === "win32"
29
+ ? join(VENV_DIR, "Scripts", "python.exe")
30
+ : join(VENV_DIR, "bin", "python");
31
+ }
32
+ function venvIsValid(expectedVersion) {
33
+ if (!existsSync(venvPython()))
34
+ return false;
35
+ if (!existsSync(VENV_MARK))
36
+ return false;
37
+ try {
38
+ const cached = readFileSync(VENV_MARK, "utf-8").trim();
39
+ return cached === expectedVersion;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ }
45
+ function runProc(cmd, args, onLine) {
46
+ return new Promise((resolve) => {
47
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
48
+ const chew = (b) => {
49
+ for (const ln of b.toString().split("\n")) {
50
+ const t = ln.trimEnd();
51
+ if (t)
52
+ onLine(t);
53
+ }
54
+ };
55
+ child.stdout?.on("data", chew);
56
+ child.stderr?.on("data", chew);
57
+ child.on("exit", (code) => resolve(code ?? 1));
58
+ child.on("error", () => resolve(1));
59
+ });
60
+ }
61
+ export async function ensurePythonEnv(systemPython, expectedVersion, onProgress = (m) => console.log(chalk.gray(` [venv] ${m}`))) {
62
+ if (venvIsValid(expectedVersion)) {
63
+ return { ok: true, pythonPath: venvPython() };
64
+ }
65
+ if (!existsSync(AGENTSCOPE)) {
66
+ return {
67
+ ok: false,
68
+ pythonPath: systemPython,
69
+ reason: `agentscope cache missing at ${AGENTSCOPE} — restart the runner so the shared bundle downloads first.`,
70
+ };
71
+ }
72
+ mkdirSync(CACHE_DIR, { recursive: true });
73
+ // Step 1: create the venv if missing.
74
+ if (!existsSync(venvPython())) {
75
+ onProgress(`creating venv at ${VENV_DIR} (one-time, ~5s)`);
76
+ const code = await runProc(systemPython, ["-m", "venv", VENV_DIR], onProgress);
77
+ if (code !== 0 || !existsSync(venvPython())) {
78
+ return {
79
+ ok: false,
80
+ pythonPath: systemPython,
81
+ reason: `python -m venv failed (exit ${code}). Confirm the venv module is available: ${systemPython} -m venv --help`,
82
+ };
83
+ }
84
+ }
85
+ // Step 2: upgrade pip + wheel so wheel-based deps install cleanly.
86
+ onProgress("upgrading pip / wheel inside the venv");
87
+ await runProc(venvPython(), ["-m", "pip", "install", "--quiet", "--upgrade", "pip", "wheel"], onProgress);
88
+ // Step 3: install agentscope from the local cache (its pyproject pulls
89
+ // anthropic, openai, shortuuid, opentelemetry-*, etc.).
90
+ onProgress("installing agentscope + 23 transitive deps (anthropic, openai, opentelemetry, …) — first run can take 1-2 min");
91
+ const acode = await runProc(venvPython(), ["-m", "pip", "install", "--quiet", "-e", AGENTSCOPE, ...EXTRA_DEPS], onProgress);
92
+ if (acode !== 0) {
93
+ return {
94
+ ok: false,
95
+ pythonPath: systemPython,
96
+ reason: `pip install agentscope failed (exit ${acode}). Check the lines above for the actual pip error.`,
97
+ };
98
+ }
99
+ writeFileSync(VENV_MARK, expectedVersion, "utf-8");
100
+ // sanity: confirm shortuuid resolves now (it's the historic canary
101
+ // import that fails on a stale env).
102
+ const probe = await runProc(venvPython(), ["-c", "import shortuuid, agentscope, anthropic, openai"], onProgress);
103
+ if (probe !== 0) {
104
+ return {
105
+ ok: false,
106
+ pythonPath: systemPython,
107
+ reason: "venv created but agentscope import probe failed. Delete ~/.melaya-runner/venv and restart the runner.",
108
+ };
109
+ }
110
+ onProgress(`✓ venv ready (python=${venvPython()})`);
111
+ return { ok: true, pythonPath: venvPython() };
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melaya/runner",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "Run Melaya AI pipelines locally with your own LM Studio or Ollama models",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,