Spaces:
Running
Running
feat(operator-widget): Chaski operator widget across all served HTML — vendored 0-CDN, live-wired, no codenames
37943a5 verified | /* a11oy-operator-widget.js — floating governed-operator widget for SZL surfaces. | |
| * Version: 3.0.0 — Chaski agent surface, wired to the LIVE a11oy/killinchu | |
| * substrate (real SSE chat + agentic loop + v4 ledger). No character codenames. | |
| * | |
| * The operator widget is a persistent, honest interface to the substrate. It is | |
| * not a destination page. It floats bottom-right, opens to a conversation panel, | |
| * and surfaces notifications (receipt minted, gate denied, build status, | |
| * doctrine drift). It ROUTES questions to the substrate backends — it has NO | |
| * model of its own — and is HONEST when a backend is unreachable (it never | |
| * fabricates an answer). The agent surface is named "Chaski" (the runner who | |
| * carries the message), per SZL doctrine. | |
| * | |
| * Auto-detection: the widget detects the host organ from the page origin | |
| * (killinchu -> /api/killinchu, otherwise -> /api/a11oy) and calls SAME-ORIGIN | |
| * endpoints by default, so it works on every served surface with zero config. | |
| * | |
| * Optional overrides via data-* attributes on the script tag: | |
| * data-a11oy-base -> absolute base URL for the a11oy substrate (cross-origin) | |
| * data-organ -> force organ id ("a11oy" | "killinchu") | |
| * data-surface -> name of the host surface (for attribution) | |
| * | |
| * Vanilla JS, no dependencies, no runtime CDN, embeddable anywhere. | |
| * | |
| * Author: Stephen P. Lutar Jr. <stephenlutar2@gmail.com> | |
| * SPDX: Apache-2.0 · DCO · ADDITIVE | |
| */ | |
| (function () { | |
| 'use strict'; | |
| if (window.__a11oyOperatorWidgetLoaded) return; | |
| window.__a11oyOperatorWidgetLoaded = true; | |
| // ---- Resolve configuration from the script tag's data-* attributes ---- | |
| var script = | |
| document.currentScript || | |
| (function () { | |
| var s = document.querySelectorAll('script[src*="a11oy-operator-widget"],script[src*="rosie-widget"]'); | |
| return s.length ? s[s.length - 1] : null; | |
| })(); | |
| var ds = (script && script.dataset) || {}; | |
| function trim(u) { return u ? u.replace(/\/+$/, '') : ''; } | |
| // Detect the host organ from the page origin (same-origin by default). | |
| function detectOrgan() { | |
| if (ds.organ) return String(ds.organ).toLowerCase(); | |
| var h = (location.hostname || '').toLowerCase(); | |
| if (h.indexOf('killinchu') >= 0) return 'killinchu'; | |
| return 'a11oy'; | |
| } | |
| var ORGAN = detectOrgan(); | |
| var CFG = { | |
| organ: ORGAN, | |
| // same-origin by default ('' => relative); cross-origin only if explicitly set | |
| base: trim(ds.a11oyBase || ds.base || ''), | |
| surface: ds.surface || (location.hostname || 'surface'), | |
| }; | |
| // a11oy.code orchestrator (chat + agent) only lives on the a11oy organ. | |
| // killinchu has its own v4 surface (ledger/inbox) but routes reasoning to a11oy. | |
| CFG.codeBase = (ORGAN === 'killinchu' && !CFG.base) | |
| ? 'https://szlholdings-a11oy.hf.space' // killinchu reasons via a11oy substrate | |
| : CFG.base; | |
| CFG.apiOrgan = ORGAN; // for v4 ledger/inbox on the host organ | |
| var LS_KEY = 'a11oy.operator.state.v1'; | |
| var SS_KEY = 'a11oy.operator.thread.v1'; | |
| // ---- Inline portrait (no runtime CDN): governed-operator monogram SVG ---- | |
| var PORTRAIT = 'data:image/svg+xml;utf8,' + encodeURIComponent( | |
| '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">' + | |
| '<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">' + | |
| '<stop offset="0" stop-color="#1c2742"/><stop offset="1" stop-color="#0a0f1e"/>' + | |
| '</linearGradient></defs>' + | |
| '<rect width="64" height="64" rx="16" fill="url(#g)"/>' + | |
| '<circle cx="32" cy="32" r="18" fill="none" stroke="#c9b787" stroke-width="2"/>' + | |
| '<path d="M32 16 L32 48 M16 32 L48 32" stroke="#c9b787" stroke-width="1.4" opacity="0.55"/>' + | |
| '<circle cx="32" cy="32" r="4.5" fill="#c9b787"/>' + | |
| '<text x="32" y="59" font-family="ui-monospace,monospace" font-size="9" fill="#9fb4cc" ' + | |
| 'text-anchor="middle" letter-spacing="1">CHASKI</text></svg>' | |
| ); | |
| // ---- Small DOM helpers ---- | |
| function el(tag, attrs, kids) { | |
| var n = document.createElement(tag); | |
| if (attrs) { | |
| for (var k in attrs) { | |
| if (k === 'class') n.className = attrs[k]; | |
| else if (k === 'text') n.textContent = attrs[k]; | |
| else if (k === 'html') n.innerHTML = attrs[k]; | |
| else if (k.slice(0, 2) === 'on' && typeof attrs[k] === 'function') | |
| n.addEventListener(k.slice(2), attrs[k]); | |
| else if (attrs[k] != null) n.setAttribute(k, attrs[k]); | |
| } | |
| } | |
| (kids || []).forEach(function (c) { | |
| if (c == null) return; | |
| n.appendChild(typeof c === 'string' ? document.createTextNode(c) : c); | |
| }); | |
| return n; | |
| } | |
| function esc(s) { | |
| return String(s).replace(/[&<>"']/g, function (c) { | |
| return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; | |
| }); | |
| } | |
| // ---- Persistent state ---- | |
| var state = { unread: 0, status: 'unknown' }; | |
| try { | |
| var saved = JSON.parse(localStorage.getItem(LS_KEY) || '{}'); | |
| if (saved && typeof saved === 'object' && typeof saved.unread === 'number') state.unread = saved.unread; | |
| } catch (e) {} | |
| function persist() { | |
| try { localStorage.setItem(LS_KEY, JSON.stringify({ unread: state.unread })); } catch (e) {} | |
| } | |
| var thread = []; | |
| try { | |
| var t = JSON.parse(sessionStorage.getItem(SS_KEY) || '[]'); | |
| if (Array.isArray(t)) thread = t; | |
| } catch (e) {} | |
| function persistThread() { | |
| try { sessionStorage.setItem(SS_KEY, JSON.stringify(thread.slice(-50))); } catch (e) {} | |
| } | |
| // ---- CSS (neutral governed palette: deep navy + signed gold + emerald) ---- | |
| var css = [ | |
| '.aow-root{position:fixed;bottom:24px;right:24px;z-index:2147483647;font-family:"Inter",system-ui,sans-serif;font-size:14px;line-height:1.5;}', | |
| '.aow-fab{width:56px;height:56px;border-radius:50%;border:1px solid rgba(201,183,135,0.45);cursor:pointer;', | |
| 'background:linear-gradient(135deg,#16203a 0%,#0a0f1e 100%);', | |
| 'box-shadow:0 4px 20px rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;', | |
| 'position:relative;transition:transform .18s,box-shadow .18s;outline:none;}', | |
| '.aow-fab:hover{transform:scale(1.08);box-shadow:0 6px 28px rgba(201,183,135,0.35);}', | |
| '.aow-fab:focus-visible{outline:3px solid #c9b787;outline-offset:3px;}', | |
| '.aow-fab img{width:40px;height:40px;border-radius:50%;object-fit:cover;}', | |
| '.aow-fab-fallback{color:#c9b787;font-weight:700;font-size:20px;}', | |
| '.aow-badge{position:absolute;top:-4px;right:-4px;min-width:18px;height:18px;', | |
| 'border-radius:9px;background:#c8415d;color:#fff;font-size:11px;font-weight:700;', | |
| 'display:none;align-items:center;justify-content:center;padding:0 4px;border:2px solid #0a0f1e;}', | |
| '[data-unread="true"] .aow-badge{display:flex;}', | |
| '.aow-panel{position:fixed;bottom:92px;right:24px;width:370px;max-width:calc(100vw - 48px);', | |
| 'height:540px;max-height:calc(100vh - 120px);background:#0c1322;', | |
| 'border:1px solid rgba(201,183,135,0.22);border-radius:16px;display:none;flex-direction:column;', | |
| 'box-shadow:0 8px 40px rgba(0,0,0,0.65);overflow:hidden;}', | |
| '[data-open="true"] .aow-panel{display:flex;}', | |
| '.aow-head{display:flex;align-items:center;gap:10px;padding:12px 14px;', | |
| 'border-bottom:1px solid rgba(201,183,135,0.16);background:rgba(22,32,58,0.6);}', | |
| '.aow-head-avatar{width:34px;height:34px;border-radius:50%;object-fit:cover;flex-shrink:0;}', | |
| '.aow-head-text{display:flex;flex-direction:column;flex:1;min-width:0;}', | |
| '.aow-eyebrow{font-size:9px;letter-spacing:.12em;color:#c9b787;font-weight:700;text-transform:uppercase;}', | |
| '.aow-head-name{font-size:13px;font-weight:600;color:#e6edf6;}', | |
| '.aow-status{display:flex;align-items:center;gap:5px;margin-left:auto;flex-shrink:0;}', | |
| '.aow-status-dot{width:8px;height:8px;border-radius:50%;background:#7d8aa3;transition:background .3s;}', | |
| '[data-state="ok"] .aow-status-dot{background:#4ade80;}', | |
| '[data-state="down"] .aow-status-dot{background:#f87171;}', | |
| '[data-state="unknown"] .aow-status-dot{background:#7d8aa3;}', | |
| '.aow-status-label{font-size:11px;color:#9fb0c8;}', | |
| '.aow-close{background:none;border:none;color:#9fb0c8;font-size:20px;cursor:pointer;', | |
| 'padding:0 4px;line-height:1;margin-left:6px;border-radius:4px;transition:color .15s;}', | |
| '.aow-close:hover{color:#e6edf6;}', | |
| /* Quick action buttons */ | |
| '.aow-quick-actions{display:flex;gap:6px;padding:8px 10px;border-bottom:1px solid rgba(201,183,135,0.12);', | |
| 'background:rgba(12,19,34,0.85);flex-wrap:wrap;}', | |
| '.aow-qa-btn{flex:1;min-width:80px;background:rgba(201,183,135,0.08);border:1px solid rgba(201,183,135,0.22);', | |
| 'color:#cdb98a;font-size:11px;font-family:inherit;border-radius:6px;padding:5px 8px;', | |
| 'cursor:pointer;transition:background .15s,border-color .15s;text-align:center;font-weight:500;', | |
| 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}', | |
| '.aow-qa-btn:hover{background:rgba(201,183,135,0.18);border-color:rgba(201,183,135,0.5);color:#fff;}', | |
| '.aow-qa-btn:active{background:rgba(201,183,135,0.28);}', | |
| '.aow-qa-btn:disabled{opacity:0.4;cursor:not-allowed;}', | |
| '.aow-thread{flex:1;overflow-y:auto;padding:12px 10px;display:flex;flex-direction:column;gap:8px;}', | |
| '.aow-empty{color:#9fb0c8;font-size:13px;text-align:center;padding:20px 6px;}', | |
| '.aow-msg{max-width:90%;padding:8px 12px;border-radius:10px;font-size:13px;word-break:break-word;white-space:pre-wrap;}', | |
| '.aow-msg-user{align-self:flex-end;background:rgba(201,183,135,0.16);color:#e6edf6;border-bottom-right-radius:3px;}', | |
| '.aow-msg-op{align-self:flex-start;background:rgba(22,32,58,0.85);color:#e6edf6;', | |
| 'border-bottom-left-radius:3px;border:1px solid rgba(201,183,135,0.14);}', | |
| '.aow-msg-meta{font-size:10px;color:#9fb0c8;margin-top:4px;font-family:ui-monospace,"JetBrains Mono",monospace;}', | |
| '.aow-typing-wrap{align-self:flex-start;}', | |
| '.aow-typing{display:flex;gap:4px;padding:10px 14px;background:rgba(22,32,58,0.85);', | |
| 'border-radius:10px;border:1px solid rgba(201,183,135,0.14);}', | |
| '.aow-typing span{width:6px;height:6px;border-radius:50%;background:#c9b787;animation:aow-bounce 1.2s infinite ease-in-out;}', | |
| '.aow-typing span:nth-child(2){animation-delay:.2s;}', | |
| '.aow-typing span:nth-child(3){animation-delay:.4s;}', | |
| '@keyframes aow-bounce{0%,80%,100%{transform:translateY(0);}40%{transform:translateY(-6px);}}', | |
| '.aow-form{display:flex;align-items:flex-end;gap:6px;padding:10px;border-top:1px solid rgba(201,183,135,0.14);}', | |
| '.aow-input{flex:1;background:rgba(22,32,58,0.5);border:1px solid rgba(201,183,135,0.22);', | |
| 'border-radius:8px;color:#e6edf6;font-family:inherit;font-size:13px;padding:7px 10px;', | |
| 'resize:none;max-height:90px;outline:none;transition:border-color .15s;}', | |
| '.aow-input:focus{border-color:rgba(201,183,135,0.55);}', | |
| '.aow-input::placeholder{color:#7d8aa3;}', | |
| '.aow-send{background:#c9b787;border:none;border-radius:8px;color:#0a0f1e;', | |
| 'font-weight:700;font-size:16px;width:36px;height:36px;cursor:pointer;flex-shrink:0;', | |
| 'display:flex;align-items:center;justify-content:center;transition:background .15s;}', | |
| '.aow-send:hover{background:#dccca0;}', | |
| '.aow-toasts{position:fixed;bottom:92px;right:24px;display:flex;flex-direction:column;gap:8px;', | |
| 'z-index:2147483646;pointer-events:none;width:330px;max-width:calc(100vw - 48px);}', | |
| '.aow-root[data-open="true"] .aow-toasts{bottom:calc(540px + 100px);}', | |
| '.aow-toast{display:flex;align-items:flex-start;gap:10px;background:#0c1322;', | |
| 'border:1px solid rgba(201,183,135,0.22);border-radius:12px;padding:10px 12px;', | |
| 'box-shadow:0 4px 16px rgba(0,0,0,0.55);pointer-events:all;animation:aow-toast-in .2s ease-out;}', | |
| '@keyframes aow-toast-in{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:none;}}', | |
| '.aow-toast-out{animation:aow-toast-out .2s ease-in forwards;}', | |
| '@keyframes aow-toast-out{to{opacity:0;transform:translateY(10px);}}', | |
| '.aow-toast-avatar{width:28px;height:28px;border-radius:50%;object-fit:cover;flex-shrink:0;}', | |
| '.aow-toast-body{flex:1;min-width:0;}', | |
| '.aow-toast-title{font-size:13px;font-weight:600;color:#e6edf6;}', | |
| '.aow-toast-text{font-size:12px;color:#9fb0c8;margin-top:2px;}', | |
| '.aow-toast-meta{font-size:10px;color:#cdb98a;font-family:ui-monospace,"JetBrains Mono",monospace;margin-top:2px;}', | |
| '.aow-toast-x{background:none;border:none;color:#9fb0c8;cursor:pointer;font-size:16px;padding:0 2px;line-height:1;flex-shrink:0;}', | |
| '[data-kind="receipt"] .aow-toast-title::before{content:"\\1F4DC ";}', | |
| '[data-kind="denial"] .aow-toast-title::before{content:"\\1F6AB ";}', | |
| '[data-kind="build-green"] .aow-toast-title::before{content:"\\2705 ";}', | |
| '[data-kind="build-red"] .aow-toast-title::before{content:"\\274C ";}', | |
| '[data-kind="doctrine"] .aow-toast-title::before{content:"\\26A0\\FE0F ";}', | |
| ].join(''); | |
| var style = document.createElement('style'); | |
| style.textContent = css; | |
| (document.head || document.documentElement).appendChild(style); | |
| // ---- Build the DOM ---- | |
| var root = el('div', { class: 'aow-root', 'data-open': 'false', 'data-unread': 'false' }); | |
| root.setAttribute('data-a11oy-operator', 'widget'); | |
| var fabImg = el('img', { src: PORTRAIT, alt: '', 'aria-hidden': 'true' }); | |
| fabImg.addEventListener('error', function () { | |
| fabImg.style.display = 'none'; | |
| fab.appendChild(el('span', { class: 'aow-fab-fallback', text: 'a11' })); | |
| }); | |
| var badge = el('span', { class: 'aow-badge', 'aria-hidden': 'true' }); | |
| var fab = el('button', { | |
| class: 'aow-fab', type: 'button', | |
| 'aria-label': 'Open the a11oy operator assistant', | |
| 'aria-haspopup': 'dialog', 'aria-expanded': 'false', | |
| }, [fabImg, badge]); | |
| var headAvatar = el('img', { class: 'aow-head-avatar', src: PORTRAIT, alt: '' }); | |
| var statusDot = el('span', { class: 'aow-status-dot' }); | |
| var statusLabel = el('span', { class: 'aow-status-label', text: 'checking' }); | |
| var statusEl = el('span', { class: 'aow-status', 'data-state': 'unknown' }, [statusDot, statusLabel]); | |
| var closeBtn = el('button', { class: 'aow-close', type: 'button', 'aria-label': 'Close the operator assistant', text: '\u00d7' }); | |
| var head = el('div', { class: 'aow-head' }, [ | |
| headAvatar, | |
| el('div', { class: 'aow-head-text' }, [ | |
| el('span', { class: 'aow-eyebrow', text: 'a11oy operator' }), | |
| el('span', { class: 'aow-head-name', text: 'Chaski \u2014 governed agent surface' }), | |
| ]), | |
| statusEl, | |
| closeBtn, | |
| ]); | |
| // Quick action buttons — wired to REAL live endpoints | |
| var qaReceipt = el('button', { | |
| class: 'aow-qa-btn', type: 'button', title: 'Show the latest receipts from the ' + CFG.organ + ' v4 ledger', | |
| text: '\uD83D\uDCDC Receipts', | |
| onclick: function () { open(); quickAction('receipt'); } | |
| }); | |
| var qaVerify = el('button', { | |
| class: 'aow-qa-btn', type: 'button', title: 'Check substrate health + receipt-chain head', | |
| text: '\uD83D\uDD10 Verify', | |
| onclick: function () { open(); quickAction('verify'); } | |
| }); | |
| var qaAgent = el('button', { | |
| class: 'aow-qa-btn', type: 'button', title: 'Run the governed Chaski agentic loop on a task', | |
| text: '\u26A1 Agent', | |
| onclick: function () { open(); quickAction('agent'); } | |
| }); | |
| var qaAsk = el('button', { | |
| class: 'aow-qa-btn', type: 'button', title: 'Ask a11oy to explain this surface (governed reasoning)', | |
| text: '\uD83E\uDDE0 Ask a11oy', | |
| onclick: function () { open(); quickAction('ask'); } | |
| }); | |
| var quickActions = el('div', { class: 'aow-quick-actions' }, [qaReceipt, qaVerify, qaAgent, qaAsk]); | |
| var threadEl = el('div', { class: 'aow-thread', role: 'log', 'aria-live': 'polite', 'aria-label': 'Conversation with the a11oy operator' }); | |
| var input = el('textarea', { | |
| class: 'aow-input', rows: '1', placeholder: 'Ask the a11oy substrate\u2026', | |
| 'aria-label': 'Message to the a11oy operator', | |
| }); | |
| var sendBtn = el('button', { class: 'aow-send', type: 'submit', 'aria-label': 'Send', html: '➤' }); | |
| var form = el('form', { class: 'aow-form' }, [input, sendBtn]); | |
| var panel = el('div', { class: 'aow-panel', role: 'dialog', 'aria-label': 'a11oy operator assistant', 'aria-modal': 'false' }, | |
| [head, quickActions, threadEl, form]); | |
| var toasts = el('div', { class: 'aow-toasts', 'aria-live': 'polite', 'aria-label': 'a11oy operator notifications' }); | |
| root.appendChild(toasts); | |
| root.appendChild(panel); | |
| root.appendChild(fab); | |
| function mount() { | |
| if (!document.body) { document.addEventListener('DOMContentLoaded', mount); return; } | |
| document.body.appendChild(root); | |
| renderThread(); | |
| refreshBadge(); | |
| checkStatus(); | |
| } | |
| mount(); | |
| // ---- Open / close ---- | |
| var isOpen = false; | |
| function open() { | |
| isOpen = true; | |
| root.setAttribute('data-open', 'true'); | |
| fab.setAttribute('aria-expanded', 'true'); | |
| state.unread = 0; persist(); refreshBadge(); | |
| if (!thread.length) renderThread(); | |
| setTimeout(function () { input.focus(); }, 60); | |
| scrollThread(); | |
| } | |
| function close() { | |
| isOpen = false; | |
| root.setAttribute('data-open', 'false'); | |
| fab.setAttribute('aria-expanded', 'false'); | |
| fab.focus(); | |
| } | |
| function toggle() { isOpen ? close() : open(); } | |
| fab.addEventListener('click', toggle); | |
| closeBtn.addEventListener('click', close); | |
| document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && isOpen) close(); }); | |
| // ---- Thread rendering ---- | |
| function refreshBadge() { | |
| root.setAttribute('data-unread', state.unread > 0 ? 'true' : 'false'); | |
| badge.textContent = state.unread > 9 ? '9+' : String(state.unread || ''); | |
| } | |
| function scrollThread() { requestAnimationFrame(function () { threadEl.scrollTop = threadEl.scrollHeight; }); } | |
| function renderThread() { | |
| threadEl.innerHTML = ''; | |
| if (!thread.length) { | |
| var em = el('div', { class: 'aow-empty' }); | |
| em.innerHTML = | |
| '<strong>a11oy operator \u2014 Chaski</strong><br>' + | |
| 'Ask the substrate anything, or use the buttons above. I route to the live ' + | |
| esc(CFG.organ) + ' backend and stay honest when it is unreachable \u2014 I never fabricate.' + | |
| '<br><br><small style="color:#6b7a96">Surface: <em>' + esc(CFG.surface) + '</em> \u2022 organ: <em>' + esc(CFG.organ) + '</em></small>'; | |
| threadEl.appendChild(em); | |
| return; | |
| } | |
| thread.forEach(function (m) { threadEl.appendChild(renderMsg(m)); }); | |
| scrollThread(); | |
| } | |
| function renderMsg(m) { | |
| var cls = m.role === 'user' ? 'aow-msg aow-msg-user' : 'aow-msg aow-msg-op'; | |
| var node = el('div', { class: cls }); | |
| node.innerHTML = esc(m.text).replace(/\n/g, '<br>'); | |
| if (m.meta) node.appendChild(el('div', { class: 'aow-msg-meta', text: m.meta })); | |
| if (m.dsse) { | |
| var dsseNode = el('div', { class: 'aow-msg-meta' }); | |
| dsseNode.innerHTML = '<strong>receipt:</strong> ' + esc(m.dsse); | |
| node.appendChild(dsseNode); | |
| } | |
| return node; | |
| } | |
| function pushMsg(role, text, meta, dsse) { | |
| var m = { role: role, text: text, meta: meta || null, ts: Date.now(), dsse: dsse || null }; | |
| thread.push(m); persistThread(); | |
| if (threadEl.querySelector('.aow-empty')) threadEl.innerHTML = ''; | |
| threadEl.appendChild(renderMsg(m)); | |
| scrollThread(); | |
| return m; | |
| } | |
| function showTyping() { | |
| var t = el('div', { class: 'aow-msg aow-msg-op aow-typing-wrap' }, [ | |
| el('div', { class: 'aow-typing' }, [el('span'), el('span'), el('span')]), | |
| ]); | |
| threadEl.appendChild(t); scrollThread(); | |
| return t; | |
| } | |
| // Append into an existing op message node (used for streaming). | |
| function appendStream(node, chunk) { | |
| node.dataset.acc = (node.dataset.acc || '') + chunk; | |
| node.innerHTML = esc(node.dataset.acc).replace(/\n/g, '<br>'); | |
| scrollThread(); | |
| } | |
| // ---- Endpoint helpers (REAL live contract) ---- | |
| function codeUrl(path) { return CFG.codeBase + '/api/a11oy/code' + path; } // chat/agent/health live on a11oy organ | |
| function v4Url(path) { return CFG.base + '/api/' + CFG.apiOrgan + '/v4' + path; } // ledger/inbox on host organ | |
| function healthUrl() { return CFG.base + '/healthz'; } | |
| // ---- Backend status (honest) ---- | |
| function checkStatus() { | |
| fetchJson(healthUrl(), { method: 'GET' }, 5000) | |
| .then(function () { setStatus('ok', CFG.organ + ' online'); }) | |
| .catch(function () { setStatus('down', CFG.organ + ' unreachable'); }); | |
| } | |
| function setStatus(s, label) { | |
| state.status = s; | |
| statusEl.setAttribute('data-state', s); | |
| statusLabel.textContent = label; | |
| } | |
| // ---- Networking ---- | |
| function fetchJson(url, opts, timeoutMs) { | |
| opts = opts || {}; | |
| var ctrl = typeof AbortController !== 'undefined' ? new AbortController() : null; | |
| if (ctrl) opts.signal = ctrl.signal; | |
| opts.headers = Object.assign({ 'Content-Type': 'application/json' }, opts.headers || {}); | |
| var timer = ctrl ? setTimeout(function () { ctrl.abort(); }, timeoutMs || 12000) : null; | |
| return fetch(url, opts).then(function (r) { | |
| if (timer) clearTimeout(timer); | |
| if (!r.ok) { var e = new Error('HTTP ' + r.status); e.status = r.status; throw e; } | |
| var ct = r.headers.get('content-type') || ''; | |
| return ct.indexOf('application/json') >= 0 ? r.json() : r.text(); | |
| }).catch(function (e) { if (timer) clearTimeout(timer); throw e; }); | |
| } | |
| // Stream a Server-Sent-Events POST endpoint, calling onEvent({event,data}) per frame. | |
| function streamSSE(url, body, onEvent, timeoutMs) { | |
| var ctrl = typeof AbortController !== 'undefined' ? new AbortController() : null; | |
| var timer = ctrl ? setTimeout(function () { ctrl.abort(); }, timeoutMs || 90000) : null; | |
| return fetch(url, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' }, | |
| body: JSON.stringify(body), | |
| signal: ctrl ? ctrl.signal : undefined, | |
| }).then(function (r) { | |
| if (!r.ok) { if (timer) clearTimeout(timer); var e = new Error('HTTP ' + r.status); e.status = r.status; throw e; } | |
| if (!r.body || !r.body.getReader) { | |
| // No streaming support — read whole body and emit one event. | |
| return r.text().then(function (txt) { if (timer) clearTimeout(timer); parseSSEBlock(txt, onEvent); }); | |
| } | |
| var reader = r.body.getReader(); | |
| var dec = new TextDecoder(); | |
| var buf = ''; | |
| function pump() { | |
| return reader.read().then(function (res) { | |
| if (res.done) { if (timer) clearTimeout(timer); if (buf.trim()) parseSSEBlock(buf, onEvent); return; } | |
| buf += dec.decode(res.value, { stream: true }); | |
| var parts = buf.split('\n\n'); | |
| buf = parts.pop(); | |
| parts.forEach(function (p) { parseSSEFrame(p, onEvent); }); | |
| return pump(); | |
| }); | |
| } | |
| return pump(); | |
| }).catch(function (e) { if (timer) clearTimeout(timer); throw e; }); | |
| } | |
| function parseSSEFrame(frame, onEvent) { | |
| var ev = 'message', data = ''; | |
| frame.split('\n').forEach(function (line) { | |
| if (line.indexOf('event:') === 0) ev = line.slice(6).trim(); | |
| else if (line.indexOf('data:') === 0) data += line.slice(5).trim(); | |
| }); | |
| if (data) { var parsed; try { parsed = JSON.parse(data); } catch (e) { parsed = data; } onEvent({ event: ev, data: parsed }); } | |
| } | |
| function parseSSEBlock(txt, onEvent) { txt.split('\n\n').forEach(function (f) { if (f.trim()) parseSSEFrame(f, onEvent); }); } | |
| // ---- Quick Actions (REAL endpoints) ---- | |
| function disableQA(d) { [qaReceipt, qaVerify, qaAgent, qaAsk].forEach(function (b) { b.disabled = d; }); } | |
| function quickAction(kind) { | |
| if (kind === 'receipt') { | |
| pushMsg('user', '\uD83D\uDCDC Latest receipts'); | |
| disableQA(true); | |
| var typing = showTyping(); | |
| fetchJson(v4Url('/receipts?limit=10'), { method: 'GET' }, 14000) | |
| .then(function (data) { | |
| typing.remove(); disableQA(false); | |
| var items = data.receipts || data.items || (Array.isArray(data) ? data : []); | |
| if (!items.length) { | |
| pushMsg('op', 'The ' + CFG.organ + ' v4 ledger is currently empty \u2014 no receipts minted yet. ' + | |
| (data.note ? '\n\nNote from the substrate: ' + data.note : '') + '\n\nThat is an honest idle state, not an error.'); | |
| } else { | |
| var top = items[0]; | |
| var hash = top.hash || top.receipt_id || top.id || top.digest; | |
| var msg = items.length + ' receipt(s) in the chain.'; | |
| if (data.total != null) msg += '\nHead total: ' + data.total; | |
| if (top.action) msg += '\nMost recent action: ' + top.action; | |
| if (top.timestamp_utc || top.ts) msg += '\nTime: ' + (top.timestamp_utc || top.ts); | |
| if (data.note) msg += '\n\n' + data.note; | |
| pushMsg('op', msg, hash ? 'receipt ' + hash : null); | |
| } | |
| setStatus('ok', 'ledger \u2713'); | |
| }) | |
| .catch(function (err) { typing.remove(); disableQA(false); handleNetErr(err); }); | |
| } else if (kind === 'verify') { | |
| pushMsg('user', '\uD83D\uDD10 Verify substrate + chain head'); | |
| disableQA(true); | |
| var typing2 = showTyping(); | |
| fetchJson(healthUrl(), { method: 'GET' }, 8000) | |
| .then(function (h) { | |
| var lock = (h && (h.lock || h.counts)) || '?'; | |
| var commit = (h && h.commit) || (h && h.lean_sha) || '?'; | |
| return fetchJson(v4Url('/receipts?limit=1'), { method: 'GET' }, 10000) | |
| .then(function (led) { | |
| typing2.remove(); disableQA(false); | |
| var items = led.receipts || led.items || []; | |
| var head = items.length ? (items[0].hash || items[0].receipt_id || items[0].id) : null; | |
| var msg = CFG.organ + ' substrate is online.\nKernel lock: ' + lock + '\nLean commit: ' + commit + | |
| '\nLocked-proven formulas: 8 {F1,F4,F7,F11,F12,F18,F19,F22} \u2014 machine-enforced.'; | |
| if (head) msg += '\nReceipt-chain head: present.'; | |
| else msg += '\nReceipt chain: empty (honest idle).'; | |
| if (led.note) msg += '\n\n' + led.note; | |
| pushMsg('op', msg, head ? 'head ' + head : null); | |
| setStatus('ok', 'chain \u2713'); | |
| }); | |
| }) | |
| .catch(function (err) { typing2.remove(); disableQA(false); handleNetErr(err); }); | |
| } else if (kind === 'agent') { | |
| var task = 'Summarise the governance posture of the ' + CFG.surface + ' surface: what is proven, what is experimental, what is roadmap. Be honest and cite.'; | |
| pushMsg('user', '\u26A1 Run Chaski agent: ' + task); | |
| disableQA(true); | |
| runAgent(task, function () { disableQA(false); }); | |
| } else if (kind === 'ask') { | |
| var q = 'In two or three sentences, what is the a11oy substrate and what does it govern? What runs on this surface (' + CFG.surface + ')?'; | |
| pushMsg('user', '\uD83E\uDDE0 ' + q); | |
| disableQA(true); | |
| runChat(q, function () { disableQA(false); }); | |
| } | |
| } | |
| // ---- Governed chat (live SSE, real LLM router) ---- | |
| function runChat(q, doneCb) { | |
| var typing = showTyping(); | |
| var opNode = null, routeMeta = null; | |
| streamSSE(codeUrl('/chat/stream'), { message: q, thread: CFG.surface, surface: CFG.surface }, | |
| function (ev) { | |
| if (ev.event === 'route' && ev.data && typeof ev.data === 'object') { | |
| routeMeta = 'router: ' + (ev.data.tier || '?') + ' \u00b7 ' + (ev.data.model || '?') + | |
| (ev.data.license_class ? ' \u00b7 ' + ev.data.license_class : ''); | |
| } else if (ev.event === 'token' || ev.event === 'delta' || ev.event === 'message') { | |
| if (typing) { typing.remove(); typing = null; } | |
| if (!opNode) { opNode = pushMsg('op', '') ; opNode = threadEl.lastChild; } | |
| var chunk = (ev.data && (ev.data.text || ev.data.token || ev.data.delta || ev.data.content)) || (typeof ev.data === 'string' ? ev.data : ''); | |
| if (chunk) appendStream(opNode, chunk); | |
| } else if (ev.event === 'done' || ev.event === 'final' || ev.event === 'end') { | |
| if (typing) { typing.remove(); typing = null; } | |
| if (ev.data && ev.data.text && opNode && !opNode.dataset.acc) appendStream(opNode, ev.data.text); | |
| if (opNode && routeMeta) opNode.appendChild(el('div', { class: 'aow-msg-meta', text: routeMeta })); | |
| // persist final accumulated text into thread store | |
| if (opNode && opNode.dataset.acc) { thread[thread.length - 1] = { role: 'op', text: opNode.dataset.acc, meta: routeMeta, ts: Date.now() }; persistThread(); } | |
| setStatus('ok', CFG.organ + ' online'); | |
| } else if (ev.event === 'error') { | |
| if (typing) { typing.remove(); typing = null; } | |
| pushMsg('op', 'The substrate reported an error: ' + (ev.data && ev.data.error ? ev.data.error : 'unknown') + '. I will not guess what it meant.'); | |
| } | |
| }, 90000) | |
| .then(function () { if (typing) { typing.remove(); } if (doneCb) doneCb(); }) | |
| .catch(function (err) { if (typing) typing.remove(); handleNetErr(err); if (doneCb) doneCb(); }); | |
| } | |
| // ---- Governed agentic loop (live SSE FSM: INTAKE..FINALIZE) ---- | |
| function runAgent(task, doneCb) { | |
| var typing = showTyping(); | |
| var stepNode = null, finalNode = null; | |
| streamSSE(codeUrl('/agent/stream'), { task: task, thread: CFG.surface, surface: CFG.surface }, | |
| function (ev) { | |
| var d = ev.data || {}; | |
| if (ev.event === 'state' || d.state) { | |
| if (typing) { typing.remove(); typing = null; } | |
| var st = d.state || ev.event; | |
| if (!stepNode) stepNode = pushMsg('op', ''); | |
| var line = '\u2022 ' + st + (d.note ? ': ' + d.note : (d.summary ? ': ' + d.summary : '')); | |
| var node = threadEl.lastChild; | |
| node.dataset.acc = (node.dataset.acc ? node.dataset.acc + '\n' : '') + line; | |
| node.innerHTML = esc(node.dataset.acc).replace(/\n/g, '<br>'); | |
| scrollThread(); | |
| } else if (ev.event === 'token' || ev.event === 'delta') { | |
| if (typing) { typing.remove(); typing = null; } | |
| if (!finalNode) { pushMsg('op', ''); finalNode = threadEl.lastChild; } | |
| var chunk = d.text || d.token || d.delta || (typeof ev.data === 'string' ? ev.data : ''); | |
| if (chunk) appendStream(finalNode, chunk); | |
| } else if (ev.event === 'final' || ev.event === 'done' || ev.event === 'finalize') { | |
| if (typing) { typing.remove(); typing = null; } | |
| var ans = d.answer || d.text || d.result || d.output; | |
| var meta = (d.chain_verified != null ? 'chain_verified=' + d.chain_verified : null); | |
| if (ans) pushMsg('op', String(ans), meta); | |
| else if (meta) pushMsg('op', 'Agent run complete.', meta); | |
| setStatus('ok', CFG.organ + ' online'); | |
| } else if (ev.event === 'error') { | |
| if (typing) { typing.remove(); typing = null; } | |
| pushMsg('op', 'The agent loop reported an error: ' + (d.error || 'unknown') + '. Honest stop \u2014 no fabricated result.'); | |
| } | |
| }, 120000) | |
| .then(function () { if (typing) typing.remove(); if (doneCb) doneCb(); }) | |
| .catch(function (err) { if (typing) typing.remove(); handleNetErr(err); if (doneCb) doneCb(); }); | |
| } | |
| function handleNetErr(err) { | |
| if (err && err.status) { | |
| pushMsg('op', 'The ' + CFG.organ + ' substrate answered with an error (' + err.status + '). I will not guess what it meant \u2014 try rephrasing, or check the service.'); | |
| } else { | |
| pushMsg('op', 'I cannot reach the ' + CFG.organ + ' substrate right now, so I will not make up an answer. Ask me again in a moment.'); | |
| setStatus('down', CFG.organ + ' unreachable'); | |
| } | |
| } | |
| // ---- Free-text routing: chat by default; explicit agent intent -> agent loop ---- | |
| function ask(q) { | |
| pushMsg('user', q); | |
| var s = q.toLowerCase(); | |
| if (/\b(agent|act|do|run|build|fix|investigate|plan and execute|carry out)\b/.test(s)) { | |
| runAgent(q); | |
| } else { | |
| runChat(q); | |
| } | |
| } | |
| function safeJson(o) { | |
| try { var s = JSON.stringify(o, null, 2); return s.length > 800 ? s.slice(0, 800) + '\u2026' : s; } | |
| catch (e) { return String(o); } | |
| } | |
| // ---- Input handling ---- | |
| form.addEventListener('submit', function (e) { | |
| e.preventDefault(); | |
| var q = input.value.trim(); | |
| if (!q) return; | |
| input.value = ''; input.style.height = 'auto'; | |
| ask(q); | |
| }); | |
| input.addEventListener('input', function () { | |
| input.style.height = 'auto'; | |
| input.style.height = Math.min(input.scrollHeight, 90) + 'px'; | |
| }); | |
| input.addEventListener('keydown', function (e) { | |
| if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); form.requestSubmit ? form.requestSubmit() : form.dispatchEvent(new Event('submit', { cancelable: true })); } | |
| }); | |
| // ---- Notifications API ---- | |
| function notify(opts) { | |
| opts = opts || {}; | |
| var kind = opts.kind || 'info'; | |
| var avatar = el('img', { class: 'aow-toast-avatar', src: PORTRAIT, alt: '' }); | |
| var body = el('div', { class: 'aow-toast-body' }); | |
| body.appendChild(el('div', { class: 'aow-toast-title', html: '<strong>' + esc(opts.title || 'a11oy operator') + '</strong>' })); | |
| if (opts.body) body.appendChild(el('div', { class: 'aow-toast-text', text: opts.body })); | |
| if (opts.meta) body.appendChild(el('div', { class: 'aow-toast-meta', text: opts.meta })); | |
| var toast; | |
| function dismiss() { | |
| if (!toast) return; | |
| toast.classList.add('aow-toast-out'); | |
| setTimeout(function () { if (toast && toast.parentNode) toast.parentNode.removeChild(toast); }, 220); | |
| } | |
| var x = el('button', { class: 'aow-toast-x', type: 'button', 'aria-label': 'Dismiss', text: '\u00d7', onclick: dismiss }); | |
| toast = el('div', { class: 'aow-toast', 'data-kind': kind, role: 'status' }, [avatar, body, x]); | |
| toasts.appendChild(toast); | |
| pushMsg('op', (opts.title ? opts.title + (opts.body ? ' \u2014 ' + opts.body : '') : opts.body || ''), opts.meta); | |
| if (!isOpen) { state.unread = (state.unread || 0) + 1; persist(); refreshBadge(); } | |
| if (!opts.sticky) setTimeout(dismiss, opts.duration || 8000); | |
| return { dismiss: dismiss }; | |
| } | |
| // ---- Public API ---- | |
| var api = { | |
| open: open, close: close, toggle: toggle, | |
| ask: function (q) { open(); ask(q); }, | |
| agent: function (task) { open(); runAgent(task); }, | |
| chat: function (q) { open(); runChat(q); }, | |
| notify: notify, | |
| config: CFG, | |
| receiptMinted: function (hash, what) { | |
| return notify({ kind: 'receipt', title: 'New receipt minted', body: what || 'A receipt was added to the ledger.', meta: hash ? 'receipt ' + hash : null }); | |
| }, | |
| gateDenied: function (gate, what) { | |
| return notify({ kind: 'denial', title: 'Policy denied an action', body: what || 'A proposed action was rejected by the gates.', meta: gate ? 'gate ' + gate : null }); | |
| }, | |
| build: function (green, detail) { | |
| return notify({ kind: green ? 'build-green' : 'build-red', title: green ? 'Build is green' : 'Build is red', body: detail || '' }); | |
| }, | |
| doctrineDrift: function (detail) { | |
| return notify({ kind: 'doctrine', title: 'Doctrine drift detected', body: detail || 'Something diverged from doctrine. Worth a look.' }); | |
| }, | |
| }; | |
| window.A11oyOperator = api; | |
| // Back-compat alias for any existing caller (NOT a user-visible name). | |
| window.Rosie = api; | |
| })(); | |