feat: integrate pi rust lab runtime
This commit is contained in:
@@ -46,6 +46,12 @@ async function quickLoginIfNeeded(page) {
|
||||
]);
|
||||
}
|
||||
|
||||
async function approveVisiblePiDialog(page) {
|
||||
const dialog = page.locator("[data-page-ai-pi-lab-ui-dialog='confirm']");
|
||||
await dialog.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
||||
await page.locator("[data-page-ai-pi-lab-ui-submit]").click();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`\n🧪 Pi Lab browser smoke (base: ${BASE})\n`);
|
||||
let browserRoot = process.env.MNOTE_PI_LAB_BROWSER_ROOT || "/tmp/mnote-pi-lab-browser-smoke";
|
||||
@@ -59,6 +65,9 @@ async function main() {
|
||||
executablePath: CHROMIUM_EXECUTABLE || undefined,
|
||||
});
|
||||
const context = await browser.newContext({ viewport: { width: 1440, height: 960 } });
|
||||
await context.addInitScript(() => {
|
||||
window.__MNOTE_PI_LAB_TEST__ = true;
|
||||
});
|
||||
await addAuth(context);
|
||||
const page = await context.newPage();
|
||||
|
||||
@@ -114,10 +123,6 @@ async function main() {
|
||||
assert(drawerEvidence.toolCount >= 5, `expected MNote tool rail, got ${drawerEvidence.toolCount}`);
|
||||
console.log(" 4. Independent drawer, context strip and default model verified");
|
||||
|
||||
const startButton = page.locator("[data-page-ai-pi-lab-btn-start]");
|
||||
if (await startButton.isVisible().catch(() => false)) {
|
||||
await startButton.click();
|
||||
}
|
||||
await page.waitForFunction(() => document.querySelector("[data-page-ai-pi-lab-status-text]")?.textContent?.includes("ready"), null, {
|
||||
timeout: UI_TIMEOUT_MS,
|
||||
});
|
||||
@@ -126,6 +131,7 @@ async function main() {
|
||||
const startStatusData = await startStatusResp.json();
|
||||
const sessionId = startStatusData.sessionId || startStatusData.session?.sessionId;
|
||||
assert(sessionId, "UI start should create sessionId");
|
||||
const workspaceId = startStatusData.session?.workspaceId || startStatusData.workspaceId || undefined;
|
||||
const firstAllowedRoot = startStatusData.session?.allowedRootsSnapshot?.roots?.[0] || null;
|
||||
if (firstAllowedRoot?.rootPath) {
|
||||
browserRoot = String(firstAllowedRoot.rootPath);
|
||||
@@ -133,29 +139,87 @@ async function main() {
|
||||
fs.mkdirSync(browserRoot, { recursive: true });
|
||||
fs.writeFileSync(path.join(browserRoot, pagePath), "# Pi Lab browser smoke\n\nBrowser Original\n", "utf8");
|
||||
}
|
||||
const autoEditResp = await page.request.post(`${BASE}/api/page-ai/pi/start`, {
|
||||
data: {
|
||||
sessionId,
|
||||
rootUri,
|
||||
workspaceId,
|
||||
pagePath,
|
||||
pageTitle: "Pi Lab browser smoke",
|
||||
permissionMode: "auto_edit",
|
||||
},
|
||||
});
|
||||
assert(autoEditResp.ok(), `auto-edit mode start should return HTTP OK, got ${autoEditResp.status()}`);
|
||||
await page.waitForTimeout(800);
|
||||
await page.locator("[data-page-ai-pi-lab-input]").fill("Pi Lab browser smoke prompt");
|
||||
await page.waitForFunction(() => {
|
||||
const button = document.querySelector("[data-page-ai-pi-lab-btn-send]");
|
||||
return button && !button.disabled;
|
||||
}, null, { timeout: UI_TIMEOUT_MS });
|
||||
console.log(" 5. Runtime started and composer is interactive");
|
||||
console.log(" 5. Runtime auto-started and composer is interactive");
|
||||
|
||||
const deniedResp = await page.request.post(`${BASE}/api/page-ai/pi/tool-call`, {
|
||||
data: { sessionId, toolName: "mnote.local_file.read", params: { path: "/etc/hosts" } },
|
||||
});
|
||||
assert(deniedResp.ok(), `deny tool call should return HTTP OK, got ${deniedResp.status()}`);
|
||||
|
||||
await page.evaluate(() => {
|
||||
window.__mnotePiLabTest.emitRpcEvent({
|
||||
type: "extension_ui_request",
|
||||
id: "browser_smoke_approval",
|
||||
method: "confirm",
|
||||
title: "审批 Pi 工具调用",
|
||||
message: "Browser smoke approval request",
|
||||
mnoteApproval: {
|
||||
approvalId: "browser_smoke_approval",
|
||||
toolName: "mnote.local_file.patch",
|
||||
paramsHash: "browser-smoke",
|
||||
},
|
||||
});
|
||||
});
|
||||
const uiResponsePromise = page.waitForResponse(
|
||||
(res) => res.url().includes("/api/page-ai/pi/ui-response") && res.request().method() === "POST",
|
||||
{ timeout: UI_TIMEOUT_MS },
|
||||
);
|
||||
const approvalBox = await page.locator("[data-page-ai-pi-lab-ui-dialog='confirm']").boundingBox();
|
||||
const inputWrapBox = await page.locator(".wolai-page-ai-pi-lab-input-wrap").boundingBox();
|
||||
assert(
|
||||
approvalBox && inputWrapBox && approvalBox.y + approvalBox.height <= inputWrapBox.y + 1,
|
||||
"approval dialog should be mounted above the input and must not cover the composer",
|
||||
);
|
||||
await approveVisiblePiDialog(page);
|
||||
const uiResponse = await uiResponsePromise;
|
||||
assert(uiResponse.ok(), `UI response should return HTTP OK, got ${uiResponse.status()}`);
|
||||
const uiResponseBody = await uiResponse.json();
|
||||
assert.equal(uiResponseBody.ok, true, "Pi approval dialog should confirm through composer-local UI");
|
||||
console.log(" 5b. Composer-local approval dialog verified");
|
||||
|
||||
const receiptResp = await page.request.post(`${BASE}/api/page-ai/pi/tool-call`, {
|
||||
data: {
|
||||
sessionId,
|
||||
toolName: "mnote.tool_receipt.write",
|
||||
params: {
|
||||
diffSummary: "browser smoke synthetic diff",
|
||||
citations: [{ title: "LightRAG mock citation", source: "lightrag-mock" }],
|
||||
},
|
||||
},
|
||||
});
|
||||
assert(receiptResp.ok(), `receipt tool call should return HTTP OK, got ${receiptResp.status()}`);
|
||||
const patchResp = await page.request.post(`${BASE}/api/page-ai/pi/tool-call`, {
|
||||
data: {
|
||||
sessionId,
|
||||
toolName: "mnote.local_file.patch",
|
||||
params: {
|
||||
path: path.join(browserRoot, pagePath),
|
||||
rootUri,
|
||||
path: pagePath,
|
||||
operations: [{ op: "replace", old: "Browser Original", new: "Browser Patched" }],
|
||||
},
|
||||
},
|
||||
});
|
||||
assert(patchResp.ok(), `patch tool call should return HTTP OK, got ${patchResp.status()}`);
|
||||
const patchPayload = await patchResp.json();
|
||||
assert.equal(patchPayload.ok, true, `patch tool call should be allowed, got ${JSON.stringify(patchPayload)}`);
|
||||
assert(fs.readFileSync(path.join(browserRoot, pagePath), "utf8").includes("Browser Patched"), "browser smoke patch should update markdown file");
|
||||
const ragResp = await page.request.post(`${BASE}/api/page-ai/pi/tool-call`, {
|
||||
data: { sessionId, toolName: "mnote.knowledge_rag.query", params: { rootUri, query: "Pi Lab browser smoke", topK: 1 } },
|
||||
timeout: 8000,
|
||||
@@ -163,38 +227,35 @@ async function main() {
|
||||
if (!ragResp.ok()) {
|
||||
console.warn(` ! LightRAG direct tool call skipped in browser smoke: ${ragResp.status()}`);
|
||||
}
|
||||
const receiptResp = await page.request.post(`${BASE}/api/page-ai/pi/tool-call`, {
|
||||
data: {
|
||||
sessionId,
|
||||
toolName: "mnote.tool_receipt.write",
|
||||
params: {
|
||||
citations: [{ title: "LightRAG mock citation", source: "lightrag-mock" }],
|
||||
},
|
||||
},
|
||||
});
|
||||
assert(receiptResp.ok(), `receipt tool call should return HTTP OK, got ${receiptResp.status()}`);
|
||||
await page.waitForTimeout(800);
|
||||
await page.waitForFunction(() => {
|
||||
const text = document.querySelector("[data-page-ai-pi-lab-receipts]")?.textContent || "";
|
||||
return text.includes("denied") && text.includes("diff") && text.includes("mnote.tool_receipt.write");
|
||||
return (text.includes("denied") || text.includes("approval required"))
|
||||
&& text.includes("diff")
|
||||
&& text.includes("mnote.local_file.patch")
|
||||
&& text.includes("mnote.tool_receipt.write");
|
||||
}, null, { timeout: UI_TIMEOUT_MS });
|
||||
|
||||
await page.locator("[data-page-ai-pi-lab-input]").fill("Pi Lab browser smoke prompt");
|
||||
const replyMarker = `MNOTE_PI_BROWSER_OK_${Date.now()}`;
|
||||
await page.locator("[data-page-ai-pi-lab-input]").fill(`请只回复 ${replyMarker}`);
|
||||
await page.locator("[data-page-ai-pi-lab-btn-send]").click();
|
||||
await page.waitForFunction(() => {
|
||||
const root = document.querySelector('[data-page-ai-pi-lab="drawer"]');
|
||||
const text = root?.textContent || "";
|
||||
return text.includes("[Pi Lab mock] prompt accepted") && text.includes("LightRAG mock citation") && text.includes("patch");
|
||||
}, null, { timeout: UI_TIMEOUT_MS });
|
||||
await page.waitForFunction((marker) => {
|
||||
return Array.from(document.querySelectorAll('[data-page-ai-pi-lab-message-role="assistant"]'))
|
||||
.some((node) => (node.textContent || "").includes(marker));
|
||||
}, replyMarker, { timeout: Math.max(UI_TIMEOUT_MS, 45000) });
|
||||
await page.locator("[data-page-ai-pi-lab-btn-abort]").click();
|
||||
await page.waitForFunction(() => (document.querySelector("[data-page-ai-pi-lab-status-text]")?.textContent || "").includes("aborted"), null, {
|
||||
timeout: UI_TIMEOUT_MS,
|
||||
});
|
||||
const runtimeEvidence = await page.evaluate(() => {
|
||||
const runtimeEvidence = await page.evaluate((marker) => {
|
||||
const root = document.querySelector('[data-page-ai-pi-lab="drawer"]');
|
||||
const text = root?.textContent || "";
|
||||
const assistantText = Array.from(document.querySelectorAll('[data-page-ai-pi-lab-message-role="assistant"]'))
|
||||
.map((node) => node.textContent || "")
|
||||
.join("\n");
|
||||
return {
|
||||
hasPromptReply: text.includes("[Pi Lab mock] prompt accepted"),
|
||||
hasPromptSubmitted: text.includes(marker),
|
||||
hasPromptReply: assistantText.includes(marker),
|
||||
hasAborted: text.includes("aborted") || text.includes("已中止"),
|
||||
hasDeny: text.includes("denied"),
|
||||
hasReceipt: text.includes("mnote.local_file.patch") || text.includes("mnote.tool_receipt.write"),
|
||||
@@ -202,15 +263,16 @@ async function main() {
|
||||
hasDiff: text.includes("patch") || text.includes("diff"),
|
||||
changedFiles: document.querySelector("[data-page-ai-pi-lab-changed-files]")?.textContent || "",
|
||||
};
|
||||
});
|
||||
assert(runtimeEvidence.hasPromptReply, "Pi Lab should show streamed mock assistant reply");
|
||||
}, replyMarker);
|
||||
assert(runtimeEvidence.hasPromptSubmitted, "Pi Lab should show submitted prompt in the real Pi Rust run");
|
||||
assert(runtimeEvidence.hasPromptReply, "Pi Lab should show a real assistant reply marker");
|
||||
assert(runtimeEvidence.hasAborted, "Pi Lab should show abort state");
|
||||
assert(runtimeEvidence.hasDeny, "Pi Lab should show allowed-roots deny receipt");
|
||||
assert(runtimeEvidence.hasReceipt, "Pi Lab should show tool receipt");
|
||||
assert(runtimeEvidence.hasCitation, "Pi Lab should show LightRAG citation evidence");
|
||||
assert(runtimeEvidence.hasDiff, "Pi Lab should show diff/changed file evidence");
|
||||
assert.notEqual(runtimeEvidence.changedFiles, "0", "changed files chip should be non-zero");
|
||||
console.log(" 6. Stream, abort, deny, citation, receipt and diff evidence visible");
|
||||
console.log(" 6. Real Pi Rust send, abort, deny, citation, receipt and diff evidence visible");
|
||||
|
||||
const openHubEvidence = await page.evaluate(async () => {
|
||||
const api = window.__mnoteSidebarPageAiRuntime;
|
||||
|
||||
Reference in New Issue
Block a user