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 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 || 3000;
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: 3000,
723
+ port: 7400,
724
724
  username: '',
725
725
  ai: { provider: '', model: '', apiKey: '' },
726
726
  tunnel: { mode: 'quick' },
@@ -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 || 3000}`)}`);
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)}`);
@@ -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 = 3000 } = safeLoadConfig();
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
 
@@ -39,7 +39,7 @@ export function createConfig() {
39
39
  if (fs.existsSync(CONFIG_PATH)) return;
40
40
 
41
41
  const config: Partial<BotConfig> = {
42
- port: 3000,
42
+ port: 7400,
43
43
  username: '',
44
44
  ai: { provider: '', model: '', apiKey: '' },
45
45
  tunnel: { mode: 'quick' },
@@ -49,7 +49,7 @@ export function bootServer({
49
49
 
50
50
  resolve({
51
51
  child,
52
- tunnelUrl: tunnelUrl || `http://localhost:${config.port || 3000}`,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bloby-bot",
3
- "version": "0.21.12",
3
+ "version": "0.22.0",
4
4
  "releaseNotes": [
5
5
  "1. react router implemented",
6
6
  "2. new workspace design",
package/shared/config.ts CHANGED
@@ -42,7 +42,7 @@ export interface BotConfig {
42
42
  }
43
43
 
44
44
  const DEFAULTS: BotConfig = {
45
- port: 3000,
45
+ port: 7400,
46
46
  username: '',
47
47
  ai: { provider: '', model: '', apiKey: '' },
48
48
  tunnel: { mode: 'quick' },
@@ -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 3000. It strips the `/app` prefix before forwarding to the backend, preserving the `/api/` path.
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:3000\/api\/channels\/whatsapp\/qr-page`/g,
33
- '[pair-whatsapp](http://localhost:3000/api/channels/whatsapp/qr-page)'
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:3000' : location.host;
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
  }
@@ -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:3004',
27
+ target: 'http://localhost:7404',
28
28
  rewrite: (path) => path.replace(/^\/app/, ''),
29
29
  },
30
- '/api': 'http://localhost:3000',
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:3000/api/channels/whatsapp/configure \
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:3000/api/channels/whatsapp/connect`
361
- 2. Tell them to open the QR page: `http://localhost:3000/api/channels/whatsapp/qr-page` (Don't mention the URL until you are actually starting the connection)
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:3000/api/channels/whatsapp/configure \
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:3000/api/channels/send \
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:3000`.
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 3000. It strips the `/app` prefix before forwarding to the backend, preserving the `/api/` path.
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 || '3004', 10);
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)