bloby-bot 0.64.0 → 0.65.2
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/bin/cli.js +19 -14
- package/cli/utils/ui.ts +30 -16
- package/package.json +1 -1
- package/scripts/install +18 -10
- package/scripts/install.ps1 +25 -17
- package/scripts/install.sh +18 -10
- package/supervisor/app-ws.js +0 -10
- package/supervisor/chat/src/hooks/useBlobyChat.ts +0 -11
- package/supervisor/chat/src/hooks/useChat.ts +0 -5
- package/supervisor/index.ts +12 -63
- package/supervisor/shell.ts +1 -1
- package/supervisor/vite-dev.ts +0 -7
- package/supervisor/widget.js +0 -6
- package/supervisor/workspace-guard.js +3 -11
- package/workspace/client/index.html +1 -0
- package/workspace/client/public/sw.js +3 -8
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +0 -3
package/bin/cli.js
CHANGED
|
@@ -278,8 +278,15 @@ const c = {
|
|
|
278
278
|
yellow: '\x1b[33m',
|
|
279
279
|
red: '\x1b[31m',
|
|
280
280
|
white: '\x1b[97m',
|
|
281
|
-
blue: '\x1b[38;2;
|
|
282
|
-
pink: '\x1b[38;2;
|
|
281
|
+
blue: '\x1b[38;2;0;173;254m',
|
|
282
|
+
pink: '\x1b[38;2;1;88;251m',
|
|
283
|
+
g1: '\x1b[38;2;0;173;254m',
|
|
284
|
+
g2: '\x1b[38;2;0;159;254m',
|
|
285
|
+
g3: '\x1b[38;2;0;145;253m',
|
|
286
|
+
g4: '\x1b[38;2;1;131;253m',
|
|
287
|
+
g5: '\x1b[38;2;1;116;252m',
|
|
288
|
+
g6: '\x1b[38;2;1;102;251m',
|
|
289
|
+
g7: '\x1b[38;2;1;88;251m',
|
|
283
290
|
};
|
|
284
291
|
|
|
285
292
|
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
@@ -287,9 +294,9 @@ const BAR_WIDTH = 30;
|
|
|
287
294
|
|
|
288
295
|
function gradientChar(i, total) {
|
|
289
296
|
const t = total > 1 ? i / (total - 1) : 0;
|
|
290
|
-
const r = Math.round(
|
|
291
|
-
const g = Math.round(
|
|
292
|
-
const b = Math.round(
|
|
297
|
+
const r = Math.round(0 + t * (1 - 0));
|
|
298
|
+
const g = Math.round(173 + t * (88 - 173));
|
|
299
|
+
const b = Math.round(254 + t * (251 - 254));
|
|
293
300
|
return `\x1b[38;2;${r};${g};${b}m`;
|
|
294
301
|
}
|
|
295
302
|
|
|
@@ -617,15 +624,13 @@ class Stepper {
|
|
|
617
624
|
|
|
618
625
|
function banner() {
|
|
619
626
|
console.log(`
|
|
620
|
-
${c.
|
|
621
|
-
${c.
|
|
622
|
-
${c.
|
|
623
|
-
${c.
|
|
624
|
-
${c.
|
|
625
|
-
${c.
|
|
626
|
-
${c.
|
|
627
|
-
${c.pink}${c.bold} ██ ${c.reset}
|
|
628
|
-
${c.pink}${c.bold} ▀▀▀ ${c.reset}
|
|
627
|
+
${c.g1}${c.bold} █▄ ${c.reset}
|
|
628
|
+
${c.g2}${c.bold} ▄ ▄ ██ ${c.reset}
|
|
629
|
+
${c.g3}${c.bold} ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██${c.reset}
|
|
630
|
+
${c.g4}${c.bold} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██${c.reset}
|
|
631
|
+
${c.g5}${c.bold} ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀${c.reset}
|
|
632
|
+
${c.g6}${c.bold} ██ ██ ${c.reset}
|
|
633
|
+
${c.g7}${c.bold} ▀ ▀▀▀ ${c.reset}
|
|
629
634
|
${c.dim}v${pkg.version} · Self-hosted AI agent${c.reset}`);
|
|
630
635
|
}
|
|
631
636
|
|
package/cli/utils/ui.ts
CHANGED
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import { pkg } from '../core/config.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
// 24-bit truecolor wrapper (picocolors only ships the 16-color set). Respects
|
|
5
|
+
// picocolors' own support detection so NO_COLOR / piped output stays clean.
|
|
6
|
+
const tc = (r: number, g: number, b: number) => (s: string) =>
|
|
7
|
+
pc.isColorSupported ? `\x1b[38;2;${r};${g};${b}m${s}\x1b[39m` : s;
|
|
8
|
+
|
|
9
|
+
// Morphy logo gradient: #00ADFE (top) -> #0158FB (bottom)
|
|
10
|
+
const grad = [
|
|
11
|
+
tc(0, 173, 254),
|
|
12
|
+
tc(0, 159, 254),
|
|
13
|
+
tc(0, 145, 253),
|
|
14
|
+
tc(1, 131, 253),
|
|
15
|
+
tc(1, 116, 252),
|
|
16
|
+
tc(1, 102, 251),
|
|
17
|
+
tc(1, 88, 251),
|
|
18
|
+
];
|
|
8
19
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
const logo = [
|
|
21
|
+
' █▄ ',
|
|
22
|
+
' ▄ ▄ ██ ',
|
|
23
|
+
' ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██',
|
|
24
|
+
' ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██',
|
|
25
|
+
' ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀',
|
|
26
|
+
' ██ ██ ',
|
|
27
|
+
' ▀ ▀▀▀ ',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export function banner() {
|
|
31
|
+
console.log('');
|
|
32
|
+
logo.forEach((row, i) => console.log(grad[i](pc.bold(row))));
|
|
33
|
+
console.log(pc.dim(`v${pkg.version || '1.0.0'} · Self-hosted AI agent`));
|
|
20
34
|
}
|
|
21
35
|
|
|
22
36
|
export function commandExample(name: string, cmd: string) {
|
|
23
|
-
return ` ${pc.dim(name)} ${
|
|
37
|
+
return ` ${pc.dim(name)} ${tc(0, 173, 254)(cmd)}`;
|
|
24
38
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bloby-bot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.2",
|
|
4
4
|
"releaseNotes": [
|
|
5
5
|
"1. Fix: image (and audio) attachments now render in chat again — /api/files is fetched with the auth token instead of a raw <img> src that 401'd after the endpoint hardening",
|
|
6
6
|
"2. Affects chat thumbnails, the image lightbox, voice-note playback, and agent image cards",
|
package/scripts/install
CHANGED
|
@@ -15,9 +15,17 @@ NODE_DIR="$TOOLS_DIR/node"
|
|
|
15
15
|
BIN_DIR="$BLOBY_HOME/bin"
|
|
16
16
|
USE_SYSTEM_NODE=false
|
|
17
17
|
|
|
18
|
-
# Brand colors: #
|
|
19
|
-
BLUE='\033[38;2;
|
|
20
|
-
PINK='\033[38;2;
|
|
18
|
+
# Brand colors: #00ADFE (light) and #0158FB (deep) -- Morphy palette, 24-bit truecolor
|
|
19
|
+
BLUE='\033[38;2;0;173;254m'
|
|
20
|
+
PINK='\033[38;2;1;88;251m'
|
|
21
|
+
# Logo gradient: #00ADFE (top) -> #0158FB (bottom)
|
|
22
|
+
G1='\033[38;2;0;173;254m'
|
|
23
|
+
G2='\033[38;2;0;159;254m'
|
|
24
|
+
G3='\033[38;2;0;145;253m'
|
|
25
|
+
G4='\033[38;2;1;131;253m'
|
|
26
|
+
G5='\033[38;2;1;116;252m'
|
|
27
|
+
G6='\033[38;2;1;102;251m'
|
|
28
|
+
G7='\033[38;2;1;88;251m'
|
|
21
29
|
YELLOW='\033[33m'
|
|
22
30
|
RED='\033[31m'
|
|
23
31
|
DIM='\033[2m'
|
|
@@ -25,13 +33,13 @@ BOLD='\033[1m'
|
|
|
25
33
|
RESET='\033[0m'
|
|
26
34
|
|
|
27
35
|
printf "\n"
|
|
28
|
-
printf "${
|
|
29
|
-
printf "${
|
|
30
|
-
printf "${
|
|
31
|
-
printf "${
|
|
32
|
-
printf "${
|
|
33
|
-
printf "${
|
|
34
|
-
printf "${
|
|
36
|
+
printf "${G1}${BOLD} █▄ ${RESET}\n"
|
|
37
|
+
printf "${G2}${BOLD} ▄ ▄ ██ ${RESET}\n"
|
|
38
|
+
printf "${G3}${BOLD} ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██${RESET}\n"
|
|
39
|
+
printf "${G4}${BOLD} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██${RESET}\n"
|
|
40
|
+
printf "${G5}${BOLD} ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀${RESET}\n"
|
|
41
|
+
printf "${G6}${BOLD} ██ ██ ${RESET}\n"
|
|
42
|
+
printf "${G7}${BOLD} ▀ ▀▀▀ ${RESET}\n"
|
|
35
43
|
printf "\n"
|
|
36
44
|
printf "${DIM} Self-hosted, self-evolving AI agent with its own dashboard.${RESET}\n"
|
|
37
45
|
printf "${DIM} ─────────────────────────────${RESET}\n\n"
|
package/scripts/install.ps1
CHANGED
|
@@ -17,9 +17,17 @@ $USE_SYSTEM_NODE = $false
|
|
|
17
17
|
# Ensure UTF-8 output for proper rendering
|
|
18
18
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
19
19
|
|
|
20
|
-
# Brand colors
|
|
21
|
-
$BLUE = "`e[38;2;
|
|
22
|
-
$PINK = "`e[38;2;
|
|
20
|
+
# Brand colors: #00ADFE (light) and #0158FB (deep) -- Morphy palette
|
|
21
|
+
$BLUE = "`e[38;2;0;173;254m"
|
|
22
|
+
$PINK = "`e[38;2;1;88;251m"
|
|
23
|
+
# Logo gradient: #00ADFE (top) -> #0158FB (bottom)
|
|
24
|
+
$G1 = "`e[38;2;0;173;254m"
|
|
25
|
+
$G2 = "`e[38;2;0;159;254m"
|
|
26
|
+
$G3 = "`e[38;2;0;145;253m"
|
|
27
|
+
$G4 = "`e[38;2;1;131;253m"
|
|
28
|
+
$G5 = "`e[38;2;1;116;252m"
|
|
29
|
+
$G6 = "`e[38;2;1;102;251m"
|
|
30
|
+
$G7 = "`e[38;2;1;88;251m"
|
|
23
31
|
$BOLD = "`e[1m"
|
|
24
32
|
$RSET = "`e[0m"
|
|
25
33
|
|
|
@@ -38,21 +46,21 @@ function Write-Down($text) {
|
|
|
38
46
|
|
|
39
47
|
Write-Host ""
|
|
40
48
|
if ($vtSupported) {
|
|
41
|
-
Write-Host "${
|
|
42
|
-
Write-Host "${
|
|
43
|
-
Write-Host "${
|
|
44
|
-
Write-Host "${
|
|
45
|
-
Write-Host "${
|
|
46
|
-
Write-Host "${
|
|
47
|
-
Write-Host "${
|
|
49
|
+
Write-Host "${G1}${BOLD} █▄ ${RSET}"
|
|
50
|
+
Write-Host "${G2}${BOLD} ▄ ▄ ██ ${RSET}"
|
|
51
|
+
Write-Host "${G3}${BOLD} ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██${RSET}"
|
|
52
|
+
Write-Host "${G4}${BOLD} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██${RSET}"
|
|
53
|
+
Write-Host "${G5}${BOLD} ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀${RSET}"
|
|
54
|
+
Write-Host "${G6}${BOLD} ██ ██ ${RSET}"
|
|
55
|
+
Write-Host "${G7}${BOLD} ▀ ▀▀▀ ${RSET}"
|
|
48
56
|
} else {
|
|
49
|
-
Write-Host "
|
|
50
|
-
Write-Host "
|
|
51
|
-
Write-Host "
|
|
52
|
-
Write-Host "
|
|
53
|
-
Write-Host "
|
|
54
|
-
Write-Host "
|
|
55
|
-
Write-Host "
|
|
57
|
+
Write-Host " █▄ " -ForegroundColor Cyan
|
|
58
|
+
Write-Host " ▄ ▄ ██ " -ForegroundColor Cyan
|
|
59
|
+
Write-Host " ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██" -ForegroundColor Cyan
|
|
60
|
+
Write-Host " ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██" -ForegroundColor Blue
|
|
61
|
+
Write-Host " ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀" -ForegroundColor Blue
|
|
62
|
+
Write-Host " ██ ██ " -ForegroundColor Blue
|
|
63
|
+
Write-Host " ▀ ▀▀▀ " -ForegroundColor Blue
|
|
56
64
|
}
|
|
57
65
|
Write-Host ""
|
|
58
66
|
Write-Host " Self-hosted, self-evolving AI agent with its own dashboard." -ForegroundColor DarkGray
|
package/scripts/install.sh
CHANGED
|
@@ -15,9 +15,17 @@ NODE_DIR="$TOOLS_DIR/node"
|
|
|
15
15
|
BIN_DIR="$BLOBY_HOME/bin"
|
|
16
16
|
USE_SYSTEM_NODE=false
|
|
17
17
|
|
|
18
|
-
# Brand colors: #
|
|
19
|
-
BLUE='\033[38;2;
|
|
20
|
-
PINK='\033[38;2;
|
|
18
|
+
# Brand colors: #00ADFE (light) and #0158FB (deep) -- Morphy palette, 24-bit truecolor
|
|
19
|
+
BLUE='\033[38;2;0;173;254m'
|
|
20
|
+
PINK='\033[38;2;1;88;251m'
|
|
21
|
+
# Logo gradient: #00ADFE (top) -> #0158FB (bottom)
|
|
22
|
+
G1='\033[38;2;0;173;254m'
|
|
23
|
+
G2='\033[38;2;0;159;254m'
|
|
24
|
+
G3='\033[38;2;0;145;253m'
|
|
25
|
+
G4='\033[38;2;1;131;253m'
|
|
26
|
+
G5='\033[38;2;1;116;252m'
|
|
27
|
+
G6='\033[38;2;1;102;251m'
|
|
28
|
+
G7='\033[38;2;1;88;251m'
|
|
21
29
|
YELLOW='\033[33m'
|
|
22
30
|
RED='\033[31m'
|
|
23
31
|
DIM='\033[2m'
|
|
@@ -25,13 +33,13 @@ BOLD='\033[1m'
|
|
|
25
33
|
RESET='\033[0m'
|
|
26
34
|
|
|
27
35
|
printf "\n"
|
|
28
|
-
printf "${
|
|
29
|
-
printf "${
|
|
30
|
-
printf "${
|
|
31
|
-
printf "${
|
|
32
|
-
printf "${
|
|
33
|
-
printf "${
|
|
34
|
-
printf "${
|
|
36
|
+
printf "${G1}${BOLD} █▄ ${RESET}\n"
|
|
37
|
+
printf "${G2}${BOLD} ▄ ▄ ██ ${RESET}\n"
|
|
38
|
+
printf "${G3}${BOLD} ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██${RESET}\n"
|
|
39
|
+
printf "${G4}${BOLD} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██${RESET}\n"
|
|
40
|
+
printf "${G5}${BOLD} ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀${RESET}\n"
|
|
41
|
+
printf "${G6}${BOLD} ██ ██ ${RESET}\n"
|
|
42
|
+
printf "${G7}${BOLD} ▀ ▀▀▀ ${RESET}\n"
|
|
35
43
|
printf "\n"
|
|
36
44
|
printf "${DIM} Self-hosted, self-evolving AI agent with its own dashboard.${RESET}\n"
|
|
37
45
|
printf "${DIM} ─────────────────────────────${RESET}\n\n"
|
package/supervisor/app-ws.js
CHANGED
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
function sendChatSubscribe() {
|
|
33
33
|
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
|
34
34
|
ws.send(JSON.stringify({ type: 'chat:subscribe', data: { clientId: chatClientId } }));
|
|
35
|
-
console.log('[app-ws] chat:subscribe sent clientId=' + chatClientId);
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
function buildWsUrl() {
|
|
@@ -59,7 +58,6 @@
|
|
|
59
58
|
for (var i = 0; i < ids.length; i++) {
|
|
60
59
|
var p = pendingRequests[ids[i]];
|
|
61
60
|
clearTimeout(p.timer);
|
|
62
|
-
console.log('[app-ws] WS dropped, falling back to fetch: ' + p.method + ' ' + p.path);
|
|
63
61
|
p.resolve(originalFetch(p.input, p.init));
|
|
64
62
|
}
|
|
65
63
|
pendingRequests = {};
|
|
@@ -74,7 +72,6 @@
|
|
|
74
72
|
connected = true;
|
|
75
73
|
reconnectDelay = RECONNECT_BASE;
|
|
76
74
|
startHeartbeat();
|
|
77
|
-
console.log('[app-ws] Connected to /app/ws');
|
|
78
75
|
if (chatSubscribed) sendChatSubscribe();
|
|
79
76
|
};
|
|
80
77
|
|
|
@@ -94,7 +91,6 @@
|
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
if (msg.type === 'chat:subscribed') {
|
|
97
|
-
console.log('[app-ws] chat:subscribed ack clientId=' + (msg.data && msg.data.clientId));
|
|
98
94
|
return;
|
|
99
95
|
}
|
|
100
96
|
|
|
@@ -104,7 +100,6 @@
|
|
|
104
100
|
clearTimeout(pending.timer);
|
|
105
101
|
delete pendingRequests[msg.data.id];
|
|
106
102
|
|
|
107
|
-
console.log('[app-ws] Response via WS: ' + msg.data.status + ' ' + pending.method + ' ' + pending.path);
|
|
108
103
|
|
|
109
104
|
var responseBody = msg.data.body;
|
|
110
105
|
if (responseBody === null || responseBody === undefined) responseBody = '';
|
|
@@ -125,7 +120,6 @@
|
|
|
125
120
|
failoverPending();
|
|
126
121
|
|
|
127
122
|
if (!intentionalClose) {
|
|
128
|
-
console.log('[app-ws] Disconnected, reconnecting in ' + reconnectDelay + 'ms');
|
|
129
123
|
reconnectTimer = setTimeout(function () {
|
|
130
124
|
reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX);
|
|
131
125
|
connect();
|
|
@@ -150,14 +144,12 @@
|
|
|
150
144
|
|
|
151
145
|
// Fallback: WS not connected
|
|
152
146
|
if (!connected || !ws || ws.readyState !== WebSocket.OPEN) {
|
|
153
|
-
console.log('[app-ws] WS not connected, falling back to fetch: ' + method + ' ' + url);
|
|
154
147
|
return originalFetch.apply(this, arguments);
|
|
155
148
|
}
|
|
156
149
|
|
|
157
150
|
// Fallback: non-serializable body (FormData, Blob, ArrayBuffer)
|
|
158
151
|
var body = init && init.body;
|
|
159
152
|
if (body && (body instanceof FormData || body instanceof Blob || body instanceof ArrayBuffer)) {
|
|
160
|
-
console.log('[app-ws] Non-serializable body, falling back to fetch: ' + method + ' ' + url);
|
|
161
153
|
return originalFetch.apply(this, arguments);
|
|
162
154
|
}
|
|
163
155
|
|
|
@@ -189,7 +181,6 @@
|
|
|
189
181
|
bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
|
|
190
182
|
}
|
|
191
183
|
|
|
192
|
-
console.log('[app-ws] Proxying via WS: ' + method + ' ' + reqPath);
|
|
193
184
|
|
|
194
185
|
ws.send(
|
|
195
186
|
JSON.stringify({
|
|
@@ -204,7 +195,6 @@
|
|
|
204
195
|
return new Promise(function (resolve, reject) {
|
|
205
196
|
var timer = setTimeout(function () {
|
|
206
197
|
delete pendingRequests[id];
|
|
207
|
-
console.log('[app-ws] Request timed out, falling back to fetch: ' + method + ' ' + reqPath);
|
|
208
198
|
resolve(originalFetch(savedInput, savedInit));
|
|
209
199
|
}, REQUEST_TIMEOUT);
|
|
210
200
|
|
|
@@ -41,7 +41,6 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
|
|
|
41
41
|
function handleExtMessage(event: MessageEvent) {
|
|
42
42
|
if (event.data?.type === 'bloby:page-context') {
|
|
43
43
|
extensionPageContext.current = event.data.context;
|
|
44
|
-
console.log('[blobyChat] Extension page context:', event.data.context?.url);
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -153,11 +152,9 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
|
|
|
153
152
|
useEffect(() => {
|
|
154
153
|
if (!ws) return;
|
|
155
154
|
|
|
156
|
-
console.log('[blobyChat] ──── WS HANDLERS REGISTERED ────');
|
|
157
155
|
|
|
158
156
|
const unsubs = [
|
|
159
157
|
ws.on('bot:typing', () => {
|
|
160
|
-
console.log('[blobyChat] bot:typing → streaming=true');
|
|
161
158
|
setStreaming(true);
|
|
162
159
|
setTools([]);
|
|
163
160
|
}),
|
|
@@ -175,7 +172,6 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
|
|
|
175
172
|
// This creates separate message bubbles and shows dots during work
|
|
176
173
|
const content = streamBufferRef.current;
|
|
177
174
|
if (content) {
|
|
178
|
-
console.log(`[blobyChat] bot:tool (${data.name}) — committing ${content.length} chars as bubble`);
|
|
179
175
|
committedTextLength.current += content.length;
|
|
180
176
|
setMessages((msgs) => [
|
|
181
177
|
...msgs,
|
|
@@ -205,9 +201,6 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
|
|
|
205
201
|
if (committedTextLength.current > 0) {
|
|
206
202
|
content = content.slice(committedTextLength.current).replace(/^\n+/, '');
|
|
207
203
|
committedTextLength.current = 0;
|
|
208
|
-
console.log('[blobyChat] bot:response — stripped partial, remaining:', content.slice(0, 60));
|
|
209
|
-
} else {
|
|
210
|
-
console.log('[blobyChat] bot:response — new bubble');
|
|
211
204
|
}
|
|
212
205
|
|
|
213
206
|
// Only add a bubble if there's new content
|
|
@@ -232,7 +225,6 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
|
|
|
232
225
|
ws.on('bot:task-created', () => {
|
|
233
226
|
const content = streamBufferRef.current;
|
|
234
227
|
if (content) {
|
|
235
|
-
console.log('[blobyChat] bot:task-created — committing stream, showing dots');
|
|
236
228
|
committedTextLength.current += content.length;
|
|
237
229
|
setMessages((msgs) => [
|
|
238
230
|
...msgs,
|
|
@@ -250,11 +242,9 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
|
|
|
250
242
|
}),
|
|
251
243
|
ws.on('bot:idle', () => {
|
|
252
244
|
// Server confirmed agent is idle — safe to stop streaming
|
|
253
|
-
console.log('[blobyChat] bot:idle → streaming=false');
|
|
254
245
|
setStreaming(false);
|
|
255
246
|
}),
|
|
256
247
|
ws.on('bot:error', (data: { error: string }) => {
|
|
257
|
-
console.log('[blobyChat] bot:error');
|
|
258
248
|
setStreamBuffer('');
|
|
259
249
|
streamBufferRef.current = '';
|
|
260
250
|
setStreaming(false);
|
|
@@ -334,7 +324,6 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
|
|
|
334
324
|
// If bot is streaming, commit partial response first
|
|
335
325
|
const partialContent = streamBufferRef.current;
|
|
336
326
|
if (partialContent) {
|
|
337
|
-
console.log('[blobyChat] Committing partial —', partialContent.length, 'chars');
|
|
338
327
|
committedTextLength.current += partialContent.length;
|
|
339
328
|
setMessages((msgs) => [
|
|
340
329
|
...msgs,
|
|
@@ -128,7 +128,6 @@ export function useChat(ws: WsClient | null) {
|
|
|
128
128
|
|
|
129
129
|
const unsubs = [
|
|
130
130
|
ws.on('bot:typing', () => {
|
|
131
|
-
console.log('[useChat] bot:typing → streaming=true');
|
|
132
131
|
setStreaming(true);
|
|
133
132
|
setTools([]);
|
|
134
133
|
}),
|
|
@@ -149,7 +148,6 @@ export function useChat(ws: WsClient | null) {
|
|
|
149
148
|
});
|
|
150
149
|
}),
|
|
151
150
|
ws.on('bot:response', (data: { conversationId: string; messageId?: string; content: string }) => {
|
|
152
|
-
console.log('[useChat] bot:response — adding message bubble');
|
|
153
151
|
setConversationId(data.conversationId);
|
|
154
152
|
|
|
155
153
|
// Always add as a new bubble
|
|
@@ -170,11 +168,9 @@ export function useChat(ws: WsClient | null) {
|
|
|
170
168
|
}),
|
|
171
169
|
ws.on('bot:idle', () => {
|
|
172
170
|
// Server confirmed agent is idle — safe to stop streaming
|
|
173
|
-
console.log('[useChat] bot:idle → streaming=false');
|
|
174
171
|
setStreaming(false);
|
|
175
172
|
}),
|
|
176
173
|
ws.on('bot:error', (data: { error: string }) => {
|
|
177
|
-
console.log('[useChat] bot:error');
|
|
178
174
|
setStreamBuffer('');
|
|
179
175
|
streamBufferRef.current = '';
|
|
180
176
|
setStreaming(false);
|
|
@@ -218,7 +214,6 @@ export function useChat(ws: WsClient | null) {
|
|
|
218
214
|
// so the user's new message appears BELOW the bot's in-progress text (chronological order)
|
|
219
215
|
const partialContent = streamBufferRef.current;
|
|
220
216
|
if (partialContent) {
|
|
221
|
-
console.log('[useChat] Committing partial stream buffer as message before user send');
|
|
222
217
|
setMessages((msgs) => [
|
|
223
218
|
...msgs,
|
|
224
219
|
{
|
package/supervisor/index.ts
CHANGED
|
@@ -128,25 +128,22 @@ var HASHED_RE = new RegExp('/assets/.+-[a-zA-Z0-9]{6,}[.](js|css)$');
|
|
|
128
128
|
// Without this, the first navigation isn't intercepted (SW wasn't
|
|
129
129
|
// controlling yet), so refresh would find an empty cache → white screen.
|
|
130
130
|
self.addEventListener('install', function(e) {
|
|
131
|
-
console.log('[SW] installing, cache:', CACHE);
|
|
132
131
|
e.waitUntil(
|
|
133
132
|
caches.open(CACHE)
|
|
134
133
|
.then(function(c) { return c.add('/'); })
|
|
135
|
-
.then(function() {
|
|
134
|
+
.then(function() { return self.skipWaiting(); })
|
|
136
135
|
.catch(function(err) { console.error('[SW] install failed:', err); throw err; })
|
|
137
136
|
);
|
|
138
137
|
});
|
|
139
138
|
|
|
140
139
|
self.addEventListener('activate', function(e) {
|
|
141
|
-
console.log('[SW] activating, cache:', CACHE);
|
|
142
140
|
e.waitUntil(
|
|
143
141
|
caches.keys()
|
|
144
142
|
.then(function(keys) {
|
|
145
143
|
var old = keys.filter(function(k) { return k !== CACHE; });
|
|
146
|
-
if (old.length) console.log('[SW] deleting old caches:', old);
|
|
147
144
|
return Promise.all(old.map(function(k) { return caches.delete(k); }));
|
|
148
145
|
})
|
|
149
|
-
.then(function() {
|
|
146
|
+
.then(function() { return self.clients.claim(); })
|
|
150
147
|
);
|
|
151
148
|
});
|
|
152
149
|
|
|
@@ -184,7 +181,6 @@ self.addEventListener('fetch', function(event) {
|
|
|
184
181
|
// /bloby/* is a separate app — let it go to network to avoid
|
|
185
182
|
// caching the wrong HTML under the / key.
|
|
186
183
|
if (request.mode === 'navigate') {
|
|
187
|
-
console.log('[SW] navigate →', url.pathname);
|
|
188
184
|
// Workspace iframe documents (?__bloby_frame=1) share pathname '/' with the shell.
|
|
189
185
|
// Network-only: never c.put() them (would overwrite the precached shell under the '/'
|
|
190
186
|
// key) and never fall back to c.match('/') (would serve the shell INTO the iframe).
|
|
@@ -194,12 +190,11 @@ self.addEventListener('fetch', function(event) {
|
|
|
194
190
|
event.respondWith(caches.open(CACHE).then(function(c) {
|
|
195
191
|
return fetch(request)
|
|
196
192
|
.then(function(r) { if (r.ok) c.put('/', r.clone()); return r; })
|
|
197
|
-
.catch(function(
|
|
193
|
+
.catch(function() { return c.match('/'); });
|
|
198
194
|
}));
|
|
199
195
|
return;
|
|
200
196
|
}
|
|
201
197
|
// Other navigations (/bloby/*, etc.) — network only
|
|
202
|
-
console.log('[SW] navigate (network-only) →', url.pathname);
|
|
203
198
|
return;
|
|
204
199
|
}
|
|
205
200
|
|
|
@@ -426,7 +421,6 @@ export async function startSupervisor() {
|
|
|
426
421
|
log.error(`Vite dev server failed to start — dashboard degraded, chat still available: ${err instanceof Error ? err.message : err}`);
|
|
427
422
|
vitePorts = { dashboard: -1 }; // sentinel → dashboard proxy serves RECOVERING_HTML
|
|
428
423
|
}
|
|
429
|
-
console.log(`[supervisor] Upgrade listeners on server: ${server.listenerCount('upgrade')}`);
|
|
430
424
|
|
|
431
425
|
// Ensure file storage dirs exist
|
|
432
426
|
ensureFileDirs();
|
|
@@ -589,7 +583,6 @@ export async function startSupervisor() {
|
|
|
589
583
|
server.on('request', async (req, res) => {
|
|
590
584
|
// Bloby widget — served directly (not part of Vite build)
|
|
591
585
|
if (req.url === '/bloby/widget.js') {
|
|
592
|
-
console.log('[supervisor] Serving /bloby/widget.js directly');
|
|
593
586
|
res.writeHead(200, { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-cache' });
|
|
594
587
|
res.end(fs.readFileSync(paths.widgetJs));
|
|
595
588
|
return;
|
|
@@ -597,7 +590,6 @@ export async function startSupervisor() {
|
|
|
597
590
|
|
|
598
591
|
// App WS client — served directly (proxies /app/api calls through WebSocket)
|
|
599
592
|
if (req.url === '/bloby/app-ws.js') {
|
|
600
|
-
console.log('[supervisor] Serving /bloby/app-ws.js directly');
|
|
601
593
|
res.writeHead(200, { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-cache' });
|
|
602
594
|
res.end(fs.readFileSync(path.join(PKG_DIR, 'supervisor', 'app-ws.js')));
|
|
603
595
|
return;
|
|
@@ -631,9 +623,7 @@ export async function startSupervisor() {
|
|
|
631
623
|
// App API routes → proxy to user's backend server
|
|
632
624
|
if (req.url?.startsWith('/app/api')) {
|
|
633
625
|
const backendPath = req.url.replace(/^\/app/, '');
|
|
634
|
-
console.log(`[supervisor] → backend :${backendPort} | ${req.method} ${backendPath}`);
|
|
635
626
|
if (!isBackendAlive()) {
|
|
636
|
-
console.log('[supervisor] Backend down — returning 503');
|
|
637
627
|
res.writeHead(503, { 'Content-Type': 'application/json' });
|
|
638
628
|
res.end(JSON.stringify({ error: 'Backend is starting...' }));
|
|
639
629
|
return;
|
|
@@ -644,18 +634,10 @@ export async function startSupervisor() {
|
|
|
644
634
|
(proxyRes) => {
|
|
645
635
|
const ct = String(proxyRes.headers['content-type'] || '');
|
|
646
636
|
const isSse = ct.includes('text/event-stream');
|
|
647
|
-
if (isSse) {
|
|
648
|
-
console.log(`[app-proxy] SSE upstream status=${proxyRes.statusCode} ct="${ct}" url=${backendPath}`);
|
|
649
|
-
}
|
|
650
637
|
res.writeHead(proxyRes.statusCode!, proxyRes.headers);
|
|
638
|
+
// SSE needs Nagle off so chat tokens flush immediately instead of in batched frames.
|
|
651
639
|
if (isSse) {
|
|
652
640
|
try { res.socket?.setNoDelay(true); } catch {}
|
|
653
|
-
proxyRes.on('data', (chunk: Buffer) => {
|
|
654
|
-
console.log(`[app-proxy] SSE chunk bytes=${chunk.length} preview=${JSON.stringify(chunk.toString('utf-8').slice(0, 80))}`);
|
|
655
|
-
});
|
|
656
|
-
proxyRes.on('end', () => console.log(`[app-proxy] SSE upstream END`));
|
|
657
|
-
proxyRes.on('error', (e: any) => console.log(`[app-proxy] SSE upstream ERROR ${e.message}`));
|
|
658
|
-
res.on('close', () => console.log(`[app-proxy] SSE res CLOSE`));
|
|
659
641
|
}
|
|
660
642
|
proxyRes.pipe(res);
|
|
661
643
|
},
|
|
@@ -2139,7 +2121,6 @@ ${alreadyLinked ? '' : `
|
|
|
2139
2121
|
if (req.method === 'GET' && agentPath === '/api/agent/chat/stream') {
|
|
2140
2122
|
const clientId = urlObj.searchParams.get('clientId') || undefined;
|
|
2141
2123
|
const subId = crypto.randomBytes(8).toString('hex');
|
|
2142
|
-
console.log(`[sse-handler] OPEN sub=${subId} clientId=${clientId} remote=${req.socket.remoteAddress}`);
|
|
2143
2124
|
|
|
2144
2125
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
2145
2126
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
@@ -2147,19 +2128,14 @@ ${alreadyLinked ? '' : `
|
|
|
2147
2128
|
res.setHeader('X-Accel-Buffering', 'no');
|
|
2148
2129
|
res.writeHead(200);
|
|
2149
2130
|
try { res.socket?.setNoDelay(true); } catch {}
|
|
2150
|
-
|
|
2151
|
-
console.log(`[sse-handler] wrote initial comment sub=${subId} writeOk=${wrote}`);
|
|
2131
|
+
res.write(': connected\n\n');
|
|
2152
2132
|
|
|
2153
2133
|
const sub: ChatSubscriber = {
|
|
2154
2134
|
id: subId,
|
|
2155
2135
|
clientId,
|
|
2156
2136
|
send: (type, data) => {
|
|
2157
|
-
if (res.writableEnded)
|
|
2158
|
-
|
|
2159
|
-
return;
|
|
2160
|
-
}
|
|
2161
|
-
const ok = res.write(`event: ${type}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
2162
|
-
console.log(`[sse-handler] write sub=${subId} type=${type} ok=${ok} bytes=${(type.length + JSON.stringify(data).length + 10)}`);
|
|
2137
|
+
if (res.writableEnded) return;
|
|
2138
|
+
res.write(`event: ${type}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
2163
2139
|
},
|
|
2164
2140
|
close: () => { try { res.end(); } catch {} },
|
|
2165
2141
|
};
|
|
@@ -2176,20 +2152,14 @@ ${alreadyLinked ? '' : `
|
|
|
2176
2152
|
const keepAlive = setInterval(() => {
|
|
2177
2153
|
if (res.writableEnded) return;
|
|
2178
2154
|
res.write(': ping\n\n');
|
|
2179
|
-
console.log(`[sse-handler] ping sub=${subId}`);
|
|
2180
2155
|
}, 25_000);
|
|
2181
2156
|
|
|
2182
2157
|
req.on('close', () => {
|
|
2183
|
-
console.log(`[sse-handler] CLOSE sub=${subId} reason=req-close`);
|
|
2184
2158
|
clearInterval(keepAlive);
|
|
2185
2159
|
chatSubscribers.delete(sub);
|
|
2186
2160
|
});
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
});
|
|
2190
|
-
res.on('error', (err: any) => {
|
|
2191
|
-
console.log(`[sse-handler] ERROR sub=${subId} ${err?.message}`);
|
|
2192
|
-
});
|
|
2161
|
+
// Keep an error listener so a client reset can't surface as an unhandled 'error'.
|
|
2162
|
+
res.on('error', () => {});
|
|
2193
2163
|
return;
|
|
2194
2164
|
}
|
|
2195
2165
|
|
|
@@ -2630,7 +2600,6 @@ ${alreadyLinked ? '' : `
|
|
|
2630
2600
|
}
|
|
2631
2601
|
|
|
2632
2602
|
// Everything else → proxy to dashboard Vite dev server
|
|
2633
|
-
console.log(`[supervisor] → dashboard Vite :${vitePorts.dashboard} | ${req.method} ${(req.url || '').split('?')[0]}`);
|
|
2634
2603
|
const GUARD_TAG = '<script defer src="/bloby/workspace-guard.js"></script>';
|
|
2635
2604
|
const proxy = http.request(
|
|
2636
2605
|
{ host: '127.0.0.1', port: vitePorts.dashboard, path: req.url, method: req.method, headers: req.headers },
|
|
@@ -2679,10 +2648,9 @@ ${alreadyLinked ? '' : `
|
|
|
2679
2648
|
const appWss = new WebSocketServer({ noServer: true });
|
|
2680
2649
|
|
|
2681
2650
|
appWss.on('connection', (ws) => {
|
|
2682
|
-
console.log('[supervisor] App API WS client connected');
|
|
2683
2651
|
// An 'error' event with no listener is rethrown by Node as an uncaught exception,
|
|
2684
2652
|
// which would crash the whole supervisor. ws still tears down + fires 'close'.
|
|
2685
|
-
ws.on('error', (
|
|
2653
|
+
ws.on('error', () => {});
|
|
2686
2654
|
// Liveness: a half-open socket (mobile/Wi-Fi drop behind the tunnel) never fires 'close', so
|
|
2687
2655
|
// its chat subscription + maps would leak and broadcastBloby would keep writing to it. The
|
|
2688
2656
|
// heartbeat below pings; a peer that misses a pong is terminated (which fires 'close' → cleanup).
|
|
@@ -2724,7 +2692,6 @@ ${alreadyLinked ? '' : `
|
|
|
2724
2692
|
close: () => {},
|
|
2725
2693
|
};
|
|
2726
2694
|
chatSubscribers.add(chatSub);
|
|
2727
|
-
console.log(`[app-ws-chat] subscribe sub=${subId} clientId=${clientId} total=${chatSubscribers.size}`);
|
|
2728
2695
|
|
|
2729
2696
|
if (agentQueryActive && currentStreamConvId) {
|
|
2730
2697
|
chatSub.send('chat:state', {
|
|
@@ -2742,7 +2709,6 @@ ${alreadyLinked ? '' : `
|
|
|
2742
2709
|
if (msg.type === 'chat:unsubscribe') {
|
|
2743
2710
|
if (chatSub) {
|
|
2744
2711
|
chatSubscribers.delete(chatSub);
|
|
2745
|
-
console.log(`[app-ws-chat] unsubscribe sub=${chatSub.id} total=${chatSubscribers.size}`);
|
|
2746
2712
|
chatSub = null;
|
|
2747
2713
|
}
|
|
2748
2714
|
return;
|
|
@@ -2753,10 +2719,8 @@ ${alreadyLinked ? '' : `
|
|
|
2753
2719
|
const { id, method, path: reqPath, headers: reqHeaders, body } = msg.data;
|
|
2754
2720
|
const backendPath = (reqPath || '').replace(/^\/app/, '');
|
|
2755
2721
|
|
|
2756
|
-
console.log(`[supervisor] App WS → backend :${backendPort} | ${method} ${backendPath} (${id})`);
|
|
2757
2722
|
|
|
2758
2723
|
if (!isBackendAlive()) {
|
|
2759
|
-
console.log('[supervisor] App WS: Backend down — returning 503');
|
|
2760
2724
|
if (ws.readyState === WebSocket.OPEN) {
|
|
2761
2725
|
ws.send(JSON.stringify({
|
|
2762
2726
|
type: 'app:api:response',
|
|
@@ -2784,7 +2748,6 @@ ${alreadyLinked ? '' : `
|
|
|
2784
2748
|
else if (Array.isArray(v)) resHeaders[k] = v.join(', ');
|
|
2785
2749
|
}
|
|
2786
2750
|
|
|
2787
|
-
console.log(`[supervisor] App WS ← backend: ${proxyRes.statusCode} (${id})`);
|
|
2788
2751
|
|
|
2789
2752
|
if (ws.readyState === WebSocket.OPEN) {
|
|
2790
2753
|
ws.send(JSON.stringify({
|
|
@@ -2813,10 +2776,8 @@ ${alreadyLinked ? '' : `
|
|
|
2813
2776
|
ws.on('close', () => {
|
|
2814
2777
|
if (chatSub) {
|
|
2815
2778
|
chatSubscribers.delete(chatSub);
|
|
2816
|
-
console.log(`[app-ws-chat] auto-unsubscribe on close sub=${chatSub.id} total=${chatSubscribers.size}`);
|
|
2817
2779
|
chatSub = null;
|
|
2818
2780
|
}
|
|
2819
|
-
console.log('[supervisor] App API WS client disconnected');
|
|
2820
2781
|
});
|
|
2821
2782
|
});
|
|
2822
2783
|
|
|
@@ -2824,18 +2785,11 @@ ${alreadyLinked ? '' : `
|
|
|
2824
2785
|
* subscribers. The workspace mirror sees the exact same event stream the widget does. */
|
|
2825
2786
|
function broadcastBloby(type: string, data: any = {}) {
|
|
2826
2787
|
const msg = JSON.stringify({ type, data });
|
|
2827
|
-
let wsCount = 0;
|
|
2828
2788
|
for (const client of blobyWss.clients) {
|
|
2829
|
-
if (client.readyState === WebSocket.OPEN)
|
|
2789
|
+
if (client.readyState === WebSocket.OPEN) client.send(msg);
|
|
2830
2790
|
}
|
|
2831
|
-
let sseCount = 0;
|
|
2832
2791
|
for (const sub of chatSubscribers) {
|
|
2833
|
-
try { sub.send(type, data);
|
|
2834
|
-
console.log(`[sse-broadcast] send failed sub=${sub.id}: ${e.message}`);
|
|
2835
|
-
}
|
|
2836
|
-
}
|
|
2837
|
-
if (type.startsWith('bot:') || type.startsWith('chat:')) {
|
|
2838
|
-
console.log(`[sse-broadcast] type=${type} ws=${wsCount} sse=${sseCount} subs=${chatSubscribers.size}`);
|
|
2792
|
+
try { sub.send(type, data); } catch {}
|
|
2839
2793
|
}
|
|
2840
2794
|
}
|
|
2841
2795
|
|
|
@@ -3406,17 +3360,14 @@ ${alreadyLinked ? '' : `
|
|
|
3406
3360
|
// Bloby chat WebSocket — Vite HMR is handled automatically (hmr.server = this server)
|
|
3407
3361
|
server.on('upgrade', async (req, socket: net.Socket, head) => {
|
|
3408
3362
|
// Strip the query string: /bloby/ws?token=<7-day session token> must not be logged.
|
|
3409
|
-
console.log(`[supervisor] WebSocket upgrade: ${(req.url || '').split('?')[0]} | protocol=${req.headers['sec-websocket-protocol'] || 'none'}`);
|
|
3410
3363
|
|
|
3411
3364
|
// App API WebSocket — no auth (backend handles its own auth)
|
|
3412
3365
|
if (req.url?.startsWith('/app/ws')) {
|
|
3413
|
-
console.log('[supervisor] → App API WebSocket');
|
|
3414
3366
|
appWss.handleUpgrade(req, socket, head, (ws) => appWss.emit('connection', ws, req));
|
|
3415
3367
|
return;
|
|
3416
3368
|
}
|
|
3417
3369
|
|
|
3418
3370
|
if (!req.url?.startsWith('/bloby/ws')) {
|
|
3419
|
-
console.log('[supervisor] → Letting Vite handle this upgrade');
|
|
3420
3371
|
return;
|
|
3421
3372
|
}
|
|
3422
3373
|
|
|
@@ -3433,7 +3384,6 @@ ${alreadyLinked ? '' : `
|
|
|
3433
3384
|
}
|
|
3434
3385
|
}
|
|
3435
3386
|
|
|
3436
|
-
console.log('[supervisor] → Bloby chat WebSocket');
|
|
3437
3387
|
blobyWss.handleUpgrade(req, socket, head, (ws) => blobyWss.emit('connection', ws, req));
|
|
3438
3388
|
});
|
|
3439
3389
|
|
|
@@ -3859,7 +3809,6 @@ ${alreadyLinked ? '' : `
|
|
|
3859
3809
|
closeDb();
|
|
3860
3810
|
await stopBackend();
|
|
3861
3811
|
stopTunnel();
|
|
3862
|
-
console.log('[supervisor] Stopping Vite dev servers...');
|
|
3863
3812
|
await stopViteDevServers();
|
|
3864
3813
|
server.close();
|
|
3865
3814
|
process.exit(0);
|
package/supervisor/shell.ts
CHANGED
|
@@ -14,6 +14,7 @@ export const SHELL_HTML = `<!DOCTYPE html>
|
|
|
14
14
|
<meta charset="UTF-8" />
|
|
15
15
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
|
|
16
16
|
<meta name="theme-color" content="#212121" />
|
|
17
|
+
<meta name="mobile-web-app-capable" content="yes" />
|
|
17
18
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
18
19
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
19
20
|
<meta name="apple-mobile-web-app-title" content="Bloby" />
|
|
@@ -31,7 +32,6 @@ export const SHELL_HTML = `<!DOCTYPE html>
|
|
|
31
32
|
id="bloby-workspace"
|
|
32
33
|
style="position:fixed;inset:0;width:100vw;height:100dvh;border:none;background:#0A0A0A"
|
|
33
34
|
allow="camera; microphone; geolocation; clipboard-read; clipboard-write; fullscreen; autoplay; display-capture"
|
|
34
|
-
allowfullscreen
|
|
35
35
|
></iframe>
|
|
36
36
|
|
|
37
37
|
<script>
|
package/supervisor/vite-dev.ts
CHANGED
|
@@ -11,9 +11,6 @@ export async function startViteDevServers(supervisorPort: number, hmrServer: htt
|
|
|
11
11
|
dashboard: supervisorPort + 2,
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
console.log(`[vite-dev] Starting dashboard Vite dev server on :${ports.dashboard}`);
|
|
15
|
-
console.log(`[vite-dev] HMR attached to supervisor server`);
|
|
16
|
-
console.log(`[vite-dev] PKG_DIR = ${PKG_DIR}`);
|
|
17
14
|
|
|
18
15
|
try {
|
|
19
16
|
dashboardVite = await createViteServer({
|
|
@@ -31,7 +28,6 @@ export async function startViteDevServers(supervisorPort: number, hmrServer: htt
|
|
|
31
28
|
logLevel: 'info',
|
|
32
29
|
});
|
|
33
30
|
await dashboardVite.listen();
|
|
34
|
-
console.log(`[vite-dev] ✓ Dashboard Vite ready on :${ports.dashboard}`);
|
|
35
31
|
} catch (err) {
|
|
36
32
|
console.error('[vite-dev] ✗ Dashboard Vite failed:', err);
|
|
37
33
|
throw err;
|
|
@@ -58,15 +54,12 @@ export async function startViteDevServers(supervisorPort: number, hmrServer: htt
|
|
|
58
54
|
/** Tell connected browsers to full-reload via Vite's HMR WebSocket */
|
|
59
55
|
export function reloadDashboard(): void {
|
|
60
56
|
if (!dashboardVite) return;
|
|
61
|
-
console.log('[vite-dev] Sending full-reload to dashboard clients');
|
|
62
57
|
dashboardVite.hot.send({ type: 'full-reload', path: '*' });
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
export async function stopViteDevServers(): Promise<void> {
|
|
66
|
-
console.log('[vite-dev] Stopping Vite dev servers...');
|
|
67
61
|
if (dashboardVite) {
|
|
68
62
|
await dashboardVite.close();
|
|
69
63
|
dashboardVite = null;
|
|
70
|
-
console.log('[vite-dev] Dashboard Vite stopped');
|
|
71
64
|
}
|
|
72
65
|
}
|
package/supervisor/widget.js
CHANGED
|
@@ -684,7 +684,6 @@
|
|
|
684
684
|
var toggleCooldown = false;
|
|
685
685
|
|
|
686
686
|
function toggle() {
|
|
687
|
-
console.log('[widget] toggle called', { canvasPhase: canvasPhase, isOpen: isOpen });
|
|
688
687
|
if (canvasPhase === 'splash' || canvasPhase === 'transitioning') {
|
|
689
688
|
skipToBubble();
|
|
690
689
|
try { sessionStorage.setItem(SPLASH_KEY, '1'); } catch(e) {}
|
|
@@ -709,14 +708,12 @@
|
|
|
709
708
|
// Hold detected via pointerdown timer. pointerup stops recording.
|
|
710
709
|
|
|
711
710
|
bubble.addEventListener('pointerdown', function(e) {
|
|
712
|
-
console.log('[widget] pointerdown', { canvasPhase: canvasPhase, hpState: hpState, type: e.pointerType });
|
|
713
711
|
if (canvasPhase !== 'bubble') return;
|
|
714
712
|
e.preventDefault(); // Prevent iOS long-press text selection
|
|
715
713
|
hpPointerDown = true;
|
|
716
714
|
hpWasHold = false;
|
|
717
715
|
|
|
718
716
|
hpHoldTimer = setTimeout(function() {
|
|
719
|
-
console.log('[widget] hold timer fired');
|
|
720
717
|
hpWasHold = true;
|
|
721
718
|
if (hpState === 'idle' || hpState === 'deactivating') {
|
|
722
719
|
startHpRecording();
|
|
@@ -725,7 +722,6 @@
|
|
|
725
722
|
});
|
|
726
723
|
|
|
727
724
|
bubble.addEventListener('pointerup', function() {
|
|
728
|
-
console.log('[widget] pointerup', { hpState: hpState, hpWasHold: hpWasHold });
|
|
729
725
|
hpPointerDown = false;
|
|
730
726
|
if (hpHoldTimer) { clearTimeout(hpHoldTimer); hpHoldTimer = null; }
|
|
731
727
|
if (hpState === 'activating' || hpState === 'recording') {
|
|
@@ -743,7 +739,6 @@
|
|
|
743
739
|
});
|
|
744
740
|
|
|
745
741
|
bubble.addEventListener('pointercancel', function() {
|
|
746
|
-
console.log('[widget] pointercancel');
|
|
747
742
|
hpPointerDown = false;
|
|
748
743
|
if (hpHoldTimer) { clearTimeout(hpHoldTimer); hpHoldTimer = null; }
|
|
749
744
|
if (hpState === 'activating' || hpState === 'recording') stopHpRecording(true);
|
|
@@ -752,7 +747,6 @@
|
|
|
752
747
|
// click still fires on desktop (pointerdown preventDefault does not suppress it there).
|
|
753
748
|
// On mobile, pointerup already handled the tap, so this is a no-op guard.
|
|
754
749
|
bubble.addEventListener('click', function(e) {
|
|
755
|
-
console.log('[widget] click', { canvasPhase: canvasPhase, hpWasHold: hpWasHold, hpState: hpState, isOpen: isOpen });
|
|
756
750
|
// Prevent double-toggle: pointerup already handled taps.
|
|
757
751
|
e.stopPropagation();
|
|
758
752
|
});
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
function flog(event, extra) {
|
|
45
45
|
var entry = { t: new Date().toISOString(), e: event };
|
|
46
46
|
if (extra) entry.x = String(extra).slice(0, 600);
|
|
47
|
-
console.log('[bloby-forensics]', entry.t, event, extra || '');
|
|
48
47
|
try {
|
|
49
48
|
var arr = JSON.parse(sessionStorage.getItem(FORENSICS_KEY) || '[]');
|
|
50
49
|
arr.push(entry);
|
|
@@ -53,22 +52,15 @@
|
|
|
53
52
|
} catch (e) {}
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
// On load:
|
|
55
|
+
// On load: record how we got here into the forensics ring buffer. The trail the previous
|
|
56
|
+
// page session left behind survives the reload in sessionStorage under FORENSICS_KEY and can
|
|
57
|
+
// be inspected there after a mystery refresh.
|
|
57
58
|
(function () {
|
|
58
59
|
var navType = '?';
|
|
59
60
|
try {
|
|
60
61
|
var nav = performance.getEntriesByType('navigation')[0];
|
|
61
62
|
if (nav) navType = nav.type; // 'navigate' | 'reload' | 'back_forward' | 'prerender'
|
|
62
63
|
} catch (e) {}
|
|
63
|
-
var trail = null;
|
|
64
|
-
try { trail = sessionStorage.getItem(FORENSICS_KEY); } catch (e) {}
|
|
65
|
-
console.log(
|
|
66
|
-
'%c[bloby-forensics] PAGE LOADED — navigation.type=' + navType +
|
|
67
|
-
' wasDiscarded=' + (document.wasDiscarded === true) +
|
|
68
|
-
'\nTrail left by the previous page session (newest last):',
|
|
69
|
-
'color:#4AEEFF;font-weight:bold',
|
|
70
|
-
trail ? JSON.parse(trail) : '(none — first load in this tab)'
|
|
71
|
-
);
|
|
72
64
|
flog('page-load', 'navType=' + navType + ' wasDiscarded=' + (document.wasDiscarded === true) + ' visible=' + document.visibilityState);
|
|
73
65
|
})();
|
|
74
66
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
|
|
6
6
|
<meta name="theme-color" content="#212121" />
|
|
7
|
+
<meta name="mobile-web-app-capable" content="yes" />
|
|
7
8
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
8
9
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
9
10
|
<meta name="apple-mobile-web-app-title" content="Bloby" />
|
|
@@ -14,25 +14,22 @@ const CACHE = 'bloby-v24';
|
|
|
14
14
|
// Without this, the first navigation isn't intercepted (SW wasn't
|
|
15
15
|
// controlling yet), so refresh would find an empty cache → white screen.
|
|
16
16
|
self.addEventListener('install', (e) => {
|
|
17
|
-
console.log('[SW] installing, cache:', CACHE);
|
|
18
17
|
e.waitUntil(
|
|
19
18
|
caches.open(CACHE)
|
|
20
19
|
.then(c => c.add('/'))
|
|
21
|
-
.then(() =>
|
|
20
|
+
.then(() => self.skipWaiting())
|
|
22
21
|
.catch(err => { console.error('[SW] install failed:', err); throw err; })
|
|
23
22
|
);
|
|
24
23
|
});
|
|
25
24
|
|
|
26
25
|
self.addEventListener('activate', (e) => {
|
|
27
|
-
console.log('[SW] activating, cache:', CACHE);
|
|
28
26
|
e.waitUntil(
|
|
29
27
|
caches.keys()
|
|
30
28
|
.then(keys => {
|
|
31
29
|
const old = keys.filter(k => k !== CACHE);
|
|
32
|
-
if (old.length) console.log('[SW] deleting old caches:', old);
|
|
33
30
|
return Promise.all(old.map(k => caches.delete(k)));
|
|
34
31
|
})
|
|
35
|
-
.then(() =>
|
|
32
|
+
.then(() => self.clients.claim())
|
|
36
33
|
);
|
|
37
34
|
});
|
|
38
35
|
|
|
@@ -70,18 +67,16 @@ self.addEventListener('fetch', (event) => {
|
|
|
70
67
|
// /bloby/* is a separate app — let it go to network to avoid
|
|
71
68
|
// caching the wrong HTML under the / key.
|
|
72
69
|
if (request.mode === 'navigate') {
|
|
73
|
-
console.log('[SW] navigate →', url.pathname);
|
|
74
70
|
if (url.pathname === '/' || url.pathname === '/index.html') {
|
|
75
71
|
// Network-first: always fetch the live dashboard; cache only as offline fallback.
|
|
76
72
|
event.respondWith(caches.open(CACHE).then(c =>
|
|
77
73
|
fetch(request)
|
|
78
74
|
.then(r => { if (r.ok) c.put('/', r.clone()); return r; })
|
|
79
|
-
.catch(
|
|
75
|
+
.catch(() => c.match('/'))
|
|
80
76
|
));
|
|
81
77
|
return;
|
|
82
78
|
}
|
|
83
79
|
// Other navigations (/bloby/*, etc.) — network only
|
|
84
|
-
console.log('[SW] navigate (network-only) →', url.pathname);
|
|
85
80
|
return;
|
|
86
81
|
}
|
|
87
82
|
|
|
@@ -14,14 +14,11 @@ export default function DashboardLayout({ children, userName, botName = 'Bloby'
|
|
|
14
14
|
|
|
15
15
|
useEffect(() => {
|
|
16
16
|
const check = () => {
|
|
17
|
-
console.log('[health] checking /app/api/health…');
|
|
18
17
|
fetch('/app/api/health', { signal: AbortSignal.timeout(3000) })
|
|
19
18
|
.then((r) => {
|
|
20
|
-
console.log(`[health] response: ${r.status} ok=${r.ok}`);
|
|
21
19
|
setStatus(r.ok ? 'healthy' : 'restarting');
|
|
22
20
|
})
|
|
23
21
|
.catch((err) => {
|
|
24
|
-
console.warn('[health] fetch failed:', err.message ?? err);
|
|
25
22
|
setStatus('restarting');
|
|
26
23
|
});
|
|
27
24
|
};
|