#!/usr/bin/env node import os from 'node:os'; import { pathToFileURL } from 'node:url'; import { createAiderExecutorAdapter, createGoosedExecutorAdapter, createOpenHandsExecutorAdapter, } from './executor-adapters.mjs'; import { createExecutorWorkerClient, createExecutorWorkerRuntime, createWorkerAdapterRegistry, } from './executor-worker-runtime.mjs'; function envFlag(value, fallback = false) { const normalized = String(value ?? '').trim().toLowerCase(); if (!normalized) return fallback; return ['1', 'true', 'yes', 'on'].includes(normalized); } function positiveInteger(value, fallback) { const number = Number(value); return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback; } function jsonObject(value, fallback = {}) { try { const parsed = JSON.parse(String(value ?? '')); return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback; } catch { return fallback; } } export function createConfiguredWorker({ env = process.env, fetchImpl = null } = {}) { const registry = createWorkerAdapterRegistry(); const workspaceAliases = jsonObject(env.MEMIND_EXECUTOR_WORKSPACE_ALIASES_JSON); const allowedRoot = String(env.MEMIND_EXECUTOR_WORKSPACE_ROOT ?? '').trim(); const goosedUrl = String(env.MEMIND_EXECUTOR_GOOSED_URL ?? '').trim(); if (goosedUrl) { registry.register(createGoosedExecutorAdapter({ baseUrl: goosedUrl, secret: env.MEMIND_EXECUTOR_GOOSED_TOKEN, ...(fetchImpl ? { fetchImpl } : {}), allowRemote: envFlag(env.MEMIND_EXECUTOR_ALLOW_REMOTE_GOOSED, false), tlsInsecure: envFlag(env.MEMIND_EXECUTOR_GOOSED_TLS_INSECURE, false), provider: env.MEMIND_EXECUTOR_GOOSED_PROVIDER, model: env.MEMIND_EXECUTOR_GOOSED_MODEL, workspaceResolver: async (reference) => workspaceAliases[reference?.id] ?? null, })); } if (env.MEMIND_EXECUTOR_AIDER_COMMAND && allowedRoot) { registry.register(createAiderExecutorAdapter({ command: env.MEMIND_EXECUTOR_AIDER_COMMAND, allowedRoot, workspaceAliases, })); } if (env.MEMIND_EXECUTOR_OPENHANDS_COMMAND && allowedRoot) { registry.register(createOpenHandsExecutorAdapter({ command: env.MEMIND_EXECUTOR_OPENHANDS_COMMAND, allowedRoot, workspaceAliases, })); } if (!registry.ids().length) { throw new Error('Executor worker has no configured adapters'); } const client = createExecutorWorkerClient({ baseUrl: env.MEMIND_ORCHESTRATOR_URL ?? 'http://127.0.0.1:8093', serviceToken: env.MEMIND_ORCHESTRATOR_SERVICE_TOKEN, workerToken: env.MEMIND_ORCHESTRATOR_WORKER_TOKEN, fetchImpl: fetchImpl ?? globalThis.fetch, }); return createExecutorWorkerRuntime({ client, registry, workerId: env.MEMIND_EXECUTOR_WORKER_ID ?? `${os.hostname()}-${process.pid}`, heartbeatMs: positiveInteger(env.MEMIND_EXECUTOR_HEARTBEAT_MS, 10_000), leaseDurationMs: positiveInteger(env.MEMIND_EXECUTOR_LEASE_MS, 30_000), }); } async function main() { const worker = createConfiguredWorker(); const once = process.argv.includes('--once'); const recover = process.argv.includes('--recover-expired'); if (recover) { console.log(JSON.stringify(await worker.recoverExpired(), null, 2)); return; } if (once) { console.log(JSON.stringify(await worker.runOnce(), null, 2)); return; } let stopping = false; const stop = async () => { if (stopping) return; stopping = true; await worker.stop(); }; process.once('SIGINT', () => void stop()); process.once('SIGTERM', () => void stop()); const pollMs = positiveInteger(process.env.MEMIND_EXECUTOR_POLL_MS, 1000); console.log(JSON.stringify({ ok: true, ...worker.status() })); while (!stopping) { await worker.runOnce(); await new Promise((resolve) => setTimeout(resolve, pollMs)); } } const isEntrypoint = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href; if (isEntrypoint) { await main(); } export const executorWorkerEntrypointInternals = { envFlag, jsonObject, positiveInteger, };