189 lines
6.9 KiB
JavaScript
189 lines
6.9 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const vm = require("node:vm");
|
|
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
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");
|
|
const backendScopePath = path.join(openHubRoot, "smart-query-backend/app/core/mnote_scope.py");
|
|
const frontendEmbedPath = path.join(openHubRoot, "smart-query-frontend/src/mnoteEmbed.js");
|
|
const assistantMessagePath = path.join(openHubRoot, "smart-query-frontend/src/components/AssistantMessage.jsx");
|
|
const smartQueryPath = path.join(openHubRoot, "smart-query-frontend/src/pages/SmartQueryPage.jsx");
|
|
const mnoteRuntimePath = path.join(repoRoot, "rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js");
|
|
const checklistPath = path.join(repoRoot, "design/07-ai/process/7-68-openhub-weknora-mnote-deep-fusion-checklist-v1.md");
|
|
const outputDir = path.join(repoRoot, "tmp", "7-68-runtime");
|
|
const resultPath = path.join(outputDir, "openhub-weknora-tool-citation-bridge-smoke-result.json");
|
|
|
|
function read(filePath) {
|
|
return fs.readFileSync(filePath, "utf8");
|
|
}
|
|
|
|
function assertCheck(failures, name, passed, details = undefined) {
|
|
if (!passed) failures.push({ name, details });
|
|
}
|
|
|
|
function base64UrlJson(value) {
|
|
return Buffer.from(JSON.stringify(value), "utf8")
|
|
.toString("base64")
|
|
.replace(/\+/g, "-")
|
|
.replace(/\//g, "_")
|
|
.replace(/=+$/g, "");
|
|
}
|
|
|
|
function extractFunction(source, name) {
|
|
const start = source.indexOf(`export const ${name}`);
|
|
if (start < 0) throw new Error(`找不到 ${name}`);
|
|
const next = source.indexOf("\nexport const ", start + 1);
|
|
return source.slice(start, next > start ? next : undefined);
|
|
}
|
|
|
|
async function probeEmbedOpenReference(embedSource) {
|
|
const scope = {
|
|
workspaceScope: {
|
|
rootUri: "file:///tmp/mnote-openhub-task782",
|
|
workspaceId: "ws-task782",
|
|
pageResourceId: "page-task782",
|
|
},
|
|
};
|
|
const posted = [];
|
|
const sandbox = {
|
|
console,
|
|
TextDecoder,
|
|
URLSearchParams,
|
|
Uint8Array,
|
|
window: {
|
|
location: {
|
|
pathname: "/page-ai/openhub/ai",
|
|
search: `?scope=session-task782&mnoteScope=${base64UrlJson(scope)}`,
|
|
origin: "http://127.0.0.1:3000",
|
|
},
|
|
parent: {
|
|
postMessage: (message, origin) => posted.push({ message, origin }),
|
|
},
|
|
atob: (value) => Buffer.from(value, "base64").toString("binary"),
|
|
},
|
|
module: { exports: {} },
|
|
exports: {},
|
|
};
|
|
const transformed = embedSource
|
|
.slice(0, embedSource.indexOf("const compactObject"))
|
|
.replace(/import\.meta\.env\.VITE_API_BASE_URL/g, "undefined")
|
|
.replace(/\bexport const /g, "const ");
|
|
vm.runInNewContext(
|
|
`${transformed}\nmodule.exports = { postMNoteOpenReference };`,
|
|
sandbox,
|
|
{ filename: frontendEmbedPath }
|
|
);
|
|
const ok = sandbox.module.exports.postMNoteOpenReference({
|
|
schema: "openhub.weknora_citation_locator.v1",
|
|
citation: {
|
|
provider: "weknora",
|
|
sourceRootRelativePath: "knowledge-rag-fixtures-7-68/task782.md",
|
|
citationLabel: "task782.md",
|
|
},
|
|
});
|
|
return { ok, posted };
|
|
}
|
|
|
|
async function main() {
|
|
const failures = [];
|
|
const backendBridge = read(backendBridgePath);
|
|
const backendApi = read(backendApiPath);
|
|
const backendStream = read(backendStreamPath);
|
|
const backendScope = read(backendScopePath);
|
|
const frontendEmbed = read(frontendEmbedPath);
|
|
const assistantMessage = read(assistantMessagePath);
|
|
const smartQuery = read(smartQueryPath);
|
|
const mnoteRuntime = read(mnoteRuntimePath);
|
|
const checklist = read(checklistPath);
|
|
|
|
assertCheck(
|
|
failures,
|
|
"OpenHub backend exposes MNote WeKnora readonly facade endpoint",
|
|
backendApi.includes("/api/mnote/tools/call") &&
|
|
backendApi.includes("call_mnote_weknora_tool") &&
|
|
backendBridge.includes("MNOTE_TOOL_CALL_ENDPOINT = \"/api/mnote/tools/call\"") &&
|
|
!backendBridge.includes("MNOTE_TOOL_CALL_ENDPOINT = \"/api/hermes/tools/mnote/call\"")
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"backend tool call carries scope and does not expose WeKnora API key",
|
|
backendBridge.includes("capabilityScope") &&
|
|
backendBridge.includes("knowledge_rag.read") &&
|
|
backendBridge.includes("allowedRoots") &&
|
|
backendBridge.includes("mnote_cookie") &&
|
|
!backendBridge.includes("MNOTE_WEKNORA_API_KEY")
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"OpenHub stream auto-bridges KB queries through MNote WeKnora",
|
|
backendStream.includes("auto_search_for_question") &&
|
|
backendStream.includes("<mnote_weknora_tool_result>") &&
|
|
backendStream.includes("_push_mnote_weknora_tool_event") &&
|
|
backendStream.includes("mnote.weknora.search")
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"MNote session cookie is only forwarded as trusted server header",
|
|
backendScope.includes("X-MNote-Session-Cookie") &&
|
|
mnoteRuntime.includes("mnote:open-reference") &&
|
|
!frontendEmbed.includes("mnote_session=")
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"OpenHub frontend renders clickable WeKnora citations",
|
|
assistantMessage.includes("data-mnote-openhub-citation") &&
|
|
assistantMessage.includes("openhub.weknora_citation_locator.v1") &&
|
|
assistantMessage.includes("onOpenMNoteCitation") &&
|
|
smartQuery.includes("postMNoteOpenReference")
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"MNote host accepts citation open-reference postMessage bridge",
|
|
mnoteRuntime.includes("message.type !== 'mnote:open-file' && message.type !== 'mnote:open-reference'") &&
|
|
mnoteRuntime.includes("openhub-citation") &&
|
|
mnoteRuntime.includes("pageAiOpenOpencodeChangedFile")
|
|
);
|
|
|
|
const runtimeProbe = await probeEmbedOpenReference(frontendEmbed);
|
|
assertCheck(
|
|
failures,
|
|
"runtime postMNoteOpenReference posts mnote:open-reference",
|
|
runtimeProbe.ok &&
|
|
runtimeProbe.posted.length === 1 &&
|
|
runtimeProbe.posted[0].message.type === "mnote:open-reference" &&
|
|
runtimeProbe.posted[0].message.source === "openhub-citation" &&
|
|
runtimeProbe.posted[0].message.path.endsWith("task782.md"),
|
|
runtimeProbe
|
|
);
|
|
assertCheck(
|
|
failures,
|
|
"checklist has task782 completion note",
|
|
checklist.includes("task782-openhub-weknora-tool-citation-bridge-smoke.js")
|
|
);
|
|
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
const result = {
|
|
ok: failures.length === 0,
|
|
task: "task782-openhub-weknora-tool-citation-bridge-smoke",
|
|
runtimeProbe,
|
|
failures,
|
|
};
|
|
fs.writeFileSync(resultPath, `${JSON.stringify(result, null, 2)}\n`, "utf8");
|
|
if (failures.length) {
|
|
console.error(JSON.stringify(result, null, 2));
|
|
process.exit(1);
|
|
}
|
|
console.log(JSON.stringify(result, null, 2));
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|