bloby-bot 0.36.1 → 0.37.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/package.json
CHANGED
|
@@ -27,11 +27,25 @@ import { startBlobyAgentQuery, startConversation, pushMessage, hasConversation,
|
|
|
27
27
|
import { WhatsAppChannel } from './whatsapp.js';
|
|
28
28
|
import type { ChannelConfig, ChannelProvider, ChannelStatus, ChannelType, InboundMessage, InboundMessageAttachment, SenderRole } from './types.js';
|
|
29
29
|
import type { AgentAttachment } from '../bloby-agent.js';
|
|
30
|
+
import { saveAttachment, type SavedFile } from '../file-saver.js';
|
|
30
31
|
|
|
31
32
|
const MAX_CONCURRENT_AGENTS = 5;
|
|
32
33
|
const MAX_BUFFER_MESSAGES = 30;
|
|
33
34
|
const DEBOUNCE_MS = 4000; // 4s — wait for the user to finish typing
|
|
34
35
|
|
|
36
|
+
/** Persist channel-inbound attachments to disk so harnesses that consume file
|
|
37
|
+
* paths (Codex's `localImage`) can see them. Failures are logged and the
|
|
38
|
+
* attachment is dropped — text-only delivery is still useful. */
|
|
39
|
+
function saveInboundAttachments(attachments?: AgentAttachment[]): SavedFile[] {
|
|
40
|
+
if (!attachments?.length) return [];
|
|
41
|
+
const saved: SavedFile[] = [];
|
|
42
|
+
for (const att of attachments) {
|
|
43
|
+
try { saved.push(saveAttachment(att)); }
|
|
44
|
+
catch (err: any) { log.warn(`[channels] Failed to save inbound attachment: ${err.message}`); }
|
|
45
|
+
}
|
|
46
|
+
return saved;
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
interface ChannelManagerOpts {
|
|
36
50
|
broadcastBloby: (type: string, data: any) => void;
|
|
37
51
|
workerApi: (path: string, method?: string, body?: any) => Promise<any>;
|
|
@@ -120,9 +134,9 @@ export class ChannelManager {
|
|
|
120
134
|
let provider = this.providers.get('whatsapp');
|
|
121
135
|
if (!provider) {
|
|
122
136
|
const whatsapp = new WhatsAppChannel(
|
|
123
|
-
(sender, senderName, text, fromMe, isSelfChat, images) => {
|
|
137
|
+
(sender, senderName, text, fromMe, isSelfChat, chatJid, isGroup, images) => {
|
|
124
138
|
const attachments = images?.map((img) => ({ type: 'image' as const, mediaType: img.mediaType, data: img.data }));
|
|
125
|
-
this.handleInboundMessage('whatsapp', sender, senderName, text, fromMe, isSelfChat, attachments);
|
|
139
|
+
this.handleInboundMessage('whatsapp', sender, senderName, text, fromMe, isSelfChat, chatJid, isGroup, attachments);
|
|
126
140
|
},
|
|
127
141
|
(status) => this.handleStatusChange(status),
|
|
128
142
|
(audioBase64) => this.transcribeAudio(audioBase64),
|
|
@@ -693,6 +707,10 @@ export class ChannelManager {
|
|
|
693
707
|
mediaType: att.mediaType,
|
|
694
708
|
data: att.data,
|
|
695
709
|
}));
|
|
710
|
+
// Save to disk so providers that consume file paths (Codex → localImage)
|
|
711
|
+
// can see the attachment. Claude consumes raw base64 from `agentAttachments`
|
|
712
|
+
// directly, but the on-disk copy is still useful for the path mention.
|
|
713
|
+
const savedFiles = saveInboundAttachments(agentAttachments);
|
|
696
714
|
|
|
697
715
|
// Show "typing..." in the correct chat
|
|
698
716
|
this.startTyping(msg.channel, msg.rawSender);
|
|
@@ -718,13 +736,19 @@ export class ChannelManager {
|
|
|
718
736
|
}).catch(() => {});
|
|
719
737
|
}
|
|
720
738
|
|
|
721
|
-
// Handle turn completion — restart backend if file tools were used
|
|
722
|
-
|
|
723
|
-
|
|
739
|
+
// Handle turn completion — restart backend if file tools were used,
|
|
740
|
+
// and tell every chat client the agent is idle so the typing dots
|
|
741
|
+
// stop. This callback owns the live conversation whenever a WhatsApp
|
|
742
|
+
// self-chat arrives before the dashboard does, so without this signal
|
|
743
|
+
// the dashboard's typing indicator would stay on forever.
|
|
744
|
+
if (type === 'bot:turn-complete') {
|
|
745
|
+
if (eventData.usedFileTools) this.opts.restartBackend();
|
|
746
|
+
broadcastBloby('bot:idle', { conversationId: convId });
|
|
747
|
+
return;
|
|
724
748
|
}
|
|
725
749
|
|
|
726
750
|
// Don't forward internal events to chat clients
|
|
727
|
-
if (type === 'bot:
|
|
751
|
+
if (type === 'bot:conversation-ended') return;
|
|
728
752
|
|
|
729
753
|
// Mirror streaming + task events to chat clients
|
|
730
754
|
broadcastBloby(type, eventData);
|
|
@@ -740,7 +764,7 @@ export class ChannelManager {
|
|
|
740
764
|
|
|
741
765
|
// Push the message into the live conversation
|
|
742
766
|
const channelContent = channelContext + msg.text;
|
|
743
|
-
pushMessage(convId, channelContent, agentAttachments);
|
|
767
|
+
pushMessage(convId, channelContent, agentAttachments, savedFiles);
|
|
744
768
|
}
|
|
745
769
|
|
|
746
770
|
/** Handle message from a customer — runs support agent in parallel with conversation context */
|
|
@@ -809,6 +833,7 @@ export class ChannelManager {
|
|
|
809
833
|
mediaType: att.mediaType,
|
|
810
834
|
data: att.data,
|
|
811
835
|
}));
|
|
836
|
+
const savedFiles = saveInboundAttachments(agentAttachments);
|
|
812
837
|
|
|
813
838
|
// Stable convId per customer (not per message)
|
|
814
839
|
const convId = `channel-${agentKey}`;
|
|
@@ -869,7 +894,7 @@ export class ChannelManager {
|
|
|
869
894
|
}
|
|
870
895
|
},
|
|
871
896
|
agentAttachments,
|
|
872
|
-
|
|
897
|
+
savedFiles,
|
|
873
898
|
{ botName, humanName },
|
|
874
899
|
recentMessages,
|
|
875
900
|
enrichedScript,
|