chore: checkpoint turso and ai runtime work

This commit is contained in:
Agent Board
2026-07-03 23:20:16 +08:00
parent a75b3d11f9
commit 36d027a4a1
67 changed files with 4224 additions and 914 deletions
+92 -5
View File
@@ -11,6 +11,7 @@
* - SKIP_OPENCODE:设为 "1" or "true" 可强制跳过 opencode
* - ENABLE_OPENHUB:设为 "1" or "true" 时启用 OpenHub FastAPI
* - OPENHUB_CMD / OPENHUB_BACKEND_CMD:覆盖 OpenHub FastAPI 启动命令;设置后即视为显式启用 OpenHub
* - OPENHUB_HOST / OPENHUB_BIND_HOSTOpenHub FastAPI 监听地址,默认 0.0.0.0 便于局域网访问
* - OPENHUB_PORT / OPENHUB_BACKEND_PORTOpenHub FastAPI 端口,默认 18080
* - SKIP_OPENHUB:设为 "1" or "true" 可强制跳过 OpenHub
* - OPENHUB_REDIS_URL / OPENHUB_REDIS_DB:记录 OpenHub Redis 位置;OPENHUB_REDIS_HEALTH_URL 可选做 HTTP health 检查
@@ -18,6 +19,8 @@
* - OPENHUB_OPENCODE_BASE_URLOpenHub 侧 opencode serve base URL;默认复用 MNOTE_OPENCODE_BASE_URL
* - SKIP_OPENHUB_HEALTH:设为 "1" or "true" 可跳过 OpenHub/Redis/opencode health 预检
* - MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE:默认 "1",禁用 OpenHub Git snapshot/restore/revert 写链
* - MNOTE_CONTROL_PLANE_BACKEND:控制面后端,默认 libsql-local;可设 turso-remote / turso-local-replica / turso-synced
* - MNOTE_TURSO_LOCAL_PATHlibsql-local 本地库路径,默认 /mnt/Data1T/Mnote_data/control-plane/control-plane-libsql.db
* - PYTHON_BIN:只在 BACKEND_CMD 未覆盖时,设置 Python 可执行文件,默认 "python"
*/
@@ -59,6 +62,7 @@ const pythonBin = resolveBackendExecutable("PYTHON_BIN", "python");
const backendPortFromEnv = Number(process.env.BACKEND_PORT || 8000);
const opencodePortFromEnv = Number(process.env.OPENCODE_PORT || 4096);
const openhubPortFromEnv = Number(process.env.OPENHUB_BACKEND_PORT || process.env.OPENHUB_PORT || 18080);
const defaultControlPlaneDir = "/mnt/Data1T/Mnote_data/control-plane";
function hasCommand(command) {
try {
@@ -83,12 +87,51 @@ function buildDefaultBackendCommand(port) {
}
function buildDefaultOpencodeCommand(port) {
return `while true; do script -qfec "opencode serve --hostname=127.0.0.1 --port ${port} --print-logs" /dev/null; sleep 1; done`;
const opencodeXdgRoot = process.env.OPENHUB_OPENCODE_XDG_ROOT || "/mnt/Data1T/Mnote_data/openhub/opencode-runtime";
const opencodeHome = process.env.OPENHUB_OPENCODE_HOME || "/mnt/Data1T/Mnote_data/openhub/opencode-home";
const modelEnvNames = [
"OPENCODE_API_KEY",
"OPENAI_API_KEY",
"OPENAI_BASE_URL",
"ANTHROPIC_API_KEY",
"GOOGLE_API_KEY",
"GOOGLE_GENERATIVE_AI_API_KEY",
"DEEPSEEK_API_KEY",
"GEMINI_API_KEY",
"MISTRAL_API_KEY",
"OPENROUTER_API_KEY",
"GROQ_API_KEY",
"XAI_API_KEY",
"AZURE_OPENAI_API_KEY",
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY",
];
const opencodeEnv = [
"env",
...modelEnvNames.map((name) => `-u ${name}`),
`HOME=${JSON.stringify(opencodeHome)}`,
`XDG_CONFIG_HOME=${JSON.stringify(path.join(opencodeXdgRoot, "config"))}`,
`XDG_DATA_HOME=${JSON.stringify(path.join(opencodeXdgRoot, "data"))}`,
`XDG_STATE_HOME=${JSON.stringify(path.join(opencodeXdgRoot, "state"))}`,
`XDG_CACHE_HOME=${JSON.stringify(path.join(opencodeXdgRoot, "cache"))}`,
].join(" ");
return [
`mkdir -p ${JSON.stringify(opencodeXdgRoot)} ${JSON.stringify(opencodeHome)}`,
`while true; do script -qfec ${JSON.stringify(`${opencodeEnv} opencode serve --hostname=127.0.0.1 --port ${port} --print-logs`)} /dev/null; sleep 1; done`,
].join(" && ");
}
function buildDefaultOpenHubCommand(port) {
const openhubBackendDir = process.env.OPENHUB_BACKEND_DIR || "/tmp/mnote-openhub-research/OpenHub/smart-query-backend";
return `cd ${JSON.stringify(openhubBackendDir)} && uvicorn app.main:app --host 127.0.0.1 --port ${port}`;
const openhubBackendDir = process.env.OPENHUB_BACKEND_DIR || "/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-backend";
const openhubHost = process.env.OPENHUB_HOST || process.env.OPENHUB_BIND_HOST || "0.0.0.0";
const uvicornBin = fs.existsSync(path.join(openhubBackendDir, ".venv", "bin", "uvicorn"))
? path.join(openhubBackendDir, ".venv", "bin", "uvicorn")
: "uvicorn";
const opencodeBaseUrl = process.env.OPENHUB_OPENCODE_BASE_URL || process.env.MNOTE_OPENCODE_BASE_URL || `http://127.0.0.1:${opencodePortFromEnv}`;
return [
`cd ${JSON.stringify(openhubBackendDir)}`,
`OPENCODE_BASE_URL=${JSON.stringify(opencodeBaseUrl)} MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE=${JSON.stringify(process.env.MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE || "1")} exec ${JSON.stringify(uvicornBin)} app.main:app --host ${JSON.stringify(openhubHost)} --port ${port}`,
].join(" && ");
}
function isEnabledEnv(value) {
@@ -132,7 +175,7 @@ function resolveOpenHubHealthPlan(env = process.env) {
enabled: shouldCheckOpenHubHealth(env),
openhub: {
label: "OpenHub FastAPI",
url: env.OPENHUB_HEALTH_URL || `${baseUrl}/health`,
url: env.OPENHUB_HEALTH_URL || `${baseUrl}/api/health`,
required: requireHealth,
},
redis: {
@@ -160,8 +203,25 @@ function resolveRuntimePlan(env = process.env) {
const frontendPort = Number(env.FRONTEND_PORT || 3000);
const skipGateway = false;
const publicPort = Number.isFinite(frontendPort) ? Math.floor(frontendPort) : 3000;
const controlPlaneBackend = String(env.MNOTE_CONTROL_PLANE_BACKEND || "libsql-local").trim() || "libsql-local";
if (controlPlaneBackend === "sqlite") {
throw new Error("desktop:hot 不再支持 SQLite control-plane fallback;请使用 libsql-local/turso-local-replica/turso-remote/turso-synced");
}
const controlPlaneEnv = {
MNOTE_CONTROL_PLANE_BACKEND: controlPlaneBackend,
};
if (controlPlaneBackend === "libsql-local" || controlPlaneBackend === "turso-local" || controlPlaneBackend === "turso") {
controlPlaneEnv.MNOTE_TURSO_LOCAL_PATH =
env.MNOTE_TURSO_LOCAL_PATH || path.join(defaultControlPlaneDir, "control-plane-libsql.db");
} else if (controlPlaneBackend === "turso-local-replica" || controlPlaneBackend === "turso-remote-replica") {
controlPlaneEnv.MNOTE_TURSO_LOCAL_REPLICA_PATH =
env.MNOTE_TURSO_LOCAL_REPLICA_PATH || path.join(defaultControlPlaneDir, "control-plane-replica.db");
} else if (controlPlaneBackend === "turso-synced") {
controlPlaneEnv.MNOTE_TURSO_SYNCED_PATH =
env.MNOTE_TURSO_SYNCED_PATH || env.MNOTE_TURSO_LOCAL_REPLICA_PATH || path.join(defaultControlPlaneDir, "control-plane-synced.db");
}
return {
const plan = {
skipGateway,
publicPort,
publicUrl: `http://localhost:${publicPort}`,
@@ -169,11 +229,14 @@ function resolveRuntimePlan(env = process.env) {
mnoteWebEnv: {
MNOTE_WEB_BIND: env.MNOTE_WEB_BIND || `0.0.0.0:${publicPort}`,
MNOTE_WEB_PUBLIC_BIND: env.MNOTE_WEB_PUBLIC_BIND || `127.0.0.1:${publicPort}`,
MNOTE_KNOWLEDGE_PROVIDER: env.MNOTE_KNOWLEDGE_PROVIDER || "lightrag_legacy",
MNOTE_OPENCODE_BASE_URL: env.MNOTE_OPENCODE_BASE_URL || `http://127.0.0.1:${opencodePortFromEnv}`,
MNOTE_OPENHUB_BASE_URL: env.MNOTE_OPENHUB_BASE_URL || `http://127.0.0.1:${openhubPortFromEnv}`,
MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE: env.MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE || "1",
...controlPlaneEnv,
},
};
return plan;
}
const runtimePlan = resolveRuntimePlan(process.env);
@@ -466,6 +529,23 @@ async function checkHttpHealth(url, label, required) {
}
}
async function isHttpHealthy(url) {
if (!url) return false;
try {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 1200);
const response = await fetch(url, {
method: "GET",
headers: { accept: "application/json,text/plain,*/*" },
signal: controller.signal,
});
clearTimeout(timer);
return response.ok;
} catch {
return false;
}
}
async function checkOpenHubHealth(plan = resolveOpenHubHealthPlan(process.env)) {
if (!plan.enabled) {
return true;
@@ -641,6 +721,11 @@ async function main() {
}
if (shouldStartOpenHub(process.env) && !process.env.OPENHUB_BACKEND_CMD && !process.env.OPENHUB_CMD) {
const openhubPortOk = await ensurePortFree(openhubPortFromEnv, "openhub");
if (!openhubPortOk) {
console.error(`OpenHub 端口 ${openhubPortFromEnv} 无法释放,已中止启动。`);
process.exit(1);
}
const openhubTask = findTask("openhub");
if (!openhubTask) {
throw new Error("缺少 OpenHub 任务配置");
@@ -674,11 +759,13 @@ if (require.main === module) {
}
module.exports = {
buildDefaultOpencodeCommand,
buildDefaultOpenHubCommand,
checkOpenHubHealth,
ensurePortFree,
getListeningPidsByPort,
getProcessNameByPid,
isHttpHealthy,
isPortFree,
resolveOpenHubHealthPlan,
resolveRuntimePlan,