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('