feat: Page AI Reasonix desktop session alignment, settings IA cleanup, knowledge RAG hardening

- ACP client/session manager: Reasonix desktop live session context
- Hermes tools: knowledge_rag tool manifest and skill updates
- Browser runtime: sidebar page AI permission/profile/render/session/tree modules
- Routes: hermes_client, hermes_tools, knowledge_rag, web_shell
- Scripts: reasonix ACP wrapper, LightRAG MCP, smoke tasks 159/558/559/561/562
- Skills: mnote-knowledge-rag and mnote-lightrag-bridge SKILL.md updates
This commit is contained in:
lix-2026
2026-06-13 22:20:01 +08:00
parent 2236a053c0
commit 4a7efd4a30
30 changed files with 5321 additions and 291 deletions
@@ -39,6 +39,9 @@ export function createSidebarPageAiRenderRuntime(context) {
pageAiSessionAgentFilterOptions,
pageAiSessionAgentLabel,
pageAiSessionAgentFilterValue,
pageAiSessionStatusFilterOptions,
pageAiSessionStatusFilterValue,
pageAiSessionStatusLabel,
pageAiSessionPreviewText,
pageAiSessionStorageLabel,
pageAiSessionSelected,
@@ -192,6 +195,50 @@ export function createSidebarPageAiRenderRuntime(context) {
'</label>';
}
function pageAiReasonixQuickControlLabel(key, fallback) {
if (key === 'ai.agent.reasonix.model_id') return '模型';
if (key === 'ai.agent.reasonix.approval_mode') return '审批';
if (key === 'ai.agent.reasonix.plan_mode') return 'Plan';
return fallback || key;
}
function pageAiRenderReasonixQuickControl(field) {
var key = String(field && field.key || '').trim();
if (!key || field.secret === true) return '';
var value = pageAiDescriptorFieldValue(field);
var options = pageAiDescriptorFieldOptions(field);
if (!options.length) return '';
return '' +
'<label class="wolai-page-ai-reasonix-control" data-page-ai-reasonix-control-row="' + escapeHtml(key) + '">' +
'<span>' + escapeHtml(pageAiReasonixQuickControlLabel(key, field.label)) + '</span>' +
'<select data-page-ai-descriptor-field="' + escapeHtml(key) + '" data-page-ai-descriptor-kind="select" data-page-ai-reasonix-quick-control="' + escapeHtml(key) + '">' +
options.map(function(option) {
return '<option value="' + escapeHtml(option.value) + '"' + (String(value) === String(option.value) ? ' selected' : '') + '>' + escapeHtml(option.label) + '</option>';
}).join('') +
'</select>' +
'</label>';
}
function pageAiReasonixQuickControlsHtml() {
if (pageAiCurrentAgentId() !== 'reasonix') return '';
var descriptor = pageAiAgentDescriptor('reasonix') || pageAiAgentRecord('reasonix');
var wanted = {
'ai.agent.reasonix.model_id': true,
'ai.agent.reasonix.approval_mode': true,
'ai.agent.reasonix.plan_mode': true
};
var fields = pageAiNormalizeArray(descriptor && descriptor.fields)
.filter(function(field) { return wanted[String(field && field.key || '').trim()] === true; })
.map(pageAiRenderReasonixQuickControl)
.filter(Boolean)
.join('');
if (!fields) return '';
return '' +
'<div class="wolai-page-ai-reasonix-controls" data-page-ai-reasonix-quick-controls>' +
fields +
'</div>';
}
function pageAiRenderAgentDescriptorCard(agentId, fallbackTitle) {
var descriptor = pageAiAgentDescriptor(agentId) || pageAiAgentRecord(agentId);
var title = String(descriptor && (descriptor.displayName || descriptor.label) || fallbackTitle || agentId).trim();
@@ -210,11 +257,27 @@ export function createSidebarPageAiRenderRuntime(context) {
function pageAiCurrentModelLabel() {
var profile = pageAiCurrentProfileRecord();
var prefs = pageUiState.pageAiSkillPreferences || {};
var toolModel = pageAiMnoteToolModel();
var reasonixModel = String(prefs['ai.agent.reasonix.model_id'] || '').trim();
var approvalMode = String(prefs['ai.agent.reasonix.approval_mode'] || 'ask').trim() || 'ask';
var planMode = String(prefs['ai.agent.reasonix.plan_mode'] || 'manual').trim() || 'manual';
var approvalLabel = {
ask: '审批:询问',
allow: '审批:自动允许',
deny: '审批:自动拒绝'
}[approvalMode] || ('审批:' + approvalMode);
var planLabel = {
manual: '计划:手动',
auto: '计划:自动'
}[planMode] || ('计划:' + planMode);
if (!profile) return 'tool: ' + toolModel;
var model = String(profile.model || '').trim();
var gateway = String(profile.gateway || '').trim();
var profileLabel = [model, gateway].filter(Boolean).join(' · ') || '由 Hermes 决定';
if (pageAiCurrentAgentId() === 'reasonix') {
return ['Reasonix', reasonixModel || toolModel, approvalLabel, planLabel].filter(Boolean).join(' · ');
}
return 'tool: ' + toolModel + ' · profile: ' + profileLabel;
}
@@ -283,6 +346,88 @@ export function createSidebarPageAiRenderRuntime(context) {
});
}
function pageAiRuntimeStatusValue(path, fallback) {
var node = pageUiState.pageAiRuntimeStatus || {};
String(path || '').split('.').forEach(function(part) {
if (!part || node == null || typeof node !== 'object') return;
node = node[part];
});
if (node == null || node === '') return fallback || '';
return String(node);
}
function pageAiRuntimeStatusLabel() {
var status = pageUiState.pageAiRuntimeStatus || {};
var runtime = String(status.runtime || pageUiState.pageAiAcpRuntime || 'reasonix');
var mode = String(status.mode || 'new_session');
if (runtime === 'reasonix') return pageAiCurrentModelLabel() + ' · ' + mode.replace(/_/g, '-');
return runtime + ' · ' + mode.replace(/_/g, '-');
}
function pageAiRuntimeDetailHtml() {
var status = pageUiState.pageAiRuntimeStatus || {};
var acp = status.acp && typeof status.acp === 'object' ? status.acp : {};
var roots = status.roots && typeof status.roots === 'object' ? status.roots : {};
var tools = status.tools && typeof status.tools === 'object' ? status.tools : {};
var mcp = status.mcp && typeof status.mcp === 'object' ? status.mcp : {};
var queue = status.session && status.session.queue && typeof status.session.queue === 'object' ? status.session.queue : {};
var logs = status.logs && Array.isArray(status.logs.items) ? status.logs.items : [];
var rows = [
['runtime', pageAiRuntimeStatusLabel()],
['session', String(status.mnoteSessionId || pageUiState.pageAiActiveSessionId || '无')],
['acpSessionId', String(acp.acpSessionId || '无')],
['load/replay', String(acp.loadStatus || 'not_applicable') + ' / replay=' + String(acp.replaySeen === true)],
['live binding', acp.liveBinding && typeof acp.liveBinding === 'object' ? String(acp.liveBinding.status || 'unknown') : '无'],
['roots', 'write=' + String(roots.write || 0) + ' readOnly=' + String(roots.readOnly || 0)],
['MCP/tools', String(mcp.status || 'unknown') + ' · enabled=' + String(tools.enabled || 0) + ' disabled=' + String(tools.disabled || 0)],
['queue', String(queue.queuedCount || pageUiState.pageAiQueueLength || 0)],
['jobs', pageAiRuntimeStatusValue('jobs.running', '0')]
];
var html = rows.map(function(row) {
return '<div class="wolai-page-ai-tool-row"><span class="wolai-page-ai-tool-name">' + escapeHtml(row[0]) + '</span><span class="wolai-page-ai-tool-meta">' + escapeHtml(row[1]) + '</span></div>';
}).join('');
if (pageUiState.pageAiRuntimeStatusError) {
html += '<div class="wolai-page-ai-inline-error">' + escapeHtml(pageUiState.pageAiRuntimeStatusError) + '</div>';
}
if (logs.length) {
html += logs.slice(-4).map(function(item) {
return '<div class="wolai-page-ai-tool-row"><span class="wolai-page-ai-tool-name">' + escapeHtml(String(item.kind || 'log')) + '</span><span class="wolai-page-ai-tool-meta">' + escapeHtml(String(item.seq || '')) + '</span></div>';
}).join('');
}
return html || '<div class="wolai-page-ai-empty">Runtime status 尚未加载。</div>';
}
function pageAiUsageDetailHtml() {
var status = pageUiState.pageAiRuntimeStatus || {};
var usage = status.usage && typeof status.usage === 'object' ? status.usage : {};
var rows = [
['last run', usage.lastRunTokens || usage.lastRunCost || 'unknown'],
['session total', usage.sessionTokens || usage.sessionCost || 'unknown'],
['cache', usage.cacheTokens || usage.cacheHit || 'unknown']
];
return rows.map(function(row) {
return '<div class="wolai-page-ai-tool-row"><span class="wolai-page-ai-tool-name">' + escapeHtml(row[0]) + '</span><span class="wolai-page-ai-tool-meta">' + escapeHtml(String(row[1])) + '</span></div>';
}).join('');
}
function pageAiSettingsTabsHtml(activeTab) {
return [
['agent', 'MNote'],
['reasonix-settings', 'Reasonix'],
['hermes-settings', 'Hermes']
].map(function(tab) {
var active = tab[0] === activeTab;
return '<button type="button" class="wolai-page-ai-tab' + (active ? ' is-active' : '') + '" data-page-ai-tab="' + escapeHtml(tab[0]) + '" role="tab" aria-selected="' + (active ? 'true' : 'false') + '">' + escapeHtml(tab[1]) + '</button>';
}).join('');
}
function pageAiStatusTabsHtml() {
return '' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="chat" role="tab" aria-selected="false">聊天</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="status" role="tab" aria-selected="true">状态</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">设置</button>';
}
function renderPageAiProviderButtons() {
var drawer = ensurePageAiDrawer();
var isAcp = pageUiState.pageAiAcpRuntime !== '';
@@ -303,6 +448,8 @@ export function createSidebarPageAiRenderRuntime(context) {
var isAcp = pageUiState.pageAiAcpRuntime !== '';
var activeAgentId = pageAiCurrentAgentId();
var activeProfile = pageAiCurrentProfile();
if (pageUiState.pageAiPage === 'runtime') pageUiState.pageAiPage = 'status';
if (pageUiState.pageAiPage === 'chat-only-settings') pageUiState.pageAiPage = 'agent';
drawer.setAttribute('data-page-ai-page', pageUiState.pageAiPage || 'chat');
drawer.setAttribute('data-page-ai-active-agent-id', activeAgentId);
drawer.setAttribute('data-mnote-acp-runtime', pageUiState.pageAiAcpRuntime || 'reasonix');
@@ -478,16 +625,32 @@ export function createSidebarPageAiRenderRuntime(context) {
if (agentPanel instanceof HTMLElement) {
agentPanel.innerHTML = '' +
(pageUiState.pageAiAgentDescriptorError ? '<div class="wolai-page-ai-inline-error">' + escapeHtml(pageUiState.pageAiAgentDescriptorError) + '</div>' : '') +
'<section class="wolai-page-ai-memory-card" data-page-ai-agent-descriptor-card="mnote">' +
'<div class="wolai-page-ai-memory-head"><div><div class="wolai-page-ai-memory-title">MNote</div><div class="wolai-page-ai-memory-scope">上下文、权限、工具、会话历史和审计</div></div></div>' +
'<div class="wolai-page-ai-tool-meta">这里配置 MNote 发送给 agent 的 envelope,不配置 Reasonix 或 Hermes 原生 runtime。</div>' +
'</section>' +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">授权区域</div><div class="wolai-page-ai-memory-scope">' + escapeHtml(pageAiBuildAllowedRoots().length ? 'SQLite directory_grants' : '需要授权') + '</div></div>' +
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">授权区域</div><div class="wolai-page-ai-memory-scope">SQLite directory_grants</div></div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-allowed-roots></div>' +
'</section>' +
pageAiRenderAgentDescriptorCard(activeAgentId, pageAiCurrentAgentSelectionLabel()) +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">默认上下文</div><div class="wolai-page-ai-memory-scope">ContextRefs</div></div>' +
'<div class="wolai-page-ai-tool-meta">由 MNote 生成 run envelope,不写入 Reasonix/Hermes native memory。</div>' +
'</section>' +
'<section class="wolai-page-ai-memory-card">' +
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">审计</div><div class="wolai-page-ai-memory-scope">changed files 默认摘要</div></div>' +
'<div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">会话与审计</div><div class="wolai-page-ai-memory-scope">history / export / changed files</div></div>' +
'<div class="wolai-page-ai-tool-meta">删除会话只删除 MNote history;外部 Hermes/provider 会话不会默认删除。</div>' +
'</section>';
var mnoteAllowedRootsNode = agentPanel.querySelector('[data-page-ai-allowed-roots]');
if (mnoteAllowedRootsNode instanceof HTMLElement) {
var mnoteAllowedRoots = pageAiBuildAllowedRoots();
mnoteAllowedRootsNode.innerHTML = mnoteAllowedRoots.length
? mnoteAllowedRoots.map(function(root) {
var label = root.rootUri.replace(/^file:\/\//, '') || root.rootUri;
return '<span class="wolai-page-ai-model-chip is-active" data-page-ai-allowed-root="' + escapeHtml(root.rootUri) + '" data-page-ai-allowed-root-permission="' + escapeHtml(root.permission) + '">' + escapeHtml(root.permission + ' · ' + label) + '</span>';
}).join('')
: '<span class="wolai-page-ai-tool-meta">' + escapeHtml(String(pageUiState.pageAiAllowedRootsError || '').trim() || '未选择授权区域') + '</span>';
}
}
var profileSummary = drawer.querySelector('[data-page-ai-profile-summary]');
if (profileSummary instanceof HTMLElement) profileSummary.textContent = '上下文可选';
@@ -512,6 +675,40 @@ export function createSidebarPageAiRenderRuntime(context) {
var queueSuffix = pageUiState.pageAiQueueLength > 0 ? ' · 队列 ' + pageUiState.pageAiQueueLength : '';
runStatus.textContent = pageAiRunStatusLabel(pageUiState.pageAiRunStatus) + queueSuffix;
}
drawer.querySelectorAll('[data-page-ai-runtime-badge]').forEach(function(runtimeBadge) {
if (runtimeBadge instanceof HTMLElement) runtimeBadge.textContent = pageAiRuntimeStatusLabel();
});
var runtimeActive = drawer.querySelector('[data-page-ai-runtime-active-run]');
if (runtimeActive instanceof HTMLElement) {
var activeRunStatus = pageAiRuntimeStatusValue('activeRun.status', pageUiState.pageAiRunStatus || 'idle');
runtimeActive.textContent = pageAiRunStatusLabel(activeRunStatus);
}
var runtimeAcp = drawer.querySelector('[data-page-ai-runtime-acp]');
if (runtimeAcp instanceof HTMLElement) runtimeAcp.textContent = pageAiRuntimeStatusValue('acp.acpSessionId', '无 ACP session');
var runtimeDetail = drawer.querySelector('[data-page-ai-runtime-detail]');
if (runtimeDetail instanceof HTMLElement) runtimeDetail.innerHTML = pageAiRuntimeDetailHtml();
var jobsList = drawer.querySelector('[data-page-ai-jobs-list]');
if (jobsList instanceof HTMLElement) {
var runtimeStatus = pageUiState.pageAiRuntimeStatus || {};
var jobs = runtimeStatus.jobs && Array.isArray(runtimeStatus.jobs.items) ? runtimeStatus.jobs.items : [];
jobsList.innerHTML = jobs.length
? jobs.map(function(job) {
return '<div class="wolai-page-ai-tool-row"><span class="wolai-page-ai-tool-name">' + escapeHtml(String(job.id || job.name || 'job')) + '</span><span class="wolai-page-ai-tool-meta">' + escapeHtml(String(job.status || 'running')) + '</span></div>';
}).join('')
: '<div class="wolai-page-ai-empty">暂无运行中的 job。</div>';
}
var logsList = drawer.querySelector('[data-page-ai-logs-list]');
if (logsList instanceof HTMLElement) {
var logStatus = pageUiState.pageAiRuntimeStatus || {};
var logs = logStatus.logs && Array.isArray(logStatus.logs.items) ? logStatus.logs.items : [];
logsList.innerHTML = logs.length
? logs.slice(-8).map(function(item) {
return '<div class="wolai-page-ai-tool-row"><span class="wolai-page-ai-tool-name">' + escapeHtml(String(item.kind || 'log')) + '</span><span class="wolai-page-ai-tool-meta">' + escapeHtml(String(item.seq || '')) + '</span></div>';
}).join('')
: '<div class="wolai-page-ai-empty">暂无 runtime logs。</div>';
}
var usageDetail = drawer.querySelector('[data-page-ai-usage-detail]');
if (usageDetail instanceof HTMLElement) usageDetail.innerHTML = pageAiUsageDetailHtml();
var stopButton = drawer.querySelector('[data-page-ai-action="stop-run"]');
if (stopButton instanceof HTMLButtonElement) {
var canStop = ['queued', 'running', 'tool_calling'].indexOf(pageUiState.pageAiRunStatus) >= 0 && pageUiState.pageAiCurrentRunId;
@@ -530,9 +727,10 @@ export function createSidebarPageAiRenderRuntime(context) {
} else {
queueList.innerHTML = pageUiState.pageAiQueuedItems.map(function(item, index) {
var label = '队列 ' + (index + 1);
var preview = String(item.preview || item.traceId || item.queueId || '').trim();
return '' +
'<div class="wolai-page-ai-tool-row" data-page-ai-queue-item="' + escapeHtml(item.queueId) + '">' +
'<div><strong>' + escapeHtml(label) + '</strong><br /><span>' + escapeHtml(item.queueId) + '</span></div>' +
'<div><strong>' + escapeHtml(label) + '</strong><br /><span>' + escapeHtml(preview || item.queueId) + '</span></div>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="cancel-queued-run" data-page-ai-queue-id="' + escapeHtml(item.queueId) + '">取消</button>' +
'</div>';
}).join('');
@@ -547,7 +745,17 @@ export function createSidebarPageAiRenderRuntime(context) {
: '等待 AI session';
}
var modelNode = drawer.querySelector('[data-page-ai-model-status]');
if (modelNode instanceof HTMLElement) modelNode.textContent = pageAiBuildAllowedRoots().length ? '已授权' : '需授权';
if (modelNode instanceof HTMLElement) modelNode.textContent = pageAiCurrentModelLabel();
var reasonixControls = drawer.querySelector('[data-page-ai-reasonix-quick-controls]');
if (reasonixControls instanceof HTMLElement) {
if (pageAiCurrentAgentId() === 'reasonix') {
reasonixControls.innerHTML = pageAiReasonixQuickControlsHtml().replace(/^<div[^>]*>|<\/div>$/g, '');
reasonixControls.hidden = false;
} else {
reasonixControls.innerHTML = '';
reasonixControls.hidden = true;
}
}
var scopeSelect = drawer.querySelector('[data-page-ai-context-scope]');
if (scopeSelect instanceof HTMLSelectElement) scopeSelect.value = pageUiState.pageAiContextScope;
var scopeLabel = drawer.querySelector('[data-page-ai-context-scope-label]');
@@ -630,6 +838,20 @@ export function createSidebarPageAiRenderRuntime(context) {
sessionAgentFilter.value = activeFilter;
pageUiState.pageAiSessionAgentFilter = activeFilter;
}
var sessionStatusFilter = drawer.querySelector('[data-page-ai-session-status-filter]');
if (sessionStatusFilter instanceof HTMLSelectElement) {
var historyRowsForStatusFilter = pageUiState.pageAiSessionSearchResults.length
? pageUiState.pageAiSessionSearchResults
: pageUiState.pageAiSessions;
var statusOptions = pageAiSessionStatusFilterOptions(historyRowsForStatusFilter);
var activeStatusFilter = String(pageUiState.pageAiSessionStatusFilter || 'all').trim() || 'all';
if (!statusOptions.some(function(option) { return option.value === activeStatusFilter; })) activeStatusFilter = 'all';
sessionStatusFilter.innerHTML = statusOptions.map(function(option) {
return '<option value="' + escapeHtml(option.value) + '"' + (option.value === activeStatusFilter ? ' selected' : '') + '>' + escapeHtml(option.label) + '</option>';
}).join('');
sessionStatusFilter.value = activeStatusFilter;
pageUiState.pageAiSessionStatusFilter = activeStatusFilter;
}
var skillSearch = drawer.querySelector('[data-page-ai-skill-search]');
if (skillSearch instanceof HTMLInputElement && documentRef.activeElement !== skillSearch) {
skillSearch.value = pageUiState.pageAiSkillQuery;
@@ -754,7 +976,7 @@ export function createSidebarPageAiRenderRuntime(context) {
}
if (gatewayDetail instanceof HTMLElement) {
if (!gatewayHealth) {
gatewayDetail.innerHTML = '<div class="wolai-page-ai-empty">打开高级后会检查 agent runtime 与当前 profile。</div>';
gatewayDetail.innerHTML = '<div class="wolai-page-ai-empty">打开 Hermes 设置后会检查 gateway 与当前 profile。</div>';
} else {
var gatewayInfo = gatewayHealth.gateway || {};
var profileInfo = gatewayHealth.profile || {};
@@ -798,8 +1020,8 @@ export function createSidebarPageAiRenderRuntime(context) {
}
var chatOnlyPanel = drawer.querySelector('[data-page-ai-chat-only-panel]');
if (chatOnlyPanel instanceof HTMLElement) {
chatOnlyPanel.innerHTML = pageAiRenderAgentDescriptorCard('chat_only', 'Chat-only') +
'<section class="wolai-page-ai-memory-card"><div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">Chat-only</div><div class="wolai-page-ai-memory-scope">默认不申请文件写权限</div></div></section>';
chatOnlyPanel.innerHTML = pageAiRenderAgentDescriptorCard('chat_only', 'API Chat / Chat-only profile') +
'<section class="wolai-page-ai-memory-card"><div class="wolai-page-ai-memory-head"><div class="wolai-page-ai-memory-title">API Chat / Chat-only profile</div><div class="wolai-page-ai-memory-scope">默认不申请文件写权限,归属 MNote 设置。</div></div></section>';
}
}
@@ -859,6 +1081,11 @@ export function createSidebarPageAiRenderRuntime(context) {
'<button type="button" class="wolai-page-ai-icon" data-page-ai-action="close" aria-label="关闭页面 AI">×</button>' +
'</div>' +
'</div>' +
'<button type="button" class="wolai-page-ai-runtime-strip" data-page-ai-runtime-strip data-page-ai-tab="status" aria-label="打开 Page AI 状态">' +
'<span data-page-ai-runtime-badge>reasonix · new-session</span>' +
'<span data-page-ai-runtime-active-run>空闲</span>' +
'<span data-page-ai-runtime-acp>无 ACP session</span>' +
'</button>' +
'<div class="wolai-page-ai-body">' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="chat">' +
'<div class="wolai-page-ai-chat-meta">' +
@@ -885,26 +1112,36 @@ export function createSidebarPageAiRenderRuntime(context) {
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="agent" role="tab" aria-selected="true">Common</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="hermes-settings" role="tab" aria-selected="false">Hermes</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="reasonix-settings" role="tab" aria-selected="false">Reasonix</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="chat-only-settings" role="tab" aria-selected="false">Chat-only</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">高级</button>' +
pageAiSettingsTabsHtml('agent') +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-profile-error hidden></div>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-memory-error hidden></div>' +
'<div class="wolai-page-ai-memory-grid" data-page-ai-agent-panel></div>' +
'<label class="wolai-page-ai-context-select">' +
'<span>默认上下文 scope</span>' +
'<select data-page-ai-context-scope>' +
'<option value="page">当前页</option>' +
'<option value="selection">当前选区</option>' +
'<option value="block">当前块</option>' +
'<option value="options">页面设置</option>' +
'</select>' +
'</label>' +
'<div class="wolai-page-ai-settings-stack" data-page-ai-agent-panel></div>' +
'<div class="wolai-page-ai-settings-stack" data-page-ai-chat-only-panel></div>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-head">' +
'<span>MNote 工具</span>' +
'<span data-page-ai-last-tool>暂无 tool call</span>' +
'</div>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-tool-error hidden></div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-tool-list></div>' +
'</div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="hermes-settings" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Common</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="hermes-settings" role="tab" aria-selected="true">Hermes</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="reasonix-settings" role="tab" aria-selected="false">Reasonix</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="chat-only-settings" role="tab" aria-selected="false">Chat-only</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">高级</button>' +
pageAiSettingsTabsHtml('hermes-settings') +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-settings-grid">' +
@@ -914,17 +1151,21 @@ export function createSidebarPageAiRenderRuntime(context) {
'</label>' +
'</div>' +
'<button type="button" class="wolai-page-ai-settings-link" data-page-ai-action="open-hermes-settings">打开 Hermes 设置</button>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-gateway-error hidden></div>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-head">' +
'<span>Hermes gateway</span>' +
'<span data-page-ai-gateway-status>未检查</span>' +
'</div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-gateway-detail></div>' +
'</div>' +
'<div class="wolai-page-ai-memory-grid" data-page-ai-hermes-panel></div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="reasonix-settings" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Common</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="hermes-settings" role="tab" aria-selected="false">Hermes</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="reasonix-settings" role="tab" aria-selected="true">Reasonix</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="chat-only-settings" role="tab" aria-selected="false">Chat-only</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">高级</button>' +
pageAiSettingsTabsHtml('reasonix-settings') +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-settings-grid">' +
@@ -935,26 +1176,13 @@ export function createSidebarPageAiRenderRuntime(context) {
'</div>' +
'<div class="wolai-page-ai-memory-grid" data-page-ai-reasonix-panel></div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="chat-only-settings" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Common</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="hermes-settings" role="tab" aria-selected="false">Hermes</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="reasonix-settings" role="tab" aria-selected="false">Reasonix</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="chat-only-settings" role="tab" aria-selected="true">Chat-only</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">高级</button>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-memory-grid" data-page-ai-chat-only-panel></div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="skills" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Agent</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">设置</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="skills" role="tab" aria-selected="true">能力</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="runtime" role="tab" aria-selected="false">Runtime</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="status" role="tab" aria-selected="false">状态</button>' +
'</div>' +
'</div>' +
'<div class="wolai-page-ai-skills-toolbar">' +
@@ -974,47 +1202,51 @@ export function createSidebarPageAiRenderRuntime(context) {
'<div class="wolai-page-ai-inline-error" data-page-ai-skill-error hidden></div>' +
'<div class="wolai-page-ai-skill-list" data-page-ai-skill-list></div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="runtime" hidden>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="status" hidden>' +
'<div class="wolai-page-ai-settings-head">' +
'<button type="button" class="wolai-page-ai-back" data-page-ai-tab="chat">← 聊天</button>' +
'<div class="wolai-page-ai-settings-tabs" role="tablist" aria-label="页面 AI 设置">' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="agent" role="tab" aria-selected="false">Agent</button>' +
'<button type="button" class="wolai-page-ai-tab" data-page-ai-tab="skills" role="tab" aria-selected="false">能力</button>' +
'<button type="button" class="wolai-page-ai-tab is-active" data-page-ai-tab="runtime" role="tab" aria-selected="true">Runtime</button>' +
pageAiStatusTabsHtml() +
'</div>' +
'</div>' +
'<button type="button" class="wolai-page-ai-settings-link" data-page-ai-action="open-hermes-settings">打开 Hermes 设置</button>' +
'<label class="wolai-page-ai-context-select">' +
'<span>兼容上下文 scope</span>' +
'<select data-page-ai-context-scope>' +
'<option value="page">当前页</option>' +
'<option value="selection">当前选区</option>' +
'<option value="block">当前块</option>' +
'<option value="options">页面设置</option>' +
'</select>' +
'</label>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-gateway-error hidden></div>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-head">' +
'<span>gateway</span>' +
'<span data-page-ai-gateway-status>未检查</span>' +
'</div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-gateway-detail></div>' +
'<div class="wolai-page-ai-toolbar">' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="stop-run">Stop turn</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="reset-runtime">Reset current runtime</button>' +
'</div>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-head">' +
'<span>queue</span>' +
'<span>Current runtime</span>' +
'<span data-page-ai-runtime-badge>未加载</span>' +
'</div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-runtime-detail></div>' +
'</div>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-head">' +
'<span>Queue</span>' +
'<span data-page-ai-queue-status>由 mnote-web 管理</span>' +
'</div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-queue-list></div>' +
'</div>' +
'<div class="wolai-page-ai-tools-panel" hidden>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-head">' +
'<span>MNote 工具调试</span>' +
'<span data-page-ai-last-tool>暂无 tool call</span>' +
'<span>Jobs</span>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="stop-all-jobs">Stop all</button>' +
'</div>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-tool-error hidden></div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-tool-list></div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-jobs-list></div>' +
'</div>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-head">' +
'<span>Logs tail</span>' +
'<span>ACP stderr / tool failed / MCP failed</span>' +
'</div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-logs-list></div>' +
'</div>' +
'<div class="wolai-page-ai-tools-panel">' +
'<div class="wolai-page-ai-tools-head">' +
'<span>Usage</span>' +
'<span>last run / session / cache</span>' +
'</div>' +
'<div class="wolai-page-ai-tool-list" data-page-ai-usage-detail></div>' +
'</div>' +
'</section>' +
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="history" hidden>' +
@@ -1034,6 +1266,10 @@ export function createSidebarPageAiRenderRuntime(context) {
'<span>按 agent 筛选</span>' +
'<select data-page-ai-session-agent-filter><option value="all">全部 agent</option></select>' +
'</label>' +
'<label class="wolai-page-ai-skill-search">' +
'<span>按状态筛选</span>' +
'<select data-page-ai-session-status-filter><option value="all">全部状态</option></select>' +
'</label>' +
'<div class="wolai-page-ai-inline-error" data-page-ai-session-error hidden></div>' +
'<div class="wolai-page-ai-conversation" data-page-ai-conversation></div>' +
'</section>' +
@@ -1041,6 +1277,7 @@ export function createSidebarPageAiRenderRuntime(context) {
'<div class="wolai-page-ai-footer">' +
'<div class="wolai-page-ai-composer">' +
'<textarea class="wolai-page-ai-input" data-page-ai-input rows="3" placeholder="输入消息…"></textarea>' +
'<div class="wolai-page-ai-reasonix-controls" data-page-ai-reasonix-quick-controls hidden></div>' +
'<div class="wolai-page-ai-composer-bar">' +
'<button type="button" class="wolai-page-ai-tool-button" data-page-ai-action="new-session" title="新会话"></button>' +
'<div class="wolai-page-ai-agent-picker">' +
@@ -1209,6 +1446,98 @@ export function createSidebarPageAiRenderRuntime(context) {
'</div>';
}
function pageAiLooksLikeReasonixPlanMessage(item) {
if (!item || item.role !== 'assistant') return false;
var text = String(item.content || '').trim();
if (!text || text.length < 40) return false;
if (/ACP connection closed|AI 当前请求失败|run failed/i.test(text)) return false;
var hasPlanWord = /计划|步骤|执行|修改|写入|新建|验证|plan/i.test(text);
var awaitsApproval = /请批准|待批准|批准后|确认后|审批|批准,|开始执行|approve/i.test(text);
var hasStepShape = /(^|\n)\s*(?:\d+[\.\、)]|[-*]\s+|\|\s*\d+\s*\|)/.test(text);
return hasPlanWord && awaitsApproval && hasStepShape;
}
function pageAiExtractReasonixPlanSteps(text) {
var seen = {};
var steps = [];
String(text || '').replace(/\r\n/g, '\n').split('\n').forEach(function(line) {
var trimmed = line.trim();
if (!trimmed) return;
var step = '';
var tableCells = trimmed.indexOf('|') >= 0
? trimmed.split('|').map(function(cell) { return cell.trim(); }).filter(Boolean)
: [];
if (tableCells.length >= 2 && /^\d+$/.test(tableCells[0])) {
step = tableCells.slice(1).join(' · ');
} else {
var numbered = trimmed.match(/^\s*\d+[\.\、)]\s+(.+)$/);
var bullet = trimmed.match(/^\s*[-*]\s+(?:\[[ xX~>|-]\]\s*)?(.+)$/);
step = numbered ? numbered[1] : (bullet ? bullet[1] : '');
}
step = String(step || '').replace(/\s+/g, ' ').trim();
if (!step || step.length < 3 || /^[-:|]+$/.test(step)) return;
if (seen[step]) return;
seen[step] = true;
var lower = step.toLowerCase();
var status = /✅|已完成|done|completed/.test(step) ? 'completed'
: (/当前|进行中|执行中|待批准|in progress|running/.test(lower) ? 'inProgress' : 'pending');
steps.push({ text: step, status: status });
});
return steps.slice(0, 8);
}
function pageAiPlanStepIcon(status) {
if (status === 'completed') return '✓';
if (status === 'inProgress') return '…';
return String.fromCharCode(8226);
}
function pageAiRenderReasonixPlanCard(item) {
var text = String(item.content || '').trim();
var steps = pageAiExtractReasonixPlanSteps(text);
var stepHtml = steps.length ? (
'<ol class="wolai-page-ai-plan-step-list">' + steps.map(function(step) {
return '' +
'<li class="wolai-page-ai-plan-step-item" data-status="' + escapeHtml(step.status || 'pending') + '">' +
'<span class="wolai-page-ai-plan-step-status" data-status="' + escapeHtml(step.status || 'pending') + '">' + escapeHtml(pageAiPlanStepIcon(step.status)) + '</span>' +
'<span class="wolai-page-ai-plan-step-text">' + escapeHtml(step.text || '') + '</span>' +
'</li>';
}).join('') + '</ol>'
) : '';
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--assistant wolai-page-ai-plan-card" data-page-ai-reasonix-plan-card="true">' +
'<div class="wolai-page-ai-plan-card-header">' +
'<div class="wolai-page-ai-plan-card-title-wrap">' +
'<span class="wolai-page-ai-plan-card-kicker">Reasonix</span>' +
'<strong class="wolai-page-ai-plan-card-title">执行计划</strong>' +
'</div>' +
'<span class="wolai-page-ai-plan-card-badge">待批准</span>' +
'</div>' +
'<div class="wolai-page-ai-plan-card-markdown">' + renderPageAiMarkdown(text) + '</div>' +
stepHtml +
'<div class="wolai-page-ai-plan-card-actions">' +
'<button type="button" class="wolai-page-ai-plan-card-button wolai-page-ai-plan-card-button--primary" data-page-ai-plan-action="approve">批准执行</button>' +
'<button type="button" class="wolai-page-ai-plan-card-button" data-page-ai-plan-action="revise">要求修改</button>' +
'<button type="button" class="wolai-page-ai-plan-card-button wolai-page-ai-plan-card-button--danger" data-page-ai-plan-action="cancel">取消</button>' +
'</div>' +
'</div>';
}
function pageAiRenderPermissionDetails(item) {
var details = item && item.permissionDetails && typeof item.permissionDetails === 'object'
? item.permissionDetails
: {};
var rows = Array.isArray(details.rows) ? details.rows : [];
if (!rows.length) {
rows = [
{ label: '请求', value: item.argsSummary || item.content || '' }
];
}
return '<div class="wolai-page-ai-permission-summary">' + rows.slice(0, 6).map(function(row) {
return '<div class="wolai-page-ai-permission-row"><span>' + escapeHtml(row.label || '') + '</span><strong>' + escapeHtml(row.value || '') + '</strong></div>';
}).join('') + '</div>';
}
function pageAiRenderNonToolMessage(item) {
var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI');
if (item.kind === 'thought') {
@@ -1224,18 +1553,34 @@ export function createSidebarPageAiRenderRuntime(context) {
'</div>';
}
if (item.kind === 'permission') {
var permissionActions = item.resolved ? '' : (
var permissionOptions = Array.isArray(item.options) ? item.options : [];
var permissionActions = '';
if (!item.resolved) {
if (permissionOptions.length) {
permissionActions = '<div class="wolai-page-ai-message-actions">' + permissionOptions.map(function(option) {
var optionText = String(option.optionId || '') + ' ' + String(option.kind || '') + ' ' + String(option.name || '');
var denyLike = /reject|deny|cancel|stop|no/.test(optionText.toLowerCase());
return '<button type="button" class="wolai-page-ai-ghost' + (denyLike ? ' wolai-page-ai-ghost--danger' : '') + '" data-page-ai-permission-action="' + (denyLike ? 'deny' : 'allow') + '" data-page-ai-permission-option-id="' + escapeHtml(option.optionId || '') + '" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">' + escapeHtml(option.name || option.optionId || '选择') + '</button>';
}).join('') + '</div>';
} else {
permissionActions = '' +
'<div class="wolai-page-ai-message-actions">' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="allow" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">允许</button>' +
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="deny" data-page-ai-permission-id="' + escapeHtml(item.permissionId || '') + '">拒绝</button>' +
'</div>'
);
'</div>';
}
}
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-permission-request="' + escapeHtml(item.permissionId || '') + '">' +
'<div class="wolai-page-ai-message-role">权限</div>' +
'<div class="wolai-page-ai-message wolai-page-ai-message--tool wolai-page-ai-permission-card" data-page-ai-permission-request="' + escapeHtml(item.permissionId || '') + '">' +
'<div class="wolai-page-ai-permission-card-header">' +
'<div class="wolai-page-ai-plan-card-title-wrap">' +
'<span class="wolai-page-ai-plan-card-kicker">权限</span>' +
'<strong class="wolai-page-ai-plan-card-title">' + escapeHtml(item.toolName || 'session/request_permission') + '</strong>' +
'</div>' +
'<span class="wolai-page-ai-permission-badge">' + escapeHtml(item.resolved ? (item.decision === 'allow' || item.decision === 'allowed' ? '已允许' : '已处理') : '待确认') + '</span>' +
'</div>' +
'<div class="wolai-page-ai-message-text">' +
'<strong>' + escapeHtml(item.toolName || 'session/request_permission') + '</strong><br />' +
'<span>' + escapeHtml(item.argsSummary || item.content || '') + '</span>' +
pageAiRenderPermissionDetails(item) +
permissionActions +
'</div>' +
'</div>';
@@ -1255,6 +1600,7 @@ export function createSidebarPageAiRenderRuntime(context) {
'</details>' +
'</div>';
}
if (pageAiLooksLikeReasonixPlanMessage(item)) return pageAiRenderReasonixPlanCard(item);
var streamingAttr = item.streaming ? ' data-page-ai-streaming="true"' : '';
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--' + escapeHtml(item.role || 'assistant') + '"' + streamingAttr + '>' +
@@ -1345,9 +1691,10 @@ export function createSidebarPageAiRenderRuntime(context) {
var selected = pageAiSessionSelected(session.id);
var usage = pageAiUsageSummary(session.usage);
var agentLabel = pageAiSessionAgentLabel(session);
var meta = [agentLabel, pageAiSessionStorageLabel(session), session.permissionLevel, session.shareId, session.status, usage].filter(Boolean).join(' · ');
var statusValue = pageAiSessionStatusFilterValue(session);
var meta = [agentLabel, pageAiSessionStorageLabel(session), session.runtimeMode, session.permissionLevel, session.shareId, pageAiSessionStatusLabel(statusValue), usage].filter(Boolean).join(' · ');
return '' +
'<div class="wolai-page-ai-message wolai-page-ai-message--history' + (active ? ' is-active' : '') + (selected ? ' is-selected' : '') + '" data-page-ai-session-row="' + escapeHtml(session.id) + '" data-page-ai-session-agent="' + escapeHtml(pageAiSessionAgentFilterValue(session)) + '">' +
'<div class="wolai-page-ai-message wolai-page-ai-message--history' + (active ? ' is-active' : '') + (selected ? ' is-selected' : '') + '" data-page-ai-session-row="' + escapeHtml(session.id) + '" data-page-ai-session-agent="' + escapeHtml(pageAiSessionAgentFilterValue(session)) + '" data-page-ai-session-status="' + escapeHtml(statusValue) + '">' +
'<button type="button" class="wolai-page-ai-session-check" data-page-ai-session-select="' + escapeHtml(session.id) + '" aria-checked="' + (selected ? 'true' : 'false') + '" aria-label="选择会话 ' + escapeHtml(session.title || session.id) + '">' +
'<span>' + (selected ? '✓' : '') + '</span>' +
'</button>' +