@realtimex/email-automator 2.6.5 → 2.7.0
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/api/src/services/intelligence.ts +8 -4
- package/api/src/services/processor.ts +86 -57
- package/dist/api/src/services/intelligence.js +8 -4
- package/dist/api/src/services/processor.js +40 -7
- package/dist/assets/index-aTk6SbAd.js +97 -0
- package/dist/assets/index-npWWfPF9.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/supabase/migrations/20260119000002_fix_compiled_context_conditions.sql +137 -0
- package/dist/assets/index-6zjdTO-X.css +0 -1
- package/dist/assets/index-8DZIcZie.js +0 -97
|
@@ -211,7 +211,8 @@ REQUIRED JSON STRUCTURE:
|
|
|
211
211
|
});
|
|
212
212
|
|
|
213
213
|
rawResponse = response.choices[0]?.message?.content || '';
|
|
214
|
-
|
|
214
|
+
const usage = response.usage;
|
|
215
|
+
console.log('[Intelligence] Raw LLM Response received (length:', rawResponse.length, ')', { usage });
|
|
215
216
|
|
|
216
217
|
// Clean the response: Find first '{' and last '}'
|
|
217
218
|
let jsonStr = rawResponse.trim();
|
|
@@ -235,7 +236,8 @@ REQUIRED JSON STRUCTURE:
|
|
|
235
236
|
if (eventLogger && emailId) {
|
|
236
237
|
await eventLogger.analysis('Decided', emailId, {
|
|
237
238
|
...validated,
|
|
238
|
-
_raw_response: rawResponse
|
|
239
|
+
_raw_response: rawResponse,
|
|
240
|
+
usage: usage // Include token usage
|
|
239
241
|
});
|
|
240
242
|
}
|
|
241
243
|
|
|
@@ -431,7 +433,8 @@ Return ONLY valid JSON.`;
|
|
|
431
433
|
});
|
|
432
434
|
|
|
433
435
|
rawResponse = response.choices[0]?.message?.content || '';
|
|
434
|
-
|
|
436
|
+
const usage = response.usage;
|
|
437
|
+
console.log('[Intelligence] Context-aware response received (length:', rawResponse.length, ')', { usage });
|
|
435
438
|
|
|
436
439
|
// Parse JSON from response
|
|
437
440
|
let jsonStr = rawResponse.trim();
|
|
@@ -455,7 +458,8 @@ Return ONLY valid JSON.`;
|
|
|
455
458
|
if (eventLogger && emailId) {
|
|
456
459
|
await eventLogger.analysis('Decided', emailId, {
|
|
457
460
|
...validated,
|
|
458
|
-
_raw_response: rawResponse
|
|
461
|
+
_raw_response: rawResponse,
|
|
462
|
+
usage: usage // Include token usage
|
|
459
463
|
});
|
|
460
464
|
}
|
|
461
465
|
|
|
@@ -55,8 +55,8 @@ export class EmailProcessorService {
|
|
|
55
55
|
throw new Error('Account not found or access denied');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
logger.info('Retrieved account settings', {
|
|
59
|
-
accountId: account.id,
|
|
58
|
+
logger.info('Retrieved account settings', {
|
|
59
|
+
accountId: account.id,
|
|
60
60
|
sync_start_date: account.sync_start_date,
|
|
61
61
|
last_sync_checkpoint: account.last_sync_checkpoint
|
|
62
62
|
});
|
|
@@ -104,7 +104,7 @@ export class EmailProcessorService {
|
|
|
104
104
|
await this.runRetentionRules(refreshedAccount, rules || [], settings, result, eventLogger);
|
|
105
105
|
|
|
106
106
|
// Trigger background worker (async) to process the queue
|
|
107
|
-
this.processQueue(userId, settings).catch(err =>
|
|
107
|
+
this.processQueue(userId, settings).catch(err =>
|
|
108
108
|
logger.error('Background worker failed', err)
|
|
109
109
|
);
|
|
110
110
|
|
|
@@ -160,7 +160,7 @@ export class EmailProcessorService {
|
|
|
160
160
|
if (errMsg.includes('Account not found') || errMsg.includes('access denied')) {
|
|
161
161
|
throw error;
|
|
162
162
|
}
|
|
163
|
-
|
|
163
|
+
|
|
164
164
|
// Otherwise, increment error count and return partial results
|
|
165
165
|
result.errors++;
|
|
166
166
|
}
|
|
@@ -199,7 +199,7 @@ export class EmailProcessorService {
|
|
|
199
199
|
const windowSizeMs = 7 * 24 * 60 * 60 * 1000;
|
|
200
200
|
const nowMs = Date.now();
|
|
201
201
|
const tomorrowMs = nowMs + (24 * 60 * 60 * 1000);
|
|
202
|
-
|
|
202
|
+
|
|
203
203
|
let currentStartMs = effectiveStartMs;
|
|
204
204
|
let messages: GmailMessage[] = [];
|
|
205
205
|
let hasMore = false;
|
|
@@ -231,7 +231,7 @@ export class EmailProcessorService {
|
|
|
231
231
|
logger.info('No emails in 7-day window, skipping forward', { start: new Date(currentStartMs).toISOString() });
|
|
232
232
|
currentStartMs = effectiveEndMs;
|
|
233
233
|
attempts++;
|
|
234
|
-
|
|
234
|
+
|
|
235
235
|
if (eventLogger && attempts % 3 === 0) {
|
|
236
236
|
await eventLogger.info('Sync', `Scanning history... reached ${new Date(currentStartMs).toLocaleDateString()}`);
|
|
237
237
|
}
|
|
@@ -262,17 +262,17 @@ export class EmailProcessorService {
|
|
|
262
262
|
|
|
263
263
|
// Update checkpoint once at the end of the batch if we made progress
|
|
264
264
|
if (maxInternalDate > effectiveStartMs) {
|
|
265
|
-
logger.info('Updating Gmail checkpoint', {
|
|
266
|
-
accountId: account.id,
|
|
265
|
+
logger.info('Updating Gmail checkpoint', {
|
|
266
|
+
accountId: account.id,
|
|
267
267
|
oldCheckpoint: account.last_sync_checkpoint,
|
|
268
|
-
newCheckpoint: maxInternalDate.toString()
|
|
268
|
+
newCheckpoint: maxInternalDate.toString()
|
|
269
269
|
});
|
|
270
|
-
|
|
270
|
+
|
|
271
271
|
const { error: updateError } = await this.supabase
|
|
272
272
|
.from('email_accounts')
|
|
273
273
|
.update({ last_sync_checkpoint: maxInternalDate.toString() })
|
|
274
274
|
.eq('id', account.id);
|
|
275
|
-
|
|
275
|
+
|
|
276
276
|
if (updateError) {
|
|
277
277
|
logger.error('Failed to update Gmail checkpoint', updateError);
|
|
278
278
|
}
|
|
@@ -346,12 +346,12 @@ export class EmailProcessorService {
|
|
|
346
346
|
|
|
347
347
|
// Update checkpoint once at the end of the batch if we made progress
|
|
348
348
|
if (latestCheckpoint && latestCheckpoint !== effectiveStartIso) {
|
|
349
|
-
logger.info('Updating Outlook checkpoint', {
|
|
350
|
-
accountId: account.id,
|
|
349
|
+
logger.info('Updating Outlook checkpoint', {
|
|
350
|
+
accountId: account.id,
|
|
351
351
|
oldCheckpoint: account.last_sync_checkpoint,
|
|
352
|
-
newCheckpoint: latestCheckpoint
|
|
352
|
+
newCheckpoint: latestCheckpoint
|
|
353
353
|
});
|
|
354
|
-
|
|
354
|
+
|
|
355
355
|
const { error: updateError } = await this.supabase
|
|
356
356
|
.from('email_accounts')
|
|
357
357
|
.update({ last_sync_checkpoint: latestCheckpoint })
|
|
@@ -387,11 +387,11 @@ export class EmailProcessorService {
|
|
|
387
387
|
if (existing) {
|
|
388
388
|
logger.debug('Message already processed', { messageId: message.id });
|
|
389
389
|
if (eventLogger) await eventLogger.info('Skipped', `Already processed ID: ${message.id}`);
|
|
390
|
-
|
|
390
|
+
|
|
391
391
|
// Still need to return the date for checkpointing even if skipped
|
|
392
|
-
const rawMime = 'raw' in message
|
|
393
|
-
? (account.provider === 'gmail'
|
|
394
|
-
? Buffer.from(message.raw, 'base64').toString('utf-8')
|
|
392
|
+
const rawMime = 'raw' in message
|
|
393
|
+
? (account.provider === 'gmail'
|
|
394
|
+
? Buffer.from(message.raw, 'base64').toString('utf-8')
|
|
395
395
|
: message.raw)
|
|
396
396
|
: '';
|
|
397
397
|
if (rawMime) {
|
|
@@ -400,11 +400,11 @@ export class EmailProcessorService {
|
|
|
400
400
|
}
|
|
401
401
|
return;
|
|
402
402
|
}
|
|
403
|
-
|
|
403
|
+
|
|
404
404
|
// Extract raw content string (Gmail is base64url, Outlook is raw text from $value)
|
|
405
|
-
const rawMime = 'raw' in message
|
|
406
|
-
? (account.provider === 'gmail'
|
|
407
|
-
? Buffer.from(message.raw, 'base64').toString('utf-8')
|
|
405
|
+
const rawMime = 'raw' in message
|
|
406
|
+
? (account.provider === 'gmail'
|
|
407
|
+
? Buffer.from(message.raw, 'base64').toString('utf-8')
|
|
408
408
|
: message.raw)
|
|
409
409
|
: '';
|
|
410
410
|
|
|
@@ -462,7 +462,7 @@ export class EmailProcessorService {
|
|
|
462
462
|
if (eventLogger) await eventLogger.info('Ingested', `Successfully ingested email: ${subject}`, { filePath }, savedEmail.id);
|
|
463
463
|
|
|
464
464
|
result.processed++;
|
|
465
|
-
|
|
465
|
+
|
|
466
466
|
return { date };
|
|
467
467
|
}
|
|
468
468
|
|
|
@@ -522,7 +522,7 @@ export class EmailProcessorService {
|
|
|
522
522
|
.select('processing_status')
|
|
523
523
|
.eq('id', email.id)
|
|
524
524
|
.single();
|
|
525
|
-
|
|
525
|
+
|
|
526
526
|
if (current?.processing_status !== 'pending') {
|
|
527
527
|
if (log) await this.supabase.from('processing_logs').delete().eq('id', log.id);
|
|
528
528
|
return;
|
|
@@ -539,7 +539,7 @@ export class EmailProcessorService {
|
|
|
539
539
|
if (!email.file_path) throw new Error('No file path found for email');
|
|
540
540
|
const rawMime = await this.storageService.readEmail(email.file_path);
|
|
541
541
|
const parsed = await simpleParser(rawMime);
|
|
542
|
-
|
|
542
|
+
|
|
543
543
|
// Extract clean content (prioritize text)
|
|
544
544
|
const cleanContent = parsed.text || parsed.textAsHtml || '';
|
|
545
545
|
|
|
@@ -561,7 +561,7 @@ export class EmailProcessorService {
|
|
|
561
561
|
// 4. Fetch pre-compiled rule context (fast path - no loop/formatting)
|
|
562
562
|
// Falls back to building context if not cached
|
|
563
563
|
let compiledContext: string | null = settings?.compiled_rule_context || null;
|
|
564
|
-
|
|
564
|
+
|
|
565
565
|
// Fetch rules for action execution (need attachments, instructions)
|
|
566
566
|
const { data: rules } = await this.supabase
|
|
567
567
|
.from('rules')
|
|
@@ -572,15 +572,44 @@ export class EmailProcessorService {
|
|
|
572
572
|
|
|
573
573
|
// Fallback: build context if not pre-compiled
|
|
574
574
|
if (!compiledContext && rules && rules.length > 0) {
|
|
575
|
-
compiledContext = rules.map((r, i) =>
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
(r.
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
575
|
+
compiledContext = rules.map((r, i) => {
|
|
576
|
+
// Build human-readable condition text
|
|
577
|
+
let conditionText = '';
|
|
578
|
+
if (r.condition) {
|
|
579
|
+
const cond = r.condition as any;
|
|
580
|
+
if (cond.field) {
|
|
581
|
+
conditionText = `When ${cond.field}`;
|
|
582
|
+
if (cond.operator === 'equals') {
|
|
583
|
+
conditionText += ` equals "${cond.value}"`;
|
|
584
|
+
} else if (cond.operator === 'contains') {
|
|
585
|
+
conditionText += ` contains "${cond.value}"`;
|
|
586
|
+
} else if (cond.operator === 'domain_equals') {
|
|
587
|
+
conditionText += ` domain equals "${cond.value}"`;
|
|
588
|
+
} else {
|
|
589
|
+
conditionText += ` ${cond.operator} "${cond.value}"`;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (cond.is_useless === true) {
|
|
593
|
+
conditionText += (conditionText ? ' AND ' : 'When ') + 'email is useless/low-value';
|
|
594
|
+
}
|
|
595
|
+
if (cond.ai_priority) {
|
|
596
|
+
conditionText += (conditionText ? ' AND ' : 'When ') + `AI priority is "${cond.ai_priority}"`;
|
|
597
|
+
}
|
|
598
|
+
// Extract older_than_days from condition JSONB
|
|
599
|
+
if (cond.older_than_days) {
|
|
600
|
+
conditionText += (conditionText ? ' AND ' : 'When ') + `email is older than ${cond.older_than_days} days`;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return `Rule ${i + 1} [ID: ${r.id}]\n` +
|
|
605
|
+
` Name: ${r.name}\n` +
|
|
606
|
+
(r.description ? ` Description: ${r.description}\n` : '') +
|
|
607
|
+
(r.intent ? ` Intent: ${r.intent}\n` : '') +
|
|
608
|
+
(conditionText ? ` Condition: ${conditionText}\n` : '') +
|
|
609
|
+
` Actions: ${r.actions?.join(', ') || r.action || 'none'}\n` +
|
|
610
|
+
(r.instructions ? ` Draft Instructions: ${r.instructions}\n` : '') +
|
|
611
|
+
'\n';
|
|
612
|
+
}).join('');
|
|
584
613
|
}
|
|
585
614
|
|
|
586
615
|
// 5. Context-Aware Analysis: AI evaluates email against user's rules
|
|
@@ -632,9 +661,9 @@ export class EmailProcessorService {
|
|
|
632
661
|
// 7. Execute actions if rule matched with sufficient confidence
|
|
633
662
|
if (account && analysis.matched_rule.rule_id && analysis.matched_rule.confidence >= 0.7) {
|
|
634
663
|
const matchedRule = rules?.find(r => r.id === analysis.matched_rule.rule_id);
|
|
635
|
-
|
|
664
|
+
|
|
636
665
|
if (eventLogger) {
|
|
637
|
-
await eventLogger.info('Rule Matched',
|
|
666
|
+
await eventLogger.info('Rule Matched',
|
|
638
667
|
`"${analysis.matched_rule.rule_name}" matched with ${(analysis.matched_rule.confidence * 100).toFixed(0)}% confidence`,
|
|
639
668
|
{ reasoning: analysis.matched_rule.reasoning },
|
|
640
669
|
email.id
|
|
@@ -644,22 +673,22 @@ export class EmailProcessorService {
|
|
|
644
673
|
// Execute each action from the AI's decision
|
|
645
674
|
for (const action of analysis.actions_to_execute) {
|
|
646
675
|
if (action === 'none') continue;
|
|
647
|
-
|
|
676
|
+
|
|
648
677
|
// Use AI-generated draft content if available
|
|
649
678
|
const draftContent = action === 'draft' ? analysis.draft_content : undefined;
|
|
650
|
-
|
|
679
|
+
|
|
651
680
|
await this.executeAction(
|
|
652
|
-
account,
|
|
653
|
-
email,
|
|
654
|
-
action as any,
|
|
655
|
-
draftContent,
|
|
656
|
-
eventLogger,
|
|
681
|
+
account,
|
|
682
|
+
email,
|
|
683
|
+
action as any,
|
|
684
|
+
draftContent,
|
|
685
|
+
eventLogger,
|
|
657
686
|
`Rule: ${matchedRule?.name || analysis.matched_rule.rule_name}`,
|
|
658
687
|
matchedRule?.attachments
|
|
659
688
|
);
|
|
660
689
|
}
|
|
661
690
|
} else if (eventLogger && rules && rules.length > 0) {
|
|
662
|
-
await eventLogger.info('No Match',
|
|
691
|
+
await eventLogger.info('No Match',
|
|
663
692
|
analysis.matched_rule.reasoning,
|
|
664
693
|
{ confidence: analysis.matched_rule.confidence },
|
|
665
694
|
email.id
|
|
@@ -670,10 +699,10 @@ export class EmailProcessorService {
|
|
|
670
699
|
if (log) {
|
|
671
700
|
await this.supabase
|
|
672
701
|
.from('processing_logs')
|
|
673
|
-
.update({
|
|
674
|
-
status: 'success',
|
|
702
|
+
.update({
|
|
703
|
+
status: 'success',
|
|
675
704
|
completed_at: new Date().toISOString(),
|
|
676
|
-
emails_processed: 1
|
|
705
|
+
emails_processed: 1
|
|
677
706
|
})
|
|
678
707
|
.eq('id', log.id);
|
|
679
708
|
}
|
|
@@ -681,13 +710,13 @@ export class EmailProcessorService {
|
|
|
681
710
|
} catch (error) {
|
|
682
711
|
logger.error('Failed to process pending email', error, { emailId: email.id });
|
|
683
712
|
if (eventLogger) await eventLogger.error('Processing Failed', error, email.id);
|
|
684
|
-
|
|
713
|
+
|
|
685
714
|
// Mark log as failed
|
|
686
715
|
if (log) {
|
|
687
716
|
await this.supabase
|
|
688
717
|
.from('processing_logs')
|
|
689
|
-
.update({
|
|
690
|
-
status: 'failed',
|
|
718
|
+
.update({
|
|
719
|
+
status: 'failed',
|
|
691
720
|
completed_at: new Date().toISOString(),
|
|
692
721
|
error_message: error instanceof Error ? error.message : String(error)
|
|
693
722
|
})
|
|
@@ -696,7 +725,7 @@ export class EmailProcessorService {
|
|
|
696
725
|
|
|
697
726
|
await this.supabase
|
|
698
727
|
.from('emails')
|
|
699
|
-
.update({
|
|
728
|
+
.update({
|
|
700
729
|
processing_status: 'failed',
|
|
701
730
|
processing_error: error instanceof Error ? error.message : String(error),
|
|
702
731
|
retry_count: (email.retry_count || 0) + 1
|
|
@@ -766,10 +795,10 @@ export class EmailProcessorService {
|
|
|
766
795
|
|
|
767
796
|
private matchesCondition(email: Partial<Email>, analysis: EmailAnalysis, condition: Record<string, unknown>): boolean {
|
|
768
797
|
if (!analysis) return false;
|
|
769
|
-
|
|
798
|
+
|
|
770
799
|
for (const [key, value] of Object.entries(condition)) {
|
|
771
800
|
const val = value as string;
|
|
772
|
-
|
|
801
|
+
|
|
773
802
|
switch (key) {
|
|
774
803
|
case 'sender_email':
|
|
775
804
|
if (email.sender?.toLowerCase() !== val.toLowerCase()) return false;
|
|
@@ -810,7 +839,7 @@ export class EmailProcessorService {
|
|
|
810
839
|
// Handle array membership check (e.g. if condition expects "reply" to be in actions)
|
|
811
840
|
const requiredActions = Array.isArray(value) ? value : [value];
|
|
812
841
|
const actualActions = analysis.suggested_actions || [];
|
|
813
|
-
const hasAllActions = requiredActions.every(req =>
|
|
842
|
+
const hasAllActions = requiredActions.every(req =>
|
|
814
843
|
actualActions.includes(req as any)
|
|
815
844
|
);
|
|
816
845
|
if (!hasAllActions) return false;
|
|
@@ -936,7 +965,7 @@ export class EmailProcessorService {
|
|
|
936
965
|
.eq('id', email.id);
|
|
937
966
|
|
|
938
967
|
logger.debug('Action executed', { emailId: email.id, action });
|
|
939
|
-
|
|
968
|
+
|
|
940
969
|
if (eventLogger) {
|
|
941
970
|
await eventLogger.action('Acted', email.id, action, reason);
|
|
942
971
|
}
|
|
@@ -166,7 +166,8 @@ REQUIRED JSON STRUCTURE:
|
|
|
166
166
|
temperature: 0.1,
|
|
167
167
|
});
|
|
168
168
|
rawResponse = response.choices[0]?.message?.content || '';
|
|
169
|
-
|
|
169
|
+
const usage = response.usage;
|
|
170
|
+
console.log('[Intelligence] Raw LLM Response received (length:', rawResponse.length, ')', { usage });
|
|
170
171
|
// Clean the response: Find first '{' and last '}'
|
|
171
172
|
let jsonStr = rawResponse.trim();
|
|
172
173
|
const startIdx = jsonStr.indexOf('{');
|
|
@@ -184,7 +185,8 @@ REQUIRED JSON STRUCTURE:
|
|
|
184
185
|
if (eventLogger && emailId) {
|
|
185
186
|
await eventLogger.analysis('Decided', emailId, {
|
|
186
187
|
...validated,
|
|
187
|
-
_raw_response: rawResponse
|
|
188
|
+
_raw_response: rawResponse,
|
|
189
|
+
usage: usage // Include token usage
|
|
188
190
|
});
|
|
189
191
|
}
|
|
190
192
|
return validated;
|
|
@@ -362,7 +364,8 @@ Return ONLY valid JSON.`;
|
|
|
362
364
|
temperature: 0.1,
|
|
363
365
|
});
|
|
364
366
|
rawResponse = response.choices[0]?.message?.content || '';
|
|
365
|
-
|
|
367
|
+
const usage = response.usage;
|
|
368
|
+
console.log('[Intelligence] Context-aware response received (length:', rawResponse.length, ')', { usage });
|
|
366
369
|
// Parse JSON from response
|
|
367
370
|
let jsonStr = rawResponse.trim();
|
|
368
371
|
const startIdx = jsonStr.indexOf('{');
|
|
@@ -381,7 +384,8 @@ Return ONLY valid JSON.`;
|
|
|
381
384
|
if (eventLogger && emailId) {
|
|
382
385
|
await eventLogger.analysis('Decided', emailId, {
|
|
383
386
|
...validated,
|
|
384
|
-
_raw_response: rawResponse
|
|
387
|
+
_raw_response: rawResponse,
|
|
388
|
+
usage: usage // Include token usage
|
|
385
389
|
});
|
|
386
390
|
}
|
|
387
391
|
return validated;
|
|
@@ -472,13 +472,46 @@ export class EmailProcessorService {
|
|
|
472
472
|
.order('priority', { ascending: false });
|
|
473
473
|
// Fallback: build context if not pre-compiled
|
|
474
474
|
if (!compiledContext && rules && rules.length > 0) {
|
|
475
|
-
compiledContext = rules.map((r, i) =>
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
(r.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
475
|
+
compiledContext = rules.map((r, i) => {
|
|
476
|
+
// Build human-readable condition text
|
|
477
|
+
let conditionText = '';
|
|
478
|
+
if (r.condition) {
|
|
479
|
+
const cond = r.condition;
|
|
480
|
+
if (cond.field) {
|
|
481
|
+
conditionText = `When ${cond.field}`;
|
|
482
|
+
if (cond.operator === 'equals') {
|
|
483
|
+
conditionText += ` equals "${cond.value}"`;
|
|
484
|
+
}
|
|
485
|
+
else if (cond.operator === 'contains') {
|
|
486
|
+
conditionText += ` contains "${cond.value}"`;
|
|
487
|
+
}
|
|
488
|
+
else if (cond.operator === 'domain_equals') {
|
|
489
|
+
conditionText += ` domain equals "${cond.value}"`;
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
conditionText += ` ${cond.operator} "${cond.value}"`;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (cond.is_useless === true) {
|
|
496
|
+
conditionText += (conditionText ? ' AND ' : 'When ') + 'email is useless/low-value';
|
|
497
|
+
}
|
|
498
|
+
if (cond.ai_priority) {
|
|
499
|
+
conditionText += (conditionText ? ' AND ' : 'When ') + `AI priority is "${cond.ai_priority}"`;
|
|
500
|
+
}
|
|
501
|
+
// Extract older_than_days from condition JSONB
|
|
502
|
+
if (cond.older_than_days) {
|
|
503
|
+
conditionText += (conditionText ? ' AND ' : 'When ') + `email is older than ${cond.older_than_days} days`;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return `Rule ${i + 1} [ID: ${r.id}]\n` +
|
|
507
|
+
` Name: ${r.name}\n` +
|
|
508
|
+
(r.description ? ` Description: ${r.description}\n` : '') +
|
|
509
|
+
(r.intent ? ` Intent: ${r.intent}\n` : '') +
|
|
510
|
+
(conditionText ? ` Condition: ${conditionText}\n` : '') +
|
|
511
|
+
` Actions: ${r.actions?.join(', ') || r.action || 'none'}\n` +
|
|
512
|
+
(r.instructions ? ` Draft Instructions: ${r.instructions}\n` : '') +
|
|
513
|
+
'\n';
|
|
514
|
+
}).join('');
|
|
482
515
|
}
|
|
483
516
|
// 5. Context-Aware Analysis: AI evaluates email against user's rules
|
|
484
517
|
const intelligenceService = getIntelligenceService(settings?.llm_model || settings?.llm_base_url || settings?.llm_api_key
|