export function createSidebarPageAiSessionRuntime(context) { const { currentDocumentId, currentRootUri, currentSourceKind, documentRef, pageAiApplyRuntimeState, pageAiCurrentAgentId, pageAiCurrentProfile, pageAiErrorMessage, pageAiNormalizeAgentId, pageAiNormalizeArray, pageAiNormalizeChatOnlyProfileId, pageAiPermissionMessage, pageAiPreviewValue, pageAiRunProfile, pageAiSetActiveProfile, pageAiTimestamp, pageUiState, renderPageAiControls, renderPageAiConversation, resolveWorkspaceId, sessionStorageVersion, windowRef, } = context; const doc = documentRef || document; const win = windowRef || window; function pageAiStorageKey() { return 'hermes_page_ai_session:' + currentDocumentId(); } function pageAiBackendSessionQuery(extra) { var params = new URLSearchParams(); params.set('source', 'acp'); params.set('workspaceId', resolveWorkspaceId(doc.body)); params.set('documentId', currentDocumentId()); params.set('profile', pageAiRunProfile()); params.set('sourceKind', currentSourceKind()); if (currentRootUri()) params.set('rootUri', currentRootUri()); Object.keys(extra || {}).forEach(function(key) { var value = extra[key]; if (value !== undefined && value !== null && String(value).trim() !== '') { params.set(key, String(value)); } }); return params.toString(); } function pageAiNewSession(title) { var now = Date.now(); var agentId = pageAiCurrentAgentId(); var profile = agentId === 'chat_only' ? pageAiNormalizeChatOnlyProfileId(pageAiCurrentProfile()) : pageAiCurrentProfile(); return { id: 'sess_' + now + '_' + Math.random().toString(16).slice(2, 8), title: title || '新会话', agentId: agentId, profileId: pageUiState.pageAiAcpRuntime === 'hermes' ? profile : '', profile: profile, acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', createdAt: now, updatedAt: now, source: 'local', usage: null, status: 'idle', messages: [] }; } function pageAiNormalizeSessions(sessions) { return (Array.isArray(sessions) ? sessions : []) .slice(0, 20) .map(function(session) { var sessionStorage = String(session && (session.sessionStorage || session.session_storage) || '').trim(); var persistence = String(session && session.persistence || '').trim(); var agentId = pageAiNormalizeAgentId(session && (session.agentId || session.agent_id)); var profileId = String(session && (session.profileId || session.profile_id || '') || '').trim(); var profile = String(session && session.profile || pageAiCurrentProfile()).trim() || 'default'; if (!profileId && agentId !== 'reasonix') profileId = profile; if (agentId === 'chat_only') { profileId = pageAiNormalizeChatOnlyProfileId(profileId || profile); profile = profileId; } return { id: String(session && session.id || '').trim() || pageAiNewSession().id, title: String(session && session.title || '').trim() || '新会话', agentId: agentId, profileId: profileId, profile: profile, acpRuntime: String(session && session.acpRuntime || 'reasonix').trim(), createdAt: pageAiTimestamp(session && session.createdAt), updatedAt: pageAiTimestamp(session && session.updatedAt), source: String(session && session.source || 'local').trim() || 'local', persistence: persistence, sessionStorage: sessionStorage, permissionLevel: String(session && (session.permissionLevel || session.permission_level) || '').trim(), shareId: String(session && (session.shareId || session.share_id) || '').trim(), acpSessionId: String(session && (session.acpSessionId || session.acp_session_id) || '').trim(), runId: String(session && (session.runId || session.run_id) || '').trim(), status: String(session && session.status || '').trim(), usage: session && session.usage && typeof session.usage === 'object' ? session.usage : null, preview: String(session && session.preview || '').trim(), messages: Array.isArray(session && session.messages) ? session.messages.slice(-300) : [] }; }) .sort(function(a, b) { return Number(b.updatedAt || 0) - Number(a.updatedAt || 0); }); } function pageAiNormalizeBackendSessionRow(row) { if (!row || typeof row !== 'object') return null; var payload = row.payload && typeof row.payload === 'object' ? row.payload : {}; var sessionId = String(row.sessionId || row.session_id || '').trim(); if (!sessionId) return null; var title = String(row.title || payload.title || payload.message || '').trim(); if (title.length > 28) title = title.slice(0, 28) + '…'; var persistence = String(row.persistence || payload.persistence || '').trim(); var sessionStorage = String(row.sessionStorage || row.session_storage || payload.sessionStorage || '').trim(); var agentId = pageAiNormalizeAgentId(row.agentId || row.agent_id || payload.agentId || payload.agent_id); var profileId = String(row.profileId || row.profile_id || payload.profileId || payload.profile_id || '').trim(); var profile = String(row.profile || payload.profile || pageAiRunProfile()).trim() || 'default'; if (!profileId && agentId !== 'reasonix') profileId = profile; if (agentId === 'chat_only') { profileId = pageAiNormalizeChatOnlyProfileId(profileId || profile); profile = profileId; } if (!sessionStorage && persistence === 'convex_acp_runtime_store') sessionStorage = 'cloud'; if (!sessionStorage && persistence === 'local_ai_session_jsonl') sessionStorage = String(row.shareId || payload.shareId || '').trim() ? 'local_shared' : 'local_private'; return { id: sessionId, title: title || '当前页问答', agentId: agentId, profileId: profileId, profile: profile, acpRuntime: String(row.acpRuntime || row.acp_runtime || payload.acpRuntime || 'reasonix').trim(), createdAt: pageAiTimestamp(row.createdAt || row.created_at), updatedAt: pageAiTimestamp(row.updatedAt || row.updated_at), source: sessionStorage === 'local_private' || sessionStorage === 'local_shared' ? 'local' : 'acp', persistence: persistence, sessionStorage: sessionStorage, permissionLevel: String(row.permissionLevel || row.permission_level || payload.permissionLevel || '').trim(), shareId: String(row.shareId || row.share_id || payload.shareId || '').trim(), acpSessionId: String(row.acpSessionId || row.acp_session_id || payload.acpSessionId || payload.acp_session_id || '').trim(), runId: String(row.runId || row.run_id || '').trim(), status: String(row.status || (row.runtime && row.runtime.status) || '').trim(), usage: row.usage && typeof row.usage === 'object' ? row.usage : null, preview: String(payload.message || row.snippet || '').trim(), messages: [] }; } function pageAiMergeSessions(localSessions, backendSessions) { var byId = {}; pageAiNormalizeSessions(localSessions).forEach(function(session) { byId[session.id] = session; }); pageAiNormalizeSessions(backendSessions).forEach(function(session) { var existing = byId[session.id]; byId[session.id] = Object.assign({}, existing || {}, session, { messages: session.messages.length ? session.messages : (existing && existing.messages || []) }); }); return pageAiNormalizeSessions(Object.keys(byId).map(function(id) { return byId[id]; })); } function pageAiSessionStorageLabel(session) { var storage = String(session && (session.sessionStorage || session.session_storage) || '').trim(); var persistence = String(session && session.persistence || '').trim(); if (storage === 'local_shared') return '共享会话'; if (storage === 'local_private') return '本地私有'; if (storage === 'cloud' || persistence === 'convex_acp_runtime_store') return '云端会话'; if (persistence === 'local_ai_session_jsonl') return '本地私有'; return String(session && session.source || '').trim() === 'acp' ? '云端会话' : '本地私有'; } function pageAiLoadSessions() { if (pageUiState.pageAiSessions.length && pageUiState.pageAiActiveSessionId) return; try { var raw = win.localStorage.getItem(pageAiStorageKey()); var parsed = raw ? JSON.parse(raw) : null; var activeId = String(parsed && parsed.activeSessionId || '').trim(); var activeProfile = String(parsed && parsed.activeProfileName || '').trim(); var activeAcpRuntime = String(parsed && parsed.activeAcpRuntime || '').trim(); var sessions = pageAiNormalizeSessions(parsed && parsed.sessions); var storageVersion = Number(parsed && parsed.version || 0); if (storageVersion >= sessionStorageVersion && activeAcpRuntime) pageUiState.pageAiAcpRuntime = activeAcpRuntime; if (activeProfile) pageAiSetActiveProfile(activeProfile); if (sessions.length) { pageUiState.pageAiSessions = sessions; var activeSession = sessions.find(function(session) { return session.id === activeId; }) || sessions[0]; pageUiState.pageAiActiveSessionId = activeSession.id; if (activeSession.profile) pageAiSetActiveProfile(activeSession.profile); if (activeSession.agentId) pageUiState.pageAiAgentId = pageAiNormalizeAgentId(activeSession.agentId); if (!pageUiState.pageAiAcpRuntime && activeSession.acpRuntime) pageUiState.pageAiAcpRuntime = activeSession.acpRuntime; pageUiState.pageAiMessages = Array.isArray(activeSession.messages) ? activeSession.messages.slice() : []; return; } if (activeId) { pageUiState.pageAiSessions = [pageAiNewSession('当前页问答')]; pageUiState.pageAiSessions[0].id = activeId; pageUiState.pageAiActiveSessionId = activeId; pageUiState.pageAiMessages = []; return; } } catch (_) {} var fresh = pageAiNewSession(); pageUiState.pageAiSessions = [fresh]; pageUiState.pageAiActiveSessionId = fresh.id; pageUiState.pageAiMessages = []; } async function pageAiLoadBackendSessions() { var response = await fetch('/api/hermes/client/sessions?' + pageAiBackendSessionQuery({ limit: 50 }), { 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, 'session_list_failed_' + response.status)); } var backendSessions = pageAiNormalizeArray(payload.sessions).map(pageAiNormalizeBackendSessionRow).filter(Boolean); if (!backendSessions.length) return []; pageUiState.pageAiSessions = pageAiMergeSessions(pageUiState.pageAiSessions, backendSessions); if (!pageUiState.pageAiActiveSessionId || !pageUiState.pageAiSessions.find(function(session) { return session.id === pageUiState.pageAiActiveSessionId; })) { pageUiState.pageAiActiveSessionId = pageUiState.pageAiSessions[0].id; } var active = pageAiCurrentSession(); if (active) { if (active.profile) pageAiSetActiveProfile(active.profile); if (!pageUiState.pageAiAcpRuntime && active.acpRuntime) pageUiState.pageAiAcpRuntime = active.acpRuntime; pageUiState.pageAiMessages = Array.isArray(active.messages) ? active.messages.slice() : pageUiState.pageAiMessages; } pageUiState.pageAiSessionError = ''; pageAiPersistSessions(); renderPageAiConversation(); renderPageAiControls(); return backendSessions; } function pageAiMessageFromRuntimeEvent(event) { if (!event || typeof event !== 'object') return null; var eventType = String(event.eventType || event.event_type || event.event || '').trim(); var payload = event.payload && typeof event.payload === 'object' ? event.payload : event; if (eventType === 'message.delta') { var delta = String(payload.delta || payload.text || payload.output_text || '').trim(); return delta ? { role: 'assistant', content: delta } : null; } if (eventType === 'thought.delta') { var thought = String(payload.delta || payload.text || '').trim(); return thought ? { role: 'assistant', kind: 'thought', content: thought } : null; } if (eventType === 'tool.started' || eventType === 'tool.completed' || eventType === 'tool.failed') { var toolName = String(payload.toolName || payload.tool || payload.name || eventType).trim(); var rawLocations = payload.locations; return { role: 'tool', content: toolName, toolName: toolName, toolCallId: String(payload.toolCallId || payload.tool_call_id || payload.id || ''), toolKind: String(payload.kind || ''), status: eventType === 'tool.completed' ? 'completed' : (eventType === 'tool.failed' ? 'failed' : 'running'), argsSummary: pageAiPreviewValue(payload.args || payload.arguments || payload.input), resultSummary: pageAiPreviewValue(payload.summary || payload.result || payload.output || payload.error), 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; }) : [], traceId: String(payload.traceId || payload.trace_id || ''), auditId: String(payload.auditId || payload.audit_id || '') }; } if (eventType === 'permission.requested' || eventType === 'permission.denied' || eventType === 'permission.allowed') { return pageAiPermissionMessage(payload, eventType); } if (eventType === 'run.completed') { var output = String(payload.output || payload.text || '').trim(); return output ? { role: 'assistant', content: output } : null; } return null; } function pageAiApplyBackendSessionDetail(payload) { var sessionPayload = payload && payload.session ? payload.session : {}; var runs = pageAiNormalizeArray(sessionPayload.runs); var latest = runs.length ? pageAiNormalizeBackendSessionRow(runs[0]) : null; var events = pageAiNormalizeArray(payload && payload.events); var storedMessages = pageAiNormalizeArray(sessionPayload.messages).map(function(message) { return { role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'), content: String(message.content || '') }; }).filter(function(message) { return message.content; }); var eventsByRunId = {}; events.forEach(function(event) { var runId = String(event && (event.runId || event.run_id) || '').trim(); if (!runId) return; if (!eventsByRunId[runId]) eventsByRunId[runId] = []; eventsByRunId[runId].push(event); }); var messages = []; if (runs.length) { runs.slice().reverse().forEach(function(run) { var runPayload = run && run.payload && typeof run.payload === 'object' ? run.payload : {}; var userMessage = String(runPayload.message || runPayload.input || '').trim(); if (userMessage) messages.push({ role: 'user', content: userMessage }); var runId = String(run && (run.runId || run.run_id) || '').trim(); pageAiNormalizeArray(eventsByRunId[runId]).forEach(function(event) { var message = pageAiMessageFromRuntimeEvent(event); if (message) messages.push(message); }); }); } if (!messages.length) messages = storedMessages; var acpSessionId = ''; events.forEach(function(event) { var eventType = String(event && (event.eventType || event.event_type || event.event) || '').trim(); var payload = event && event.payload && typeof event.payload === 'object' ? event.payload : event; if (eventType === 'session.info.updated') { var nextAcpSessionId = String(payload && (payload.acpSessionId || payload.acp_session_id) || '').trim(); if (nextAcpSessionId) acpSessionId = nextAcpSessionId; } }); var current = pageAiCurrentSession(); if (latest && current) { Object.assign(current, latest); } if (current) { current.messages = messages.slice(-300); current.updatedAt = Math.max(Number(current.updatedAt || 0), Date.now()); if (acpSessionId) current.acpSessionId = acpSessionId; if (latest && latest.usage) current.usage = latest.usage; } pageAiApplyRuntimeState(payload && payload.runtime); pageUiState.pageAiMessages = messages.slice(-300); pageAiPersistSessions(); } async function pageAiLoadBackendSessionDetail(sessionId) { sessionId = String(sessionId || pageUiState.pageAiActiveSessionId || '').trim(); if (!sessionId) return null; var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '?' + pageAiBackendSessionQuery({ limit: 200 }), { 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, 'session_detail_failed_' + response.status)); } pageAiApplyBackendSessionDetail(payload); renderPageAiConversation(); renderPageAiControls(); return payload; } async function pageAiSearchBackendSessions(query) { var q = String(query || '').trim(); pageUiState.pageAiSessionSearchQuery = q; if (!q) { pageUiState.pageAiSessionSearchResults = []; renderPageAiConversation(); return []; } var response = await fetch('/api/hermes/client/sessions/search?' + pageAiBackendSessionQuery({ q: q, limit: 20 }), { 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, 'session_search_failed_' + response.status)); } pageUiState.pageAiSessionSearchResults = pageAiNormalizeArray(payload.results).map(function(row) { var normalized = pageAiNormalizeBackendSessionRow(row) || {}; normalized.snippet = String(row && row.snippet || normalized.preview || '').trim(); return normalized; }).filter(function(row) { return row.id; }); renderPageAiConversation(); return pageUiState.pageAiSessionSearchResults; } function pageAiPersistSessions() { doc.documentElement.setAttribute('data-mnote-page-ai-session-owner', 'hermes'); doc.documentElement.setAttribute('data-mnote-page-ai-session-key', pageAiStorageKey()); try { pageAiSyncCurrentSessionMessages(); win.localStorage.setItem(pageAiStorageKey(), JSON.stringify({ version: sessionStorageVersion, activeSessionId: pageUiState.pageAiActiveSessionId, activeProfileName: pageAiCurrentProfile(), activeAcpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', sessions: pageAiNormalizeSessions(pageUiState.pageAiSessions) })); } catch (_) {} } async function pageAiEnsureHermesSession(forceCreate) { pageAiLoadSessions(); var current = pageAiCurrentSession(); var runProfile = pageAiRunProfile(); if (!forceCreate && current && String(current.id || '').startsWith('mnote_') && current.profile === runProfile) return current; var response = await fetch('/api/hermes/client/sessions', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ workspaceId: resolveWorkspaceId(doc.body), documentId: currentDocumentId(), sourceKind: currentSourceKind(), rootUri: currentRootUri(), traceId: 'page-ai-' + Date.now().toString(36), profile: runProfile, agentId: pageAiCurrentAgentId(), profileId: pageUiState.pageAiAcpRuntime === 'hermes' ? runProfile : '', title: current && current.title ? current.title : '当前页问答' }) }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok || !payload || payload.ok !== true) { throw new Error(pageAiErrorMessage(payload, 'hermes_session_failed_' + response.status)); } var session = { id: String(payload.sessionId || '').trim(), title: String(payload.title || '当前页问答'), agentId: pageAiCurrentAgentId(), profileId: pageUiState.pageAiAcpRuntime === 'hermes' ? runProfile : '', profile: String(payload.profile || runProfile).trim() || 'default', acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix', persistence: String(payload.persistence || '').trim(), sessionStorage: String(payload.sessionStorage || '').trim(), permissionLevel: String(payload.permissionLevel || '').trim(), shareId: String(payload.shareId || '').trim(), acpSessionId: String(payload.acpSessionId || payload.acp_session_id || '').trim(), createdAt: Date.now(), updatedAt: Date.now(), messages: pageUiState.pageAiMessages.slice() }; pageUiState.pageAiSessions = pageAiNormalizeSessions([session]); pageUiState.pageAiActiveSessionId = session.id; pageAiPersistSessions(); renderPageAiConversation(); return session; } async function pageAiRestoreHermesSession() { var current = pageAiCurrentSession(); if (!current || !String(current.id || '').startsWith('mnote_')) return; var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(current.id) + '/resume?' + pageAiBackendSessionQuery({}), { method: 'POST', headers: { 'accept': 'application/json' } }); if (!response.ok) return; var payload = await response.json().catch(function(){ return null; }); if (payload && payload.persistence === 'convex_acp_runtime_store') { pageAiApplyBackendSessionDetail(payload); renderPageAiConversation(); renderPageAiControls(); return; } var session = payload && (payload.session || (payload.upstream && payload.upstream.session) || payload); var messages = session && Array.isArray(session.messages) ? session.messages : []; pageAiApplyRuntimeState(payload && payload.runtime); if (session && (session.profile || session.profileName)) { current.profile = String(session.profile || session.profileName || current.profile || pageAiCurrentProfile()); } if (!messages.length) { renderPageAiControls(); return; } pageUiState.pageAiMessages = messages.slice(-300).map(function(message) { return { role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'), content: String(message.content || '') }; }); current.messages = pageUiState.pageAiMessages.slice(); current.updatedAt = Date.now(); renderPageAiConversation(); renderPageAiControls(); } function pageAiCurrentSession() { return pageUiState.pageAiSessions.find(function(session) { return session.id === pageUiState.pageAiActiveSessionId; }) || null; } function pageAiSyncCurrentSessionMessages() { var session = pageAiCurrentSession(); if (!session) return; var runProfile = pageAiRunProfile(); session.messages = Array.isArray(pageUiState.pageAiMessages) ? pageUiState.pageAiMessages.slice(-300) : []; session.agentId = pageAiCurrentAgentId(); session.profileId = pageUiState.pageAiAcpRuntime === 'hermes' ? runProfile : ''; session.profile = runProfile; session.acpRuntime = pageUiState.pageAiAcpRuntime || 'reasonix'; session.updatedAt = Date.now(); } function pageAiSetActiveSession(sessionId) { var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; }); if (!session) return; pageUiState.pageAiActiveSessionId = session.id; if (session.agentId) pageUiState.pageAiAgentId = pageAiNormalizeAgentId(session.agentId); if (session.acpRuntime) pageUiState.pageAiAcpRuntime = session.acpRuntime; if (session.profileId || session.profile) pageAiSetActiveProfile(session.profileId || session.profile); pageUiState.pageAiMessages = Array.isArray(session.messages) ? session.messages.slice() : []; pageUiState.pageAiPage = 'chat'; pageAiPersistSessions(); renderPageAiControls(); renderPageAiConversation(); if (session.source === 'acp' || String(session.id || '').startsWith('mnote_')) { void pageAiLoadBackendSessionDetail(session.id).catch(function(error) { pageUiState.pageAiSessionError = error instanceof Error ? error.message : String(error); renderPageAiControls(); }); } } function pageAiStartNewSession() { var session = pageAiNewSession(); pageUiState.pageAiSessions = pageAiNormalizeSessions([session].concat(pageUiState.pageAiSessions)); pageUiState.pageAiActiveSessionId = session.id; pageUiState.pageAiMessages = []; pageUiState.pageAiPage = 'chat'; pageAiPersistSessions(); renderPageAiControls(); renderPageAiConversation(); } async function pageAiRenameBackendSession(sessionId) { var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; }); if (!session) return; var title = win.prompt('重命名 AI 会话', session.title || '当前页问答'); if (title === null) return; title = String(title || '').trim(); if (!title) return; var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/rename?' + pageAiBackendSessionQuery({}), { method: 'POST', headers: { 'content-type': 'application/json', 'accept': 'application/json' }, body: JSON.stringify({ title: title }) }); var payload = await response.json().catch(function(){ return null; }); if (!response.ok || !payload || payload.ok !== true) { throw new Error(pageAiErrorMessage(payload, 'session_rename_failed_' + response.status)); } session.title = String((payload.result && payload.result.title) || title); session.updatedAt = Date.now(); pageAiPersistSessions(); renderPageAiConversation(); renderPageAiControls(); } async function pageAiDeleteBackendSession(sessionId) { var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; }); if (!session) return; if (!win.confirm('确定删除 AI 会话“' + (session.title || sessionId) + '”吗?')) return; var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '?' + pageAiBackendSessionQuery({}), { method: 'DELETE', 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, 'session_delete_failed_' + response.status)); } pageUiState.pageAiSessions = pageUiState.pageAiSessions.filter(function(item) { return item.id !== sessionId; }); if (pageUiState.pageAiActiveSessionId === sessionId) { var next = pageUiState.pageAiSessions[0] || pageAiNewSession(); if (!pageUiState.pageAiSessions.length) pageUiState.pageAiSessions = [next]; pageUiState.pageAiActiveSessionId = next.id; pageUiState.pageAiMessages = Array.isArray(next.messages) ? next.messages.slice() : []; } pageAiPersistSessions(); renderPageAiConversation(); renderPageAiControls(); } async function pageAiResumeBackendSession(sessionId) { sessionId = String(sessionId || pageUiState.pageAiActiveSessionId || '').trim(); if (!sessionId) return; pageAiSetActiveSession(sessionId); var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(sessionId) + '/resume?' + pageAiBackendSessionQuery({}), { method: 'POST', 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, 'session_resume_failed_' + response.status)); } pageAiApplyBackendSessionDetail(payload); pageUiState.pageAiPage = 'chat'; renderPageAiConversation(); renderPageAiControls(); } return { pageAiStorageKey, pageAiBackendSessionQuery, pageAiNewSession, pageAiNormalizeSessions, pageAiNormalizeBackendSessionRow, pageAiMergeSessions, pageAiSessionStorageLabel, pageAiLoadSessions, pageAiLoadBackendSessions, pageAiMessageFromRuntimeEvent, pageAiApplyBackendSessionDetail, pageAiLoadBackendSessionDetail, pageAiSearchBackendSessions, pageAiPersistSessions, pageAiEnsureHermesSession, pageAiRestoreHermesSession, pageAiCurrentSession, pageAiSyncCurrentSessionMessages, pageAiSetActiveSession, pageAiStartNewSession, pageAiRenameBackendSession, pageAiDeleteBackendSession, pageAiResumeBackendSession }; }