feat: integrate pi rust lab runtime

This commit is contained in:
Agent Board
2026-07-10 10:54:34 +08:00
parent c8472bb898
commit 896c94b696
73 changed files with 19852 additions and 1152 deletions
+56 -10
View File
@@ -17,6 +17,10 @@ const files = {
webShell: path.join(repoRoot, 'rust/crates/mnote-web/src/routes/web_shell.rs'),
gateway: path.join(repoRoot, 'rust/crates/mnote-web/src/routes/gateway.rs'),
app: path.join(repoRoot, 'rust/crates/mnote-web/src/app.rs'),
mnotePiPackage: path.join(repoRoot, 'packages/pi-mnote/package.json'),
mnotePiExtension: path.join(repoRoot, 'packages/pi-mnote/extensions/mnote-bridge.ts'),
mnotePiMcpExtension: path.join(repoRoot, 'packages/pi-mnote/extensions/mnote-mcp/index.ts'),
mnotePiMcpClient: path.join(repoRoot, 'packages/pi-mnote/extensions/mnote-mcp/client.mjs'),
};
function readFile(p) {
@@ -29,6 +33,10 @@ const routesMod = readFile(files.mod);
const webShell = readFile(files.webShell);
const gateway = readFile(files.gateway);
const app = readFile(files.app);
const mnotePiPackage = readFile(files.mnotePiPackage);
const mnotePiExtension = readFile(files.mnotePiExtension);
const mnotePiMcpExtension = readFile(files.mnotePiMcpExtension);
const mnotePiMcpClient = readFile(files.mnotePiMcpClient);
const checks = [
// === Runtime JS: existence ===
@@ -69,15 +77,15 @@ const checks = [
['runtime renders tool calls', runtime.includes('toolCalls')],
['runtime renders citations', runtime.includes('citations')],
['runtime renders diff summary', runtime.includes('diffSummary')],
['runtime has start button', runtime.includes('btn-start')],
['runtime starts through drawer open/send instead of manual start button', !runtime.includes('btn-start')],
['runtime has send button', runtime.includes('btn-send')],
['runtime has abort button', runtime.includes('btn-abort')],
['runtime has clear button', runtime.includes('btn-clear')],
// === Runtime JS: Pi builtin disabled ===
['runtime handles disabledPiBuiltinTools from status/start', runtime.includes('disabledBuiltinTools') || runtime.includes('disabledPiBuiltinTools')],
['runtime has Pi builtin disabled UI indicator', runtime.includes('builtin-disabled')],
['runtime mentions bash/read/write/edit disabled', runtime.includes('bash') && runtime.includes('read') && runtime.includes('write') && runtime.includes('edit')],
// === Runtime JS: Pi builtins managed by MNote policy ===
['runtime handles managedPiBuiltinTools from status/start', runtime.includes('managedBuiltinTools') || runtime.includes('managedPiBuiltinTools')],
['runtime has Pi builtin managed UI indicator', runtime.includes('builtin-managed')],
['runtime mentions managed bash/read/write/edit tools', runtime.includes('bash') && runtime.includes('read') && runtime.includes('write') && runtime.includes('edit')],
// === Runtime JS: tool receipt ===
['runtime references receipt', runtime.includes('receipt') || runtime.includes('Receipt')],
@@ -92,6 +100,13 @@ const checks = [
['runtime has no OpenHub provider tab inside Pi Lab', !runtime.includes('data-page-ai-pi-lab-openhub-tab')],
['runtime has context strip chips', runtime.includes('data-page-ai-pi-lab-context-strip') && runtime.includes('data-page-ai-pi-lab-current-page') && runtime.includes('data-page-ai-pi-lab-allowed-roots') && runtime.includes('data-page-ai-pi-lab-lightrag')],
['runtime collapses secondary context/settings like OpenHub chrome', runtime.includes('<details class="wolai-page-ai-pi-lab-settings"') && runtime.includes('<details class="wolai-page-ai-pi-lab-context-strip"')],
['runtime settings gear opens AI management page', runtime.includes('data-page-ai-pi-lab-open-settings title="AI 管理"') && runtime.includes("window.location.assign('/user/ai#ai-admin-access')")],
['runtime keeps only meaningful top commandbar buttons', runtime.includes('data-page-ai-pi-lab-new') && runtime.includes('data-page-ai-pi-lab-history') && runtime.includes('data-page-ai-pi-lab-btn-clear') && runtime.includes('data-page-ai-pi-lab-open-settings')],
['runtime removes no-op top commandbar buttons', !runtime.includes('data-page-ai-pi-lab-open title=') && !runtime.includes('data-page-ai-pi-lab-toggle-artifacts') && !runtime.includes('data-page-ai-pi-lab-clock') && !runtime.includes('data-page-ai-pi-lab-notify')],
['runtime has OpenHub-style left history drawer markers', runtime.includes('data-page-ai-pi-lab-history-layer') && runtime.includes('wolai-page-ai-pi-lab-history-scrim') && runtime.includes('历史对话')],
['runtime history drawer supports refresh/delete/export/clear', runtime.includes('data-page-ai-pi-lab-history-refresh') && runtime.includes('data-page-ai-pi-lab-history-delete') && runtime.includes('data-page-ai-pi-lab-history-export') && runtime.includes('data-page-ai-pi-lab-history-clear')],
['runtime does not expose manual Pi runtime start button', !runtime.includes('data-page-ai-pi-lab-btn-start') && !runtime.includes('启动 Pi runtime') && !runtime.includes('预启动 Pi 会话')],
['runtime auto starts Pi session when drawer opens', runtime.includes('function showPiLab') && runtime.includes('checkStatus().then(function ()') && runtime.includes('return startRuntime();')],
['runtime has native model controls', runtime.includes('data-page-ai-pi-lab-model-provider') && runtime.includes('data-page-ai-pi-lab-model-id') && runtime.includes('data-page-ai-pi-lab-model-custom')],
['runtime collapses secondary right rail sections', runtime.includes('<details class="wolai-page-ai-pi-lab-rail-section"') && runtime.includes('data-page-ai-pi-lab-receipts-section')],
['runtime has diagnostics collapsed by default', runtime.includes('<details class="wolai-page-ai-pi-lab-diagnostics"') && !runtime.includes('<details class="wolai-page-ai-pi-lab-diagnostics" open')],
@@ -132,13 +147,15 @@ const checks = [
['route returns schema mnote.page_ai_pi.abort.v1', route.includes('mnote.page_ai_pi.abort.v1')],
['route has event schema PI_LAB_SCHEMA_EVENT', route.includes('PI_LAB_SCHEMA_EVENT')],
['route has receipt schema PI_LAB_SCHEMA_RECEIPT', route.includes('PI_LAB_SCHEMA_RECEIPT')],
['route disables Pi builtin tools', route.includes('disabledPiBuiltinTools') || route.includes('--no-builtin-tools')],
['route exposes managed Pi builtin tools', route.includes('managedPiBuiltinTools') && route.includes('PI_LAB_MANAGED_BUILTIN_TOOLS')],
['route has receipt storage policy', route.includes('receiptStorage') || route.includes('PI_LAB_SCHEMA_RECEIPT')],
['route has session dir policy', route.includes('managedPiSessionDirPolicy')],
['route enforces allowed roots', route.includes('active_allowed_roots') || route.includes('allowed_roots')],
['route blocks path escape', route.includes('path_escape') || route.includes('path_is_inside')],
['route has default model provider constant PI_LAB_DEFAULT_MODEL_PROVIDER=omniroute', route.includes('PI_LAB_DEFAULT_MODEL_PROVIDER') && route.includes('omniroute')],
['route has default model id constant PI_LAB_DEFAULT_MODEL_ID=freefirst', route.includes('PI_LAB_DEFAULT_MODEL_ID') && route.includes('freefirst')],
['route defaults to Pi Rust runtime implementation', route.includes('PI_LAB_RUNTIME_IMPL_RUST') && route.includes('"pi-rust"') && route.includes('MNOTE_PAGE_AI_PI_RUST_BIN')],
['route keeps TS Pi as explicit fallback only', route.includes('PI_LAB_RUNTIME_IMPL_TS') && route.includes('MNOTE_PAGE_AI_PI_TS_BIN')],
['route rejects arbitrary cookie_header auth bypass', route.includes('page_ai_pi_lab_invalid_session_cookie') && !route.includes('context.auth.cookie_header.is_some() {\n return Ok') ],
['route stores Pi Lab session owner id', route.includes('mnote_user_id') && route.includes('ensure_session_owner')],
['route validates session owner on send/abort/tool/events', route.includes('get_session_for_context')],
@@ -146,15 +163,43 @@ const checks = [
['route tool_receipt.write is not a silent no-op', route.includes('"requestedReceipt"') && route.includes('execute_tool 统一写入')],
['route has bridge token header and non-serialized session token', route.includes('HEADER_PI_LAB_BRIDGE_TOKEN') && route.includes('x-mnote-pi-lab-bridge-token') && route.includes('skip_serializing')],
['route generates bridge token from OS randomness', route.includes('generate_bridge_token') && route.includes('/dev/urandom') && !route.includes('bridge_token: generate_id("pi_bridge")')],
['route does not write bridge token literal into extension file', route.includes('MNOTE_PI_LAB_BRIDGE_TOKEN') && route.includes('process.env.MNOTE_PI_LAB_BRIDGE_TOKEN') && !route.includes('const BRIDGE_TOKEN = {bridge_token}')],
['route does not write obsolete private MCP bridge config', !route.includes('write_session_mcp_bridge_config') && !route.includes('mnote.pi.mcp-bridge.v1') && !route.includes('mnote-bridge.json')],
['route never writes bridge token into generated extension source', !route.includes('const BRIDGE_TOKEN = {bridge_token}')],
['route rejects bridge calls for non-running sessions', route.includes('page_ai_pi_lab_bridge_session_not_running') && route.includes('PiLabSessionStatus::RuntimeRunning | PiLabSessionStatus::TurnRunning')],
['route generates Pi extension with registered MNote tools', route.includes('ensure_session_tool_bridge_extension') && route.includes('pi.registerTool') && route.includes('mnote_current_page_read')],
['route starts Pi with explicit extension bridge', route.includes('--extension') && route.includes('mnoteToolBridgeExtension')],
['route loads official MNote Pi package extension', route.includes('mnote_pi_extension_path') && route.includes('packages/pi-mnote/extensions/mnote-bridge.ts')],
['route starts Pi with explicit MNote extension bridge', route.includes('--extension') && route.includes('mnotePiExtension')],
['route starts Pi with MNote tool allowlist', route.includes('--tools') && route.includes('mnoteToolNames') && route.includes('pi_lab_extension_tool_names')],
['route keeps Pi builtin tools disabled while loading extension', route.includes('--no-builtin-tools') && route.includes('disabledBuiltinTools')],
['route passes MNote tool manifest to extension env', route.includes('MNOTE_PI_BRIDGE_TOOLS') && route.includes('PI_MNOTE_BRIDGE_TOOLS') && route.includes('mnote_pi_tool_manifest')],
['route writes dynamic Pi Rust MNote context file', route.includes('mnote.pi.context.v1') && route.includes('PI_MNOTE_CONTEXT_FILE') && route.includes('write_pi_mnote_context_snapshot')],
['route injects dynamic Pi Rust input context', route.includes('MNOTE_PI_CONTEXT_V1') && route.includes('pi_mnote_input_context_prefix')],
['route disables Pi builtins by default unless external permission extension is opt-in', route.includes('pi_lab_enabled_builtin_tools') && route.includes('MNOTE_PAGE_AI_PI_ALLOW_EXTERNAL_EXTENSIONS') && route.includes('configuredPiExtensionSources') && route.includes('pi_lab_runtime_extension_sources')],
['route exposes Rust Pi runtime diagnostics', route.includes('hashline_edit') && route.includes('runtimeImplementation') && route.includes('runtimeBinary') && route.includes('runtimeAvailable') && route.includes('runtimeInstallHint') && route.includes('runtimeError')],
['runtime send path auto-starts Pi Rust instead of dead-end not-ready toast', runtime.includes('Pi Rust 正在启动,启动后自动发送') && runtime.includes('piLabStartPromise') && !runtime.includes('Pi 会话尚未就绪,请重新打开 Pi 面板或稍后重试')],
['route applies TTL and rate limits', route.includes('PI_LAB_SESSION_TTL_MS') && route.includes('PI_LAB_RATE_LIMITS') && route.includes('check_rate_limit')],
['route cleans expired session dirs and rate buckets', route.includes('fs::remove_dir_all') && route.includes('buckets.retain')],
// === MNote Pi package checks ===
['@mnote/pi package manifest exists', mnotePiPackage.includes('"name": "@mnote/pi"') && mnotePiPackage.includes('"pi"')],
['@mnote/pi package declares extension', mnotePiPackage.includes('./extensions/mnote-bridge.ts')],
['@mnote/pi package declares Pi Rust MCP extension', mnotePiPackage.includes('./extensions/mnote-mcp/index.ts')],
['@mnote/pi extension registers MNote tools', mnotePiExtension.includes('pi.registerTool') && mnotePiExtension.includes('mnote_current_page_read')],
['@mnote/pi extension keeps legacy MNote bridge API fallback', mnotePiExtension.includes('/api/page-ai/pi/tool-call-bridge') && mnotePiExtension.includes('x-mnote-pi-lab-bridge-token')],
['@mnote/pi extension uses Pi Rust native current-page read', mnotePiExtension.includes('pi-rust-native-fs') && mnotePiExtension.includes('PI_MNOTE_CONTEXT_FILE') && mnotePiExtension.includes('fs.readFileSync')],
['@mnote/pi extension consumes Pi Rust input context anywhere after skill expansion', mnotePiExtension.includes('pi.on?.("input"') && mnotePiExtension.includes('MNOTE_PI_CONTEXT_V1') && mnotePiExtension.includes('text.indexOf(CONTEXT_PREFIX)') && mnotePiExtension.includes('action: "transform"')],
['route preserves slash skill command before hidden input context', route.includes('pi_lab_command_message_for_session') && route.includes('message.starts_with("/skill:")') && route.includes('format!("{command}\\n{input_context_prefix}{effective_args}")')],
['@mnote/pi extension supports LightRAG tools', mnotePiExtension.includes('mnote_knowledge_rag_query') && mnotePiExtension.includes('mnote_knowledge_rag_section_context')],
['@mnote/pi MCP extension registers mcp tool', mnotePiMcpExtension.includes('registerTool') && mnotePiMcpExtension.includes('name: "mcp"')],
['@mnote/pi MCP extension uses official synchronous child_process bridge', mnotePiMcpExtension.includes('execFileSync') && mnotePiMcpExtension.includes('client.mjs') && mnotePiMcpExtension.includes('transport: "pi-rust-sync-client"') && !mnotePiMcpExtension.includes('/api/page-ai/pi/mcp-call-bridge') && !mnotePiMcpExtension.includes('fetch(')],
['@mnote/pi MCP extension does not depend on private session token config', !mnotePiMcpExtension.includes('mnote-bridge.json') && !mnotePiMcpExtension.includes('BRIDGE_TOKEN') && !mnotePiMcpExtension.includes('SESSION_ID')],
['@mnote/pi MCP client accepts inline sync request and adjacent session config', mnotePiMcpClient.includes('--request-json') && mnotePiMcpClient.includes('resolve(extDir, ".pi", "mcp.json")')],
['@mnote/pi MCP client implements protocol handshake and tool calls', mnotePiMcpClient.includes('"initialize"') && mnotePiMcpClient.includes('"tools/list"') && mnotePiMcpClient.includes('"tools/call"')],
['@mnote/pi MCP client supports stdio and streamable HTTP', mnotePiMcpClient.includes('spawn(') && mnotePiMcpClient.includes('text/event-stream')],
['route loads built-in Pi Rust MCP extension', route.includes('mnote_pi_mcp_extension_path') && route.includes('packages/pi-mnote/extensions/mnote-mcp/index.ts')],
['route stages per-session MCP config next to built-in extension', route.includes('stage_project_mcp_config_for_extension') && route.includes('extension_dir.join(".pi")') && route.includes('mcp.json')],
['route does not pass unsupported Pi Rust --mcp-config flag', !route.includes('.arg("--mcp-config")')],
['route does not globally disable Pi Rust extension capability policy', !route.includes('command.env("PI_EXTENSION_ALLOW_DANGEROUS"') && !route.includes('pi_tool_names.push("bash".into())')],
['route narrowly enables Pi Rust sync exec only for configured MCP sessions', route.includes('if pi_lab_mcp_enabled(&session)') && route.includes('command.env("PIJS_ALLOW_UNSAFE_SYNC_EXEC", "1")')],
// === mod.rs checks ===
['mod.rs declares page_ai_pi module', routesMod.includes('mod page_ai_pi;')],
['mod.rs mounts pi status route', routesMod.includes('/api/page-ai/pi/status')],
@@ -162,6 +207,7 @@ const checks = [
['mod.rs mounts pi send route', routesMod.includes('/api/page-ai/pi/send')],
['mod.rs mounts pi abort route', routesMod.includes('/api/page-ai/pi/abort')],
['mod.rs mounts pi events route', routesMod.includes('/api/page-ai/pi/events')],
['mod.rs mounts pi session history delete and clear routes', routesMod.includes('get(page_ai_pi::list_sessions).delete(page_ai_pi::clear_sessions)') && routesMod.includes('.delete(page_ai_pi::delete_session_history)')],
['mod.rs mounts pi tool-call route', routesMod.includes('/api/page-ai/pi/tool-call')],
['mod.rs mounts pi internal tool-call-bridge route', routesMod.includes('/api/page-ai/pi/tool-call-bridge')],
['mod.rs mounts pi bootstrap route (legacy)', routesMod.includes('/api/page-ai/pi/bootstrap')],