feat: Page AI Reasonix desktop session alignment, settings IA cleanup, knowledge RAG hardening
- ACP client/session manager: Reasonix desktop live session context - Hermes tools: knowledge_rag tool manifest and skill updates - Browser runtime: sidebar page AI permission/profile/render/session/tree modules - Routes: hermes_client, hermes_tools, knowledge_rag, web_shell - Scripts: reasonix ACP wrapper, LightRAG MCP, smoke tasks 159/558/559/561/562 - Skills: mnote-knowledge-rag and mnote-lightrag-bridge SKILL.md updates
This commit is contained in:
@@ -113,6 +113,17 @@ async function sendPrompt(page, prompt, expectedCompact) {
|
||||
return { runId, text };
|
||||
}
|
||||
|
||||
async function waitUntil(label, predicate, timeoutMs = UI_TIMEOUT_MS) {
|
||||
const start = Date.now();
|
||||
let last = null;
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
last = await predicate().catch((error) => error);
|
||||
if (last === true) return;
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
throw new Error(`${label}_timeout: ${String(last && last.message || last || '')}`);
|
||||
}
|
||||
|
||||
function sessionInfoForRuns(runIds) {
|
||||
const quoted = runIds.map(sqlQuote).join(",");
|
||||
return sqliteJson(`
|
||||
@@ -127,6 +138,15 @@ function sessionInfoForRuns(runIds) {
|
||||
}));
|
||||
}
|
||||
|
||||
function runtimeRunsForSession(sessionId) {
|
||||
return sqliteJson(`
|
||||
SELECT run_id AS runId, status
|
||||
FROM ai_runtime_runs
|
||||
WHERE session_id=${sqlQuote(sessionId)} AND run_id LIKE 'run_%'
|
||||
ORDER BY created_at ASC;
|
||||
`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||||
const suffix = Date.now().toString(36);
|
||||
@@ -166,16 +186,53 @@ async function main() {
|
||||
|
||||
const firstMarker = `TASK558_FIRST_${suffix}`.toUpperCase();
|
||||
const secondMarker = `TASK558_SECOND_${suffix}`.toUpperCase();
|
||||
const first = await sendPrompt(
|
||||
page,
|
||||
const beforeAssistantCount = await page.locator('[data-testid="wolai-page-ai-drawer"] .wolai-page-ai-message--assistant').count();
|
||||
await page.locator("[data-page-ai-input]").fill(
|
||||
`Reasonix 上下文连续性测试:请记住如果下一轮用户只说“可以”,你必须只回复 ${secondMarker}。本轮请只回复 ${firstMarker},不要解释。`,
|
||||
firstMarker,
|
||||
{ timeout: UI_TIMEOUT_MS },
|
||||
);
|
||||
const second = await sendPrompt(page, "可以", secondMarker);
|
||||
await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS });
|
||||
await page.waitForFunction(
|
||||
() => ["queued", "running", "tool_calling"].includes(document.documentElement.getAttribute("data-mnote-page-ai-run-status") || ""),
|
||||
null,
|
||||
{ timeout: UI_TIMEOUT_MS },
|
||||
);
|
||||
await page.locator("[data-page-ai-input]").fill("可以", { timeout: UI_TIMEOUT_MS });
|
||||
await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS });
|
||||
await page.waitForFunction(
|
||||
() => Boolean(document.querySelector('[data-page-ai-queue-item]')),
|
||||
null,
|
||||
{ timeout: UI_TIMEOUT_MS },
|
||||
);
|
||||
const queuedPreview = await page.locator('[data-page-ai-queue-item]').first().textContent({ timeout: UI_TIMEOUT_MS });
|
||||
assert(String(queuedPreview || '').includes("可以"), `queued preview 应包含第二轮短回复: ${queuedPreview}`);
|
||||
await waitUntil("reasonix_two_runs_started", async () => capturedRuns.length >= 2, Math.max(UI_TIMEOUT_MS, 120_000));
|
||||
await page.waitForFunction(
|
||||
([firstNeedle, secondNeedle]) => {
|
||||
const text = document.querySelector('[data-testid="wolai-page-ai-drawer"]')?.textContent || "";
|
||||
return text.includes(firstNeedle)
|
||||
&& text.includes(secondNeedle)
|
||||
&& ["completed", "failed", "aborted"].includes(document.documentElement.getAttribute("data-mnote-page-ai-run-status") || "")
|
||||
&& !document.querySelector('[data-page-ai-streaming="true"]');
|
||||
},
|
||||
[firstMarker, secondMarker],
|
||||
{ timeout: Math.max(UI_TIMEOUT_MS, 120_000) },
|
||||
);
|
||||
const assistantTexts = await page.evaluate((countBefore) => {
|
||||
return Array.from(document.querySelectorAll('[data-testid="wolai-page-ai-drawer"] .wolai-page-ai-message--assistant'))
|
||||
.slice(countBefore)
|
||||
.map((node) => node.querySelector(".wolai-page-ai-message-text")?.textContent || "");
|
||||
}, beforeAssistantCount);
|
||||
assert(assistantTexts.some((text) => normalize(text) === firstMarker), `第一轮回复缺失: ${JSON.stringify(assistantTexts)}`);
|
||||
assert(assistantTexts.some((text) => normalize(text) === secondMarker), `第二轮回复缺失: ${JSON.stringify(assistantTexts)}`);
|
||||
assert.equal(capturedRuns.length, 2, `应捕获两次 Page AI run,实际 ${capturedRuns.length}`);
|
||||
assert(capturedRuns.every((body) => body.acpRuntime === "reasonix" && body.profile === "reasonix"), "两次 run 都应走 Reasonix ACP");
|
||||
assert(capturedRuns[1].acpSessionId, "第二轮请求应携带上一轮 acpSessionId");
|
||||
|
||||
const runtimeRuns = runtimeRunsForSession(capturedRuns[0].sessionId);
|
||||
assert(runtimeRuns.length >= 2, `应有至少两条 runtime run: ${JSON.stringify(runtimeRuns)}`);
|
||||
const first = { runId: runtimeRuns[0].runId, text: firstMarker };
|
||||
const second = { runId: runtimeRuns[1].runId, text: secondMarker };
|
||||
const infos = sessionInfoForRuns([first.runId, second.runId]);
|
||||
assert.equal(infos.length, 2, `应有两条 session.info.updated,实际 ${infos.length}: ${JSON.stringify(infos)}`);
|
||||
const acpSessionIds = infos.map((info) => String(info.payload.acpSessionId || "")).filter(Boolean);
|
||||
@@ -194,6 +251,7 @@ async function main() {
|
||||
documentId,
|
||||
first,
|
||||
second,
|
||||
queuedPreview,
|
||||
acpSessionId: acpSessionIds[0],
|
||||
capturedRuns: capturedRuns.map((body) => ({ message: body.message, acpRuntime: body.acpRuntime, profile: body.profile, acpSessionId: body.acpSessionId || "" })),
|
||||
sessionInfo: infos,
|
||||
|
||||
Reference in New Issue
Block a user