fix(node): use node subprocess explicitly (#2391)

* fix(node): use node subprocess explicitly

* add explicit documentation

* fixed build
This commit is contained in:
Waleed
2025-12-15 18:18:26 -08:00
committed by GitHub
parent 57e6a0b621
commit a5b7148375
2 changed files with 68 additions and 43 deletions

View File

@@ -130,6 +130,7 @@ When running with Docker, use `host.docker.internal` if vLLM is on your host mac
**Requirements:**
- [Bun](https://bun.sh/) runtime
- [Node.js](https://nodejs.org/) v20+ (required for sandboxed code execution)
- PostgreSQL 12+ with [pgvector extension](https://github.com/pgvector/pgvector) (required for AI embeddings)
**Note:** Sim uses vector embeddings for AI features like knowledge bases and semantic search, which requires the `pgvector` PostgreSQL extension.

View File

@@ -1,4 +1,4 @@
import { type ChildProcess, spawn } from 'node:child_process'
import { type ChildProcess, execSync } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
@@ -7,6 +7,19 @@ import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('IsolatedVMExecution')
let nodeAvailable: boolean | null = null
function checkNodeAvailable(): boolean {
if (nodeAvailable !== null) return nodeAvailable
try {
execSync('node --version', { stdio: 'ignore' })
nodeAvailable = true
} catch {
nodeAvailable = false
}
return nodeAvailable
}
export interface IsolatedVMExecutionRequest {
code: string
params: Record<string, unknown>
@@ -171,6 +184,16 @@ async function ensureWorker(): Promise<void> {
if (workerReadyPromise) return workerReadyPromise
workerReadyPromise = new Promise<void>((resolve, reject) => {
if (!checkNodeAvailable()) {
reject(
new Error(
'Node.js is required for code execution but was not found. ' +
'Please install Node.js (v20+) from https://nodejs.org'
)
)
return
}
const currentDir = path.dirname(fileURLToPath(import.meta.url))
const workerPath = path.join(currentDir, 'isolated-vm-worker.cjs')
@@ -179,53 +202,54 @@ async function ensureWorker(): Promise<void> {
return
}
worker = spawn(process.execPath, [workerPath], {
stdio: ['ignore', 'pipe', 'inherit', 'ipc'],
serialization: 'json',
})
import('node:child_process').then(({ spawn }) => {
worker = spawn('node', [workerPath], {
stdio: ['ignore', 'pipe', 'inherit', 'ipc'],
serialization: 'json',
})
worker.on('message', handleWorkerMessage)
worker.on('message', handleWorkerMessage)
const startTimeout = setTimeout(() => {
worker?.kill()
worker = null
workerReady = false
workerReadyPromise = null
reject(new Error('Worker failed to start within timeout'))
}, 10000)
const startTimeout = setTimeout(() => {
worker?.kill()
worker = null
workerReady = false
workerReadyPromise = null
reject(new Error('Worker failed to start within timeout'))
}, 10000)
const readyHandler = (message: unknown) => {
if (
typeof message === 'object' &&
message !== null &&
(message as { type?: string }).type === 'ready'
) {
workerReady = true
clearTimeout(startTimeout)
worker?.off('message', readyHandler)
resolve()
const readyHandler = (message: unknown) => {
if (
typeof message === 'object' &&
message !== null &&
(message as { type?: string }).type === 'ready'
) {
workerReady = true
clearTimeout(startTimeout)
worker?.off('message', readyHandler)
resolve()
}
}
}
worker.on('message', readyHandler)
worker.on('message', readyHandler)
worker.on('exit', (code) => {
logger.warn('Isolated-vm worker exited', { code })
if (workerIdleTimeout) {
clearTimeout(workerIdleTimeout)
workerIdleTimeout = null
}
worker = null
workerReady = false
workerReadyPromise = null
for (const [id, pending] of pendingExecutions) {
clearTimeout(pending.timeout)
pending.resolve({
result: null,
stdout: '',
error: { message: 'Worker process exited unexpectedly', name: 'WorkerError' },
})
pendingExecutions.delete(id)
}
worker.on('exit', () => {
if (workerIdleTimeout) {
clearTimeout(workerIdleTimeout)
workerIdleTimeout = null
}
worker = null
workerReady = false
workerReadyPromise = null
for (const [id, pending] of pendingExecutions) {
clearTimeout(pending.timeout)
pending.resolve({
result: null,
stdout: '',
error: { message: 'Worker process exited unexpectedly', name: 'WorkerError' },
})
pendingExecutions.delete(id)
}
})
})
})