diff --git a/bugs/07-ai/done/7-65-page-ai-pi-rust-diagnostics-ui-checklist-v1.md b/bugs/07-ai/done/7-65-page-ai-pi-rust-diagnostics-ui-checklist-v1.md new file mode 100644 index 00000000..31d5ec8e --- /dev/null +++ b/bugs/07-ai/done/7-65-page-ai-pi-rust-diagnostics-ui-checklist-v1.md @@ -0,0 +1,25 @@ +# 7-65 Page AI Pi Rust diagnostics UI checklist + +## 背景 + +- P2 取舍里确认:`--extension-policy` / `--repair-policy` / session durability / request timeout / max tool iterations / hide cwd 等高级 runtime 策略可以先不做普通用户设置项,但必须在诊断里暴露 effective policy。 +- Pi Rust 官方 `doctor` / `context-preview` / `list` / `search` / `info` / `update-index` 不属于对话主链 bug,但适合进入 Pi Lab 折叠诊断和 AI 管理页。 +- MNote 只做宿主、授权、文件地址和受控诊断入口,不用 MNote 工具替代 Pi Rust 官方能力。 + +## Checklist + +- [x] Pi Lab 折叠诊断区接入 `/api/page-ai/pi/diagnostics`,提供 `doctor` / `context-preview` / `list` / `search` / `info` / `update-index` 受控入口。 +- [x] Pi Lab 诊断状态显示 effective `advancedRuntime` 摘要,覆盖 extension policy、repair policy、session durability、request timeout、max tool iterations、hide cwd。 +- [x] AI 管理页 Pi Rust 服务卡展示 effective `advancedRuntime` 摘要与 JSON 明细。 +- [x] AI 管理页 Pi Rust 服务卡提供同一组官方诊断按钮,复用后端受控 diagnostics wrapper,不透传 raw args。 +- [x] 静态 smoke 覆盖 Pi Lab runtime 的 diagnostics API、按钮、输出区域和 advanced runtime summary。 +- [x] 静态 smoke 覆盖 AI 管理页 diagnostics 卡片、按钮、路由和 effective advanced runtime 展示。 + +## 验收命令 + +```bash +node scripts/task-pi-lab-static-smoke.js +node scripts/task-ai-management-control-plane-static-smoke.js +cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_pi -- --nocapture +cargo test --manifest-path rust/Cargo.toml -p mnote-web ai_admin -- --nocapture +``` diff --git a/rust/crates/mnote-web/browser/sidebar-page-ai-pi-lab-runtime.js b/rust/crates/mnote-web/browser/sidebar-page-ai-pi-lab-runtime.js index 028da99f..948bd343 100644 --- a/rust/crates/mnote-web/browser/sidebar-page-ai-pi-lab-runtime.js +++ b/rust/crates/mnote-web/browser/sidebar-page-ai-pi-lab-runtime.js @@ -39,6 +39,7 @@ COMPACT: '/api/page-ai/pi/compact', CONFIGURE: '/api/page-ai/pi/configure', QUEUE_CONFIG: '/api/page-ai/pi/queue-config', + DIAGNOSTICS: '/api/page-ai/pi/diagnostics', EFFECTIVE: '/api/ai-settings/effective', }; @@ -81,6 +82,7 @@ runtimeInstallHint: '', runtimeError: '', runtimePid: null, + advancedRuntime: {}, warmupRunning: false, warmupSessionId: null, managedBuiltinTools: [], @@ -584,6 +586,7 @@ '.wolai-page-ai-pi-lab-diagnostics{display:none;border-top:1px solid #e6e2da;background:#fff;flex:0 0 auto}' + '.wolai-page-ai-pi-lab-drawer[data-config-open="true"] .wolai-page-ai-pi-lab-diagnostics{display:block}' + '.wolai-page-ai-pi-lab-diagnostics>summary{cursor:pointer;padding:8px 12px;font-size:11px;color:#6f6a60}' + + '.wolai-page-ai-pi-lab-diagnostic-actions{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:0 12px 8px}.wolai-page-ai-pi-lab-diagnostic-actions button{height:26px;border:1px solid #ddd8cf;border-radius:7px;background:#fff;color:#42403a;padding:0 8px;font-size:11px;cursor:pointer}.wolai-page-ai-pi-lab-diagnostic-actions button:hover{background:#f8f7f3}.wolai-page-ai-pi-lab-diagnostic-actions input{height:26px;min-width:120px;border:1px solid #ddd8cf;border-radius:7px;background:#fff;color:#42403a;padding:0 8px;font-size:11px;outline:none}.wolai-page-ai-pi-lab-diagnostic-output[hidden]{display:none!important}.wolai-page-ai-pi-lab-diagnostic-output{border-top:1px solid #eee9e0;background:#faf9f6}' + '.wolai-page-ai-pi-lab-diagnostics pre{margin:0;padding:0 12px 10px;white-space:pre-wrap;word-break:break-word;font:11px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;color:#5d594f}' + '@media (max-width: 760px){.wolai-page-ai-pi-lab-drawer{left:10px;right:10px;top:58px;bottom:10px;width:auto}.wolai-page-ai-pi-lab-history-panel{width:min(320px,calc(100vw - 34px))}.wolai-page-ai-pi-lab-rail{left:14px;right:14px;top:auto;bottom:132px;width:auto;max-height:min(360px,calc(100% - 164px))}.wolai-page-ai-pi-lab-config{grid-template-columns:1fr}}' + '@media (max-width: 520px){.wolai-page-ai-pi-lab-topbar{height:54px;padding:0 14px}.wolai-page-ai-pi-lab-title strong{font-size:21px}.wolai-page-ai-pi-lab-commandbar{height:50px;margin:0 14px 12px;gap:6px;overflow:hidden}.wolai-page-ai-pi-lab-history-panel{top:116px;left:12px;right:12px;max-height:min(300px,calc(100% - 300px))}.wolai-page-ai-pi-lab-history-row{grid-template-columns:1fr}.wolai-page-ai-pi-lab-history-actions{justify-content:flex-start}.wolai-page-ai-pi-lab-input{min-height:88px;padding:16px 58px 10px 18px;font-size:16px}.wolai-page-ai-pi-lab-modebar{min-height:48px;flex-wrap:nowrap;gap:6px;padding:6px 10px}.wolai-page-ai-pi-lab-model-control-wrap{flex:1 1 130px;min-width:92px;max-width:none}.wolai-page-ai-pi-lab-toast-stack{left:14px;right:14px;bottom:154px}.wolai-page-ai-pi-lab-toast{width:100%}}'; @@ -2018,7 +2021,17 @@ '' + '
' + '诊断' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + '
' +
+          '' +
         '
