fluxy-bot 0.8.9 → 0.9.0
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/README.md +53 -84
- package/bin/cli.js +1717 -6
- package/components.json +18 -18
- package/package.json +98 -109
- package/postcss.config.js +3 -3
- package/scripts/install.ps1 +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/postinstall.js +47 -58
- package/shared/ai.ts +118 -202
- package/shared/config.ts +37 -39
- package/shared/logger.ts +6 -14
- package/shared/paths.ts +10 -16
- package/shared/relay.ts +89 -98
- package/supervisor/backend.ts +77 -89
- package/supervisor/chat/ARCHITECTURE.md +13 -14
- package/supervisor/chat/OnboardWizard.tsx +2194 -3419
- package/supervisor/chat/fluxy-main.tsx +495 -633
- package/supervisor/chat/fluxy.html +24 -36
- package/supervisor/chat/onboard-main.tsx +8 -8
- package/supervisor/chat/onboard.html +10 -13
- package/supervisor/chat/src/components/Chat/AudioBubble.tsx +94 -108
- package/supervisor/chat/src/components/Chat/ChatView.tsx +14 -36
- package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +71 -89
- package/supervisor/chat/src/components/Chat/InputBar.tsx +498 -728
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +154 -246
- package/supervisor/chat/src/components/Chat/MessageList.tsx +114 -138
- package/supervisor/chat/src/components/Chat/TypingIndicator.tsx +38 -50
- package/supervisor/chat/src/components/LoginScreen.tsx +245 -296
- package/supervisor/chat/src/hooks/useChat.ts +210 -259
- package/supervisor/chat/src/hooks/useFluxyChat.ts +232 -322
- package/supervisor/chat/src/hooks/useSpeechRecognition.ts +126 -148
- package/supervisor/chat/src/lib/auth.ts +20 -23
- package/supervisor/chat/src/lib/ws-client.ts +113 -125
- package/supervisor/chat/src/styles/globals.css +76 -85
- package/supervisor/file-saver.ts +39 -52
- package/supervisor/fluxy-agent.ts +213 -292
- package/supervisor/index.ts +895 -1263
- package/supervisor/scheduler.ts +237 -299
- package/supervisor/tunnel.ts +113 -167
- package/supervisor/vite-dev.ts +51 -61
- package/supervisor/widget.js +135 -147
- package/supervisor/worker.ts +45 -49
- package/tsconfig.json +18 -24
- package/vite.config.ts +48 -55
- package/vite.fluxy.config.ts +34 -34
- package/worker/claude-auth.ts +202 -265
- package/worker/codex-auth.ts +144 -181
- package/worker/db.ts +63 -191
- package/worker/index.ts +601 -842
- package/workspace/MYSELF.md +1 -1
- package/workspace/PULSE.json +6 -6
- package/workspace/backend/index.ts +16 -19
- package/workspace/client/index.html +47 -60
- package/workspace/client/public/manifest.json +21 -21
- package/workspace/client/public/sw.js +29 -31
- package/workspace/client/src/App.tsx +91 -136
- package/workspace/client/src/components/Dashboard/DashboardPage.tsx +68 -72
- package/workspace/client/src/components/ErrorBoundary.tsx +11 -11
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +35 -35
- package/workspace/client/src/components/Layout/Footer.tsx +12 -12
- package/workspace/client/src/components/Layout/MobileNav.tsx +23 -23
- package/workspace/client/src/components/Layout/Sidebar.tsx +81 -81
- package/workspace/client/src/components/ui/avatar.tsx +85 -85
- package/workspace/client/src/components/ui/badge.tsx +40 -41
- package/workspace/client/src/components/ui/button.tsx +56 -57
- package/workspace/client/src/components/ui/card.tsx +75 -75
- package/workspace/client/src/components/ui/dialog.tsx +114 -117
- package/workspace/client/src/components/ui/dropdown-menu.tsx +191 -199
- package/workspace/client/src/components/ui/input.tsx +17 -17
- package/workspace/client/src/components/ui/scroll-area.tsx +47 -47
- package/workspace/client/src/components/ui/select.tsx +145 -151
- package/workspace/client/src/components/ui/separator.tsx +21 -21
- package/workspace/client/src/components/ui/sheet.tsx +102 -102
- package/workspace/client/src/components/ui/skeleton.tsx +10 -10
- package/workspace/client/src/components/ui/switch.tsx +26 -26
- package/workspace/client/src/components/ui/tabs.tsx +69 -69
- package/workspace/client/src/components/ui/textarea.tsx +14 -14
- package/workspace/client/src/components/ui/tooltip.tsx +37 -37
- package/workspace/client/src/lib/utils.ts +1 -1
- package/workspace/client/src/main.tsx +3 -3
- package/workspace/client/src/styles/globals.css +76 -85
- package/workspace/skills/code-reviewer/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/code-reviewer/skills/code-reviewer/SKILL.md +0 -6
- package/workspace/skills/daily-standup/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/daily-standup/skills/daily-standup/SKILL.md +0 -7
- package/workspace/skills/workspace-helper/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/workspace-helper/skills/workspace-helper/SKILL.md +0 -2
- package/bin/cli.backup.js +0 -2138
- package/cli/commands/daemon.ts +0 -42
- package/cli/commands/init.ts +0 -32
- package/cli/commands/start.ts +0 -113
- package/cli/commands/tunnel.temp.ts +0 -206
- package/cli/commands/tunnel.ts +0 -227
- package/cli/commands/update.ts +0 -163
- package/cli/core/base-adapter.ts +0 -156
- package/cli/core/cloudflared.ts +0 -113
- package/cli/core/config.ts +0 -73
- package/cli/core/os-detector.ts +0 -43
- package/cli/core/server.ts +0 -109
- package/cli/core/types.ts +0 -15
- package/cli/index.ts +0 -72
- package/cli/platforms/darwin.ts +0 -146
- package/cli/platforms/index.ts +0 -21
- package/cli/platforms/linux.ts +0 -156
- package/cli/platforms/win32.ts +0 -34
- package/cli/utils/ui.ts +0 -37
package/shared/relay.ts
CHANGED
|
@@ -5,99 +5,90 @@ const RELAY_API = 'https://api.fluxy.bot/api';
|
|
|
5
5
|
// ─── Register a new handle ──────────────────────────────────────────────────
|
|
6
6
|
|
|
7
7
|
export async function registerHandle(
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
username: string,
|
|
9
|
+
tier: string,
|
|
10
10
|
): Promise<{ token: string; relayUrl: string }> {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const res = await fetch(`${RELAY_API}/register`, {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers: { 'Content-Type': 'application/json' },
|
|
14
|
+
body: JSON.stringify({ username, tier }),
|
|
15
|
+
});
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const data = await res.json();
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
throw new Error(data.error || `Registration failed (${res.status})`);
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
return { token: data.token, relayUrl: data.relayUrl };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// ─── Check username availability ─────────────────────────────────────────────
|
|
27
27
|
|
|
28
|
-
export async function checkAvailability(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
paid: boolean;
|
|
35
|
-
price: number;
|
|
36
|
-
available: boolean;
|
|
37
|
-
}[];
|
|
28
|
+
export async function checkAvailability(
|
|
29
|
+
username: string,
|
|
30
|
+
): Promise<{
|
|
31
|
+
valid: boolean;
|
|
32
|
+
error?: string;
|
|
33
|
+
handles: { tier: string; url: string; paid: boolean; price: number; available: boolean }[];
|
|
38
34
|
}> {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
42
|
-
return res.json();
|
|
35
|
+
const res = await fetch(`${RELAY_API}/availability/${encodeURIComponent(username)}`);
|
|
36
|
+
return res.json();
|
|
43
37
|
}
|
|
44
38
|
|
|
45
39
|
// ─── Claim a reserved (purchased) handle ────────────────────────────────────
|
|
46
40
|
|
|
47
41
|
export async function claimReservedHandle(
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
handle: string,
|
|
43
|
+
hash: string,
|
|
50
44
|
): Promise<{ token: string; relayUrl: string }> {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
const res = await fetch(`${RELAY_API}/handle/claim-reserved`, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: JSON.stringify({ handle, hash }),
|
|
49
|
+
});
|
|
56
50
|
|
|
57
|
-
|
|
51
|
+
const data = await res.json();
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
throw new Error(data.error || `Claim failed (${res.status})`);
|
|
55
|
+
}
|
|
62
56
|
|
|
63
|
-
|
|
57
|
+
return { token: data.token, relayUrl: data.relayUrl };
|
|
64
58
|
}
|
|
65
59
|
|
|
66
60
|
// ─── Release handle ─────────────────────────────────────────────────────────
|
|
67
61
|
|
|
68
62
|
export async function releaseHandle(token: string): Promise<void> {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
63
|
+
const res = await fetch(`${RELAY_API}/handle`, {
|
|
64
|
+
method: 'DELETE',
|
|
65
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
const data = await res.json().catch(() => ({}));
|
|
70
|
+
throw new Error(data.error || `Release failed (${res.status})`);
|
|
71
|
+
}
|
|
78
72
|
}
|
|
79
73
|
|
|
80
74
|
// ─── Update tunnel URL ──────────────────────────────────────────────────────
|
|
81
75
|
|
|
82
|
-
export async function updateTunnelUrl(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
log.ok('Relay updated with tunnel URL');
|
|
76
|
+
export async function updateTunnelUrl(token: string, tunnelUrl: string): Promise<void> {
|
|
77
|
+
const res = await fetch(`${RELAY_API}/tunnel`, {
|
|
78
|
+
method: 'PUT',
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
Authorization: `Bearer ${token}`,
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({ tunnelUrl }),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
const data = await res.json().catch(() => ({}));
|
|
88
|
+
throw new Error(data.error || `Tunnel update failed (${res.status})`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
log.ok('Relay updated with tunnel URL');
|
|
101
92
|
}
|
|
102
93
|
|
|
103
94
|
// ─── Heartbeat ───────────────────────────────────────────────────────────────
|
|
@@ -105,45 +96,45 @@ export async function updateTunnelUrl(
|
|
|
105
96
|
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
106
97
|
|
|
107
98
|
export function startHeartbeat(token: string, tunnelUrl?: string): void {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
99
|
+
stopHeartbeat();
|
|
100
|
+
|
|
101
|
+
const beat = async () => {
|
|
102
|
+
try {
|
|
103
|
+
await fetch(`${RELAY_API}/heartbeat`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'application/json',
|
|
107
|
+
Authorization: `Bearer ${token}`,
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify(tunnelUrl ? { tunnelUrl } : {}),
|
|
110
|
+
});
|
|
111
|
+
} catch {
|
|
112
|
+
// Silent — next heartbeat will retry
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
beat(); // immediate first beat
|
|
117
|
+
heartbeatTimer = setInterval(beat, 30_000); // every 30s
|
|
127
118
|
}
|
|
128
119
|
|
|
129
120
|
export function stopHeartbeat(): void {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
121
|
+
if (heartbeatTimer) {
|
|
122
|
+
clearInterval(heartbeatTimer);
|
|
123
|
+
heartbeatTimer = null;
|
|
124
|
+
}
|
|
134
125
|
}
|
|
135
126
|
|
|
136
127
|
// ─── Disconnect (graceful shutdown) ──────────────────────────────────────────
|
|
137
128
|
|
|
138
129
|
export async function disconnect(token: string): Promise<void> {
|
|
139
|
-
|
|
130
|
+
stopHeartbeat();
|
|
140
131
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
132
|
+
try {
|
|
133
|
+
await fetch(`${RELAY_API}/disconnect`, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
136
|
+
});
|
|
137
|
+
} catch {
|
|
138
|
+
// Best-effort — if it fails, heartbeat timeout will mark offline
|
|
139
|
+
}
|
|
149
140
|
}
|
package/supervisor/backend.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from 'child_process';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { PKG_DIR
|
|
4
|
+
import { PKG_DIR } from '../shared/paths.js';
|
|
5
5
|
import { log } from '../shared/logger.js';
|
|
6
6
|
|
|
7
7
|
let child: ChildProcess | null = null;
|
|
@@ -11,108 +11,96 @@ let intentionallyStopped = false;
|
|
|
11
11
|
const MAX_RESTARTS = 3;
|
|
12
12
|
const STABLE_THRESHOLD = 30_000; // 30s — if backend ran this long, it wasn't a crash loop
|
|
13
13
|
|
|
14
|
-
const LOG_FILE = path.join(
|
|
14
|
+
const LOG_FILE = path.join(PKG_DIR, 'workspace', '.backend.log');
|
|
15
15
|
|
|
16
16
|
export function getBackendPort(basePort: number): number {
|
|
17
|
-
|
|
17
|
+
return basePort + 4;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function spawnBackend(port: number): ChildProcess {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
restarts++;
|
|
77
|
-
const delay = Math.min(1000 * restarts, 5000);
|
|
78
|
-
log.info(
|
|
79
|
-
`Restarting backend (attempt ${restarts}, delay ${delay}ms)...`
|
|
80
|
-
);
|
|
81
|
-
setTimeout(() => spawnBackend(port), delay);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
log.ok(`Backend spawned on port ${port}`);
|
|
85
|
-
return child;
|
|
21
|
+
const backendPath = path.join(PKG_DIR, 'workspace', 'backend', 'index.ts');
|
|
22
|
+
lastSpawnTime = Date.now();
|
|
23
|
+
intentionallyStopped = false;
|
|
24
|
+
|
|
25
|
+
// Clear log file on each restart — only keeps current run
|
|
26
|
+
try { fs.writeFileSync(LOG_FILE, ''); } catch {}
|
|
27
|
+
|
|
28
|
+
// Wrap the backend in an inline loader that:
|
|
29
|
+
// 1. Dynamically imports the user's backend (tsx handles .ts compilation)
|
|
30
|
+
// 2. Adds a keepalive timer so the event loop never drains (fixes exit code 0 under systemd)
|
|
31
|
+
// 3. Catches and logs import errors instead of silently exiting
|
|
32
|
+
const backendUrl = 'file://' + backendPath.replace(/\\/g, '/');
|
|
33
|
+
const wrapper = [
|
|
34
|
+
`import('${backendUrl}')`,
|
|
35
|
+
` .catch(e => { console.error('[backend] Fatal:', e); process.exit(1); });`,
|
|
36
|
+
`setInterval(() => {}, 60000);`,
|
|
37
|
+
].join('\n');
|
|
38
|
+
|
|
39
|
+
child = spawn(process.execPath, ['--import', 'tsx/esm', '--input-type=module', '-e', wrapper], {
|
|
40
|
+
cwd: path.join(PKG_DIR, 'workspace'),
|
|
41
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
42
|
+
env: { ...process.env, BACKEND_PORT: String(port) },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.stdout?.on('data', (d) => {
|
|
46
|
+
process.stdout.write(d);
|
|
47
|
+
try { fs.appendFileSync(LOG_FILE, d); } catch {}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
child.stderr?.on('data', (d) => {
|
|
51
|
+
process.stderr.write(d);
|
|
52
|
+
try { fs.appendFileSync(LOG_FILE, d); } catch {}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
child.on('exit', (code) => {
|
|
56
|
+
// Supervisor called stopBackend() — don't auto-restart
|
|
57
|
+
if (intentionallyStopped) return;
|
|
58
|
+
|
|
59
|
+
// Any unexpected exit (crash, SIGTERM, OOM, null code) — restart
|
|
60
|
+
log.warn(`Backend exited unexpectedly (code ${code})`);
|
|
61
|
+
// If backend was alive for >30s, it's not a crash loop — reset counter
|
|
62
|
+
if (Date.now() - lastSpawnTime > STABLE_THRESHOLD) {
|
|
63
|
+
restarts = 0;
|
|
64
|
+
}
|
|
65
|
+
if (restarts < MAX_RESTARTS) {
|
|
66
|
+
restarts++;
|
|
67
|
+
log.info(`Restarting backend (${restarts}/${MAX_RESTARTS})...`);
|
|
68
|
+
setTimeout(() => spawnBackend(port), 1000 * restarts);
|
|
69
|
+
} else {
|
|
70
|
+
log.error('Backend failed too many times. Use Fluxy chat to debug.');
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
log.ok(`Backend spawned on port ${port}`);
|
|
75
|
+
return child;
|
|
86
76
|
}
|
|
87
77
|
|
|
88
78
|
/** Stop the backend and wait for the process to fully exit before resolving.
|
|
89
79
|
* This prevents port collisions when restarting (old process must release the port first). */
|
|
90
80
|
export function stopBackend(): Promise<void> {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}, 3000);
|
|
109
|
-
});
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
if (!child || child.exitCode !== null) {
|
|
83
|
+
child = null;
|
|
84
|
+
resolve();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
intentionallyStopped = true;
|
|
88
|
+
const dying = child;
|
|
89
|
+
child = null;
|
|
90
|
+
dying.once('exit', () => resolve());
|
|
91
|
+
dying.kill();
|
|
92
|
+
// Safety: force kill after 3s if SIGTERM doesn't work
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
try { dying.kill('SIGKILL'); } catch {}
|
|
95
|
+
resolve();
|
|
96
|
+
}, 3000);
|
|
97
|
+
});
|
|
110
98
|
}
|
|
111
99
|
|
|
112
100
|
export function isBackendAlive(): boolean {
|
|
113
|
-
|
|
101
|
+
return child !== null && child.exitCode === null;
|
|
114
102
|
}
|
|
115
103
|
|
|
116
104
|
export function resetBackendRestarts(): void {
|
|
117
|
-
|
|
105
|
+
restarts = 0;
|
|
118
106
|
}
|
|
@@ -48,7 +48,6 @@ When a user registers a handle (e.g. `bug.at.fluxy.bot`), the relay proxies all
|
|
|
48
48
|
However, **POST requests from the chat iframe fail** (502 / timeout). The relay + tunnel chain cannot reliably forward HTTP POST bodies originating from an iframe context. The exact cause is in the relay/tunnel infrastructure — the request never reaches the supervisor.
|
|
49
49
|
|
|
50
50
|
This does NOT affect:
|
|
51
|
-
|
|
52
51
|
- The initial onboard (`/fluxy/onboard.html` in a separate iframe, runs before the relay is configured)
|
|
53
52
|
- Direct `localhost` access
|
|
54
53
|
- GET requests from the chat iframe
|
|
@@ -72,19 +71,19 @@ The supervisor's WebSocket handler receives the `settings:save` message and make
|
|
|
72
71
|
|
|
73
72
|
### When to use WebSocket vs HTTP
|
|
74
73
|
|
|
75
|
-
| From where
|
|
76
|
-
|
|
77
|
-
| Initial onboard | `fetch()`
|
|
78
|
-
| Chat iframe
|
|
79
|
-
| Dashboard
|
|
74
|
+
| From where | GET | POST/PUT | Notes |
|
|
75
|
+
|---|---|---|---|
|
|
76
|
+
| Initial onboard | `fetch()` | `fetch()` | Runs before relay exists, direct tunnel works |
|
|
77
|
+
| Chat iframe | `fetch()` / `authFetch()` | **WebSocket** | POST through relay fails |
|
|
78
|
+
| Dashboard | `fetch()` | `fetch()` | Not in an iframe, relay handles it fine |
|
|
80
79
|
|
|
81
80
|
## Key Files
|
|
82
81
|
|
|
83
|
-
| File
|
|
84
|
-
|
|
85
|
-
| `supervisor/widget.js`
|
|
86
|
-
| `supervisor/chat/fluxy-main.tsx`
|
|
87
|
-
| `supervisor/chat/OnboardWizard.tsx`
|
|
88
|
-
| `supervisor/chat/src/lib/ws-client.ts` | WebSocket client with reconnect + message queue
|
|
89
|
-
| `supervisor/index.ts`
|
|
90
|
-
| `worker/index.ts`
|
|
82
|
+
| File | Purpose |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `supervisor/widget.js` | Creates the chat iframe + bubble on the dashboard |
|
|
85
|
+
| `supervisor/chat/fluxy-main.tsx` | Chat app entry point, WS connection, wizard integration |
|
|
86
|
+
| `supervisor/chat/OnboardWizard.tsx` | Setup wizard (uses `onSave` prop for WS saves) |
|
|
87
|
+
| `supervisor/chat/src/lib/ws-client.ts` | WebSocket client with reconnect + message queue |
|
|
88
|
+
| `supervisor/index.ts` | Supervisor HTTP server + WS handler (`settings:save`) |
|
|
89
|
+
| `worker/index.ts` | Worker API (`POST /api/onboard`, settings DB) |
|