#!/usr/bin/env node "use strict"; const assert = require("node:assert"); const fs = require("node:fs"); const path = require("node:path"); const { request } = require("playwright"); const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, ""); const ROOT_PATH = process.env.MNOTE_KNOWLEDGE_RAG_ROOT_PATH || "/mnt/Data1T/Mnote_data/users/mnote-e2e/workspaces/my-space"; const ROOT_URI = process.env.MNOTE_KNOWLEDGE_RAG_ROOT_URI || `file://${ROOT_PATH}`; const WORKSPACE_ID = process.env.MNOTE_KNOWLEDGE_RAG_WORKSPACE_ID || "local-ws:mnote-e2e:my-space"; const TASK772_RESULT_PATH = path.join(process.cwd(), "tmp", "task772-weknora-ingest-search-open-reference-e2e", "result.json"); const OUTPUT_DIR = path.join(process.cwd(), "tmp", "7-68-runtime"); const RESULT_PATH = path.join(OUTPUT_DIR, "weknora-section-context-smoke-result.json"); const UI_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_UI_TIMEOUT_MS || 30_000); function readTask772Result() { if (!fs.existsSync(TASK772_RESULT_PATH)) { throw new Error(`缺少 task772 结果,请先运行 node scripts/task772-weknora-ingest-search-open-reference-e2e.js: ${TASK772_RESULT_PATH}`); } const payload = JSON.parse(fs.readFileSync(TASK772_RESULT_PATH, "utf8")); assert.equal(payload.ok, true, `task772 结果不是 ok=true: ${JSON.stringify(payload, null, 2).slice(0, 1000)}`); const sourcePath = payload.sourcePath; const providerKnowledgeId = payload.providerKnowledgeId; const providerKnowledgeBaseId = payload.providerKnowledgeBaseId; const providerChunkId = payload.search?.providerIds?.chunkId || payload.openReference?.providerIds?.chunkId; assert(sourcePath, "task772 结果缺少 sourcePath"); assert(providerKnowledgeId, "task772 结果缺少 providerKnowledgeId"); assert(providerChunkId, "task772 结果缺少 provider chunkId"); return { sourcePath, providerKnowledgeId, providerKnowledgeBaseId, providerChunkId, marker: payload.marker, }; } async function signIn(context) { const response = await context.post(`${BASE_URL}/api/auth`, { data: { action: "auth:signIn", args: { provider: "password", params: { account: "mnote-e2e", password: process.env.MNOTE_E2E_PASSWORD || "MnoteE2E123!", flow: "signIn", }, }, }, timeout: UI_TIMEOUT_MS, }); assert(response.ok(), `登录失败: ${response.status()} ${await response.text()}`); } async function sectionContext(context, body) { const response = await context.post(`${BASE_URL}/api/knowledge-rag/section-context`, { data: { rootUri: ROOT_URI, workspaceId: WORKSPACE_ID, maxChars: 4000, ...body, }, headers: { "x-mnote-actor-id": "mnote-e2e", "x-mnote-actor-type": "user", "x-mnote-workspace-id": WORKSPACE_ID, accept: "application/json", }, timeout: UI_TIMEOUT_MS, }); const text = await response.text(); let payload = null; try { payload = JSON.parse(text); } catch { payload = { rawText: text }; } assert(response.ok(), `section-context HTTP ${response.status()}: ${text.slice(0, 1000)}`); return payload; } function assertProviderChunkPayload(payload, expected) { assert.equal(payload.provider, "weknora", `provider 应为 weknora: ${JSON.stringify(payload, null, 2).slice(0, 1200)}`); assert.equal(payload.sidecarRead, false, "WeKnora section-context 不应读取 LightRAG sidecar"); assert.equal(payload.providerChunkFetch?.attempted, true, "应尝试 WeKnora provider chunk fetch"); assert.equal(payload.providerChunkFetch?.ok, true, `provider chunk fetch 应成功: ${JSON.stringify(payload.providerChunkFetch, null, 2)}`); assert(Array.isArray(payload.blocks) && payload.blocks.length > 0, "section-context 应返回 provider chunk blocks"); assert(Array.isArray(payload.chunks) && payload.chunks.length > 0, "section-context 应返回 agent chunks"); assert(String(payload.text || "").includes(expected.marker), "section-context text 应包含 task772 marker"); const first = payload.blocks[0]; assert(first.providerChunk, `block 应保留 providerChunk metadata: ${JSON.stringify(first, null, 2).slice(0, 1000)}`); assert.equal(first.providerChunk.knowledgeId, expected.providerKnowledgeId, "providerChunk knowledgeId 应保留"); } async function main() { fs.mkdirSync(OUTPUT_DIR, { recursive: true }); const expected = readTask772Result(); const context = await request.newContext({ baseURL: BASE_URL }); let result = { ok: false, task: "task777-weknora-section-context-smoke", expected }; try { await signIn(context); const byChunk = await sectionContext(context, { sourcePath: expected.sourcePath, providerKnowledgeId: expected.providerKnowledgeId, providerKnowledgeBaseId: expected.providerKnowledgeBaseId, providerChunkId: expected.providerChunkId, }); assertProviderChunkPayload(byChunk, expected); const byKnowledge = await sectionContext(context, { sourcePath: expected.sourcePath, providerKnowledgeId: expected.providerKnowledgeId, providerKnowledgeBaseId: expected.providerKnowledgeBaseId, }); assertProviderChunkPayload(byKnowledge, expected); result = { ...result, ok: true, byChunk: { providerChunkFetch: byChunk.providerChunkFetch, providerChunkId: byChunk.providerChunkId, returnedBlocks: byChunk.blocks.length, returnedChunks: byChunk.chunks.length, degradedReason: byChunk.degradedReason, }, byKnowledge: { providerChunkFetch: byKnowledge.providerChunkFetch, returnedBlocks: byKnowledge.blocks.length, returnedChunks: byKnowledge.chunks.length, degradedReason: byKnowledge.degradedReason, }, }; } catch (error) { result = { ...result, ok: false, error: error instanceof Error ? error.stack || error.message : String(error), }; } finally { await context.dispose().catch(() => undefined); fs.writeFileSync(RESULT_PATH, `${JSON.stringify(result, null, 2)}\n`, "utf8"); } console.log(JSON.stringify(result, null, 2)); if (!result.ok) process.exit(1); } main().catch((error) => { console.error(error instanceof Error ? error.stack || error.message : String(error)); process.exit(1); });