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