feat: enrich AI stream with replyToMessageId, sourceGroupName, organizationId + tenant/source filters

- Extract quotedPlatformMsgId from Baileys contextInfo.stanzaId in normalizer
- Thread quotedPlatformMsgId through IngestJobData → ingest processor → Message record
- Widen ingest processor group/tenant selects to include name and organizationId
- Add replyToMessageId, sourceGroupName, organizationId to StreamMessagePayload and XADD
- Update StreamRecord, parseFields, and streamMessages in AI stream service with new fields
- Add optional ?tenant= and ?source= SSE filters; cursor advances for filtered entries
  so Last-Event-ID resume remains accurate across reconnects

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 01:07:42 +05:30
parent 5a0f7aa58f
commit e598073dc7
7 changed files with 39 additions and 15 deletions
+1
View File
@@ -203,6 +203,7 @@ async function bootstrap() {
content: msg.content,
tags,
effectiveAction: finalAction ?? undefined,
quotedPlatformMsgId: msg.quotedPlatformMsgId,
},
{ attempts: 3, backoff: { type: 'exponential', delay: 1000 } },
);
+6 -2
View File
@@ -19,7 +19,7 @@ export async function processIngestJob(
// Defensive: drop messages from non-CLAIMED groups
const group = await prisma.group.findUnique({
where: { id: job.sourceGroupId },
select: { claimStatus: true },
select: { claimStatus: true, name: true },
});
if (!group || group.claimStatus !== 'CLAIMED') {
return;
@@ -28,7 +28,7 @@ export async function processIngestJob(
// Safety net: drop if tenant is inactive or paused
const tenant = await prisma.tenant.findUnique({
where: { id: job.tenantId },
select: { isActive: true, isForwardingPaused: true },
select: { isActive: true, isForwardingPaused: true, organizationId: true },
});
if (!tenant || !tenant.isActive || tenant.isForwardingPaused) {
return;
@@ -86,6 +86,7 @@ export async function processIngestJob(
content: job.content,
tags: job.tags,
status: initialStatus,
quotedPlatformMsgId: job.quotedPlatformMsgId,
},
update: {},
});
@@ -100,6 +101,9 @@ export async function processIngestJob(
senderJid: job.senderJid,
senderName: job.senderName,
sourceGroupId: job.sourceGroupId,
sourceGroupName: group.name,
organizationId: tenant.organizationId ?? undefined,
replyToMessageId: job.quotedPlatformMsgId,
tags: job.tags,
effectiveAction: job.effectiveAction ?? null,
timestamp: new Date().toISOString(),
+16 -10
View File
@@ -13,6 +13,9 @@ export interface StreamMessagePayload {
senderJid: string;
senderName?: string;
sourceGroupId: string;
sourceGroupName?: string;
organizationId?: string;
replyToMessageId?: string;
tags: string[];
effectiveAction: string | null;
traceId?: string;
@@ -34,15 +37,18 @@ export async function publishToMessageStream(
STREAM_KEY,
'MAXLEN', '~', String(STREAM_MAX_LEN),
'*',
'messageId', payload.messageId,
'tenantId', payload.tenantId,
'content', payload.content,
'senderJid', payload.senderJid,
'senderName', payload.senderName ?? '',
'sourceGroupId', payload.sourceGroupId,
'tags', JSON.stringify(payload.tags),
'effectiveAction', payload.effectiveAction ?? '',
'traceId', payload.traceId ?? '',
'timestamp', payload.timestamp,
'messageId', payload.messageId,
'tenantId', payload.tenantId,
'content', payload.content,
'senderJid', payload.senderJid,
'senderName', payload.senderName ?? '',
'sourceGroupId', payload.sourceGroupId,
'sourceGroupName', payload.sourceGroupName ?? '',
'organizationId', payload.organizationId ?? '',
'replyToMessageId', payload.replyToMessageId ?? '',
'tags', JSON.stringify(payload.tags),
'effectiveAction', payload.effectiveAction ?? '',
'traceId', payload.traceId ?? '',
'timestamp', payload.timestamp,
);
}
+1
View File
@@ -40,6 +40,7 @@ export function normalizeMessage(
senderName: msg.pushName ?? undefined,
content,
accountId,
quotedPlatformMsgId: msg.message?.extendedTextMessage?.contextInfo?.stanzaId ?? undefined,
};
}