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 +1 -1
- package/supervisor/index.ts +37 -2
- package/supervisor/workspace-guard.js +103 -0
package/package.json
CHANGED
package/supervisor/index.ts
CHANGED
|
@@ -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
|
-
|
|
1805
|
-
proxyRes.
|
|
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
|
+
})();
|