chore: checkpoint turso and ai runtime work
This commit is contained in:
+92
-5
@@ -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_HOST:OpenHub FastAPI 监听地址,默认 0.0.0.0 便于局域网访问
|
||||
* - OPENHUB_PORT / OPENHUB_BACKEND_PORT:OpenHub 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_URL:OpenHub 侧 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_PATH:libsql-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,
|
||||
|
||||
@@ -3,9 +3,11 @@ const { spawn } = require("node:child_process");
|
||||
const net = require("node:net");
|
||||
const { test } = require("node:test");
|
||||
const {
|
||||
buildDefaultOpencodeCommand,
|
||||
buildDefaultOpenHubCommand,
|
||||
resolveBackendExecutable,
|
||||
ensurePortFree,
|
||||
isHttpHealthy,
|
||||
isPortFree,
|
||||
resolveOpenHubHealthPlan,
|
||||
resolveRuntimePlan,
|
||||
@@ -129,12 +131,48 @@ test("默认热启动计划只使用 mnote-web 作为 3000 owner", () => {
|
||||
assert.deepEqual(plan.mnoteWebEnv, {
|
||||
MNOTE_WEB_BIND: "0.0.0.0:3000",
|
||||
MNOTE_WEB_PUBLIC_BIND: "127.0.0.1:3000",
|
||||
MNOTE_KNOWLEDGE_PROVIDER: "lightrag_legacy",
|
||||
MNOTE_OPENCODE_BASE_URL: "http://127.0.0.1:4096",
|
||||
MNOTE_OPENHUB_BASE_URL: "http://127.0.0.1:18080",
|
||||
MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE: "1",
|
||||
MNOTE_CONTROL_PLANE_BACKEND: "libsql-local",
|
||||
MNOTE_TURSO_LOCAL_PATH: "/mnt/Data1T/Mnote_data/control-plane/control-plane-libsql.db",
|
||||
});
|
||||
});
|
||||
|
||||
test("热启动计划支持 libSQL local 控制面后端", () => {
|
||||
const plan = resolveRuntimePlan({
|
||||
FRONTEND_PORT: "3000",
|
||||
MNOTE_CONTROL_PLANE_BACKEND: "libsql-local",
|
||||
});
|
||||
|
||||
assert.equal(plan.mnoteWebEnv.MNOTE_CONTROL_PLANE_BACKEND, "libsql-local");
|
||||
assert.equal(
|
||||
plan.mnoteWebEnv.MNOTE_TURSO_LOCAL_PATH,
|
||||
"/mnt/Data1T/Mnote_data/control-plane/control-plane-libsql.db",
|
||||
);
|
||||
assert.equal(plan.mnoteWebEnv.MNOTE_CONTROL_PLANE_DB_PATH, undefined);
|
||||
});
|
||||
|
||||
test("热启动计划拒绝 SQLite runtime fallback", () => {
|
||||
assert.throws(
|
||||
() => resolveRuntimePlan({
|
||||
FRONTEND_PORT: "3000",
|
||||
MNOTE_CONTROL_PLANE_BACKEND: "sqlite",
|
||||
}),
|
||||
/不再支持 SQLite control-plane fallback/,
|
||||
);
|
||||
});
|
||||
|
||||
test("显式配置知识库 provider 时透传给 MNote host", () => {
|
||||
const plan = resolveRuntimePlan({
|
||||
FRONTEND_PORT: "3000",
|
||||
MNOTE_KNOWLEDGE_PROVIDER: "weknora",
|
||||
});
|
||||
|
||||
assert.equal(plan.mnoteWebEnv.MNOTE_KNOWLEDGE_PROVIDER, "weknora");
|
||||
});
|
||||
|
||||
test("默认跳过 FastAPI 后端,只有显式开启时才启动", () => {
|
||||
assert.equal(shouldStartBackend({}), false);
|
||||
assert.equal(shouldStartBackend({ ENABLE_BACKEND: "1" }), true);
|
||||
@@ -161,7 +199,7 @@ test("OpenHub health plan 包含 FastAPI、Redis、opencode 和默认关闭 Git
|
||||
});
|
||||
|
||||
assert.equal(plan.enabled, true);
|
||||
assert.equal(plan.openhub.url, "http://127.0.0.1:18081/health");
|
||||
assert.equal(plan.openhub.url, "http://127.0.0.1:18081/api/health");
|
||||
assert.equal(plan.openhub.required, true);
|
||||
assert.equal(plan.redis.url, "http://127.0.0.1:6379/health");
|
||||
assert.equal(plan.redis.skipped, false);
|
||||
@@ -175,7 +213,38 @@ test("OpenHub health plan 包含 FastAPI、Redis、opencode 和默认关闭 Git
|
||||
|
||||
test("OpenHub 默认命令只启动 FastAPI,不包含 snapshot/restore 写操作", () => {
|
||||
const command = buildDefaultOpenHubCommand(18082);
|
||||
assert.match(command, /uvicorn app\.main:app/);
|
||||
assert.match(command, /app\.main:app/);
|
||||
assert.match(command, /--host "?0\.0\.0\.0"?/);
|
||||
assert.match(command, /--port 18082/);
|
||||
assert.doesNotMatch(command, /snapshot|restore|revert|kill-port/i);
|
||||
assert.match(command, /\/mnt\/Data1T\/Mnote_data\/openhub\/OpenHub\/smart-query-backend/);
|
||||
assert.match(command, /MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE="1"/);
|
||||
assert.doesNotMatch(command, /git\s+(snapshot|restore|revert)|kill-port/i);
|
||||
});
|
||||
|
||||
test("opencode 默认命令使用 OpenHub 专用运行目录", () => {
|
||||
const command = buildDefaultOpencodeCommand(18085);
|
||||
assert.match(command, /\/mnt\/Data1T\/Mnote_data\/openhub\/opencode-runtime/);
|
||||
assert.match(command, /\/mnt\/Data1T\/Mnote_data\/openhub\/opencode-home/);
|
||||
assert.match(command, /HOME=/);
|
||||
assert.match(command, /XDG_CONFIG_HOME=/);
|
||||
assert.doesNotMatch(command, /已检测到现有 opencode/);
|
||||
assert.match(command, /opencode serve --hostname=127\.0\.0\.1 --port 18085 --print-logs/);
|
||||
});
|
||||
|
||||
test("isHttpHealthy 只把 2xx HTTP health 视为可复用服务", async () => {
|
||||
const server = net.createServer((socket) => {
|
||||
socket.once("data", () => {
|
||||
socket.end("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK");
|
||||
});
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
server.listen(0, "127.0.0.1", resolve);
|
||||
server.once("error", reject);
|
||||
});
|
||||
const { port } = server.address();
|
||||
try {
|
||||
assert.equal(await isHttpHealthy(`http://127.0.0.1:${port}/health`), true);
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
+43
-1
@@ -58,12 +58,54 @@ function devHotBindAddr(env = process.env) {
|
||||
}
|
||||
|
||||
function buildDevHotEnv(baseEnv = process.env) {
|
||||
return {
|
||||
const opencodePort = String(baseEnv.OPENCODE_PORT || "4096").trim();
|
||||
const openhubPort = String(baseEnv.OPENHUB_BACKEND_PORT || baseEnv.OPENHUB_PORT || "18080").trim();
|
||||
const opencodeBaseUrl = String(baseEnv.MNOTE_OPENCODE_BASE_URL || `http://127.0.0.1:${opencodePort}`).trim();
|
||||
const openhubBaseUrl = String(baseEnv.MNOTE_OPENHUB_BASE_URL || `http://127.0.0.1:${openhubPort}`).trim();
|
||||
const openhubBackendDir = String(
|
||||
baseEnv.OPENHUB_BACKEND_DIR || "/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-backend"
|
||||
).trim();
|
||||
const openhubOpencodeXdgRoot = String(
|
||||
baseEnv.OPENHUB_OPENCODE_XDG_ROOT || "/mnt/Data1T/Mnote_data/openhub/opencode-runtime"
|
||||
).trim();
|
||||
const openhubOpencodeHome = String(
|
||||
baseEnv.OPENHUB_OPENCODE_HOME || "/mnt/Data1T/Mnote_data/openhub/opencode-home"
|
||||
).trim();
|
||||
const skipOpenHub = String(baseEnv.SKIP_OPENHUB || "").trim();
|
||||
const controlPlaneBackend = String(baseEnv.MNOTE_CONTROL_PLANE_BACKEND || "libsql-local").trim() || "libsql-local";
|
||||
if (controlPlaneBackend === "sqlite") {
|
||||
throw new Error("dev:hot 不再支持 SQLite control-plane fallback;请使用 libsql-local/turso-local-replica/turso-remote/turso-synced");
|
||||
}
|
||||
const defaultControlPlaneDir = "/mnt/Data1T/Mnote_data/control-plane";
|
||||
const env = {
|
||||
...baseEnv,
|
||||
MNOTE_WEB_DEV_HOT_RELOAD: "1",
|
||||
MNOTE_WEB_BIND: devHotBindAddr(baseEnv),
|
||||
MNOTE_WEB_CMD: String(baseEnv.MNOTE_WEB_CMD || "").trim() || cargoWatchCommand(baseEnv),
|
||||
MNOTE_KNOWLEDGE_PROVIDER: String(baseEnv.MNOTE_KNOWLEDGE_PROVIDER || "lightrag_legacy").trim(),
|
||||
ENABLE_OPENHUB: skipOpenHub ? String(baseEnv.ENABLE_OPENHUB || "") : "1",
|
||||
CHECK_OPENHUB_HEALTH: String(baseEnv.CHECK_OPENHUB_HEALTH || "1"),
|
||||
MNOTE_OPENCODE_BASE_URL: opencodeBaseUrl,
|
||||
OPENHUB_OPENCODE_BASE_URL: String(baseEnv.OPENHUB_OPENCODE_BASE_URL || opencodeBaseUrl).trim(),
|
||||
OPENHUB_BACKEND_DIR: openhubBackendDir,
|
||||
OPENHUB_OPENCODE_XDG_ROOT: openhubOpencodeXdgRoot,
|
||||
OPENHUB_OPENCODE_HOME: openhubOpencodeHome,
|
||||
MNOTE_OPENHUB_BASE_URL: openhubBaseUrl,
|
||||
OPENHUB_HEALTH_URL: String(baseEnv.OPENHUB_HEALTH_URL || `${openhubBaseUrl.replace(/\/+$/, "")}/api/health`).trim(),
|
||||
MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE: String(baseEnv.MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE || "1"),
|
||||
MNOTE_CONTROL_PLANE_BACKEND: controlPlaneBackend,
|
||||
};
|
||||
if (controlPlaneBackend === "libsql-local" || controlPlaneBackend === "turso-local" || controlPlaneBackend === "turso") {
|
||||
env.MNOTE_TURSO_LOCAL_PATH =
|
||||
baseEnv.MNOTE_TURSO_LOCAL_PATH || path.join(defaultControlPlaneDir, "control-plane-dev-hot-libsql.db");
|
||||
} else if (controlPlaneBackend === "turso-local-replica" || controlPlaneBackend === "turso-remote-replica") {
|
||||
env.MNOTE_TURSO_LOCAL_REPLICA_PATH =
|
||||
baseEnv.MNOTE_TURSO_LOCAL_REPLICA_PATH || path.join(defaultControlPlaneDir, "control-plane-dev-hot-replica.db");
|
||||
} else if (controlPlaneBackend === "turso-synced") {
|
||||
env.MNOTE_TURSO_SYNCED_PATH =
|
||||
baseEnv.MNOTE_TURSO_SYNCED_PATH || baseEnv.MNOTE_TURSO_LOCAL_REPLICA_PATH || path.join(defaultControlPlaneDir, "control-plane-dev-hot-synced.db");
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
||||
@@ -24,7 +24,8 @@ const BUILD_CACHE_DIR = path.join(ROOT, "dist", "prod-build-cache", "mnote-web")
|
||||
const RUN_DIR = path.join(ROOT, "dist", "run", "mnote-web-prod");
|
||||
const RUN_STATE_PATH = path.join(RUN_DIR, "process.json");
|
||||
const DEFAULT_PORT = 3003;
|
||||
const DEFAULT_CONTROL_PLANE_DB = "/mnt/Data1T/Mnote_data/control-plane/control-plane.db";
|
||||
const DEFAULT_TURSO_LOCAL_PATH = "/mnt/Data1T/Mnote_data/control-plane/control-plane-prod-libsql.db";
|
||||
const DEFAULT_CONTROL_PLANE_BACKEND = "libsql-local";
|
||||
const ENV_ALL_PATH = path.join(ROOT, ".env.all");
|
||||
const RELEASE_BUILD_SCHEMA = "mnote-web-prod-runtime-assets-v2";
|
||||
const SUPPLEMENTAL_RUNTIME_ASSETS = [
|
||||
@@ -529,6 +530,11 @@ async function startRelease(release) {
|
||||
const logFd = fs.openSync(logPath, "a");
|
||||
const bind = process.env.MNOTE_WEB_BIND || `0.0.0.0:${port}`;
|
||||
const publicBind = process.env.MNOTE_WEB_PUBLIC_BIND || defaultPublicBind(port);
|
||||
const controlPlaneBackend =
|
||||
process.env.MNOTE_CONTROL_PLANE_BACKEND || DEFAULT_CONTROL_PLANE_BACKEND;
|
||||
if (controlPlaneBackend === "sqlite") {
|
||||
throw new Error("prod runtime 不再支持 SQLite control-plane fallback;请使用 libsql-local/turso-remote/turso-local-replica/turso-synced");
|
||||
}
|
||||
|
||||
const child = spawn(binaryPath, [], {
|
||||
cwd: release.metadata?.sourceDir || release.releaseDir,
|
||||
@@ -538,8 +544,13 @@ async function startRelease(release) {
|
||||
...process.env,
|
||||
MNOTE_WEB_BIND: bind,
|
||||
MNOTE_WEB_PUBLIC_BIND: publicBind,
|
||||
MNOTE_CONTROL_PLANE_DB_PATH:
|
||||
process.env.MNOTE_CONTROL_PLANE_DB_PATH || DEFAULT_CONTROL_PLANE_DB,
|
||||
MNOTE_CONTROL_PLANE_BACKEND: controlPlaneBackend,
|
||||
...(controlPlaneBackend === "libsql-local" || controlPlaneBackend === "turso-local" || controlPlaneBackend === "turso"
|
||||
? {
|
||||
MNOTE_TURSO_LOCAL_PATH:
|
||||
process.env.MNOTE_TURSO_LOCAL_PATH || DEFAULT_TURSO_LOCAL_PATH,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -16,6 +16,25 @@ assert.match(env.MNOTE_WEB_CMD, /crates\/mnote-web\/src/);
|
||||
assert.match(env.MNOTE_WEB_CMD, /crates\/mnote-web\/browser/);
|
||||
assert.equal(env.FRONTEND_PORT, "3200");
|
||||
assert.equal(env.MNOTE_WEB_BIND, "0.0.0.0:3200");
|
||||
assert.equal(env.MNOTE_KNOWLEDGE_PROVIDER, "lightrag_legacy");
|
||||
assert.equal(env.ENABLE_OPENHUB, "1");
|
||||
assert.equal(env.CHECK_OPENHUB_HEALTH, "1");
|
||||
assert.equal(env.MNOTE_OPENCODE_BASE_URL, "http://127.0.0.1:4096");
|
||||
assert.equal(env.OPENHUB_OPENCODE_BASE_URL, "http://127.0.0.1:4096");
|
||||
assert.equal(env.OPENHUB_BACKEND_DIR, "/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-backend");
|
||||
assert.equal(env.OPENHUB_OPENCODE_XDG_ROOT, "/mnt/Data1T/Mnote_data/openhub/opencode-runtime");
|
||||
assert.equal(env.OPENHUB_OPENCODE_HOME, "/mnt/Data1T/Mnote_data/openhub/opencode-home");
|
||||
assert.equal(env.MNOTE_OPENHUB_BASE_URL, "http://127.0.0.1:18080");
|
||||
assert.equal(env.OPENHUB_HEALTH_URL, "http://127.0.0.1:18080/api/health");
|
||||
assert.equal(env.MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE, "1");
|
||||
assert.equal(env.MNOTE_CONTROL_PLANE_BACKEND, "libsql-local");
|
||||
assert.equal(env.MNOTE_TURSO_LOCAL_PATH, "/mnt/Data1T/Mnote_data/control-plane/control-plane-dev-hot-libsql.db");
|
||||
assert.equal(env.MNOTE_CONTROL_PLANE_DB_PATH, undefined);
|
||||
|
||||
const explicitKnowledgeProviderEnv = buildDevHotEnv({
|
||||
MNOTE_KNOWLEDGE_PROVIDER: "weknora",
|
||||
});
|
||||
assert.equal(explicitKnowledgeProviderEnv.MNOTE_KNOWLEDGE_PROVIDER, "weknora");
|
||||
|
||||
const loopbackEnv = buildDevHotEnv({
|
||||
MNOTE_WEB_BIND: "127.0.0.1:3300",
|
||||
@@ -24,4 +43,16 @@ const loopbackEnv = buildDevHotEnv({
|
||||
assert.equal(loopbackEnv.MNOTE_WEB_BIND, "0.0.0.0:3300");
|
||||
assert.equal(loopbackEnv.MNOTE_WEB_CMD, "custom");
|
||||
|
||||
const skipEnv = buildDevHotEnv({
|
||||
SKIP_OPENHUB: "1",
|
||||
});
|
||||
assert.equal(skipEnv.ENABLE_OPENHUB, "");
|
||||
|
||||
assert.throws(
|
||||
() => buildDevHotEnv({
|
||||
MNOTE_CONTROL_PLANE_BACKEND: "sqlite",
|
||||
}),
|
||||
/不再支持 SQLite control-plane fallback/,
|
||||
);
|
||||
|
||||
console.log(JSON.stringify({ ok: true, command: env.MNOTE_WEB_CMD }, null, 2));
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
require("./task436-local-markdown-open-document-external-change-smoke.js");
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
require("./task436-local-markdown-open-document-external-change-smoke.js");
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
require("./task779-openhub-file-edit-document-pane-refresh-smoke.js");
|
||||
@@ -70,7 +70,7 @@ async function validateAuthEntry(baseUrl) {
|
||||
assert.match(authText, /name="account"[^>]*type="text"|type="text"[^>]*name="account"/);
|
||||
assert.match(authText, /data-auth-field="username" hidden/);
|
||||
assert.match(authText, /name="password"[^>]*type="password"|type="password"[^>]*name="password"/);
|
||||
assert.match(authText, /data-auth-mode="sqlite-session"/);
|
||||
assert.match(authText, /data-auth-mode="control-plane-session"/);
|
||||
assert.match(authText, /没有账号?注册/);
|
||||
assert.match(authText, /测试账号快速登录/);
|
||||
assert.doesNotMatch(authText, /隐私政策|使用\s*Google|使用\s*GitHub|第三方快捷/iu);
|
||||
|
||||
@@ -122,9 +122,17 @@ async function main() {
|
||||
window.EventSource = FakeEventSource;
|
||||
window.__mnoteTask540EventSources = sources;
|
||||
window.__mnoteTask540CompatEvents = [];
|
||||
window.__mnoteTask540FileChangeBatches = [];
|
||||
window.__mnoteTask540FileChangeReactions = [];
|
||||
window.addEventListener("tree:local-folder-watch-batch", (event) => {
|
||||
window.__mnoteTask540CompatEvents.push(event.detail || {});
|
||||
});
|
||||
window.addEventListener("mnote:file-change-batch", (event) => {
|
||||
window.__mnoteTask540FileChangeBatches.push(event.detail || {});
|
||||
});
|
||||
window.addEventListener("mnote:file-change-reaction", (event) => {
|
||||
window.__mnoteTask540FileChangeReactions.push(event.detail || {});
|
||||
});
|
||||
});
|
||||
|
||||
const page = await context.newPage();
|
||||
@@ -203,7 +211,7 @@ async function main() {
|
||||
window.__mnoteTask540FileProjectionRequests = [];
|
||||
window.__mnoteTask540SidebarProjectionRequests = [];
|
||||
});
|
||||
await page.evaluate(({ rootUriValue, pagePath, assetPath }) => {
|
||||
await page.evaluate(({ rootUriValue, workspaceIdValue, pagePath, assetPath }) => {
|
||||
const detail = {
|
||||
source: "synthetic_page_ai_receipt",
|
||||
reason: "agent_run_receipt",
|
||||
@@ -214,15 +222,37 @@ async function main() {
|
||||
rootUri: rootUriValue,
|
||||
revision: "task540-revision",
|
||||
changedPaths: [
|
||||
{ relativePath: pagePath, changeType: "modified" },
|
||||
{
|
||||
relativePath: pagePath,
|
||||
documentId: `local-md:${pagePath.replaceAll("/", "~2F")}`,
|
||||
changeType: "modified",
|
||||
eventKind: "Modify(Data(Content))",
|
||||
observedFileVersion: "sha256:task540-observed",
|
||||
bufferFileVersion: "sha256:task540-buffer",
|
||||
selfWriteEcho: false,
|
||||
},
|
||||
{ relativePath: assetPath, changeType: "modified" },
|
||||
],
|
||||
affectedParents: [{ relativePath: "", reason: "task540" }],
|
||||
},
|
||||
};
|
||||
window.__mnoteLocalFolderEventBus.emitSyntheticWatchBatch(detail);
|
||||
window.__mnoteLocalFolderEventBus.emitSyntheticWatchBatch(detail);
|
||||
}, { rootUriValue: rootUri, pagePath: relativePath, assetPath: resourcePath });
|
||||
window.__mnoteLocalFolderEventBus.emitChangedFiles({
|
||||
source: detail.source,
|
||||
reason: detail.reason,
|
||||
rootUri: rootUriValue,
|
||||
workspaceId: workspaceIdValue,
|
||||
changedFiles: detail.payload.changedPaths,
|
||||
affectedParents: detail.payload.affectedParents,
|
||||
});
|
||||
window.__mnoteLocalFolderEventBus.emitChangedFiles({
|
||||
source: detail.source,
|
||||
reason: detail.reason,
|
||||
rootUri: rootUriValue,
|
||||
workspaceId: workspaceIdValue,
|
||||
changedFiles: detail.payload.changedPaths,
|
||||
affectedParents: detail.payload.affectedParents,
|
||||
});
|
||||
}, { rootUriValue: rootUri, workspaceIdValue: workspaceId, pagePath: relativePath, assetPath: resourcePath });
|
||||
|
||||
await page.waitForFunction(() => {
|
||||
const events = window.__mnoteTask540CompatEvents || [];
|
||||
@@ -251,6 +281,14 @@ async function main() {
|
||||
lastReason: document.documentElement.getAttribute("data-mnote-local-folder-event-bus-last-reason") || "",
|
||||
compatEventCount: compatEvents.length,
|
||||
lastCompatViaEventBus: Boolean(compatEvents.at(-1)?.viaEventBus),
|
||||
fileChangeService: document.documentElement.getAttribute("data-mnote-file-change-service") || "",
|
||||
fileChangeSchema: document.documentElement.getAttribute("data-mnote-file-change-service-last-schema") || "",
|
||||
fileChangeCount: document.documentElement.getAttribute("data-mnote-file-change-service-last-count") || "",
|
||||
fileChangeDroppedCount: document.documentElement.getAttribute("data-mnote-file-change-service-dropped-count") || "",
|
||||
fileChangeLastReaction: document.documentElement.getAttribute("data-mnote-file-change-service-last-reaction") || "",
|
||||
fileChangeBatchCount: (window.__mnoteTask540FileChangeBatches || []).length,
|
||||
fileChangeReactionTypes: (window.__mnoteTask540FileChangeReactions || []).map((event) => event.type),
|
||||
lastFileChangeBatch: (window.__mnoteTask540FileChangeBatches || []).at(-1) || null,
|
||||
hasPageAiSyntheticCompatEvent: compatEvents.some((event) => (
|
||||
event?.source === "synthetic_page_ai_receipt"
|
||||
&& event?.reason === "agent_run_receipt"
|
||||
@@ -268,6 +306,30 @@ async function main() {
|
||||
assert.equal(result.connectionCount, "1", "event bus diagnostics connectionCount 应为 1");
|
||||
assert.equal(result.hasPageAiSyntheticCompatEvent, true, "Page AI synthetic 事件应保留 source/reason 并通过 bus 派发兼容事件");
|
||||
assert.equal(result.lastCompatViaEventBus, true, "兼容 tree:local-folder-watch-batch 必须标记 viaEventBus");
|
||||
assert.equal(result.fileChangeService, "ready", "FileChangeService diagnostics 未 ready");
|
||||
assert.equal(typeof result.lastFileChangeBatch, "object", "应暴露标准 FileChange batch");
|
||||
assert.equal(result.fileChangeSchema, "mnote.file_change_batch.v1", "应生成标准 FileChange batch");
|
||||
assert.equal(result.fileChangeCount, "2", "同 tick 两个 changed path 应进入同一个 FileChange batch");
|
||||
assert.equal(result.fileChangeDroppedCount, "0", "rootUri 内 changed path 不应被丢弃");
|
||||
assert.equal(result.fileChangeBatchCount, 1, "同 tick 两个 receipt 应合并成一次标准 FileChange batch");
|
||||
assert(
|
||||
result.fileChangeReactionTypes.includes("refresh_current_document"),
|
||||
`当前 Markdown 应生成 refresh_current_document reaction: ${JSON.stringify(result.fileChangeReactionTypes)}`,
|
||||
);
|
||||
assert(
|
||||
result.fileChangeReactionTypes.includes("refresh_resource_tab"),
|
||||
`资源文件应生成 refresh_resource_tab reaction: ${JSON.stringify(result.fileChangeReactionTypes)}`,
|
||||
);
|
||||
assert.equal(
|
||||
result.lastFileChangeBatch?.changes?.[0]?.observedFileVersion,
|
||||
"sha256:task540-observed",
|
||||
"FileChange batch 应保留 observedFileVersion",
|
||||
);
|
||||
assert.equal(
|
||||
result.lastFileChangeBatch?.changes?.[0]?.selfWriteEcho,
|
||||
false,
|
||||
"FileChange batch 应保留 selfWriteEcho",
|
||||
);
|
||||
assert.equal(result.resourceWatchReady, "event-bus", "resource tab watch 应通过 event bus 准备好");
|
||||
assert(
|
||||
result.browserStatRequests.some((url) => String(url).includes(encodeURIComponent(resourcePath)) || String(url).includes(resourcePath)),
|
||||
|
||||
@@ -17,7 +17,7 @@ const checks = [
|
||||
['mnote auth truth copy', route.includes('"authTruth": "mnote_session"') && runtime.includes('拒绝 OpenHub JWT/localStorage')],
|
||||
['openhub user key derived', route.includes('"openhubUserKey"') && route.includes('stable_hash("openhub_user"')],
|
||||
['workspace session tool scope', route.includes('"openhubSessionScope"') && route.includes('"skillScope"') && route.includes('"mcpScope"') && route.includes('"toolPermissionScope"')],
|
||||
['proxy injects mnote scope headers', route.includes('add_mnote_scope_headers') && route.includes('x-mnote-user-key') && route.includes('x-mnote-workspace-key') && route.includes('x-mnote-session-scope') && route.includes('x-mnote-tool-permission-scope') && route.includes('x-mnote-weknora-tool-scope')],
|
||||
['proxy injects mnote scope headers', route.includes('add_mnote_scope_headers') && route.includes('x-mnote-user-key') && route.includes('x-mnote-user-id') && route.includes('x-mnote-display-name') && route.includes('x-mnote-workspace-key') && route.includes('x-mnote-session-scope') && route.includes('x-mnote-tool-permission-scope') && route.includes('x-mnote-weknora-tool-scope')],
|
||||
['proxy carries full mnote scope internally', route.includes('mnoteScope') && route.includes('mnote_scope_from_query') && route.includes('proxy_query_without_internal_scope')],
|
||||
['snake case bootstrap scope aliases', route.includes('"openhub_user_key"') && route.includes('"workspace_key"') && route.includes('"session_scope"') && route.includes('"tool_permission_scope"')],
|
||||
['weknora tool scope', route.includes('"weknoraToolScope"') && route.includes('"weknora_tool_scope"') && runtime.includes('weknora_tool_scope')],
|
||||
|
||||
@@ -4,7 +4,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const openHubRoot =
|
||||
process.env.OPENHUB_RESEARCH_ROOT || '/tmp/mnote-openhub-research/OpenHub';
|
||||
process.env.OPENHUB_RESEARCH_ROOT || '/mnt/Data1T/Mnote_data/openhub/OpenHub';
|
||||
const backendRoot = path.join(openHubRoot, 'smart-query-backend');
|
||||
const disableEnv = 'MNOTE_OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE';
|
||||
const legacyDisableEnv = 'OPENHUB_DISABLE_GIT_SNAPSHOT_RESTORE';
|
||||
|
||||
@@ -4,7 +4,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const openHubRoot =
|
||||
process.env.OPENHUB_RESEARCH_ROOT || '/tmp/mnote-openhub-research/OpenHub';
|
||||
process.env.OPENHUB_RESEARCH_ROOT || '/mnt/Data1T/Mnote_data/openhub/OpenHub';
|
||||
const backendRoot = path.join(openHubRoot, 'smart-query-backend');
|
||||
|
||||
function read(relativePath) {
|
||||
@@ -43,21 +43,28 @@ assertCheck(
|
||||
'MNOTE_HOST_TRUTH = "mnote_controlled_headers"',
|
||||
'def derive_mnote_user',
|
||||
'def resolve_user_workspace',
|
||||
'def resolve_user_asset_workspace',
|
||||
'def get_mnote_scope_metadata',
|
||||
'X-MNote-Display-Name',
|
||||
...requiredHeaders,
|
||||
])
|
||||
);
|
||||
|
||||
assertCheck(
|
||||
'derived user is stable and not a shared singleton',
|
||||
'derived user is stable per MNote user and not tied to workspace scope',
|
||||
includesAll(mnoteScope, [
|
||||
'def _stable_openhub_user_id',
|
||||
'def _stable_openhub_user_id(user_key: str)',
|
||||
'mnote_openhub_user_id',
|
||||
'user_key',
|
||||
'workspace_key',
|
||||
'display_name',
|
||||
'get_user_by_username',
|
||||
'"openhub_user_id"',
|
||||
'"openhub_username"',
|
||||
]) && !mnoteScope.includes('mnote_shared_user')
|
||||
]) &&
|
||||
!mnoteScope.includes('def _stable_openhub_user_id(user_key: str, workspace_key: str)') &&
|
||||
!mnoteScope.includes('_stable_openhub_user_id(user_key, workspace_key)') &&
|
||||
!mnoteScope.includes('mnote_shared_user')
|
||||
);
|
||||
|
||||
assertCheck(
|
||||
@@ -78,6 +85,7 @@ assertCheck(
|
||||
includesAll(mnoteScope, [
|
||||
'tool_permission_scope',
|
||||
'weknora_tool_scope',
|
||||
'provisioned_workspace_path',
|
||||
'_parse_scope_header',
|
||||
'"mnote_scope"',
|
||||
'"source_headers"',
|
||||
@@ -101,7 +109,7 @@ assertCheck(
|
||||
'query/session entries consume derived workspace and scope',
|
||||
query.includes('resolve_user_workspace(current_user)') &&
|
||||
query.includes('not current_user.get("mnote_host_mode")') &&
|
||||
query.includes('mnote_scope=get_mnote_scope_metadata(current_user)') &&
|
||||
query.includes('mnote_scope = get_mnote_scope_metadata(current_user)') &&
|
||||
session.includes('resolve_user_workspace(current_user)') &&
|
||||
session.includes('mnote_scope=get_mnote_scope_metadata(current_user)')
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const openHubRoot = process.env.OPENHUB_RESEARCH_ROOT || "/tmp/mnote-openhub-research/OpenHub";
|
||||
const openHubRoot = process.env.OPENHUB_RESEARCH_ROOT || "/mnt/Data1T/Mnote_data/openhub/OpenHub";
|
||||
const backendSessionPath = path.join(openHubRoot, "smart-query-backend/app/api/session.py");
|
||||
const frontendApiPath = path.join(openHubRoot, "smart-query-frontend/src/services/api.js");
|
||||
const diffViewerPath = path.join(openHubRoot, "smart-query-frontend/src/components/DiffViewer.jsx");
|
||||
|
||||
@@ -82,6 +82,7 @@ async function readDocumentPaneState(page) {
|
||||
openhubRefreshMarker: document.documentElement.getAttribute("data-mnote-page-ai-openhub-document-pane-refresh") || "",
|
||||
eventBusSource: document.documentElement.getAttribute("data-mnote-local-folder-event-bus-last-source") || "",
|
||||
eventBusReason: document.documentElement.getAttribute("data-mnote-local-folder-event-bus-last-reason") || "",
|
||||
fileChangeSources: window.__mnoteTask779FileChangeSources || [],
|
||||
documentSessionDebug: window.__mnoteDebugDocumentSessions?.snapshot?.() || null,
|
||||
};
|
||||
});
|
||||
@@ -166,6 +167,12 @@ async function run() {
|
||||
await waitForEditorText(page, initialText);
|
||||
await waitForPageAiRuntime(page);
|
||||
await installOpenHubChangedFileBridge(page);
|
||||
await page.evaluate(() => {
|
||||
window.__mnoteTask779FileChangeSources = [];
|
||||
window.addEventListener("mnote:file-change-batch", (event) => {
|
||||
window.__mnoteTask779FileChangeSources.push(String(event?.detail?.source || ""));
|
||||
});
|
||||
});
|
||||
debug.before = await readDocumentPaneState(page);
|
||||
|
||||
fs.writeFileSync(
|
||||
@@ -182,7 +189,10 @@ async function run() {
|
||||
assert(debug.after.editorText.includes(changedText), `document pane 应显示磁盘新内容: ${debug.after.editorText}`);
|
||||
assert(!debug.after.editorText.includes(initialText), `document pane 不应保留旧正文: ${debug.after.editorText}`);
|
||||
assert.equal(debug.after.openhubRefreshMarker, relativePath, "应记录 OpenHub document pane refresh marker");
|
||||
assert.equal(debug.after.eventBusSource, "openhub_changed_file_bridge", "应复用 local-folder event bus synthetic watch batch");
|
||||
assert(
|
||||
debug.after.fileChangeSources.some((source) => source.includes("openhub_changed_file_bridge")),
|
||||
`应通过 FileChangeService 消费 OpenHub changedFiles adapter: ${JSON.stringify(debug.after.fileChangeSources)}`,
|
||||
);
|
||||
|
||||
await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true });
|
||||
const result = {
|
||||
|
||||
@@ -7,7 +7,7 @@ const vm = require("node:vm");
|
||||
const { TextDecoder } = require("node:util");
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const openHubRoot = process.env.OPENHUB_RESEARCH_ROOT || "/tmp/mnote-openhub-research/OpenHub";
|
||||
const openHubRoot = process.env.OPENHUB_RESEARCH_ROOT || "/mnt/Data1T/Mnote_data/openhub/OpenHub";
|
||||
const embedPath = path.join(openHubRoot, "smart-query-frontend/src/mnoteEmbed.js");
|
||||
const smartQueryPath = path.join(openHubRoot, "smart-query-frontend/src/pages/SmartQueryPage.jsx");
|
||||
const diffViewerPath = path.join(openHubRoot, "smart-query-frontend/src/components/DiffViewer.jsx");
|
||||
|
||||
@@ -6,7 +6,7 @@ const path = require("node:path");
|
||||
const vm = require("node:vm");
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const openHubRoot = process.env.OPENHUB_RESEARCH_ROOT || "/tmp/mnote-openhub-research/OpenHub";
|
||||
const openHubRoot = process.env.OPENHUB_RESEARCH_ROOT || "/mnt/Data1T/Mnote_data/openhub/OpenHub";
|
||||
const backendBridgePath = path.join(openHubRoot, "smart-query-backend/app/services/mnote_weknora.py");
|
||||
const backendApiPath = path.join(openHubRoot, "smart-query-backend/app/api/mnote_tools.py");
|
||||
const backendStreamPath = path.join(openHubRoot, "smart-query-backend/app/services/stream.py");
|
||||
|
||||
@@ -224,7 +224,10 @@ async function verifyInBrowser(fixture, name) {
|
||||
url.searchParams.set("treeView", "filetree");
|
||||
await page.goto(url.toString(), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
||||
|
||||
await page.locator('[data-testid="mnote-knowledge-rag-settings-toggle"]').click({ timeout: UI_TIMEOUT_MS });
|
||||
const knowledgeEntry = page.locator('[data-testid="mnote-knowledge-rag-settings-toggle"]');
|
||||
assert.equal(await knowledgeEntry.getAttribute("href"), "/knowledge", "主知识库入口应打开 WeKnora 原生 host");
|
||||
assert.equal(await knowledgeEntry.getAttribute("data-mnote-action"), "open-knowledge-host", "主知识库入口不应再打开旧设置壳");
|
||||
await page.goto(`${BASE_URL}/debug/knowledge-rag`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
|
||||
const panel = page.locator('[data-testid="mnote-weknora-knowledge-settings-panel"]');
|
||||
await panel.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
assert.equal(await panel.getAttribute("data-knowledge-rag-default-provider"), "weknora", "设置面板应声明 WeKnora provider");
|
||||
@@ -251,7 +254,7 @@ async function verifyInBrowser(fixture, name) {
|
||||
const firstSourceInput = panel.locator('[data-knowledge-rag-source-input]').first();
|
||||
await firstSourceInput.fill(fixture.folderPath);
|
||||
await panel.locator('[data-knowledge-rag-action="ingest"]').click({ timeout: UI_TIMEOUT_MS });
|
||||
await page.locator('[data-knowledge-rag-action="filter-sources"][data-knowledge-rag-filter="all"]').click({ timeout: UI_TIMEOUT_MS });
|
||||
await page.locator('[data-knowledge-rag-action="filter-sources"][data-knowledge-rag-filter="all"]').first().click({ timeout: UI_TIMEOUT_MS });
|
||||
const sourceRows = await Promise.all(fixture.sourcePaths.map(async (sourcePath) => {
|
||||
const row = page.locator(`[data-knowledge-rag-source-row="true"][data-knowledge-rag-source-path="${sourcePath}"]`).first();
|
||||
await row.waitFor({ state: "visible", timeout: POLL_TIMEOUT_MS });
|
||||
|
||||
@@ -5,7 +5,7 @@ const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const openHubRoot = process.env.OPENHUB_RESEARCH_ROOT || "/tmp/mnote-openhub-research/OpenHub";
|
||||
const openHubRoot = process.env.OPENHUB_RESEARCH_ROOT || "/mnt/Data1T/Mnote_data/openhub/OpenHub";
|
||||
const files = {
|
||||
chatInput: path.join(openHubRoot, "smart-query-frontend/src/components/ChatInput.jsx"),
|
||||
embed: path.join(openHubRoot, "smart-query-frontend/src/mnoteEmbed.js"),
|
||||
|
||||
@@ -6,7 +6,7 @@ const path = require("node:path");
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const openHubRoot =
|
||||
process.env.OPENHUB_RESEARCH_ROOT || "/mnt/Data1T/tmp/mnote-openhub-research/OpenHub";
|
||||
process.env.OPENHUB_RESEARCH_ROOT || "/mnt/Data1T/Mnote_data/openhub/OpenHub";
|
||||
|
||||
const files = {
|
||||
chatInput: path.join(openHubRoot, "smart-query-frontend/src/components/ChatInput.jsx"),
|
||||
@@ -48,7 +48,7 @@ function check(failures, name, passed, detail = "") {
|
||||
const chatInput = read(files.chatInput);
|
||||
const smartQueryPage = read(files.smartQueryPage);
|
||||
const api = read(files.api);
|
||||
const controlRow = extractBetween(chatInput, "<Segmented", "<TextArea");
|
||||
const controlRow = extractBetween(chatInput, "<AgentModeToggle", "{currentTodos");
|
||||
const handleMNoteSend = extractBetween(chatInput, "const handleSendWithMNoteContext", "useEffect(() =>");
|
||||
const handleSend = extractBetween(smartQueryPage, "const handleSend = async", "const handleKeyPress");
|
||||
|
||||
@@ -91,13 +91,13 @@ check(
|
||||
check(
|
||||
failures,
|
||||
"MNote context buttons are in the controls row beside agent/model controls",
|
||||
controlRow.includes("<Segmented") &&
|
||||
controlRow.includes("<AgentModeToggle") &&
|
||||
controlRow.includes("<ModelSelect") &&
|
||||
controlRow.includes("data-mnote-openhub-current-tab-toggle") &&
|
||||
controlRow.includes("data-mnote-openhub-current-folder-toggle") &&
|
||||
controlRow.indexOf("<Segmented") < controlRow.indexOf("data-mnote-openhub-current-tab-toggle") &&
|
||||
controlRow.indexOf("<AgentModeToggle") < controlRow.indexOf("data-mnote-openhub-current-tab-toggle") &&
|
||||
controlRow.indexOf("<ModelSelect") < controlRow.indexOf("data-mnote-openhub-current-folder-toggle"),
|
||||
"native buttons must be before TextArea and in the same row as agent segmented/model select"
|
||||
"native buttons must stay in the same compact controls row as agent toggle/model select"
|
||||
);
|
||||
|
||||
check(
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
const http = require("node:http");
|
||||
const { spawn } = require("node:child_process");
|
||||
|
||||
const OPENHUB_BACKEND_DIR = process.env.OPENHUB_BACKEND_DIR || "/tmp/mnote-openhub-research/OpenHub/smart-query-backend";
|
||||
const OPENHUB_BACKEND_DIR = process.env.OPENHUB_BACKEND_DIR || "/mnt/Data1T/Mnote_data/openhub/OpenHub/smart-query-backend";
|
||||
const OPENCODE_PORT = Number(process.env.TASK793_OPENCODE_PORT || 19096);
|
||||
const OPENHUB_PORT = Number(process.env.TASK793_OPENHUB_PORT || 18181);
|
||||
const SESSION_ID = "ses_mnote_context_fullchain";
|
||||
|
||||
Reference in New Issue
Block a user