chore: 保存当前架构收口与 bug 修复快照

归档本轮 P0/P1 bug 修复、设计审查迁移、AI selection scope 收口与 stream contract 调整,并保留当前 05 主线迁移起点。
This commit is contained in:
lix-2026
2026-05-18 17:01:35 +08:00
parent 61ee4a38a2
commit a2cb1338c8
953 changed files with 14383 additions and 211845 deletions
@@ -16,6 +16,15 @@ const {
} = require("./tree-shell-smoke-helpers");
const OUT_DIR = path.join(process.cwd(), "tmp", "page-aggregate-refresh-persistence-smoke");
const CHROME_EXECUTABLE = process.env.PLAYWRIGHT_CHROME_EXECUTABLE || "";
async function callMnoteTool(requestContext, payload) {
return requestJson(requestContext, "/api/hermes/tools/mnote/call", {
method: "POST",
headers: { "x-mnote-actor-id": payload.actorId || "smoke-user" },
data: payload,
});
}
async function fetchPageAggregate(requestContext, workspaceId, documentId) {
const payload = await requestJson(
@@ -27,6 +36,37 @@ async function fetchPageAggregate(requestContext, workspaceId, documentId) {
return payload.result;
}
function pageSubtreeSummary(aggregate) {
const subtree = aggregate?.tree?.pageSubtree ?? null;
return {
rootNodeId: subtree?.rootNodeId ?? null,
outlineLength: Array.isArray(subtree?.outline) ? subtree.outline.length : null,
};
}
async function fetchAiDoc(requestContext, target, actorId, suffix, label) {
const response = await callMnoteTool(requestContext, {
toolName: "mnote.doc.fetch",
workspaceId: target.workspaceId,
documentId: target.documentId,
actorId,
sessionId: `sess_page_aggregate_ai_fetch_${suffix}`,
runId: `run_page_aggregate_ai_fetch_${suffix}_${label}`,
toolCallId: `call_page_aggregate_ai_fetch_${suffix}_${label}`,
traceId: `trace_page_aggregate_ai_fetch_${suffix}_${label}`,
capabilityScope: ["page.read"],
args: {
scope: "full",
detail: "with_ids",
maxBlocks: 20,
},
});
assert.equal(response.ok, true, `${label}: mnote.doc.fetch 应成功`);
assert.equal(response.result.schema, "mnote.page_ai_context.v1", `${label}: AI fetch schema 应稳定`);
assert(Array.isArray(response.result.blocks), `${label}: AI fetch 应返回 blocks`);
return response.result;
}
function findBlockWithText(aggregate, text) {
const blocks = aggregate?.body?.blockDocument?.blocks;
if (!Array.isArray(blocks)) return null;
@@ -289,7 +329,11 @@ async function main() {
const bodyText = `Page Aggregate refresh body ${suffix}`;
const evidencePath = path.join(OUT_DIR, `${suffix}.json`);
const screenshotPath = path.join(OUT_DIR, `${suffix}.png`);
const browser = await chromium.launch({ headless: true });
const browser = await chromium.launch({
headless: true,
...(CHROME_EXECUTABLE ? { executablePath: CHROME_EXECUTABLE } : {}),
args: ["--no-sandbox", "--disable-dev-shm-usage"],
});
const context = await browser.newContext({ viewport: { width: 1440, height: 960 } });
const page = await context.newPage();
const createdIds = [];
@@ -325,6 +369,20 @@ async function main() {
title,
bodyText,
);
const beforeReloadAiFetch = await fetchAiDoc(context.request, target, viewer.userId, suffix, "before_reload");
const beforeReloadAiBlock = beforeReloadAiFetch.blocks.find((block) => block.blockId === beforeReloadBlock.blockId);
assert.equal(beforeReloadAiFetch.revision, beforeReloadAggregate.body?.revision, "刷新前 AI fetch revision 应与 Page Aggregate 一致");
assert.equal(
beforeReloadAiFetch.conflictDetectionKey,
beforeReloadAggregate.body?.conflictDetectionKey,
"刷新前 AI fetch conflictDetectionKey 应与 Page Aggregate 一致",
);
assert.equal(beforeReloadAiBlock?.text, bodyText, "刷新前 AI fetch 应回读 Page Aggregate 正文块");
assert.equal(
beforeReloadAggregate.tree?.pageSubtree?.rootNodeId,
target.documentId,
"刷新前 Page Aggregate pageSubtree.rootNodeId 应等于 documentId",
);
await page.reload({ waitUntil: "commit", timeout: UI_TIMEOUT_MS });
await waitForPageTitleInput(page);
@@ -354,6 +412,20 @@ async function main() {
title,
bodyText,
);
const afterReloadAiFetch = await fetchAiDoc(context.request, target, viewer.userId, suffix, "after_reload");
const afterReloadAiBlock = afterReloadAiFetch.blocks.find((block) => block.blockId === afterReloadBlock.blockId);
assert.equal(afterReloadAiFetch.revision, afterReloadAggregate.body?.revision, "刷新后 AI fetch revision 应与 Page Aggregate 一致");
assert.equal(
afterReloadAiFetch.conflictDetectionKey,
afterReloadAggregate.body?.conflictDetectionKey,
"刷新后 AI fetch conflictDetectionKey 应与 Page Aggregate 一致",
);
assert.equal(afterReloadAiBlock?.text, bodyText, "刷新后 AI fetch 应回读 Page Aggregate 正文块");
assert.equal(
afterReloadAggregate.tree?.pageSubtree?.rootNodeId,
target.documentId,
"刷新后 Page Aggregate pageSubtree.rootNodeId 应等于 documentId",
);
await page.screenshot({ path: screenshotPath, fullPage: false });
const evidence = {
@@ -383,12 +455,23 @@ async function main() {
conflictDetectionKey: beforeReloadAggregate.body?.conflictDetectionKey ?? null,
pageOptions: beforeReloadAggregate.layout?.pageOptions ?? null,
blockProjectionVersion: beforeReloadAggregate.body?.blockProjectionVersion ?? null,
pageSubtree: pageSubtreeSummary(beforeReloadAggregate),
},
matchedBlock: {
blockId: beforeReloadBlock.blockId,
text: beforeReloadBlock.text,
revisionRef: beforeReloadBlock.revisionRef,
},
aiFetch: {
schema: beforeReloadAiFetch.schema,
revision: beforeReloadAiFetch.revision,
conflictDetectionKey: beforeReloadAiFetch.conflictDetectionKey,
matchedBlock: {
blockId: beforeReloadAiBlock?.blockId ?? null,
text: beforeReloadAiBlock?.text ?? null,
revisionRef: beforeReloadAiBlock?.revisionRef ?? null,
},
},
runtime: beforeReloadRuntime,
},
afterReload: {
@@ -398,12 +481,23 @@ async function main() {
conflictDetectionKey: afterReloadAggregate.body?.conflictDetectionKey ?? null,
pageOptions: afterReloadAggregate.layout?.pageOptions ?? null,
blockProjectionVersion: afterReloadAggregate.body?.blockProjectionVersion ?? null,
pageSubtree: pageSubtreeSummary(afterReloadAggregate),
},
matchedBlock: {
blockId: afterReloadBlock.blockId,
text: afterReloadBlock.text,
revisionRef: afterReloadBlock.revisionRef,
},
aiFetch: {
schema: afterReloadAiFetch.schema,
revision: afterReloadAiFetch.revision,
conflictDetectionKey: afterReloadAiFetch.conflictDetectionKey,
matchedBlock: {
blockId: afterReloadAiBlock?.blockId ?? null,
text: afterReloadAiBlock?.text ?? null,
revisionRef: afterReloadAiBlock?.revisionRef ?? null,
},
},
title: afterReloadTitle,
editorText: afterReloadEditorText,
runtime: afterReloadRuntime,