import { createSidebarPageAiMarkdownRuntime } from './sidebar-page-ai-markdown-runtime.js'; import { createSidebarPageAiPermissionRuntime } from './sidebar-page-ai-permission-runtime.js'; import { createSidebarPageAiProfileRuntime } from './sidebar-page-ai-profile-runtime.js'; import { createSidebarPageAiRenderRuntime } from './sidebar-page-ai-render-runtime.js'; import { createSidebarPageAiSessionRuntime } from './sidebar-page-ai-session-runtime.js'; import { createSidebarPageAiSkillRuntime } from './sidebar-page-ai-skill-runtime.js'; import { createSidebarPageAiTargetRuntime } from './sidebar-page-ai-target-runtime.js'; export function createSidebarPageAiRuntime(context) { const { buildLocalFileOpenUrl, currentDocumentId, currentPageAggregate, currentPageOptions, currentRootUri, currentSourceKind, escapeHtml, cssEscape, openLocalResourceInActiveTab, pageUiState, resolveWorkspaceId, searchText, } = context; var PAGE_AI_SESSION_STORAGE_VERSION = 4; // Page AI 请求合同:agentId 只负责路由;contextRefs 是用户勾选的上下文地址; // allowedRoots 只展示 SQLite directory_grants 解析结果,服务端仍会按当前用户重算; // runTargetSnapshot 必须来自 OpenEditorsSnapshot,避免运行中 UI 切换导致目标漂移。 var PAGE_AI_AGENT_FALLBACK_REGISTRY = [ { id: 'hermes', label: 'Hermes', acpRuntime: 'hermes', canWriteFiles: true }, { id: 'reasonix', label: 'Reasonix', acpRuntime: 'reasonix', canWriteFiles: true }, { id: 'chat_only', label: 'Chat-only', acpRuntime: 'hermes', canWriteFiles: false } ]; var PAGE_AI_DESCRIPTOR_SCHEMA = 'mnote.ai_agent_descriptor.v1'; var PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY = [ { profileId: 'shared_deepseek_chat', baseProfile: 'deepseek-chat', label: 'DeepSeek' }, { profileId: 'shared_doubao_chat', baseProfile: 'doubao-chat', label: '豆包' }, { profileId: 'shared_gemini_chat', baseProfile: 'gemini-chat', label: 'Gemini' }, { profileId: 'shared_api_deepseek_flash_chat', baseProfile: 'api-deepseek-flash-chat', label: 'DeepSeek Flash', providerKind: 'api-chat' }, { profileId: 'shared_api_deepseek_pro_chat', baseProfile: 'api-deepseek-pro-chat', label: 'DeepSeek Pro', providerKind: 'api-chat' }, { profileId: 'shared_api_gpt_chat', baseProfile: 'api-gpt-chat', label: 'GPT', providerKind: 'api-chat' }, { profileId: 'shared_api_kimi_chat', baseProfile: 'api-kimi-chat', label: 'Kimi', providerKind: 'api-chat' }, { profileId: 'shared_api_gemini_chat', baseProfile: 'api-gemini-chat', label: 'Gemini API', providerKind: 'api-chat' }, { profileId: 'shared_api_grok_chat', baseProfile: 'api-grok-chat', label: 'Grok API', providerKind: 'api-chat' } ]; var PAGE_AI_CONTEXT_REF_REGISTRY = [ { id: 'current_page', label: '当前页' }, { id: 'selection', label: '选区' }, { id: 'active_editor', label: '打开资源' }, { id: 'file', label: '文件' }, { id: 'folder', label: '文件夹' }, { id: 'changed_files', label: '最近修改' } ]; var pageAiDelegatesInstalled = false; var PAGE_AI_DRAWER_WIDTH_STORAGE_KEY = 'mnote.page_ai.drawer_width'; var PAGE_AI_DRAWER_DEFAULT_WIDTH = 440; var PAGE_AI_DRAWER_MIN_WIDTH = 340; var PAGE_AI_DRAWER_MAX_WIDTH = 760; var PAGE_AI_OPENCODE_MODEL = { providerID: 'opencodego', modelID: 'deepseek-v4-flash' }; function clonePageAiDefaultValue(value) { if (Array.isArray(value)) return value.slice(); if (value && typeof value === 'object') return Object.assign({}, value); return value; } function pageAiClampDrawerWidth(width) { var viewportMax = Math.max(PAGE_AI_DRAWER_MIN_WIDTH, Math.min(PAGE_AI_DRAWER_MAX_WIDTH, window.innerWidth - 24)); var numeric = Number(width); if (!Number.isFinite(numeric) || numeric <= 0) numeric = PAGE_AI_DRAWER_DEFAULT_WIDTH; return Math.max(PAGE_AI_DRAWER_MIN_WIDTH, Math.min(viewportMax, Math.round(numeric))); } function pageAiStoredDrawerWidth() { try { return pageAiClampDrawerWidth(window.localStorage ? window.localStorage.getItem(PAGE_AI_DRAWER_WIDTH_STORAGE_KEY) : PAGE_AI_DRAWER_DEFAULT_WIDTH); } catch (_error) { return pageAiClampDrawerWidth(PAGE_AI_DRAWER_DEFAULT_WIDTH); } } function pageAiApplyDrawerWidth(drawer, width) { var target = drawer instanceof HTMLElement ? drawer : document.querySelector('[data-testid="wolai-page-ai-drawer"]'); if (!(target instanceof HTMLElement)) return; var next = pageAiClampDrawerWidth(width == null ? pageAiStoredDrawerWidth() : width); target.style.setProperty('--mnote-page-ai-width', next + 'px'); } function handlePageAiPointerDown(event, helpers) { var closestAction = helpers && helpers.closestAction; if (typeof closestAction !== 'function') return false; var handle = closestAction(event.target, '[data-page-ai-resize-handle]'); if (!(handle instanceof HTMLElement)) return false; var drawer = handle.closest('[data-testid="wolai-page-ai-drawer"]'); var panel = drawer instanceof HTMLElement ? drawer.querySelector('.wolai-page-ai-panel') : null; if (!(drawer instanceof HTMLElement) || !(panel instanceof HTMLElement)) return false; event.preventDefault(); var pointerId = event.pointerId; var startX = Number(event.clientX || 0); var startWidth = panel.getBoundingClientRect().width || pageAiStoredDrawerWidth(); drawer.setAttribute('data-page-ai-resizing', 'true'); document.documentElement.setAttribute('data-mnote-page-ai-resizing', 'true'); try { handle.setPointerCapture(pointerId); } catch (_error) {} function move(nextEvent) { var nextWidth = startWidth + (startX - Number(nextEvent.clientX || 0)); pageAiApplyDrawerWidth(drawer, nextWidth); } function finish(nextEvent) { move(nextEvent); var value = drawer.style.getPropertyValue('--mnote-page-ai-width').replace('px', '').trim(); try { if (window.localStorage) window.localStorage.setItem(PAGE_AI_DRAWER_WIDTH_STORAGE_KEY, String(pageAiClampDrawerWidth(value))); } catch (_error) {} drawer.removeAttribute('data-page-ai-resizing'); document.documentElement.removeAttribute('data-mnote-page-ai-resizing'); window.removeEventListener('pointermove', move, true); window.removeEventListener('pointerup', finish, true); window.removeEventListener('pointercancel', finish, true); try { handle.releasePointerCapture(pointerId); } catch (_error) {} } window.addEventListener('pointermove', move, true); window.addEventListener('pointerup', finish, true); window.addEventListener('pointercancel', finish, true); return true; } function ensurePageAiStateFacade(state) { var defaults = { pageAiOpen: false, pageAiBusy: false, pageAiMessages: [], pageAiSuggestionIndex: 0, pageAiProvider: 'hermes', pageAiPage: 'chat', pageAiRunStatus: 'idle', pageAiCurrentRunId: '', pageAiAcpRuntime: 'reasonix', pageAiAcpRuntimes: [], pageAiQueueLength: 0, pageAiQueuedItems: [], pageAiClientQueuedPrompts: [], pageAiStoppedRunIds: {}, pageAiAbortController: null, pageAiContextScope: 'page', pageAiContextPopoverOpen: false, pageAiTargetPopoverOpen: false, pageAiSelectedTargetId: '', pageAiSelectedContextRefs: null, pageAiCurrentRunTargetSnapshot: null, pageAiAllowedRoots: [], pageAiAllowedRootsError: '', pageAiAgentId: 'reasonix', pageAiAgentPopoverOpen: false, pageAiAgentDescriptors: [], pageAiAgentDescriptorError: '', pageAiAgentDescriptorsLoaded: false, pageAiTools: [], pageAiToolsError: '', pageAiGatewayHealth: null, pageAiGatewayHealthError: '', pageAiLastToolCall: null, pageAiProfiles: [], pageAiActiveProfileName: 'mnoteai', pageAiProfileError: '', pageAiProfileMemory: { memory: '', user: '', soul: '' }, pageAiProfileMemoryDrafts: { memory: '', user: '', soul: '' }, pageAiProfileMemoryError: '', pageAiSkills: { categories: [], archived: [] }, pageAiSkillCatalogs: { mnote: { categories: [], archived: [] }, reasonix: { categories: [], archived: [] }, hermes: { categories: [], archived: [] } }, pageAiSkillPreferences: {}, pageAiCollapsedSkillGroups: {}, pageAiSkillQuery: '', pageAiSkillError: '', pageAiSkillLoadSeq: 0, pageAiActiveSkillSource: 'mnote', pageAiSessions: [], pageAiActiveSessionId: '', pageAiBoardWorkerId: 'mnote-page-ai-zcode', pageAiBoardWorkflowId: 'builtin-mnote-page-ai-chat', pageAiBoardModelOverride: 'zcode-default', pageAiBoardRunDetails: {}, pageAiBoardActiveDetailRunId: '', pageAiOpencodeStatus: null, pageAiOpencodeStatusError: '', pageAiOpencodeChangedFiles: [], pageAiOpencodeDiffError: '', pageAiOpencodeSessionId: '', pageAiOpencodeBaseUrl: 'http://127.0.0.1:4096', pageAiOpencodeIframeUrl: '', pageAiOpencodeBindingKey: '', pageAiOpencodeBindingInFlight: false, pageAiOpencodeEventSource: null, pageAiOpencodeEventSessionId: '', pageAiOpencodeEventTimer: 0, pageAiSessionSearchQuery: '', pageAiSessionSearchResults: [], pageAiSessionSearchTimer: 0, pageAiSessionAgentFilter: 'all', pageAiSessionStatusFilter: 'all', pageAiSelectedSessionIds: {}, pageAiSessionError: '', pageAiRuntimeStatus: null, pageAiRuntimeStatusError: '', pageAiPermissionRequests: [] }; Object.keys(defaults).forEach(function(key) { if (typeof state[key] === 'undefined') { state[key] = clonePageAiDefaultValue(defaults[key]); } }); if (!state.pageAiSkillCatalogs || typeof state.pageAiSkillCatalogs !== 'object') { state.pageAiSkillCatalogs = clonePageAiDefaultValue(defaults.pageAiSkillCatalogs); } if (!state.pageAiProfileMemory || typeof state.pageAiProfileMemory !== 'object') { state.pageAiProfileMemory = clonePageAiDefaultValue(defaults.pageAiProfileMemory); } if (!state.pageAiProfileMemoryDrafts || typeof state.pageAiProfileMemoryDrafts !== 'object') { state.pageAiProfileMemoryDrafts = clonePageAiDefaultValue(defaults.pageAiProfileMemoryDrafts); } if (!state.pageAiSkills || typeof state.pageAiSkills !== 'object') { state.pageAiSkills = clonePageAiDefaultValue(defaults.pageAiSkills); } if (!Array.isArray(state.pageAiClientQueuedPrompts)) { state.pageAiClientQueuedPrompts = []; } return state; } ensurePageAiStateFacade(pageUiState); function closestAction(target, selector) { var node = target && target.nodeType === Node.TEXT_NODE ? target.parentElement : target; return node && typeof node.closest === 'function' ? node.closest(selector) : null; } const pageAiMarkdown = createSidebarPageAiMarkdownRuntime({ escapeHtml }); const textFromUnknown = (...args) => pageAiMarkdown.textFromUnknown(...args); const renderPageAiMarkdown = (...args) => pageAiMarkdown.renderPageAiMarkdown(...args); let pageAiRenderRuntime = null; function pageAiRenderApi() { if (!pageAiRenderRuntime) throw new Error('page_ai_render_runtime_not_ready'); return pageAiRenderRuntime; } function ensurePageAiDrawer(...args) { return pageAiRenderApi().ensurePageAiDrawer(...args); } function humanizePageAiResponse(...args) { return pageAiRenderApi().humanizePageAiResponse(...args); } function pageAiContextButtonSummary(...args) { return pageAiRenderApi().pageAiContextButtonSummary(...args); } function pageAiContextRefDetail(...args) { return pageAiRenderApi().pageAiContextRefDetail(...args); } function pageAiContextRefDisabled(...args) { return pageAiRenderApi().pageAiContextRefDisabled(...args); } function pageAiContextRefLabel(...args) { return pageAiRenderApi().pageAiContextRefLabel(...args); } function pageAiContextScopeLabel(...args) { return pageAiRenderApi().pageAiContextScopeLabel(...args); } function pageAiCurrentAgentSelectionLabel(...args) { return pageAiRenderApi().pageAiCurrentAgentSelectionLabel(...args); } function pageAiCurrentModelLabel(...args) { return pageAiRenderApi().pageAiCurrentModelLabel(...args); } function pageAiFilteredSkillEntries(...args) { return pageAiRenderApi().pageAiFilteredSkillEntries(...args); } function pageAiFormatChangedFiles(...args) { return pageAiRenderApi().pageAiFormatChangedFiles(...args); } function pageAiMemoryFileLabel(...args) { return pageAiRenderApi().pageAiMemoryFileLabel(...args); } function pageAiRenderAgentProfileOption(...args) { return pageAiRenderApi().pageAiRenderAgentProfileOption(...args); } function pageAiRunStatusLabel(...args) { return pageAiRenderApi().pageAiRunStatusLabel(...args); } function pageAiSuggestions(...args) { return pageAiRenderApi().pageAiSuggestions(...args); } function pageAiTargetDetail(...args) { return pageAiRenderApi().pageAiTargetDetail(...args); } function pageAiTargetLabel(...args) { return pageAiRenderApi().pageAiTargetLabel(...args); } function renderPageAiControls(...args) { return pageAiRenderApi().renderPageAiControls(...args); } function renderPageAiConversation(...args) { return pageAiRenderApi().renderPageAiConversation(...args); } function renderPageAiProviderButtons(...args) { return pageAiRenderApi().renderPageAiProviderButtons(...args); } function renderPageAiSuggestions(...args) { return pageAiRenderApi().renderPageAiSuggestions(...args); } function readLocalEditorBlocks() { var editor = document.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror'); if (!(editor instanceof HTMLElement)) return []; return Array.from(editor.children).filter(function(node) { return node instanceof HTMLElement; }).map(function(node, index) { var tag = String(node.tagName || '').toUpperCase(); var headingMatch = tag.match(/^H([1-6])$/); var type = headingMatch ? 'heading' : 'paragraph'; var text = searchText(node.textContent || ''); var id = searchText(node.getAttribute('data-id') || node.id || 'local-block-' + index); var props = headingMatch ? { level: Number(headingMatch[1]) } : {}; return { id: id, type: type, props: props, content: text }; }).filter(function(block) { return block.content || block.type === 'heading'; }); } function buildPageAiLocalSubtree(blocks, title) { var documentId = currentDocumentId() || 'current-page'; var rootNodeId = 'page:' + documentId; var headingCounters = [0, 0, 0, 0, 0, 0]; var headingStack = []; var nodes = [{ id: rootNodeId, nodeId: rootNodeId, nodeType: 'page', blockId: null, blockType: 'page', title: title || '', parentNodeId: null, headingLevel: null }]; var outline = []; var evidence = []; blocks.forEach(function(block, index) { var level = block.type === 'heading' ? Math.max(1, Math.min(6, Number(block.props && block.props.level || 1))) : null; if (level != null) { while (headingStack.length && headingStack[headingStack.length - 1].level >= level) headingStack.pop(); } var parentNodeId = headingStack.length ? headingStack[headingStack.length - 1].nodeId : rootNodeId; var nodeId = 'local-node:' + String(block.id || index); var titleText = block.content || (block.type === 'heading' ? '未命名标题' : ''); nodes.push({ id: nodeId, nodeId: nodeId, nodeType: 'block', blockId: block.id, blockType: block.type, title: titleText, parentNodeId: parentNodeId, headingLevel: level }); if (level != null) { headingCounters[level - 1] += 1; for (var i = level; i < headingCounters.length; i += 1) headingCounters[i] = 0; var numbering = headingCounters.slice(0, level).filter(function(count) { return count > 0; }).join('.'); outline.push({ id: 'local-outline:' + String(block.id || index), nodeId: nodeId, anchorBlockId: block.id, level: level, title: titleText, numbering: numbering }); headingStack.push({ level: level, nodeId: nodeId }); } if (titleText) { evidence.push({ id: 'local-evidence:' + String(block.id || index), nodeId: nodeId, anchorBlockId: block.id, text: titleText, kind: block.type }); } }); return { projectionId: 'local-editor-dom:' + documentId, rootNode: { id: rootNodeId, documentId: documentId, title: title || '', nodeType: 'page' }, subtree: { rootNodeId: rootNodeId, nodes: nodes }, outline: outline, evidence: evidence, stats: { nodeCount: nodes.length, headingCount: outline.length, evidenceCount: evidence.length }, source: 'local' }; } function currentPageAiContextSnapshot() { var aggregate = currentPageAggregate(); var body = aggregate.body || {}; var serverSubtree = aggregate.tree && aggregate.tree.pageSubtree ? aggregate.tree.pageSubtree : null; return { aggregate: aggregate, body: body, subtree: serverSubtree, pageSubtreeSource: serverSubtree ? 'server' : 'none' }; } function pageAiNormalizeAgentDescriptor(descriptor) { if (!descriptor || typeof descriptor !== 'object') return null; var agentId = String(descriptor.agentId || descriptor.id || '').trim(); if (!agentId) return null; return Object.assign({}, descriptor, { id: agentId, agentId: agentId, label: String(descriptor.displayName || descriptor.label || agentId).trim() || agentId, acpRuntime: String(descriptor.acpRuntime || descriptor.acp_runtime || '').trim() || (agentId === 'reasonix' ? 'reasonix' : 'hermes'), canWriteFiles: descriptor.canWriteFiles !== false, capabilities: pageAiNormalizeArray(descriptor.capabilities), fields: pageAiNormalizeArray(descriptor.fields), tools: pageAiNormalizeArray(descriptor.tools), capabilityPacks: pageAiNormalizeArray(descriptor.capabilityPacks), schema: String(descriptor.schema || PAGE_AI_DESCRIPTOR_SCHEMA).trim() }); } function pageAiAgentRegistry() { var descriptors = pageAiNormalizeArray(pageUiState.pageAiAgentDescriptors) .map(pageAiNormalizeAgentDescriptor) .filter(Boolean); return descriptors.length ? descriptors : PAGE_AI_AGENT_FALLBACK_REGISTRY; } function pageAiAgentDescriptor(agentId) { var normalized = String(agentId || '').trim(); if (!normalized) normalized = pageAiCurrentAgentId(); return pageAiAgentRegistry().find(function(agent) { return agent.id === normalized || agent.agentId === normalized; }) || null; } function pageAiNormalizeAgentId(agentId) { var value = String(agentId || '').trim(); return pageAiAgentRegistry().some(function(agent) { return agent.id === value; }) ? value : 'reasonix'; } function pageAiAgentRecord(agentId) { var normalized = pageAiNormalizeAgentId(agentId); return pageAiAgentRegistry().find(function(agent) { return agent.id === normalized; }) || pageAiAgentRegistry()[1] || PAGE_AI_AGENT_FALLBACK_REGISTRY[1]; } function pageAiCurrentAgentId() { var explicit = String(pageUiState.pageAiAgentId || '').trim(); if (explicit) return pageAiNormalizeAgentId(explicit); var runtime = String(pageUiState.pageAiAcpRuntime || 'reasonix').trim(); if (runtime === 'hermes') return 'hermes'; return 'reasonix'; } const pageAiProfileRuntime = createSidebarPageAiProfileRuntime({ chatOnlyProfileRegistry: PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY, documentRef: document, pageAiAgentRecord, pageAiCurrentAgentId, pageAiNormalizeAgentId, pageUiState, }); const pageAiProviderLabel = (...args) => pageAiProfileRuntime.pageAiProviderLabel(...args); const pageAiNormalizeArray = (...args) => pageAiProfileRuntime.pageAiNormalizeArray(...args); const pageAiDefaultAcpRuntimes = (...args) => pageAiProfileRuntime.pageAiDefaultAcpRuntimes(...args); const pageAiNormalizeAcpRuntimes = (...args) => pageAiProfileRuntime.pageAiNormalizeAcpRuntimes(...args); const pageAiUnwrapUpstream = (...args) => pageAiProfileRuntime.pageAiUnwrapUpstream(...args); const pageAiProfileValue = (...args) => pageAiProfileRuntime.pageAiProfileValue(...args); const pageAiCurrentProfile = (...args) => pageAiProfileRuntime.pageAiCurrentProfile(...args); const pageAiRunProfile = (...args) => pageAiProfileRuntime.pageAiRunProfile(...args); const pageAiMnoteToolModel = (...args) => pageAiProfileRuntime.pageAiMnoteToolModel(...args); const pageAiCurrentProfileRecord = (...args) => pageAiProfileRuntime.pageAiCurrentProfileRecord(...args); const pageAiChatOnlyProfileSpec = (...args) => pageAiProfileRuntime.pageAiChatOnlyProfileSpec(...args); const pageAiDefaultChatOnlyProfileSpec = (...args) => pageAiProfileRuntime.pageAiDefaultChatOnlyProfileSpec(...args); const pageAiNormalizeChatOnlyProfileId = (...args) => pageAiProfileRuntime.pageAiNormalizeChatOnlyProfileId(...args); const pageAiProfileDisplayLabel = (...args) => pageAiProfileRuntime.pageAiProfileDisplayLabel(...args); const pageAiProfileRecordById = (...args) => pageAiProfileRuntime.pageAiProfileRecordById(...args); const pageAiSessionAgentFilterValue = (...args) => pageAiProfileRuntime.pageAiSessionAgentFilterValue(...args); 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); const pageAiPermissionRuntime = createSidebarPageAiPermissionRuntime({ documentRef: document, pageAiPreviewValue, pageUiState, renderPageAiConversation, }); 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({ currentDocumentId, currentRootUri, currentSourceKind, documentRef: document, pageAiApplyRuntimeState, pageAiCurrentAgentId, pageAiCurrentProfile, pageAiErrorMessage, pageAiNormalizeAgentId, pageAiNormalizeArray, pageAiNormalizeChatOnlyProfileId, pageAiPermissionMessage, pageAiPreviewValue, pageAiRunProfile, pageAiSetActiveProfile, pageAiTimestamp, pageUiState, renderPageAiControls, renderPageAiConversation, resolveWorkspaceId, sessionStorageVersion: PAGE_AI_SESSION_STORAGE_VERSION, windowRef: window, }); const pageAiStorageKey = (...args) => pageAiSessionRuntime.pageAiStorageKey(...args); const pageAiBackendSessionQuery = (...args) => pageAiSessionRuntime.pageAiBackendSessionQuery(...args); const pageAiNewSession = (...args) => pageAiSessionRuntime.pageAiNewSession(...args); const pageAiNormalizeSessions = (...args) => pageAiSessionRuntime.pageAiNormalizeSessions(...args); const pageAiNormalizeBackendSessionRow = (...args) => pageAiSessionRuntime.pageAiNormalizeBackendSessionRow(...args); const pageAiMergeSessions = (...args) => pageAiSessionRuntime.pageAiMergeSessions(...args); const pageAiSessionStorageLabel = (...args) => pageAiSessionRuntime.pageAiSessionStorageLabel(...args); const pageAiLoadSessions = (...args) => pageAiSessionRuntime.pageAiLoadSessions(...args); const pageAiLoadBackendSessions = (...args) => pageAiSessionRuntime.pageAiLoadBackendSessions(...args); const pageAiMessageFromRuntimeEvent = (...args) => pageAiSessionRuntime.pageAiMessageFromRuntimeEvent(...args); const pageAiApplyBackendSessionDetail = (...args) => pageAiSessionRuntime.pageAiApplyBackendSessionDetail(...args); const pageAiLoadBackendSessionDetail = (...args) => pageAiSessionRuntime.pageAiLoadBackendSessionDetail(...args); const pageAiSearchBackendSessions = (...args) => pageAiSessionRuntime.pageAiSearchBackendSessions(...args); const pageAiPersistSessions = (...args) => pageAiSessionRuntime.pageAiPersistSessions(...args); const pageAiEnsureHermesSession = (...args) => pageAiSessionRuntime.pageAiEnsureHermesSession(...args); const pageAiRestoreHermesSession = (...args) => pageAiSessionRuntime.pageAiRestoreHermesSession(...args); const pageAiCurrentSession = (...args) => pageAiSessionRuntime.pageAiCurrentSession(...args); const pageAiSyncCurrentSessionMessages = (...args) => pageAiSessionRuntime.pageAiSyncCurrentSessionMessages(...args); const pageAiSetActiveSession = (...args) => pageAiSessionRuntime.pageAiSetActiveSession(...args); const pageAiStartNewSession = (...args) => pageAiSessionRuntime.pageAiStartNewSession(...args); const pageAiRenameBackendSession = (...args) => pageAiSessionRuntime.pageAiRenameBackendSession(...args); const pageAiExportBackendSession = (...args) => pageAiSessionRuntime.pageAiExportBackendSession(...args); const pageAiDeleteBackendSession = (...args) => pageAiSessionRuntime.pageAiDeleteBackendSession(...args); const pageAiDeleteSelectedBackendSessions = (...args) => pageAiSessionRuntime.pageAiDeleteSelectedBackendSessions(...args); const pageAiResumeBackendSession = (...args) => pageAiSessionRuntime.pageAiResumeBackendSession(...args); const pageAiSessionSelected = (...args) => pageAiSessionRuntime.pageAiSessionSelected(...args); const pageAiToggleSessionSelection = (...args) => pageAiSessionRuntime.pageAiToggleSessionSelection(...args); const pageAiClearSessionSelection = (...args) => pageAiSessionRuntime.pageAiClearSessionSelection(...args); const pageAiSelectedSessionCount = (...args) => pageAiSessionRuntime.pageAiSelectedSessionCount(...args); const pageAiCheckActiveRun = (...args) => pageAiSessionRuntime.pageAiCheckActiveRun(...args); const pageAiSkillRuntime = createSidebarPageAiSkillRuntime({ pageAiCurrentAgentId, pageAiCurrentProfile, pageAiLoadSkills, pageAiNormalizeArray, pageAiPersistAiPreference, pageAiPersistRawAiPreference, pageAiProfileValue, pageUiState, renderPageAiControls, }); const pageAiSkillSourceOptions = (...args) => pageAiSkillRuntime.pageAiSkillSourceOptions(...args); const pageAiDefaultSkillSource = (...args) => pageAiSkillRuntime.pageAiDefaultSkillSource(...args); const pageAiNormalizeSkillSource = (...args) => pageAiSkillRuntime.pageAiNormalizeSkillSource(...args); const pageAiCurrentSkillSource = (...args) => pageAiSkillRuntime.pageAiCurrentSkillSource(...args); const pageAiSetSkillSource = (...args) => pageAiSkillRuntime.pageAiSetSkillSource(...args); const pageAiSkillSourceParts = (...args) => pageAiSkillRuntime.pageAiSkillSourceParts(...args); const pageAiCurrentSkillSourceLabel = (...args) => pageAiSkillRuntime.pageAiCurrentSkillSourceLabel(...args); const pageAiSkillOriginLabel = (...args) => pageAiSkillRuntime.pageAiSkillOriginLabel(...args); const pageAiSkillPreferenceKey = (...args) => pageAiSkillRuntime.pageAiSkillPreferenceKey(...args); const pageAiSkillPreferenceTable = (...args) => pageAiSkillRuntime.pageAiSkillPreferenceTable(...args); const pageAiHermesHideBuiltinPreferenceKey = (...args) => pageAiSkillRuntime.pageAiHermesHideBuiltinPreferenceKey(...args); const pageAiHideHermesBuiltinSkills = (...args) => pageAiSkillRuntime.pageAiHideHermesBuiltinSkills(...args); const pageAiReasonixMemoryEnabled = (...args) => pageAiSkillRuntime.pageAiReasonixMemoryEnabled(...args); const pageAiSetReasonixMemoryEnabled = (...args) => pageAiSkillRuntime.pageAiSetReasonixMemoryEnabled(...args); const pageAiSetHideHermesBuiltinSkills = (...args) => pageAiSkillRuntime.pageAiSetHideHermesBuiltinSkills(...args); const pageAiSkillIsBuiltin = (...args) => pageAiSkillRuntime.pageAiSkillIsBuiltin(...args); const pageAiToggleableSkillEntries = (...args) => pageAiSkillRuntime.pageAiToggleableSkillEntries(...args); const pageAiAllSkillEntries = (...args) => pageAiSkillRuntime.pageAiAllSkillEntries(...args); const pageAiSkillGroupCollapsed = (...args) => pageAiSkillRuntime.pageAiSkillGroupCollapsed(...args); const pageAiToggleSkillGroup = (...args) => pageAiSkillRuntime.pageAiToggleSkillGroup(...args); const pageAiSetSkillPreference = (...args) => pageAiSkillRuntime.pageAiSetSkillPreference(...args); const pageAiSkillEnabled = (...args) => pageAiSkillRuntime.pageAiSkillEnabled(...args); function pageAiEnsureContextRefState() { if (!pageUiState.pageAiSelectedContextRefs || typeof pageUiState.pageAiSelectedContextRefs !== 'object') { pageUiState.pageAiSelectedContextRefs = { current_page: true, selection: false, active_editor: true, file: false, folder: false, changed_files: false }; } return pageUiState.pageAiSelectedContextRefs; } const pageAiTargetRuntime = createSidebarPageAiTargetRuntime({ currentDocumentId, currentPageOptions, currentRootUri, currentSourceKind, documentRef: document, escapeHtml, pageAiEnsureContextRefState, pageAiNormalizeArray, pageUiState, resolveWorkspaceId, searchText, }); const currentPageAiOpenEditorsSnapshot = (...args) => pageAiTargetRuntime.currentPageAiOpenEditorsSnapshot(...args); const currentPageAiEditorTarget = (...args) => pageAiTargetRuntime.currentPageAiEditorTarget(...args); const currentPageAiPageEditorTarget = (...args) => pageAiTargetRuntime.currentPageAiPageEditorTarget(...args); const currentPageAiScopedEditorTarget = (...args) => pageAiTargetRuntime.currentPageAiScopedEditorTarget(...args); const pageAiCloneJson = (...args) => pageAiTargetRuntime.pageAiCloneJson(...args); const pageAiEditorTargetCandidates = (...args) => pageAiTargetRuntime.pageAiEditorTargetCandidates(...args); const pageAiFallbackEditorTarget = (...args) => pageAiTargetRuntime.pageAiFallbackEditorTarget(...args); const pageAiResourceKindForTarget = (...args) => pageAiTargetRuntime.pageAiResourceKindForTarget(...args); const pageAiTargetFromOpenEditor = (...args) => pageAiTargetRuntime.pageAiTargetFromOpenEditor(...args); const pageAiTargetId = (...args) => pageAiTargetRuntime.pageAiTargetId(...args); const localMarkdownRelativePathFromPageAiDocumentId = (...args) => pageAiTargetRuntime.localMarkdownRelativePathFromPageAiDocumentId(...args); const localMarkdownDocumentIdFromPageAiRelativePath = (...args) => pageAiTargetRuntime.localMarkdownDocumentIdFromPageAiRelativePath(...args); const pageAiWorkspacePathForDocument = (...args) => pageAiTargetRuntime.pageAiWorkspacePathForDocument(...args); const pageAiWorkspacePathForTarget = (...args) => pageAiTargetRuntime.pageAiWorkspacePathForTarget(...args); const pageAiBuildAllowedRoots = (...args) => pageAiTargetRuntime.pageAiBuildAllowedRoots(...args); const pageAiBuildContextRefs = (...args) => pageAiTargetRuntime.pageAiBuildContextRefs(...args); const pageAiBuildRunTargetSnapshot = (...args) => pageAiTargetRuntime.pageAiBuildRunTargetSnapshot(...args); const pageAiPageContextForRefs = (...args) => pageAiTargetRuntime.pageAiPageContextForRefs(...args); const pageAiSetRunTargetSnapshot = (...args) => pageAiTargetRuntime.pageAiSetRunTargetSnapshot(...args); const assertPageAiTargetInCurrentWorkspace = (...args) => pageAiTargetRuntime.assertPageAiTargetInCurrentWorkspace(...args); const pageAiBuildAgentTargetPackage = (...args) => pageAiTargetRuntime.pageAiBuildAgentTargetPackage(...args); const pageAiBlockingDirtyState = (...args) => pageAiTargetRuntime.pageAiBlockingDirtyState(...args); const fetchPageAiTargetBufferState = (...args) => pageAiTargetRuntime.fetchPageAiTargetBufferState(...args); const assertPageAiTargetWritable = (...args) => pageAiTargetRuntime.assertPageAiTargetWritable(...args); const currentPageAiSelectedText = (...args) => pageAiTargetRuntime.currentPageAiSelectedText(...args); const pageAiProjectionBlocks = (...args) => pageAiTargetRuntime.pageAiProjectionBlocks(...args); const pageAiBlockText = (...args) => pageAiTargetRuntime.pageAiBlockText(...args); const pageAiSelectedBlockIdsFromSelection = (...args) => pageAiTargetRuntime.pageAiSelectedBlockIdsFromSelection(...args); const pageAiBlocksToPageXml = (...args) => pageAiTargetRuntime.pageAiBlocksToPageXml(...args); const buildPageAiContext = (...args) => pageAiTargetRuntime.buildPageAiContext(...args); const pageAiScopedPageContext = (...args) => pageAiTargetRuntime.pageAiScopedPageContext(...args); pageAiRenderRuntime = createSidebarPageAiRenderRuntime({ contextRefRegistry: PAGE_AI_CONTEXT_REF_REGISTRY, cssEscape, currentDocumentId, currentPageAiEditorTarget, currentPageAiSelectedText, documentRef: document, escapeHtml, localMarkdownRelativePathFromPageAiDocumentId, pageAiAgentDescriptor, pageAiAgentRecord, pageAiAllSkillEntries, pageAiBuildAllowedRoots, pageAiChatOnlyProfileEntries, pageAiChatOnlyProfileSpec, pageAiCurrentAgentId, pageAiCurrentProfile, pageAiCurrentProfileRecord, pageAiCurrentSession, pageAiCurrentSkillSource, pageAiCurrentSkillSourceLabel, pageAiDefaultChatOnlyProfileSpec, pageAiDescriptorFieldOptions, pageAiDescriptorFieldValue, pageAiEditorTargetCandidates, pageAiEnsureContextRefState, pageAiFilteredHistoryRows, pageAiHermesProfileEntries, pageAiHermesSettingsUrl, pageAiHideHermesBuiltinSkills, pageAiMnoteToolModel, pageAiNormalizeAcpRuntimes, pageAiNormalizeArray, pageAiNormalizeSkillSource, pageAiProfileDisplayLabel, pageAiProfileValue, pageAiProviderLabel, pageAiReasonixMemoryEnabled, pageAiRunProfile, pageAiSessionAgentFilterOptions, pageAiSessionAgentFilterValue, pageAiSessionAgentLabel, pageAiSessionPreviewText, pageAiSessionStatusFilterOptions, pageAiSessionStatusFilterValue, pageAiSessionStatusLabel, pageAiSessionStorageLabel, pageAiSessionSelected, pageAiSelectedSessionCount, pageAiSkillEnabled, pageAiSkillGroupCollapsed, pageAiSkillListEntries, pageAiSkillOriginLabel, pageAiSkillSourceOptions, pageAiSkillSourceParts, pageAiToggleableSkillEntries, pageAiUsageSummary, pageAiBoardFirstEnabled, pageAiBoardWorkerOptions, pageAiBoardWorkflowOptions, pageAiBoardSelectedWorkerId, pageAiBoardSelectedWorkflowId, pageAiBoardWorkerLabel, pageAiBoardWorkflowLabel, pageAiBoardModelOptions, pageAiBoardSelectedModelOverride, pageAiBoardModelLabel, pageUiState, renderPageAiMarkdown, searchText, }); function pageAiSetAgentId(agentId) { var next = pageAiNormalizeAgentId(agentId); var record = pageAiAgentRecord(next); pageUiState.pageAiAgentId = next; pageUiState.pageAiAcpRuntime = record.acpRuntime || 'reasonix'; if (record.tools) pageUiState.pageAiTools = record.tools; pageUiState.pageAiAgentPopoverOpen = next === 'hermes'; if (next === 'hermes') pageAiSetActiveProfile(pageAiCurrentProfile()); if (next === 'chat_only') pageAiSetActiveProfile(pageAiNormalizeChatOnlyProfileId(pageAiCurrentProfile())); document.documentElement.setAttribute('data-mnote-page-ai-agent-id', next); pageAiPersistAiPreference('default_agent_id', next); void pageAiLoadSkills(); renderPageAiProviderButtons(); renderPageAiControls(); } function pageAiToggleContextRef(kind) { var normalized = String(kind || '').trim(); if (!PAGE_AI_CONTEXT_REF_REGISTRY.some(function(ref) { return ref.id === normalized; })) return; var selected = pageAiEnsureContextRefState(); selected[normalized] = !selected[normalized]; if (!Object.keys(selected).some(function(key) { return selected[key]; })) selected.current_page = true; pageAiPersistAiPreference('context_refs.default_selected', selected); renderPageAiControls(); } function pageAiSetContextPopoverOpen(open) { pageUiState.pageAiContextPopoverOpen = Boolean(open); if (open) { pageUiState.pageAiAgentPopoverOpen = false; pageUiState.pageAiTargetPopoverOpen = false; } renderPageAiControls(); } function pageAiSetAgentPopoverOpen(open) { pageUiState.pageAiAgentPopoverOpen = Boolean(open); if (open) { pageUiState.pageAiContextPopoverOpen = false; pageUiState.pageAiTargetPopoverOpen = false; } renderPageAiControls(); } function pageAiSetTargetPopoverOpen(open) { pageUiState.pageAiTargetPopoverOpen = Boolean(open); if (open) { pageUiState.pageAiAgentPopoverOpen = false; pageUiState.pageAiContextPopoverOpen = false; } renderPageAiControls(); } function pageAiSelectTarget(targetId) { var normalized = String(targetId || '').trim(); var candidates = pageAiEditorTargetCandidates(); var target = candidates.find(function(candidate) { return candidate.targetId === normalized; }) || null; if (!target) return; pageUiState.pageAiSelectedTargetId = target.targetId; pageUiState.pageAiTargetPopoverOpen = false; renderPageAiControls(); } function pageAiPersistAiPreference(key, value) { pageAiPersistRawAiPreference('ai.common.' + key, value); } function pageAiPersistRawAiPreference(key, value) { try { var workspaceId = resolveWorkspaceId(document.body); var body = { workspaceId: workspaceId, sourceKind: currentSourceKind(), rootUri: currentRootUri(), documentId: currentDocumentId(), updates: {} }; body.updates[key] = value; void fetch('/api/ui/preferences', { method: 'PUT', headers: { 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify(body) }).catch(function() {}); } catch (_) {} } function pageAiDescriptorPreferenceValues() { return pageUiState.pageAiSkillPreferences && typeof pageUiState.pageAiSkillPreferences === 'object' ? pageUiState.pageAiSkillPreferences : {}; } function pageAiDescriptorFieldValue(field) { var key = String(field && field.key || '').trim(); var preferences = pageAiDescriptorPreferenceValues(); if (key && Object.prototype.hasOwnProperty.call(preferences, key)) return preferences[key]; return field && Object.prototype.hasOwnProperty.call(field, 'default') ? field.default : ''; } function pageAiDescriptorFieldOptions(field) { var source = String(field && field.optionsSource || '').trim(); if (source === 'hermes_profiles') { return pageAiHermesProfileEntries().map(function(profile) { var value = pageAiProfileValue(profile); return { value: value, label: pageAiProfileDisplayLabel(profile, value) }; }).filter(function(option) { return option.value; }); } if (source === 'chat_only_profiles') { return pageAiChatOnlyProfileEntries().map(function(profile) { var value = pageAiProfileValue(profile); return { value: value, label: profile.menuLabel || pageAiProfileDisplayLabel(profile, value) }; }).filter(function(option) { return option.value; }); } return pageAiNormalizeArray(field && field.options).map(function(option) { if (option && typeof option === 'object') { var value = String(option.value || option.id || option.key || '').trim(); return { value: value, label: String(option.label || option.title || value).trim() || value }; } var value = String(option || '').trim(); return { value: value, label: value }; }).filter(function(option) { return option.value; }); } function pageAiSetDescriptorFieldValue(key, value) { var normalizedKey = String(key || '').trim(); if (!normalizedKey) return; pageUiState.pageAiSkillPreferences = Object.assign({}, pageAiDescriptorPreferenceValues(), { [normalizedKey]: value }); pageAiPersistRawAiPreference(normalizedKey, value); if (normalizedKey === 'ai.agent.hermes.profile_id') { pageAiSetActiveProfile(value); void pageAiLoadAgentDescriptors(); void pageAiLoadSkills(); void pageAiLoadTools(); } if (normalizedKey === 'ai.agent.chat_only.model_id' && pageAiCurrentAgentId() === 'chat_only') { pageAiSetActiveProfile(pageAiNormalizeChatOnlyProfileId(value)); } 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 pageAiBoardFirstEnabled() { try { return window.localStorage ? window.localStorage.getItem('mnote.page_ai.legacy_provider_mode') !== '1' : true; } catch (_error) { return true; } } function pageAiOpencodeHostEnabled() { try { return window.localStorage ? window.localStorage.getItem('mnote.page_ai.opencode_host') === '1' : false; } catch (_error) { return false; } } function pageAiOpencodeStatusLabel(status) { var node = status && typeof status === 'object' ? status : {}; var state = String(node.status || node.state || node.runtimeStatus || '').trim(); var ok = node.ok === true || node.ready === true || state === 'ready' || state === 'running' || state === 'ok'; if (ok) return state ? '可用 · ' + state : '可用'; if (state) return state; return '未知'; } function pageAiOpencodeSessionIdFromStatus(status) { var node = status && typeof status === 'object' ? status : {}; var session = node.session && typeof node.session === 'object' ? node.session : {}; return String(node.sessionId || node.activeSessionId || node.currentSessionId || session.id || '').trim(); } function pageAiOpencodeData(payload) { return payload && payload.data && typeof payload.data === 'object' ? payload.data : payload; } function pageAiOpencodeSessionArray(payload) { var data = pageAiOpencodeData(payload); return pageAiNormalizeArray(data && data.data) .concat(pageAiNormalizeArray(data && data.sessions)) .concat(Array.isArray(data) ? data : []); } function pageAiOpencodeCollectChangedFileCandidates(value, output, depth) { if (value == null || depth > 5) return; if (typeof value === 'string') { if (/\.(md|markdown|txt|json|ya?ml|toml|rs|js|ts|tsx|jsx|css|html)$/i.test(value) || value.indexOf('/') !== -1) { output.push(value); } return; } if (Array.isArray(value)) { value.forEach(function(item) { pageAiOpencodeCollectChangedFileCandidates(item, output, depth + 1); }); return; } if (typeof value !== 'object') return; var directPath = value.path || value.file || value.filename || value.relativePath || value.relative_path || value.absolutePath || value.absolute_path; if (directPath) output.push(value); ['changedFiles', 'changed_files', 'files', 'diff', 'diffs', 'changes', 'changed', 'edited', 'created', 'deleted', 'data', 'properties'].forEach(function(key) { if (Object.prototype.hasOwnProperty.call(value, key)) pageAiOpencodeCollectChangedFileCandidates(value[key], output, depth + 1); }); } function pageAiOpencodeNormalizeChangedFiles(payload) { var source = []; pageAiOpencodeCollectChangedFileCandidates(payload, source, 0); var seen = {}; return source.map(function(item) { var path = typeof item === 'string' ? item : String(item && (item.path || item.file || item.filename || item.relativePath || item.relative_path || item.absolutePath || item.absolute_path) || '').trim(); var status = typeof item === 'object' && item ? String(item.status || item.change || item.kind || item.type || '').trim() : ''; return { path: path, status: status }; }).filter(function(item) { if (!item.path || seen[item.path]) return false; seen[item.path] = true; return true; }); } function pageAiNormalizeRootRelativePath(path) { return String(path || '').trim().replace(/\\/g, '/').replace(/^\/+/, ''); } function pageAiRootPathFromFileUri(rootUri) { var root = String(rootUri || '').trim(); if (!root.startsWith('file://')) return ''; try { return decodeURIComponent(new URL(root).pathname || '').replace(/\/+$/, ''); } catch (_error) { return ''; } } function pageAiRootRelativePathFromChangedPath(path, rootUri) { var value = String(path || '').trim(); if (!value) return ''; try { var parsed = new URL(value, window.location.origin); if (parsed.pathname === '/api/local-folder/files/open') { return pageAiNormalizeRootRelativePath(parsed.searchParams.get('path') || ''); } if (parsed.protocol === 'file:') value = decodeURIComponent(parsed.pathname || ''); } catch (_error) {} value = value.replace(/^file:\/\//, ''); var rootPath = pageAiRootPathFromFileUri(rootUri || currentRootUri()); if (rootPath && value.startsWith(rootPath + '/')) { value = value.slice(rootPath.length + 1); } return pageAiNormalizeRootRelativePath(value); } function pageAiDocumentIdFromRootRelativePath(relativePath) { var normalized = pageAiNormalizeRootRelativePath(relativePath); if (!normalized || !/\.(md|markdown)$/i.test(normalized)) return ''; return localMarkdownDocumentIdFromPageAiRelativePath(normalized); } function pageAiOpencodeCurrentPageLabel() { var aggregate = currentPageAggregate(); var meta = aggregate && aggregate.meta && typeof aggregate.meta === 'object' ? aggregate.meta : {}; var target = currentPageAiEditorTarget() || {}; return String(meta.title || target.title || document.title || currentDocumentId() || '当前页').trim(); } function pageAiOpencodePrimaryPath() { var target = currentPageAiEditorTarget() || {}; var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {}; return String(workspacePath.relativePath || workspacePath.path || target.relativePath || localMarkdownRelativePathFromPageAiDocumentId(currentDocumentId()) || '').trim(); } function pageAiOpencodeWritableLabel() { var roots = pageAiNormalizeArray(pageUiState.pageAiAllowedRoots && pageUiState.pageAiAllowedRoots.length ? pageUiState.pageAiAllowedRoots : pageAiBuildAllowedRoots()); if (!roots.length) return '授权待同步'; var writable = roots.some(function(root) { return /write|writable|rw/i.test(String(root && (root.permission || root.access || root.mode || '') || '')) || root && root.writable === true; }); return writable ? '可写' : '只读'; } function pageAiFilePathFromRootUri(rootUri, relativePath) { var root = String(rootUri || '').trim(); var relative = String(relativePath || '').trim().replace(/\\/g, '/').replace(/^\/+/, ''); if (!root || !relative || !root.startsWith('file://')) return ''; try { var pathname = new URL(root).pathname || ''; return decodeURIComponent(pathname).replace(/\/+$/, '') + '/' + relative; } catch (_error) { return ''; } } function pageAiMNoteDocumentPathSegment(documentId) { return encodeURIComponent(String(documentId || '').trim()).replace(/%3A/g, ':').replace(/%7E/g, '~'); } function pageAiParentRelativePath(relativePath) { var normalized = pageAiNormalizeRootRelativePath(relativePath); if (!normalized || normalized.indexOf('/') === -1) return ''; return normalized.split('/').slice(0, -1).join('/'); } function pageAiFileUriForRootRelativePath(rootUri, relativePath) { var root = String(rootUri || '').trim().replace(/\/+$/, ''); var relative = pageAiNormalizeRootRelativePath(relativePath); if (!root) return ''; if (!relative) return root; return root + '/' + relative.split('/').map(function(segment) { return encodeURIComponent(segment); }).join('/'); } function pageAiCurrentActiveTabEditorTarget() { var snapshot = currentPageAiOpenEditorsSnapshot(); if (snapshot && snapshot.activeEditor) { var activeTarget = pageAiTargetFromOpenEditor(snapshot.activeEditor, 'open_editors_active_tab'); if (activeTarget) return activeTarget; } if (snapshot) { var entries = pageAiNormalizeArray(snapshot.editors).concat(pageAiNormalizeArray(snapshot.resourceEditors)); var activeEntry = entries.find(function(entry) { return entry && entry.active === true; }); if (activeEntry) { var target = pageAiTargetFromOpenEditor(activeEntry, 'open_editors_active_tab'); if (target) return target; } } return pageAiFallbackEditorTarget(); } function pageAiCurrentActiveTabAddressPayload() { var target = pageAiCurrentActiveTabEditorTarget() || {}; var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {}; var rootUri = String(workspacePath.rootUri || target.rootUri || currentRootUri() || '').trim(); var sourceKind = String(workspacePath.sourceKind || target.sourceKind || currentSourceKind() || '').trim(); var workspaceId = String(workspacePath.workspaceId || target.workspaceId || resolveWorkspaceId(document.body) || '').trim(); var documentId = String(workspacePath.documentId || target.documentId || currentDocumentId() || '').trim(); var relativePath = pageAiNormalizeRootRelativePath(workspacePath.relativePath || workspacePath.path || target.relativePath || target.path || ''); if (!relativePath) relativePath = pageAiNormalizeRootRelativePath(localMarkdownRelativePathFromPageAiDocumentId(documentId)); if (!documentId && relativePath && /\.(md|markdown)$/i.test(relativePath)) { documentId = localMarkdownDocumentIdFromPageAiRelativePath(relativePath); } if (!documentId) documentId = String(currentDocumentId() || '').trim(); var resourceKind = String(target.resourceKind || workspacePath.resourceKind || target.editorKind || '').trim().toLowerCase(); var editorKind = String(target.editorKind || '').trim().toLowerCase(); var objectIdentity = String(target.objectIdentity || target.targetId || workspacePath.objectIdentity || '').trim(); var tabUrl = new URL('/documents/' + pageAiMNoteDocumentPathSegment(documentId), window.location.origin); if (sourceKind) tabUrl.searchParams.set('sourceKind', sourceKind); if (rootUri) tabUrl.searchParams.set('rootUri', rootUri); if (workspaceId) tabUrl.searchParams.set('workspaceId', workspaceId); var isResourceTab = Boolean(relativePath) && resourceKind !== 'markdown_page' && resourceKind !== 'page' && editorKind !== 'page'; if (isResourceTab) { tabUrl.searchParams.set('resourcePath', relativePath); if (objectIdentity) tabUrl.searchParams.set('resourceTab', objectIdentity); } var folderRelativePath = pageAiParentRelativePath(relativePath); var folderUrl = pageAiFileUriForRootRelativePath(rootUri, folderRelativePath); if (!folderUrl) { var folderMNoteUrl = new URL('/documents/' + pageAiMNoteDocumentPathSegment(documentId), window.location.origin); if (sourceKind) folderMNoteUrl.searchParams.set('sourceKind', sourceKind); if (rootUri) folderMNoteUrl.searchParams.set('rootUri', rootUri); if (workspaceId) folderMNoteUrl.searchParams.set('workspaceId', workspaceId); if (folderRelativePath) folderMNoteUrl.searchParams.set('fileTreeScope', folderRelativePath); folderUrl = folderMNoteUrl.toString(); } return { schema: 'mnote.page_ai.active_tab_address.v1', tabUrl: tabUrl.toString(), folderUrl: folderUrl, documentId: documentId, rootUri: rootUri, workspaceId: workspaceId, relativePath: relativePath, folderRelativePath: folderRelativePath, targetId: String(target.targetId || objectIdentity || '').trim(), resourceKind: resourceKind || 'markdown_page' }; } function pageAiBuildOpencodeContextPayload() { var selectedText = currentPageAiSelectedText(); var target = currentPageAiEditorTarget() || {}; var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {}; var pagePath = pageAiOpencodePrimaryPath(); var rootUri = String(workspacePath.rootUri || target.rootUri || currentRootUri() || '').trim(); return { pageTitle: pageAiOpencodeCurrentPageLabel(), pagePath: pagePath, pageAbsolutePath: pageAiFilePathFromRootUri(rootUri, pagePath), rootUri: rootUri, selection: selectedText, allowedRoots: pageAiNormalizeArray(pageUiState.pageAiAllowedRoots && pageUiState.pageAiAllowedRoots.length ? pageUiState.pageAiAllowedRoots : pageAiBuildAllowedRoots()), pageId: String(currentDocumentId() || target.documentId || target.id || ''), workspaceId: String(target.workspaceId || ''), }; } function pageAiOpencodeContextTextForInsert() { var context = pageAiBuildOpencodeContextPayload(); var lines = ['MNote 当前页上下文']; if (context.pageTitle) lines.push('标题:' + context.pageTitle); if (context.pagePath) lines.push('文件:' + context.pagePath); if (context.selection) lines.push('选区:\n' + context.selection); var roots = pageAiNormalizeArray(context.allowedRoots).map(function(root) { return String(root && (root.path || root.root || root.uri || root.label) || '').trim(); }).filter(Boolean).slice(0, 4); if (roots.length) lines.push('允许目录:' + roots.join(', ')); return lines.join('\n'); } function pageAiPostOpencodeBridgeMessage(type, payload) { var iframe = document.querySelector('[data-page-ai-opencode-iframe]'); if (!(iframe instanceof HTMLIFrameElement) || !iframe.contentWindow) return false; iframe.contentWindow.postMessage({ source: 'mnote-page-ai', target: 'opencode-webui', type: type, payload: payload || {} }, window.location.origin); return true; } function pageAiInstallOpencodeBridge() { if (pageUiState.pageAiOpencodeBridgeInstalled) return; pageUiState.pageAiOpencodeBridgeInstalled = true; window.addEventListener('message', function(event) { if (event.origin !== window.location.origin) return; var message = event.data && typeof event.data === 'object' ? event.data : null; if (!message || message.source !== 'mnote-opencode-bridge') return; var payload = message.payload && typeof message.payload === 'object' ? message.payload : {}; if (message.type === 'session-ready') { pageUiState.pageAiOpencodeBridgeReady = true; pageAiRenderOpencodeHostChrome(); return; } if (message.type === 'open-file') { pageAiOpenOpencodeChangedFile(payload.path || payload.href || payload.text || ''); return; } if (message.type === 'refresh-file') { pageAiRefreshOpencodeCurrentPage('page-ai-opencode-bridge'); } }); } function pageAiOpencodeMessageInfo(message) { return message && message.info && typeof message.info === 'object' ? message.info : message || {}; } function pageAiOpencodePartText(part) { if (!part || typeof part !== 'object') return ''; if (part.text) return String(part.text); if (part.content) return String(part.content); if (part.summary) return String(part.summary); if (part.type === 'patch') return '修改文件:' + pageAiNormalizeArray(part.files).join(', '); if (part.type === 'file') return String(part.filename || part.url || 'file'); if (part.type === 'tool') return String(part.tool || part.name || 'tool'); return ''; } function pageAiOpencodeIsContextEnvelope(message, parts) { var info = pageAiOpencodeMessageInfo(message); var text = String(info.text || '').trim() || pageAiNormalizeArray(parts).map(pageAiOpencodePartText).join('\n').trim(); return text.indexOf('MNote context envelope') === 0; } function pageAiOpencodeNormalizeMessages(payload) { var data = pageAiOpencodeData(payload); var items = pageAiNormalizeArray(data && data.data) .concat(pageAiNormalizeArray(data && data.messages)) .concat(Array.isArray(data) ? data : []); return items.map(function(item) { var info = pageAiOpencodeMessageInfo(item); var parts = pageAiNormalizeArray(item && item.parts).concat(pageAiNormalizeArray(info.content)); var type = String(info.role || info.type || item && item.type || '').toLowerCase(); var role = type.indexOf('assistant') !== -1 ? 'assistant' : type.indexOf('user') !== -1 ? 'user' : type || 'event'; return { id: String(info.id || item && item.id || Math.random()).trim(), role: role, type: type, agent: String(info.agent || ''), model: info.model || null, finish: String(info.finish || ''), error: info.error || null, cost: info.cost, tokens: info.tokens || null, parts: parts, hiddenContext: pageAiOpencodeIsContextEnvelope(item, parts), text: String(info.text || '').trim(), }; }).filter(function(message) { return !message.hiddenContext && (message.text || message.parts.length || message.error || message.type); }); } function pageAiOpencodeNormalizePermissions(payload) { var data = pageAiOpencodeData(payload); return pageAiNormalizeArray(data && data.data) .concat(pageAiNormalizeArray(data && data.permissions)) .concat(Array.isArray(data) ? data : []); } function pageAiOpencodeNormalizeTodo(payload) { var data = pageAiOpencodeData(payload); return pageAiNormalizeArray(data && data.data).concat(pageAiNormalizeArray(data && data.todo)).concat(Array.isArray(data) ? data : []); } function pageAiOpencodeCompact(value, max) { var text = typeof value === 'string' ? value : JSON.stringify(value || {}); max = max || 260; return text.length > max ? text.slice(0, max - 1) + '…' : text; } function pageAiRenderOpencodePart(part) { if (!part || typeof part !== 'object') return ''; var type = String(part.type || 'part'); if (type === 'text') return '
' + escapeHtml(pageAiOpencodePartText(part)).replace(/\n/g, '
') + '
'; if (type === 'reasoning') return '
reasoning
' + escapeHtml(pageAiOpencodePartText(part)) + '
'; if (type === 'tool') { var state = part.state && typeof part.state === 'object' ? part.state : {}; var status = String(state.status || state.type || part.status || 'pending'); return '
' + escapeHtml(part.tool || 'tool') + '' + escapeHtml(status) + '
' + escapeHtml(pageAiOpencodeCompact(state.output || state.input || state.error || state)) + '
'; } if (type === 'patch') { var files = pageAiNormalizeArray(part.files); return '
patch' + files.map(function(file) { var path = String(file || '').trim(); return ''; }).join('') + '
'; } if (type === 'file') { var filePath = String(part.filename || part.url || '').trim(); return '
file
'; } if (type === 'step-start' || type === 'step-finish' || type === 'snapshot' || type === 'agent' || type === 'subtask' || type === 'retry' || type === 'compaction') { return '
' + escapeHtml(type) + '
' + escapeHtml(pageAiOpencodeCompact(part, 420)) + '
'; } return '
' + escapeHtml(type) + '
' + escapeHtml(pageAiOpencodeCompact(part, 420)) + '
'; } function pageAiRenderOpencodeMessages(drawer) { var node = drawer.querySelector('[data-page-ai-opencode-messages]'); if (!(node instanceof HTMLElement)) return; var messages = pageAiNormalizeArray(pageUiState.pageAiOpencodeMessages); if (!messages.length) { node.innerHTML = '
输入消息后由 opencode 原生 runtime 回复;tool、reasoning、patch 会在这里显示。
'; return; } node.innerHTML = messages.map(function(message) { var model = message.model && typeof message.model === 'object' ? String(message.model.providerID || message.model.provider || '') + '/' + String(message.model.id || message.model.modelID || '') : ''; var parts = pageAiNormalizeArray(message.parts).map(pageAiRenderOpencodePart).join('') || (message.text ? '
' + escapeHtml(message.text).replace(/\n/g, '
') + '
' : ''); var error = message.error ? '
' + escapeHtml(pageAiOpencodeCompact(message.error, 500)) + '
' : ''; return '
' + '
' + escapeHtml(message.role === 'assistant' ? 'opencode' : message.role === 'user' ? '你' : message.type || 'event') + '' + escapeHtml([message.agent, model, message.finish].filter(Boolean).join(' · ')) + '
' + parts + error + '
'; }).join(''); node.scrollTop = node.scrollHeight; } function pageAiRenderOpencodePermissions(drawer) { var node = drawer.querySelector('[data-page-ai-opencode-permissions]'); if (!(node instanceof HTMLElement)) return; var permissions = pageAiNormalizeArray(pageUiState.pageAiOpencodePermissions); node.innerHTML = permissions.map(function(item) { var id = String(item && item.id || '').trim(); var action = String(item && item.action || 'permission').trim(); var resources = pageAiNormalizeArray(item && item.resources).join(', '); return '
' + '' + escapeHtml(action) + '' + '' + escapeHtml(resources || '等待审批') + '' + '' + '' + '' + '
'; }).join(''); } function pageAiRenderOpencodeSessions(drawer) { var node = drawer.querySelector('[data-page-ai-opencode-sessions]'); if (!(node instanceof HTMLElement)) return; var sessions = pageAiNormalizeArray(pageUiState.pageAiOpencodeSessions).slice(0, 12); node.innerHTML = sessions.length ? sessions.map(function(session) { var id = String(session && session.id || '').trim(); var active = id && id === String(pageUiState.pageAiOpencodeSessionId || ''); var title = String(session && (session.title || session.name || session.id) || 'session').trim(); var model = session && session.model && typeof session.model === 'object' ? String(session.model.providerID || '') + '/' + String(session.model.id || session.model.modelID || '') : ''; return ''; }).join('') : '暂无 session 列表'; } function pageAiRenderOpencodeTodo(drawer) { var node = drawer.querySelector('[data-page-ai-opencode-todo]'); if (!(node instanceof HTMLElement)) return; var todo = pageAiNormalizeArray(pageUiState.pageAiOpencodeTodo); node.innerHTML = todo.length ? todo.map(function(item) { return '
  • ' + escapeHtml(String(item && (item.content || item.text || item.title) || item || '')) + '
  • '; }).join('') : ''; } function pageAiRenderOpencodeHostChrome() { var drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]'); if (!(drawer instanceof HTMLElement) || drawer.getAttribute('data-page-ai-opencode-host') !== 'true') return; var status = pageUiState.pageAiOpencodeStatus || {}; var session = status.session && typeof status.session === 'object' ? status.session : status; var sessionId = String(pageUiState.pageAiOpencodeSessionId || pageAiOpencodeSessionIdFromStatus(status) || '').trim(); var primaryPath = pageAiOpencodePrimaryPath(); var titleNode = drawer.querySelector('[data-page-ai-opencode-current-page]'); if (titleNode instanceof HTMLElement) titleNode.textContent = pageAiOpencodeCurrentPageLabel(); var pathNode = drawer.querySelector('[data-page-ai-opencode-current-path]'); if (pathNode instanceof HTMLElement) pathNode.textContent = primaryPath || '未定位真实 Markdown 路径'; var selectionNode = drawer.querySelector('[data-page-ai-opencode-selection]'); if (selectionNode instanceof HTMLElement) selectionNode.textContent = currentPageAiSelectedText() ? '已选中 ' + currentPageAiSelectedText().length + ' 字' : '无选区'; var writableNode = drawer.querySelector('[data-page-ai-opencode-writable]'); if (writableNode instanceof HTMLElement) writableNode.textContent = pageAiOpencodeWritableLabel(); var statusNode = drawer.querySelector('[data-page-ai-opencode-runtime-status]'); if (statusNode instanceof HTMLElement) { statusNode.textContent = pageUiState.pageAiOpencodeStatusError || pageAiOpencodeStatusLabel(status); statusNode.setAttribute('data-state', pageUiState.pageAiOpencodeStatusError ? 'error' : pageAiOpencodeStatusLabel(status)); } var sessionNode = drawer.querySelector('[data-page-ai-opencode-session]'); if (sessionNode instanceof HTMLElement) sessionNode.textContent = sessionId ? 'session ' + sessionId : '未绑定 session'; var modelNode = drawer.querySelector('[data-page-ai-opencode-model]'); if (modelNode instanceof HTMLElement) { var model = session.model && typeof session.model === 'object' ? String(session.model.providerID || '') + '/' + String(session.model.id || session.model.modelID || '') : ''; modelNode.textContent = model || 'model 未知'; } var chipsNode = drawer.querySelector('[data-page-ai-opencode-changed-files]'); if (chipsNode instanceof HTMLElement) { var changedFiles = pageAiNormalizeArray(pageUiState.pageAiOpencodeChangedFiles); chipsNode.innerHTML = changedFiles.length ? changedFiles.map(function(file) { var path = String(file && file.path || '').trim(); var label = path.split('/').filter(Boolean).pop() || path; var statusText = String(file && file.status || '').trim(); return ''; }).join('') : '' + escapeHtml(pageUiState.pageAiOpencodeDiffError || '暂无 changed files') + ''; } var iframe = drawer.querySelector('[data-page-ai-opencode-iframe]'); if (iframe instanceof HTMLIFrameElement) { var nextUrl = String(pageUiState.pageAiOpencodeIframeUrl || '').trim(); if (nextUrl && iframe.getAttribute('src') !== nextUrl) iframe.setAttribute('src', nextUrl); iframe.hidden = !nextUrl; } var fallbackNode = drawer.querySelector('[data-page-ai-opencode-iframe-fallback]'); if (fallbackNode instanceof HTMLElement) { var hasUrl = Boolean(String(pageUiState.pageAiOpencodeIframeUrl || '').trim()); fallbackNode.hidden = hasUrl && !pageUiState.pageAiOpencodeStatusError; fallbackNode.textContent = pageUiState.pageAiOpencodeStatusError ? 'opencode WebUI 暂不可用:' + pageUiState.pageAiOpencodeStatusError : '正在连接 opencode WebUI…'; } } function pageAiEnsureOpencodeHostDrawer() { var drawer = ensurePageAiDrawer(); if (drawer.getAttribute('data-page-ai-opencode-host') !== 'true') { drawer.setAttribute('data-page-ai-opencode-host', 'true'); drawer.setAttribute('data-mnote-acp-runtime', 'opencode'); drawer.innerHTML = '' + '' + ''; } pageAiInstallOpencodeBridge(); pageAiRenderOpencodeHostChrome(); void pageAiEnsureOpencodeSessionBinding(false); return drawer; } async function pageAiEnsureOpencodeSessionBinding(force) { if (pageUiState.pageAiOpencodeBindingInFlight) return; var binding = pageUiState.pageAiOpencodeStatus && typeof pageUiState.pageAiOpencodeStatus === 'object' ? pageUiState.pageAiOpencodeStatus : {}; var context = pageAiBuildOpencodeContextPayload(); var pathSignature = JSON.stringify([context.pageTitle, context.pagePath, context.pageAbsolutePath, context.rootUri, context.selection.length, JSON.stringify(context.allowedRoots).slice(0, 512)]); if (!force && pageUiState.pageAiOpencodeSessionId && pageUiState.pageAiOpencodeBindingKey === pathSignature) return; pageUiState.pageAiOpencodeBindingInFlight = true; pageUiState.pageAiOpencodeBindingKey = pathSignature; try { var response = await fetch('/api/page-ai/opencode/session', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, cache: 'no-store', body: JSON.stringify(Object.assign({ sessionId: force ? pageUiState.pageAiOpencodeSessionId || undefined : undefined, forceNew: force && !pageUiState.pageAiOpencodeSessionId }, context)), }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(payload && payload.error ? String(payload.error) : 'HTTP ' + response.status); pageUiState.pageAiOpencodeStatus = payload.session || binding; pageUiState.pageAiOpencodeStatusError = ''; pageUiState.pageAiOpencodeSessionId = String(payload.sessionId || '').trim(); pageUiState.pageAiOpencodeIframeUrl = String(payload.iframeUrl || pageUiState.pageAiOpencodeIframeUrl || '').trim(); pageAiStartOpencodeEvents(); void pageAiRefreshOpencodeProjection(); pageAiRenderOpencodeHostChrome(); } catch (error) { pageUiState.pageAiOpencodeStatusError = error instanceof Error ? error.message : String(error); pageUiState.pageAiOpencodeSending = false; pageAiRenderOpencodeHostChrome(); } finally { pageUiState.pageAiOpencodeBindingInFlight = false; } } async function pageAiLoadOpencodeStatus() { try { var response = await fetch('/api/page-ai/opencode/status', { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(payload && payload.error ? String(payload.error) : 'HTTP ' + response.status); pageUiState.pageAiOpencodeStatus = payload || {}; pageUiState.pageAiOpencodeStatusError = ''; pageUiState.pageAiOpencodeBaseUrl = String(payload && payload.baseUrl || pageUiState.pageAiOpencodeBaseUrl || 'http://127.0.0.1:4096').trim(); pageUiState.pageAiOpencodeSessionId = pageAiOpencodeSessionIdFromStatus(payload) || pageUiState.pageAiOpencodeSessionId; pageAiRenderOpencodeHostChrome(); await pageAiRefreshOpencodeProjection(); } catch (error) { pageUiState.pageAiOpencodeStatusError = error instanceof Error ? error.message : String(error); pageUiState.pageAiOpencodeSending = false; pageAiRenderOpencodeHostChrome(); } } async function pageAiLoadOpencodeSessions() { try { var response = await fetch('/api/page-ai/opencode/sessions', { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(payload && payload.error ? String(payload.error) : 'HTTP ' + response.status); pageUiState.pageAiOpencodeSessions = pageAiOpencodeSessionArray(payload && payload.sessions || payload); pageAiRenderOpencodeHostChrome(); } catch (error) { pageUiState.pageAiOpencodeStatusError = error instanceof Error ? error.message : String(error); pageUiState.pageAiOpencodeSending = false; pageAiRenderOpencodeHostChrome(); } } async function pageAiLoadOpencodeMessages() { var sessionId = String(pageUiState.pageAiOpencodeSessionId || '').trim(); if (!sessionId) return; var response = await fetch('/api/page-ai/opencode/messages?sessionId=' + encodeURIComponent(sessionId), { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(payload && payload.error ? String(payload.error) : 'HTTP ' + response.status); pageUiState.pageAiOpencodeMessages = pageAiOpencodeNormalizeMessages(payload); pageAiRenderOpencodeHostChrome(); } async function pageAiLoadOpencodePermissions() { var sessionId = String(pageUiState.pageAiOpencodeSessionId || '').trim(); if (!sessionId) return; var response = await fetch('/api/page-ai/opencode/permissions?sessionId=' + encodeURIComponent(sessionId), { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(payload && payload.error ? String(payload.error) : 'HTTP ' + response.status); pageUiState.pageAiOpencodePermissions = pageAiOpencodeNormalizePermissions(payload); pageAiRenderOpencodeHostChrome(); } async function pageAiLoadOpencodeTodo() { var sessionId = String(pageUiState.pageAiOpencodeSessionId || '').trim(); if (!sessionId) return; var response = await fetch('/api/page-ai/opencode/todo?sessionId=' + encodeURIComponent(sessionId), { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (response.ok) pageUiState.pageAiOpencodeTodo = pageAiOpencodeNormalizeTodo(payload); pageAiRenderOpencodeHostChrome(); } async function pageAiLoadOpencodeDiff(sessionId) { var id = String(sessionId || pageUiState.pageAiOpencodeSessionId || '').trim(); if (!id) return; try { var response = await fetch('/api/page-ai/opencode/diff?sessionId=' + encodeURIComponent(id), { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(payload && payload.error ? String(payload.error) : 'HTTP ' + response.status); pageUiState.pageAiOpencodeChangedFiles = pageAiOpencodeNormalizeChangedFiles(payload); pageUiState.pageAiOpencodeDiffError = ''; pageAiRenderOpencodeHostChrome(); if (pageAiOpencodeChangedFilesHitCurrentPage(pageUiState.pageAiOpencodeChangedFiles)) pageAiRefreshOpencodeCurrentPage('page-ai-opencode-event'); } catch (error) { pageUiState.pageAiOpencodeDiffError = error instanceof Error ? error.message : String(error); pageAiRenderOpencodeHostChrome(); } } async function pageAiRefreshOpencodeProjection() { if (!pageUiState.pageAiOpencodeSessionId) return; await Promise.allSettled([ pageAiLoadOpencodeMessages(), pageAiLoadOpencodePermissions(), pageAiLoadOpencodeTodo(), pageAiLoadOpencodeDiff(pageUiState.pageAiOpencodeSessionId), pageAiLoadOpencodeSessions(), ]); } function pageAiOpencodeChangedFilesHitCurrentPage(files) { var currentPath = String(pageAiOpencodePrimaryPath() || '').trim(); if (!currentPath) return false; var normalizedCurrent = currentPath.replace(/\\/g, '/'); return pageAiNormalizeArray(files).some(function(file) { var path = String(file && file.path || '').trim().replace(/\\/g, '/'); return path && (path === normalizedCurrent || path.endsWith('/' + normalizedCurrent.split('/').pop())); }); } function pageAiOpencodeEventMatchesSession(payload, sessionId) { var id = String(sessionId || '').trim(); if (!id) return false; var node = payload && typeof payload === 'object' ? (payload.properties || payload.data || payload) : {}; if (String(node.sessionID || node.sessionId || '').trim() === id) return true; var text = ''; try { text = JSON.stringify(payload || {}); } catch (_error) { text = ''; } return text.indexOf(id) !== -1; } function pageAiScheduleOpencodeRefresh(reason) { if (!pageUiState.pageAiOpencodeSessionId) return; if (pageUiState.pageAiOpencodeEventTimer) window.clearTimeout(pageUiState.pageAiOpencodeEventTimer); pageUiState.pageAiOpencodeEventTimer = window.setTimeout(function() { pageUiState.pageAiOpencodeEventTimer = 0; void pageAiRefreshOpencodeProjection(); }, reason === 'open' ? 250 : 700); } function pageAiStartOpencodeEvents() { var sessionId = String(pageUiState.pageAiOpencodeSessionId || '').trim(); if (!sessionId || !('EventSource' in window)) return; var existing = pageUiState.pageAiOpencodeEventSource; if (existing && pageUiState.pageAiOpencodeEventSessionId === sessionId) return; if (existing && typeof existing.close === 'function') existing.close(); var source = new EventSource('/api/page-ai/opencode/events'); pageUiState.pageAiOpencodeEventSource = source; pageUiState.pageAiOpencodeEventSessionId = sessionId; var handleEvent = function(event) { var payload = null; try { payload = event && event.data ? JSON.parse(event.data) : null; } catch (_error) { payload = event && event.data ? { raw: String(event.data) } : null; } var eventType = String(event && event.type || payload && (payload.type || payload.event) || 'message'); var changedFiles = pageAiOpencodeNormalizeChangedFiles(payload); if (changedFiles.length) { pageUiState.pageAiOpencodeChangedFiles = changedFiles; pageUiState.pageAiOpencodeDiffError = ''; if (pageAiOpencodeChangedFilesHitCurrentPage(changedFiles)) pageAiRefreshOpencodeCurrentPage('page-ai-opencode-event'); pageAiRenderOpencodeHostChrome(); } if (pageAiOpencodeEventMatchesSession(payload, sessionId) || /diff|file|permission|session|message/.test(eventType)) pageAiScheduleOpencodeRefresh('event'); }; source.onopen = function() { pageAiScheduleOpencodeRefresh('open'); }; source.onmessage = handleEvent; ['message.updated', 'message.part.updated', 'message.part.delta', 'session.diff', 'session.error', 'session.updated', 'session.next.text.delta', 'session.next.tool.called', 'session.next.tool.success', 'permission.v2.asked', 'permission.v2.replied', 'file.edited'].forEach(function(type) { source.addEventListener(type, handleEvent); }); source.onerror = function() { pageUiState.pageAiOpencodeDiffError = pageUiState.pageAiOpencodeDiffError || 'opencode event 暂不可用,保留手动刷新'; pageAiRenderOpencodeHostChrome(); }; } async function pageAiSendOpencodeMessage(text) { var sessionId = String(pageUiState.pageAiOpencodeSessionId || '').trim(); if (!sessionId) await pageAiEnsureOpencodeSessionBinding(true); sessionId = String(pageUiState.pageAiOpencodeSessionId || '').trim(); if (!sessionId) throw new Error('opencode session 未就绪'); pageUiState.pageAiOpencodeMessages = pageAiNormalizeArray(pageUiState.pageAiOpencodeMessages); pageUiState.pageAiOpencodeSending = true; pageAiRenderOpencodeHostChrome(); var response = await fetch('/api/page-ai/opencode/prompt', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, cache: 'no-store', body: JSON.stringify({ sessionId: sessionId, text: text, model: PAGE_AI_OPENCODE_MODEL }) }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(payload && payload.error ? String(payload.error) : 'HTTP ' + response.status); pageUiState.pageAiOpencodeSending = false; pageAiScheduleOpencodeRefresh('prompt'); } async function pageAiReplyOpencodePermission(id, response) { var sessionId = String(pageUiState.pageAiOpencodeSessionId || '').trim(); if (!sessionId || !id) return; await fetch('/api/page-ai/opencode/permission/reply', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, cache: 'no-store', body: JSON.stringify({ sessionId: sessionId, requestId: id, response: response }) }); void pageAiLoadOpencodePermissions(); } async function pageAiAbortOpencodeRun() { var sessionId = String(pageUiState.pageAiOpencodeSessionId || '').trim(); if (!sessionId) return; await fetch('/api/page-ai/opencode/abort', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, cache: 'no-store', body: JSON.stringify({ sessionId: sessionId }) }); pageAiScheduleOpencodeRefresh('abort'); } function pageAiSelectOpencodeSession(sessionId) { var id = String(sessionId || '').trim(); if (!id) return; pageUiState.pageAiOpencodeSessionId = id; pageUiState.pageAiOpencodeMessages = []; pageUiState.pageAiOpencodeLocalMessages = []; pageAiStartOpencodeEvents(); void pageAiRefreshOpencodeProjection(); pageAiRenderOpencodeHostChrome(); } function pageAiOpenOpencodeChangedFile(path, options) { var rootUri = String(options && options.rootUri || currentRootUri() || '').trim(); var targetPath = pageAiRootRelativePathFromChangedPath(path, rootUri); if (!targetPath || !window.__mnoteDocumentPaneRuntime) return false; var documentId = String(options && options.documentId || pageAiDocumentIdFromRootRelativePath(targetPath) || '').trim(); var workspaceId = String(options && options.workspaceId || resolveWorkspaceId(document.body) || '').trim(); var isMarkdown = Boolean(documentId); if (isMarkdown) { if (window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitChangedFiles === 'function') { window.__mnoteLocalFolderEventBus.emitChangedFiles({ source: 'opencode_changed_file_bridge', reason: 'opencode_changed_file', rootUri: rootUri, workspaceId: workspaceId, changedFiles: [{ relativePath: targetPath, documentId: documentId, changeType: 'modified' }], affectedParents: [{ relativePath: pageAiParentRelativePath(targetPath), reason: 'opencode_changed_file' }] }); } if (typeof window.__mnoteDocumentPaneRuntime.openPrimaryDocument === 'function') { if (documentId === String(currentDocumentId() || '').trim()) { document.documentElement.setAttribute('data-mnote-page-ai-opencode-document-pane-refresh', targetPath); return true; } void window.__mnoteDocumentPaneRuntime.openPrimaryDocument({ documentId: documentId, workspaceId: workspaceId, sourceKind: currentSourceKind() || 'local_folder', rootUri: rootUri }); document.documentElement.setAttribute('data-mnote-page-ai-opencode-document-pane-refresh', targetPath); return true; } } if (typeof window.__mnoteDocumentPaneRuntime.openResourceInActiveTab !== 'function') return false; void window.__mnoteDocumentPaneRuntime.openResourceInActiveTab({ path: targetPath, rootUri: rootUri }); return true; } function pageAiRefreshOpencodeCurrentPage(reason) { var rootUri = String(currentRootUri() || '').trim(); var targetPath = String(pageAiOpencodePrimaryPath() || '').trim(); var documentId = String(currentDocumentId() || pageAiDocumentIdFromRootRelativePath(targetPath) || '').trim(); if (rootUri && targetPath && documentId && window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitChangedFiles === 'function') { window.__mnoteLocalFolderEventBus.emitChangedFiles({ source: 'opencode_changed_files_adapter', reason: reason || 'page-ai-opencode-host', rootUri: rootUri, workspaceId: resolveWorkspaceId(document.body), changedFiles: [{ relativePath: targetPath, documentId: documentId, changeType: 'modified' }], affectedParents: [{ relativePath: pageAiParentRelativePath(targetPath), reason: reason || 'page-ai-opencode-host' }] }); return true; } return false; } function pageAiApplyBoardFirstShell() { if (!pageAiBoardFirstEnabled()) return; var drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]'); if (!(drawer instanceof HTMLElement)) return; if (drawer.getAttribute('data-page-ai-opencode-host') === 'true') return; drawer.setAttribute('data-page-ai-board-first', 'true'); var subtitle = drawer.querySelector('.wolai-page-ai-subtitle'); if (subtitle instanceof HTMLElement) { subtitle.innerHTML = 'Agent BoardZCode 默认 developerlocal-first 文件编辑'; } var runtimeStrip = drawer.querySelector('[data-page-ai-runtime-strip]'); if (runtimeStrip instanceof HTMLElement) { runtimeStrip.setAttribute('aria-label', '打开 Agent Board 运行状态'); } var runtimeBadge = drawer.querySelector('[data-page-ai-runtime-badge]'); if (runtimeBadge instanceof HTMLElement) runtimeBadge.textContent = 'Agent Board · ' + pageAiBoardWorkflowLabel(); var agentChip = drawer.querySelector('.wolai-page-ai-agent-chip'); if (agentChip instanceof HTMLElement) { agentChip.textContent = pageAiBoardWorkerLabel() + ' / ' + pageAiBoardModelLabel(); agentChip.setAttribute('title', '当前 worker/model:' + pageAiBoardWorkerLabel() + ' / ' + pageAiBoardModelLabel()); } drawer.querySelectorAll('[data-page-ai-tab="agent"], [data-page-ai-tab="reasonix-settings"], [data-page-ai-tab="hermes-settings"]').forEach(function(node) { if (node instanceof HTMLElement) node.hidden = true; }); drawer.querySelectorAll('[data-page-ai-panel="agent"], [data-page-ai-panel="reasonix-settings"], [data-page-ai-panel="hermes-settings"]').forEach(function(node) { if (node instanceof HTMLElement) node.setAttribute('data-page-ai-legacy-provider-panel', 'true'); }); } function pageAiBoardDefaultWorkerId() { return 'mnote-page-ai-zcode'; } function pageAiBoardDefaultWorkflowId() { return 'builtin-mnote-page-ai-chat'; } function pageAiBoardDefaultModelOverride() { return 'zcode-default'; } function pageAiBoardDefaultModelOptions() { return [ { id: 'zcode-default', label: '默认', default: true }, { id: 'zcode-fast', label: '快速' }, { id: 'zcode-strong', label: '强力' } ]; } function pageAiBoardSelectedWorkerId() { return String(pageUiState.pageAiBoardWorkerId || pageAiBoardDefaultWorkerId()).trim() || pageAiBoardDefaultWorkerId(); } function pageAiBoardSelectedWorkflowId() { return String(pageUiState.pageAiBoardWorkflowId || pageAiBoardDefaultWorkflowId()).trim() || pageAiBoardDefaultWorkflowId(); } function pageAiBoardModelOptions(workerId) { var selectedWorkerId = String(workerId || pageAiBoardSelectedWorkerId()).trim() || pageAiBoardDefaultWorkerId(); var worker = pageAiBoardWorkerOptions().find(function(item) { return String(item.id || '') === selectedWorkerId; }) || {}; var options = pageAiNormalizeArray(worker.modelOptions || worker.models || worker.allowedModels) .map(function(option) { if (typeof option === 'string') return { id: option, label: option }; return { id: String(option && (option.id || option.value || option.modelOverride || option.model) || '').trim(), label: String(option && (option.label || option.name || option.id || option.value || option.modelOverride || option.model) || '').trim(), default: Boolean(option && option.default) }; }) .filter(function(option) { return option.id; }); if (!options.length && selectedWorkerId === pageAiBoardDefaultWorkerId()) options = pageAiBoardDefaultModelOptions(); return options; } function pageAiBoardSelectedModelOverride() { var options = pageAiBoardModelOptions(); var fallback = (options.find(function(option) { return option.default; }) || options[0] || { id: pageAiBoardDefaultModelOverride() }).id; var selected = String(pageUiState.pageAiBoardModelOverride || fallback || pageAiBoardDefaultModelOverride()).trim(); if (!options.length) return selected || pageAiBoardDefaultModelOverride(); return options.some(function(option) { return option.id === selected; }) ? selected : fallback; } function pageAiBoardModelLabel(modelOverride) { var id = String(modelOverride || pageAiBoardSelectedModelOverride()).trim(); var option = pageAiBoardModelOptions().find(function(item) { return item.id === id; }); return String(option && (option.label || option.id) || id || '默认').trim(); } function pageAiBoardWorkerOptions() { var byId = {}; var fallback = { id: pageAiBoardDefaultWorkerId(), name: 'MNote 页面 AI · ZCode', surface: 'mnote-page-ai', role: 'developer', agentType: 'zcode', agentModel: 'default', capabilities: ['text', 'repo-edit', 'terminal', 'mnote-capability-envelope', 'local-markdown-edit'], modelOptions: pageAiBoardDefaultModelOptions() }; pageAiNormalizeArray(pageUiState.pageAiBoardWorkers).concat([fallback]).forEach(function(worker) { var id = String(worker && (worker.id || worker.workerPresetId || worker.presetId) || '').trim(); var surface = String(worker && (worker.surface || worker.productSurface) || '').trim(); if (!id || id !== pageAiBoardDefaultWorkerId()) return; if (surface && surface !== 'mnote-page-ai') return; byId[id] = Object.assign({}, fallback, worker, { id: id, surface: 'mnote-page-ai' }); }); return Object.keys(byId).map(function(id) { return byId[id]; }); } function pageAiBoardWorkflowOptions() { var byId = {}; var fallback = { id: pageAiBoardDefaultWorkflowId(), name: 'MNote 页面 AI', surface: 'mnote-page-ai' }; pageAiNormalizeArray(pageUiState.pageAiBoardWorkflows).concat([fallback]).forEach(function(workflow) { var id = String(workflow && (workflow.id || workflow.workflowId) || '').trim(); var surface = String(workflow && (workflow.surface || workflow.productSurface) || '').trim(); if (!id || id !== pageAiBoardDefaultWorkflowId()) return; if (surface && surface !== 'mnote-page-ai') return; byId[id] = Object.assign({}, fallback, workflow, { id: id, surface: 'mnote-page-ai' }); }); return Object.keys(byId).map(function(id) { return byId[id]; }); } function pageAiBoardWorkerLabel(workerId) { var id = String(workerId || pageAiBoardSelectedWorkerId()).trim(); var worker = pageAiBoardWorkerOptions().find(function(item) { return String(item.id || '') === id; }); return String(worker && (worker.name || worker.id) || id || 'Board worker').trim(); } function pageAiBoardWorkflowLabel(workflowId) { var id = String(workflowId || pageAiBoardSelectedWorkflowId()).trim(); var workflow = pageAiBoardWorkflowOptions().find(function(item) { return String(item.id || '') === id; }); return String(workflow && (workflow.name || workflow.id) || id || 'Board workflow').trim(); } async function pageAiLoadBoardCatalog() { if (!pageAiBoardFirstEnabled()) return; try { var workersResponse = await fetch('/api/page-ai/board/workers', { headers: { accept: 'application/json' }, cache: 'no-store' }); var workersPayload = await workersResponse.json().catch(function() { return null; }); if (workersResponse.ok) { var workers = pageAiNormalizeArray(workersPayload && workersPayload.workers); if (!workers.length) workers = pageAiNormalizeArray(workersPayload && workersPayload.workers && workersPayload.workers.workers); pageUiState.pageAiBoardWorkers = workers; } var workflowsResponse = await fetch('/api/page-ai/board/workflows', { headers: { accept: 'application/json' }, cache: 'no-store' }); var workflowsPayload = await workflowsResponse.json().catch(function() { return null; }); if (workflowsResponse.ok) { var workflows = pageAiNormalizeArray(workflowsPayload && workflowsPayload.workflows); pageUiState.pageAiBoardWorkflows = workflows; } pageUiState.pageAiBoardCatalogError = ''; } catch (error) { pageUiState.pageAiBoardCatalogError = error instanceof Error ? error.message : String(error); } renderPageAiControls(); pageAiApplyBoardFirstShell(); } function pageAiExtractFinalAnswer(summary) { var text = String(summary || '').trim(); if (!text) return ''; var match = text.match(/(?:^|\n)##\s*FINAL_ANSWER\s*\n([\s\S]*?)(?=\n##\s+|$)/i); if (match && match[1].trim()) return match[1].trim(); return text .replace(/^##\s*Completed\s*/i, '') .replace(/\n##\s*Comments[\s\S]*$/i, '') .replace(/\n##\s*Remaining[\s\S]*$/i, '') .trim(); } function pageAiFileUriToPath(rootUri) { var value = String(rootUri || '').trim(); if (!value.indexOf('file://') === 0 && value.slice(0, 7) !== 'file://') return ''; try { return decodeURIComponent(value.replace(/^file:\/\//, '')); } catch (_error) { return value.replace(/^file:\/\//, ''); } } function pageAiJoinPath(rootPath, relativePath) { var root = String(rootPath || '').replace(/\/+$/, ''); var rel = String(relativePath || '').replace(/\\/g, '/').replace(/^\/+/, ''); if (!root || !rel) return root || rel; return root + '/' + rel; } function pageAiBoardCapabilities() { return [ 'mnote.current_page.read', 'mnote.selection.read', 'mnote.open_resources.snapshot', 'mnote.allowed_roots.describe', 'mnote.weknora.search', 'mnote.reference.open', 'mnote.page_aggregate.snapshot', 'mnote.local_file.receipt' ]; } function pageAiBuildBoardEnvelope(prompt, scopedContext, runTargetSnapshot, contextRefs, allowedRoots, agentTargetPackage) { var workspacePath = agentTargetPackage && agentTargetPackage.workspacePath && typeof agentTargetPackage.workspacePath === 'object' ? agentTargetPackage.workspacePath : (runTargetSnapshot && runTargetSnapshot.workspacePath && typeof runTargetSnapshot.workspacePath === 'object' ? runTargetSnapshot.workspacePath : {}); var relativePath = String(workspacePath.relativePath || agentTargetPackage && agentTargetPackage.relativePath || runTargetSnapshot && runTargetSnapshot.relativePath || '').trim(); var rootPath = pageAiFileUriToPath(currentRootUri()); var absolutePath = String(workspacePath.absolutePath || workspacePath.path || '').trim() || pageAiJoinPath(rootPath, relativePath); var selectedText = String(scopedContext && scopedContext.selectedText || '').trim(); var writeIntent = /\b(edit|write|append|replace|fix|rewrite|update)\b|编辑|修改|追加|替换|修复|改写|更新/.test(String(prompt || '').toLowerCase()); return { schema: 'mnote.page_ai.board_task.v1', intent: writeIntent ? 'edit' : 'ask', message: prompt, workspaceId: resolveWorkspaceId(document.body), sourceKind: currentSourceKind(), rootUri: currentRootUri(), documentId: currentDocumentId(), pageTitle: String((currentPageAggregate().meta || {}).title || document.title || '').trim(), board: { workflowId: pageAiBoardSelectedWorkflowId(), workflowName: pageAiBoardWorkflowLabel(), workerPresetId: pageAiBoardSelectedWorkerId(), workerName: pageAiBoardWorkerLabel(), modelOverride: pageAiBoardSelectedModelOverride(), modelLabel: pageAiBoardModelLabel() }, primaryTarget: { kind: 'markdown_file', relativePath: relativePath, absolutePath: absolutePath, documentId: currentDocumentId(), workspacePath: workspacePath }, selection: { text: selectedText, range: null }, contextRefs: contextRefs, allowedRoots: allowedRoots, capabilities: pageAiBoardCapabilities(), modelOverride: pageAiBoardSelectedModelOverride(), pageContext: pageAiPageContextForRefs(scopedContext.pageContext, contextRefs), runTargetSnapshot: runTargetSnapshot, targetPackage: agentTargetPackage, writePolicy: { allowFileWrite: allowedRoots.some(function(root) { return String(root.permission || '').indexOf('write') >= 0; }), requireHumanApproval: false, forbidMnoteInternalStateWrite: true } }; } function pageAiBoardReceiptSummary(payload) { var receipt = payload && payload.receipt && typeof payload.receipt === 'object' ? payload.receipt : null; var board = payload && payload.board && typeof payload.board === 'object' ? payload.board : null; if (receipt && String(receipt.summary || '').trim()) return String(receipt.summary || '').trim(); var nodeRuns = Array.isArray(board && board.nodeRuns) ? board.nodeRuns : []; for (var i = nodeRuns.length - 1; i >= 0; i -= 1) { if (String(nodeRuns[i] && nodeRuns[i].outputSummary || '').trim()) return String(nodeRuns[i].outputSummary).trim(); } var events = Array.isArray(board && board.events) ? board.events : []; if (events.length) return String(events[events.length - 1].message || '').trim(); return ''; } function pageAiBoardRunStatus(payload) { var board = payload && payload.board && typeof payload.board === 'object' ? payload.board : null; var run = board && board.run && typeof board.run === 'object' ? board.run : (board && board.id ? board : null); return String(run && run.status || '').trim(); } function pageAiBoardRunId(payload) { var board = payload && payload.board && typeof payload.board === 'object' ? payload.board : null; var run = board && board.run && typeof board.run === 'object' ? board.run : (board && board.id ? board : null); return String(run && run.id || payload && payload.runId || payload && payload.id || '').trim(); } function pageAiBoardReceiptObject(payload) { var receipt = payload && payload.receipt && typeof payload.receipt === 'object' ? payload.receipt : null; if (receipt && receipt.receipt && typeof receipt.receipt === 'object') return receipt.receipt; return receipt; } function pageAiBoardFinalAnswer(payload) { var receipt = pageAiBoardReceiptObject(payload); var candidates = [ receipt && receipt.finalAnswer, receipt && receipt.summary, receipt && receipt.outputSummary, pageAiBoardReceiptSummary(payload) ]; for (var i = 0; i < candidates.length; i += 1) { var text = pageAiExtractFinalAnswer(candidates[i]); if (text) return text; } return ''; } function pageAiBoardChangedFiles(payload) { var receipt = pageAiBoardReceiptObject(payload) || {}; var changed = pageAiNormalizeArray(receipt.changedFiles || receipt.changed_files || receipt.filesChanged); return changed.map(function(file) { if (typeof file === 'string') return { path: file }; return { path: String(file && (file.path || file.absolutePath || file.relativePath || file.file) || '').trim(), status: String(file && (file.status || file.changeType || file.kind) || '').trim() }; }).filter(function(file) { return file.path; }); } function pageAiBoardTimeline(payload) { var board = payload && payload.board && typeof payload.board === 'object' ? payload.board : null; var events = pageAiNormalizeArray(board && board.events); if (!events.length && board && board.run && Array.isArray(board.run.events)) events = board.run.events; return pageAiNormalizeArray(events).map(function(event) { var timestamp = String(event && (event.timestamp || event.createdAt || event.created_at || event.time) || '').trim(); var type = String(event && (event.type || event.eventType || event.event || event.status) || '').trim(); var message = String(event && (event.message || event.summary || event.title || '') || '').trim(); return { timestamp: timestamp, type: type, message: message }; }).filter(function(event) { return event.type || event.message; }); } function pageAiBoardVerification(payload) { var receipt = pageAiBoardReceiptObject(payload) || {}; return pageAiNormalizeArray(receipt.verification || receipt.checks).map(function(item) { if (typeof item === 'string') return { command: item, status: '' }; return { command: String(item && (item.command || item.name || item.label) || '').trim(), status: String(item && (item.status || item.result) || '').trim(), output: String(item && (item.output || item.summary) || '').trim() }; }).filter(function(item) { return item.command || item.status || item.output; }); } function pageAiBuildBoardRunDetail(runId, payload) { var status = pageAiBoardRunStatus(payload); return { schema: 'mnote.page_ai.board_run_detail.v1', runId: runId, status: status === 'complete' ? 'completed' : (status || 'completed'), workflowId: pageAiBoardSelectedWorkflowId(), workflowName: pageAiBoardWorkflowLabel(), workerPresetId: pageAiBoardSelectedWorkerId(), workerName: pageAiBoardWorkerLabel(), modelOverride: pageAiBoardSelectedModelOverride(), modelLabel: pageAiBoardModelLabel(), finalAnswer: pageAiBoardFinalAnswer(payload), changedFiles: pageAiBoardChangedFiles(payload), verification: pageAiBoardVerification(payload), timeline: pageAiBoardTimeline(payload), receipt: pageAiBoardReceiptObject(payload) || null, payload: payload || null, updatedAt: Date.now() }; } function pageAiStoreBoardRunDetail(runId, payload) { if (!runId) return null; if (!pageUiState.pageAiBoardRunDetails || typeof pageUiState.pageAiBoardRunDetails !== 'object') { pageUiState.pageAiBoardRunDetails = {}; } var detail = pageAiBuildBoardRunDetail(runId, payload); pageUiState.pageAiBoardRunDetails[runId] = detail; var current = pageAiCurrentSession(); if (current) { current.boardRuns = Object.assign({}, current.boardRuns || {}, { [runId]: detail }); current.boardRunId = runId; current.workflowId = detail.workflowId; current.workerPresetId = detail.workerPresetId; current.modelOverride = detail.modelOverride; current.receiptId = String(detail.receipt && (detail.receipt.id || detail.receipt.receiptId) || runId || '').trim(); } return detail; } async function pageAiPollBoardRun(runId) { var lastPayload = null; for (var attempt = 0; attempt < 360; attempt += 1) { var response = await fetch('/api/page-ai/board/runs/' + encodeURIComponent(runId), { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'board_run_status_failed_' + response.status)); lastPayload = payload; var status = pageAiBoardRunStatus(payload); if (status) pageAiSetRunStatus(status === 'complete' ? 'completed' : status); renderPageAiControls(); if (['complete', 'failed', 'blocked', 'cancelled'].indexOf(status) >= 0) return payload; await new Promise(function(resolve) { window.setTimeout(resolve, attempt < 8 ? 1000 : 2500); }); } return lastPayload; } async function sendPageAiBoardMessage(text) { if (pageUiState.pageAiBusy) return; var prompt = searchText(text); if (!prompt) return; pageUiState.pageAiBusy = true; pageAiSetRunStatus('preparing'); var currentSession = pageAiCurrentSession(); try { var contextSnapshot = currentPageAiContextSnapshot(); var scopedContext = pageAiScopedPageContext(contextSnapshot); scopedContext.editorTarget = currentPageAiScopedEditorTarget(); assertPageAiTargetInCurrentWorkspace(scopedContext.editorTarget); if (currentSourceKind() === 'local_folder' && !pageAiNormalizeArray(pageUiState.pageAiAllowedRoots).length) { await pageAiLoadAllowedRoots(); } await assertPageAiTargetWritable(scopedContext.editorTarget); var runTargetSnapshot = pageAiBuildRunTargetSnapshot(scopedContext, prompt); var contextRefs = pageAiBuildContextRefs(scopedContext, runTargetSnapshot); var allowedRoots = pageAiBuildAllowedRoots(); var agentTargetPackage = pageAiBuildAgentTargetPackage(scopedContext, runTargetSnapshot); assertPageAiLocalWritePermission(prompt, agentTargetPackage); var envelope = pageAiBuildBoardEnvelope(prompt, scopedContext, runTargetSnapshot, contextRefs, allowedRoots, agentTargetPackage); pageUiState.pageAiMessages.push({ role: 'user', content: prompt }); pageUiState.pageAiMessages.push({ role: 'assistant', kind: 'status', content: '正在处理…', streaming: true }); if (currentSession) { if (currentSession.title === '新会话') currentSession.title = prompt.length > 18 ? prompt.slice(0, 18) + '…' : prompt; currentSession.schema = 'mnote.page_ai.session.v2'; currentSession.source = 'board'; currentSession.workerPresetId = pageAiBoardSelectedWorkerId(); currentSession.workflowId = pageAiBoardSelectedWorkflowId(); currentSession.modelOverride = pageAiBoardSelectedModelOverride(); currentSession.messages = pageUiState.pageAiMessages.slice(); currentSession.updatedAt = Date.now(); } pageAiPersistSessions(); renderPageAiConversation(); renderPageAiControls(); pageAiApplyBoardFirstShell(); var response = await fetch('/api/page-ai/board/runs', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify({ workflowId: pageAiBoardSelectedWorkflowId(), workerPresetId: pageAiBoardSelectedWorkerId(), modelOverride: pageAiBoardSelectedModelOverride(), message: prompt, envelope: envelope, autoRun: true, priority: 'high' }) }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'board_run_failed_' + response.status)); var runId = pageAiBoardRunId(payload); pageUiState.pageAiCurrentRunId = runId; if (runId) { pageUiState.pageAiMessages = pageUiState.pageAiMessages.filter(function(message) { return !(message && message.kind === 'status' && message.streaming); }); pageUiState.pageAiMessages.push({ role: 'assistant', kind: 'status', content: 'Agent Board 正在处理…', streaming: true, boardRunId: runId }); } pageAiSetRunStatus('running'); renderPageAiConversation(); pageAiApplyBoardFirstShell(); var finalPayload = runId ? await pageAiPollBoardRun(runId) : payload; var detail = pageAiStoreBoardRunDetail(runId, finalPayload); var finalAnswer = detail && detail.finalAnswer || 'Agent Board run 已结束,但没有返回 final answer。'; pageUiState.pageAiMessages = pageUiState.pageAiMessages.filter(function(message) { return !(message && message.kind === 'status' && message.streaming); }); pageUiState.pageAiMessages.push({ role: 'assistant', content: finalAnswer, boardRunId: runId, workflowId: pageAiBoardSelectedWorkflowId(), workerPresetId: pageAiBoardSelectedWorkerId(), modelOverride: pageAiBoardSelectedModelOverride(), retryPrompt: prompt, receiptId: String(detail && detail.receipt && (detail.receipt.id || detail.receipt.receiptId) || runId || '').trim() }); if (currentSession) { currentSession.schema = 'mnote.page_ai.session.v2'; currentSession.source = 'board'; currentSession.messages = pageUiState.pageAiMessages.slice(); currentSession.updatedAt = Date.now(); currentSession.boardRunId = runId; currentSession.workflowId = pageAiBoardSelectedWorkflowId(); currentSession.workerPresetId = pageAiBoardSelectedWorkerId(); currentSession.modelOverride = pageAiBoardSelectedModelOverride(); } pageAiPersistSessions(); pageAiSetRunStatus(pageAiBoardRunStatus(finalPayload) === 'complete' ? 'completed' : pageAiBoardRunStatus(finalPayload) || 'completed'); renderPageAiConversation(); pageAiApplyBoardFirstShell(); } catch (error) { pageAiSetRunStatus('failed'); pageUiState.pageAiMessages.push({ role: 'assistant', content: 'Agent Board 处理失败:' + (error instanceof Error ? error.message : String(error)) }); renderPageAiConversation(); pageAiApplyBoardFirstShell(); } finally { pageUiState.pageAiBusy = false; renderPageAiControls(); pageAiApplyBoardFirstShell(); } } function pageAiApplyAgentDescriptorsPayload(payload) { var descriptors = pageAiNormalizeArray(payload && payload.descriptors) .map(pageAiNormalizeAgentDescriptor) .filter(function(descriptor) { return descriptor.schema === PAGE_AI_DESCRIPTOR_SCHEMA; }); if (!descriptors.length) return false; pageUiState.pageAiAgentDescriptors = descriptors; pageUiState.pageAiAgentDescriptorsLoaded = true; pageUiState.pageAiAgentDescriptorError = ''; if (payload && payload.preferenceValues && typeof payload.preferenceValues === 'object') { pageUiState.pageAiSkillPreferences = Object.assign( {}, pageAiDescriptorPreferenceValues(), payload.preferenceValues ); } var current = pageAiCurrentAgentId(); var record = pageAiAgentRecord(current); pageUiState.pageAiAgentId = record.id; pageUiState.pageAiAcpRuntime = record.acpRuntime || pageUiState.pageAiAcpRuntime || 'reasonix'; if (record.tools) pageUiState.pageAiTools = record.tools; return true; } async function pageAiLoadAgentDescriptors() { try { var url = new URL('/api/page-ai/agents/descriptors', window.location.origin); url.searchParams.set('profile', pageAiCurrentProfile()); var response = await fetch(url.toString(), { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'agent_descriptors_failed_' + response.status)); if (!pageAiApplyAgentDescriptorsPayload(payload)) throw new Error('agent_descriptors_empty'); } catch (error) { pageUiState.pageAiAgentDescriptorError = error instanceof Error ? error.message : String(error); pageUiState.pageAiAgentDescriptorsLoaded = false; } renderPageAiProviderButtons(); renderPageAiControls(); return pageUiState.pageAiAgentDescriptors; } async function pageAiLoadAiPreferences() { try { var url = new URL('/api/ui/preferences/effective', window.location.origin); url.searchParams.set('workspaceId', resolveWorkspaceId(document.body)); url.searchParams.set('sourceKind', currentSourceKind()); url.searchParams.set('rootUri', currentRootUri()); url.searchParams.set('documentId', currentDocumentId()); var response = await fetch(url.toString(), { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (!response.ok || !payload || !payload.result) return; var preferences = payload.result.aiPreferences && typeof payload.result.aiPreferences === 'object' ? payload.result.aiPreferences : {}; pageUiState.pageAiSkillPreferences = Object.assign({}, preferences); var defaultAgent = String(preferences['ai.common.default_agent_id'] || '').trim(); if (defaultAgent) { var agent = pageAiAgentRecord(defaultAgent); pageUiState.pageAiAgentId = agent.id; pageUiState.pageAiAcpRuntime = agent.acpRuntime || pageUiState.pageAiAcpRuntime || 'reasonix'; if (agent.tools) pageUiState.pageAiTools = agent.tools; } var hermesProfile = String(preferences['ai.agent.hermes.profile_id'] || '').trim(); if (hermesProfile) pageAiSetActiveProfile(hermesProfile); var skillSource = String(preferences['ai.common.skills.active_source'] || '').trim(); if (skillSource) pageUiState.pageAiActiveSkillSource = skillSource; var selected = preferences['ai.common.context_refs.default_selected']; if (selected && typeof selected === 'object' && !Array.isArray(selected)) { pageUiState.pageAiSelectedContextRefs = Object.assign({}, pageAiEnsureContextRefState(), selected); } var collapsedSkillGroups = preferences['ai.common.skills.groups.collapsed']; if (collapsedSkillGroups && typeof collapsedSkillGroups === 'object' && !Array.isArray(collapsedSkillGroups)) { pageUiState.pageAiCollapsedSkillGroups = Object.assign({}, collapsedSkillGroups); } } catch (_) {} renderPageAiControls(); } function pageAiNormalizeAccessGrant(grant) { if (!grant || typeof grant !== 'object') return null; var rootUri = String(grant.rootUri || grant.root_uri || '').trim(); if (!rootUri) return null; var permission = String(grant.permission || grant.access || '').trim() || 'read'; return { id: String(grant.id || '').trim(), rootUri: rootUri, rootPath: String(grant.rootPath || grant.root_path || '').trim(), permission: permission === 'read_write' ? 'write' : permission, recursive: grant.recursive !== false, source: String(grant.source || 'sqlite_directory_grant').trim() || 'sqlite_directory_grant', status: String(grant.status || 'active').trim() || 'active' }; } async function pageAiLoadAllowedRoots() { if (currentSourceKind() !== 'local_folder') { pageUiState.pageAiAllowedRoots = []; renderPageAiControls(); return []; } try { var response = await fetch('/api/user/access-policy', { headers: { accept: 'application/json' }, cache: 'no-store' }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'access_policy_failed_' + response.status)); var grants = pageAiNormalizeArray(payload && (payload.grants || payload.policy && payload.policy.grants)); var currentRoot = String(currentRootUri() || '').trim(); pageUiState.pageAiAllowedRoots = grants.map(pageAiNormalizeAccessGrant).filter(Boolean).filter(function(grant) { return grant.status === 'active' && (!currentRoot || grant.rootUri === currentRoot); }); pageUiState.pageAiAllowedRootsError = ''; } catch (error) { pageUiState.pageAiAllowedRoots = []; pageUiState.pageAiAllowedRootsError = error instanceof Error ? error.message : String(error); } renderPageAiControls(); return pageUiState.pageAiAllowedRoots; } function updatePageAiTriggerState() { var trigger = document.querySelector('[data-testid="wolai-floating-ai"]'); if (!(trigger instanceof HTMLElement)) return; trigger.setAttribute('data-state', pageUiState.pageAiOpen ? 'open' : 'closed'); trigger.setAttribute('aria-expanded', pageUiState.pageAiOpen ? 'true' : 'false'); } function pageAiOpenLocation(loc) { var path = String(loc || '').trim(); if (!path) return; openLocalResourceInActiveTab({ path: path }).then(function(opened) { if (!opened) { var href = buildLocalFileOpenUrl(path, false); if (href) window.open(href, '_blank', 'noopener,noreferrer'); } }); } function pageAiEvidenceRangeFromParam(value) { var text = String(value || '').trim(); if (!text) return null; var match = text.match(/^(\d+)[-:,](\d+)$/); if (!match) return text; return { start: Number(match[1]), end: Number(match[2]) }; } function pageAiEvidenceBboxFromParam(value) { var text = String(value || '').trim(); if (!text) return null; var parts = text.split(',').map(function(part) { return Number(part.trim()); }); if (parts.length < 4 || parts.slice(0, 4).some(function(part) { return !Number.isFinite(part); })) return text; return { x0: parts[0], y0: parts[1], x1: parts[2], y1: parts[3] }; } function pageAiDocumentIdFromUrl(url) { var prefix = '/documents/'; if (!url.pathname.startsWith(prefix)) return ''; try { var raw = url.pathname.slice(prefix.length).split('/')[0] || ''; var decoded = decodeURIComponent(raw); if (decoded.indexOf('%') >= 0 || decoded.indexOf('\uFFFD') >= 0) return ''; return decoded; } catch (_error) { return ''; } } function pageAiCitationResourcePath(url, rootUri) { var explicitPath = String(url.searchParams.get('resourcePath') || '').trim(); if (explicitPath) return explicitPath; var raw = String(url.searchParams.get('resourceTab') || '').trim(); if (!raw) { try { var decodedHash = decodeURIComponent(String(url.hash || '')); var marker = '#resource-tab-'; var markerIndex = decodedHash.indexOf(marker); if (markerIndex >= 0) raw = decodedHash.slice(markerIndex + marker.length).replace(/^#+/, '').trim(); } catch (_error) {} } if (raw.indexOf('::') >= 0) raw = raw.slice(raw.indexOf('::') + 2); if (!raw.startsWith('resource:file:')) return ''; var rest = raw.slice('resource:file:'.length); var prefix = String(rootUri || '').trim() + ':'; if (!prefix.trim() || !rest.startsWith(prefix)) return ''; return rest.slice(prefix.length).replace(/^\/+/, ''); } function pageAiSameMnoteOrigin(url) { try { var current = new URL(window.location.origin); if (url.origin === current.origin) return true; if (url.hostname === 'mnote.local') return true; var localNames = ['localhost', '127.0.0.1', '::1', 'mnote.local']; function defaultPort(protocol) { return protocol === 'https:' ? '443' : protocol === 'http:' ? '80' : ''; } return localNames.indexOf(url.hostname) >= 0 && localNames.indexOf(current.hostname) >= 0 && String(url.port || defaultPort(url.protocol)) === String(current.port || defaultPort(current.protocol)); } catch (_error) { return false; } } function pageAiPortableCitationUrl(url) { try { if (url.pathname === '/api/local-folder/files/open') { return Boolean(String(url.searchParams.get('rootUri') || '').trim()) && Boolean(String(url.searchParams.get('path') || '').trim()); } if (!url.pathname.startsWith('/documents/')) return false; var resourceTab = String(url.searchParams.get('resourceTab') || '').trim(); return String(url.searchParams.get('sourceKind') || '').trim() === 'local_folder' || Boolean(String(url.searchParams.get('rootUri') || '').trim()) || Boolean(String(url.searchParams.get('resourcePath') || '').trim()) || resourceTab.startsWith('resource:file:'); } catch (_error) { return false; } } function pageAiOpenCitationUrl(href) { var url; try { url = new URL(String(href || ''), window.location.origin); } catch (_error) { return false; } var unwrappedHref = pageAiUnwrapSearchCitationUrl(url); if (unwrappedHref) return pageAiOpenCitationUrl(unwrappedHref); if (!pageAiSameMnoteOrigin(url) && !pageAiPortableCitationUrl(url)) return false; if (url.pathname === '/api/local-folder/files/open') { var fileRootUri = String(url.searchParams.get('rootUri') || currentRootUri() || '').trim(); var filePath = String(url.searchParams.get('path') || '').trim(); if (!filePath) return false; void openLocalResourceInActiveTab({ path: filePath, rootUri: fileRootUri, documentId: currentDocumentId() || '', workspaceId: resolveWorkspaceId(document.body) || '', sourceKind: currentSourceKind() || 'local_folder', page: url.searchParams.get('page') || undefined, bbox: pageAiEvidenceBboxFromParam(url.searchParams.get('bbox')), sourceMapPath: String(url.searchParams.get('sourceMapPath') || '').trim(), blockId: String(url.searchParams.get('blockId') || '').trim(), evidenceText: String(url.searchParams.get('evidenceText') || '').trim(), lineRange: pageAiEvidenceRangeFromParam(url.searchParams.get('lineRange')), charRange: pageAiEvidenceRangeFromParam(url.searchParams.get('charRange')), openTarget: 'active-tab', paneRole: 'primary' }).then(function(opened) { if (!opened) window.open(url.toString(), '_blank', 'noopener,noreferrer'); }); return true; } if (!url.pathname.startsWith('/documents/')) return false; var rootUri = String(url.searchParams.get('rootUri') || currentRootUri() || '').trim(); var resourcePath = pageAiCitationResourcePath(url, rootUri); var documentId = pageAiDocumentIdFromUrl(url) || currentDocumentId() || ''; var workspaceId = String(url.searchParams.get('workspaceId') || resolveWorkspaceId(document.body) || '').trim(); if (resourcePath) { void openLocalResourceInActiveTab({ path: resourcePath, rootUri: rootUri, documentId: documentId, workspaceId: workspaceId, sourceKind: String(url.searchParams.get('sourceKind') || currentSourceKind() || 'local_folder').trim(), page: url.searchParams.get('page') || undefined, bbox: pageAiEvidenceBboxFromParam(url.searchParams.get('bbox')), sourceMapPath: String(url.searchParams.get('sourceMapPath') || '').trim(), blockId: String(url.searchParams.get('blockId') || '').trim(), evidenceText: String(url.searchParams.get('evidenceText') || '').trim(), lineRange: pageAiEvidenceRangeFromParam(url.searchParams.get('lineRange')), charRange: pageAiEvidenceRangeFromParam(url.searchParams.get('charRange')), openTarget: 'active-tab', paneRole: 'primary' }).then(function(opened) { if (!opened) window.location.assign(url.pathname + url.search + url.hash); }); return true; } if (typeof window.__mnoteDocumentPaneRuntime?.openPrimaryDocument === 'function') { void window.__mnoteDocumentPaneRuntime.openPrimaryDocument({ documentId: documentId, workspaceId: workspaceId, sourceKind: String(url.searchParams.get('sourceKind') || currentSourceKind() || '').trim(), rootUri: rootUri, url: url }); return true; } return false; } function pageAiChatOnlyProfileEntries() { var profiles = pageAiNormalizeArray(pageUiState.pageAiProfiles); return PAGE_AI_CHAT_ONLY_PROFILE_REGISTRY.map(function(spec) { var profile = profiles.find(function(candidate) { var candidateSpec = pageAiChatOnlyProfileSpec(candidate); return candidateSpec && candidateSpec.profileId === spec.profileId; }); return Object.assign({}, profile || { profileId: spec.profileId, name: spec.profileId, alias: spec.label, kind: 'shared', baseProfile: spec.baseProfile, providerKind: spec.providerKind || '', readonly: true }, { menuLabel: spec.label }); }); } function pageAiHermesProfileEntries() { var profiles = pageAiNormalizeArray(pageUiState.pageAiProfiles).filter(function(profile) { return !pageAiChatOnlyProfileSpec(profile); }); if (profiles.length) return profiles; return [{ profileId: pageAiCurrentProfile(), name: pageAiCurrentProfile(), alias: pageAiCurrentProfile(), kind: 'personal' }]; } function pageAiNormalizeProfiles(payload) { var upstream = pageAiUnwrapUpstream(payload); var profiles = pageAiNormalizeArray(upstream && upstream.profiles ? upstream.profiles : upstream); return profiles.map(function(profile) { var profileId = String(profile && (profile.profileId || profile.id || profile.name) || '').trim(); var kind = String(profile && profile.kind || profile && profile.profileKind || '').trim(); var canManageSkills = profile && profile.canManageSkills !== false; return { profileId: profileId || pageAiProfileValue(profile) || 'default', name: profileId || pageAiProfileValue(profile) || 'default', active: Boolean(profile && profile.active), model: String(profile && profile.model || '').trim(), gateway: String(profile && profile.gateway || '').trim(), alias: String(profile && (profile.displayName || profile.alias) || '').trim(), kind: kind, ownerUserId: String(profile && profile.ownerUserId || '').trim(), baseProfile: String(profile && profile.baseProfile || '').trim(), isolatedProfile: String(profile && profile.isolatedProfile || '').trim(), providerKind: String(profile && profile.providerKind || profile && profile.provider_kind || '').trim(), canRun: profile ? profile.canRun !== false : true, canManageSkills: canManageSkills, canManageConfig: profile ? profile.canManageConfig !== false : canManageSkills, readonly: profile ? (profile.readonly === true || !canManageSkills) : false, grantRole: String(profile && profile.grantRole || '').trim() }; }); } function pageAiNormalizeSkills(payload) { var upstream = pageAiUnwrapUpstream(payload); var categories = pageAiNormalizeArray(upstream && upstream.categories ? upstream.categories : []); var archived = pageAiNormalizeArray(upstream && upstream.archived ? upstream.archived : []); return { categories: categories.map(function(category) { var skillRows = pageAiNormalizeArray(category && (category.skills || category.capabilities)); return { name: String(category && category.name || '').trim() || 'misc', description: String(category && category.description || '').trim(), title: String(category && category.title || '').trim(), capabilities: pageAiNormalizeArray(category && category.capabilities), skills: skillRows.map(function(skill) { return { name: String(skill && skill.name || '').trim(), id: String(skill && skill.id || skill && skill.name || '').trim(), title: String(skill && skill.title || skill && skill.name || skill && skill.id || '').trim(), description: String(skill && skill.description || '').trim(), enabled: skill && skill.enabled !== false, toggleable: skill && skill.toggleable !== false, source: String(skill && skill.source || 'local').trim() || 'local', origin: String(skill && skill.origin || '').trim(), createdBy: String(skill && skill.createdBy || '').trim(), patchCount: Number(skill && skill.patchCount || 0), modified: Boolean(skill && skill.modified), builtin: Boolean(skill && skill.builtin), configurable: skill && skill.configurable !== false, configScope: String(skill && skill.configScope || '').trim(), skillKind: String(skill && skill.skillKind || '').trim(), profileId: String(skill && skill.profileId || '').trim(), toolNames: pageAiNormalizeArray(skill && skill.toolNames), tools: pageAiNormalizeArray(skill && skill.tools), toolCount: Number(skill && skill.toolCount || 0), disabledToolCount: Number(skill && skill.disabledToolCount || 0), status: String(skill && skill.status || '').trim(), capabilityId: String(skill && skill.capabilityId || '').trim(), capabilityKind: String(skill && skill.capabilityKind || '').trim(), uiKind: String(skill && skill.uiKind || '').trim(), requiresContextRefs: pageAiNormalizeArray(skill && skill.requiresContextRefs), readOnly: Boolean(skill && (skill.readOnly || skill.readonly || skill.configurable === false)), category: String(skill && skill.category || category && category.name || '').trim(), categoryTitle: String(skill && skill.categoryTitle || category && category.title || category && category.name || '').trim() }; }) }; }), archived: archived.map(function(skill) { return { name: String(skill && skill.name || '').trim(), id: String(skill && skill.id || skill && skill.name || '').trim(), title: String(skill && skill.title || skill && skill.name || skill && skill.id || '').trim(), description: String(skill && skill.description || '').trim(), enabled: skill && skill.enabled !== false, toggleable: skill && skill.toggleable !== false, source: String(skill && skill.source || 'local').trim() || 'local', origin: String(skill && skill.origin || '').trim(), createdBy: String(skill && skill.createdBy || '').trim(), patchCount: Number(skill && skill.patchCount || 0), modified: Boolean(skill && skill.modified), builtin: Boolean(skill && skill.builtin), configurable: skill && skill.configurable !== false, configScope: String(skill && skill.configScope || '').trim(), skillKind: String(skill && skill.skillKind || '').trim(), profileId: String(skill && skill.profileId || '').trim(), toolNames: pageAiNormalizeArray(skill && skill.toolNames), tools: pageAiNormalizeArray(skill && skill.tools), toolCount: Number(skill && skill.toolCount || 0), disabledToolCount: Number(skill && skill.disabledToolCount || 0), status: String(skill && skill.status || '').trim(), capabilityId: String(skill && skill.capabilityId || '').trim(), capabilityKind: String(skill && skill.capabilityKind || '').trim(), uiKind: String(skill && skill.uiKind || '').trim(), requiresContextRefs: pageAiNormalizeArray(skill && skill.requiresContextRefs), categoryTitle: String(skill && skill.categoryTitle || '').trim(), readOnly: Boolean(skill && (skill.readOnly || skill.readonly || skill.configurable === false)) }; }) }; } function pageAiSkillListEntries() { var result = []; pageAiNormalizeArray(pageUiState.pageAiSkills.categories).forEach(function(category) { pageAiNormalizeArray(category.skills).forEach(function(skill) { result.push({ category: category.name, name: skill.name, id: skill.id || skill.name, title: skill.title || skill.name, description: skill.description, enabled: skill.enabled !== false, toggleable: skill.toggleable !== false, source: skill.source || 'local', origin: skill.origin || '', createdBy: skill.createdBy || '', patchCount: Number(skill.patchCount || 0), modified: Boolean(skill.modified), builtin: Boolean(skill.builtin), configurable: skill.configurable !== false, configScope: skill.configScope || '', skillKind: skill.skillKind || '', profileId: skill.profileId || '', toolNames: skill.toolNames || [], tools: skill.tools || [], toolCount: Number(skill.toolCount || 0), disabledToolCount: Number(skill.disabledToolCount || 0), status: skill.status || '', capabilityId: skill.capabilityId || '', capabilityKind: skill.capabilityKind || '', uiKind: skill.uiKind || '', requiresContextRefs: skill.requiresContextRefs || [], categoryTitle: skill.categoryTitle || category.title || category.name || '', readOnly: Boolean(skill.readOnly || skill.readonly || skill.configurable === false) }); }); }); return result.concat(pageAiNormalizeArray(pageUiState.pageAiSkills.archived)); } function pageAiSetActiveProfile(profileName) { var next = String(profileName || '').trim() || 'mnoteai'; pageUiState.pageAiActiveProfileName = next; document.documentElement.setAttribute('data-mnote-page-ai-profile', next); document.documentElement.setAttribute('data-mnote-page-ai-tool-model', pageAiMnoteToolModel()); } function pageAiSetRunStatus(status, runId) { pageUiState.pageAiRunStatus = status || 'idle'; pageUiState.pageAiCurrentRunId = runId || pageUiState.pageAiCurrentRunId || ''; document.documentElement.setAttribute('data-mnote-page-ai-run-status', pageUiState.pageAiRunStatus); if (pageUiState.pageAiCurrentRunId) { document.documentElement.setAttribute('data-mnote-page-ai-run-id', pageUiState.pageAiCurrentRunId); } pageAiUpdateActiveRunSnapshotFromStatus(pageUiState.pageAiRunStatus, pageUiState.pageAiCurrentRunId); } function pageAiApplyRuntimeState(runtime) { if (!runtime || typeof runtime !== 'object') return; var status = String(runtime.status || '').trim(); var runId = String(runtime.runId || runtime.run_id || '').trim(); if (status) pageAiSetRunStatus(status, runId); var queueLength = Number(runtime.queueLength || runtime.queue_length || 0); if (Number.isFinite(queueLength)) pageUiState.pageAiQueueLength = Math.max(0, queueLength); var toolName = String(runtime.lastToolName || runtime.last_tool_name || '').trim(); if (toolName) { pageUiState.pageAiLastToolCall = { event: String(runtime.lastEvent || runtime.last_event || ''), name: toolName, runId: runId, traceId: String(runtime.traceId || runtime.trace_id || ''), auditId: String(runtime.lastAuditId || runtime.last_audit_id || '') }; } } 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'; } function pageAiReadActiveRunSnapshot() { try { var raw = window.localStorage ? window.localStorage.getItem(pageAiActiveRunSnapshotKey()) : ''; return raw ? JSON.parse(raw) : null; } catch (_) { return null; } } function pageAiWriteActiveRunSnapshot(snapshot) { if (!snapshot || !snapshot.hostRunId) return; try { window.localStorage.setItem(pageAiActiveRunSnapshotKey(), JSON.stringify(Object.assign({ schema: 'mnote.page_ai_active_run_snapshot.v1', createdAt: Date.now() }, snapshot, { updatedAt: Date.now() }))); document.documentElement.setAttribute('data-mnote-page-ai-active-run-snapshot', 'true'); } catch (_) {} } function pageAiClearActiveRunSnapshot(runId) { var existing = pageAiReadActiveRunSnapshot(); if (runId && existing && existing.hostRunId && existing.hostRunId !== runId) return; try { if (window.localStorage) window.localStorage.removeItem(pageAiActiveRunSnapshotKey()); document.documentElement.removeAttribute('data-mnote-page-ai-active-run-snapshot'); } catch (_) {} } function pageAiRunStatusIsTerminal(status) { return ['completed', 'failed', 'aborted', 'cancelled', 'canceled'].indexOf(String(status || '').trim()) >= 0; } function pageAiUpdateActiveRunSnapshotFromStatus(status, runId) { var id = String(runId || '').trim(); if (!id) return; if (pageAiRunStatusIsTerminal(status)) { pageAiClearActiveRunSnapshot(id); return; } if (['running', 'tool_calling', 'queued', 'pending', 'interrupted', 'acp_pending'].indexOf(String(status || '').trim()) < 0) return; var previous = pageAiReadActiveRunSnapshot(); var lastSeq = previous && previous.hostRunId === id ? String(previous.lastSeq || '000000000000000000') : '000000000000000000'; pageAiWriteActiveRunSnapshot({ hostRunId: id, sessionId: pageUiState.pageAiActiveSessionId || '', lastSeq: lastSeq, status: status || 'running' }); } async function pageAiCheckAndResumeActiveRun() { var snapshot = pageAiReadActiveRunSnapshot(); var sessionId = snapshot && snapshot.sessionId ? String(snapshot.sessionId || '').trim() : ''; var run = await pageAiCheckActiveRun(sessionId || undefined); if (run && sessionId && pageUiState.pageAiActiveSessionId !== sessionId) { pageAiSetActiveSession(sessionId); } if (run) await pageAiResumeActiveRunJournal(run); if (!run && snapshot && snapshot.hostRunId) { pageAiStopStreamingAssistantMessage(snapshot.hostRunId); pageAiClearActiveRunSnapshot(snapshot.hostRunId); pageUiState.pageAiBusy = false; pageAiSetRunStatus('idle', snapshot.hostRunId); } return run; } function pageAiApplyQueuedRun(payload) { if (!payload || payload.queued !== true) return false; var queueId = String(payload.queueId || payload.queue_id || '').trim(); var queueLength = Number(payload.queueLength || payload.queue_length || 0); pageUiState.pageAiQueueLength = Number.isFinite(queueLength) ? Math.max(0, queueLength) : 1; if (queueId) { pageUiState.pageAiQueuedItems = pageUiState.pageAiQueuedItems.filter(function(item) { return item.queueId !== queueId; }).concat([{ queueId: queueId, sessionId: String(payload.sessionId || payload.session_id || pageUiState.pageAiActiveSessionId || ''), traceId: String(payload.traceId || payload.trace_id || ''), queuedAt: Number(payload.queuedAt || payload.queued_at || Date.now()) }]); } pageAiSetRunStatus('queued', pageUiState.pageAiCurrentRunId); 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 = ''; ['wd', 'q', 'query'].some(function(key) { raw = String(url.searchParams.get(key) || '').trim(); return Boolean(raw); }); if (!raw) return ''; if (/^documents\//i.test(raw)) raw = '/' + raw; if (!/^\/documents\//i.test(raw) && !/^https?:\/\/[^/]+\/documents\//i.test(raw) && !/^mnote:\/\/open/i.test(raw)) return ''; if (/^mnote:\/\/open/i.test(raw)) return raw; var nested = new URL(raw, window.location.origin); if (!nested.pathname.startsWith('/documents/')) return ''; ['sourceKind', 'rootUri', 'workspaceId', 'resourceTab', 'resourcePath', 'page', 'bbox', 'sourceMapPath', 'blockId', 'evidenceText', 'lineRange', 'charRange'].forEach(function(key) { if (nested.searchParams.get(key)) return; var value = String(url.searchParams.get(key) || '').trim(); if (value) nested.searchParams.set(key, value); }); return nested.pathname + nested.search; } catch (_error) { return ''; } } function pageAiPreviewValue(value) { if (value == null || value === '') return ''; if (typeof value === 'string') return value.length > 120 ? value.slice(0, 120) + '…' : value; try { var text = JSON.stringify(value); return text.length > 120 ? text.slice(0, 120) + '…' : text; } catch (_) { return String(value); } } function pageAiNormalizeToolName(name) { return String(name || '').trim().replace(/_/g, '.'); } function pageAiParentRelativePath(path) { var normalized = String(path || '').trim().replace(/\\/g, '/').replace(/^\/+/, ''); if (!normalized || normalized.indexOf('/') < 0) return ''; return normalized.split('/').slice(0, -1).join('/'); } function pageAiChangedFileRelativePath(file) { return String(file && (file.path || file.relativePath || file.relative_path || file.filePath || file.file_path || '') || '').trim(); } function pageAiDispatchReceiptRefresh(receipt, changedFiles, fallbackAudit, runId, traceId) { var refresh = receipt && typeof receipt === 'object' && receipt.refresh && typeof receipt.refresh === 'object' ? receipt.refresh : {}; var files = pageAiNormalizeArray( receipt && typeof receipt === 'object' && receipt.changedFiles !== undefined ? receipt.changedFiles : changedFiles ); if (files.length) { var changedPaths = files.map(function(file) { var relativePath = pageAiChangedFileRelativePath(file); return { relativePath: relativePath, changeType: String(file && (file.changeType || file.change_type || 'modified') || 'modified') }; }).filter(function(item) { return item.relativePath; }); var affectedParents = changedPaths.map(function(item) { return { relativePath: pageAiParentRelativePath(item.relativePath), reason: 'agent-run-receipt' }; }).filter(function(item, index, list) { return index === list.findIndex(function(candidate) { return candidate.relativePath === item.relativePath; }); }); if (changedPaths.length) { var rootUri = String((receipt && receipt.rootUri) || (fallbackAudit && fallbackAudit.rootUri) || currentRootUri() || ''); var syntheticPayload = { schema: 'mnote.local_folder.watch_batch.v1', source: 'agent_run_receipt', runId: runId, rootUri: rootUri, changedPaths: changedPaths, affectedParents: affectedParents }; document.documentElement.setAttribute('data-mnote-page-ai-receipt-filetree-refresh', 'true'); if (window.__mnoteLocalFolderEventBus && typeof window.__mnoteLocalFolderEventBus.emitChangedFiles === 'function') { window.__mnoteLocalFolderEventBus.emitChangedFiles({ source: 'synthetic_page_ai_receipt', reason: 'agent_run_receipt', rootUri: rootUri, workspaceId: resolveWorkspaceId(document.body), changedFiles: changedPaths, affectedParents: affectedParents, payload: syntheticPayload }); } else { document.documentElement.setAttribute('data-mnote-page-ai-receipt-fallback-direct', 'true'); window.dispatchEvent(new CustomEvent('tree:local-folder-watch-batch', { detail: { payload: syntheticPayload } })); } } } if (refresh.touchesCurrentFile === true && !files.length) { var documentId = String(refresh.currentDocumentId || receipt && receipt.documentId || currentDocumentId() || '').trim(); document.documentElement.setAttribute('data-mnote-page-ai-receipt-current-refresh', 'true'); window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', { detail: { toolName: 'agent.run_receipt', normalizedToolName: 'agent.run_receipt', documentId: documentId, workspaceId: String(receipt && receipt.workspaceId || resolveWorkspaceId(document.body) || ''), rootUri: String(receipt && receipt.rootUri || fallbackAudit && fallbackAudit.rootUri || currentRootUri() || ''), changedFiles: files, receipt: receipt || null, runId: runId, traceId: traceId, toolCallId: runId + ':agent.run_receipt' } })); } } function pageAiToolEventDeepFindString(value, keys, depth) { if (!value || typeof value !== 'object' || depth > 5) return ''; for (var index = 0; index < keys.length; index += 1) { var key = keys[index]; if (Object.prototype.hasOwnProperty.call(value, key) && typeof value[key] === 'string' && value[key].trim()) { return value[key].trim(); } } if (Array.isArray(value)) { for (var arrayIndex = 0; arrayIndex < value.length; arrayIndex += 1) { var fromArray = pageAiToolEventDeepFindString(value[arrayIndex], keys, depth + 1); if (fromArray) return fromArray; } return ''; } var preferred = ['audit', 'args', 'arguments', 'input', 'result', 'summary', 'output', 'upstream']; for (var prefIndex = 0; prefIndex < preferred.length; prefIndex += 1) { var child = value[preferred[prefIndex]]; var fromPreferred = pageAiToolEventDeepFindString(child, keys, depth + 1); if (fromPreferred) return fromPreferred; } var objectKeys = Object.keys(value); for (var objectIndex = 0; objectIndex < objectKeys.length; objectIndex += 1) { var fromObject = pageAiToolEventDeepFindString(value[objectKeys[objectIndex]], keys, depth + 1); if (fromObject) return fromObject; } return ''; } function pageAiNotifyToolWriteCompleted(toolName, toolEvent, runId, runTraceId) { var normalizedTool = pageAiNormalizeToolName(toolName); var writesCurrentPage = [ 'mnote.page.save', 'mnote.page.update.title', 'mnote.page.update.options', 'mnote.doc.apply.block.ops', 'mnote.block.replace', 'mnote.block.insert.after', 'mnote.block.delete', 'mnote.block.move.after' ].indexOf(normalizedTool) >= 0 || [ 'mnote.page.update_title', 'mnote.page.update_options', 'mnote.doc.apply_block_ops', 'mnote.block.replace', 'mnote.block.insert_after', 'mnote.block.delete', 'mnote.block.move_after' ].indexOf(String(toolName || '').trim()) >= 0; if (!writesCurrentPage) return; var documentId = pageAiToolEventDeepFindString(toolEvent, ['documentId', 'document_id'], 0) || currentDocumentId(); var workspaceId = pageAiToolEventDeepFindString(toolEvent, ['workspaceId', 'workspace_id'], 0) || resolveWorkspaceId(document.body); try { window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', { detail: { toolName: toolName, normalizedToolName: normalizedTool, documentId: documentId, workspaceId: workspaceId, runId: String(toolEvent && (toolEvent.run_id || toolEvent.runId) || runId || ''), traceId: String(toolEvent && (toolEvent.trace_id || toolEvent.traceId) || runTraceId || ''), toolCallId: String(toolEvent && (toolEvent.tool_call_id || toolEvent.toolCallId || toolEvent.id) || '') } })); } catch (error) { console.warn('mnote 页面 AI 写入刷新事件派发失败', error); } } function pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId) { var toolEvent = null; try { toolEvent = JSON.parse(payloadText || 'null'); } catch (_) { toolEvent = {}; } var rawToolName = toolEvent && (toolEvent.name || toolEvent.tool || toolEvent.toolName || ''); var toolName = String(rawToolName || eventName); var toolCallId = String(toolEvent && (toolEvent.tool_call_id || toolEvent.toolCallId || toolEvent.id) || (runId + ':' + toolName)); var eventStatus = String(toolEvent && toolEvent.status || '').trim(); var status = eventName === 'tool.completed' ? (toolEvent && toolEvent.error ? 'failed' : 'completed') : (eventName === 'tool.failed' ? 'failed' : (eventStatus || 'running')); if (status === 'in_progress' || status === 'pending') status = 'running'; var traceId = String(toolEvent && (toolEvent.trace_id || toolEvent.traceId) || runTraceId || ''); var auditId = String(toolEvent && (toolEvent.audit_id || toolEvent.auditId) || ''); var argsSummary = pageAiPreviewValue(toolEvent && (toolEvent.arguments || toolEvent.args || toolEvent.input)); var resultSource = toolEvent && (toolEvent.summary || toolEvent.result || toolEvent.output); if (!resultSource && status === 'failed') { resultSource = [toolEvent && toolEvent.code, toolEvent && toolEvent.error].filter(Boolean).join(' '); } var resultSummary = pageAiPreviewValue(resultSource); pageUiState.pageAiLastToolCall = { event: eventName, name: toolName, runId: String(toolEvent && (toolEvent.run_id || toolEvent.runId) || runId), traceId: traceId, auditId: auditId }; var rawLocations = toolEvent && toolEvent.locations; var locations = Array.isArray(rawLocations) ? rawLocations.filter(function(l) { return typeof l === 'string' || (typeof l === 'object' && l && l.path); }).map(function(l) { return typeof l === 'string' ? l : l.path; }) : []; var existing = pageUiState.pageAiMessages.find(function(item) { return item.role === 'tool' && item.toolCallId === toolCallId; }); if (!rawToolName && existing && existing.toolName) toolName = existing.toolName; if (!existing) { existing = { role: 'tool', content: toolName, toolCallId: toolCallId, toolName: toolName, toolKind: String(toolEvent && toolEvent.kind || ''), status: status, argsSummary: '', resultSummary: '', locations: locations, traceId: traceId, auditId: auditId }; pageUiState.pageAiMessages.push(existing); } existing.content = toolName; existing.toolName = toolName; existing.toolKind = String(toolEvent && toolEvent.kind || existing.toolKind || ''); existing.status = status; existing.traceId = traceId || existing.traceId || ''; existing.auditId = auditId || existing.auditId || ''; if (argsSummary) existing.argsSummary = argsSummary; if (resultSummary) existing.resultSummary = resultSummary; if (locations.length) existing.locations = locations; existing.rawOutput = toolEvent && toolEvent.output; existing.rawResult = resultSource; if (status === 'completed') { pageAiNotifyToolWriteCompleted(toolName, toolEvent, runId, runTraceId); } return existing; } 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/page-ai/sessions/' + encodeURIComponent(sessionId) + '/queue/' + encodeURIComponent(queueId), { method: 'DELETE', headers: { 'accept': 'application/json' } }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'queue_cancel_failed_' + response.status)); pageUiState.pageAiQueuedItems = pageUiState.pageAiQueuedItems.filter(function(entry) { return entry.queueId !== queueId; }); var queueLength = Number(payload && (payload.queueLength || payload.queue_length || 0)); pageUiState.pageAiQueueLength = Number.isFinite(queueLength) ? Math.max(0, queueLength) : pageUiState.pageAiQueuedItems.length; pageUiState.pageAiMessages.push({ role: 'assistant', content: '已取消一条 AI 队列项。' }); } catch (error) { pageUiState.pageAiMessages.push({ role: 'assistant', content: '取消 AI 队列项失败:' + (error instanceof Error ? error.message : String(error)) }); } renderPageAiConversation(); 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: '已重置 runtime;MNote 会话历史保留,外部 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; document.documentElement.setAttribute('data-mnote-page-ai-context-scope', next); } function pageAiHermesSettingsUrl() { var configured = String(window.__mnoteHermesSettingsUrl || '').trim(); return configured || ''; } function pageAiOpenHermesSettings() { var url = pageAiHermesSettingsUrl(); if (url) { window.open(url, '_blank', 'noopener,noreferrer'); return; } pageUiState.pageAiProfileError = '未配置 Hermes 设置入口:请设置 MNOTE_WEB_HERMES_UPSTREAM_URL。'; renderPageAiControls(); } function pageAiNormalizeTools(payload) { var upstream = pageAiUnwrapUpstream(payload) || {}; var tools = pageAiNormalizeArray(upstream.tools || upstream); return tools.map(function(tool) { var name = String(tool && (tool.name || tool.toolName || tool.tool) || '').trim(); if (!name) return null; return { name: name, description: String(tool && tool.description || '').trim(), scope: String(tool && (tool.scope || tool.requiredScope || '') || '').trim(), kind: String(tool && (tool.kind || tool.mode || '') || '').trim(), status: String(tool && (tool.status || tool.permission || 'available') || '').trim(), enabled: tool && tool.enabled !== false, unavailableReason: String(tool && (tool.unavailableReason || tool.reason || '') || '').trim() }; }).filter(Boolean); } function pageAiErrorMessage(payload, fallback) { if (!payload || typeof payload !== 'object') return fallback; return String(payload.message || payload.error || payload.code || fallback || '').trim() || fallback; } function pageAiNormalizeGatewayHealth(payload) { var upstream = pageAiUnwrapUpstream(payload) || payload || {}; return { ok: Boolean(upstream.ok), profile: upstream.profile || null, gateway: upstream.gateway || null, suggestions: pageAiNormalizeArray(upstream.suggestions).map(function(item) { return String(item || '').trim(); }).filter(Boolean) }; } function pageAiSetDraftForSection(section, value) { pageUiState.pageAiProfileMemoryDrafts[section] = String(value == null ? '' : value); } function pageAiApplyProfileMemory(payload) { var upstream = pageAiUnwrapUpstream(payload) || {}; pageUiState.pageAiProfileMemory = { memory: String(upstream.memory || ''), user: String(upstream.user || ''), soul: String(upstream.soul || '') }; pageUiState.pageAiProfileMemoryDrafts = { memory: pageUiState.pageAiProfileMemory.memory, user: pageUiState.pageAiProfileMemory.user, soul: pageUiState.pageAiProfileMemory.soul }; } async function pageAiLoadTools() { try { var descriptor = pageAiAgentDescriptor(pageAiCurrentAgentId()); if (descriptor && pageUiState.pageAiAgentDescriptorsLoaded) { pageUiState.pageAiTools = pageAiNormalizeArray(descriptor.tools); pageUiState.pageAiToolsError = ''; renderPageAiControls(); return; } var response = await fetch('/api/hermes/client/tools?scope=mnote&profile=' + encodeURIComponent(pageAiCurrentProfile()), { headers: { 'accept': 'application/json' } }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'tools_failed_' + response.status)); pageUiState.pageAiTools = pageAiNormalizeTools(payload); pageUiState.pageAiToolsError = ''; } catch (error) { pageUiState.pageAiToolsError = error instanceof Error ? error.message : String(error); } renderPageAiControls(); } async function pageAiLoadGatewayHealth() { try { var response = await fetch('/api/hermes/client/gateway/health?profile=' + encodeURIComponent(pageAiCurrentProfile()), { headers: { 'accept': 'application/json' } }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'gateway_health_failed_' + response.status)); pageUiState.pageAiGatewayHealth = pageAiNormalizeGatewayHealth(payload); pageUiState.pageAiGatewayHealthError = ''; } catch (error) { pageUiState.pageAiGatewayHealthError = error instanceof Error ? error.message : String(error); } renderPageAiControls(); } async function pageAiStopRun() { var runId = String(pageUiState.pageAiCurrentRunId || '').trim(); if (!runId || pageUiState.pageAiRunStatus === 'idle' || pageUiState.pageAiRunStatus === 'completed') return; pageUiState.pageAiStoppedRunIds[runId] = true; try { if (pageAiBoardFirstEnabled()) { var boardResponse = await fetch('/api/page-ai/board/runs/' + encodeURIComponent(runId) + '/cancel', { method: 'POST', headers: { accept: 'application/json' } }); var boardPayload = await boardResponse.json().catch(function(){ return null; }); if (!boardResponse.ok) throw new Error(pageAiErrorMessage(boardPayload, 'board_run_cancel_failed_' + boardResponse.status)); pageAiSetRunStatus('cancelled', runId); pageUiState.pageAiMessages.push({ role: 'assistant', content: '已请求停止当前 Board run。', boardRunId: runId }); return; } var response = await fetch('/api/hermes/client/runs/' + encodeURIComponent(runId) + '/abort', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ sessionId: pageUiState.pageAiActiveSessionId, profile: pageAiRunProfile(), acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', reason: 'page_ai_user_stop' }) }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'run_abort_failed_' + response.status)); pageAiApplyRuntimeState(payload && payload.runtime); pageAiSetRunStatus('aborted', runId); pageUiState.pageAiMessages.push({ role: 'assistant', content: '已请求停止当前 AI run。' }); } catch (error) { pageAiSetRunStatus('failed', runId); pageUiState.pageAiMessages.push({ role: 'assistant', content: '停止 AI run 失败:' + (error instanceof Error ? error.message : String(error)) }); } renderPageAiControls(); renderPageAiConversation(); } function pageAiLoadSkillCatalog(runtime, profile) { var params = new URLSearchParams(); params.set('runtime', runtime); var endpoint = '/api/hermes/client/skills'; if (runtime === 'mnote') { endpoint = '/api/hermes/client/capabilities'; params.set('agentId', 'reasonix'); if (profile) params.set('profile', profile); } else if (runtime === 'reasonix') { params.set('runtime', 'reasonix'); } else if (runtime === 'hermes' && profile) { params.set('profileId', profile); } return fetch(endpoint + '?' + params.toString(), { headers: { 'accept': 'application/json' } }).then(function(response) { return response.json().catch(function(){ return null; }).then(function(payload) { if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'capabilities_failed_' + response.status)); return pageAiNormalizeSkills(payload); }); }); } async function pageAiLoadProfiles() { try { var response = await fetch('/api/ai/agent-profiles?agentId=hermes', { headers: { 'accept': 'application/json' } }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profiles_failed_' + response.status)); var profiles = pageAiNormalizeProfiles(payload); pageUiState.pageAiProfiles = profiles.length ? profiles : [{ profileId: 'shared_lite', name: 'shared_lite', alias: 'Lite', kind: 'shared', readonly: true, canManageSkills: false }]; try { var runtimeResponse = await fetch('/api/hermes/client/profiles', { headers: { 'accept': 'application/json' } }); var runtimePayload = await runtimeResponse.json().catch(function(){ return null; }); var acpRuntimes = Array.isArray(runtimePayload && runtimePayload.acpRuntimes) ? runtimePayload.acpRuntimes : []; pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(acpRuntimes); } catch (_) { pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(pageUiState.pageAiAcpRuntimes); } var current = pageAiCurrentProfile(); var hasCurrent = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === current; }); var personal = pageUiState.pageAiProfiles.find(function(profile) { return profile.kind === 'personal'; }); var active = pageAiProfileValue(pageUiState.pageAiProfiles.find(function(profile) { return profile.active; })); pageAiSetActiveProfile(hasCurrent && current !== 'default' ? current : (active || pageAiProfileValue(personal) || pageAiProfileValue(pageUiState.pageAiProfiles[0]) || current)); pageUiState.pageAiProfileError = ''; void pageAiLoadAgentDescriptors(); void pageAiLoadGatewayHealth(); } catch (error) { pageUiState.pageAiProfileError = error instanceof Error ? error.message : String(error); if (!pageUiState.pageAiProfiles.length) pageUiState.pageAiProfiles = [{ profileId: 'shared_lite', name: 'shared_lite', alias: 'Lite', kind: 'shared', readonly: true, canManageSkills: false }]; pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(pageUiState.pageAiAcpRuntimes); pageAiSetActiveProfile(pageAiCurrentProfile()); void pageAiLoadAgentDescriptors(); } renderPageAiProviderButtons(); renderPageAiControls(); } async function pageAiSwitchProfile(profileName) { var next = String(profileName || '').trim(); if (!next) return; var previousSkillSource = pageAiCurrentSkillSource(); pageAiSetActiveProfile(next); pageAiPersistRawAiPreference('ai.agent.hermes.profile_id', next); if (previousSkillSource.indexOf('hermes:') === 0) { pageUiState.pageAiActiveSkillSource = 'hermes:' + next; pageAiPersistAiPreference('skills.active_source', pageUiState.pageAiActiveSkillSource); } pageUiState.pageAiSkills = { categories: [], archived: [] }; pageUiState.pageAiSkillCatalogs = Object.assign({}, pageUiState.pageAiSkillCatalogs || {}, { hermes: { categories: [], archived: [] } }); pageUiState.pageAiSkillError = ''; renderPageAiProviderButtons(); renderPageAiControls(); try { pageUiState.pageAiSessions = pageUiState.pageAiSessions.filter(function(session) { return session && session.profile === next; }); pageUiState.pageAiActiveSessionId = ''; pageUiState.pageAiMessages = []; pageAiPersistSessions(); await pageAiEnsureHermesSession(true); await pageAiLoadProfileMemory(); await pageAiLoadAgentDescriptors(); await pageAiLoadSkills(); await pageAiLoadTools(); await pageAiLoadGatewayHealth(); renderPageAiControls(); } catch (error) { pageUiState.pageAiProfileError = error instanceof Error ? error.message : String(error); } renderPageAiConversation(); } async function pageAiLoadProfileMemory() { try { var response = await fetch('/api/hermes/client/profile-memory?profileId=' + encodeURIComponent(pageAiCurrentProfile()), { headers: { 'accept': 'application/json' } }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profile_memory_failed_' + response.status)); pageAiApplyProfileMemory(payload); pageUiState.pageAiProfileMemoryError = ''; } catch (error) { pageUiState.pageAiProfileMemoryError = error instanceof Error ? error.message : String(error); } renderPageAiControls(); renderPageAiConversation(); } async function pageAiSaveProfileMemory(section) { var normalized = String(section || '').trim(); if (['memory', 'user', 'soul'].indexOf(normalized) < 0) return; var content = String(pageUiState.pageAiProfileMemoryDrafts[normalized] || ''); try { var response = await fetch('/api/hermes/client/profile-memory', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ profileId: pageAiCurrentProfile(), section: normalized, content: content }) }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'profile_memory_save_failed_' + response.status)); pageUiState.pageAiProfileMemory[normalized] = content; pageUiState.pageAiProfileMemoryError = ''; document.documentElement.setAttribute('data-mnote-page-ai-memory-saved', normalized); } catch (error) { pageUiState.pageAiProfileMemoryError = error instanceof Error ? error.message : String(error); } renderPageAiControls(); renderPageAiConversation(); } async function pageAiLoadSkills() { var requestedSource = pageAiCurrentSkillSource(); var sourceParts = pageAiSkillSourceParts(requestedSource); var loadSeq = (Number(pageUiState.pageAiSkillLoadSeq || 0) || 0) + 1; pageUiState.pageAiSkillLoadSeq = loadSeq; try { var catalog = await pageAiLoadSkillCatalog(sourceParts.group, sourceParts.profile || pageAiCurrentProfile()); if (pageUiState.pageAiSkillLoadSeq !== loadSeq || pageAiCurrentSkillSource() !== requestedSource) return; pageUiState.pageAiSkillCatalogs = Object.assign({}, pageUiState.pageAiSkillCatalogs || {}, { [sourceParts.group]: catalog, [requestedSource]: catalog }); pageUiState.pageAiSkills = catalog; pageUiState.pageAiSkillError = ''; } catch (error) { if (pageUiState.pageAiSkillLoadSeq !== loadSeq || pageAiCurrentSkillSource() !== requestedSource) return; pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error); pageUiState.pageAiSkills = { categories: [], archived: [] }; pageUiState.pageAiSkillCatalogs = pageUiState.pageAiSkillCatalogs || { mnote: { categories: [], archived: [] }, reasonix: { categories: [], archived: [] }, hermes: { categories: [], archived: [] } }; } renderPageAiControls(); renderPageAiConversation(); } async function pageAiToggleSkill(skillName, enabled, group, profile) { var name = String(skillName || '').trim(); if (!name) return; var skillGroup = String(group || 'hermes').trim() || 'hermes'; var skillProfile = String(profile || pageAiCurrentProfile() || '').trim(); if (skillGroup === 'reasonix') { pageAiSetSkillPreference(skillGroup, name, Boolean(enabled), skillProfile); document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', skillGroup + ':' + name); renderPageAiControls(); return; } if (skillGroup === 'mnote') { try { var mnoteResponse = await fetch('/api/hermes/client/capabilities/toggle', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ runtime: 'mnote', profile: skillProfile || pageAiCurrentProfile(), id: name, enabled: Boolean(enabled) }) }); var mnotePayload = await mnoteResponse.json().catch(function(){ return null; }); if (!mnoteResponse.ok) throw new Error(pageAiErrorMessage(mnotePayload, 'mnote_capability_toggle_failed_' + mnoteResponse.status)); pageAiSetSkillPreference(skillGroup, name, Boolean(enabled), skillProfile); pageAiNormalizeArray(pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs.mnote && pageUiState.pageAiSkillCatalogs.mnote.categories).forEach(function(category) { pageAiNormalizeArray(category.skills).forEach(function(skill) { if (skill.id === name || skill.name === name) { skill.enabled = Boolean(enabled); skill.status = enabled ? 'available' : 'disabled'; pageAiNormalizeArray(skill.tools).forEach(function(tool) { tool.enabled = Boolean(enabled); tool.status = enabled ? 'available' : 'disabled'; }); skill.disabledToolCount = enabled ? 0 : Number(skill.toolCount || pageAiNormalizeArray(skill.tools).length || 0); } }); }); document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', skillGroup + ':' + name); } catch (error) { pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error); } renderPageAiControls(); return; } var previous = null; pageAiToggleableSkillEntries('hermes', skillProfile || pageAiCurrentProfile()).forEach(function(skill) { if (skill.id === name || skill.name === name) previous = skill.enabled !== false; }); try { var response = await fetch('/api/hermes/client/skills/toggle', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ profile: skillProfile || pageAiCurrentProfile(), profileId: skillProfile || pageAiCurrentProfile(), skillKind: 'hermes_profile', name: name, enabled: Boolean(enabled) }) }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'skill_toggle_failed_' + response.status)); var hermesCatalog = pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs.hermes ? pageUiState.pageAiSkillCatalogs.hermes : pageUiState.pageAiSkills; pageAiNormalizeArray(hermesCatalog.categories).forEach(function(category) { pageAiNormalizeArray(category.skills).forEach(function(skill) { if (skill.name === name || skill.id === name) skill.enabled = Boolean(enabled); }); }); pageUiState.pageAiSkills = hermesCatalog; pageAiSetSkillPreference('hermes', name, Boolean(enabled), skillProfile || pageAiCurrentProfile()); document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', name); pageUiState.pageAiSkillError = ''; } catch (error) { pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error); if (previous != null) { var rollbackCatalog = pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs.hermes ? pageUiState.pageAiSkillCatalogs.hermes : pageUiState.pageAiSkills; pageAiNormalizeArray(rollbackCatalog.categories).forEach(function(category) { pageAiNormalizeArray(category.skills).forEach(function(skill) { if (skill.name === name || skill.id === name) skill.enabled = previous; }); }); } } renderPageAiControls(); renderPageAiConversation(); } async function pageAiToggleTool(toolName, enabled) { var name = String(toolName || '').trim(); if (!name) return; var previous = null; pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) { if (tool.name === name && previous == null) previous = tool.enabled !== false; }); try { var response = await fetch('/api/hermes/client/tools/toggle', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ profile: pageAiCurrentProfile(), name: name, enabled: Boolean(enabled) }) }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'tool_toggle_failed_' + response.status)); pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) { if (tool.name === name) { tool.enabled = Boolean(enabled); tool.status = Boolean(enabled) ? 'available' : 'disabled'; tool.unavailableReason = Boolean(enabled) ? '' : '当前 Hermes profile 已关闭该 mnote tool'; } }); document.documentElement.setAttribute('data-mnote-page-ai-tool-toggled', name); pageUiState.pageAiToolsError = ''; } catch (error) { pageUiState.pageAiToolsError = error instanceof Error ? error.message : String(error); if (previous != null) { pageAiNormalizeArray(pageUiState.pageAiTools).forEach(function(tool) { if (tool.name === name) tool.enabled = previous; }); } } renderPageAiControls(); } function openPageAiDrawer() { if (pageAiOpencodeHostEnabled()) { pageAiEnsureContextRefState(); void pageAiLoadAllowedRoots().then(function() { pageAiRenderOpencodeHostChrome(); }).catch(function() { pageAiRenderOpencodeHostChrome(); }); var opencodeDrawer = pageAiEnsureOpencodeHostDrawer(); pageAiApplyDrawerWidth(opencodeDrawer); opencodeDrawer.hidden = false; pageUiState.pageAiOpen = true; updatePageAiTriggerState(); void pageAiLoadOpencodeStatus(); return; } pageUiState.pageAiAgentId = pageAiCurrentAgentId(); pageAiEnsureContextRefState(); pageAiLoadSessions(); pageAiRestorePermissionRequests(); renderPageAiSuggestions(); renderPageAiConversation(); renderPageAiProviderButtons(); renderPageAiControls(); pageAiApplyBoardFirstShell(); var drawer = ensurePageAiDrawer(); pageAiApplyDrawerWidth(drawer); drawer.hidden = false; pageUiState.pageAiOpen = true; updatePageAiTriggerState(); 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(); pageAiApplyBoardFirstShell(); return null; }); }); void pageAiLoadBackendSessions().then(function() { return pageAiCheckAndResumeActiveRun(); }).catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); pageAiApplyBoardFirstShell(); }); Promise.all([ pageAiLoadProfiles(), pageAiLoadProfileMemory(), pageAiLoadAgentDescriptors(), pageAiLoadSkills(), pageAiLoadTools(), pageAiLoadGatewayHealth(), pageAiLoadAiPreferences(), pageAiLoadBoardCatalog(), pageAiLoadAllowedRoots(), pageAiLoadBackendSessions(), pageAiLoadRuntimeStatus() ]).then(function() { renderPageAiControls(); pageAiApplyBoardFirstShell(); }).then(function() { return pageAiCheckAndResumeActiveRun().catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); pageAiApplyBoardFirstShell(); return null; }); }).then(function() { if (!pageAiBoardFirstEnabled()) return pageAiRestoreHermesSession(); return null; }).then(function() { renderPageAiControls(); pageAiApplyBoardFirstShell(); }).catch(function(error) { pageUiState.pageAiMessages.push({ role: 'assistant', content: '当前 AI 不可用:' + (error instanceof Error ? error.message : String(error)) }); renderPageAiConversation(); renderPageAiControls(); pageAiApplyBoardFirstShell(); }); } function closePageAiDrawer() { var drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]'); if (drawer instanceof HTMLElement) drawer.hidden = true; pageUiState.pageAiOpen = false; updatePageAiTriggerState(); } function isPageAiDrawerOpen() { var drawer = document.querySelector('[data-testid="wolai-page-ai-drawer"]'); return drawer instanceof HTMLElement && !drawer.hidden; } async function streamPageAiResponse(response, onEvent) { if (!response.body || typeof response.body.getReader !== 'function') return; var reader = response.body.getReader(); var decoder = new TextDecoder(); var buffer = ''; while (true) { var chunk = await reader.read(); if (chunk.done) break; buffer += decoder.decode(chunk.value, { stream: true }); var frames = buffer.split('\n\n'); buffer = frames.pop() || ''; frames.forEach(function(frame) { var eventName = ''; var dataLines = []; frame.split('\n').forEach(function(line) { if (line.startsWith('event:')) eventName = line.slice(6).trim(); if (line.startsWith('data:')) dataLines.push(line.slice(5).trim()); }); var payloadText = dataLines.join('\n'); if (!eventName && payloadText) { try { var parsed = JSON.parse(payloadText); eventName = parsed && parsed.event ? String(parsed.event) : ''; } catch (_) {} } if (eventName) onEvent(eventName, payloadText); }); } } function pageAiDecodeDeltaText(payloadText) { try { var payload = JSON.parse(payloadText || 'null'); return String((payload && (payload.text || payload.delta)) || ''); } catch (_) { return String(payloadText || ''); } } function pageAiEventSeqFromPayloadText(payloadText) { try { var payload = JSON.parse(payloadText || 'null'); return String((payload && payload.seq) || '').trim(); } catch (_) { return ''; } } function pageAiTrackStreamingRunEvent(runId, eventName, payloadText) { var seq = pageAiEventSeqFromPayloadText(payloadText); if (!seq) return; pageAiWriteActiveRunSnapshot({ hostRunId: runId, sessionId: pageUiState.pageAiActiveSessionId || '', lastSeq: seq, status: pageAiRunStatusIsTerminal(eventName) ? pageUiState.pageAiRunStatus : (pageUiState.pageAiRunStatus || 'running') }); document.documentElement.setAttribute('data-mnote-page-ai-active-run-last-seq', seq); } function pageAiEnsureStreamingAssistantMessage(runId) { var id = String(runId || pageUiState.pageAiCurrentRunId || ''); var existing = pageUiState.pageAiMessages.find(function(item) { return item.role === 'assistant' && item.streaming === true && item.runId === id; }); if (existing) return existing; existing = { role: 'assistant', content: '', runId: id, streaming: true }; pageUiState.pageAiMessages.push(existing); return existing; } function pageAiAppendStreamingAssistantDelta(runId, deltaText) { var delta = String(deltaText || ''); if (!delta) return ''; var message = pageAiEnsureStreamingAssistantMessage(runId); message.content = String(message.content || '') + delta; pageAiSyncCurrentSessionMessages(); renderPageAiConversation(); return message.content; } function pageAiAppendCitationSection(content, citations) { var text = String(content || ''); var unique = []; pageAiNormalizeArray(citations).forEach(function(value) { var citation = String(value || '').trim(); if (!citation || unique.indexOf(citation) >= 0) return; unique.push(citation); }); var missing = unique.filter(function(citation) { return text.indexOf(citation) < 0; }); if (!missing.length) return text; return text.replace(/\s+$/g, '') + '\n\n**引用**\n' + missing.map(function(citation) { return '- ' + citation; }).join('\n'); } function pageAiPromptNeedsKnowledgeRagCitations(prompt) { var text = searchText(prompt); if (!text) return false; var asksCitation = ['链接', '引用', '来源', '出处', '证据', '定位', 'link', 'citation', 'source'].some(function(word) { return text.indexOf(word) >= 0; }); if (!asksCitation) return false; return ['weknora', '资料库', '知识库', 'rag', '文献', '论文', '保护基'].some(function(word) { return text.indexOf(word) >= 0; }); } function pageAiKnowledgeRagFallbackQuery(prompt) { var text = searchText(prompt); if (!text) return ''; var afterColon = text.split(/[::]/).slice(1).join(':').trim(); var candidate = afterColon || text; candidate = candidate .replace(/^(请|请用|帮我|帮忙|用)?(资料库|知识库|weknora|rag)?(搜索|检索|查找|查询|回答|说明|解释|总结)?/i, '') .replace(/(请)?(给出|给我|附上|提供)?(链接|引用|来源|出处|证据|定位).*$/i, '') .trim(); var stopIndex = candidate.search(/(在|的|是|有哪些|有什么|如何|怎么|用于|用途|作用|资料|文献|论文)/); if (stopIndex > 0) candidate = candidate.slice(0, stopIndex).trim(); var cjkMatch = candidate.match(/[A-Za-z0-9\u4e00-\u9fff·α-ωΑ-Ω\-]{2,24}/); if (cjkMatch && cjkMatch[0]) return cjkMatch[0]; return text; } function pageAiCleanDegradedCitationNotes(content) { return String(content || '') .split('\n') .filter(function(line) { var text = String(line || ''); return text.indexOf('来源定位降级') < 0 && text.toLowerCase().indexOf('locator degraded') < 0; }) .join('\n') .replace(/\n{3,}/g, '\n\n') .trim(); } function pageAiCitationPrecisionLabel(value) { var precision = searchText(value || '').toLowerCase(); if (precision === 'bbox') return '精确定位'; if (precision === 'paragraph') return '段落级'; if (precision === 'file') return '文件级'; return ''; } function pageAiCitationSourceTitle(citation) { if (!citation || typeof citation !== 'object') return ''; var path = searchText(citation.sourceRootRelativePath || citation.sourcePath || citation.filePath || citation.lightRagFilePath || ''); if (path) { var parts = path.split('/').filter(Boolean); return parts[parts.length - 1] || path; } var markdown = searchText(citation.citationMarkdown || ''); var labelMatch = markdown.match(/^\[([^\]]+)\]/); return labelMatch ? labelMatch[1] : ''; } function pageAiCitationDisplayMarkdown(value) { if (typeof value === 'string') return value.trim(); if (!value || typeof value !== 'object') return ''; var markdown = searchText(value.citationMarkdown || ''); var citationId = searchText(value.citationLabel || (value.citationId ? '[' + value.citationId + ']' : '')); var title = pageAiCitationSourceTitle(value); var headingPath = pageAiNormalizeArray(value.headingPath).map(function(item) { return searchText(item); }).filter(Boolean).join(' > '); var precisionLabel = pageAiCitationPrecisionLabel(value.locatorPrecision); var quote = searchText(value.displayQuote || value.quote || ''); if (quote.length > 120) quote = quote.slice(0, 117) + '...'; var head = markdown || title; if (citationId && head.indexOf(citationId) < 0) head = citationId + ' ' + head; var details = []; if (precisionLabel) details.push(precisionLabel); if (headingPath) details.push(headingPath); if (quote) details.push(quote); return [head].concat(details).filter(Boolean).join(' · ').trim(); } function pageAiFilterCitationCards(values) { var cards = pageAiNormalizeArray(values); var hasPreciseCitation = cards.some(function(value) { if (typeof value === 'string') return value.indexOf('来源定位降级') < 0; return value && typeof value === 'object' && searchText(value.citationMarkdown || '') && value.locatorDegraded !== true; }); var seen = {}; return cards.map(pageAiCitationDisplayMarkdown).filter(function(card, index) { var source = cards[index]; if (!card || seen[card]) return false; if (hasPreciseCitation) { if (typeof source === 'string' && source.indexOf('来源定位降级') >= 0) return false; if (source && typeof source === 'object' && source.locatorDegraded === true) return false; } seen[card] = true; return true; }).slice(0, 5); } function pageAiCollectKnowledgeRagApiCitations(payload) { var structuredCitations = pageAiNormalizeArray(payload && payload.citations); if (structuredCitations.length) { return pageAiFilterCitationCards(structuredCitations); } var references = pageAiNormalizeArray(payload && payload.references); return pageAiFilterCitationCards(references); } async function pageAiAppendKnowledgeRagFallbackCitations(runId, promptText) { if (!pageAiPromptNeedsKnowledgeRagCitations(promptText)) return; var rootUri = currentRootUri(); if (!rootUri) return; var fallbackQuery = pageAiKnowledgeRagFallbackQuery(promptText); if (!fallbackQuery) return; try { var response = await fetch('/api/knowledge-rag/search', { method: 'POST', headers: { 'content-type': 'application/json', 'accept': 'application/json' }, body: JSON.stringify({ workspaceId: resolveWorkspaceId(document.body) || undefined, rootUri: rootUri, query: fallbackQuery, mode: 'mix', topK: 12, chunkTopK: 24, includeChunkContent: true }) }); var payload = await response.json().catch(function() { return null; }); if (!response.ok) return; var citations = pageAiCollectKnowledgeRagApiCitations(payload); if (!citations.length) return; var message = pageUiState.pageAiMessages.find(function(item) { return item.role === 'assistant' && item.runId === runId; }) || pageUiState.pageAiMessages.filter(function(item) { return item.role === 'assistant'; }).slice(-1)[0]; if (!message) return; var hasPrecise = citations.some(function(citation) { return citation.indexOf('来源定位降级') < 0; }); var baseContent = hasPrecise ? pageAiCleanDegradedCitationNotes(message.content) : String(message.content || ''); message.content = pageAiAppendCitationSection(baseContent, citations); pageAiSyncCurrentSessionMessages(); pageAiPersistSessions(); renderPageAiConversation(); } catch (error) { console.warn('MNote Page AI 自动追加知识库引用失败', error); } } function pageAiFinishStreamingAssistantMessage(runId, finalText, promptText, citations) { var id = String(runId || pageUiState.pageAiCurrentRunId || ''); var message = pageUiState.pageAiMessages.find(function(item) { return item.role === 'assistant' && item.streaming === true && item.runId === id; }); var text = String(finalText || (message && message.content) || ''); var hasPreciseCitation = pageAiNormalizeArray(citations).some(function(citation) { return String(citation || '').indexOf('来源定位降级') < 0; }); var contentBase = humanizePageAiResponse(text, promptText); if (hasPreciseCitation) contentBase = pageAiCleanDegradedCitationNotes(contentBase); var content = pageAiAppendCitationSection(contentBase, citations); if (message) { message.content = content; message.streaming = false; } else if (content) { pageUiState.pageAiMessages.push({ role: 'assistant', content: content, runId: id }); } pageAiSyncCurrentSessionMessages(); pageAiPersistSessions(); void pageAiAppendKnowledgeRagFallbackCitations(id, promptText); } function pageAiStopStreamingAssistantMessage(runId) { var id = String(runId || pageUiState.pageAiCurrentRunId || ''); var changed = false; pageUiState.pageAiMessages.forEach(function(message) { if (message && message.role === 'assistant' && message.streaming === true && (!id || message.runId === id)) { message.streaming = false; changed = true; } }); if (!changed) return; pageAiSyncCurrentSessionMessages(); pageAiPersistSessions(); renderPageAiConversation(); } function pageAiToolOutputText(value) { var parts = []; function visit(node) { if (node == null) return; if (typeof node === 'string') { parts.push(node); return; } if (typeof node === 'number' || typeof node === 'boolean') return; if (Array.isArray(node)) { node.forEach(visit); return; } if (typeof node !== 'object') return; if (typeof node.text === 'string') parts.push(node.text); if (typeof node.content === 'string') parts.push(node.content); if (node.content && typeof node.content === 'object') visit(node.content); if (node.output && typeof node.output === 'object') visit(node.output); if (node.result && typeof node.result === 'object') visit(node.result); } visit(value); return parts.join('\n'); } function pageAiParseJsonMaybe(value) { if (value && typeof value === 'object') return value; var text = String(value || '').trim(); if (!text || (text[0] !== '{' && text[0] !== '[')) return null; try { return JSON.parse(text); } catch (_error) { var firstLine = text.split('\n')[0].trim(); if (firstLine && firstLine !== text && (firstLine[0] === '{' || firstLine[0] === '[')) { try { return JSON.parse(firstLine); } catch (_lineError) {} } return null; } } function pageAiCollectCitationMarkdowns(value) { var citations = []; var seen = {}; function add(value) { var citation = String(value || '').trim(); if (!citation || seen[citation]) return; seen[citation] = true; citations.push(citation); } function visit(node) { if (node == null) return; if (typeof node === 'string') { var parsed = pageAiParseJsonMaybe(node); if (parsed) visit(parsed); return; } if (Array.isArray(node)) { node.forEach(visit); return; } if (typeof node !== 'object') return; if (typeof node.citationMarkdown === 'string') add(pageAiCitationDisplayMarkdown(node)); if (Array.isArray(node.citationMarkdowns)) { node.citationMarkdowns.forEach(function(value) { if (typeof value === 'string') add(value); else if (value && typeof value === 'object') add(pageAiCitationDisplayMarkdown(value)); else visit(value); }); } Object.keys(node).forEach(function(key) { visit(node[key]); }); } visit(value); return citations; } function pageAiCollectKnowledgeRagCitations(toolItem, toolEvent) { var toolName = String((toolItem && toolItem.toolName) || (toolEvent && (toolEvent.name || toolEvent.tool || toolEvent.toolName)) || ''); var sources = [ toolEvent, toolItem, toolEvent && toolEvent.output, toolEvent && toolEvent.result, toolEvent && toolEvent.summary, toolItem && toolItem.rawOutput, toolItem && toolItem.rawResult, toolItem && toolItem.resultSummary ]; var citations = []; var outputText = ''; sources.forEach(function(source) { citations = citations.concat(pageAiCollectCitationMarkdowns(source)); var sourceText = pageAiToolOutputText(source); if (sourceText) outputText += '\n' + sourceText; var parsed = pageAiParseJsonMaybe(sourceText); if (parsed) citations = citations.concat(pageAiCollectCitationMarkdowns(parsed)); }); var looksLikeKnowledgeRag = toolName.indexOf('knowledge_rag') >= 0 || toolName.indexOf('knowledge.rag') >= 0 || outputText.indexOf('mnote.knowledge_rag') >= 0 || outputText.indexOf('uiCitations') >= 0; if (!looksLikeKnowledgeRag && citations.length === 0) return []; var hasPreciseCitation = citations.some(function(value) { return String(value || '').indexOf('来源定位降级') < 0; }); var seen = {}; return citations.filter(function(value) { var citation = String(value || '').trim(); if (!citation || seen[citation]) return false; if (hasPreciseCitation && citation.indexOf('来源定位降级') >= 0) return false; seen[citation] = true; return true; }); } function pageAiJournalPayloadText(event) { try { return JSON.stringify(event && event.payload && typeof event.payload === 'object' ? event.payload : {}); } catch (_) { return '{}'; } } function pageAiLastUserPrompt() { var messages = pageAiNormalizeArray(pageUiState.pageAiMessages); for (var index = messages.length - 1; index >= 0; index -= 1) { if (messages[index] && messages[index].role === 'user') return String(messages[index].content || ''); } return ''; } function pageAiApplyRunJournalEvent(event, runId, runTraceId) { if (!event || typeof event !== 'object') return; var eventName = String(event.kind || event.eventType || event.event || '').trim(); var payload = event.payload && typeof event.payload === 'object' ? event.payload : {}; if (payload.source === 'adapter_replay' || payload.replay === true) { document.documentElement.setAttribute('data-mnote-page-ai-adapter-replay-seen', 'true'); return; } var payloadText = pageAiJournalPayloadText(event); var prompt = pageAiLastUserPrompt(); if (eventName === 'message.delta') { pageAiAppendStreamingAssistantDelta(runId, pageAiDecodeDeltaText(payloadText)); } else if (eventName === 'run.completed') { pageAiFinishStreamingAssistantMessage(runId, String(payload.output || ''), prompt, []); pageAiSetRunStatus('completed', runId); } else if (eventName === 'run.failed') { pageAiFinishStreamingAssistantMessage(runId, String(payload.message || payload.code || payload.error || 'AI run failed'), prompt, []); pageAiSetRunStatus('failed', runId); } else if (eventName === 'run.aborted' || eventName === 'run.cancelled' || eventName === 'run.canceled') { pageUiState.pageAiStoppedRunIds[runId] = true; pageAiStopStreamingAssistantMessage(runId); pageAiSetRunStatus('aborted', runId); } else if (eventName === 'tool.started' || eventName === 'tool.completed' || eventName === 'tool.failed') { var toolItem = pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId); if (eventName === 'tool.completed') { var citations = pageAiCollectKnowledgeRagCitations(toolItem, payload); if (citations.length) pageAiFinishStreamingAssistantMessage(runId, '', prompt, citations); } pageAiSetRunStatus('tool_calling', runId); } else if (eventName === 'plan.updated') { var planEntries = Array.isArray(payload.entries) ? payload.entries : []; if (planEntries.length) { pageUiState.pageAiMessages.push({ role: 'system', kind: 'plan', entries: planEntries, createdAt: Date.now(), updatedAt: Date.now() }); } } var seq = String(event.seq || '').trim(); if (seq) { pageAiWriteActiveRunSnapshot({ hostRunId: runId, sessionId: pageUiState.pageAiActiveSessionId || String(event.sessionId || ''), lastSeq: seq, status: pageUiState.pageAiRunStatus || 'running' }); document.documentElement.setAttribute('data-mnote-page-ai-active-run-last-seq', seq); } pageAiSyncCurrentSessionMessages(); pageAiPersistSessions(); renderPageAiConversation(); renderPageAiControls(); } async function pageAiResumeActiveRunJournal(run) { if (!run || typeof run !== 'object') return; var runId = String(run.hostRunId || run.runId || '').trim(); if (!runId) return; var snapshot = pageAiReadActiveRunSnapshot(); var afterSeq = snapshot && snapshot.hostRunId === runId ? String(snapshot.lastSeq || '000000000000000000') : '000000000000000000'; var response = await fetch('/api/page-ai/runs/' + encodeURIComponent(runId) + '/events?afterSeq=' + encodeURIComponent(afterSeq), { 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, 'run_journal_failed_' + response.status)); } var runTraceId = String((payload.run && payload.run.traceId) || (run && run.traceId) || ''); pageAiNormalizeArray(payload.events).forEach(function(event) { pageAiApplyRunJournalEvent(event, runId, runTraceId); }); var status = String((payload.run && payload.run.status) || run.status || '').trim(); if (pageAiRunStatusIsTerminal(status)) pageAiClearActiveRunSnapshot(runId); document.documentElement.setAttribute('data-mnote-page-ai-run-journal-resumed', runId); } function pageAiLooksLikeBlockEdit(prompt) { var text = searchText(prompt); return ['新增', '添加', '插入', '删除', '删掉', '修改', '替换', '改成', '移动', '移到', 'move', 'replace', 'delete', 'insert'].some(function(word) { return text.indexOf(word) >= 0; }); } function assertPageAiLocalWritePermission(prompt, targetPackage) { if (currentSourceKind() !== 'local_folder') return; if (!pageAiLooksLikeBlockEdit(prompt)) return; var target = pageAiNormalizeArray(targetPackage && targetPackage.targets)[0] || null; var permission = String(target && target.policy && target.policy.permission || '').trim(); if (permission === 'read_write') return; var error = new Error('当前本地工作区只读授权,不能让 AI 写入目标文件。请切换到有写权限的工作区或调整授权后重试。'); error.code = 'page_ai_target_readonly'; throw error; } async function pageAiTryBlockEditWorkflow(prompt, scopedContext, runTargetSnapshot) { if (currentSourceKind() === 'local_folder') return false; if (!pageAiLooksLikeBlockEdit(prompt)) return false; var runId = 'page-ai-fast-' + Date.now().toString(36); var traceId = 'page-ai-fast-' + Date.now().toString(36); pageAiSetRunStatus('running', runId); renderPageAiControls(); var started = Date.now(); var response = await fetch('/api/page-ai/block-edit-workflow', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ workspaceId: resolveWorkspaceId(document.body), documentId: currentDocumentId(), sourceKind: currentSourceKind(), rootUri: currentRootUri(), sessionId: pageUiState.pageAiActiveSessionId, runId: runId, profile: pageAiCurrentProfile(), model: pageAiMnoteToolModel(), message: prompt, pageContext: scopedContext.pageContext, editorTarget: scopedContext.editorTarget, runTargetSnapshot: runTargetSnapshot || null, selectedBlockId: scopedContext.selectedBlockId, selectedText: scopedContext.selectedText, traceId: traceId }) }); var payload = await response.json().catch(function() { return null; }); if (!response.ok || !payload || payload.ok !== true) { var code = payload && payload.code ? String(payload.code) : ''; if (code === 'page_ai_workflow_not_block_edit') return false; pageUiState.pageAiMessages.push({ role: 'tool', toolName: 'mnote.page_ai.block_edit_workflow', status: 'failed', toolCallId: runId, resultSummary: pageAiErrorMessage(payload, 'fast_path_failed_' + response.status) }); renderPageAiConversation(); pageAiSetRunStatus('failed', runId); renderPageAiControls(); return true; } pageUiState.pageAiMessages.push({ role: 'tool', toolName: 'mnote.page_ai.block_edit_workflow', status: 'completed', toolCallId: runId, resultSummary: 'operations=' + String(Array.isArray(payload.operations) ? payload.operations.length : 0) + ' · ' + String(Date.now() - started) + 'ms' }); pageUiState.pageAiMessages.push({ role: 'assistant', content: payload.message || '已通过页面块编辑快路径完成写入。' }); pageAiSetRunStatus('completed', runId); try { window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', { detail: { toolName: 'mnote.doc.apply_block_ops', normalizedToolName: 'mnote.doc.apply.block.ops', documentId: currentDocumentId(), workspaceId: resolveWorkspaceId(document.body), runId: runId, traceId: traceId, toolCallId: runId } })); } catch (_) {} var currentSession = pageAiCurrentSession(); if (currentSession) { currentSession.messages = pageUiState.pageAiMessages.slice(); currentSession.updatedAt = Date.now(); } pageAiPersistSessions(); renderPageAiConversation(); renderPageAiControls(); return true; } async function sendPageAiMessage(text) { if (pageAiBoardFirstEnabled()) { await sendPageAiBoardMessage(text); return; } 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 { await pageAiEnsureHermesSession(); if (!allowQueue) pageAiSetRunStatus('queued'); renderPageAiControls(); var contextSnapshot = currentPageAiContextSnapshot(); var scopedContext = pageAiScopedPageContext(contextSnapshot); scopedContext.editorTarget = currentPageAiScopedEditorTarget(); assertPageAiTargetInCurrentWorkspace(scopedContext.editorTarget); if (currentSourceKind() === 'local_folder' && !pageAiNormalizeArray(pageUiState.pageAiAllowedRoots).length) { await pageAiLoadAllowedRoots(); } await assertPageAiTargetWritable(scopedContext.editorTarget); var runTargetSnapshot = pageAiBuildRunTargetSnapshot(scopedContext, prompt); var contextRefs = pageAiBuildContextRefs(scopedContext, runTargetSnapshot); var allowedRoots = pageAiBuildAllowedRoots(); var agentTargetPackage = pageAiBuildAgentTargetPackage(scopedContext, runTargetSnapshot); assertPageAiLocalWritePermission(prompt, agentTargetPackage); if (scopedContext.pageContext && scopedContext.pageContext.aiContext) { scopedContext.pageContext.aiContext.runTargetSnapshot = runTargetSnapshot; scopedContext.pageContext.aiContext.agentTargetPackage = agentTargetPackage; } var requestPageContext = pageAiPageContextForRefs(scopedContext.pageContext, contextRefs); pageUiState.pageAiMessages.push({ role: 'user', content: prompt }); currentSession = pageAiCurrentSession(); var runProfile = pageAiRunProfile(); if (currentSession) { if (currentSession.title === '新会话') { currentSession.title = prompt.length > 18 ? prompt.slice(0, 18) + '…' : prompt; } currentSession.messages = pageUiState.pageAiMessages.slice(); currentSession.updatedAt = Date.now(); } pageAiPersistSessions(); renderPageAiConversation(); 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' }, body: JSON.stringify({ workspaceId: resolveWorkspaceId(document.body), documentId: currentDocumentId(), sourceKind: currentSourceKind(), rootUri: currentRootUri(), agentId: pageAiCurrentAgentId(), sessionId: pageUiState.pageAiActiveSessionId, acpSessionId: currentSession && currentSession.acpSessionId ? currentSession.acpSessionId : '', profile: runProfile, profileId: pageUiState.pageAiAcpRuntime === 'hermes' ? runProfile : '', acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', contextScope: pageUiState.pageAiContextScope, contextRefs: contextRefs, allowedRoots: allowedRoots, reasonixSettings: reasonixSettings, skillPreferences: { mnote: pageAiSkillPreferenceTable('mnote', ''), reasonix: pageAiSkillPreferenceTable('reasonix', ''), hermes: pageAiSkillPreferenceTable('hermes', runProfile) }, message: promptForRun, model: reasonixSettings && reasonixSettings.modelId ? reasonixSettings.modelId : pageAiMnoteToolModel(), pageContext: requestPageContext, editorTarget: scopedContext.editorTarget, runTargetSnapshot: runTargetSnapshot, targetPackage: agentTargetPackage, selectedBlockId: scopedContext.selectedBlockId, selectedText: scopedContext.selectedText || '', traceId: 'page-ai-run-' + Date.now().toString(36) }) }); if (!response.ok) { var errorPayload = await response.json().catch(function(){ return null; }); throw new Error(pageAiErrorMessage(errorPayload, 'page_ai_failed_' + response.status)); } var runPayload = await response.json().catch(function(){ return null; }); if (pageAiApplyQueuedRun(runPayload)) { pageUiState.pageAiMessages.push({ role: 'assistant', content: '已加入 AI 队列,前一条 run 完成后继续处理。' }); if (currentSession) { currentSession.messages = pageUiState.pageAiMessages.slice(); currentSession.updatedAt = Date.now(); } pageAiPersistSessions(); renderPageAiConversation(); renderPageAiControls(); void pageAiLoadRuntimeStatus().catch(function(error) { pageUiState.pageAiRuntimeStatusError = error instanceof Error ? error.message : String(error); renderPageAiControls(); }); return; } if (allowQueue) { throw new Error('hermes_queue_expected_queued_response'); } var upstream = runPayload && runPayload.upstream ? runPayload.upstream : runPayload; var runId = upstream && (upstream.run_id || upstream.runId); if (!runId) throw new Error('hermes_run_missing_run_id'); var runTraceId = String((upstream && (upstream.trace_id || upstream.traceId)) || (runPayload && (runPayload.trace_id || runPayload.traceId)) || ''); pageAiSetRunStatus('running', runId); pageAiSetRunTargetSnapshot(Object.assign({}, runTargetSnapshot, { runId: runId, sessionId: pageUiState.pageAiActiveSessionId, traceId: runTraceId })); renderPageAiControls(); var eventResponse = await fetch('/api/hermes/client/events/' + encodeURIComponent(runId), { headers: { 'accept': 'text/event-stream' } }); if (!eventResponse.ok) { var eventError = await eventResponse.json().catch(function(){ return null; }); throw new Error(pageAiErrorMessage(eventError, 'hermes_events_failed_' + eventResponse.status)); } var assistantText = ''; var autoCitations = []; await streamPageAiResponse(eventResponse, function(eventName, payloadText) { pageAiTrackStreamingRunEvent(runId, eventName, payloadText); var streamPayload = null; try { streamPayload = JSON.parse(payloadText || 'null'); } catch (_) {} if (streamPayload && (streamPayload.source === 'adapter_replay' || streamPayload.replay === true)) { document.documentElement.setAttribute('data-mnote-page-ai-adapter-replay-seen', 'true'); return; } if (eventName === 'message.delta') { if (pageUiState.pageAiStoppedRunIds[runId]) return; assistantText = pageAiAppendStreamingAssistantDelta(runId, pageAiDecodeDeltaText(payloadText)); } if (eventName === 'thought.delta') { try { var thoughtPayload = JSON.parse(payloadText || 'null'); var thoughtText = String((thoughtPayload && (thoughtPayload.delta || thoughtPayload.text)) || ''); if (thoughtText) { var msgs = pageUiState.pageAiMessages; var lastThought = msgs.length > 0 && msgs[msgs.length - 1].kind === 'thought' ? msgs[msgs.length - 1] : null; if (lastThought) { lastThought.content += thoughtText; } else { msgs.push({ role: 'assistant', kind: 'thought', content: thoughtText }); } pageAiSyncCurrentSessionMessages(); pageAiPersistSessions(); renderPageAiConversation(); } } catch (_) {} } if (eventName === 'usage.updated') { try { var usagePayload = JSON.parse(payloadText || 'null') || {}; var sessionForUsage = pageAiCurrentSession(); if (sessionForUsage) { sessionForUsage.usage = { source: 'usage_update', used: Number(usagePayload.used ?? usagePayload.contextUsed ?? 0), size: Number(usagePayload.size ?? usagePayload.contextSize ?? 0) }; pageAiPersistSessions(); } } catch (_) {} renderPageAiControls(); } if (eventName === 'permission.requested' || eventName === 'permission.denied' || eventName === 'permission.allowed') { pageAiApplyPermissionEvent(eventName, payloadText); pageAiSyncCurrentSessionMessages(); pageAiPersistSessions(); renderPageAiConversation(); renderPageAiControls(); } if (eventName === 'run.completed') { try { var completed = JSON.parse(payloadText || 'null'); if (completed && completed.output) assistantText = String(completed.output || ''); if (completed && completed.usage) { var completedSession = pageAiCurrentSession(); if (completedSession) completedSession.usage = completed.usage; } var agentAudit = completed && completed.agentAudit && typeof completed.agentAudit === 'object' ? completed.agentAudit : null; var agentRunReceipt = agentAudit && agentAudit.agentRunReceipt && typeof agentAudit.agentRunReceipt === 'object' ? agentAudit.agentRunReceipt : null; var changedFiles = pageAiNormalizeArray(agentAudit && agentAudit.changedFiles).map(function(file) { if (!file || typeof file !== 'object') return file; return Object.assign({ actorId: String(agentAudit && agentAudit.actorId || ''), actorType: String(agentAudit && agentAudit.actorType || ''), agentKind: String(agentAudit && agentAudit.agentKind || ''), runId: runId }, file); }); if (changedFiles.length) { pageUiState.pageAiMessages.push({ role: 'tool', content: 'agent.changed_files', toolCallId: runId + ':agent.changed_files', toolName: 'agent.changed_files', toolKind: 'audit', status: 'completed', argsSummary: [agentAudit.rootUri, agentAudit.actorId, agentAudit.agentKind].map(function(value) { return String(value || '').trim(); }).filter(Boolean).join(' · '), resultSummary: String(agentAudit.diffSummary || changedFiles.length + ' changed file(s)'), changedFiles: changedFiles, traceId: runTraceId, auditId: String(agentAudit.eventId || '') }); } if (agentRunReceipt || changedFiles.length) { try { pageAiDispatchReceiptRefresh(agentRunReceipt, changedFiles, agentAudit, runId, runTraceId); } catch (_) {} } } catch (_) {} pageAiSetRunStatus('completed', runId); } if (eventName === 'run.failed') { try { var failed = JSON.parse(payloadText || 'null'); assistantText = String((failed && (failed.message || failed.code || failed.error)) || 'AI run failed'); } catch (_) {} pageAiSetRunStatus('failed', runId); } if (eventName === 'run.aborted' || eventName === 'run.cancelled' || eventName === 'run.canceled') { pageUiState.pageAiStoppedRunIds[runId] = true; pageAiStopStreamingAssistantMessage(runId); pageAiSetRunStatus('aborted', runId); } if (eventName === 'session.info.updated') { try { var infoPayload = JSON.parse(payloadText || 'null') || {}; var newTitle = String(infoPayload.title || '').trim(); var acpSessionId = String(infoPayload.acpSessionId || infoPayload.acp_session_id || '').trim(); var sessionForInfo = pageAiCurrentSession(); if (acpSessionId && sessionForInfo) { sessionForInfo.acpSessionId = acpSessionId; sessionForInfo.updatedAt = Date.now(); pageAiPersistSessions(); } if (newTitle) { var sessionForTitle = sessionForInfo || pageAiCurrentSession(); if (sessionForTitle) { sessionForTitle.title = newTitle; sessionForTitle.updatedAt = Date.now(); pageAiPersistSessions(); renderPageAiControls(); } } } catch (_) {} } if (eventName === 'plan.updated') { try { var planPayload = JSON.parse(payloadText || 'null') || {}; var planEntries = Array.isArray(planPayload.entries) ? planPayload.entries : []; if (planEntries.length) { var planMsgs = pageUiState.pageAiMessages; var existingPlan = planMsgs.length > 0 && planMsgs[planMsgs.length - 1].kind === 'plan' ? planMsgs[planMsgs.length - 1] : null; if (existingPlan) { existingPlan.entries = planEntries; existingPlan.updatedAt = Date.now(); } else { planMsgs.push({ role: 'system', kind: 'plan', entries: planEntries, createdAt: Date.now(), updatedAt: Date.now() }); } pageAiSyncCurrentSessionMessages(); pageAiPersistSessions(); renderPageAiConversation(); } } catch (_) {} } if (eventName === 'tool.started' || eventName === 'tool.completed' || eventName === 'tool.failed') { var toolEventPayload = null; try { toolEventPayload = JSON.parse(payloadText || 'null'); } catch (_) {} var toolItem = pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId); if (eventName === 'tool.completed') { var completedCitations = pageAiCollectKnowledgeRagCitations(toolItem, toolEventPayload); if (completedCitations.length) autoCitations = completedCitations; } pageAiSyncCurrentSessionMessages(); pageAiPersistSessions(); renderPageAiConversation(); pageAiSetRunStatus('tool_calling', runId); renderPageAiControls(); } }); if (!pageUiState.pageAiStoppedRunIds[runId]) { pageAiFinishStreamingAssistantMessage(runId, assistantText, prompt, autoCitations); } currentSession = pageAiCurrentSession(); if (currentSession) { currentSession.messages = pageUiState.pageAiMessages.slice(); currentSession.updatedAt = Date.now(); } pageAiPersistSessions(); } catch (error) { pageAiSetRunStatus('failed'); pageUiState.pageAiMessages.push({ role: 'assistant', content: 'AI 当前请求失败:' + (error instanceof Error ? error.message : String(error)) }); currentSession = pageAiCurrentSession(); if (currentSession) { currentSession.messages = pageUiState.pageAiMessages.slice(); currentSession.updatedAt = Date.now(); } 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(); } } function handlePageAiClick(event, helpers) { var closestAction = helpers && helpers.closestAction; if (typeof closestAction !== 'function') return false; var pageAiCitationLink = closestAction(event.target, 'a[data-page-ai-citation-link="true"]'); if (pageAiCitationLink && !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) { var href = pageAiCitationLink.getAttribute('href') || ''; if (pageAiOpenCitationUrl(href)) { event.preventDefault(); return true; } } var pageAiClose = closestAction(event.target, '[data-page-ai-action="close"]'); if (pageAiClose) { event.preventDefault(); closePageAiDrawer(); return true; } var pageAiOpencodeFile = closestAction(event.target, '[data-page-ai-opencode-open-file]'); if (pageAiOpencodeFile) { event.preventDefault(); pageAiOpenOpencodeChangedFile(pageAiOpencodeFile.getAttribute('data-page-ai-opencode-open-file') || ''); return true; } var pageAiSettings = closestAction(event.target, '[data-page-ai-action="open-hermes-settings"]'); if (pageAiSettings) { event.preventDefault(); pageAiOpenHermesSettings(); return true; } var pageAiStop = closestAction(event.target, '[data-page-ai-action="stop-run"]'); if (pageAiStop) { event.preventDefault(); 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(); pageUiState.pageAiSuggestionIndex += 1; renderPageAiSuggestions(); return true; } var pageAiIntent = closestAction(event.target, '[data-page-ai-intent]'); if (pageAiIntent) { event.preventDefault(); var intentName = pageAiIntent.getAttribute('data-page-ai-intent') || ''; if (intentName === 'create-summary') void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_summary,为当前页面创建或更新 AI Summary。'); if (intentName === 'create-ai-note') void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_ai_note,基于当前页面创建一篇新的 AI Note。'); return true; } var pageAiTab = closestAction(event.target, '[data-page-ai-tab]'); if (pageAiTab) { event.preventDefault(); pageUiState.pageAiPage = pageAiTab.getAttribute('data-page-ai-tab') || 'chat'; 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(); return true; } var pageAiProvider = closestAction(event.target, '[data-page-ai-provider]'); if (pageAiProvider) { event.preventDefault(); pageUiState.pageAiProvider = pageAiProvider.getAttribute('data-page-ai-provider') || 'hermes'; renderPageAiProviderButtons(); return true; } var pageAiAgent = closestAction(event.target, '[data-page-ai-agent-id]'); if (pageAiAgent) { event.preventDefault(); var nextAgentId = pageAiAgent.getAttribute('data-page-ai-agent-id') || 'reasonix'; var nextProfileId = pageAiAgent.getAttribute('data-page-ai-profile-id') || ''; pageAiSetAgentId(nextAgentId); if (nextProfileId) { pageUiState.pageAiAgentPopoverOpen = false; void pageAiSwitchProfile(nextProfileId); renderPageAiControls(); } return true; } var pageAiAgentButton = closestAction(event.target, '[data-page-ai-agent-button]'); if (pageAiAgentButton) { event.preventDefault(); pageAiSetAgentPopoverOpen(pageAiAgentButton.getAttribute('aria-expanded') !== 'true'); return true; } var pageAiContextButton = closestAction(event.target, '[data-page-ai-context-button]'); if (pageAiContextButton) { event.preventDefault(); pageAiSetContextPopoverOpen(pageAiContextButton.getAttribute('aria-expanded') !== 'true'); return true; } var pageAiTargetButton = closestAction(event.target, '[data-page-ai-target-button]'); if (pageAiTargetButton) { event.preventDefault(); pageAiSetTargetPopoverOpen(pageAiTargetButton.getAttribute('aria-expanded') !== 'true'); return true; } var pageAiAction = closestAction(event.target, '[data-page-ai-action]'); if (pageAiAction) { event.preventDefault(); var action = pageAiAction.getAttribute('data-page-ai-action') || ''; if (action === 'close-agent-popover') pageAiSetAgentPopoverOpen(false); if (action === 'close-context-popover') pageAiSetContextPopoverOpen(false); if (action === 'close-target-popover') pageAiSetTargetPopoverOpen(false); if (action === 'new-session') { pageUiState.pageAiPage = 'chat'; pageAiStartNewSession(); } if (action === 'history') { pageAiLoadSessions(); pageUiState.pageAiPage = pageUiState.pageAiPage === 'history' ? 'chat' : 'history'; renderPageAiControls(); renderPageAiConversation(); if (pageUiState.pageAiPage === 'history') { void pageAiLoadBackendSessions().catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); }); } } if (action === 'send') { var input = ensurePageAiDrawer().querySelector('[data-page-ai-input]'); if (input instanceof HTMLTextAreaElement) { var message = input.value; input.value = ''; void sendPageAiMessage(message); } } if (action === 'opencode-send') { var opencodeInput = ensurePageAiDrawer().querySelector('[data-page-ai-opencode-input]'); var opencodeText = opencodeInput instanceof HTMLTextAreaElement ? opencodeInput.value.trim() : ''; if (opencodeText) { opencodeInput.value = ''; void pageAiSendOpencodeMessage(opencodeText).catch(function(error) { pageUiState.pageAiOpencodeStatusError = error instanceof Error ? error.message : String(error); pageUiState.pageAiOpencodeSending = false; pageAiRenderOpencodeHostChrome(); }); } } if (action === 'cancel-queued-run') { void pageAiCancelQueuedRun(pageAiAction.getAttribute('data-page-ai-queue-id')); } if (action === 'opencode-refresh-status') { void pageAiLoadOpencodeStatus(); } if (action === 'opencode-new-session') { pageUiState.pageAiOpencodeSessionId = ''; pageUiState.pageAiOpencodeBindingKey = ''; pageUiState.pageAiOpencodeMessages = []; pageUiState.pageAiOpencodeLocalMessages = []; void pageAiEnsureOpencodeSessionBinding(true); } if (action === 'opencode-abort') { void pageAiAbortOpencodeRun(); } if (action === 'opencode-refresh-current-page') { pageAiRefreshOpencodeCurrentPage(); } if (action === 'opencode-insert-context') { pageAiPostOpencodeBridgeMessage('insert-text', { text: pageAiOpencodeContextTextForInsert() }); } return true; } var pageAiSessionSelect = closestAction(event.target, '[data-page-ai-opencode-session-select]'); if (pageAiSessionSelect) { event.preventDefault(); pageAiSelectOpencodeSession(pageAiSessionSelect.getAttribute('data-page-ai-opencode-session-select') || ''); return true; } var pageAiPermission = closestAction(event.target, '[data-page-ai-opencode-permission]'); if (pageAiPermission) { event.preventDefault(); void pageAiReplyOpencodePermission(pageAiPermission.getAttribute('data-page-ai-opencode-permission-id') || '', pageAiPermission.getAttribute('data-page-ai-opencode-permission') || 'once'); return true; } var pageAiTargetOption = closestAction(event.target, '[data-page-ai-target-option]'); if (pageAiTargetOption) { event.preventDefault(); pageAiSelectTarget(pageAiTargetOption.getAttribute('data-page-ai-target-option') || ''); return true; } var pageAiContextRef = closestAction(event.target, '[data-page-ai-context-ref]'); if (pageAiContextRef) { event.preventDefault(); pageAiToggleContextRef(pageAiContextRef.getAttribute('data-page-ai-context-ref') || ''); return true; } var pageAiMemorySave = closestAction(event.target, '[data-page-ai-memory-save]'); if (pageAiMemorySave) { event.preventDefault(); void pageAiSaveProfileMemory(pageAiMemorySave.getAttribute('data-page-ai-memory-save') || ''); return true; } var pageAiSkillToggle = closestAction(event.target, '[data-page-ai-skill-toggle]'); if (pageAiSkillToggle) { event.preventDefault(); void pageAiToggleSkill( pageAiSkillToggle.getAttribute('data-page-ai-skill-toggle') || '', pageAiSkillToggle.getAttribute('aria-pressed') !== 'true', pageAiSkillToggle.getAttribute('data-page-ai-skill-group') || '', pageAiSkillToggle.getAttribute('data-page-ai-skill-profile') || '' ); return true; } var pageAiSkillGroupToggle = closestAction(event.target, '[data-page-ai-skill-group-toggle]'); if (pageAiSkillGroupToggle) { event.preventDefault(); pageAiToggleSkillGroup(pageAiSkillGroupToggle.getAttribute('data-page-ai-skill-group-toggle') || ''); return true; } var pageAiToolToggle = closestAction(event.target, '[data-page-ai-tool-toggle]'); if (pageAiToolToggle) { event.preventDefault(); void pageAiToggleTool(pageAiToolToggle.getAttribute('data-page-ai-tool-toggle') || '', pageAiToolToggle.getAttribute('aria-pressed') !== 'true'); return true; } var pageAiSessionSelect = closestAction(event.target, '[data-page-ai-session-select]'); if (pageAiSessionSelect) { event.preventDefault(); pageAiToggleSessionSelection( pageAiSessionSelect.getAttribute('data-page-ai-session-select') || '', pageAiSessionSelect.getAttribute('aria-checked') !== 'true' ); return true; } var pageAiClearSelectedSessions = closestAction(event.target, '[data-page-ai-session-clear-selection]'); if (pageAiClearSelectedSessions) { event.preventDefault(); pageAiClearSessionSelection(); return true; } var pageAiDeleteSelectedSessions = closestAction(event.target, '[data-page-ai-session-delete-selected]'); if (pageAiDeleteSelectedSessions) { event.preventDefault(); void pageAiDeleteSelectedBackendSessions().catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); }); return true; } var pageAiSessionResume = closestAction(event.target, '[data-page-ai-session-resume]'); if (pageAiSessionResume) { event.preventDefault(); void pageAiResumeBackendSession(pageAiSessionResume.getAttribute('data-page-ai-session-resume') || '').catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); }); return true; } var pageAiSessionRename = closestAction(event.target, '[data-page-ai-session-rename]'); if (pageAiSessionRename) { event.preventDefault(); void pageAiRenameBackendSession(pageAiSessionRename.getAttribute('data-page-ai-session-rename') || '').catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); }); return true; } var pageAiSessionExport = closestAction(event.target, '[data-page-ai-session-export]'); if (pageAiSessionExport) { event.preventDefault(); void pageAiExportBackendSession(pageAiSessionExport.getAttribute('data-page-ai-session-export') || '').catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); }); return true; } var pageAiSessionDelete = closestAction(event.target, '[data-page-ai-session-delete]'); if (pageAiSessionDelete) { event.preventDefault(); void pageAiDeleteBackendSession(pageAiSessionDelete.getAttribute('data-page-ai-session-delete') || '').catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); }); return true; } 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', 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]'); if (pageAiOpenLocationAction) { event.preventDefault(); var loc = String(pageAiOpenLocationAction.getAttribute('data-page-ai-open-location') || '').trim(); if (loc) pageAiOpenLocation(loc); return true; } var pageAiBoardRunDetail = closestAction(event.target, '[data-page-ai-board-run-detail]'); if (pageAiBoardRunDetail) { event.preventDefault(); pageUiState.pageAiBoardActiveDetailRunId = pageAiBoardRunDetail.getAttribute('data-page-ai-board-run-detail') || ''; renderPageAiConversation(); return true; } var pageAiCopyMessage = closestAction(event.target, '[data-page-ai-copy-message]'); if (pageAiCopyMessage) { event.preventDefault(); var runId = pageAiCopyMessage.getAttribute('data-page-ai-copy-message') || ''; var message = pageAiNormalizeArray(pageUiState.pageAiMessages).find(function(item) { return String(item && item.boardRunId || '') === runId; }); if (navigator.clipboard && message) void navigator.clipboard.writeText(String(message.content || '')); return true; } var pageAiRetryMessage = closestAction(event.target, '[data-page-ai-retry-message]'); if (pageAiRetryMessage) { event.preventDefault(); var retryRunId = pageAiRetryMessage.getAttribute('data-page-ai-retry-message') || ''; var retryMessage = pageAiNormalizeArray(pageUiState.pageAiMessages).find(function(item) { return String(item && item.boardRunId || '') === retryRunId; }); var retryPrompt = String(retryMessage && retryMessage.retryPrompt || '').trim(); if (retryPrompt) void sendPageAiMessage(retryPrompt); return true; } var pageAiSession = closestAction(event.target, '[data-page-ai-session]'); if (pageAiSession) { event.preventDefault(); pageAiSetActiveSession(pageAiSession.getAttribute('data-page-ai-session') || ''); return true; } var pageAiSuggestion = closestAction(event.target, '[data-page-ai-suggestion]'); if (pageAiSuggestion) { event.preventDefault(); var text = pageAiSuggestion.getAttribute('data-page-ai-suggestion') || ''; var inputNode = ensurePageAiDrawer().querySelector('[data-page-ai-input]'); if (inputNode instanceof HTMLTextAreaElement) { inputNode.value = text; inputNode.focus(); } return true; } return false; } function handlePageAiKeyDown(event, helpers) { var closestAction = helpers && helpers.closestAction; if (typeof closestAction !== 'function') return false; if (event.key === 'Enter' && !event.shiftKey) { var aiInput = closestAction(event.target, '[data-page-ai-input]'); if (aiInput instanceof HTMLTextAreaElement) { event.preventDefault(); var text = aiInput.value; aiInput.value = ''; void sendPageAiMessage(text); return true; } } return false; } function handlePageAiInput(event, helpers) { var closestAction = helpers && helpers.closestAction; if (typeof closestAction !== 'function') return false; var skillSearch = closestAction(event.target, '[data-page-ai-skill-search]'); if (skillSearch instanceof HTMLInputElement) { pageUiState.pageAiSkillQuery = skillSearch.value; renderPageAiControls(); return true; } var sessionSearch = closestAction(event.target, '[data-page-ai-session-search]'); if (sessionSearch instanceof HTMLInputElement) { var sessionQuery = sessionSearch.value; window.clearTimeout(pageUiState.pageAiSessionSearchTimer || 0); pageUiState.pageAiSessionSearchTimer = window.setTimeout(function() { void pageAiSearchBackendSessions(sessionQuery).catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); }); }, 200); return true; } var memoryEditor = closestAction(event.target, '[data-page-ai-memory-editor]'); if (memoryEditor instanceof HTMLTextAreaElement) { var section = memoryEditor.getAttribute('data-page-ai-memory-editor') || ''; if (['memory', 'user', 'soul'].indexOf(section) >= 0) pageUiState.pageAiProfileMemoryDrafts[section] = memoryEditor.value; return true; } return false; } function handlePageAiChange(event, helpers) { var closestAction = helpers && helpers.closestAction; if (typeof closestAction !== 'function') return false; var pageAiDescriptorField = closestAction(event.target, '[data-page-ai-descriptor-field]'); if (pageAiDescriptorField instanceof HTMLInputElement || pageAiDescriptorField instanceof HTMLSelectElement) { var key = pageAiDescriptorField.getAttribute('data-page-ai-descriptor-field') || ''; var kind = pageAiDescriptorField.getAttribute('data-page-ai-descriptor-kind') || ''; var value = kind === 'boolean' && pageAiDescriptorField instanceof HTMLInputElement ? pageAiDescriptorField.checked : pageAiDescriptorField.value; pageAiSetDescriptorFieldValue(key, value); return true; } var pageAiAcpRuntimeSelect = closestAction(event.target, '[data-page-ai-acp-runtime]'); if (pageAiAcpRuntimeSelect instanceof HTMLSelectElement) { var next = String(pageAiAcpRuntimeSelect.value || 'reasonix').trim() || 'reasonix'; pageUiState.pageAiAcpRuntime = next; pageUiState.pageAiSkills = { categories: [], archived: [] }; pageUiState.pageAiSkillError = ''; pageAiPersistSessions(); void pageAiLoadProfiles(); if (next !== 'reasonix') void pageAiLoadProfileMemory(); void pageAiLoadSkills(); void pageAiLoadBackendSessions().catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); }); renderPageAiControls(); renderPageAiProviderButtons(); return true; } var pageAiProfileSelect = closestAction(event.target, '[data-page-ai-profile-select]'); if (pageAiProfileSelect instanceof HTMLSelectElement) { void pageAiSwitchProfile(pageAiProfileSelect.value); return true; } var pageAiBoardWorker = closestAction(event.target, '[data-page-ai-board-worker]'); if (pageAiBoardWorker instanceof HTMLSelectElement) { pageUiState.pageAiBoardWorkerId = String(pageAiBoardWorker.value || pageAiBoardDefaultWorkerId()).trim() || pageAiBoardDefaultWorkerId(); pageUiState.pageAiBoardModelOverride = pageAiBoardSelectedModelOverride(); pageAiPersistAiPreference('board.worker_preset_id', pageUiState.pageAiBoardWorkerId); pageAiPersistAiPreference('board.model_override', pageUiState.pageAiBoardModelOverride); renderPageAiControls(); pageAiApplyBoardFirstShell(); return true; } var pageAiBoardModel = closestAction(event.target, '[data-page-ai-board-model]'); if (pageAiBoardModel instanceof HTMLSelectElement) { pageUiState.pageAiBoardModelOverride = String(pageAiBoardModel.value || pageAiBoardSelectedModelOverride()).trim() || pageAiBoardSelectedModelOverride(); pageAiPersistAiPreference('board.model_override', pageUiState.pageAiBoardModelOverride); var current = pageAiCurrentSession(); if (current) { current.modelOverride = pageUiState.pageAiBoardModelOverride; current.updatedAt = Date.now(); } pageAiPersistSessions(); renderPageAiControls(); pageAiApplyBoardFirstShell(); return true; } var pageAiBoardWorkflow = closestAction(event.target, '[data-page-ai-board-workflow]'); if (pageAiBoardWorkflow instanceof HTMLSelectElement) { pageUiState.pageAiBoardWorkflowId = String(pageAiBoardWorkflow.value || pageAiBoardDefaultWorkflowId()).trim() || pageAiBoardDefaultWorkflowId(); pageAiPersistAiPreference('board.workflow_id', pageUiState.pageAiBoardWorkflowId); renderPageAiControls(); pageAiApplyBoardFirstShell(); return true; } var pageAiSkillSourceSelect = closestAction(event.target, '[data-page-ai-skill-source-select]'); if (pageAiSkillSourceSelect instanceof HTMLSelectElement) { pageAiSetSkillSource(pageAiSkillSourceSelect.value); return true; } var pageAiSessionAgentFilter = closestAction(event.target, '[data-page-ai-session-agent-filter]'); if (pageAiSessionAgentFilter instanceof HTMLSelectElement) { pageUiState.pageAiSessionAgentFilter = String(pageAiSessionAgentFilter.value || 'all').trim() || 'all'; renderPageAiControls(); 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); return true; } var pageAiReasonixMemory = closestAction(event.target, '[data-page-ai-reasonix-memory]'); if (pageAiReasonixMemory instanceof HTMLInputElement) { pageAiSetReasonixMemoryEnabled(pageAiReasonixMemory.checked); return true; } var pageAiContextSelect = closestAction(event.target, '[data-page-ai-context-scope]'); if (pageAiContextSelect instanceof HTMLSelectElement) { pageAiSetContextScope(pageAiContextSelect.value); renderPageAiControls(); return true; } return false; } function installPageAiDelegates() { if (pageAiDelegatesInstalled) return; pageAiDelegatesInstalled = true; var helpers = { closestAction: closestAction }; document.addEventListener('click', function(event) { handlePageAiClick(event, helpers); }); document.addEventListener('submit', function(event) { var form = event.target && event.target.closest ? event.target.closest('[data-page-ai-opencode-composer]') : null; if (!form) return; event.preventDefault(); var input = form.querySelector('[data-page-ai-opencode-input]'); var text = input instanceof HTMLTextAreaElement ? input.value.trim() : ''; if (!text) return; input.value = ''; void pageAiSendOpencodeMessage(text).catch(function(error) { pageUiState.pageAiOpencodeStatusError = error instanceof Error ? error.message : String(error); pageAiRenderOpencodeHostChrome(); }); }); document.addEventListener('pointerdown', function(event) { handlePageAiPointerDown(event, helpers); }); document.addEventListener('keydown', function(event) { handlePageAiKeyDown(event, helpers); }); document.addEventListener('input', function(event) { handlePageAiInput(event, helpers); }); document.addEventListener('change', function(event) { handlePageAiChange(event, helpers); }); document.addEventListener('visibilitychange', function() { if (document.visibilityState !== 'visible' || !pageUiState.pageAiOpen) return; void pageAiCheckAndResumeActiveRun().catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); }); }); } var api = { ensurePageAiStateFacade: (...args) => ensurePageAiStateFacade(...args), installPageAiDelegates: (...args) => installPageAiDelegates(...args), handlePageAiClick: (...args) => handlePageAiClick(...args), handlePageAiKeyDown: (...args) => handlePageAiKeyDown(...args), handlePageAiInput: (...args) => handlePageAiInput(...args), handlePageAiChange: (...args) => handlePageAiChange(...args), openPageAiDrawer: (...args) => openPageAiDrawer(...args), closePageAiDrawer: (...args) => closePageAiDrawer(...args), isPageAiDrawerOpen: (...args) => isPageAiDrawerOpen(...args), ensurePageAiDrawer: (...args) => ensurePageAiDrawer(...args), sendPageAiMessage: (...args) => sendPageAiMessage(...args), pageAiOpenHermesSettings: (...args) => pageAiOpenHermesSettings(...args), pageAiStopRun: (...args) => pageAiStopRun(...args), pageAiLoadGatewayHealth: (...args) => pageAiLoadGatewayHealth(...args), renderPageAiControls: (...args) => renderPageAiControls(...args), renderPageAiConversation: (...args) => renderPageAiConversation(...args), renderPageAiProviderButtons: (...args) => renderPageAiProviderButtons(...args), renderPageAiSuggestions: (...args) => renderPageAiSuggestions(...args), pageAiSaveProfileMemory: (...args) => pageAiSaveProfileMemory(...args), pageAiToggleSkill: (...args) => pageAiToggleSkill(...args), pageAiToggleTool: (...args) => pageAiToggleTool(...args), pageAiResumeBackendSession: (...args) => pageAiResumeBackendSession(...args), pageAiRenameBackendSession: (...args) => pageAiRenameBackendSession(...args), pageAiExportBackendSession: (...args) => pageAiExportBackendSession(...args), pageAiDeleteBackendSession: (...args) => pageAiDeleteBackendSession(...args), pageAiResolvePermission: (...args) => pageAiResolvePermission(...args), pageAiOpenLocation: (...args) => pageAiOpenLocation(...args), pageAiSetActiveSession: (...args) => pageAiSetActiveSession(...args), pageAiStartNewSession: (...args) => pageAiStartNewSession(...args), pageAiLoadSessions: (...args) => pageAiLoadSessions(...args), pageAiLoadBackendSessions: (...args) => pageAiLoadBackendSessions(...args), pageAiCancelQueuedRun: (...args) => pageAiCancelQueuedRun(...args), pageAiSearchBackendSessions: (...args) => pageAiSearchBackendSessions(...args), pageAiSetTargetPopoverOpen: (...args) => pageAiSetTargetPopoverOpen(...args), pageAiSelectTarget: (...args) => pageAiSelectTarget(...args), pageAiPersistSessions: (...args) => pageAiPersistSessions(...args), pageAiLoadProfiles: (...args) => pageAiLoadProfiles(...args), pageAiLoadProfileMemory: (...args) => pageAiLoadProfileMemory(...args), pageAiLoadSkills: (...args) => pageAiLoadSkills(...args), pageAiSwitchProfile: (...args) => pageAiSwitchProfile(...args), pageAiToggleSkillGroup: (...args) => pageAiToggleSkillGroup(...args), pageAiSetReasonixMemoryEnabled: (...args) => pageAiSetReasonixMemoryEnabled(...args), pageAiSetHideHermesBuiltinSkills: (...args) => pageAiSetHideHermesBuiltinSkills(...args), pageAiSetSkillSource: (...args) => pageAiSetSkillSource(...args), pageAiSetContextScope: (...args) => pageAiSetContextScope(...args), pageAiSetAgentId: (...args) => pageAiSetAgentId(...args), pageAiToggleContextRef: (...args) => pageAiToggleContextRef(...args), pageAiSetContextPopoverOpen: (...args) => pageAiSetContextPopoverOpen(...args), pageAiSetAgentPopoverOpen: (...args) => pageAiSetAgentPopoverOpen(...args), pageAiLoadAllowedRoots: (...args) => pageAiLoadAllowedRoots(...args), pageAiEnsureOpencodeHostDrawer: (...args) => pageAiEnsureOpencodeHostDrawer(...args), pageAiLoadOpencodeStatus: (...args) => pageAiLoadOpencodeStatus(...args), pageAiLoadOpencodeDiff: (...args) => pageAiLoadOpencodeDiff(...args), pageAiOpenOpencodeChangedFile: (...args) => pageAiOpenOpencodeChangedFile(...args), pageAiRefreshOpencodeCurrentPage: (...args) => pageAiRefreshOpencodeCurrentPage(...args), updatePageAiTriggerState: (...args) => updatePageAiTriggerState(...args) }; window.__mnoteSidebarPageAiRuntime = api; return api; }