/* node:coverage disable */ (function (root, factory) { 'use strict'; const api = factory(); if (typeof module === 'object' && module.exports) module.exports = api; else { root.HermesHuxBootstrap = api; api.autoStart(root); } }(typeof globalThis === 'object' ? globalThis : this, function () { /* node:coverage enable */ 'use strict'; const ACCEPT = 'application/vnd.hermes.hux+json; version=1'; const ID = /^[a-z]{2,6}_[A-Za-z0-9._-]{4,80}$/; const SLOT = /^slot-[0-9]{1,3}$/; const USER = /^usr_[0-9a-f]{16,64}$/; const WEBUI_SESSION = /^[A-Za-z0-9._:-]{4,160}$/; const PROJECT_SOURCE = /^[A-Za-z0-9._:@+-]{1,200}$/; const STREAM = /^[A-Za-z0-9._:-]{1,120}$/; const TRUST = new Set(['router', 'relay', 'worker']); const SURFACE = new Set(['chat', 'worker', 'telegram', 'voice', 'api']); const CARD_FLAGS = Object.freeze({ 'HUX-11': 'hux.foundation', 'HUX-01': 'hux.activity_timeline', 'HUX-02': 'hux.memory_control', 'HUX-03': 'hux.projects', 'HUX-04': 'hux.artifacts', 'HUX-05': 'hux.autonomy', 'HUX-06': 'hux.friendly_modes', 'HUX-07': 'hux.multimodal', 'HUX-08': 'hux.research', 'HUX-09': 'hux.onboarding', 'HUX-10': 'hux.privacy', 'HUX-12': 'hux.release_followthrough', }); const REQUIRED_ROUTES = Object.freeze({ 'HUX-11': [['HUX-11', '/hux/v1/capabilities'], ['HUX-11', '/hux/v1/context/bootstrap']], 'HUX-01': [['HUX-01', '/hux/v1/conversations/{id}/events']], 'HUX-02': [['HUX-02', '/hux/v1/memory'], ['HUX-02', '/hux/v1/memory/{id}/{action}']], 'HUX-03': [['HUX-03', '/hux/v1/projects/{id}'], ['HUX-03', '/hux/v1/conversations'], ['HUX-03', '/hux/v1/conversations/{id}'], ['HUX-03', '/hux/v1/conversations/{id}/lineage']], 'HUX-04': [['HUX-04', '/hux/v1/artifacts'], ['HUX-04', '/hux/v1/artifacts/{id}/versions'], ['HUX-04', '/hux/v1/artifacts/{id}/promote']], 'HUX-05': [['HUX-05', '/hux/v1/policy'], ['HUX-05', '/hux/v1/approvals'], ['HUX-05', '/hux/v1/approvals/{id}'], ['HUX-05', '/hux/v1/runs/{id}/stop'], ['HUX-05', '/hux/v1/runs/{id}/budget'], ['HUX-05', '/hux/v1/runs/{id}/gate']], 'HUX-06': [['HUX-06', '/hux/v1/modes'], ['HUX-06', '/hux/v1/projects/{project_id}/conversations/{id}/mode']], 'HUX-07': [['HUX-07', '/hux/v1/projects/{project_id}/conversations/{id}/multimodal/items'], ['HUX-07', '/hux/v1/projects/{project_id}/conversations/{id}/multimodal/items/{item_id}'], ['HUX-07', '/hux/v1/projects/{project_id}/conversations/{id}/multimodal/items/{item_id}/transcript-corrections'], ['HUX-07', '/hux/v1/projects/{project_id}/conversations/{id}/capture-intents']], 'HUX-08': [['HUX-08', '/hux/v1/messages/{id}/citations'], ['HUX-08', '/hux/v1/notebooks'], ['HUX-08', '/hux/v1/notebooks/{id}']], 'HUX-09': [['HUX-09', '/hux/v1/projects/{project_id}/conversations/{id}/suggestions/evaluate'], ['HUX-09', '/hux/v1/projects/{project_id}/conversations/{id}/suggestions/{suggestion_id}/decisions'], ['HUX-09', '/hux/v1/projects/{project_id}/conversations/{id}/suggestions/states']], 'HUX-10': [['HUX-10', '/hux/v1/privacy/policy'], ['HUX-10', '/hux/v1/privacy/notices'], ['HUX-10', '/hux/v1/conversations/{id}/forget'], ['HUX-10', '/hux/v1/privacy/audit']], 'HUX-12': [['HUX-12', '/hux/v1/projects/{project_id}/conversations/{id}/releases'], ['HUX-12', '/hux/v1/projects/{project_id}/conversations/{id}/releases/{release_id}'], ['HUX-12', '/hux/v1/projects/{project_id}/conversations/{id}/releases/{release_id}/transitions']], }); const WAVES = Object.freeze({ a: ['HUX-01', 'HUX-02'], b: ['HUX-03', 'HUX-04', 'HUX-06', 'HUX-08'], governance: ['HUX-05', 'HUX-10'], c: ['HUX-07', 'HUX-09', 'HUX-12'], }); function record(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : null; } function immutable(value) { if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value; Object.values(value).forEach(immutable); return Object.freeze(value); } function opaque(value, prefix) { return typeof value === 'string' && ID.test(value) && value.startsWith(`${prefix}_`); } function normalizeIdentity(value) { if (!exact(value, ['tenant_slot', 'subject', 'surface', 'trust']) || !SLOT.test(value.tenant_slot) || !USER.test(value.subject) || !SURFACE.has(value.surface) || !TRUST.has(value.trust)) return null; return immutable({tenantSlot: value.tenant_slot, userRef: value.subject, surface: value.surface, trust: value.trust}); } function normalizeContext(session) { const value = record(session); const context = value && record(value.hux_context); const contextKeys = new Set(['schema', 'webui_session_id', 'session_id', 'conversation_id', 'project_id', 'project_source', 'identity', 'run_id', 'message_id', 'branch_point_message_id', 'notebook_id']); if (!context || context.schema !== 'hux.webui_context.v1' || Object.keys(context).some((key) => !contextKeys.has(key)) || !WEBUI_SESSION.test(String(value.session_id || '')) || context.webui_session_id !== value.session_id || !opaque(context.session_id, 'ses') || !opaque(context.conversation_id, 'conv') || !opaque(context.project_id, 'prj') || !PROJECT_SOURCE.test(String(context.project_source || ''))) return null; const identity = normalizeIdentity(context.identity); if (!identity) return null; const optional = {runId: context.run_id, messageId: context.message_id, branchPointMessageId: context.branch_point_message_id, notebookId: context.notebook_id}; if (optional.runId !== undefined && !opaque(optional.runId, 'run')) return null; for (const name of ['messageId', 'branchPointMessageId']) { if (optional[name] !== undefined && !opaque(optional[name], 'msg')) return null; } if (optional.notebookId !== undefined && !opaque(optional.notebookId, 'nb')) return null; return immutable({webuiSessionId: value.session_id, sessionId: context.session_id, conversationId: context.conversation_id, projectId: context.project_id, projectSource: context.project_source, identity, ...optional}); } function sameIdentity(raw, expected) { return Boolean(exact(raw, ['tenant_slot', 'subject', 'surface', 'trust']) && raw.tenant_slot === expected.tenantSlot && raw.subject === expected.userRef && raw.surface === expected.surface && raw.trust === expected.trust); } function exact(value, keys) { return Boolean(record(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key))); } function normalizeBootstrap(payload, context, status) { const keys = ['schema', 'contract_version', 'identity', 'session_id', 'project_id', 'conversation_id', 'revisions', 'created']; if (!exact(payload, keys) || payload.schema !== 'hux.context_bootstrap.v1' || payload.contract_version !== '1.1.0' || !sameIdentity(payload.identity, context.identity) || payload.session_id !== context.sessionId || payload.project_id !== context.projectId || payload.conversation_id !== context.conversationId || !exact(payload.revisions, ['project', 'conversation']) || !exact(payload.created, ['project', 'conversation'])) return null; const revisions = payload.revisions; const created = payload.created; if (!Number.isSafeInteger(revisions.project) || revisions.project < 1 || !Number.isSafeInteger(revisions.conversation) || revisions.conversation < 1 || typeof created.project !== 'boolean' || typeof created.conversation !== 'boolean' || (status === 201) !== (created.project || created.conversation)) return null; return immutable({revisions: {...revisions}, created: {...created}}); } function normalizeCapabilities(payload, expected) { if (!exact(payload, ['schema', 'contract_version', 'identity', 'cards', 'server']) || payload.schema !== 'hux.capabilities.v1' || !/^1\.\d+\.\d+$/.test(String(payload.contract_version)) || !sameIdentity(payload.identity, expected) || !Array.isArray(payload.cards) || payload.cards.length > Object.keys(CARD_FLAGS).length || !record(payload.server) || Object.keys(payload.server).some((key) => !['commit', 'image_digest'].includes(key)) || Object.values(payload.server).some((item) => typeof item !== 'string' || item.length > 160)) return null; const cards = {}; for (const item of payload.cards) { if (!exact(item, ['card', 'flag', 'enabled', 'routes']) || !Object.prototype.hasOwnProperty.call(CARD_FLAGS, item.card) || CARD_FLAGS[item.card] !== item.flag || typeof item.enabled !== 'boolean' || !Array.isArray(item.routes) || cards[item.card]) return null; const routes = item.routes.filter((route) => typeof route === 'string' && /^\/hux\/v1(?:\/[A-Za-z0-9._:{}-]+)*$/.test(route)); if (routes.length !== item.routes.length || new Set(routes).size !== routes.length) return null; cards[item.card] = immutable({enabled: item.enabled, routes}); } if (!cardComplete(cards, 'HUX-11')) return null; return immutable(cards); } function cardComplete(cards, card) { const current = cards[card]; if (!current || !current.enabled) return false; return REQUIRED_ROUTES[card].every(([owner, route]) => cards[owner] && cards[owner].enabled && cards[owner].routes.includes(route)); } function enabledWaves(cards) { const result = []; Object.entries(WAVES).forEach(([wave, members]) => { const enabled = members.filter((card) => cards[card] && cards[card].enabled); if (enabled.length && enabled.every((card) => cardComplete(cards, card))) result.push(wave); }); return Object.freeze(result); } function safeCsrf(config) { const value = record(config) && config.csrfToken; return typeof value === 'string' && value.length >= 16 && value.length <= 512 && !/[\u0000-\u001f\u007f]/.test(value) ? value : null; } function scopedFetcher(fetcher, csrfToken) { return function huxFetch(url, init) { const target = String(url || ''); if (!/^\/hux\/v1(?:[/?]|$)/.test(target) || target.startsWith('//') || target.includes('..')) { return Promise.reject(new TypeError('HUX browser requests must remain same-origin')); } const options = init || {}; const method = String(options.method || 'GET').toUpperCase(); const headers = {...(options.headers || {})}; if (!['GET', 'HEAD'].includes(method)) headers['X-Hermes-CSRF-Token'] = csrfToken; return fetcher(target, {...options, cache: 'no-store', credentials: 'same-origin', headers}); }; } async function preflight(fetcher, context) { try { const response = await fetcher('/hux/v1/capabilities', {method: 'GET', headers: {Accept: ACCEPT}}); if (!response || response.status === 404 || !response.ok) return null; const cards = normalizeCapabilities(await response.json(), context.identity); const waves = cards && enabledWaves(cards); return waves && waves.length ? immutable({cards, waves}) : null; } catch (_) { return null; } } async function bootstrapContext(fetcher, context) { try { const response = await fetcher('/hux/v1/context/bootstrap', {method: 'POST', headers: { Accept: ACCEPT, 'Content-Type': 'application/json', 'Idempotency-Key': `hux:context:${context.sessionId}`, }, body: JSON.stringify({raw_session_id: context.webuiSessionId, project_source: context.projectSource, session_id: context.sessionId, conversation_id: context.conversationId, project_id: context.projectId})}); if (!response || ![200, 201].includes(response.status) || !response.ok) return null; return normalizeBootstrap(await response.json(), context, response.status); } catch (_) { return null; } } function node(doc, tag, attributes, text) { const value = doc.createElement(tag); Object.entries(attributes).forEach(([name, item]) => value.setAttribute(name, item)); if (text !== undefined) value.textContent = String(text); return value; } function railHost(doc) { // The app's left icon rail (chat, tasks, telegram, …), the desktop nav home // (shown from ~641px up). Optional: bundles without it fall through to the // top app bar, then to the floating toggle. if (typeof doc.querySelector !== 'function') return null; const rail = doc.querySelector('nav.rail'); return rail && typeof rail.insertBefore === 'function' ? rail : null; } function titlebarHost(doc) { // The top app bar (hamburger · avatar · title · new-chat · reload) — the // mobile nav home, always visible below the rail breakpoint where the rail // collapses. Mounting the Workspace toggle here keeps it beside the app's // own icons instead of floating over the composer. if (typeof doc.querySelector !== 'function') return null; const bar = doc.querySelector('header.app-titlebar'); return bar && typeof bar.insertBefore === 'function' ? bar : null; } function railIcon(doc) { try { if (typeof doc.createElementNS !== 'function') return null; const svgNS = 'http://www.w3.org/2000/svg'; const svg = doc.createElementNS(svgNS, 'svg'); [['width', '20'], ['height', '20'], ['viewBox', '0 0 24 24'], ['fill', 'none'], ['stroke', 'currentColor'], ['stroke-width', '1.5'], ['stroke-linecap', 'round'], ['stroke-linejoin', 'round'], ['aria-hidden', 'true'], ].forEach(([name, value]) => svg.setAttribute(name, value)); const path = doc.createElementNS(svgNS, 'path'); path.setAttribute('d', 'M3 5a2 2 0 0 1 2-2h6v18H5a2 2 0 0 1-2-2V5Zm10-2h6a2 2 0 0 1 2 2v5h-8V3Zm0 11h8v5a2 2 0 0 1-2 2h-6v-7Z'); svg.appendChild(path); return svg; } catch (_) { return null; } } function navIconToggle(doc, cls) { // An icon-only Workspace entry that sits WITH the app's own nav items and is // styled by their rules. Shared by the desktop rail and the mobile top app // bar so both look native beside their siblings. const toggle = node(doc, 'button', {'class': cls, type: 'button', 'aria-controls': 'huxWorkspaceDrawer', 'aria-expanded': 'false', 'data-tooltip': 'Workspace', 'aria-label': 'Workspace'}); const icon = railIcon(doc); if (icon) toggle.appendChild(icon); else toggle.textContent = '⧉'; return toggle; } function createChrome(doc) { // The Workspace toggle mounts WITH the other nav items at every width and // never floats over the composer: the desktop icon rail (nav.rail) hands off // to the mobile top app bar (header.app-titlebar), each showing exactly one // toggle via bootstrap.css. A floating button appears ONLY when neither nav // home exists (headless DOMs), pinned to a TOP corner clear of the composer. const rail = railHost(doc); const titlebar = titlebarHost(doc); let railToggle = null; if (rail) { railToggle = navIconToggle(doc, 'rail-btn has-tooltip hux-workspace-toggle-rail'); const spacer = typeof rail.querySelector === 'function' ? rail.querySelector('.rail-spacer') : null; rail.insertBefore(railToggle, spacer || null); } let titlebarToggle = null; if (titlebar) { titlebarToggle = navIconToggle(doc, 'hux-workspace-titlebar-toggle has-tooltip has-tooltip--bottom'); titlebar.insertBefore(titlebarToggle, null); } const drawer = node(doc, 'aside', {id: 'huxWorkspaceDrawer', 'class': 'hux-workspace-drawer', 'aria-labelledby': 'huxWorkspaceTitle'}); const header = node(doc, 'header', {'class': 'hux-workspace-drawer__header'}); const title = node(doc, 'h2', {id: 'huxWorkspaceTitle'}, 'Hermes workspace'); const close = node(doc, 'button', {type: 'button', 'aria-label': 'Close Hermes workspace'}, 'Close'); const content = node(doc, 'div', {'class': 'hux-workspace-drawer__content'}); drawer.hidden = true; header.appendChild(title); header.appendChild(close); drawer.appendChild(header); drawer.appendChild(content); doc.body.appendChild(drawer); let floating = null; if (!rail && !titlebar) { floating = node(doc, 'button', {'class': 'hux-workspace-toggle', type: 'button', 'aria-controls': 'huxWorkspaceDrawer', 'aria-expanded': 'false'}, 'Workspace'); doc.body.appendChild(floating); } const toggles = [railToggle, titlebarToggle, floating].filter(Boolean); const open = floating || railToggle || titlebarToggle; function setOpen(value) { drawer.hidden = !value; toggles.forEach((toggle) => toggle.setAttribute('aria-expanded', value ? 'true' : 'false')); if (value && typeof close.focus === 'function') close.focus(); } const onOpen = () => setOpen(true); const onClose = () => setOpen(false); const onKey = (event) => { if (event.key === 'Escape' && !drawer.hidden) setOpen(false); }; toggles.forEach((toggle) => toggle.addEventListener('click', onOpen)); close.addEventListener('click', onClose); doc.addEventListener('keydown', onKey); return Object.freeze({content, drawer, open, railToggle, titlebarToggle, setOpen, destroy() { doc.removeEventListener('keydown', onKey); toggles.forEach((toggle) => { if (toggle.parentNode) toggle.parentNode.removeChild(toggle); }); if (drawer.parentNode) drawer.parentNode.removeChild(drawer); }}); } async function defaultRuntimeFactory(root, target, context, fetcher, waves, controls) { const runtimes = []; const common = {document: root.document, fetcher, expectedIdentity: context.identity, projectId: context.projectId, conversationId: context.conversationId, sessionId: context.sessionId, runId: context.runId, messageId: context.messageId, branchPointMessageId: context.branchPointMessageId, notebookId: context.notebookId, stopModelResponse: controls && controls.stopModelResponse, canStopModelResponse: controls && controls.canStopModelResponse}; const constructors = { a: root.HermesHuxWaveA && root.HermesHuxWaveA.createWaveARuntime, b: root.HermesHuxWaveBRuntime && root.HermesHuxWaveBRuntime.createWaveBRuntime, governance: root.HermesHuxAutonomyPrivacy && root.HermesHuxAutonomyPrivacy.createAutonomyPrivacyRuntime, c: root.HermesHuxWaveC && root.HermesHuxWaveC.createWaveCRuntime, }; try { for (const wave of waves) { if (typeof constructors[wave] !== 'function') throw new Error('HUX runtime is incomplete'); const host = node(root.document, 'section', {'class': `hux-workspace-wave hux-workspace-wave--${wave}`}); target.appendChild(host); const runtime = constructors[wave](common); runtimes.push(runtime); await runtime.mount(host); } } catch (error) { runtimes.reverse().forEach((runtime) => runtime.destroy()); throw error; } return Object.freeze({destroy() { runtimes.reverse().forEach((runtime) => runtime.destroy()); }}); } function ownedActiveStream(session, activeStreamId, expected) { const current = normalizeContext(session); const raw = record(session); return current && expected && current.webuiSessionId === expected.webuiSessionId && current.conversationId === expected.conversationId && current.identity.userRef === expected.identity.userRef && STREAM.test(String(activeStreamId || '')) && raw.active_stream_id === activeStreamId ? activeStreamId : null; } function createOwnedStop(root, sessionReader, activeStreamReader, context, timeoutMs) { const bounded = Number.isSafeInteger(timeoutMs) && timeoutMs >= 250 && timeoutMs <= 10000 ? timeoutMs : 3000; const canStopModelResponse = () => Boolean(ownedActiveStream(sessionReader(), activeStreamReader(), context) && typeof root.cancelStream === 'function'); async function stopModelResponse() { const streamId = ownedActiveStream(sessionReader(), activeStreamReader(), context); if (!streamId || typeof root.cancelStream !== 'function') return Object.freeze({accepted: false}); let timer = null; try { const timeout = new Promise((resolve) => { timer = root.setTimeout(() => resolve(false), bounded); }); const accepted = await Promise.race([Promise.resolve(root.cancelStream('hux-stop')), timeout]); if (timer !== null) root.clearTimeout(timer); return Object.freeze({accepted: accepted === true, sessionId: context.webuiSessionId, streamId}); } catch (_) { if (timer !== null) root.clearTimeout(timer); return Object.freeze({accepted: false}); } } return Object.freeze({canStopModelResponse, stopModelResponse}); } function createCoordinator(options) { const settings = options || {}; const root = settings.root; const doc = settings.document || (root && root.document); const rawFetch = settings.fetcher || (root && root.fetch && root.fetch.bind(root)); const sessionReader = settings.sessionReader; const activeStreamReader = settings.activeStreamReader || (() => browserActiveStreamReader()); const configReader = settings.configReader || (() => root.__HERMES_CONFIG__); const runtimeFactory = settings.runtimeFactory || ((target, context, fetcher, waves, controls) => defaultRuntimeFactory(root, target, context, fetcher, waves, controls)); if (!root || !doc || !rawFetch || typeof sessionReader !== 'function') { throw new TypeError('HUX coordinator requires browser, fetch, and trusted session reader'); } let fingerprint = null; let pendingFingerprint = null; let generation = 0; let chrome = null; let runtime = null; let timer = null; function clear() { if (runtime) runtime.destroy(); if (chrome) chrome.destroy(); runtime = null; chrome = null; } async function refresh() { const context = normalizeContext(sessionReader()); const activeStream = context ? ownedActiveStream(sessionReader(), activeStreamReader(), context) : null; const next = context ? `${context.webuiSessionId}\0${context.conversationId}\0${context.identity.userRef}` + `\0${context.runId || ''}\0${activeStream || ''}` : ''; if (next === fingerprint || next === pendingFingerprint) return false; pendingFingerprint = next; const current = ++generation; clear(); if (!context) { fingerprint = ''; pendingFingerprint = null; return false; } const csrf = safeCsrf(configReader()); if (!csrf) { pendingFingerprint = null; return false; } const fetcher = scopedFetcher(rawFetch, csrf); const capability = await preflight(fetcher, context); if (current !== generation) return false; if (!capability) { pendingFingerprint = null; return false; } const binding = await bootstrapContext(fetcher, context); if (current !== generation) return false; if (!binding) { pendingFingerprint = null; return false; } const nextChrome = createChrome(doc); try { const controls = createOwnedStop(root, sessionReader, activeStreamReader, context, settings.stopTimeoutMs); const nextRuntime = await runtimeFactory(nextChrome.content, context, fetcher, capability.waves, controls); if (current !== generation) { nextRuntime.destroy(); nextChrome.destroy(); return false; } chrome = nextChrome; runtime = nextRuntime; fingerprint = next; pendingFingerprint = null; return true; } catch (_) { nextChrome.destroy(); pendingFingerprint = null; return false; } } function start() { if (timer !== null) return; void refresh(); timer = root.setInterval(() => { void refresh(); }, settings.pollMs || 750); root.addEventListener('pageshow', refresh); } function destroy() { generation += 1; fingerprint = null; pendingFingerprint = null; if (timer !== null) root.clearInterval(timer); timer = null; root.removeEventListener('pageshow', refresh); clear(); } return Object.freeze({destroy, refresh, start, getState: () => ({active: Boolean(runtime), fingerprint})}); } function browserSessionReader() { return typeof S === 'object' ? S.session : null; } function browserActiveStreamReader() { return typeof S === 'object' ? S.activeStreamId : null; } function mergeTrustedContext(session, payload) { const current = record(session); const response = record(payload); if (!current || !response || response.session_id !== current.session_id || !record(response.hux_context)) return false; const next = normalizeContext({...current, hux_context: response.hux_context}); if (!next || next.webuiSessionId !== current.session_id) return false; current.hux_context = response.hux_context; return true; } function autoStart(root) { if (!root || !root.document || typeof root.fetch !== 'function') return null; const coordinator = createCoordinator({root, sessionReader: browserSessionReader}); if (root.document.readyState === 'loading') { root.document.addEventListener('DOMContentLoaded', () => coordinator.start(), {once: true}); } else coordinator.start(); return coordinator; } return Object.freeze({ACCEPT, autoStart, browserActiveStreamReader, cardComplete, createChrome, createCoordinator, createOwnedStop, ownedActiveStream, bootstrapContext, defaultRuntimeFactory, enabledWaves, normalizeBootstrap, normalizeCapabilities, normalizeContext, normalizeIdentity, mergeTrustedContext, preflight, safeCsrf, scopedFetcher}); }));