bloby-bot 0.21.12 → 0.22.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/bin/cli.js +2 -2
- package/cli/commands/start.ts +1 -1
- package/cli/commands/tunnel.ts +1 -1
- package/cli/core/config.ts +1 -1
- package/cli/core/server.ts +1 -1
- package/package.json +1 -1
- package/shared/config.ts +1 -1
- package/supervisor/agents/prompts/coder.txt +1 -1
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +2 -2
- package/supervisor/chat/src/lib/ws-client.ts +1 -1
- package/supervisor/index.ts +35 -0
- package/vite.config.ts +2 -2
- package/worker/prompts/bloby-system-prompt.txt +7 -7
- package/workspace/backend/index.ts +1 -1
package/bin/cli.js
CHANGED
|
@@ -431,7 +431,7 @@ async function runNamedTunnelSetup() {
|
|
|
431
431
|
|
|
432
432
|
// Generate cloudflared config
|
|
433
433
|
const config = fs.existsSync(CONFIG_PATH) ? JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')) : {};
|
|
434
|
-
const port = config.port ||
|
|
434
|
+
const port = config.port || 7400;
|
|
435
435
|
const cfHome = path.join(os.homedir(), '.cloudflared');
|
|
436
436
|
const cfConfigPath = path.join(DATA_DIR, 'cloudflared-config.yml');
|
|
437
437
|
|
|
@@ -720,7 +720,7 @@ function createConfig() {
|
|
|
720
720
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
721
721
|
if (!fs.existsSync(CONFIG_PATH)) {
|
|
722
722
|
const config = {
|
|
723
|
-
port:
|
|
723
|
+
port: 7400,
|
|
724
724
|
username: '',
|
|
725
725
|
ai: { provider: '', model: '', apiKey: '' },
|
|
726
726
|
tunnel: { mode: 'quick' },
|
package/cli/commands/start.ts
CHANGED
|
@@ -64,7 +64,7 @@ export function registerStartCommand(program: Command) {
|
|
|
64
64
|
s.stop(pc.green('Server running'));
|
|
65
65
|
|
|
66
66
|
console.log(`\n${pc.bold('Bloby is ready!')}`);
|
|
67
|
-
console.log(` ${pc.dim('Local:')} ${pc.blue(`http://localhost:${config.port ||
|
|
67
|
+
console.log(` ${pc.dim('Local:')} ${pc.blue(`http://localhost:${config.port || 7400}`)}`);
|
|
68
68
|
|
|
69
69
|
if (result.tunnelUrl && hasTunnel) {
|
|
70
70
|
console.log(` ${pc.dim('Tunnel:')} ${pc.blue(result.tunnelUrl)}`);
|
package/cli/commands/tunnel.ts
CHANGED
|
@@ -54,7 +54,7 @@ async function runNamedTunnelSetup() {
|
|
|
54
54
|
const domain = await text({ message: 'Your domain (e.g. bot.mydomain.com):' });
|
|
55
55
|
if (isCancel(domain) || !domain) { cancel('Domain is required.'); process.exit(1); }
|
|
56
56
|
|
|
57
|
-
const { port =
|
|
57
|
+
const { port = 7400 } = safeLoadConfig();
|
|
58
58
|
const cfHome = path.join(os.homedir(), '.cloudflared');
|
|
59
59
|
const cfConfigPath = path.join(DATA_DIR, 'cloudflared-config.yml');
|
|
60
60
|
|
package/cli/core/config.ts
CHANGED
package/cli/core/server.ts
CHANGED
|
@@ -49,7 +49,7 @@ export function bootServer({
|
|
|
49
49
|
|
|
50
50
|
resolve({
|
|
51
51
|
child,
|
|
52
|
-
tunnelUrl: tunnelUrl || `http://localhost:${config.port ||
|
|
52
|
+
tunnelUrl: tunnelUrl || `http://localhost:${config.port || 7400}`,
|
|
53
53
|
relayUrl: relayUrl || config.relay?.url || null,
|
|
54
54
|
tunnelFailed,
|
|
55
55
|
viteWarm,
|
package/package.json
CHANGED
package/shared/config.ts
CHANGED
|
@@ -68,7 +68,7 @@ Your working directory is the `workspace/` folder. This is your full-stack works
|
|
|
68
68
|
|
|
69
69
|
## Backend Routing (Critical)
|
|
70
70
|
|
|
71
|
-
A supervisor process sits in front of everything on port
|
|
71
|
+
A supervisor process sits in front of everything on port 7400. It strips the `/app` prefix before forwarding to the backend, preserving the `/api/` path.
|
|
72
72
|
|
|
73
73
|
```
|
|
74
74
|
Browser: GET /app/api/tasks → Supervisor strips /app → Backend receives: GET /api/tasks
|
|
@@ -29,8 +29,8 @@ function formatTime(iso: string): string {
|
|
|
29
29
|
/** Convert backtick-wrapped WhatsApp QR URL into a markdown link */
|
|
30
30
|
function preprocessContent(text: string): string {
|
|
31
31
|
return text.replace(
|
|
32
|
-
/`http:\/\/localhost
|
|
33
|
-
'[pair-whatsapp](
|
|
32
|
+
/`http:\/\/localhost:\d+\/api\/channels\/whatsapp\/qr-page`/g,
|
|
33
|
+
'[pair-whatsapp](/api/channels/whatsapp/qr-page)'
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -21,7 +21,7 @@ export class WsClient {
|
|
|
21
21
|
|
|
22
22
|
constructor(url?: string, tokenGetter?: (() => string | null) | null) {
|
|
23
23
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
24
|
-
const host = import.meta.env.DEV ? 'localhost:
|
|
24
|
+
const host = import.meta.env.DEV ? 'localhost:7400' : location.host;
|
|
25
25
|
this.url = url ?? `${proto}//${host}/ws`;
|
|
26
26
|
this.tokenGetter = tokenGetter ?? null;
|
|
27
27
|
}
|
package/supervisor/index.ts
CHANGED
|
@@ -215,11 +215,35 @@ const RECOVERING_HTML = `<!DOCTYPE html><html style="background:#222122"><head><
|
|
|
215
215
|
</div><script>setTimeout(function(){location.reload()},3000)</script>
|
|
216
216
|
<script src="/bloby/widget.js"></script></body></html>`;
|
|
217
217
|
|
|
218
|
+
/** Kill any stale process holding a port. Ensures clean startup after crashes/updates. */
|
|
219
|
+
function killPort(port: number): void {
|
|
220
|
+
try {
|
|
221
|
+
const pids = execSync(`lsof -ti :${port} 2>/dev/null`, { encoding: 'utf-8' }).trim();
|
|
222
|
+
if (pids) {
|
|
223
|
+
const pidList = pids.split('\n').filter(Boolean);
|
|
224
|
+
const ownPid = process.pid.toString();
|
|
225
|
+
const toKill = pidList.filter((p) => p !== ownPid);
|
|
226
|
+
if (toKill.length) {
|
|
227
|
+
log.info(`[startup] Killing stale process(es) on port ${port}: ${toKill.join(', ')}`);
|
|
228
|
+
execSync(`kill -9 ${toKill.join(' ')} 2>/dev/null`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
// No process on port, or kill failed (already dead) — fine
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
218
236
|
export async function startSupervisor() {
|
|
219
237
|
const config = loadConfig();
|
|
220
238
|
const backendPort = getBackendPort(config.port);
|
|
221
239
|
const internalSecret = crypto.randomBytes(16).toString('hex');
|
|
222
240
|
|
|
241
|
+
// Kill any stale processes from previous crashes/updates
|
|
242
|
+
log.info(`[startup] Clearing ports ${config.port}, ${config.port + 2}, ${backendPort}...`);
|
|
243
|
+
killPort(config.port); // supervisor
|
|
244
|
+
killPort(config.port + 2); // vite
|
|
245
|
+
killPort(backendPort); // backend
|
|
246
|
+
|
|
223
247
|
// Create HTTP server first (Vite needs it for HMR WebSocket)
|
|
224
248
|
// The request handler is set up later via server.on('request')
|
|
225
249
|
const server = http.createServer();
|
|
@@ -1184,6 +1208,10 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
1184
1208
|
}
|
|
1185
1209
|
if (pendingUpdate) {
|
|
1186
1210
|
pendingUpdate = false;
|
|
1211
|
+
// End the live conversation before updating — SDK child processes
|
|
1212
|
+
// must be cleaned up or process.exit() may not terminate cleanly
|
|
1213
|
+
log.info('[orchestrator] Ending conversation before update...');
|
|
1214
|
+
endConversation(convId);
|
|
1187
1215
|
runDeferredUpdate();
|
|
1188
1216
|
}
|
|
1189
1217
|
// Tell the client the agent is idle — streaming can stop
|
|
@@ -1487,6 +1515,13 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
1487
1515
|
pendingUpdate = true;
|
|
1488
1516
|
log.info('Update requested — deferring until agent turn ends');
|
|
1489
1517
|
} else {
|
|
1518
|
+
// End any live conversations before updating
|
|
1519
|
+
for (const cid of Array.from(clientConvs.values())) {
|
|
1520
|
+
if (hasConversation(cid)) {
|
|
1521
|
+
log.info(`[update] Ending conversation ${cid} before update`);
|
|
1522
|
+
endConversation(cid);
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1490
1525
|
runDeferredUpdate();
|
|
1491
1526
|
}
|
|
1492
1527
|
}
|
package/vite.config.ts
CHANGED
|
@@ -24,10 +24,10 @@ export default defineConfig({
|
|
|
24
24
|
port: 5173,
|
|
25
25
|
proxy: {
|
|
26
26
|
'/app/api': {
|
|
27
|
-
target: 'http://localhost:
|
|
27
|
+
target: 'http://localhost:7404',
|
|
28
28
|
rewrite: (path) => path.replace(/^\/app/, ''),
|
|
29
29
|
},
|
|
30
|
-
'/api': 'http://localhost:
|
|
30
|
+
'/api': 'http://localhost:7400',
|
|
31
31
|
},
|
|
32
32
|
warmup: {
|
|
33
33
|
clientFiles: ['./src/main.tsx'],
|
|
@@ -278,7 +278,7 @@ skills/
|
|
|
278
278
|
|
|
279
279
|
Only ONE skill can be active for customer-facing mode at a time. The active skill is set in the channel config (`channels.whatsapp.skill`). When your human asks to switch skills, update the config:
|
|
280
280
|
```bash
|
|
281
|
-
curl -s -X POST http://localhost:
|
|
281
|
+
curl -s -X POST http://localhost:7400/api/channels/whatsapp/configure \
|
|
282
282
|
-H "Content-Type: application/json" -d '{"skill":"whatsapp-clinic"}'
|
|
283
283
|
```
|
|
284
284
|
|
|
@@ -357,14 +357,14 @@ The format is: `[Channel | phone | role | name (optional)]`
|
|
|
357
357
|
### Setting Up WhatsApp
|
|
358
358
|
|
|
359
359
|
When your human asks to configure WhatsApp:
|
|
360
|
-
1. Start the connection: `curl -s -X POST http://localhost:
|
|
361
|
-
2. Tell them to open the QR page: `http://localhost:
|
|
360
|
+
1. Start the connection: `curl -s -X POST http://localhost:7400/api/channels/whatsapp/connect`
|
|
361
|
+
2. Tell them to open the QR page: `http://localhost:7400/api/channels/whatsapp/qr-page` (Don't mention the URL until you are actually starting the connection)
|
|
362
362
|
3. They scan the QR with their WhatsApp app
|
|
363
363
|
4. The default mode is **channel** (self-chat only)
|
|
364
364
|
|
|
365
365
|
To switch to **business mode** with admin numbers:
|
|
366
366
|
```bash
|
|
367
|
-
curl -s -X POST http://localhost:
|
|
367
|
+
curl -s -X POST http://localhost:7400/api/channels/whatsapp/configure \
|
|
368
368
|
-H "Content-Type: application/json" \
|
|
369
369
|
-d '{"mode":"business","admins":["+17865551234","+5511999887766"]}'
|
|
370
370
|
```
|
|
@@ -373,7 +373,7 @@ curl -s -X POST http://localhost:3000/api/channels/whatsapp/configure \
|
|
|
373
373
|
|
|
374
374
|
To INITIATE a WhatsApp message (during pulse, cron, or when you want to reach out first):
|
|
375
375
|
```bash
|
|
376
|
-
curl -s -X POST http://localhost:
|
|
376
|
+
curl -s -X POST http://localhost:7400/api/channels/send \
|
|
377
377
|
-H "Content-Type: application/json" \
|
|
378
378
|
-d '{"channel":"whatsapp","to":"5511999888777","text":"Your appointment is confirmed for tomorrow at 2pm."}'
|
|
379
379
|
```
|
|
@@ -403,7 +403,7 @@ This is your memory of that customer. Next time they message, read their file fi
|
|
|
403
403
|
| `/api/channels/whatsapp/configure` | POST | Set mode + admins array |
|
|
404
404
|
| `/api/channels/send` | POST | Send proactive message via any channel |
|
|
405
405
|
|
|
406
|
-
All endpoints are on `http://localhost:
|
|
406
|
+
All endpoints are on `http://localhost:7400`.
|
|
407
407
|
|
|
408
408
|
---
|
|
409
409
|
|
|
@@ -480,7 +480,7 @@ Rules:
|
|
|
480
480
|
|
|
481
481
|
## Backend Routing (Critical)
|
|
482
482
|
|
|
483
|
-
A supervisor process sits in front of everything on port
|
|
483
|
+
A supervisor process sits in front of everything on port 7400. It strips the `/app` prefix before forwarding to the backend, preserving the `/api/` path.
|
|
484
484
|
|
|
485
485
|
```
|
|
486
486
|
Browser: GET /app/api/tasks → Supervisor strips /app → Backend receives: GET /api/tasks
|
|
@@ -3,7 +3,7 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
|
|
6
|
-
const PORT = parseInt(process.env.BACKEND_PORT || '
|
|
6
|
+
const PORT = parseInt(process.env.BACKEND_PORT || '7404', 10);
|
|
7
7
|
const WORKSPACE = path.resolve(import.meta.dirname, '..');
|
|
8
8
|
|
|
9
9
|
// Load workspace/.env manually (no dotenv dep needed)
|