' + ''; } @@ -2714,6 +2727,7 @@ lines.push('providerSession=' + (piLabState.providerSessionId || 'none')); lines.push('model=' + currentModelLabel()); lines.push('runtime=' + currentRuntimeLabel()); + lines.push('advancedRuntime=' + advancedRuntimeSummary(piLabState.advancedRuntime)); lines.push('managedBuiltinTools=' + ((Array.isArray(piLabState.managedBuiltinTools) && piLabState.managedBuiltinTools.length) ? piLabState.managedBuiltinTools.join('/') : 'pending')); if (Array.isArray(piLabState.piExtensionSources) && piLabState.piExtensionSources.length) { lines.push('piExtensions=' + piLabState.piExtensionSources.join(',')); @@ -2723,6 +2737,29 @@ pre.textContent = lines.join('\n'); } + function advancedRuntimeSummary(config) { + config = config || {}; + var pairs = [ + ['extensionPolicy', config.extensionPolicy], + ['repairPolicy', config.repairPolicy], + ['sessionDurability', config.sessionDurability], + ['requestTimeoutSecs', config.requestTimeoutSecs], + ['maxToolIterations', config.maxToolIterations], + ['hideCwdInPrompt', config.hideCwdInPrompt === true ? 'true' : 'false'], + ].filter(function (item) { return item[1] !== undefined && item[1] !== null && item[1] !== ''; }); + return pairs.length ? pairs.map(function (item) { return item[0] + '=' + item[1]; }).join(',') : 'default'; + } + + function setDiagnosticOutput(payload) { + if (!piLabPanelEl) return; + var output = piLabPanelEl.querySelector('[data-page-ai-pi-lab-diagnostic-output]'); + var details = piLabPanelEl.querySelector('[data-page-ai-pi-lab-diagnostics]'); + if (!output) return; + if (details) details.open = true; + output.hidden = false; + output.textContent = typeof payload === 'string' ? payload : JSON.stringify(payload || {}, null, 2); + } + function updatePiFloatingLayerPosition(layer) { if (!layer || !piLabPanelEl) return; var composer = piLabPanelEl.querySelector('.wolai-page-ai-pi-lab-composer'); @@ -3479,6 +3516,55 @@ }); } + function runPiDiagnostic(command) { + var input = piLabPanelEl && piLabPanelEl.querySelector('[data-page-ai-pi-lab-diagnostic-query]'); + var keyword = input ? String(input.value || '').trim() : ''; + var params = {}; + if (command === 'doctor') params.format = 'json'; + if (command === 'context-preview') { + params.format = 'json'; + params.query = [keyword || 'MNote Pi Rust diagnostics']; + } + if (command === 'search') { + if (!keyword) { + showPiToast('请输入 search keyword', 'warning'); + return Promise.resolve(); + } + params.query = keyword; + params.limit = 10; + } + if (command === 'info') { + if (!keyword) { + showPiToast('请输入 info name/id', 'warning'); + return Promise.resolve(); + } + params.name = keyword; + } + updateDiagnostics('Pi diagnostics: ' + command); + setDiagnosticOutput('Running ' + command + '...'); + return fetch(API.DIAGNOSTICS, { + method: 'POST', + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + command: command, + params: params, + rootUri: piLabState.session && piLabState.session.rootUri || undefined, + workspaceId: piLabState.session && piLabState.session.workspaceId || undefined, + }), + }).then(function (response) { + return responseJsonOrError(response, 'Diagnostics failed'); + }).then(function (payload) { + setDiagnosticOutput(payload); + updateDiagnostics('Pi diagnostics done: ' + command); + return payload; + }).catch(function (error) { + setDiagnosticOutput({ ok: false, command: command, error: error.message || String(error), payload: error.payload || null }); + updateDiagnostics('Pi diagnostics failed: ' + (error.message || String(error))); + showPiToast('诊断失败', 'error'); + }); + } + function checkStatus() { return Promise.all([ fetch(API.STATUS, { credentials: 'same-origin', cache: 'no-store' }).then(function (r) { return r.json(); }), @@ -3511,6 +3597,7 @@ piLabState.runtimeInstallHint = data.runtimeInstallHint || data.runtime_install_hint || ''; piLabState.runtimeError = data.runtimeError || (data.session && data.session.runtimeError) || ''; piLabState.runtimePid = data.pid || (data.session && data.session.runtimePid) || piLabState.runtimePid || null; + piLabState.advancedRuntime = data.advancedRuntime || (data.session && data.session.advancedRuntime) || (data.session && data.session.runtimePolicySnapshot && data.session.runtimePolicySnapshot.piRuntime) || piLabState.advancedRuntime || {}; piLabState.warmupRunning = data.warmupRunning === true; piLabState.warmupSessionId = data.warmupSessionId || null; piLabState.managedBuiltinTools = data.managedPiBuiltinTools || data.managedBuiltinTools || piLabState.managedBuiltinTools || []; @@ -3589,6 +3676,7 @@ piLabState.runtimeInstallHint = data.runtimeInstallHint || data.runtime_install_hint || ''; piLabState.runtimeError = session.runtimeError || data.runtimeError || ''; piLabState.runtimePid = session.runtimePid || data.pid || piLabState.runtimePid || null; + piLabState.advancedRuntime = data.advancedRuntime || session.advancedRuntime || (session.runtimePolicySnapshot && session.runtimePolicySnapshot.piRuntime) || piLabState.advancedRuntime || {}; piLabState.managedBuiltinTools = data.managedPiBuiltinTools || data.managedBuiltinTools || piLabState.managedBuiltinTools || []; piLabState.piExtensionSources = data.piExtensionSources || piLabState.piExtensionSources || []; piLabState.piExtensionToolNames = data.piExtensionToolNames || piLabState.piExtensionToolNames || []; @@ -3733,6 +3821,7 @@ piLabState.runtimePid = payload.pid || piLabState.runtimePid; piLabState.runtimeMode = payload.runtimeMode || piLabState.runtimeMode; piLabState.managedBuiltinTools = payload.managedBuiltinTools || payload.managedPiBuiltinTools || piLabState.managedBuiltinTools; + piLabState.advancedRuntime = payload.advancedRuntime || piLabState.advancedRuntime || {}; piLabState.piExtensionSources = payload.piExtensionSources || piLabState.piExtensionSources || []; piLabState.piExtensionToolNames = payload.piExtensionToolNames || piLabState.piExtensionToolNames || []; setState(STATE_STARTED); @@ -4723,11 +4812,16 @@ }); return; } - var openSessionJsonl = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-open-session-jsonl]') : null; - if (openSessionJsonl) { - openCurrentSessionJsonl(); - return; - } + var openSessionJsonl = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-open-session-jsonl]') : null; + if (openSessionJsonl) { + openCurrentSessionJsonl(); + return; + } + var diagnosticCommand = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-diagnostic-command]') : null; + if (diagnosticCommand) { + runPiDiagnostic(diagnosticCommand.getAttribute('data-page-ai-pi-lab-diagnostic-command') || 'doctor'); + return; + } var openArtifact = event.target && event.target.closest ? event.target.closest('[data-page-ai-pi-lab-artifact-open]') : null; if (openArtifact) { var artUrl = openArtifact.getAttribute('data-page-ai-pi-lab-artifact-open') || openArtifact.getAttribute('href') || ''; diff --git a/rust/crates/mnote-web/src/ssr/pages/ai_admin.rs b/rust/crates/mnote-web/src/ssr/pages/ai_admin.rs index 664c5a1a..d5c1fa70 100644 --- a/rust/crates/mnote-web/src/ssr/pages/ai_admin.rs +++ b/rust/crates/mnote-web/src/ssr/pages/ai_admin.rs @@ -1040,6 +1040,12 @@ const AI_ADMIN_SCRIPT: &str = r#" root.addEventListener('click', function (event) { var closeTarget = event.target && event.target.closest ? event.target.closest('[data-action="close-user-drawer"], [data-ai-admin-drawer-backdrop]') : null; if (closeTarget && drawer) drawer.classList.remove('is-open'); + var piDiagnosticTarget = event.target && event.target.closest ? event.target.closest('[data-ai-admin-pi-diagnostic-command]') : null; + if (piDiagnosticTarget) { + event.preventDefault(); + runPiDiagnostic(piDiagnosticTarget.getAttribute('data-ai-admin-pi-diagnostic-command') || 'doctor'); + return; + } }); function setText(node, value) { @@ -1081,6 +1087,66 @@ const AI_ADMIN_SCRIPT: &str = r#" return '
' + escapeHtml(label) + '' + escapeHtml(value == null || value === '' ? '-' : value) + '
'; } + function advancedRuntimeSummary(config) { + config = config || {}; + var pairs = [ + ['extensionPolicy', config.extensionPolicy || config.extension_policy], + ['repairPolicy', config.repairPolicy || config.repair_policy], + ['sessionDurability', config.sessionDurability || config.session_durability], + ['requestTimeoutSecs', config.requestTimeoutSecs || config.request_timeout_secs], + ['maxToolIterations', config.maxToolIterations || config.max_tool_iterations], + ['hideCwdInPrompt', (config.hideCwdInPrompt === true || config.hide_cwd_in_prompt === true) ? 'true' : 'false'], + ].filter(function (item) { return item[1] !== undefined && item[1] !== null && item[1] !== ''; }); + return pairs.length ? pairs.map(function (item) { return item[0] + '=' + item[1]; }).join(', ') : 'default'; + } + + function piDiagnosticParams(command) { + var input = root.querySelector('[data-ai-admin-pi-diagnostic-query]'); + var keyword = input ? String(input.value || '').trim() : ''; + var params = {}; + if (command === 'doctor') params.format = 'json'; + if (command === 'context-preview') { + params.format = 'json'; + params.query = [keyword || 'MNote Pi Rust diagnostics']; + } + if (command === 'search') { + if (!keyword) throw new Error('请输入 search keyword'); + params.query = keyword; + params.limit = 10; + } + if (command === 'info') { + if (!keyword) throw new Error('请输入 info name/id'); + params.name = keyword; + } + return params; + } + + function setPiDiagnosticOutput(payload) { + var details = root.querySelector('[data-ai-admin-pi-diagnostics-card]'); + var output = root.querySelector('[data-ai-admin-pi-diagnostics-output]'); + if (details) details.open = true; + if (!output) return; + output.hidden = false; + output.textContent = typeof payload === 'string' ? payload : JSON.stringify(payload || {}, null, 2); + } + + async function runPiDiagnostic(command) { + try { + var params = piDiagnosticParams(command); + setPiDiagnosticOutput('Running ' + command + '...'); + var payload = await requestJson('/api/page-ai/pi/diagnostics', { + method: 'POST', + body: JSON.stringify({ + command: command, + params: params, + }), + }); + setPiDiagnosticOutput(payload); + } catch (err) { + setPiDiagnosticOutput({ ok: false, command: command, error: err.message || '诊断失败' }); + } + } + function normalizeDirectoryGrants(payload) { var grants = payload && (payload.grants || (payload.policy && payload.policy.grants) || payload.allowedRoots || payload.allowed_roots || []); return Array.isArray(grants) ? grants.filter(Boolean) : []; @@ -1216,6 +1282,7 @@ const AI_ADMIN_SCRIPT: &str = r#" var piRuntimeBinary = piStatus ? (piStatus.runtimeBinary || piStatus.runtime_binary || '-') : '-'; var piRuntimeAvailable = !piStatus || piStatus.runtimeAvailable !== false; var piRuntimeError = piStatus && (piStatus.runtimeError || piStatus.runtime_error || piStatus.runtimeInstallHint || piStatus.runtime_install_hint || ''); + var advancedRuntime = piStatus && (piStatus.advancedRuntime || piStatus.advanced_runtime) || {}; var piRunning = piStatus && piStatus.running === true; var piExtensions = piStatus && Array.isArray(piStatus.piExtensionSources) ? piStatus.piExtensionSources @@ -1242,9 +1309,24 @@ const AI_ADMIN_SCRIPT: &str = r#" renderKv('Runtime PID', piStatus && piStatus.pid ? String(piStatus.pid) : 'none') + renderKv('受控 Pi 原生工具', (piStatus && piStatus.managedPiBuiltinTools || ['read','write','edit','bash','grep','find','ls','hashline_edit']).join(', ')) + renderKv('Pi 扩展包', piExtensions.length ? piExtensions.join(', ') : '待 runtime 启动') + + renderKv('Advanced runtime', advancedRuntimeSummary(advancedRuntime)) + renderKv('Receipt storage', piStatus && piStatus.receiptStorage ? piStatus.receiptStorage : 'control-plane / provider-neutral adapter') + renderKv('Session dir policy', piStatus && piStatus.managedPiSessionDirPolicy ? piStatus.managedPiSessionDirPolicy : '/.mnote/ai/pi-sessions//') + '' + + '
' + + 'Pi 官方诊断 / effective runtime' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '' + + '
' + escapeHtml(JSON.stringify(advancedRuntime, null, 2)) + '
' + + '
' + '' + '' + '
' + diff --git a/scripts/task-ai-management-control-plane-static-smoke.js b/scripts/task-ai-management-control-plane-static-smoke.js index f0c2407e..ec6bae37 100644 --- a/scripts/task-ai-management-control-plane-static-smoke.js +++ b/scripts/task-ai-management-control-plane-static-smoke.js @@ -355,6 +355,30 @@ check( "Pi Rust service panel exposes runtime implementation, binary, availability, and error" ); +check( + "AI management service panel exposes effective Pi advanced runtime", + aiAdminRs.includes("Advanced runtime") && + aiAdminRs.includes("advancedRuntimeSummary") && + aiAdminRs.includes("data-ai-admin-pi-advanced-runtime-json") && + aiAdminRs.includes("extensionPolicy") && + aiAdminRs.includes("maxToolIterations"), + "Shows effective extension/repair/session/request/tool-iteration policy instead of hiding host decisions" +); + +check( + "AI management service panel exposes controlled Pi official diagnostics", + routesMod.includes('/api/page-ai/pi/diagnostics", post(page_ai_pi::diagnostics)') && + aiAdminRs.includes('data-ai-admin-pi-diagnostic-command="doctor"') && + aiAdminRs.includes('data-ai-admin-pi-diagnostic-command="context-preview"') && + aiAdminRs.includes('data-ai-admin-pi-diagnostic-command="list"') && + aiAdminRs.includes('data-ai-admin-pi-diagnostic-command="update-index"') && + aiAdminRs.includes('data-ai-admin-pi-diagnostic-command="search"') && + aiAdminRs.includes('data-ai-admin-pi-diagnostic-command="info"') && + aiAdminRs.includes("runPiDiagnostic") && + aiAdminRs.includes("/api/page-ai/pi/diagnostics"), + "Doctor/context-preview/list/search/info/update-index are available from Pi service diagnostics" +); + check( "OpenHub is described as compatibility/admin boundary, not default chat", aiAdminRs.includes("迁移期兼容 / admin 边界") && diff --git a/scripts/task-pi-lab-static-smoke.js b/scripts/task-pi-lab-static-smoke.js index d2c472f8..58482715 100644 --- a/scripts/task-pi-lab-static-smoke.js +++ b/scripts/task-pi-lab-static-smoke.js @@ -131,6 +131,11 @@ const checks = [ ['runtime applies model controls through Pi RPC configure endpoint', runtime.includes("CONFIGURE: '/api/page-ai/pi/configure'") && runtime.includes('applyModelConfigToRuntime') && runtime.includes('pendingModelConfigApply')], ['runtime collapses secondary right rail sections', runtime.includes('