(function (root, factory) { 'use strict'; const api = factory(); if (typeof module === 'object' && module.exports) module.exports = api; else root.HermesHuxFoundation = api; }(typeof globalThis === 'object' ? globalThis : this, function () { 'use strict'; const API_VERSION = 'hux.v1'; const CAPABILITY_SCHEMA = 'hux.capabilities.v1'; const FLAG_DEPENDENCIES = Object.freeze({ 'hux.activity_timeline': ['hux.foundation'], 'hux.memory_control': ['hux.foundation', 'hux.privacy'], 'hux.projects': ['hux.foundation'], 'hux.artifacts': ['hux.foundation', 'hux.projects'], 'hux.autonomy': ['hux.foundation', 'hux.activity_timeline'], 'hux.friendly_modes': ['hux.foundation'], 'hux.multimodal': ['hux.foundation', 'hux.artifacts', 'hux.autonomy'], 'hux.research': ['hux.foundation', 'hux.friendly_modes'], 'hux.onboarding': ['hux.foundation', 'hux.projects', 'hux.friendly_modes'], 'hux.privacy': ['hux.foundation'], 'hux.foundation': [], 'hux.release_followthrough': [], }); const FLAGS = Object.freeze(Object.keys(FLAG_DEPENDENCIES)); const SURFACES = new Set(['chat', 'worker', 'telegram', 'voice', 'api']); const ID = /^[a-z]{2,6}_[A-Za-z0-9._-]{4,80}$/; const TENANT_REF = /^tnt_[0-9a-f]{16,64}$/; const USER_REF = /^usr_[0-9a-f]{16,64}$/; const TIMESTAMP = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,6})?Z$/; const EVENT_KINDS = new Set([ 'message.user', 'message.assistant', 'decision.route', 'decision.plan', 'tool.call', 'tool.result', 'approval.requested', 'approval.resolved', 'memory.proposed', 'memory.committed', 'memory.forgotten', 'artifact.created', 'artifact.version', 'artifact.promoted', 'citation.attached', 'mode.changed', 'run.started', 'run.cancelled', 'run.completed', 'run.failed', 'privacy.notice', 'suggestion.shown', 'suggestion.dismissed', 'release.transition', ]); const EVIDENCE_KINDS = new Set([ 'message', 'tool_call', 'tool_result', 'artifact_version', 'source', 'passage', 'memory', 'approval', 'run', 'url', 'file', 'build', 'flux', 'pod', ]); const SENSITIVITY = new Set(['public', 'personal', 'sensitive', 'restricted']); class HuxContractError extends Error { constructor(message) { super(message); this.name = 'HuxContractError'; } } function immutable(value) { if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value; Object.values(value).forEach(immutable); return Object.freeze(value); } function requiredString(value, pattern, field) { if (typeof value !== 'string' || !pattern.test(value)) { throw new HuxContractError(`Invalid ${field}`); } return value; } function normalizeIdentity(value) { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw new HuxContractError('Missing scoped identity'); } if (!SURFACES.has(value.surface)) throw new HuxContractError('Invalid identity surface'); return immutable({ tenantRef: requiredString(value.tenant_ref || value.tenantRef, TENANT_REF, 'tenant reference'), userRef: requiredString(value.user_ref || value.userRef, USER_REF, 'user reference'), surface: value.surface, }); } function sameIdentity(left, right) { return left.tenantRef === right.tenantRef && left.userRef === right.userRef && left.surface === right.surface; } function dependenciesEnabled(flag, declared, seen) { if (!declared.has(flag) || seen.has(flag)) return declared.has(flag); const next = new Set(seen).add(flag); return FLAG_DEPENDENCIES[flag].every((dependency) => dependenciesEnabled(dependency, declared, next)); } function normalizeCapabilities(payload, expectedIdentity) { if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { throw new HuxContractError('Capability response is not an object'); } if (payload.schema !== CAPABILITY_SCHEMA || payload.api_version !== API_VERSION) { throw new HuxContractError('Unsupported HUX capability version'); } const actualIdentity = normalizeIdentity(payload.identity); if (!sameIdentity(actualIdentity, expectedIdentity)) { throw new HuxContractError('Capability identity does not match this surface'); } if (!Array.isArray(payload.flags)) throw new HuxContractError('Capability flags are missing'); if (payload.flags.length > FLAGS.length * 2) throw new HuxContractError('Too many capability flags'); const declared = new Set(payload.flags.filter((flag) => FLAGS.includes(flag))); const enabled = FLAGS.filter((flag) => dependenciesEnabled(flag, declared, new Set())); return immutable({ schema: CAPABILITY_SCHEMA, apiVersion: API_VERSION, identity: actualIdentity, flags: enabled, }); } function createClient(options) { const settings = options || {}; const fetcher = Object.prototype.hasOwnProperty.call(settings, 'fetcher') ? settings.fetcher : (typeof fetch === 'function' ? fetch.bind(globalThis) : null); if (!fetcher) throw new TypeError('A fetch implementation is required'); const expectedIdentity = normalizeIdentity(settings.expectedIdentity); const baseUrl = String(settings.baseUrl || '/hux/v1').replace(/\/$/, ''); if (!baseUrl.startsWith('/') || baseUrl.startsWith('//') || /[?#\\]/.test(baseUrl) || baseUrl.includes('..')) { throw new TypeError('HUX baseUrl must be same-origin'); } let state = immutable({phase: 'disabled', capabilities: null, error: null}); let generation = 0; const listeners = new Set(); function publish(next) { state = immutable(next); listeners.forEach((listener) => listener(state)); return state; } async function negotiate() { const current = ++generation; publish({phase: 'loading', capabilities: null, error: null}); try { const response = await fetcher(`${baseUrl}/capabilities`, { cache: 'no-store', credentials: 'same-origin', headers: {Accept: 'application/vnd.hermes.hux+json; version=1'}, }); if (current !== generation) return state; if (response.status === 404) { return publish({phase: 'disabled', capabilities: null, error: null}); } if (!response.ok) throw new Error(`Capability request failed (${response.status})`); const capabilities = normalizeCapabilities(await response.json(), expectedIdentity); if (!capabilities.flags.includes('hux.foundation')) { return publish({phase: 'disabled', capabilities: null, error: null}); } return publish({phase: 'ready', capabilities, error: null}); } catch (error) { if (current !== generation) return state; const message = error instanceof HuxContractError ? 'Hermes workspace capabilities could not be verified.' : 'Hermes workspace features are temporarily unavailable.'; return publish({phase: 'error', capabilities: null, error: message}); } } function subscribe(listener) { if (typeof listener !== 'function') throw new TypeError('Listener must be a function'); listeners.add(listener); listener(state); return function unsubscribe() { listeners.delete(listener); }; } return Object.freeze({ apiVersion: API_VERSION, identity: expectedIdentity, getState: () => state, negotiate, subscribe, enabled: (flag) => state.phase === 'ready' && FLAGS.includes(flag) && state.capabilities.flags.includes(flag), endpoint: (path) => { const safe = String(path || ''); if (!safe.startsWith('/') || safe.startsWith('//') || safe.includes('..') || /\\|%(?:2e|2f|5c)/i.test(safe)) { throw new TypeError('HUX endpoint must be a scoped relative path'); } return baseUrl + safe; }, }); } function createAdapters(scope) { const identity = normalizeIdentity(scope && scope.identity); const conversationId = requiredString(scope && scope.conversationId, ID, 'conversation id'); function assertRecord(record) { if (!record || typeof record !== 'object' || Array.isArray(record)) { throw new HuxContractError('HUX record is not an object'); } if (record.owner && record.owner !== identity.userRef) { throw new HuxContractError('HUX record owner crossed the user boundary'); } if (record.conversation_id && record.conversation_id !== conversationId) { throw new HuxContractError('HUX record crossed the conversation boundary'); } } function adaptEvent(record) { assertRecord(record); if (record.schema !== 'hux.event.v1' || !Number.isSafeInteger(record.seq) || record.seq < 0) { throw new HuxContractError('Invalid activity event'); } requiredString(record.id, ID, 'event id'); requiredString(record.ts, TIMESTAMP, 'event timestamp'); if (!EVENT_KINDS.has(record.kind) || !SENSITIVITY.has(record.sensitivity)) { throw new HuxContractError('Invalid activity event classification'); } if (typeof record.summary !== 'string' || !record.summary.length || record.summary.length > 280) { throw new HuxContractError('Invalid activity event summary'); } if (!record.redaction || !['none', 'partial', 'full'].includes(record.redaction.level)) { throw new HuxContractError('Invalid activity event redaction'); } if (record.conversation_id !== conversationId) { throw new HuxContractError('Activity event has no matching conversation'); } const hidden = record.redaction && record.redaction.level === 'full'; const evidence = Array.isArray(record.evidence) ? record.evidence.slice(0, 64).map((item) => ({ kind: String(item && item.kind || ''), id: String(item && item.id || ''), })).filter((item) => EVIDENCE_KINDS.has(item.kind) && item.id.length <= 200) : []; return immutable({ schema: record.schema, id: record.id, seq: record.seq, timestamp: String(record.ts || ''), kind: String(record.kind || ''), summary: hidden ? 'Details hidden by privacy controls.' : String(record.summary || '').slice(0, 280), sensitivity: String(record.sensitivity || 'personal'), redacted: hidden, evidence, }); } function adaptObject(record) { assertRecord(record); if (typeof record.schema !== 'string' || !/^hux\.[a-z_]+\.v1$/.test(record.schema)) { throw new HuxContractError('Unsupported HUX object version'); } requiredString(record.id, ID, 'object id'); return immutable({ schema: record.schema, id: record.id, title: String(record.title || record.question || record.kind || 'Hermes item').slice(0, 300), kind: String(record.kind || record.type || ''), status: String(record.status || ''), sensitivity: String(record.sensitivity || 'personal'), conversationId: record.conversation_id || null, projectId: record.project_id || null, updatedAt: record.updated_at || record.created_at || null, }); } return Object.freeze({identity, conversationId, adaptEvent, adaptObject}); } return Object.freeze({ API_VERSION, CAPABILITY_SCHEMA, FLAGS, HuxContractError, createAdapters, createClient, normalizeCapabilities, normalizeIdentity, }); }));