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
@@ -140,6 +140,7 @@ export function createSidebarPageAiRuntime(context) {
pageAiAcpRuntimes: [],
pageAiQueueLength: 0,
pageAiQueuedItems: [],
pageAiClientQueuedPrompts: [],
pageAiStoppedRunIds: {},
pageAiAbortController: null,
pageAiContextScope: 'page',
@@ -184,8 +185,11 @@ export function createSidebarPageAiRuntime(context) {
pageAiSessionSearchResults: [],
pageAiSessionSearchTimer: 0,
pageAiSessionAgentFilter: 'all',
pageAiSessionStatusFilter: 'all',
pageAiSelectedSessionIds: {},
pageAiSessionError: '',
pageAiRuntimeStatus: null,
pageAiRuntimeStatusError: '',
pageAiPermissionRequests: []
};
Object.keys(defaults).forEach(function(key) {
@@ -205,6 +209,9 @@ export function createSidebarPageAiRuntime(context) {
if (!state.pageAiSkills || typeof state.pageAiSkills !== 'object') {
state.pageAiSkills = clonePageAiDefaultValue(defaults.pageAiSkills);
}
if (!Array.isArray(state.pageAiClientQueuedPrompts)) {
state.pageAiClientQueuedPrompts = [];
}
return state;
}
@@ -435,6 +442,9 @@ export function createSidebarPageAiRuntime(context) {
const pageAiSessionAgentLabel = (...args) => pageAiProfileRuntime.pageAiSessionAgentLabel(...args);
const pageAiSessionPreviewText = (...args) => pageAiProfileRuntime.pageAiSessionPreviewText(...args);
const pageAiSessionAgentFilterOptions = (...args) => pageAiProfileRuntime.pageAiSessionAgentFilterOptions(...args);
const pageAiSessionStatusFilterValue = (...args) => pageAiProfileRuntime.pageAiSessionStatusFilterValue(...args);
const pageAiSessionStatusLabel = (...args) => pageAiProfileRuntime.pageAiSessionStatusLabel(...args);
const pageAiSessionStatusFilterOptions = (...args) => pageAiProfileRuntime.pageAiSessionStatusFilterOptions(...args);
const pageAiFilteredHistoryRows = (...args) => pageAiProfileRuntime.pageAiFilteredHistoryRows(...args);
const pageAiTimestamp = (...args) => pageAiProfileRuntime.pageAiTimestamp(...args);
const pageAiUsageSummary = (...args) => pageAiProfileRuntime.pageAiUsageSummary(...args);
@@ -447,6 +457,7 @@ export function createSidebarPageAiRuntime(context) {
const pageAiPermissionMessage = (...args) => pageAiPermissionRuntime.pageAiPermissionMessage(...args);
const pageAiApplyPermissionEvent = (...args) => pageAiPermissionRuntime.pageAiApplyPermissionEvent(...args);
const pageAiResolvePermission = (...args) => pageAiPermissionRuntime.pageAiResolvePermission(...args);
const pageAiRestorePermissionRequests = (...args) => pageAiPermissionRuntime.pageAiRestorePermissionRequests(...args);
const pageAiHidePermissionDialog = (...args) => pageAiPermissionRuntime.pageAiHidePermissionDialog(...args);
const pageAiShowPermissionDialog = (...args) => pageAiPermissionRuntime.pageAiShowPermissionDialog(...args);
const pageAiSessionRuntime = createSidebarPageAiSessionRuntime({
@@ -638,6 +649,9 @@ export function createSidebarPageAiRuntime(context) {
pageAiSessionAgentFilterValue,
pageAiSessionAgentLabel,
pageAiSessionPreviewText,
pageAiSessionStatusFilterOptions,
pageAiSessionStatusFilterValue,
pageAiSessionStatusLabel,
pageAiSessionStorageLabel,
pageAiSessionSelected,
pageAiSelectedSessionCount,
@@ -806,6 +820,28 @@ export function createSidebarPageAiRuntime(context) {
renderPageAiControls();
}
function pageAiReasonixPreference(key, fallback) {
var preferences = pageAiDescriptorPreferenceValues();
var value = preferences[key];
if (value == null || String(value).trim() === '') return fallback;
return String(value).trim();
}
function pageAiReasonixControlState() {
return {
modelId: pageAiReasonixPreference('ai.agent.reasonix.model_id', 'deepseek-flash'),
approvalMode: pageAiReasonixPreference('ai.agent.reasonix.approval_mode', 'ask'),
planMode: pageAiReasonixPreference('ai.agent.reasonix.plan_mode', 'manual')
};
}
function pageAiApplyReasonixPlanMode(prompt) {
if (pageAiCurrentAgentId() !== 'reasonix') return prompt;
var control = pageAiReasonixControlState();
if (control.planMode !== 'auto') return prompt;
return '请按 Reasonix 自动计划模式处理:复杂或会写文件的任务先给出简短计划,并等待我批准后再执行。\n\n' + prompt;
}
function pageAiApplyAgentDescriptorsPayload(payload) {
var descriptors = pageAiNormalizeArray(payload && payload.descriptors)
.map(pageAiNormalizeAgentDescriptor)
@@ -1316,6 +1352,68 @@ export function createSidebarPageAiRuntime(context) {
}
}
function pageAiApplyRuntimeStatus(payload) {
if (!payload || typeof payload !== 'object') return null;
pageUiState.pageAiRuntimeStatus = payload;
pageUiState.pageAiRuntimeStatusError = '';
var activeRun = payload.activeRun && typeof payload.activeRun === 'object' ? payload.activeRun : null;
if (activeRun && activeRun.hostRunId) {
pageAiSetRunStatus(String(activeRun.status || 'running'), String(activeRun.hostRunId || ''));
} else if (pageAiRunStatusIsTerminal(pageUiState.pageAiRunStatus)) {
pageAiUpdateActiveRunSnapshotFromStatus(pageUiState.pageAiRunStatus, pageUiState.pageAiCurrentRunId);
} else if (!activeRun && ['running', 'tool_calling', 'queued', 'pending', 'acp_pending'].indexOf(pageUiState.pageAiRunStatus) >= 0) {
pageAiSetRunStatus('idle', pageUiState.pageAiCurrentRunId);
}
var queue = payload.session && payload.session.queue && typeof payload.session.queue === 'object' ? payload.session.queue : null;
if (queue) {
var queuedCount = Number(queue.queuedCount || 0);
pageUiState.pageAiQueueLength = Number.isFinite(queuedCount) ? Math.max(0, queuedCount) : 0;
if (Array.isArray(queue.items)) {
pageUiState.pageAiQueuedItems = queue.items.map(function(item) {
return {
queueId: String(item.queueId || ''),
sessionId: String(item.sessionId || payload.mnoteSessionId || ''),
traceId: String(item.traceId || ''),
preview: String(item.preview || ''),
queuedAt: Number(item.queuedAt || Date.now())
};
}).filter(function(item) { return item.queueId; });
}
}
var acpSessionId = payload.acp && typeof payload.acp === 'object' ? String(payload.acp.acpSessionId || '') : '';
var current = pageAiCurrentSession();
if (current && acpSessionId) {
current.acpSessionId = acpSessionId;
current.runtimeMode = String(payload.mode || '');
current.updatedAt = Date.now();
pageAiPersistSessions();
}
document.documentElement.setAttribute('data-mnote-page-ai-runtime-status-loaded', 'true');
document.documentElement.setAttribute('data-mnote-page-ai-runtime', String(payload.runtime || ''));
document.documentElement.setAttribute('data-mnote-page-ai-runtime-mode', String(payload.mode || ''));
return payload;
}
async function pageAiLoadRuntimeStatus() {
var params = new URLSearchParams();
var sessionId = String(pageUiState.pageAiActiveSessionId || '').trim();
if (sessionId) params.set('sessionId', sessionId);
var workspaceId = String(resolveWorkspaceId(document.body) || '').trim();
if (workspaceId) params.set('workspaceId', workspaceId);
var documentId = String(currentDocumentId() || '').trim();
if (documentId) params.set('documentId', documentId);
var profile = String(pageAiCurrentProfile() || '').trim();
if (profile) params.set('profile', profile);
var response = await fetch('/api/page-ai/runtime/status?' + params.toString(), {
headers: { 'accept': 'application/json' }
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(pageAiErrorMessage(payload, 'page_ai_runtime_status_failed_' + response.status));
}
return pageAiApplyRuntimeStatus(payload);
}
function pageAiActiveRunSnapshotKey() {
return pageAiStorageKey() + ':active-run';
}
@@ -1409,6 +1507,60 @@ export function createSidebarPageAiRuntime(context) {
return true;
}
function pageAiRefreshClientQueueState() {
var clientItems = pageAiNormalizeArray(pageUiState.pageAiClientQueuedPrompts);
var serverItems = pageAiNormalizeArray(pageUiState.pageAiQueuedItems)
.filter(function(item) { return String(item.queueId || '').indexOf('client_') !== 0; });
pageUiState.pageAiQueuedItems = serverItems.concat(clientItems.map(function(item) {
return {
queueId: item.queueId,
sessionId: item.sessionId || pageUiState.pageAiActiveSessionId || '',
traceId: item.traceId || '',
preview: item.preview || item.prompt || '',
queuedAt: item.queuedAt || Date.now()
};
}));
pageUiState.pageAiQueueLength = pageUiState.pageAiQueuedItems.length;
}
function pageAiEnqueueClientPrompt(prompt) {
var queueId = 'client_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);
pageUiState.pageAiClientQueuedPrompts.push({
queueId: queueId,
prompt: prompt,
preview: String(prompt || '').slice(0, 120),
sessionId: pageUiState.pageAiActiveSessionId || '',
queuedAt: Date.now()
});
pageAiRefreshClientQueueState();
pageAiSetRunStatus('queued', pageUiState.pageAiCurrentRunId);
return queueId;
}
function pageAiRemoveClientQueuedPrompt(queueId) {
var before = pageUiState.pageAiClientQueuedPrompts.length;
pageUiState.pageAiClientQueuedPrompts = pageUiState.pageAiClientQueuedPrompts.filter(function(item) {
return item.queueId !== queueId;
});
pageAiRefreshClientQueueState();
return before !== pageUiState.pageAiClientQueuedPrompts.length;
}
function pageAiStartNextClientQueuedPrompt() {
if (pageUiState.pageAiBusy) return false;
if (['running', 'tool_calling', 'pending', 'acp_pending'].indexOf(pageUiState.pageAiRunStatus) >= 0) return false;
var next = pageUiState.pageAiClientQueuedPrompts.shift();
if (!next) {
pageAiRefreshClientQueueState();
return false;
}
pageAiRefreshClientQueueState();
window.setTimeout(function() {
void sendPageAiMessage(next.prompt);
}, 0);
return true;
}
function pageAiUnwrapSearchCitationUrl(url) {
try {
var raw = '';
@@ -1668,13 +1820,21 @@ export function createSidebarPageAiRuntime(context) {
async function pageAiCancelQueuedRun(queueId) {
queueId = String(queueId || '').trim();
if (!queueId) return;
if (queueId.indexOf('client_') === 0) {
if (pageAiRemoveClientQueuedPrompt(queueId)) {
pageUiState.pageAiMessages.push({ role: 'assistant', content: '已取消一条 AI 队列项。' });
}
renderPageAiConversation();
renderPageAiControls();
return;
}
var item = pageUiState.pageAiQueuedItems.find(function(entry) {
return entry.queueId === queueId;
});
var sessionId = String(item && item.sessionId || pageUiState.pageAiActiveSessionId || '').trim();
if (!sessionId) return;
try {
var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/queue/' + encodeURIComponent(queueId), {
var response = await fetch('/api/page-ai/sessions/' + encodeURIComponent(sessionId) + '/queue/' + encodeURIComponent(queueId), {
method: 'DELETE',
headers: { 'accept': 'application/json' }
});
@@ -1696,6 +1856,40 @@ export function createSidebarPageAiRuntime(context) {
renderPageAiControls();
}
async function pageAiResetRuntime() {
var sessionId = String(pageUiState.pageAiActiveSessionId || '').trim();
if (!sessionId) return;
try {
var response = await fetch('/api/page-ai/runtime/reset', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
sessionId: sessionId,
runtime: pageUiState.pageAiAcpRuntime || '',
profile: pageAiCurrentProfile()
})
});
var payload = await response.json().catch(function(){ return null; });
if (!response.ok || !payload || payload.ok !== true) {
throw new Error(pageAiErrorMessage(payload, 'runtime_reset_failed_' + response.status));
}
pageUiState.pageAiRuntimeStatus = null;
pageUiState.pageAiRuntimeStatusError = '';
pageUiState.pageAiMessages.push({
role: 'assistant',
content: '已重置 runtimeMNote 会话历史保留,外部 provider 会话未删除。'
});
await pageAiLoadRuntimeStatus().catch(function(error) {
pageUiState.pageAiRuntimeStatusError = error instanceof Error ? error.message : String(error);
return null;
});
} catch (error) {
pageUiState.pageAiRuntimeStatusError = error instanceof Error ? error.message : String(error);
}
renderPageAiConversation();
renderPageAiControls();
}
function pageAiSetContextScope(scope) {
var next = String(scope || '').trim() || 'page';
pageUiState.pageAiContextScope = next;
@@ -2141,6 +2335,7 @@ export function createSidebarPageAiRuntime(context) {
pageUiState.pageAiAgentId = pageAiCurrentAgentId();
pageAiEnsureContextRefState();
pageAiLoadSessions();
pageAiRestorePermissionRequests();
renderPageAiSuggestions();
renderPageAiConversation();
renderPageAiProviderButtons();
@@ -2153,6 +2348,12 @@ export function createSidebarPageAiRuntime(context) {
void pageAiCheckAndResumeActiveRun().catch(function(error) {
pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error);
renderPageAiControls();
}).then(function() {
return pageAiLoadRuntimeStatus().catch(function(error) {
pageUiState.pageAiRuntimeStatusError = error instanceof Error ? error.message : String(error);
renderPageAiControls();
return null;
});
});
void pageAiLoadBackendSessions().then(function() {
return pageAiCheckAndResumeActiveRun();
@@ -2169,7 +2370,8 @@ export function createSidebarPageAiRuntime(context) {
pageAiLoadGatewayHealth(),
pageAiLoadAiPreferences(),
pageAiLoadAllowedRoots(),
pageAiLoadBackendSessions()
pageAiLoadBackendSessions(),
pageAiLoadRuntimeStatus()
]).then(function() {
renderPageAiControls();
}).then(function() {
@@ -2803,10 +3005,27 @@ export function createSidebarPageAiRuntime(context) {
}
async function sendPageAiMessage(text) {
var allowQueue = ['running', 'tool_calling'].indexOf(pageUiState.pageAiRunStatus) >= 0;
var allowQueue = pageUiState.pageAiBusy
|| ['queued', 'running', 'tool_calling', 'pending', 'acp_pending'].indexOf(pageUiState.pageAiRunStatus) >= 0;
if (pageUiState.pageAiBusy && !allowQueue) return;
var prompt = searchText(text);
if (!prompt) return;
if (allowQueue) {
pageAiEnqueueClientPrompt(prompt);
pageUiState.pageAiMessages.push({
role: 'assistant',
content: '已加入 AI 队列,前一条 run 完成后继续处理。'
});
var queuedSession = pageAiCurrentSession();
if (queuedSession) {
queuedSession.messages = pageUiState.pageAiMessages.slice();
queuedSession.updatedAt = Date.now();
}
pageAiPersistSessions();
renderPageAiConversation();
renderPageAiControls();
return;
}
if (!allowQueue) pageUiState.pageAiBusy = true;
var currentSession = null;
try {
@@ -2846,6 +3065,8 @@ export function createSidebarPageAiRuntime(context) {
if (!allowQueue && await pageAiTryBlockEditWorkflow(prompt, scopedContext, runTargetSnapshot)) {
return;
}
var reasonixSettings = pageAiCurrentAgentId() === 'reasonix' ? pageAiReasonixControlState() : null;
var promptForRun = pageAiApplyReasonixPlanMode(prompt);
var response = await fetch('/api/hermes/client/runs', {
method: 'POST',
headers: { 'content-type': 'application/json' },
@@ -2863,13 +3084,14 @@ export function createSidebarPageAiRuntime(context) {
contextScope: pageUiState.pageAiContextScope,
contextRefs: contextRefs,
allowedRoots: allowedRoots,
reasonixSettings: reasonixSettings,
skillPreferences: {
mnote: pageAiSkillPreferenceTable('mnote', ''),
reasonix: pageAiSkillPreferenceTable('reasonix', ''),
hermes: pageAiSkillPreferenceTable('hermes', runProfile)
},
message: prompt,
model: pageAiMnoteToolModel(),
message: promptForRun,
model: reasonixSettings && reasonixSettings.modelId ? reasonixSettings.modelId : pageAiMnoteToolModel(),
pageContext: requestPageContext,
editorTarget: scopedContext.editorTarget,
runTargetSnapshot: runTargetSnapshot,
@@ -2896,6 +3118,10 @@ export function createSidebarPageAiRuntime(context) {
pageAiPersistSessions();
renderPageAiConversation();
renderPageAiControls();
void pageAiLoadRuntimeStatus().catch(function(error) {
pageUiState.pageAiRuntimeStatusError = error instanceof Error ? error.message : String(error);
renderPageAiControls();
});
return;
}
if (allowQueue) {
@@ -3114,8 +3340,13 @@ export function createSidebarPageAiRuntime(context) {
pageAiPersistSessions();
} finally {
if (!allowQueue) pageUiState.pageAiBusy = false;
await pageAiLoadRuntimeStatus().catch(function(error) {
pageUiState.pageAiRuntimeStatusError = error instanceof Error ? error.message : String(error);
return null;
});
renderPageAiConversation();
renderPageAiControls();
pageAiStartNextClientQueuedPrompt();
}
}
@@ -3148,6 +3379,20 @@ export function createSidebarPageAiRuntime(context) {
void pageAiStopRun();
return true;
}
var pageAiReset = closestAction(event.target, '[data-page-ai-action="reset-runtime"]');
if (pageAiReset) {
event.preventDefault();
void pageAiResetRuntime();
return true;
}
var pageAiStopAllJobs = closestAction(event.target, '[data-page-ai-action="stop-all-jobs"]');
if (pageAiStopAllJobs) {
event.preventDefault();
pageUiState.pageAiMessages.push({ role: 'assistant', content: '当前没有可停止的后台 job;长任务 job 将在 runtime status 中独立显示。' });
renderPageAiConversation();
renderPageAiControls();
return true;
}
var pageAiRotate = closestAction(event.target, '[data-page-ai-action="rotate"]');
if (pageAiRotate) {
event.preventDefault();
@@ -3167,7 +3412,15 @@ export function createSidebarPageAiRuntime(context) {
if (pageAiTab) {
event.preventDefault();
pageUiState.pageAiPage = pageAiTab.getAttribute('data-page-ai-tab') || 'chat';
if (pageUiState.pageAiPage === 'runtime') void pageAiLoadGatewayHealth();
if (pageUiState.pageAiPage === 'runtime') pageUiState.pageAiPage = 'status';
if (pageUiState.pageAiPage === 'chat-only-settings') pageUiState.pageAiPage = 'agent';
if (pageUiState.pageAiPage === 'status') {
void pageAiLoadGatewayHealth();
void pageAiLoadRuntimeStatus().catch(function(error) {
pageUiState.pageAiRuntimeStatusError = error instanceof Error ? error.message : String(error);
renderPageAiControls();
});
}
if (pageUiState.pageAiPage === 'skills') void pageAiLoadSkills();
renderPageAiControls();
renderPageAiConversation();
@@ -3351,7 +3604,30 @@ export function createSidebarPageAiRuntime(context) {
var pageAiPermissionAction = closestAction(event.target, '[data-page-ai-permission-action]');
if (pageAiPermissionAction) {
event.preventDefault();
pageAiResolvePermission(pageAiPermissionAction.getAttribute('data-page-ai-permission-id') || '', pageAiPermissionAction.getAttribute('data-page-ai-permission-action') || 'deny');
pageAiResolvePermission(
pageAiPermissionAction.getAttribute('data-page-ai-permission-id') || '',
pageAiPermissionAction.getAttribute('data-page-ai-permission-action') || 'deny',
pageAiPermissionAction.getAttribute('data-page-ai-permission-option-id') || ''
);
return true;
}
var pageAiPlanAction = closestAction(event.target, '[data-page-ai-plan-action]');
if (pageAiPlanAction) {
event.preventDefault();
var action = String(pageAiPlanAction.getAttribute('data-page-ai-plan-action') || '').trim();
var inputNode = ensurePageAiDrawer().querySelector('[data-page-ai-input]');
if (action === 'approve') {
void sendPageAiMessage('批准,开始执行。');
return true;
}
if (action === 'cancel') {
void sendPageAiMessage('取消执行。');
return true;
}
if (inputNode instanceof HTMLTextAreaElement) {
inputNode.value = action === 'revise' ? '请修改计划:' : '';
inputNode.focus();
}
return true;
}
var pageAiOpenLocationAction = closestAction(event.target, '[data-page-ai-open-location]');
@@ -3474,6 +3750,13 @@ export function createSidebarPageAiRuntime(context) {
renderPageAiConversation();
return true;
}
var pageAiSessionStatusFilter = closestAction(event.target, '[data-page-ai-session-status-filter]');
if (pageAiSessionStatusFilter instanceof HTMLSelectElement) {
pageUiState.pageAiSessionStatusFilter = String(pageAiSessionStatusFilter.value || 'all').trim() || 'all';
renderPageAiControls();
renderPageAiConversation();
return true;
}
var pageAiHideHermesBuiltin = closestAction(event.target, '[data-page-ai-hide-hermes-builtin]');
if (pageAiHideHermesBuiltin instanceof HTMLInputElement) {
pageAiSetHideHermesBuiltinSkills(pageAiHideHermesBuiltin.checked);