bloby-bot 0.20.8 → 0.21.1
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/dist-bloby/assets/{bloby-C2KDOC_1.js → bloby-DQ36AQA0.js} +4 -4
- package/dist-bloby/assets/{highlighted-body-OFNGDK62-CdUBnqzY.js → highlighted-body-OFNGDK62-CPyk9gZ2.js} +1 -1
- package/dist-bloby/assets/mermaid-GHXKKRXX-BbN_HY2S.js +1 -0
- package/dist-bloby/bloby.html +1 -1
- package/package.json +1 -1
- package/supervisor/bloby-agent.ts +430 -149
- package/supervisor/channels/manager.ts +22 -22
- package/supervisor/chat/src/components/Chat/InputBar.tsx +1 -1
- package/supervisor/index.ts +103 -67
- package/worker/prompts/bloby-system-prompt.txt +1 -1
- package/dist-bloby/assets/mermaid-GHXKKRXX-mjSiQkZC.js +0 -1
|
@@ -20,7 +20,7 @@ import path from 'path';
|
|
|
20
20
|
import { loadConfig } from '../../shared/config.js';
|
|
21
21
|
import { WORKSPACE_DIR } from '../../shared/paths.js';
|
|
22
22
|
import { log } from '../../shared/logger.js';
|
|
23
|
-
import { startBlobyAgentQuery, type RecentMessage } from '../bloby-agent.js';
|
|
23
|
+
import { startBlobyAgentQuery, startConversation, pushMessage, hasConversation, type RecentMessage } from '../bloby-agent.js';
|
|
24
24
|
import { WhatsAppChannel } from './whatsapp.js';
|
|
25
25
|
import type { ChannelConfig, ChannelProvider, ChannelStatus, ChannelType, InboundMessage, InboundMessageAttachment, SenderRole } from './types.js';
|
|
26
26
|
import type { AgentAttachment } from '../bloby-agent.js';
|
|
@@ -392,14 +392,14 @@ export class ChannelManager {
|
|
|
392
392
|
// Show "typing..." while the agent processes
|
|
393
393
|
this.startTyping(msg.channel, msg.rawSender);
|
|
394
394
|
|
|
395
|
-
// Track text chunks for WhatsApp —
|
|
395
|
+
// Track text chunks for WhatsApp — lives for the conversation lifetime
|
|
396
396
|
let waChunkBuf = '';
|
|
397
397
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
(type, eventData) => {
|
|
398
|
+
// Start a live conversation if one doesn't exist (shared with chat UI)
|
|
399
|
+
if (!hasConversation(convId)) {
|
|
400
|
+
log.info(`[channels] Starting live conversation for admin: ${convId}`);
|
|
401
|
+
|
|
402
|
+
await startConversation(convId, model, (type, eventData) => {
|
|
403
403
|
// Accumulate text tokens
|
|
404
404
|
if (type === 'bot:token' && eventData.token) {
|
|
405
405
|
waChunkBuf += eventData.token;
|
|
@@ -414,7 +414,7 @@ export class ChannelManager {
|
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
if (type === 'bot:response' && eventData.content) {
|
|
417
|
-
// Send remaining text
|
|
417
|
+
// Send remaining text
|
|
418
418
|
const remaining = waChunkBuf.trim();
|
|
419
419
|
if (remaining) {
|
|
420
420
|
this.sendMessage(msg.channel, msg.rawSender, remaining).catch((err) => {
|
|
@@ -431,22 +431,22 @@ export class ChannelManager {
|
|
|
431
431
|
}).catch(() => {});
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
-
//
|
|
435
|
-
if (type === 'bot:
|
|
436
|
-
broadcastBloby(type, eventData);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (type === 'bot:done' && eventData.usedFileTools) {
|
|
434
|
+
// Handle turn completion — restart backend if needed
|
|
435
|
+
if (type === 'bot:turn-complete' && eventData.usedFileTools) {
|
|
440
436
|
this.opts.restartBackend();
|
|
441
437
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
438
|
+
|
|
439
|
+
// Don't forward internal events to chat clients
|
|
440
|
+
if (type === 'bot:turn-complete' || type === 'bot:conversation-ended') return;
|
|
441
|
+
|
|
442
|
+
// Mirror streaming + task events to chat clients
|
|
443
|
+
broadcastBloby(type, eventData);
|
|
444
|
+
}, { botName, humanName }, recentMessages);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Push the message into the live conversation
|
|
448
|
+
const channelContent = channelContext + msg.text;
|
|
449
|
+
pushMessage(convId, channelContent, agentAttachments);
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
/** Handle message from a customer — runs support agent in parallel with conversation context */
|
|
@@ -441,7 +441,7 @@ export default function InputBar({ onSend, onStop, streaming, whisperEnabled, on
|
|
|
441
441
|
<Camera className="h-4 w-4" />
|
|
442
442
|
</button>
|
|
443
443
|
</div>
|
|
444
|
-
{streaming ? (
|
|
444
|
+
{streaming && !hasText ? (
|
|
445
445
|
<button
|
|
446
446
|
onClick={onStop}
|
|
447
447
|
className="flex items-center justify-center h-12 w-12 shrink-0 rounded-full bg-destructive text-destructive-foreground transition-colors"
|
package/supervisor/index.ts
CHANGED
|
@@ -13,7 +13,12 @@ import { createWorkerApp } from '../worker/index.js';
|
|
|
13
13
|
import { closeDb, getSession, getSetting } from '../worker/db.js';
|
|
14
14
|
import { spawnBackend, stopBackend, getBackendPort, isBackendAlive, isBackendStopping, resetBackendRestarts } from './backend.js';
|
|
15
15
|
import { updateTunnelUrl, startHeartbeat, stopHeartbeat, disconnect } from '../shared/relay.js';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
startConversation, pushMessage, hasConversation, endConversation,
|
|
18
|
+
isConversationBusy, stopSubAgentTask,
|
|
19
|
+
startBlobyAgentQuery, stopBlobyAgentQuery,
|
|
20
|
+
type RecentMessage,
|
|
21
|
+
} from './bloby-agent.js';
|
|
17
22
|
import { ensureFileDirs, saveAttachment, type SavedFile } from './file-saver.js';
|
|
18
23
|
import { startViteDevServers, stopViteDevServers } from './vite-dev.js';
|
|
19
24
|
import { startScheduler, stopScheduler } from './scheduler.js';
|
|
@@ -1127,84 +1132,101 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
1127
1132
|
}
|
|
1128
1133
|
} catch {}
|
|
1129
1134
|
|
|
1130
|
-
// Mirror chat responses to WhatsApp self-chat (if connected)
|
|
1131
|
-
const waStatus = channelManager.getStatus('whatsapp');
|
|
1132
|
-
const waMirrorJid = waStatus?.connected ? waStatus.info?.phoneNumber : null;
|
|
1133
|
-
let waChunkBuf = '';
|
|
1134
|
-
|
|
1135
|
-
// Start orchestrator query (maxTurns: 8 — quick tasks direct, coding delegated)
|
|
1136
1135
|
log.info(`[orchestrator] ──── USER MESSAGE ────`);
|
|
1137
1136
|
log.info(`[orchestrator] Content: "${content.slice(0, 100)}..."`);
|
|
1138
|
-
log.info(`[orchestrator] Model: ${freshConfig.ai.model}`);
|
|
1139
1137
|
log.info(`[orchestrator] Conv: ${convId}`);
|
|
1140
|
-
log.info(`[orchestrator]
|
|
1141
|
-
agentQueryActive = true;
|
|
1142
|
-
currentStreamConvId = convId;
|
|
1143
|
-
currentStreamBuffer = '';
|
|
1144
|
-
startBlobyAgentQuery(convId, content, freshConfig.ai.model, (type, eventData) => {
|
|
1145
|
-
// Track stream buffer for reconnecting clients
|
|
1146
|
-
if (type === 'bot:token' && eventData.token) {
|
|
1147
|
-
currentStreamBuffer += eventData.token;
|
|
1148
|
-
if (waMirrorJid) waChunkBuf += eventData.token;
|
|
1149
|
-
}
|
|
1138
|
+
log.info(`[orchestrator] Live conversation exists: ${hasConversation(convId)}`);
|
|
1150
1139
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
waChunkBuf = '';
|
|
1155
|
-
}
|
|
1140
|
+
// Start a live conversation if one doesn't exist
|
|
1141
|
+
if (!hasConversation(convId)) {
|
|
1142
|
+
log.info(`[orchestrator] Starting new live conversation...`);
|
|
1156
1143
|
|
|
1157
|
-
//
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
stopBackend().then(() => spawnBackend(backendPort));
|
|
1171
|
-
}
|
|
1172
|
-
// Run deferred update if agent requested one
|
|
1173
|
-
if (pendingUpdate) {
|
|
1174
|
-
pendingUpdate = false;
|
|
1175
|
-
runDeferredUpdate();
|
|
1144
|
+
// WhatsApp mirror state — lives for the conversation lifetime
|
|
1145
|
+
let waChunkBuf = '';
|
|
1146
|
+
|
|
1147
|
+
await startConversation(convId, freshConfig.ai.model, (type, eventData) => {
|
|
1148
|
+
// Check WA mirror on each event (connection state may change)
|
|
1149
|
+
const waStatus = channelManager.getStatus('whatsapp');
|
|
1150
|
+
const waMirrorJid = waStatus?.connected ? waStatus.info?.phoneNumber : null;
|
|
1151
|
+
|
|
1152
|
+
// Track stream buffer for reconnecting clients
|
|
1153
|
+
if (type === 'bot:typing') {
|
|
1154
|
+
currentStreamConvId = convId;
|
|
1155
|
+
currentStreamBuffer = '';
|
|
1156
|
+
agentQueryActive = true;
|
|
1176
1157
|
}
|
|
1177
|
-
return; // don't forward bot:done to client
|
|
1178
|
-
}
|
|
1179
1158
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1159
|
+
if (type === 'bot:token' && eventData.token) {
|
|
1160
|
+
currentStreamBuffer += eventData.token;
|
|
1161
|
+
if (waMirrorJid) waChunkBuf += eventData.token;
|
|
1162
|
+
}
|
|
1183
1163
|
|
|
1184
|
-
// WhatsApp mirror: send
|
|
1185
|
-
if (waMirrorJid && waChunkBuf.trim()) {
|
|
1164
|
+
// WhatsApp mirror: send intermediate chunk when agent pauses for tool use
|
|
1165
|
+
if (type === 'bot:tool' && waMirrorJid && waChunkBuf.trim()) {
|
|
1186
1166
|
channelManager.sendMessage('whatsapp', `${waMirrorJid}@s.whatsapp.net`, waChunkBuf.trim()).catch(() => {});
|
|
1187
1167
|
waChunkBuf = '';
|
|
1188
1168
|
}
|
|
1189
1169
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1170
|
+
// Agent finished a turn — handle backend restart + state cleanup
|
|
1171
|
+
if (type === 'bot:turn-complete') {
|
|
1172
|
+
log.info(`[orchestrator] ──── TURN COMPLETE ────`);
|
|
1173
|
+
log.info(`[orchestrator] File tools used: ${eventData.usedFileTools}`);
|
|
1174
|
+
agentQueryActive = false;
|
|
1175
|
+
currentStreamConvId = null;
|
|
1176
|
+
currentStreamBuffer = '';
|
|
1177
|
+
|
|
1178
|
+
if (eventData.usedFileTools || pendingBackendRestart) {
|
|
1179
|
+
log.info('[orchestrator] Restarting backend (file tools used)');
|
|
1180
|
+
pendingBackendRestart = false;
|
|
1181
|
+
if (backendRestartTimer) { clearTimeout(backendRestartTimer); backendRestartTimer = null; }
|
|
1182
|
+
resetBackendRestarts();
|
|
1183
|
+
stopBackend().then(() => spawnBackend(backendPort));
|
|
1197
1184
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1185
|
+
if (pendingUpdate) {
|
|
1186
|
+
pendingUpdate = false;
|
|
1187
|
+
runDeferredUpdate();
|
|
1188
|
+
}
|
|
1189
|
+
return; // don't forward to client
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Conversation ended (query loop exited)
|
|
1193
|
+
if (type === 'bot:conversation-ended') {
|
|
1194
|
+
log.info(`[orchestrator] Conversation ended: ${convId}`);
|
|
1195
|
+
agentQueryActive = false;
|
|
1196
|
+
currentStreamConvId = null;
|
|
1197
|
+
currentStreamBuffer = '';
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
1200
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1201
|
+
// Save assistant response to DB
|
|
1202
|
+
if (type === 'bot:response') {
|
|
1203
|
+
currentStreamBuffer = '';
|
|
1204
|
+
|
|
1205
|
+
// WhatsApp mirror: send remaining chunk
|
|
1206
|
+
if (waMirrorJid && waChunkBuf.trim()) {
|
|
1207
|
+
channelManager.sendMessage('whatsapp', `${waMirrorJid}@s.whatsapp.net`, waChunkBuf.trim()).catch(() => {});
|
|
1208
|
+
waChunkBuf = '';
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
(async () => {
|
|
1212
|
+
try {
|
|
1213
|
+
await workerApi(`/api/conversations/${convId}/messages`, 'POST', {
|
|
1214
|
+
role: 'assistant', content: eventData.content, meta: { model: freshConfig.ai.model },
|
|
1215
|
+
});
|
|
1216
|
+
} catch (err: any) {
|
|
1217
|
+
log.warn(`[bloby] DB persist bot response error: ${err.message}`);
|
|
1218
|
+
}
|
|
1219
|
+
})();
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Stream all events to every connected client
|
|
1223
|
+
broadcastBloby(type, eventData);
|
|
1224
|
+
}, { botName, humanName }, recentMessages);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// Push the user message into the live conversation
|
|
1228
|
+
log.info(`[orchestrator] Pushing message into live conversation`);
|
|
1229
|
+
pushMessage(convId, content, data.attachments, savedFiles);
|
|
1208
1230
|
})();
|
|
1209
1231
|
return;
|
|
1210
1232
|
}
|
|
@@ -1238,7 +1260,13 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
1238
1260
|
}
|
|
1239
1261
|
|
|
1240
1262
|
if (msg.type === 'user:stop') {
|
|
1241
|
-
|
|
1263
|
+
// End the live conversation (if any) or stop a one-shot query
|
|
1264
|
+
if (hasConversation(convId)) {
|
|
1265
|
+
log.info(`[orchestrator] user:stop — ending live conversation ${convId}`);
|
|
1266
|
+
endConversation(convId);
|
|
1267
|
+
} else {
|
|
1268
|
+
stopBlobyAgentQuery(convId);
|
|
1269
|
+
}
|
|
1242
1270
|
return;
|
|
1243
1271
|
}
|
|
1244
1272
|
|
|
@@ -1256,6 +1284,11 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
1256
1284
|
if (msg.type === 'user:clear-context') {
|
|
1257
1285
|
(async () => {
|
|
1258
1286
|
try {
|
|
1287
|
+
// End the live conversation
|
|
1288
|
+
if (hasConversation(convId)) {
|
|
1289
|
+
log.info(`[orchestrator] clear-context — ending live conversation ${convId}`);
|
|
1290
|
+
endConversation(convId);
|
|
1291
|
+
}
|
|
1259
1292
|
clientConvs.delete(ws);
|
|
1260
1293
|
await workerApi('/api/context/clear', 'POST');
|
|
1261
1294
|
} catch (err: any) {
|
|
@@ -1325,11 +1358,14 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
1325
1358
|
}
|
|
1326
1359
|
});
|
|
1327
1360
|
|
|
1328
|
-
// Track whether an agent
|
|
1361
|
+
// Track whether an agent is actively processing — file watcher defers restarts during active turns
|
|
1329
1362
|
let agentQueryActive = false;
|
|
1330
1363
|
let pendingBackendRestart = false; // Set when file watcher fires during agent turn
|
|
1331
1364
|
let pendingUpdate = false; // Set when .update file is created during agent turn
|
|
1332
1365
|
|
|
1366
|
+
// Note: with live conversations, agentQueryActive is true while the agent processes a message
|
|
1367
|
+
// and false when it's idle (waiting for next message). The live conversation stays alive between messages.
|
|
1368
|
+
|
|
1333
1369
|
// Run bloby update as a child process.
|
|
1334
1370
|
// BLOBY_SELF_UPDATE=1 tells bin/cli.js to skip daemon stop/restart —
|
|
1335
1371
|
// the supervisor exits after the update finishes, and systemd (Restart=on-failure)
|
|
@@ -221,7 +221,7 @@ You handle two kinds of work differently:
|
|
|
221
221
|
- Complex research or data gathering
|
|
222
222
|
- Any coding task that touches workspace source files (client/, backend/)
|
|
223
223
|
|
|
224
|
-
For quick tasks, use your tools directly — Read, Write, Edit, Bash.
|
|
224
|
+
For quick tasks, use your tools directly — Read, Write, Edit, Bash.
|
|
225
225
|
|
|
226
226
|
For coding tasks, use the Agent tool. It runs in the background — you respond immediately while the work happens behind the scenes.
|
|
227
227
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{i as e}from"./bloby-C2KDOC_1.js";export{e as Mermaid};
|