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:
@@ -7,26 +7,133 @@ export function createSidebarPageAiPermissionRuntime(context) {
|
||||
} = context;
|
||||
const doc = documentRef || document;
|
||||
|
||||
function pageAiPermissionStorageKey() {
|
||||
var sessionId = String(pageUiState.pageAiActiveSessionId || 'no-session').trim() || 'no-session';
|
||||
var path = doc && doc.location ? String(doc.location.pathname || '') : '';
|
||||
return 'mnote.page_ai.permission_queue.v1:' + path + ':' + sessionId;
|
||||
}
|
||||
|
||||
function pageAiPersistPermissionRequests() {
|
||||
try {
|
||||
var pending = pageUiState.pageAiPermissionRequests.filter(function(item) {
|
||||
return item && item.kind === 'permission' && !item.resolved;
|
||||
}).slice(-20);
|
||||
if (window.localStorage) window.localStorage.setItem(pageAiPermissionStorageKey(), JSON.stringify(pending));
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
function pageAiRestorePermissionRequests() {
|
||||
try {
|
||||
var raw = window.localStorage ? window.localStorage.getItem(pageAiPermissionStorageKey()) : '';
|
||||
var items = raw ? JSON.parse(raw) : [];
|
||||
if (!Array.isArray(items)) items = [];
|
||||
pageUiState.pageAiPermissionRequests = items.filter(function(item) {
|
||||
return item && item.kind === 'permission' && !item.resolved;
|
||||
}).slice(-20);
|
||||
pageUiState.pageAiPermissionRequests.forEach(function(item) {
|
||||
var exists = pageUiState.pageAiMessages.some(function(message) {
|
||||
return message.kind === 'permission' && message.permissionId === item.permissionId;
|
||||
});
|
||||
if (!exists) pageUiState.pageAiMessages.push(item);
|
||||
});
|
||||
var pending = pageUiState.pageAiPermissionRequests.find(function(item) { return !item.resolved; });
|
||||
if (pending) {
|
||||
if (!pageAiApplyPermissionMode(pending)) pageAiShowPermissionDialog(pending);
|
||||
}
|
||||
return pageUiState.pageAiPermissionRequests;
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function pageAiPermissionMessage(payload, eventType) {
|
||||
payload = payload && typeof payload === 'object' ? payload : {};
|
||||
var permissionId = String(payload.permissionId || payload.permission_id || payload.id || ('perm_' + Date.now())).trim();
|
||||
var toolName = String(payload.toolName || payload.tool || payload.name || payload.method || 'session/request_permission').trim();
|
||||
var detail = pageAiPermissionDetail(payload, permissionId);
|
||||
var toolName = detail.toolName || String(payload.toolName || payload.tool || payload.name || payload.method || 'session/request_permission').trim();
|
||||
var args = payload.arguments || payload.args || payload.input || payload.params || payload;
|
||||
var decision = String(payload.decision || payload.result || '').trim();
|
||||
if (!decision && eventType === 'permission.denied') decision = 'denied';
|
||||
if (!decision && eventType === 'permission.allowed') decision = 'allowed';
|
||||
var options = pageAiPermissionOptions(payload);
|
||||
return {
|
||||
role: 'tool',
|
||||
kind: 'permission',
|
||||
permissionId: permissionId,
|
||||
runId: String(payload.runId || payload.run_id || pageUiState.pageAiCurrentRunId || '').trim(),
|
||||
toolName: toolName,
|
||||
argsSummary: pageAiPreviewValue(args),
|
||||
argsSummary: detail.summary || pageAiPreviewValue(args),
|
||||
content: decision === 'denied' ? '已自动拒绝权限请求' : (decision === 'allowed' ? '已自动允许权限请求' : '等待权限确认'),
|
||||
resolved: decision === 'denied' || decision === 'allowed',
|
||||
decision: decision
|
||||
decision: decision,
|
||||
options: options,
|
||||
permissionDetails: detail
|
||||
};
|
||||
}
|
||||
|
||||
function pageAiPermissionDetail(payload, permissionId) {
|
||||
var params = payload && payload.params && typeof payload.params === 'object' ? payload.params : {};
|
||||
var toolCall = params.toolCall && typeof params.toolCall === 'object' ? params.toolCall : {};
|
||||
var rawInput = toolCall.rawInput && typeof toolCall.rawInput === 'object' ? toolCall.rawInput : {};
|
||||
var title = String(toolCall.title || payload.toolName || payload.tool || payload.name || '').trim();
|
||||
var kind = String(toolCall.kind || params.kind || '').trim();
|
||||
var command = String(rawInput.command || rawInput.cmd || '').trim();
|
||||
var path = String(rawInput.path || rawInput.file || rawInput.target || '').trim();
|
||||
var toolName = title || String(payload.toolName || payload.tool || payload.name || 'session/request_permission').trim();
|
||||
var primary = command || path || String(rawInput.pattern || rawInput.query || '').trim() || title || '权限请求';
|
||||
var rows = [];
|
||||
rows.push({ label: '操作', value: toolName });
|
||||
if (kind) rows.push({ label: '类型', value: pageAiPermissionKindLabel(kind) });
|
||||
if (command) rows.push({ label: '命令', value: command });
|
||||
return {
|
||||
toolName: toolName,
|
||||
kind: kind,
|
||||
kindLabel: pageAiPermissionKindLabel(kind),
|
||||
command: command,
|
||||
path: path,
|
||||
summary: primary,
|
||||
rows: rows
|
||||
};
|
||||
}
|
||||
|
||||
function pageAiPermissionKindLabel(kind) {
|
||||
var normalized = String(kind || '').trim().toLowerCase();
|
||||
if (normalized === 'execute') return '执行命令';
|
||||
if (normalized === 'edit') return '写入文件';
|
||||
if (normalized === 'read') return '读取';
|
||||
if (normalized === 'other') return '其他';
|
||||
return kind || '';
|
||||
}
|
||||
|
||||
function pageAiPermissionOptions(payload) {
|
||||
var rawOptions = payload && Array.isArray(payload.options)
|
||||
? payload.options
|
||||
: (payload && payload.params && Array.isArray(payload.params.options) ? payload.params.options : []);
|
||||
return rawOptions.map(function(option) {
|
||||
option = option && typeof option === 'object' ? option : {};
|
||||
var optionId = String(option.optionId || option.option_id || option.id || '').trim();
|
||||
if (!optionId) return null;
|
||||
return {
|
||||
optionId: optionId,
|
||||
name: pageAiPermissionOptionLabel(optionId, option),
|
||||
kind: String(option.kind || '').trim()
|
||||
};
|
||||
}).filter(Boolean).slice(0, 8);
|
||||
}
|
||||
|
||||
function pageAiPermissionOptionLabel(optionId, option) {
|
||||
var id = String(optionId || '').trim().toLowerCase();
|
||||
var raw = String((option && (option.name || option.title)) || '').trim();
|
||||
if (id === 'allow_once') return raw && !/^allow$/i.test(raw) ? raw.replace(/^Allow\b/i, '允许') : '允许一次';
|
||||
if (id === 'allow_always') return '本会话允许';
|
||||
if (id === 'allow_persistent') return '始终允许';
|
||||
if (id === 'reject_once' || id === 'reject') return '拒绝';
|
||||
if (id === 'cancel') return '取消';
|
||||
if (id === 'refine' || id === 'revise') return '要求修改';
|
||||
if (id === 'accept') return '接受';
|
||||
return raw || optionId || '选择';
|
||||
}
|
||||
|
||||
function pageAiApplyPermissionEvent(eventName, payloadText) {
|
||||
var payload = null;
|
||||
try {
|
||||
@@ -46,6 +153,10 @@ export function createSidebarPageAiPermissionRuntime(context) {
|
||||
pageUiState.pageAiPermissionRequests = pageUiState.pageAiPermissionRequests.filter(function(item) {
|
||||
return item.permissionId !== message.permissionId;
|
||||
}).concat([message]).slice(-20);
|
||||
pageAiPersistPermissionRequests();
|
||||
if (!message.resolved && pageAiApplyPermissionMode(message)) {
|
||||
return;
|
||||
}
|
||||
if (!message.resolved) {
|
||||
pageAiShowPermissionDialog(message);
|
||||
} else {
|
||||
@@ -53,15 +164,76 @@ export function createSidebarPageAiPermissionRuntime(context) {
|
||||
}
|
||||
}
|
||||
|
||||
function pageAiResolvePermission(permissionId, decision) {
|
||||
function pageAiReasonixApprovalMode() {
|
||||
var preferences = pageUiState.pageAiSkillPreferences && typeof pageUiState.pageAiSkillPreferences === 'object'
|
||||
? pageUiState.pageAiSkillPreferences
|
||||
: {};
|
||||
var mode = String(preferences['ai.agent.reasonix.approval_mode'] || 'ask').trim() || 'ask';
|
||||
return mode === 'allow' || mode === 'deny' ? mode : 'ask';
|
||||
}
|
||||
|
||||
function pageAiApplyPermissionMode(message) {
|
||||
if (!message || message.resolved) return false;
|
||||
var mode = pageAiReasonixApprovalMode();
|
||||
if (mode === 'ask') return false;
|
||||
var optionId = pageAiPermissionPreferredOptionId(message, mode);
|
||||
window.setTimeout(function() {
|
||||
pageAiResolvePermission(message.permissionId, mode, optionId);
|
||||
}, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
function pageAiPermissionPreferredOptionId(message, decision) {
|
||||
var options = Array.isArray(message && message.options) ? message.options : [];
|
||||
if (!options.length) return '';
|
||||
var allowKeywords = ['allow_once', 'allow', 'approve', 'yes', 'allow_always', 'allow_persistent'];
|
||||
var denyKeywords = ['deny_once', 'reject_once', 'deny', 'reject', 'no', 'cancel', 'stop'];
|
||||
var keywords = decision === 'allow' ? allowKeywords : denyKeywords;
|
||||
for (var i = 0; i < keywords.length; i += 1) {
|
||||
var keyword = keywords[i];
|
||||
var found = options.find(function(option) {
|
||||
var text = [option && option.optionId, option && option.kind, option && option.name].map(function(value) {
|
||||
return String(value || '').toLowerCase();
|
||||
}).join(' ');
|
||||
return text.indexOf(keyword) >= 0;
|
||||
});
|
||||
if (found && found.optionId) return String(found.optionId || '');
|
||||
}
|
||||
if (decision === 'allow') {
|
||||
var allowFallback = options.find(function(option) { return !pageAiPermissionOptionRejectLike(option); });
|
||||
return allowFallback && allowFallback.optionId ? String(allowFallback.optionId || '') : '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function pageAiResolvePermission(permissionId, decision, optionId) {
|
||||
permissionId = String(permissionId || '').trim();
|
||||
decision = String(decision || '').trim() || 'deny';
|
||||
optionId = String(optionId || '').trim();
|
||||
if (!permissionId) return;
|
||||
var runId = pageUiState.pageAiCurrentRunId;
|
||||
var runId = String(pageUiState.pageAiCurrentRunId || '').trim();
|
||||
if (!runId && pageUiState.pageAiActiveSessionId && Array.isArray(pageUiState.pageAiSessions)) {
|
||||
var activeSession = pageUiState.pageAiSessions.find(function(session) {
|
||||
return session && session.id === pageUiState.pageAiActiveSessionId;
|
||||
});
|
||||
runId = String(activeSession && (activeSession.runId || activeSession.hostRunId) || '').trim();
|
||||
}
|
||||
if (!runId && Array.isArray(pageUiState.pageAiPermissionRequests)) {
|
||||
var pending = pageUiState.pageAiPermissionRequests.find(function(item) {
|
||||
return item && item.permissionId === permissionId;
|
||||
});
|
||||
runId = String(pending && pending.runId || '').trim();
|
||||
}
|
||||
if (!runId && doc && doc.documentElement) {
|
||||
runId = String(doc.documentElement.getAttribute('data-mnote-page-ai-run-id') || doc.documentElement.getAttribute('data-mnote-page-ai-active-host-run-id') || '').trim();
|
||||
}
|
||||
if (runId) {
|
||||
var body = { permissionId: permissionId, decision: decision };
|
||||
if (optionId) body.optionId = optionId;
|
||||
fetch('/api/hermes/client/runs/' + encodeURIComponent(runId) + '/resolve-permission', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ permissionId: permissionId, decision: decision })
|
||||
body: JSON.stringify(body)
|
||||
}).then(function(response) {
|
||||
if (!response.ok) console.warn('resolve-permission 后端返回异常', response.status);
|
||||
}).catch(function(err) {
|
||||
@@ -79,6 +251,7 @@ export function createSidebarPageAiPermissionRuntime(context) {
|
||||
});
|
||||
var dialog = doc.querySelector('[data-page-ai-permission-dialog]');
|
||||
if (dialog instanceof HTMLElement) dialog.hidden = true;
|
||||
pageAiPersistPermissionRequests();
|
||||
renderPageAiConversation();
|
||||
}
|
||||
|
||||
@@ -101,31 +274,70 @@ export function createSidebarPageAiPermissionRuntime(context) {
|
||||
dialog.innerHTML = '' +
|
||||
'<div class="wolai-page-ai-permission-panel" role="dialog" aria-modal="false">' +
|
||||
'<div class="wolai-page-ai-memory-title" data-page-ai-permission-tool></div>' +
|
||||
'<div class="wolai-page-ai-tool-meta" data-page-ai-permission-args></div>' +
|
||||
'<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-dialog-action>允许</button>' +
|
||||
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="deny" data-page-ai-permission-dialog-action>拒绝</button>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-ai-permission-summary" data-page-ai-permission-args></div>' +
|
||||
'<div class="wolai-page-ai-message-actions" data-page-ai-permission-actions></div>' +
|
||||
'</div>';
|
||||
doc.body.appendChild(dialog);
|
||||
}
|
||||
var tool = dialog.querySelector('[data-page-ai-permission-tool]');
|
||||
if (tool instanceof HTMLElement) tool.textContent = message.toolName || 'session/request_permission';
|
||||
if (tool instanceof HTMLElement) tool.textContent = "权限 - " + (message.toolName || "session/request_permission");
|
||||
var args = dialog.querySelector('[data-page-ai-permission-args]');
|
||||
if (args instanceof HTMLElement) args.textContent = message.argsSummary || message.content || '';
|
||||
dialog.querySelectorAll('[data-page-ai-permission-dialog-action]').forEach(function(button) {
|
||||
if (button instanceof HTMLButtonElement) {
|
||||
button.setAttribute('data-page-ai-permission-id', message.permissionId || '');
|
||||
button.disabled = Boolean(message.resolved);
|
||||
}
|
||||
});
|
||||
if (args instanceof HTMLElement) args.innerHTML = pageAiPermissionDetailsHtml(message);
|
||||
var actions = dialog.querySelector('[data-page-ai-permission-actions]');
|
||||
if (actions instanceof HTMLElement) actions.innerHTML = pageAiPermissionActionsHtml(message);
|
||||
dialog.hidden = false;
|
||||
}
|
||||
|
||||
function pageAiPermissionDetailsHtml(message) {
|
||||
var details = message && message.permissionDetails && typeof message.permissionDetails === 'object'
|
||||
? message.permissionDetails
|
||||
: {};
|
||||
var rows = Array.isArray(details.rows) ? details.rows : [];
|
||||
if (!rows.length) {
|
||||
return '<div class="wolai-page-ai-permission-row"><span>请求</span><strong>' + escapeHtml(message.argsSummary || message.content || '') + '</strong></div>';
|
||||
}
|
||||
return 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('');
|
||||
}
|
||||
|
||||
function pageAiPermissionActionsHtml(message) {
|
||||
var options = Array.isArray(message.options) ? message.options : [];
|
||||
if (!options.length) {
|
||||
return '' +
|
||||
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="allow" data-page-ai-permission-id="' + escapeAttr(message.permissionId || '') + '" data-page-ai-permission-dialog-action>允许</button>' +
|
||||
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-permission-action="deny" data-page-ai-permission-id="' + escapeAttr(message.permissionId || '') + '" data-page-ai-permission-dialog-action>拒绝</button>';
|
||||
}
|
||||
return options.map(function(option) {
|
||||
var denyLike = pageAiPermissionOptionRejectLike(option);
|
||||
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="' + escapeAttr(option.optionId || '') + '" data-page-ai-permission-id="' + escapeAttr(message.permissionId || '') + '" data-page-ai-permission-dialog-action>' + escapeHtml(option.name || option.optionId || '选择') + '</button>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function pageAiPermissionOptionRejectLike(option) {
|
||||
var text = String((option && option.optionId) || '') + ' ' + String((option && option.kind) || '') + ' ' + String((option && option.name) || '');
|
||||
text = text.toLowerCase();
|
||||
return /reject|deny|cancel|stop|no/.test(text);
|
||||
}
|
||||
|
||||
function escapeAttr(value) {
|
||||
return escapeHtml(String(value || ''));
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value == null ? '' : value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
return {
|
||||
pageAiPermissionMessage,
|
||||
pageAiApplyPermissionEvent,
|
||||
pageAiResolvePermission,
|
||||
pageAiRestorePermissionRequests,
|
||||
pageAiHidePermissionDialog,
|
||||
pageAiShowPermissionDialog
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user