@automagik/genie 4.260409.2 → 4.260409.4

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.
@@ -536,6 +536,8 @@ export class OmniBridge {
536
536
  parsed.chatId = parsed.chatId || parts[3];
537
537
  }
538
538
 
539
+ console.log(`[omni-bridge] NATS message received: ${msg.subject} agent=${parsed.agent} chat=${parsed.chatId}`);
540
+
539
541
  if (!parsed.chatId || !parsed.agent) {
540
542
  console.warn('[omni-bridge] Dropping message: missing chatId or agent', msg.subject);
541
543
  continue;
@@ -549,7 +551,11 @@ export class OmniBridge {
549
551
  const env = (raw.env as Record<string, string>) ?? {};
550
552
  await this.queue.enqueue(parsed, env);
551
553
  } else {
554
+ const key = `${parsed.agent}:${parsed.chatId}`;
555
+ const hasSession = this.sessions.has(key);
556
+ console.log(`[omni-bridge] Routing message for ${key} (hasSession=${hasSession}, queue=${!!this.queue})`);
552
557
  await this.routeMessage(parsed);
558
+ console.log(`[omni-bridge] routeMessage done for ${key}`);
553
559
  }
554
560
  } catch (err) {
555
561
  console.error('[omni-bridge] Error processing message:', err);
@@ -570,8 +576,23 @@ export class OmniBridge {
570
576
  const instanceId = parts[3];
571
577
  const chatId = parts.slice(4).join('.'); // chatId may contain dots
572
578
 
573
- const sessionKey = this.findSessionKey(instanceId, chatId);
574
- if (sessionKey) await this.routeTurnEvent(eventType, sessionKey, payload);
579
+ console.log(`[omni-bridge] Turn event: ${eventType} instance=${instanceId} chat=${chatId}`);
580
+ let sessionKey = this.findSessionKey(instanceId, chatId);
581
+
582
+ // Fallback: if chatId didn't match (e.g. LID vs phone mismatch),
583
+ // try finding the session by turnId from the payload.
584
+ if (!sessionKey && payload.turnId) {
585
+ sessionKey = this.findSessionKeyByTurnId(payload.turnId);
586
+ if (sessionKey) {
587
+ console.log(`[omni-bridge] Matched session via turnId fallback: ${sessionKey}`);
588
+ }
589
+ }
590
+
591
+ if (sessionKey) {
592
+ await this.routeTurnEvent(eventType, sessionKey, payload);
593
+ } else {
594
+ console.log(`[omni-bridge] No session found for turn.${eventType} (instance=${instanceId}, chat=${chatId})`);
595
+ }
575
596
  } catch (err) {
576
597
  console.warn('[omni-bridge] Error processing turn event:', err);
577
598
  }
@@ -589,6 +610,7 @@ export class OmniBridge {
589
610
  break;
590
611
  case 'done':
591
612
  this.turnTracker.close(sessionKey, payload.action);
613
+ await this.handleTurnDone(sessionKey);
592
614
  break;
593
615
  case 'nudge':
594
616
  await this.handleTurnNudge(sessionKey, payload.message);
@@ -604,16 +626,31 @@ export class OmniBridge {
604
626
  * Scans the sessions Map for a matching entry since the Map is keyed by
605
627
  * `${agentName}:${chatId}` but we need to match by instanceId+chatId.
606
628
  */
629
+ private findSessionKeyByTurnId(turnId: string): string | undefined {
630
+ for (const [key] of this.sessions) {
631
+ if (this.turnTracker.getTurnId(key) === turnId) return key;
632
+ }
633
+ return undefined;
634
+ }
635
+
636
+ /** Map internal chat UUID → external chatId, populated on turn.open events. */
637
+ private chatIdMap = new Map<string, string>();
638
+
607
639
  private findSessionKey(instanceId: string, chatId: string): string | undefined {
640
+ // If chatId is an internal UUID, try resolving to external via the map
641
+ const resolvedChatId = this.chatIdMap.get(chatId);
642
+
608
643
  for (const [key, entry] of this.sessions) {
609
644
  if (entry.instanceId !== instanceId) continue;
610
645
  // Live entry — match against the spawned session's chatId.
611
646
  if (entry.session?.chatId === chatId) return key;
647
+ if (resolvedChatId && entry.session?.chatId === resolvedChatId) return key;
612
648
  // Spawning entry — session is null, so match against the map key suffix
613
649
  // (`${agent}:${chatId}`). Without this fallback, a reset arriving in the
614
650
  // narrow window between placeholder insertion and spawn completion would
615
651
  // be misclassified as a cold chat and ignored.
616
652
  if (entry.spawning && key.endsWith(`:${chatId}`)) return key;
653
+ if (resolvedChatId && entry.spawning && key.endsWith(`:${resolvedChatId}`)) return key;
617
654
  }
618
655
  return undefined;
619
656
  }
@@ -718,6 +755,20 @@ export class OmniBridge {
718
755
  await this.drainQueue();
719
756
  }
720
757
 
758
+ /**
759
+ * Handle a turn.done event — the agent called `omni done`.
760
+ * The turn is closed but the SESSION stays alive. The next inbound message
761
+ * will be delivered to the same session (same tmux pane / SDK conversation).
762
+ * Session teardown only happens on idle timeout or explicit reset.
763
+ */
764
+ private async handleTurnDone(sessionKey: string): Promise<void> {
765
+ const entry = this.sessions.get(sessionKey);
766
+ if (!entry?.session) return;
767
+ console.log(`[omni-bridge] Turn done for ${sessionKey}, session stays alive for next message`);
768
+ // Reset the idle timer — the session is idle until the next message arrives
769
+ this.resetIdleTimer(sessionKey);
770
+ }
771
+
721
772
  /**
722
773
  * Handle a turn timeout event — evict the session and shut down the executor.
723
774
  */
@@ -778,6 +829,17 @@ export class OmniBridge {
778
829
  private async spawnSession(message: OmniMessage): Promise<void> {
779
830
  const key = `${message.agent}:${message.chatId}`;
780
831
 
832
+ // Guard: if a session or spawning placeholder already exists, buffer instead of double-spawning.
833
+ // This prevents the race where two concurrent messages for the same chatId both call spawnSession.
834
+ const existing = this.sessions.get(key);
835
+ if (existing) {
836
+ if (existing.buffer.length < MAX_BUFFER_PER_CHAT) {
837
+ existing.buffer.push(message);
838
+ console.log(`[omni-bridge] Buffered message for existing session ${key} (buffer=${existing.buffer.length})`);
839
+ }
840
+ return;
841
+ }
842
+
781
843
  // Check concurrency limit (count spawning entries too to prevent oversubscription)
782
844
  const activeCount = this.sessions.size;
783
845
  if (activeCount >= this.maxConcurrent) {
@@ -810,10 +872,11 @@ export class OmniBridge {
810
872
  OMNI_CHAT: payloadEnv?.OMNI_CHAT ?? message.chatId,
811
873
  OMNI_MESSAGE: payloadEnv?.OMNI_MESSAGE ?? (raw.messageId as string) ?? '',
812
874
  OMNI_TURN_ID: payloadEnv?.OMNI_TURN_ID ?? '',
875
+ OMNI_SENDER_NAME: payloadEnv?.OMNI_SENDER_NAME ?? message.sender ?? '',
813
876
  };
814
877
 
815
878
  console.log(`[omni-bridge] Spawning session for ${key}...`);
816
- const session = await this.executor.spawn(message.agent, message.chatId, spawnEnv);
879
+ const session = await this.executor.spawn(message.agent, message.chatId, spawnEnv, message.content);
817
880
 
818
881
  // Reset arrived while spawn was in flight — tear down the freshly-created
819
882
  // session and bail. The placeholder was already removed from the map by