Daemon: supervise the worker child, drop WSL, Docker, Eve and CORS

vili-daemon.mjs spawns the Python worker itself (no shell, port 0 from the
ready line, nice 10, one child at a time, respawn after exit, group kill on
SIGTERM) with a closed environment. WSL/Docker, the Eve surfaces and deck
WebSocket, the provider advertisement, /smoke, CORS, JSON sidecars and the
pid file are deleted. POSTs with an Origin or a non-JSON body are refused
403 cross-site. cultcache-ts comes from the vendor/CultLib submodule at
b549d731; ws is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-23 12:14:10 +02:00
commit ef0edc4073
6 changed files with 922 additions and 391 deletions

451
package-lock.json generated
View file

@ -8,11 +8,433 @@
"name": "@gamecult/vili",
"version": "0.1.0",
"dependencies": {
"cultcache-ts": "file:../CultLib/packages/cultcache-ts",
"ws": "^8.18.0"
"cultcache-ts": "file:vendor/CultLib/packages/cultcache-ts"
}
},
"../CultLib/packages/cultcache-ts": {
"name": "@gamecult/cultcache-ts",
"version": "0.14.2",
"extraneous": true,
"license": "MIT",
"dependencies": {
"@msgpack/msgpack": "^3.1.3",
"koffi": "3.3.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/node": "^24.0.0",
"typescript": "^5.9.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@koromix/koffi-android-arm64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-android-arm64/-/koffi-android-arm64-3.3.0.tgz",
"integrity": "sha512-lHXE2mq+uG6CVeX1iepn2ATcAWMZPagr+J0ICYge7wTMDYc5pPRQMq0Rr/16775i6QlUqQqpk8c5Gu66+u3gGA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-android-x64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-android-x64/-/koffi-android-x64-3.3.0.tgz",
"integrity": "sha512-7EeVK/KS9XNbOsUfy0B3uuSjPQZenjJ35y9QSi6R6eK4Fv0oGvREQaBzIJRJwonxp7sJfGObnwnQTnKTVK2DWA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-darwin-arm64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-arm64/-/koffi-darwin-arm64-3.3.0.tgz",
"integrity": "sha512-2R9bihYmYFyRP98wRG2m5WQVKMONf7DoPJI8RkGCOUB2afOXJEVXFepiSKxBZGPml8i1USwAxMUwkzbJ5uMG1A==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-darwin-x64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-x64/-/koffi-darwin-x64-3.3.0.tgz",
"integrity": "sha512-OecYCQfzhUx+XyAtatIHlk0jIz87wKNjtYvwlwgMwgaGQwO/TxFeReNo+s3167Rh1a9c8rUIOjq1jfzAPBxbKA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-freebsd-arm64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-arm64/-/koffi-freebsd-arm64-3.3.0.tgz",
"integrity": "sha512-6ytHzV1DuyxTP5LtFmQ3imBk71j/F/Nb0Hcwhjcn35esnknm5rQbgRl6tNRldrkGnM4GIAJpsagooUBh1xAzYg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-freebsd-ia32": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-ia32/-/koffi-freebsd-ia32-3.3.0.tgz",
"integrity": "sha512-hINo5z1Iz2u3Rx3oHfzcmi5A03gYAoYf4nCJJYaY0bb5IuGdIC7noQTyyI9YxfyTJgoqw3wKFWWYtvsAmEo9Hw==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-freebsd-x64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-x64/-/koffi-freebsd-x64-3.3.0.tgz",
"integrity": "sha512-7XQYAZh828nW6+y3IdyUNdo2VbVHL5ZC8Qb+yqHrlDjTjVXSyGYZJHAyJZ5c7eSTTKBhtIMTX238rIsdexxrog==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-linux-arm": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-linux-arm/-/koffi-linux-arm-3.3.0.tgz",
"integrity": "sha512-NbahRKKKFkHoFN/ZpLY+w4F6eYSqV4KMN40PmjZ1xQjG/XjYb6PIYgE41c7/acAPxOYqujrcaI3B4BAgWYm5/A==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-linux-arm64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-linux-arm64/-/koffi-linux-arm64-3.3.0.tgz",
"integrity": "sha512-Eo/+wGeawGICzJkUlkzi/KerAJ6U1L/Hf7cnJKH7aGgCOY5RRnjUqXs/eL+UDpRcFHzGeUn+Fq+8q2VxYxvAfw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-linux-ia32": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-linux-ia32/-/koffi-linux-ia32-3.3.0.tgz",
"integrity": "sha512-u9tKuj42RFWJU8ZgZqcBhkFCkIZE/SE6oR1ljpukttJUhOVuaf/lmsWxyOxiVu3U8T1gtD658FlLgrbMVIn6aQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-linux-loong64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-linux-loong64/-/koffi-linux-loong64-3.3.0.tgz",
"integrity": "sha512-2hKlNZ6OXkTUp6fcueZcP0sQ6yhpdfANg9/mWxDKzT1o8qIj4qJD7UcZLCcRIC8JXiP8JQwZBtlPuyXIqofDQg==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-linux-ppc64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-linux-ppc64/-/koffi-linux-ppc64-3.3.0.tgz",
"integrity": "sha512-bIVAoHgzyRN7xs529BK7rF3BbZUTJgIr0t/wvBvAgaIpreKsdZ/QWxhedKRbTN7ljlDHkPZFmu5XcCjb/vqUag==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-linux-riscv64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-linux-riscv64/-/koffi-linux-riscv64-3.3.0.tgz",
"integrity": "sha512-2niYAe+TBXbvf7yog/aiHiDUrKQn+RkMSH5x3ZYo65OQyII1CY3Jk2kP3Pe735gq0LzIYJsDuGMW+02ighSadQ==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-linux-x64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-linux-x64/-/koffi-linux-x64-3.3.0.tgz",
"integrity": "sha512-apU8ZMOrOh1eqMP/Pp0sDZ/WY6A4r6JWph26h63VM9nZ+5KCTYuPgojfoLPNvrfI/bn1PY3+bpXKRhZHI3gxbQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-openbsd-arm64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-arm64/-/koffi-openbsd-arm64-3.3.0.tgz",
"integrity": "sha512-iTyHMCSSs+X579sWJv7XVFMp9gOfROI8IUEwp09YxJpGvmiSoipI0J7kytFuFq6dyuwO0Ymy4JdlmKnCM0qkxg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-openbsd-ia32": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-ia32/-/koffi-openbsd-ia32-3.3.0.tgz",
"integrity": "sha512-xCvMV+4VNHjTxgVPEsBzU0k8dlA7aoFO5QjRF9lVU/NzGhUedlYvFgaF6IWsGOoWEaNt9hgP4KK/wxMbm9v8EQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-openbsd-x64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-x64/-/koffi-openbsd-x64-3.3.0.tgz",
"integrity": "sha512-xItIwx1elM8k1pTudEfuV/1U2gQ06+rFfxCVXrZkyWYYGTzmA5To00f8garJFR+E9gbjQJbn8QDFvWMI+i2sMA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-win32-arm64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-win32-arm64/-/koffi-win32-arm64-3.3.0.tgz",
"integrity": "sha512-fD41LgKBx5i1YlJDuds4tRHAaG5vV55tvagzJwKFDR/5BDeaFvH9QFMUMDoNl2LkmVyeug0dIElHr/oxejh3IA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-win32-ia32": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-win32-ia32/-/koffi-win32-ia32-3.3.0.tgz",
"integrity": "sha512-BfDbDVEuQwPZOvWXawc3JT/7i9cN7QHYotgo014jjXb9pNv46Ykhb0RRglkcHg+SdytW/7pxYn18YwcQP6LLvQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@koromix/koffi-win32-x64": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@koromix/koffi-win32-x64/-/koffi-win32-x64-3.3.0.tgz",
"integrity": "sha512-D+ORp73rTmvGVfq01+4uBmgVZqrPqU9N9xCZCfexaCDlVk0WQsaqid1dGsi6eScJuHt4Cp8s1i5/4CsecxFTKQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"funding": {
"url": "https://liberapay.com/Koromix"
}
},
"node_modules/@msgpack/msgpack": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz",
"integrity": "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==",
"license": "ISC",
"engines": {
"node": ">= 18"
}
},
"node_modules/@types/node": {
"version": "24.19.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.2.tgz",
"integrity": "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": ">=7.24.0 <7.24.7"
}
},
"node_modules/cultcache-ts": {
"resolved": "vendor/CultLib/packages/cultcache-ts",
"link": true
},
"node_modules/koffi": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/koffi/-/koffi-3.3.0.tgz",
"integrity": "sha512-UeyQtptuPeCP0BqKD+Q8ftTQhIUBHO7bXyK9DVIaW9hJSg0BrICm4K5N8Qtn7zKk20sb5vpnES9KBqVksM2kpQ==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"url": "https://liberapay.com/Koromix"
},
"optionalDependencies": {
"@koromix/koffi-android-arm64": "3.3.0",
"@koromix/koffi-android-x64": "3.3.0",
"@koromix/koffi-darwin-arm64": "3.3.0",
"@koromix/koffi-darwin-x64": "3.3.0",
"@koromix/koffi-freebsd-arm64": "3.3.0",
"@koromix/koffi-freebsd-ia32": "3.3.0",
"@koromix/koffi-freebsd-x64": "3.3.0",
"@koromix/koffi-linux-arm": "3.3.0",
"@koromix/koffi-linux-arm64": "3.3.0",
"@koromix/koffi-linux-ia32": "3.3.0",
"@koromix/koffi-linux-loong64": "3.3.0",
"@koromix/koffi-linux-ppc64": "3.3.0",
"@koromix/koffi-linux-riscv64": "3.3.0",
"@koromix/koffi-linux-x64": "3.3.0",
"@koromix/koffi-openbsd-arm64": "3.3.0",
"@koromix/koffi-openbsd-ia32": "3.3.0",
"@koromix/koffi-openbsd-x64": "3.3.0",
"@koromix/koffi-win32-arm64": "3.3.0",
"@koromix/koffi-win32-ia32": "3.3.0",
"@koromix/koffi-win32-x64": "3.3.0"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"dev": true,
"license": "MIT"
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"vendor/CultLib/packages/cultcache-ts": {
"name": "@gamecult/cultcache-ts",
"version": "0.14.2",
"license": "MIT",
@ -28,31 +450,6 @@
"engines": {
"node": ">=20"
}
},
"node_modules/cultcache-ts": {
"resolved": "../CultLib/packages/cultcache-ts",
"link": true
},
"node_modules/ws": {
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

View file

@ -7,11 +7,9 @@
"scripts": {
"start": "node scripts/vili-daemon.mjs",
"health": "node scripts/vili-daemon.mjs --health",
"smoke": "node scripts/smoke-vili.mjs",
"provider": "node scripts/vili-daemon.mjs --print-provider-advertisement"
"test": "node --test scripts/test-vili-daemon.mjs"
},
"dependencies": {
"cultcache-ts": "file:../CultLib/packages/cultcache-ts",
"ws": "^8.18.0"
"cultcache-ts": "file:vendor/CultLib/packages/cultcache-ts"
}
}

View file

@ -0,0 +1,57 @@
// Stands in for kimodo-resident-worker.py in the daemon's tests. It binds port 0, prints the ready line the
// daemon waits for, and answers /health and /generate with what the daemon gave it.
import http from "node:http";
import os from "node:os";
import path from "node:path";
import { appendFileSync } from "node:fs";
const argv = process.argv.slice(2);
const flag = (name) => argv[argv.indexOf(name) + 1];
const host = flag("--host");
let requests = 0;
appendFileSync(path.join(process.env.VILI_EMBEDDINGS_DIR, "stub-spawns.log"), `${process.pid}\n`);
const server = http.createServer((request, response) => {
const chunks = [];
request.on("data", (chunk) => chunks.push(chunk));
request.on("end", () => {
const send = (body) => {
response.writeHead(200, { "content-type": "application/json" });
response.end(JSON.stringify(body));
};
if (request.method === "GET" && request.url === "/health") {
send({ ok: true, model: "stub-model", displayModel: "Stub Model", pid: process.pid, requests });
} else if (request.method === "POST" && request.url === "/generate") {
requests += 1;
const payload = JSON.parse(Buffer.concat(chunks).toString("utf8"));
send({
ok: true,
pid: process.pid,
requests,
output: payload.output,
priority: os.getPriority(),
env: {
OMP_NUM_THREADS: process.env.OMP_NUM_THREADS,
MKL_NUM_THREADS: process.env.MKL_NUM_THREADS,
VILI_THREADS: process.env.VILI_THREADS,
HF_HUB_OFFLINE: process.env.HF_HUB_OFFLINE,
TRANSFORMERS_OFFLINE: process.env.TRANSFORMERS_OFFLINE,
HF_HOME: process.env.HF_HOME,
PYTHONPATH: process.env.PYTHONPATH,
VILI_KIMODO_COMMIT: process.env.VILI_KIMODO_COMMIT,
VILI_PYTHON_ENV: process.env.VILI_PYTHON_ENV,
LEAK_CHECK: process.env.LEAK_CHECK,
},
argv,
});
} else {
response.writeHead(404);
response.end();
}
});
});
server.listen(Number(flag("--port")), host, () => {
console.log(JSON.stringify({ event: "kimodo-worker-ready", port: server.address().port }));
});

View file

@ -0,0 +1,218 @@
// node --test scripts/test-vili-daemon.mjs
// Runs the real daemon against scripts/fixtures/stub-worker.mjs as its --python/--worker-entry.
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import http from "node:http";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
const here = path.dirname(fileURLToPath(import.meta.url));
const daemonPath = path.join(here, "vili-daemon.mjs");
const stubPath = path.join(here, "fixtures", "stub-worker.mjs");
const THREADS = 5;
async function until(condition, what) {
const deadline = Date.now() + 15000;
for (;;) {
const value = await condition();
if (value) return value;
assert.ok(Date.now() < deadline, `timed out waiting for ${what}`);
await new Promise((resolve) => setTimeout(resolve, 25));
}
}
async function startDaemon({ configured = true } = {}) {
const stateRoot = mkdtempSync(path.join(os.tmpdir(), "vili-test-"));
const modelsRoot = path.join(stateRoot, "models");
const argv = [
daemonPath, "--port", "0", "--state-root", stateRoot, "--python", process.execPath, "--worker-entry", stubPath,
"--threads", String(THREADS), "--python-env", "test-env",
];
if (configured) argv.push("--models-root", modelsRoot, "--kimodo-commit", "abc1234");
const proc = spawn(process.execPath, argv, { env: { ...process.env, LEAK_CHECK: "leaked" }, stdio: ["ignore", "pipe", "pipe"] });
let output = "";
proc.stdout.on("data", (chunk) => { output += chunk; });
const port = await until(() => /listening on http:\/\/127\.0\.0\.1:(\d+)/.exec(output)?.[1], "the daemon to listen");
return {
stateRoot,
modelsRoot,
proc,
port: Number(port),
spawns: () => {
const log = path.join(stateRoot, "embeddings", "stub-spawns.log");
return existsSync(log) ? readFileSync(log, "utf8").split("\n").filter(Boolean).map(Number) : [];
},
async stop() {
if (proc.exitCode === null && proc.signalCode === null) {
proc.kill("SIGTERM");
await until(() => proc.exitCode !== null || proc.signalCode !== null, "the daemon to exit");
}
rmSync(stateRoot, { recursive: true, force: true });
},
};
}
function request(port, method, urlPath, { body, headers = {} } = {}) {
return new Promise((resolve, reject) => {
const req = http.request({ host: "127.0.0.1", port, method, path: urlPath, headers }, (res) => {
const chunks = [];
res.on("data", (chunk) => chunks.push(chunk));
res.on("end", () => {
const raw = Buffer.concat(chunks).toString("utf8");
let parsed = null;
try {
parsed = JSON.parse(raw);
} catch {
// not JSON
}
resolve({ status: res.statusCode, headers: res.headers, raw, body: parsed });
});
});
req.on("error", reject);
req.end(body);
});
}
const generate = (daemon, payload = { prompt: "a person waves", duration: 2 }, headers = {}) =>
request(daemon.port, "POST", "/motion/generate", {
body: typeof payload === "string" ? payload : JSON.stringify(payload),
headers: { "content-type": "application/json", ...headers },
});
const operatorState = async (daemon) => (await request(daemon.port, "GET", "/operator-state")).body;
test("the worker's port comes from its ready line", async () => {
const daemon = await startDaemon();
try {
const answer = await generate(daemon);
assert.equal(answer.status, 200);
const backend = (await operatorState(daemon)).backend;
assert.equal(backend.state, "ready");
assert.ok(Number.isInteger(backend.port) && backend.port > 0 && backend.port !== 8825);
assert.equal(answer.body.worker.pid, backend.pid);
assert.deepEqual(answer.body.worker.argv.slice(-4), ["--host", "127.0.0.1", "--port", "0"]);
// the request reached the stub on the port the stub printed
assert.equal((await request(backend.port, "GET", "/health")).body.requests, 1);
} finally {
await daemon.stop();
}
});
test("one child, respawned after exit", async () => {
const daemon = await startDaemon();
try {
const [first, second] = await Promise.all([generate(daemon), generate(daemon)]);
assert.equal(first.status, 200);
assert.equal(second.status, 200);
assert.equal(daemon.spawns().length, 1);
const [pid] = daemon.spawns();
process.kill(pid, "SIGKILL");
await until(async () => (await operatorState(daemon)).backend.state === "stopped", "the daemon to see the exit");
const third = await generate(daemon);
assert.equal(third.status, 200);
const spawns = daemon.spawns();
assert.equal(spawns.length, 2);
assert.notEqual(spawns[1], pid);
assert.equal(third.body.worker.pid, spawns[1]);
} finally {
await daemon.stop();
}
});
test("environment and nice", async () => {
const daemon = await startDaemon();
try {
const { env, priority } = (await generate(daemon)).body.worker;
assert.equal(env.OMP_NUM_THREADS, String(THREADS));
assert.equal(env.MKL_NUM_THREADS, String(THREADS));
assert.equal(env.VILI_THREADS, String(THREADS));
assert.equal(env.HF_HUB_OFFLINE, "1");
assert.equal(env.TRANSFORMERS_OFFLINE, "1");
assert.equal(env.HF_HOME, daemon.modelsRoot);
assert.equal(env.VILI_KIMODO_COMMIT, "abc1234");
assert.equal(env.VILI_PYTHON_ENV, "test-env");
assert.ok(env.PYTHONPATH.endsWith(path.join("vendor", "CultLib", "packages", "cultcache-py", "src")));
assert.equal(env.LEAK_CHECK, undefined, "the daemon's own environment does not reach the worker");
assert.equal(priority, 10);
} finally {
await daemon.stop();
}
});
test("SIGTERM leaves no child", async () => {
const daemon = await startDaemon();
try {
assert.equal((await generate(daemon)).status, 200);
const [pid] = daemon.spawns();
process.kill(pid, 0);
daemon.proc.kill("SIGTERM");
await until(() => daemon.proc.exitCode !== null, "the daemon to exit");
assert.throws(() => process.kill(pid, 0), { code: "ESRCH" });
} finally {
await daemon.stop();
}
});
test("cross-site is refused before the worker", async () => {
const daemon = await startDaemon();
try {
assert.equal((await generate(daemon)).status, 200);
const withOrigin = await generate(daemon, undefined, { origin: "https://example.test" });
assert.equal(withOrigin.status, 403);
assert.equal(withOrigin.body.code, "cross-site");
const plain = await generate(daemon, undefined, { "content-type": "text/plain" });
assert.equal(plain.status, 403);
assert.equal(plain.body.code, "cross-site");
const options = await request(daemon.port, "OPTIONS", "/motion/generate", { headers: { origin: "https://example.test" } });
assert.equal(options.status, 404);
const health = await request(daemon.port, "GET", "/health");
for (const reply of [withOrigin, plain, options, health]) {
assert.equal(Object.keys(reply.headers).filter((name) => name.startsWith("access-control-")).length, 0);
}
const backend = (await operatorState(daemon)).backend;
assert.equal((await request(backend.port, "GET", "/health")).body.requests, 1, "only the same-site request reached the worker");
} finally {
await daemon.stop();
}
});
test("unconfigured refuses before spawning", async () => {
const daemon = await startDaemon({ configured: false });
try {
const answer = await generate(daemon);
assert.equal(answer.status, 503);
assert.equal(answer.body.code, "worker-unconfigured");
assert.deepEqual(daemon.spawns(), []);
assert.equal((await operatorState(daemon)).backend.state, "stopped");
} finally {
await daemon.stop();
}
});
test("an error never echoes the request", async () => {
const daemon = await startDaemon();
try {
const malformed = await generate(daemon, "{\"prompt\": \"the launch code is swordfish\", ");
assert.equal(malformed.status, 400);
assert.ok(!malformed.raw.includes("swordfish"));
const missing = await generate(daemon, { texts: ["the launch code is swordfish"] });
assert.equal(missing.status, 400);
assert.ok(!missing.raw.includes("swordfish"));
} finally {
await daemon.stop();
}
});
test("a thread count outside 1 to 16 stops the daemon without echoing it", async () => {
for (const threads of ["0", "17", "99", "8x"]) {
const proc = spawn(process.execPath, [daemonPath, "--threads", threads], { stdio: ["ignore", "pipe", "pipe"] });
let output = "";
proc.stdout.on("data", (chunk) => { output += chunk; });
proc.stderr.on("data", (chunk) => { output += chunk; });
const code = await new Promise((resolve) => proc.on("exit", resolve));
assert.equal(code, 2);
assert.ok(!output.includes(threads), `the refusal repeated ${threads}`);
}
});

View file

@ -1,11 +1,13 @@
#!/usr/bin/env node
import http from "node:http";
import os from "node:os";
import { closeSync, openSync, writeSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import readline from "node:readline";
import { fileURLToPath } from "node:url";
import { createRequire } from "node:module";
import { execFile } from "node:child_process";
import { WebSocketServer } from "ws";
import { spawn } from "node:child_process";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const require = createRequire(import.meta.url);
@ -16,37 +18,9 @@ const {
defineDocumentType,
} = require("cultcache-ts");
const projectRoot = path.resolve(__dirname, "..");
const ravenBaseUrl = process.env.VILI_PUBLIC_BASE_URL || "http://10.77.0.4:8824";
const ravenDeckUrl = process.env.VILI_PUBLIC_DECK_URL || "ws://10.77.0.4:8824/eve/deck";
const providerId = "vili.animation";
const viliStoreFileName = "vili.service.cc";
const kimodoWorkerName = process.env.VILI_KIMODO_WORKER_NAME || "vili-kimodo-worker";
const kimodoWorkerPort = Number(process.env.VILI_KIMODO_WORKER_PORT || 8825);
const kimodoWorkerUrl = `http://127.0.0.1:${kimodoWorkerPort}`;
const dockerHuggingFaceEnv = [
"export HF_TOKEN=\"$(cat /root/.cache/huggingface/token 2>/dev/null || true)\"",
"export HUGGING_FACE_HUB_TOKEN=\"$HF_TOKEN\"",
].join("; ");
const dockerHuggingFaceArgs = [
"-e HF_TOKEN",
"-e HUGGING_FACE_HUB_TOKEN",
"-v /root/.cache/huggingface:/root/.cache/huggingface",
].join(" ");
const objectSchema = { parse: (value) => value };
const providerAdvertisementDefinition = defineDocumentType({
type: "gamecult.eve.provider_advertisement",
schemaName: "gamecult.eve.provider_advertisement",
schemaId: "gamecult.eve.provider_advertisement.v1",
schemaVersion: "gamecult.eve.provider_advertisement.v1",
name: (value) => value?.providerId || "provider",
schema: objectSchema,
members: [
{ slot: 0, memberName: "providerId", typeName: "string", isName: true },
{ slot: 1, memberName: "title", typeName: "string" },
{ slot: 2, memberName: "verseId", typeName: "string" },
{ slot: 3, memberName: "updatedAt", typeName: "string" },
],
});
const operatorStateDefinition = defineDocumentType({
type: "gamecult.vili.operator_state",
schemaName: "gamecult.vili.operator_state",
@ -61,20 +35,6 @@ const operatorStateDefinition = defineDocumentType({
{ slot: 3, memberName: "backend", typeName: "object" },
],
});
const eveSurfaceDefinition = defineDocumentType({
type: "gamecult.eve.surface_state",
schemaName: "gamecult.eve.surface_state",
schemaId: "gamecult.eve.surface_state.v1",
schemaVersion: "gamecult.eve.surface_state.v1",
name: (value) => value?.providerId || "surface",
schema: objectSchema,
members: [
{ slot: 0, memberName: "providerId", typeName: "string", isName: true },
{ slot: 1, memberName: "title", typeName: "string" },
{ slot: 2, memberName: "updatedAt", typeName: "string" },
{ slot: 3, memberName: "surface", typeName: "object" },
],
});
const commandBoundaryDefinition = defineDocumentType({
type: "gamecult.vili.command_boundary",
schemaName: "gamecult.vili.command_boundary",
@ -106,20 +66,26 @@ const transportProfileDefinition = defineDocumentType({
],
});
const viliDocumentRegistry = defineDocumentRegistry(
providerAdvertisementDefinition,
operatorStateDefinition,
eveSurfaceDefinition,
commandBoundaryDefinition,
transportProfileDefinition,
);
const MAX_THREADS = 16;
function parseArgs(argv) {
const env = process.env;
const args = {
host: process.env.VILI_DAEMON_HOST || "127.0.0.1",
port: Number(process.env.VILI_DAEMON_PORT || 8824),
stateRoot: process.env.VILI_STATE_ROOT || path.join(projectRoot, ".vili"),
host: env.VILI_DAEMON_HOST || "127.0.0.1",
port: Number(env.VILI_DAEMON_PORT || 8824),
stateRoot: path.resolve(env.VILI_STATE_ROOT || path.join(projectRoot, ".vili")),
python: env.VILI_PYTHON || "python3",
workerEntry: path.resolve(env.VILI_WORKER_ENTRY || path.join(__dirname, "kimodo-resident-worker.py")),
modelsRoot: env.VILI_MODELS_ROOT || "",
threads: env.VILI_THREADS || "8",
kimodoCommit: env.VILI_KIMODO_COMMIT || "",
pythonEnv: env.VILI_PYTHON_ENV || "",
health: false,
printProviderAdvertisement: false,
};
for (let index = 0; index < argv.length; index += 1) {
@ -127,53 +93,29 @@ function parseArgs(argv) {
if (arg === "--host") args.host = argv[++index];
else if (arg === "--port") args.port = Number(argv[++index]);
else if (arg === "--state-root") args.stateRoot = path.resolve(argv[++index]);
else if (arg === "--python") args.python = argv[++index];
else if (arg === "--worker-entry") args.workerEntry = path.resolve(argv[++index]);
else if (arg === "--models-root") args.modelsRoot = argv[++index];
else if (arg === "--threads") args.threads = argv[++index];
else if (arg === "--kimodo-commit") args.kimodoCommit = argv[++index];
else if (arg === "--python-env") args.pythonEnv = argv[++index];
else if (arg === "--health") args.health = true;
else if (arg === "--print-provider-advertisement") args.printProviderAdvertisement = true;
}
if (!/^[0-9]{1,2}$/.test(String(args.threads)) || Number(args.threads) < 1 || Number(args.threads) > MAX_THREADS) {
console.error(`threads must be an integer from 1 to ${MAX_THREADS}`);
process.exit(2);
}
args.threads = Number(args.threads);
if (args.modelsRoot) args.modelsRoot = path.resolve(args.modelsRoot);
return args;
}
const args = parseArgs(process.argv.slice(2));
const startedAt = new Date().toISOString();
let backendStatus = {
checkedAt: null,
dockerImage: "unknown",
nvidiaRuntime: "unknown",
huggingFaceToken: "unknown",
kimodoHelp: "unknown",
kimodoWorker: "unknown",
kimodoWorkerDetail: null,
lastError: null,
};
function run(command, commandArgs, options = {}) {
return new Promise((resolve) => {
execFile(command, commandArgs, {
cwd: projectRoot,
timeout: options.timeout ?? 20000,
env: { ...process.env, ...(options.env || {}) },
windowsHide: true,
maxBuffer: 1024 * 1024 * 4,
}, (error, stdout, stderr) => {
resolve({
ok: !error,
code: error?.code ?? 0,
signal: error?.signal ?? null,
stdout: String(stdout || "").trim(),
stderr: String(stderr || "").trim(),
error: error ? String(error.message || error) : null,
});
});
});
}
function wsl(script, options = {}) {
return run("wsl.exe", ["-d", "Ubuntu-24.04", "-u", "root", "--", "bash", "-lc", script], options);
}
function shQuote(value) {
return `'${String(value).replaceAll("'", "'\\''")}'`;
}
// The worker child. Display state only: nothing reads `worker` to decide anything but ensureWorker.
const worker = { state: "stopped", proc: null, pid: null, port: null, model: null, servedModels: [], lastError: null };
let starting = null;
function firstDefined(...values) {
return values.find((value) => value !== undefined && value !== null);
@ -183,22 +125,11 @@ function boolValue(value) {
return value === true || String(value).toLowerCase() === "true" || String(value) === "1";
}
async function wslJson(script, options = {}) {
const result = await wsl(script, options);
if (!result.ok) return { ...result, body: null };
try {
return { ...result, body: JSON.parse(result.stdout) };
} catch (error) {
return { ...result, ok: false, body: null, error: `Invalid JSON: ${error.message}`, stderr: result.stderr };
}
}
function json(response, statusCode, body) {
const payload = JSON.stringify(body, null, 2);
response.writeHead(statusCode, {
"content-type": "application/json; charset=utf-8",
"cache-control": "no-store",
"access-control-allow-origin": "*",
});
response.end(payload);
}
@ -207,38 +138,31 @@ function text(response, statusCode, body) {
response.writeHead(statusCode, {
"content-type": "text/plain; charset=utf-8",
"cache-control": "no-store",
"access-control-allow-origin": "*",
});
response.end(body);
}
function providerAdvertisement() {
// A browser page can POST to localhost; a browser cannot send application/json cross-site without a preflight,
// and no route answers a preflight. So a POST that carries an Origin, or is not JSON, never reaches the body.
function refuseCrossSite(request, response) {
const contentType = String(request.headers["content-type"] || "").split(";")[0].trim().toLowerCase();
if (request.headers.origin !== undefined || contentType !== "application/json") {
json(response, 403, { ok: false, code: "cross-site" });
return true;
}
return false;
}
function backendStatus() {
return {
schema: "gamecult.eve.provider_advertisement.v1",
providerId,
title: "Vili Animation",
description: "Persona animation and Kimodo motion generation daemon on Raven.",
canonicalService: "asgard.vili",
locatedService: "asgard.raven.vili",
verseId: "raven.local",
cultMeshAddress: "asgard.raven.vili/eve/operator",
updatedAt: new Date().toISOString(),
endpoints: {
http: ravenBaseUrl,
health: `${ravenBaseUrl}/health`,
smoke: `${ravenBaseUrl}/smoke`,
providerAdvertisement: `${ravenBaseUrl}/provider-advertisement`,
operatorState: `${ravenBaseUrl}/operator-state`,
operatorSurface: `${ravenBaseUrl}/eve/operator`,
deckBroker: ravenDeckUrl,
providerDeck: `${ravenDeckUrl}/${providerId}`,
},
capabilities: [
"kimodo.motion.generate",
"persona.performance.intent",
"eve.operator.surface",
"cultmesh.provider.discovery",
],
state: worker.state,
pid: worker.pid,
port: worker.port,
threads: args.threads,
pythonEnv: args.pythonEnv || null,
kimodoCommit: args.kimodoCommit || null,
modelsRoot: args.modelsRoot || null,
lastError: worker.lastError,
};
}
@ -247,18 +171,15 @@ function operatorState() {
schema: "gamecult.vili.operator_state.v0",
service: "vili",
providerId,
verseId: "raven.local",
host: "raven",
host: os.hostname(),
startedAt,
updatedAt: new Date().toISOString(),
backend: backendStatus,
backend: backendStatus(),
authority: {
owns: [
"animation job intake",
"Kimodo runtime checks",
"Kimodo resident worker lifecycle",
"worker process lifecycle",
"motion artifact references",
"Vili provider and operator surfaces",
],
doesNotOwn: [
"Persona speech generation",
@ -269,11 +190,7 @@ function operatorState() {
},
routes: {
health: "/health",
providerAdvertisement: "/provider-advertisement",
operatorState: "/operator-state",
operatorSurface: "/eve/operator",
deckProviders: "/eve/deck/providers",
smoke: "/smoke",
generate: "/motion/generate",
},
};
@ -293,16 +210,11 @@ function commandBoundary() {
owns: [
"animation job identity",
"request persistence",
"Kimodo resident worker lifecycle",
"worker process lifecycle",
"timeout handling",
"motion artifact references",
],
},
smoke: {
method: "GET",
path: "/smoke",
owns: ["backend readiness proof", "resident worker warmup"],
},
liveness: {
method: "GET",
path: "/health",
@ -311,7 +223,7 @@ function commandBoundary() {
},
},
forbiddenAuthority:
"HTTP health, WebSocket decks, and JSON sidecars are compatibility/operator lowerings; they do not own daemon health or provider truth when CultCache/CultNet records exist.",
"HTTP health and the operator-state projection are compatibility lowerings; they do not own daemon health or provider truth when CultCache/CultNet records exist.",
};
}
@ -321,178 +233,168 @@ function transportProfile() {
profileId: "vili",
daemonId: "vili",
targetTransport: "cultnet.transport.rudp.v0",
currentTransport: "CultCache local state + compatibility HTTP/WS lowerings",
currentTransport: "CultCache local state + compatibility HTTP lowerings",
state: "local-cultcache-only",
providerAdvertisementSchema: "gamecult.eve.provider_advertisement.v1",
operatorStateSchema: "gamecult.vili.operator_state.v1",
commandBoundarySchema: "gamecult.vili.command_boundary.v1",
compatibilityMechanisms: [
`${ravenBaseUrl}/health`,
`${ravenBaseUrl}/provider-advertisement`,
`${ravenDeckUrl}/${providerId}`,
],
compatibilityMechanisms: ["/health", "/operator-state"],
cutLine:
"Vili owns typed provider, operator, command, and transport state. HTTP/WS/JSON projections are lowerings until Odin consumes Vili provider records over CultNet/RUDP.",
"Vili owns typed operator, command, and transport state. HTTP projections are lowerings until Odin consumes Vili records over CultNet/RUDP.",
updatedAt: new Date().toISOString(),
};
}
function eveSurface() {
const state = operatorState();
return {
schema: "gamecult.eve.surface.v1",
providerId,
title: "Vili Animation",
updatedAt: state.updatedAt,
surface: {
root: {
type: "panel",
title: "Vili Animation",
children: [
{ type: "stat", label: "Host", value: "Raven" },
{ type: "stat", label: "Verse", value: "raven.local" },
{ type: "stat", label: "Docker image", value: state.backend.dockerImage },
{ type: "stat", label: "NVIDIA runtime", value: state.backend.nvidiaRuntime },
{ type: "stat", label: "Kimodo", value: state.backend.kimodoHelp },
{ type: "stat", label: "Kimodo worker", value: state.backend.kimodoWorker },
{ type: "stat", label: "HF token", value: state.backend.huggingFaceToken },
{ type: "command", label: "Smoke", method: "GET", href: `${ravenBaseUrl}/smoke` },
{ type: "command", label: "Generate motion", method: "POST", href: `${ravenBaseUrl}/motion/generate` },
],
},
},
state,
};
}
async function persistSurfaces() {
await mkdir(args.stateRoot, { recursive: true });
await persistCultCacheSurfaces();
await writeFile(path.join(args.stateRoot, "vili-daemon.pid"), `${process.pid}\n`);
await writeFile(path.join(args.stateRoot, "provider-advertisement.json"), JSON.stringify(providerAdvertisement(), null, 2));
await writeFile(path.join(args.stateRoot, "operator-state.json"), JSON.stringify(operatorState(), null, 2));
await writeFile(path.join(args.stateRoot, "eve-operator-surface.json"), JSON.stringify(eveSurface(), null, 2));
await writeFile(path.join(args.stateRoot, "command-boundary.json"), JSON.stringify(commandBoundary(), null, 2));
await writeFile(path.join(args.stateRoot, "transport-profile.json"), JSON.stringify(transportProfile(), null, 2));
}
async function persistCultCacheSurfaces() {
const cache = CultCache.builder()
.withRegistry(viliDocumentRegistry)
.withGenericStore(new SingleFileMessagePackBackingStore(path.join(args.stateRoot, viliStoreFileName)))
.build();
await cache.put(providerAdvertisementDefinition, providerId, providerAdvertisement());
await cache.put(operatorStateDefinition, "vili", operatorState());
await cache.put(eveSurfaceDefinition, providerId, eveSurface());
await cache.put(commandBoundaryDefinition, "vili", commandBoundary());
await cache.put(transportProfileDefinition, "vili", transportProfile());
}
async function refreshBackendStatus({ includeKimodoHelp = false } = {}) {
const docker = await wsl([
"set -e",
"service docker start >/dev/null 2>&1 || true",
"docker images gamecult/kimodo:latest --format '{{.Repository}}:{{.Tag}}' | head -n 1",
].join("; "), { timeout: 20000 });
let nvidia = await wsl([
"set -e",
"service docker start >/dev/null 2>&1 || true",
"docker info --format '{{json .Runtimes}}' | grep -q nvidia && echo configured || echo missing",
].join("; "), { timeout: 20000 });
const token = await wsl("test -f /root/.cache/huggingface/token && echo present || echo missing", { timeout: 10000 });
const worker = await kimodoWorkerHealth(20000);
let help = { ok: true, stdout: backendStatus.kimodoHelp === "ok" ? "ok" : "not checked", stderr: "" };
if (includeKimodoHelp) {
nvidia = await wsl([
"set -e",
"service docker start >/dev/null 2>&1 || true",
"docker run --rm --runtime=nvidia --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi --query-gpu=name --format=csv,noheader | head -n 1",
].join("; "), { timeout: 45000 });
help = await wsl([
"set -e",
"service docker start >/dev/null 2>&1 || true",
dockerHuggingFaceEnv,
`docker run --rm --runtime=nvidia --gpus all ${dockerHuggingFaceArgs} gamecult/kimodo:latest kimodo_gen --help | sed -n '1,6p'`,
].join("; "), { timeout: 60000 });
}
backendStatus = {
checkedAt: new Date().toISOString(),
dockerImage: docker.ok && docker.stdout ? docker.stdout : "missing",
nvidiaRuntime: nvidia.ok && nvidia.stdout ? nvidia.stdout : "unavailable",
huggingFaceToken: token.ok ? token.stdout : "unknown",
kimodoHelp: includeKimodoHelp ? (help.ok ? "ok" : "failed") : backendStatus.kimodoHelp,
kimodoWorker: worker.ok && worker.body?.ok ? "resident" : "not running",
kimodoWorkerDetail: worker.body || null,
lastError: [docker, nvidia, token, help].find((result) => !result.ok)?.stderr || null,
// The whole child environment, in one place. Nothing else from process.env reaches the worker.
function childEnv() {
const threads = String(args.threads);
const env = {
HF_HOME: args.modelsRoot,
HF_HUB_OFFLINE: "1",
TRANSFORMERS_OFFLINE: "1",
VILI_EMBEDDINGS_DIR: path.join(args.stateRoot, "embeddings"),
VILI_THREADS: threads,
OMP_NUM_THREADS: threads,
MKL_NUM_THREADS: threads,
PYTHONPATH: path.join(projectRoot, "vendor", "CultLib", "packages", "cultcache-py", "src"),
PYTHONDONTWRITEBYTECODE: "1",
PYTHONUNBUFFERED: "1",
VILI_KIMODO_COMMIT: args.kimodoCommit,
VILI_PYTHON_ENV: args.pythonEnv,
};
await persistSurfaces();
return { docker, nvidia, token, help, backendStatus };
for (const name of ["PATH", "HOME", "LANG"]) {
if (process.env[name] !== undefined) env[name] = process.env[name];
}
return env;
}
async function kimodoWorkerHealth(timeout = 10000) {
return wslJson(`curl -fsS --max-time ${Math.ceil(timeout / 1000)} ${shQuote(`${kimodoWorkerUrl}/health`)}`, { timeout });
function workerUnconfigured() {
return !args.modelsRoot || !args.kimodoCommit || !args.pythonEnv;
}
async function startKimodoWorker({ model = null } = {}) {
const script = [
"set -e",
"service docker start >/dev/null 2>&1 || true",
dockerHuggingFaceEnv,
"mkdir -p /mnt/e/Projects/Vili/.vili/artifacts /mnt/e/Projects/Vili/.vili/logs /mnt/e/Projects/Vili/.vili/embeddings",
"export VILI_KIMODO_COMMIT=\"$(git -C /opt/gamecult/kimodo rev-parse HEAD)\"",
"export VILI_KIMODO_IMAGE=\"$(docker image inspect --format '{{.Id}}' gamecult/kimodo:latest)\"",
"if ! kill -0 $(cat /tmp/vili-wsl-keepalive.pid 2>/dev/null) >/dev/null 2>&1; then nohup bash -c 'while true; do sleep 3600; done' >/tmp/vili-wsl-keepalive.log 2>&1 & echo $! >/tmp/vili-wsl-keepalive.pid; fi",
`if docker ps --format '{{.Names}}' | grep -qx ${shQuote(kimodoWorkerName)}; then echo running; exit 0; fi`,
`docker rm -f ${shQuote(kimodoWorkerName)} >/dev/null 2>&1 || true`,
[
"docker run -d",
`--name ${shQuote(kimodoWorkerName)}`,
"--runtime=nvidia --gpus all",
`-p 127.0.0.1:${kimodoWorkerPort}:${kimodoWorkerPort}`,
dockerHuggingFaceArgs,
"-e VILI_KIMODO_COMMIT -e VILI_KIMODO_IMAGE",
"-e PYTHONPATH=/opt/cultlib/cultcache-py",
"-v /mnt/e/Projects/Vili/scripts:/opt/vili:ro",
"-v /mnt/e/Projects/CultLib/packages/cultcache-py/src:/opt/cultlib/cultcache-py:ro",
"-v /mnt/e/Projects/Vili/.vili/embeddings:/vili-embeddings",
"-v /mnt/e/Projects/Vili/.vili/artifacts:/outputs",
"gamecult/kimodo:latest",
"python",
"/opt/vili/kimodo-resident-worker.py",
`--port ${kimodoWorkerPort}`,
model ? `--model ${shQuote(model)}` : "",
].join(" "),
].join("; ");
return wsl(script, { timeout: 30000 });
function waitForExit(proc, timeoutMs) {
return new Promise((resolve) => {
if (proc.exitCode !== null || proc.signalCode !== null) return resolve(true);
const timer = setTimeout(() => resolve(false), timeoutMs);
proc.once("exit", () => {
clearTimeout(timer);
resolve(true);
});
});
}
async function ensureKimodoWorker({ timeoutMs = 1000 * 60 * 8, model = null } = {}) {
const health = await kimodoWorkerHealth(10000);
if (health.ok && health.body?.ok) {
if (!model || health.body.model === model || health.body.displayModel === model) return health;
await wsl(`docker rm -f ${shQuote(kimodoWorkerName)} >/dev/null 2>&1 || true`, { timeout: 30000 });
// The worker leads its own process group, so the encoder child it spawns dies with it.
async function stopWorker() {
const proc = worker.proc;
if (!proc || proc.pid === undefined) return;
worker.state = "stopped";
for (const signal of ["SIGTERM", "SIGKILL"]) {
try {
process.kill(-proc.pid, signal);
} catch {
// the group is already gone
}
if (await waitForExit(proc, signal === "SIGTERM" ? 10000 : 5000)) break;
}
}
async function startWorker(model, timeoutMs) {
await mkdir(path.join(args.stateRoot, "logs"), { recursive: true });
await mkdir(path.join(args.stateRoot, "embeddings"), { recursive: true });
const logFd = openSync(path.join(args.stateRoot, "logs", "worker.log"), "a");
const argv = [args.workerEntry, "--host", "127.0.0.1", "--port", "0"];
if (model) argv.push("--model", model);
const proc = spawn(args.python, argv, {
cwd: __dirname,
env: childEnv(),
stdio: ["ignore", "pipe", logFd],
shell: false,
detached: true,
});
closeSync(logFd);
Object.assign(worker, { state: "starting", proc, pid: proc.pid ?? null, port: null, model: model || null, servedModels: [], lastError: null });
const exited = new Promise((resolve) => {
proc.once("error", () => resolve("the worker could not be spawned"));
proc.once("exit", (code, signal) => resolve(`the worker exited (${signal ?? `code ${code}`})`));
});
exited.then((reason) => {
if (worker.proc !== proc) return;
Object.assign(worker, { state: "stopped", proc: null, pid: null, port: null, lastError: worker.state === "stopped" ? worker.lastError : reason });
});
const fail = async (reason) => {
worker.lastError = reason;
await stopWorker();
Object.assign(worker, { state: "failed", proc: null, pid: null, port: null, lastError: reason });
throw new Error(reason);
};
if (proc.pid === undefined) await fail(await exited);
try {
os.setPriority(proc.pid, 10);
} catch {
await fail("the worker priority could not be lowered");
}
const started = await startKimodoWorker({ model });
if (!started.ok) return started;
// Stdout lines are the worker's log, until the one that says it is ready.
const deadline = Date.now() + timeoutMs;
let last = started;
while (Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 5000));
last = await kimodoWorkerHealth(10000);
if (last.ok && last.body?.ok) return last;
let readyTimer;
const ready = await Promise.race([
new Promise((resolve) => {
const lines = readline.createInterface({ input: proc.stdout });
const sink = openSync(path.join(args.stateRoot, "logs", "worker.log"), "a");
lines.on("line", (line) => {
try {
const event = JSON.parse(line);
if (event?.event === "kimodo-worker-ready" && Number.isInteger(event.port)) return resolve(event);
} catch {
// not JSON: an ordinary log line
}
writeSync(sink, `${line}\n`);
});
lines.on("close", () => closeSync(sink));
}),
exited.then((reason) => ({ failed: reason })),
new Promise((resolve) => { readyTimer = setTimeout(() => resolve({ failed: "the worker was not ready in time" }), timeoutMs); }),
]);
clearTimeout(readyTimer);
if (ready.failed) await fail(ready.failed);
worker.port = ready.port;
for (;;) {
try {
const health = await fetch(`http://127.0.0.1:${worker.port}/health`, { signal: AbortSignal.timeout(10000) });
const body = await health.json();
if (health.ok && body?.ok) {
worker.servedModels = [body.model, body.displayModel].filter(Boolean);
worker.state = "ready";
return;
}
} catch {
// not answering yet
}
if (worker.proc !== proc) throw new Error(worker.lastError || "the worker exited");
if (Date.now() > deadline) await fail("the worker did not become healthy in time");
await new Promise((resolve) => setTimeout(resolve, 500));
}
return {
ok: false,
stdout: last.stdout || "",
stderr: last.stderr || "",
error: `Kimodo worker did not become healthy within ${timeoutMs}ms.`,
body: last.body || null,
};
}
async function ensureWorker({ model = null, timeoutMs = 1000 * 60 * 8 } = {}) {
if (starting) await starting;
if (worker.state === "ready" && model && !worker.servedModels.includes(model)) await stopWorker();
if (worker.state === "ready") return;
if (!starting) starting = startWorker(model, timeoutMs).finally(() => { starting = null; });
await starting;
}
async function readRequestBody(request) {
@ -503,11 +405,15 @@ async function readRequestBody(request) {
}
async function generateMotion(request, response) {
if (workerUnconfigured()) {
json(response, 503, { ok: false, code: "worker-unconfigured" });
return;
}
let body;
try {
body = await readRequestBody(request);
} catch (error) {
json(response, 400, { ok: false, error: `Invalid JSON: ${error.message}` });
} catch {
json(response, 400, { ok: false, error: "Invalid JSON." });
return;
}
@ -560,7 +466,7 @@ async function generateMotion(request, response) {
if (typeof body.constraints === "string") return body.constraints;
if (body.constraints_path) return body.constraints_path;
if (body.constraintsPath) return body.constraintsPath;
if (body.constraints !== undefined) return `/outputs/${jobId}/constraints.json`;
if (body.constraints !== undefined) return path.join(artifactDir, "constraints.json");
return undefined;
})();
if (body.constraints !== undefined && typeof body.constraints !== "string") {
@ -588,12 +494,13 @@ async function generateMotion(request, response) {
inputFolder: firstDefined(body.inputFolder, body.input_folder),
};
await writeFile(path.join(artifactDir, "meta.json"), JSON.stringify(meta, null, 2));
const worker = await ensureKimodoWorker({
timeoutMs: Number(firstDefined(body.workerTimeoutMs, body.worker_timeout_ms, 1000 * 60 * 8)),
model: body.model || null,
});
let result = worker;
if (worker.ok) {
let result;
try {
await ensureWorker({
timeoutMs: Number(firstDefined(body.workerTimeoutMs, body.worker_timeout_ms, 1000 * 60 * 8)),
model: body.model || null,
});
const workerRequest = {
model: body.model,
prompt: prompt || undefined,
@ -613,20 +520,19 @@ async function generateMotion(request, response) {
bvhStandardTpose: boolValue(firstDefined(body.bvhStandardTpose, body.bvh_standard_tpose)),
noPostprocess: boolValue(firstDefined(body.noPostprocess, body.no_postprocess)),
saveExampleDir: boolValue(firstDefined(body.saveExampleDir, body.save_example_dir)),
output: `/outputs/${jobId}/motion`,
output: path.join(artifactDir, "motion"),
};
await writeFile(path.join(artifactDir, "request.json"), JSON.stringify(workerRequest, null, 2));
const curlCommand = [
`curl -sS --max-time ${Math.ceil(Number(body.timeoutMs || 1000 * 60 * 30) / 1000)}`,
"-H 'content-type: application/json'",
`--data-binary @${shQuote(`/mnt/e/Projects/Vili/.vili/artifacts/${jobId}/request.json`)}`,
shQuote(`${kimodoWorkerUrl}/generate`),
].join(" ");
result = await wslJson([
"set -e",
curlCommand,
].join("; "), { timeout: Number(body.timeoutMs || 1000 * 60 * 30) });
if (result.body?.ok === false) result = { ...result, ok: false };
const answer = await fetch(`http://127.0.0.1:${worker.port}/generate`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(workerRequest),
signal: AbortSignal.timeout(Number(body.timeoutMs || 1000 * 60 * 30)),
});
const workerBody = await answer.json();
result = { ok: answer.ok && workerBody?.ok !== false, error: answer.ok ? null : `the worker answered ${answer.status}`, body: workerBody };
} catch (error) {
result = { ok: false, error: `the worker request failed (${error?.name || "Error"})`, body: null };
}
const job = {
@ -641,54 +547,21 @@ async function generateMotion(request, response) {
seed,
artifactDir,
ok: result.ok,
stdout: result.stdout,
stderr: result.stderr,
error: result.error,
worker: result.body,
};
await writeFile(path.join(args.stateRoot, "jobs", `${jobId}.json`), JSON.stringify(job, null, 2)).catch(async () => {
await mkdir(path.join(args.stateRoot, "jobs"), { recursive: true });
await writeFile(path.join(args.stateRoot, "jobs", `${jobId}.json`), JSON.stringify(job, null, 2));
});
await writeFile(path.join(args.stateRoot, "jobs", `${jobId}.json`), JSON.stringify(job, null, 2));
json(response, result.ok ? 200 : 502, job);
}
async function handleRequest(request, response) {
const url = new URL(request.url, `http://${request.headers.host || `${args.host}:${args.port}`}`);
if (request.method === "OPTIONS") {
response.writeHead(204, {
"access-control-allow-origin": "*",
"access-control-allow-methods": "GET,POST,OPTIONS",
"access-control-allow-headers": "content-type",
});
response.end();
return;
}
if (request.method === "POST" && refuseCrossSite(request, response)) return;
if (url.pathname === "/" || url.pathname === "/health") {
await refreshBackendStatus({ includeKimodoHelp: false });
json(response, 200, { ok: true, service: "vili", providerId, state: operatorState() });
} else if (url.pathname === "/provider-advertisement") {
json(response, 200, providerAdvertisement());
} else if (url.pathname === "/operator-state") {
json(response, 200, operatorState());
} else if (url.pathname === "/eve/operator") {
json(response, 200, eveSurface());
} else if (url.pathname === "/eve/deck/providers") {
json(response, 200, {
providers: [{
id: providerId,
title: "Vili Animation",
description: "Persona animation and Kimodo motion generation daemon.",
endpoint: `${ravenDeckUrl}/${providerId}`,
}],
});
} else if (url.pathname === "/smoke") {
const result = await refreshBackendStatus({ includeKimodoHelp: true });
const worker = await ensureKimodoWorker();
await refreshBackendStatus({ includeKimodoHelp: false });
const ok = result.backendStatus.kimodoHelp === "ok" && worker.ok && worker.body?.ok;
json(response, ok ? 200 : 502, { ok, ...result, worker: worker.body || worker });
} else if (url.pathname === "/motion/generate" && request.method === "POST") {
await generateMotion(request, response);
} else {
@ -704,11 +577,6 @@ async function healthCheck() {
if (!body.ok) throw new Error("health returned ok=false");
}
if (args.printProviderAdvertisement) {
console.log(JSON.stringify(providerAdvertisement(), null, 2));
process.exit(0);
}
if (args.health) {
healthCheck().then(() => process.exit(0), (error) => {
console.error(error.message);
@ -720,32 +588,25 @@ if (args.health) {
const server = http.createServer((request, response) => {
handleRequest(request, response).catch((error) => {
json(response, 500, { ok: false, error: error.message, stack: process.env.NODE_ENV === "development" ? error.stack : undefined });
// The error's message can carry the request; name only its class.
console.error(`request failed: ${error?.name || "Error"}`);
if (!response.headersSent) json(response, 500, { ok: false, code: "internal" });
else response.end();
});
});
const wss = new WebSocketServer({ noServer: true });
wss.on("connection", (socket) => {
const sendSurface = () => {
if (socket.readyState === socket.OPEN) socket.send(JSON.stringify(eveSurface()));
};
sendSurface();
const timer = setInterval(sendSurface, 5000);
socket.on("close", () => clearInterval(timer));
});
server.on("upgrade", (request, socket, head) => {
const url = new URL(request.url, `http://${request.headers.host || `${args.host}:${args.port}`}`);
if (url.pathname === `/eve/deck/${providerId}` || url.pathname === "/eve/deck") {
wss.handleUpgrade(request, socket, head, (ws) => wss.emit("connection", ws, request));
} else {
socket.destroy();
}
});
server.listen(args.port, args.host, () => {
console.log(`Vili listening on http://${args.host}:${args.port}`);
console.log(`Vili listening on http://${args.host}:${server.address().port}`);
});
setInterval(() => persistSurfaces().catch((error) => console.error(error)), 5000);
const surfaceTimer = setInterval(() => persistSurfaces().catch((error) => console.error(`persist failed: ${error?.name || "Error"}`)), 5000);
const shutdown = async () => {
clearInterval(surfaceTimer);
await stopWorker();
server.close();
process.exit(0);
};
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
}

2
vendor/CultLib vendored

@ -1 +1 @@
Subproject commit 02cf04b5c9266772947a69a6c25218e0d157507d
Subproject commit b549d73183aa36c6ebb8163ebc68cad60b09c85d