bloby-bot 0.53.5 → 0.53.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bloby-bot",
3
- "version": "0.53.5",
3
+ "version": "0.53.6",
4
4
  "releaseNotes": [
5
5
  "1. New Morphy animation system: config-driven sprites loaded from /morphy/*.json",
6
6
  "2. Swapped teleporting (splash) and headphones (bubble + chat) to the new format",
@@ -530,6 +530,15 @@ export async function startSupervisor() {
530
530
  return;
531
531
  }
532
532
 
533
+ // Workspace guard — injected by the supervisor into the dashboard HTML (see the Vite proxy
534
+ // below). Auto-reloads into the "backend down" interstitial when the backend gives up, and
535
+ // replaces Vite's raw error overlay with a friendly one. Served here so it's always current.
536
+ if (req.url === '/bloby/workspace-guard.js') {
537
+ res.writeHead(200, { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-cache' });
538
+ res.end(fs.readFileSync(path.join(PKG_DIR, 'supervisor', 'workspace-guard.js')));
539
+ return;
540
+ }
541
+
533
542
  // Service worker — served from embedded constant (supervisor/ is always updated)
534
543
  if (req.url === '/sw.js' || req.url === '/bloby/sw.js') {
535
544
  res.writeHead(200, { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-cache' });
@@ -1798,11 +1807,37 @@ mint();
1798
1807
 
1799
1808
  // Everything else → proxy to dashboard Vite dev server
1800
1809
  console.log(`[supervisor] → dashboard Vite :${vitePorts.dashboard} | ${req.method} ${(req.url || '').split('?')[0]}`);
1810
+ const GUARD_TAG = '<script defer src="/bloby/workspace-guard.js"></script>';
1801
1811
  const proxy = http.request(
1802
1812
  { host: '127.0.0.1', port: vitePorts.dashboard, path: req.url, method: req.method, headers: req.headers },
1803
1813
  (proxyRes) => {
1804
- res.writeHead(proxyRes.statusCode!, proxyRes.headers);
1805
- proxyRes.pipe(res);
1814
+ const ct = String(proxyRes.headers['content-type'] || '');
1815
+ const enc = String(proxyRes.headers['content-encoding'] || '');
1816
+ // Inject the workspace guard into dashboard HTML *documents* only (and only when
1817
+ // uncompressed — Vite dev serves plain HTML). Assets, HMR, JSON, etc. stream through
1818
+ // untouched. The guard auto-reloads into the "backend down" interstitial and replaces
1819
+ // Vite's raw error overlay — supervisor-side so it reaches every workspace, no edits needed.
1820
+ if (!ct.includes('text/html') || enc) {
1821
+ res.writeHead(proxyRes.statusCode!, proxyRes.headers);
1822
+ proxyRes.pipe(res);
1823
+ return;
1824
+ }
1825
+ const chunks: Buffer[] = [];
1826
+ proxyRes.on('data', (c: Buffer) => chunks.push(c));
1827
+ proxyRes.on('end', () => {
1828
+ let html = Buffer.concat(chunks).toString('utf-8');
1829
+ if (!html.includes('workspace-guard.js')) {
1830
+ html = html.includes('</head>') ? html.replace('</head>', GUARD_TAG + '</head>') : GUARD_TAG + html;
1831
+ }
1832
+ const headers = { ...proxyRes.headers };
1833
+ // Body length changed — drop both framing headers and let Node set content-length
1834
+ // from the single res.end() write.
1835
+ delete headers['content-length'];
1836
+ delete headers['transfer-encoding'];
1837
+ res.writeHead(proxyRes.statusCode!, headers);
1838
+ res.end(html);
1839
+ });
1840
+ proxyRes.on('error', () => { try { res.destroy(); } catch {} });
1806
1841
  },
1807
1842
  );
1808
1843
  proxy.on('error', (e) => {
@@ -0,0 +1,103 @@
1
+ (function () {
2
+ // Injected by the supervisor into the workspace dashboard HTML (and ONLY there — never the
3
+ // chat PWA or the supervisor's own interstitials). Two jobs, both aimed at non-technical users:
4
+ //
5
+ // 1. Backend-down auto-detect — when the workspace backend crash-loops and gives up, the
6
+ // supervisor serves a full "backend down" interstitial on the next navigation. Poll for
7
+ // that state and reload automatically so the user doesn't have to press F5 to discover
8
+ // their app's server broke. We reload ONLY on the terminal "dead" (gave-up) state, never
9
+ // during a normal 1-2s restart (which reports alive:false, dead:false).
10
+ //
11
+ // 2. Friendly frontend-error overlay — Vite's dev server throws a raw red error box on a
12
+ // compile/import error, covering the whole screen (even the chat). We hide Vite's overlay
13
+ // and show a friendly one instead: the error text behind a "copy" button + a pointer to
14
+ // the chat. It auto-clears when the agent fixes the error (Vite removes its overlay).
15
+ if (window.__blobyWorkspaceGuard) return;
16
+ window.__blobyWorkspaceGuard = true;
17
+
18
+ /* ── 1. Backend-down auto-detect ──────────────────────────────────────── */
19
+ var reloading = false;
20
+ function pollBackend() {
21
+ fetch('/__bloby/backend-status', { cache: 'no-store' })
22
+ .then(function (r) { return r.json(); })
23
+ .then(function (s) { if (s && s.dead && !reloading) { reloading = true; location.reload(); } })
24
+ .catch(function () {});
25
+ }
26
+ setInterval(pollBackend, 4000);
27
+ pollBackend();
28
+
29
+ /* ── 2. Friendly frontend-error overlay ───────────────────────────────── */
30
+ // Hide Vite's built-in overlay (kept enabled so we can read the error text from its shadow DOM).
31
+ var hideStyle = document.createElement('style');
32
+ hideStyle.textContent = 'vite-error-overlay{display:none!important}';
33
+ (document.head || document.documentElement).appendChild(hideStyle);
34
+
35
+ var overlay = null;
36
+ var dismissed = false;
37
+ var lastErr = '';
38
+
39
+ function readViteError() {
40
+ var ov = document.querySelector('vite-error-overlay');
41
+ if (!ov || !ov.shadowRoot) return '';
42
+ var sr = ov.shadowRoot, parts = [];
43
+ ['.message-body', '.file', '.frame'].forEach(function (sel) {
44
+ var el = sr.querySelector(sel);
45
+ if (el && el.textContent && el.textContent.trim()) parts.push(el.textContent.trim());
46
+ });
47
+ return parts.join('\n\n');
48
+ }
49
+
50
+ function buildOverlay() {
51
+ var d = document.createElement('div');
52
+ d.id = '__bloby_fe_error';
53
+ // z-index 99990: below the chat widget (99998/99999) so the user can still open chat, above the app.
54
+ d.setAttribute('style', 'position:fixed;inset:0;z-index:99990;background:#0a0a0b;color:#e4e4e7;display:flex;align-items:center;justify-content:center;padding:1.5rem;font-family:system-ui,-apple-system,sans-serif');
55
+ d.innerHTML =
56
+ '<div style="text-align:center;max-width:480px;width:100%">' +
57
+ '<div style="position:relative;width:160px;height:160px;margin:0 auto 1.2rem">' +
58
+ '<div style="position:absolute;inset:-18px;background:radial-gradient(circle,rgba(1,102,255,.18) 0%,transparent 60%);filter:blur(18px)"></div>' +
59
+ '<video autoplay loop muted playsinline style="position:relative;width:100%;height:100%;object-fit:contain;border-radius:50%">' +
60
+ '<source src="/what-happened.webm" type="video/webm"><source src="/what-happened.mp4" type="video/mp4">' +
61
+ '</video>' +
62
+ '</div>' +
63
+ '<h1 style="font-size:1.5rem;font-weight:700;margin:0 0 .6rem;background:linear-gradient(135deg,#0166FF,#009AFE,#4AEEFF);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">A screen in your app has an error</h1>' +
64
+ '<p style="color:#e4e4e7;font-size:1rem;line-height:1.6;margin:0 0 .4rem">Your latest change didn\'t compile.</p>' +
65
+ '<p style="color:#a1a1aa;font-size:.93rem;line-height:1.6;margin:0 0 1.2rem">Ask your agent to fix it — the chat is in the bottom corner. Copy the error so it can debug faster.</p>' +
66
+ '<div><button id="__bloby_fe_copy" style="font:inherit;cursor:pointer;border:none;border-radius:10px;padding:.65rem 1.2rem;font-size:.9rem;font-weight:600;background:linear-gradient(135deg,#0166FF,#0069FE);color:#fff">Copy error for your agent</button> ' +
67
+ '<button id="__bloby_fe_dismiss" style="font:inherit;cursor:pointer;border-radius:10px;padding:.65rem 1.1rem;font-size:.9rem;font-weight:600;border:1px solid #27272a;background:#18181b;color:#e4e4e7">Dismiss</button></div>' +
68
+ '</div>';
69
+ document.body.appendChild(d);
70
+
71
+ var copyBtn = d.querySelector('#__bloby_fe_copy');
72
+ copyBtn.addEventListener('click', function () {
73
+ var text = 'A screen in my app has a frontend build error. Find and fix the root cause. Error:\n\n' + (lastErr || '(no details captured)');
74
+ function ok() { copyBtn.textContent = '✓ Copied — paste it to your agent'; setTimeout(function () { copyBtn.textContent = 'Copy error for your agent'; }, 2600); }
75
+ function fb() { var ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); ok(); } catch (e) {} document.body.removeChild(ta); }
76
+ if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(ok).catch(fb); else fb();
77
+ });
78
+ d.querySelector('#__bloby_fe_dismiss').addEventListener('click', function () {
79
+ dismissed = true;
80
+ if (overlay) { overlay.remove(); overlay = null; }
81
+ });
82
+ return d;
83
+ }
84
+
85
+ function sync() {
86
+ var ov = document.querySelector('vite-error-overlay');
87
+ if (ov) {
88
+ var err = readViteError();
89
+ if (err) lastErr = err;
90
+ if (!overlay && !dismissed) overlay = buildOverlay();
91
+ } else {
92
+ // Error cleared (agent fixed it) — drop our overlay and re-arm for the next episode.
93
+ dismissed = false;
94
+ if (overlay) { overlay.remove(); overlay = null; }
95
+ }
96
+ }
97
+
98
+ // Vite appends <vite-error-overlay> directly under <body>; observing body's childList is enough
99
+ // and far lighter than a full-document subtree observer.
100
+ var obs = new MutationObserver(sync);
101
+ obs.observe(document.body, { childList: true });
102
+ sync();
103
+ })();