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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bloby-bot",
3
- "version": "0.36.1",
3
+ "version": "0.37.1",
4
4
  "releaseNotes": [
5
5
  "1. # voice note (PTT bubble)",
6
6
  "2. # audio file + caption",
@@ -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
- if (type === 'bot:turn-complete' && eventData.usedFileTools) {
723
- this.opts.restartBackend();
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:turn-complete' || type === 'bot:conversation-ended') return;
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
- undefined,
897
+ savedFiles,
873
898
  { botName, humanName },
874
899
  recentMessages,
875
900
  enrichedScript,