roboticus-api 0.11.3

HTTP routes, WebSocket, auth, rate limiting, and dashboard for the Roboticus agent runtime
Documentation
  // --- Help modal (wiki + hints toggle) ---
  (function bindHelpModal() {
    var wikiA = document.getElementById('sidebar-help-wiki-link');
    if (wikiA) wikiA.setAttribute('href', HELP_WIKI_URL);
    function openHelpModal() {
      var ov = document.getElementById('sidebar-help-overlay');
      var cb = document.getElementById('modal-hints-toggle');
      if (cb) cb.checked = hintsEnabled();
      if (ov) {
        ov.style.display = 'flex';
        ov.setAttribute('aria-hidden', 'false');
      }
      var closeBtn = document.getElementById('sidebar-help-close');
      if (closeBtn) closeBtn.focus();
    }
    function closeHelpModal() {
      var ov = document.getElementById('sidebar-help-overlay');
      if (ov) {
        ov.style.display = 'none';
        ov.setAttribute('aria-hidden', 'true');
      }
    }
    var sbOpen = document.getElementById('sidebar-help-open');
    var hdOpen = document.getElementById('header-help-open');
    if (sbOpen) sbOpen.addEventListener('click', openHelpModal);
    if (hdOpen) hdOpen.addEventListener('click', openHelpModal);
    var closeEl = document.getElementById('sidebar-help-close');
    if (closeEl) closeEl.addEventListener('click', closeHelpModal);
    var helpOv = document.getElementById('sidebar-help-overlay');
    if (helpOv) {
      helpOv.addEventListener('click', function(ev) {
        if (ev.target === helpOv) closeHelpModal();
      });
    }
    var helpDlg = document.getElementById('sidebar-help-dialog');
    if (helpDlg) {
      helpDlg.addEventListener('click', function(ev) { ev.stopPropagation(); });
    }
    document.addEventListener('keydown', function(ev) {
      if (ev.key !== 'Escape') return;
      var ov = document.getElementById('sidebar-help-overlay');
      if (ov && ov.style.display === 'flex') {
        ev.preventDefault();
        closeHelpModal();
      }
    });
    var modalHints = document.getElementById('modal-hints-toggle');
    if (modalHints) {
      modalHints.addEventListener('change', function() {
        var enabled = !!modalHints.checked;
        if (enabled) { setHintsEnabled(true); } else { disableAndClearHints(); }
        try { window.localStorage.setItem(HINTS_PROMPTED_KEY, '1'); } catch (_) {}
        toast(enabled ? 'Hints enabled' : 'Hints disabled');
        if (App && App.page) App.navigate(App.page);
      });
    }
  })();

  // --- JSON syntax highlighter ---
  function highlightJson(text) {
    // Escape HTML first
    var h = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    // Highlight JSON tokens
    h = h.replace(/"([^"\\]|\\.)*"\s*:/g, function(m) {
      return '<span class="jh-key">' + m + '</span>';
    });
    h = h.replace(/"([^"\\]|\\.)*"/g, function(m) {
      return '<span class="jh-str">' + m + '</span>';
    });
    h = h.replace(/\b(-?\d+\.?\d*([eE][+-]?\d+)?)\b/g, '<span class="jh-num">$1</span>');
    h = h.replace(/\b(true|false)\b/g, '<span class="jh-bool">$1</span>');
    h = h.replace(/\bnull\b/g, '<span class="jh-null">null</span>');
    h = h.replace(/([{}[\]])/g, '<span class="jh-brace">$1</span>');
    return h;
  }

  function highlightToml(text) {
    function highlightTomlValue(rawValue) {
      var placeholders = [];
      var escaped = String(rawValue || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
      escaped = escaped.replace(/"([^"\\]|\\.)*"/g, function(m) {
        var idx = placeholders.length;
        placeholders.push('<span class="jh-str">' + m + '</span>');
        return '___TOML_STR_' + idx + '___';
      });
      escaped = escaped.replace(/\b(true|false)\b/g, '<span class="jh-bool">$1</span>');
      escaped = escaped.replace(/\b(-?\d+\.?\d*([eE][+-]?\d+)?)\b/g, '<span class="jh-num">$1</span>');
      escaped = escaped.replace(/([\[\]\{\}])/g, '<span class="jh-brace">$1</span>');
      return escaped.replace(/___TOML_STR_(\d+)___/g, function(_, idx) {
        return placeholders[Number(idx)] || '';
      });
    }

    var lines = String(text || '').split('\n');
    return lines.map(function(line) {
      if (/^\s*#/.test(line)) {
        return '<span class="jh-comment">' + line.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</span>';
      }
      if (/^\s*\[\[?[^\]]+\]\]?\s*$/.test(line)) {
        return '<span class="jh-section">' + line.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</span>';
      }
      var match = line.match(/^(\s*)([A-Za-z0-9_.-]+)(\s*=\s*)(.*)$/);
      if (!match) {
        return highlightTomlValue(line);
      }
      var leading = match[1].replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
      var key = match[2].replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
      var sep = match[3].replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
      var value = highlightTomlValue(match[4]);
      return leading + '<span class="jh-key">' + key + '</span>' + sep + value;
    }).join('\n');
  }

  function syncJsonHighlight() {
    var ta = document.getElementById('settings-json-editor');
    var pre = document.getElementById('json-highlight');
    if (!ta || !pre) return;
    pre.innerHTML = highlightJson(ta.value) + '\n';
    pre.scrollTop = ta.scrollTop;
    pre.scrollLeft = ta.scrollLeft;
  }

  function syncRawHighlight() {
    var ta = document.getElementById('settings-raw-editor');
    var pre = document.getElementById('settings-raw-highlight');
    if (!ta || !pre) return;
    pre.innerHTML = highlightToml(ta.value) + '\n'; // eslint-disable-line -- operator's own TOML, not untrusted input
    // Stretch textarea to match pre's content height so the caret
    // tracks correctly within the wrapper's scroll container.
    ta.style.height = pre.scrollHeight + 'px';
  }

  function syncSettingsHighlight() {
    if (document.getElementById('settings-raw-editor')) {
      syncRawHighlight();
      return;
    }
    syncJsonHighlight();
  }

  // Hook scroll sync for json editor (non-overlay editors only;
  // the TOML overlay editor scrolls via the wrapper container).
  if (content) content.addEventListener('scroll', function(e) {
    if (e.target.id === 'settings-json-editor') {
      var pre = document.getElementById('json-highlight');
      if (pre) { pre.scrollTop = e.target.scrollTop; pre.scrollLeft = e.target.scrollLeft; }
    }
  }, true);

  function onHash() {
    var hash = (window.location.hash || '#overview').slice(1) || 'overview';
    if (hash === 'roster') { hash = 'agents'; App._agentsTab = 'roster'; window.location.hash = 'agents'; return; }
    if (hash === 'observability') { hash = 'metrics'; window.location.hash = 'metrics'; return; }
    if (pages.indexOf(hash) === -1) hash = 'overview';
    ensureAuth().then(function() {
      refreshSidebarIdentity();
      App.navigate(hash);
    });
  }

  document.querySelectorAll('.sidebar-nav a, .mobile-nav a').forEach(function(a) {
    a.addEventListener('click', function(ev) {
      ev.preventDefault();
      var p = a.getAttribute('data-page');
      if (p === 'efficiency') App._effTab = 'performance';
      if (p) window.location.hash = p;
      var sb = document.getElementById('sidebar');
      if (sb && window.innerWidth <= 768) sb.classList.remove('open');
    });
  });