chore: checkpoint pi lab rust integration work
This commit is contained in:
@@ -23,7 +23,7 @@ const PAGE_DIR = `pi-input-controls-${STAMP}`;
|
||||
const PAGE_PATH = `${PAGE_DIR}/pi-input-controls-${STAMP}.md`;
|
||||
const ROOT_PAGE_PATH = `pi-input-controls-root-${STAMP}.md`;
|
||||
const MODEL_PROVIDER = process.env.MNOTE_PI_INPUT_MODEL_PROVIDER || "omniroute";
|
||||
const MODEL_ID = process.env.MNOTE_PI_INPUT_MODEL_ID || "freefirst";
|
||||
const MODEL_ID = process.env.MNOTE_PI_INPUT_MODEL_ID || "gpt-5.4-mini";
|
||||
const CHROMIUM_EXECUTABLE = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE
|
||||
|| (fs.existsSync("/usr/bin/chromium-browser") ? "/usr/bin/chromium-browser" : "")
|
||||
|| (fs.existsSync("/usr/bin/chromium") ? "/usr/bin/chromium" : "")
|
||||
@@ -238,6 +238,13 @@ async function openActionMenu(page) {
|
||||
return menu;
|
||||
}
|
||||
|
||||
async function waitForSendEnabled(page) {
|
||||
await page.waitForFunction(() => {
|
||||
const button = document.querySelector("[data-page-ai-pi-lab-btn-send]");
|
||||
return button && !button.disabled;
|
||||
}, null, { timeout: TIMEOUT });
|
||||
}
|
||||
|
||||
async function sendViaMenuAndCapture(page, action, text) {
|
||||
await page.locator("[data-page-ai-pi-lab-input]").fill(text);
|
||||
await emit(page, { type: "message_update", assistantMessageEvent: { type: "text_start" } });
|
||||
@@ -266,6 +273,12 @@ async function main() {
|
||||
page.on("console", (message) => {
|
||||
if (["error", "warning"].includes(message.type())) consoleMessages.push(`${message.type()}: ${message.text()}`);
|
||||
});
|
||||
page.on("response", (response) => {
|
||||
if (response.status() >= 400) {
|
||||
const postData = response.request().postData();
|
||||
consoleMessages.push(`response: ${response.status()} ${response.url()}${postData ? ` body=${postData}` : ""}`);
|
||||
}
|
||||
});
|
||||
page.on("pageerror", (error) => consoleMessages.push(`pageerror: ${error.message}`));
|
||||
|
||||
const result = {
|
||||
@@ -289,9 +302,80 @@ async function main() {
|
||||
thinkingLevel: session.thinkingLevel,
|
||||
};
|
||||
await openPiUi(page);
|
||||
await page.waitForFunction(() => document.querySelector("[data-page-ai-pi-lab-thinking-label]")?.textContent?.includes("思考 高"), null, {
|
||||
timeout: TIMEOUT,
|
||||
});
|
||||
|
||||
result.checks.thinkingInitialValue = await page.locator("[data-page-ai-pi-lab-thinking]").inputValue();
|
||||
assert.equal(result.checks.thinkingInitialValue, "high", "thinking selector should reflect current session");
|
||||
const contextProbe = await page.evaluate(({ rootUri, workspaceId, pagePath }) => {
|
||||
const originalRuntime = window.__mnoteDocumentPaneRuntime;
|
||||
const readContext = (activeEditor) => {
|
||||
window.__mnoteDocumentPaneRuntime = {
|
||||
getOpenEditorsSnapshot() {
|
||||
return { activeEditor };
|
||||
},
|
||||
};
|
||||
return window.__mnotePiLabTest.getCurrentContext();
|
||||
};
|
||||
const directory = readContext({
|
||||
documentId: "local-folder:.opencode",
|
||||
workspacePath: {
|
||||
documentId: "local-folder:.opencode",
|
||||
relativePath: ".opencode",
|
||||
resourceKind: "directory",
|
||||
rootUri,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
const markdown = readContext({
|
||||
documentId: `local-md:${pagePath}`,
|
||||
workspacePath: {
|
||||
documentId: `local-md:${pagePath}`,
|
||||
relativePath: pagePath,
|
||||
resourceKind: "page",
|
||||
rootUri,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
if (originalRuntime === undefined) delete window.__mnoteDocumentPaneRuntime;
|
||||
else window.__mnoteDocumentPaneRuntime = originalRuntime;
|
||||
return { directory, markdown };
|
||||
}, { rootUri: ROOT_URI, workspaceId: WORKSPACE_ID, pagePath: PAGE_PATH });
|
||||
result.checks.standaloneDirectoryPagePath = contextProbe.directory.pagePath;
|
||||
result.checks.standaloneMarkdownPagePath = contextProbe.markdown.pagePath;
|
||||
assert.equal(contextProbe.directory.pagePath, "", "standalone Pi page must not attach a directory as current page");
|
||||
assert.equal(contextProbe.markdown.pagePath, PAGE_PATH, "standalone Pi page should follow the active Markdown page");
|
||||
|
||||
const toolOnlyId = `tool-only-${STAMP}`;
|
||||
await emit(page, {
|
||||
type: "tool_execution_start",
|
||||
toolCallId: toolOnlyId,
|
||||
toolName: "todo",
|
||||
args: {},
|
||||
});
|
||||
await emit(page, {
|
||||
type: "tool_execution_end",
|
||||
toolCallId: toolOnlyId,
|
||||
toolName: "todo",
|
||||
result: { content: [{ type: "text", text: "No todos" }] },
|
||||
isError: false,
|
||||
});
|
||||
await emit(page, {
|
||||
type: "message_end",
|
||||
message: { role: "assistant", content: [], stopReason: "stop" },
|
||||
});
|
||||
await emit(page, {
|
||||
type: "agent_end",
|
||||
messages: [{ role: "assistant", content: [], stopReason: "stop" }],
|
||||
});
|
||||
const toolOnlyReply = page.locator('[data-page-ai-pi-lab-message-role="assistant"]').last();
|
||||
await toolOnlyReply.waitFor({ state: "visible", timeout: TIMEOUT });
|
||||
result.checks.toolOnlyReply = ((await toolOnlyReply.textContent()) || "").trim();
|
||||
result.checks.emptyReplyErrorCount = await page.getByText("Pi runtime 返回了空回复", { exact: false }).count();
|
||||
assert.match(result.checks.toolOnlyReply, /todo(完成)/, "tool-only turn should finish with a visible tool summary");
|
||||
assert.equal(result.checks.emptyReplyErrorCount, 0, "message_end and agent_end must not duplicate an empty-reply error");
|
||||
|
||||
result.checks.thinkingInitialLabel = (await page.locator("[data-page-ai-pi-lab-thinking-label]").textContent() || "").trim();
|
||||
assert(result.checks.thinkingInitialLabel.includes("思考 高"), "thinking label should reflect current session: " + result.checks.thinkingInitialLabel);
|
||||
result.checks.permissionLabel = (await page.locator("[data-page-ai-pi-lab-permission-label]").textContent() || "").trim();
|
||||
assert(/确认|审批|受限|自动/.test(result.checks.permissionLabel), `permission label missing: ${result.checks.permissionLabel}`);
|
||||
await page.locator("[data-page-ai-pi-lab-permission]").click();
|
||||
@@ -300,8 +384,15 @@ async function main() {
|
||||
result.checks.permissionModes = await page.locator("[data-page-ai-pi-lab-permission-mode]").evaluateAll((nodes) => nodes.map((node) => node.getAttribute("data-page-ai-pi-lab-permission-mode")));
|
||||
assert.deepEqual(result.checks.permissionModes, ["confirm", "auto_edit", "plan", "full_access"]);
|
||||
const modeStartRequestPromise = page.waitForRequest(
|
||||
(request) => request.url().includes("/api/page-ai/pi/start") && request.method() === "POST",
|
||||
{ timeout: TIMEOUT },
|
||||
(request) => {
|
||||
if (!request.url().includes("/api/page-ai/pi/start") || request.method() !== "POST") return false;
|
||||
try {
|
||||
return request.postDataJSON()?.permissionMode === "auto_edit";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ timeout: Math.min(TIMEOUT, 30000) },
|
||||
);
|
||||
await page.locator('[data-page-ai-pi-lab-permission-mode="auto_edit"]').click();
|
||||
const modeStartBody = (await modeStartRequestPromise).postDataJSON();
|
||||
@@ -319,8 +410,15 @@ async function main() {
|
||||
await page.locator("[data-page-ai-pi-lab-permission]").click();
|
||||
await page.locator("[data-page-ai-pi-lab-permission-menu]").waitFor({ state: "visible", timeout: TIMEOUT });
|
||||
const fullAccessStartRequestPromise = page.waitForRequest(
|
||||
(request) => request.url().includes("/api/page-ai/pi/start") && request.method() === "POST",
|
||||
{ timeout: TIMEOUT },
|
||||
(request) => {
|
||||
if (!request.url().includes("/api/page-ai/pi/start") || request.method() !== "POST") return false;
|
||||
try {
|
||||
return request.postDataJSON()?.permissionMode === "full_access";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ timeout: Math.min(TIMEOUT, 30000) },
|
||||
);
|
||||
await page.locator('[data-page-ai-pi-lab-permission-mode="full_access"]').click();
|
||||
const fullAccessStartBody = (await fullAccessStartRequestPromise).postDataJSON();
|
||||
@@ -433,15 +531,20 @@ async function main() {
|
||||
const historyStartRequestPromise = page.waitForRequest(
|
||||
(request) => request.url().includes("/api/page-ai/pi/start") && request.method() === "POST",
|
||||
{ timeout: TIMEOUT },
|
||||
);
|
||||
).catch((error) => error);
|
||||
const historySendRequestPromise = page.waitForRequest(
|
||||
(request) => request.url().includes("/api/page-ai/pi/send") && request.method() === "POST",
|
||||
{ timeout: TIMEOUT },
|
||||
);
|
||||
).catch((error) => error);
|
||||
await page.locator("[data-page-ai-pi-lab-input]").fill("history continue input controls smoke");
|
||||
await waitForSendEnabled(page);
|
||||
await page.locator("[data-page-ai-pi-lab-btn-send]").click();
|
||||
const historyStartBody = (await historyStartRequestPromise).postDataJSON();
|
||||
const historySendBody = (await historySendRequestPromise).postDataJSON();
|
||||
const historyStartRequest = await historyStartRequestPromise;
|
||||
const historySendRequest = await historySendRequestPromise;
|
||||
if (historyStartRequest instanceof Error) throw historyStartRequest;
|
||||
if (historySendRequest instanceof Error) throw historySendRequest;
|
||||
const historyStartBody = historyStartRequest.postDataJSON();
|
||||
const historySendBody = historySendRequest.postDataJSON();
|
||||
result.checks.historyContinueStartSessionId = historyStartBody.sessionId;
|
||||
result.checks.historyContinueSendSessionId = historySendBody.sessionId;
|
||||
result.checks.historyContinueMessage = historySendBody.message;
|
||||
@@ -452,7 +555,9 @@ async function main() {
|
||||
result.screenshots.historySessionContinues = path.join(OUT, "03-history-session-continues.png");
|
||||
|
||||
await page.locator("[data-page-ai-pi-lab-new]").click();
|
||||
await page.locator("[data-page-ai-pi-lab-thinking]").selectOption("xhigh");
|
||||
await page.locator("[data-page-ai-pi-lab-thinking-menu-toggle]").click();
|
||||
await page.locator("[data-page-ai-pi-lab-thinking-menu]").waitFor({ state: "visible", timeout: TIMEOUT });
|
||||
await page.locator('[data-page-ai-pi-lab-thinking-option="xhigh"]').click();
|
||||
const startRequestPromise = page.waitForRequest(
|
||||
(request) => request.url().includes("/api/page-ai/pi/start") && request.method() === "POST",
|
||||
{ timeout: TIMEOUT },
|
||||
@@ -463,11 +568,29 @@ async function main() {
|
||||
const startBody = startRequest.postDataJSON();
|
||||
result.checks.startThinkingLevel = startBody.thinkingLevel;
|
||||
assert.equal(startBody.thinkingLevel, "xhigh", "thinking selector should be sent on start");
|
||||
await requestJson(page, "/api/page-ai/pi/abort", {
|
||||
method: "POST",
|
||||
data: { sessionId: startBody.sessionId || session.sessionId },
|
||||
}).catch(() => null);
|
||||
await page.waitForFunction(() => {
|
||||
const status = document.querySelector("[data-page-ai-pi-lab-status-text]")?.textContent || "";
|
||||
return !/starting|streaming/.test(status);
|
||||
}, null, { timeout: TIMEOUT }).catch(() => null);
|
||||
await startSession(page, session.sessionId, "high");
|
||||
await openPiUi(page);
|
||||
await clickQuickAndAssertActive(page, "read-page", true, "send current-page context on after new conversation");
|
||||
await clickQuickAndAssertActive(page, "current-folder", true, "send current-folder context on after new conversation");
|
||||
await clickQuickAndAssertActive(page, "selection", true, "send selection context on after new conversation");
|
||||
await clickQuickAndAssertActive(page, "rag", true, "send LightRAG context on after new conversation");
|
||||
|
||||
const sendRequestPromise = page.waitForRequest(
|
||||
const sendRequestPromise = page.waitForRequest(
|
||||
(request) => request.url().includes("/api/page-ai/pi/send") && request.method() === "POST",
|
||||
{ timeout: TIMEOUT },
|
||||
);
|
||||
const sendResponsePromise = page.waitForResponse(
|
||||
(response) => response.url().includes("/api/page-ai/pi/send") && response.request().method() === "POST",
|
||||
{ timeout: TIMEOUT },
|
||||
);
|
||||
await page.locator("[data-page-ai-pi-lab-input]").fill("send button input controls smoke");
|
||||
await page.locator("[data-page-ai-pi-lab-btn-send]").click();
|
||||
const sendRequest = await sendRequestPromise;
|
||||
@@ -486,6 +609,15 @@ async function main() {
|
||||
assert.equal(sendBody.selectedContext.currentPage.pagePath, PAGE_PATH, "selectedContext should include current page address");
|
||||
assert.equal(sendBody.selectedContext.currentFolder.folderPath, PAGE_DIR, "selectedContext should include current folder address");
|
||||
assert.equal(sendBody.selectedContext.lightrag.enabled, true, "selectedContext should include LightRAG toggle");
|
||||
const sendResponse = await sendResponsePromise;
|
||||
const sendResponseText = await sendResponse.text().catch(() => "");
|
||||
result.checks.sendButtonResponseStatus = sendResponse.status();
|
||||
result.checks.sendButtonResponseBody = sendResponseText.slice(0, 500);
|
||||
assert(sendResponse.ok(), `send button request should succeed: ${sendResponse.status()} ${sendResponseText.slice(0, 500)}`);
|
||||
await requestJson(page, "/api/page-ai/pi/abort", {
|
||||
method: "POST",
|
||||
data: { sessionId: sendBody.sessionId || session.sessionId },
|
||||
}).catch(() => null);
|
||||
|
||||
await openActionMenu(page);
|
||||
await Promise.all([
|
||||
|
||||
Reference in New Issue
Block a user