/* node:coverage disable */ (function (root, factory) { 'use strict'; const common = typeof module === 'object' && module.exports; const foundation = common ? require('../foundation.js') : root.HermesHuxFoundation; const shellApi = common ? require('../shell.js') : root.HermesHuxShell; const contract = common ? require('./wave_a_contract.js') : root.HermesHuxWaveAContract; const api = factory(foundation, shellApi, contract); if (common) module.exports = api; else root.HermesHuxWaveA = api; }(typeof globalThis === 'object' ? globalThis : this, function (foundation, shellApi, contract) { /* node:coverage enable */ 'use strict'; const KIND_LABELS = Object.freeze({ 'message.user': 'You replied', 'message.assistant': 'Hermes replied', 'decision.route': 'Route selected', 'decision.plan': 'Plan updated', 'tool.call': 'Tool started', 'tool.result': 'Tool finished', 'approval.requested': 'Approval requested', 'approval.resolved': 'Approval resolved', 'memory.proposed': 'Memory suggested', 'memory.committed': 'Memory saved', 'memory.forgotten': 'Memory forgotten', 'memory.suppressed': 'Memory not saved', 'memory.retrieval_removed': 'Memory retrieval changed', 'artifact.created': 'Artifact created', 'artifact.version': 'Artifact updated', 'artifact.promoted': 'Artifact promoted', 'citation.attached': 'Citation attached', 'mode.changed': 'Mode changed', 'run.started': 'Run started', 'run.cancelled': 'Run cancelled', 'run.completed': 'Run completed', 'run.failed': 'Run failed', 'privacy.notice': 'Privacy notice', 'suggestion.shown': 'Suggestion shown', 'suggestion.dismissed': 'Suggestion dismissed', 'release.transition': 'Release advanced', 'delegation.started': 'Delegation started', 'delegation.completed': 'Delegation completed', 'delegation.failed': 'Delegation failed', 'budget.exhausted': 'Budget exhausted', 'side_effect.blocked': 'Action blocked', 'side_effect.released': 'Action approved', }); function element(doc, tag, attributes, text) { const node = doc.createElement(tag); Object.entries(attributes || {}).forEach(([name, value]) => node.setAttribute(name, value)); if (text !== undefined && text !== null) node.textContent = String(text); return node; } function announce(doc, container, message, alert) { const box = element(doc, 'p', {'class': alert ? 'hux-wave-a__error' : 'hux-wave-a__status', 'role': alert ? 'alert' : 'status', 'aria-live': alert ? 'assertive' : 'polite'}, message); container.replaceChildren(box); return box; } function friendlyKind(kind) { return KIND_LABELS[kind] || String(kind).replace(/[._]/g, ' '); } function activityExtension(options) { const doc = options.document; const conversationId = options.conversationId; if (!contract.isId(conversationId, 'conv')) throw new TypeError('Activity requires an opaque conversation id'); return Object.freeze({id: 'wave-a-activity', flag: 'hux.activity_timeline', label: 'Activity', order: 10, render(context) { const panel = context.container; let generation = 0; function show(page) { const section = element(doc, 'section', {'class': 'hux-wave-a hux-wave-a--activity', 'aria-labelledby': 'hux-wave-a-activity-title'}); const heading = element(doc, 'header', {'class': 'hux-wave-a__heading'}); heading.appendChild(element(doc, 'div', {}, null)); heading.children[0].appendChild(element(doc, 'h3', {'id': 'hux-wave-a-activity-title'}, 'What is happening')); heading.children[0].appendChild(element(doc, 'p', {}, 'A compact, privacy-filtered timeline for this conversation.')); const refresh = element(doc, 'button', {'type': 'button'}, 'Refresh activity'); refresh.addEventListener('click', load); heading.appendChild(refresh); section.appendChild(heading); const list = element(doc, 'ol', {'class': 'hux-wave-a__timeline'}); page.items.forEach((item) => { const row = element(doc, 'li', {'class': 'hux-wave-a__event', 'data-kind': item.kind}); const stamp = element(doc, 'time', {'datetime': item.timestamp}, new Date(item.timestamp).toLocaleString()); const title = element(doc, 'strong', {}, friendlyKind(item.kind)); const summary = element(doc, 'p', {}, item.summary); row.appendChild(stamp); row.appendChild(title); row.appendChild(summary); if (item.evidence.length) { const details = element(doc, 'details', {'class': 'hux-wave-a__evidence'}); details.appendChild(element(doc, 'summary', {}, `${item.evidence.length} evidence reference${item.evidence.length === 1 ? '' : 's'}`)); const refs = element(doc, 'ul'); item.evidence.forEach((ref) => refs.appendChild(element(doc, 'li', {}, `${ref.kind}: ${ref.id}`))); details.appendChild(refs); row.appendChild(details); } list.appendChild(row); }); section.appendChild(list); if (!page.items.length) section.appendChild(element(doc, 'p', {'class': 'hux-wave-a__empty'}, 'No activity has been recorded yet.')); section.appendChild(element(doc, 'p', {'class': 'hux-wave-a__sr', 'role': 'status', 'aria-live': 'polite'}, `${page.items.length} activity events available`)); panel.replaceChildren(section); } async function load() { const current = ++generation; announce(doc, panel, 'Loading activity…', false); try { const response = await context.client.request(`/conversations/${conversationId}/events?after_seq=0&limit=200`); if (current !== generation) return; show(contract.normalizeEventPage(response.body, context.client.identity, conversationId)); } catch (_) { if (current === generation) announce(doc, panel, 'Activity could not be verified. No event details were shown.', true); } } load(); }}); } function memoryActions(item) { if (item.status === 'proposed') return [['approve', 'Approve'], ['reject', 'Reject']]; if (item.status === 'active') return [ [item.retrievable ? 'remove_retrieval' : 'restore_retrieval', item.retrievable ? 'Stop using in replies' : 'Use in replies'], ['forget', 'Forget'], ]; if (item.status === 'expired') return [['forget', 'Forget']]; return []; } function memoryExtension(options) { const doc = options.document; const conversationId = options.conversationId; const keyFactory = options.idempotencyKey; if (!contract.isId(conversationId, 'conv')) throw new TypeError('Memory requires an opaque conversation id'); if (typeof keyFactory !== 'function') throw new TypeError('Memory requires an idempotency key factory'); return Object.freeze({id: 'wave-a-memory', flag: 'hux.memory_control', label: 'Memory', order: 20, render(context) { const panel = context.container; let generation = 0; async function mutate(item, action, body) { announce(doc, panel, `${friendlyKind(`memory.${action}`)}…`, false); try { await context.client.request(`/memory/${encodeURIComponent(item.id)}/${action}`, { method: 'POST', headers: {'If-Match': String(item.revision)}, ...(body ? {body} : {}), }); await load(); } catch (error) { announce(doc, panel, error && error.status === 409 ? 'This memory changed. Refresh before trying again.' : 'The memory change could not be completed.', true); } } function card(item) { const article = element(doc, 'article', {'class': 'hux-wave-a__memory', 'data-status': item.status}); const header = element(doc, 'header'); header.appendChild(element(doc, 'span', {'class': 'hux-wave-a__pill'}, item.kind)); header.appendChild(element(doc, 'strong', {}, item.status)); article.appendChild(header); article.appendChild(element(doc, 'p', {'class': 'hux-wave-a__memory-content'}, item.content)); const why = element(doc, 'dl', {'class': 'hux-wave-a__meta'}); [['Why saved', item.reason], ['Scope', item.scope.scopeId ? `${item.scope.level}: ${item.scope.scopeId}` : item.scope.level], ['Privacy', item.sensitivity], ['Retention', item.retention]].forEach(([label, value]) => { const row = element(doc, 'div'); row.appendChild(element(doc, 'dt', {}, label)); row.appendChild(element(doc, 'dd', {}, value)); why.appendChild(row); }); article.appendChild(why); const actions = element(doc, 'div', {'class': 'hux-wave-a__actions'}); memoryActions(item).forEach(([action, label]) => { const button = element(doc, 'button', {'type': 'button'}, label); button.addEventListener('click', () => mutate(item, action)); actions.appendChild(button); }); article.appendChild(actions); if (item.status === 'active') { const edit = element(doc, 'form', {'class': 'hux-wave-a__edit'}); const fieldId = `hux-memory-edit-${item.id}`; edit.appendChild(element(doc, 'label', {'for': fieldId}, 'Correct this memory')); const input = element(doc, 'textarea', {'id': fieldId, 'maxlength': '2000', 'required': 'required'}); edit.appendChild(input); const submit = element(doc, 'button', {'type': 'submit'}, 'Save as a corrected memory'); edit.appendChild(submit); edit.addEventListener('submit', (event) => { event.preventDefault(); const content = contract.safeText(input.value, '', 2000); if (content) mutate(item, 'edit', {content}); }); article.appendChild(edit); } return article; } function show(page) { const section = element(doc, 'section', {'class': 'hux-wave-a hux-wave-a--memory', 'aria-labelledby': 'hux-wave-a-memory-title'}); const heading = element(doc, 'header', {'class': 'hux-wave-a__heading'}); const title = element(doc, 'div'); title.appendChild(element(doc, 'h3', {'id': 'hux-wave-a-memory-title'}, 'Memory control')); title.appendChild(element(doc, 'p', {}, 'See what Hermes remembers, why, and how it is used.')); heading.appendChild(title); const refresh = element(doc, 'button', {'type': 'button'}, 'Refresh memory'); refresh.addEventListener('click', load); heading.appendChild(refresh); section.appendChild(heading); const propose = element(doc, 'form', {'class': 'hux-wave-a__propose'}); propose.appendChild(element(doc, 'h4', {}, 'Suggest a memory')); const content = element(doc, 'textarea', {'maxlength': '2000', 'required': 'required', 'aria-label': 'What should Hermes remember?'}); const reason = element(doc, 'input', {'maxlength': '280', 'required': 'required', 'aria-label': 'Why should Hermes remember this?'}); const kind = element(doc, 'select', {'aria-label': 'Memory kind'}); ['preference', 'fact', 'instruction', 'context'].forEach((value) => kind.appendChild(element(doc, 'option', {'value': value}, value))); propose.appendChild(content); propose.appendChild(reason); propose.appendChild(kind); propose.appendChild(element(doc, 'button', {'type': 'submit'}, 'Propose for review')); propose.addEventListener('submit', async (event) => { event.preventDefault(); const clean = contract.safeText(content.value, '', 2000); const why = contract.safeText(reason.value, '', 280); if (!clean || !why) return; announce(doc, panel, 'Submitting memory proposal…', false); try { const idempotencyKey = contract.safeIdempotencyKey(keyFactory()); await context.client.request('/memory', {method: 'POST', headers: {'Idempotency-Key': idempotencyKey}, body: {kind: kind.value || 'fact', content: clean, reason: why, approval_mode: 'ask', conversation_id: conversationId, scope: {level: 'conversation', scope_id: conversationId}}}); await load(); } catch (_) { announce(doc, panel, 'The memory proposal could not be completed.', true); } }); section.appendChild(propose); const list = element(doc, 'div', {'class': 'hux-wave-a__memory-list'}); page.items.forEach((item) => list.appendChild(card(item))); section.appendChild(list); if (!page.items.length) section.appendChild(element(doc, 'p', {'class': 'hux-wave-a__empty'}, 'No memory entries are available.')); section.appendChild(element(doc, 'p', {'class': 'hux-wave-a__sr', 'role': 'status', 'aria-live': 'polite'}, `${page.items.length} memory entries available`)); panel.replaceChildren(section); } async function load() { const current = ++generation; announce(doc, panel, 'Loading memory…', false); try { const response = await context.client.request('/memory'); if (current !== generation) return; show(contract.normalizeMemoryPage(response.body, context.client.identity, conversationId)); } catch (_) { if (current === generation) announce(doc, panel, 'Memory could not be verified. No saved details were shown.', true); } } load(); }}); } function defaultKeyFactory() { let sequence = 0; return () => `webui:memory:${Date.now().toString(36)}:${(++sequence).toString(36)}`; } function createWaveARuntime(options) { const settings = options || {}; const doc = settings.document || (typeof document === 'object' ? document : null); if (!doc) throw new TypeError('Wave A runtime requires a document'); const client = contract.createCanonicalClient(settings); const shell = shellApi.createShell({client, document: doc, instanceId: settings.instanceId || 'wave-a'}); const shared = {document: doc, conversationId: settings.conversationId, idempotencyKey: settings.idempotencyKey || defaultKeyFactory()}; const removeActivity = shell.register(activityExtension(shared)); const removeMemory = shell.register(memoryExtension(shared)); let mounted = false; return Object.freeze({client, async mount(target) { if (mounted) throw new Error('Wave A runtime is already mounted'); mounted = true; shell.mount(target); await client.negotiate(); }, destroy() { removeActivity(); removeMemory(); shell.destroy(); mounted = false; }, }); } return Object.freeze({activityExtension, createWaveARuntime, friendlyKind, memoryActions, memoryExtension}); }));