8ccf2db05c
Memind CI / Test, build, and release guards (push) Failing after 3m18s
Map Page Data failure codes to Chinese remediation hints, trigger one-shot goosed repair for remediable cases, and recover poisoned thinking sessions. Route local DeepSeek through the no-think proxy via host.docker.internal so tool rounds no longer hit reasoning_content 400; document gate and case-study scenarios for event registration repair. Co-authored-by: Cursor <cursoragent@cursor.com>
123 lines
4.0 KiB
JavaScript
123 lines
4.0 KiB
JavaScript
#!/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,
|
|
};
|