@realtimex/email-automator 2.15.0 → 2.16.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 +30 -19
- package/api/src/services/processor.ts +236 -34
- package/dist/api/src/services/intelligence.js +30 -19
- package/dist/api/src/services/processor.js +160 -22
- package/dist/assets/{index-Bl_xnDuM.js → index-CEdeZWkE.js} +5 -5
- package/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -36,18 +36,24 @@ export const ContextAwareAnalysisSchema = z.object({
|
|
|
36
36
|
summary: z.string().describe('A brief summary of the email content'),
|
|
37
37
|
category: z.enum(['spam', 'newsletter', 'promotional', 'transactional', 'social', 'support', 'client', 'internal', 'personal', 'other'])
|
|
38
38
|
.describe('The category of the email'),
|
|
39
|
+
sentiment: z.enum(['Positive', 'Neutral', 'Negative']).optional()
|
|
40
|
+
.describe('The emotional tone of the email'),
|
|
39
41
|
priority: z.enum(['High', 'Medium', 'Low'])
|
|
40
42
|
.describe('The urgency of the email'),
|
|
43
|
+
key_points: z.array(z.string()).optional()
|
|
44
|
+
.describe('Key points extracted from the email'),
|
|
45
|
+
language: z.string().optional()
|
|
46
|
+
.describe('The primary language of the email (e.g., "English", "Vietnamese", "Japanese")'),
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
rule_id: z.string().
|
|
44
|
-
rule_name: z.string().
|
|
48
|
+
matched_rules: z.array(z.object({
|
|
49
|
+
rule_id: z.string().describe('ID of the matched rule'),
|
|
50
|
+
rule_name: z.string().describe('Name of the matched rule'),
|
|
45
51
|
confidence: z.number().min(0).max(1).describe('Confidence score for the match (0-1)'),
|
|
46
|
-
reasoning: z.string().describe('
|
|
47
|
-
}),
|
|
52
|
+
reasoning: z.string().describe('Brief explanation of why this rule matched'),
|
|
53
|
+
})).describe('All rules that apply to this email (can be multiple)'),
|
|
48
54
|
|
|
49
|
-
actions_to_execute: z.array(z.enum(['none', 'delete', 'archive', 'draft', '
|
|
50
|
-
.describe('Actions to execute
|
|
55
|
+
actions_to_execute: z.array(z.enum(['none', 'delete', 'archive', 'draft', 'star']))
|
|
56
|
+
.describe('Actions to execute after conflict resolution'),
|
|
51
57
|
|
|
52
58
|
draft_content: z.string().nullable().optional()
|
|
53
59
|
.describe('Generated draft reply if the action includes drafting'),
|
|
@@ -349,7 +355,7 @@ REQUIRED JSON STRUCTURE:
|
|
|
349
355
|
rulesContext = compiledRulesContext.map(r => `- ${r.name}: ${r.intent}`).join('\n');
|
|
350
356
|
}
|
|
351
357
|
|
|
352
|
-
const systemPrompt = `You are an AI Automation Agent. Analyze the email and
|
|
358
|
+
const systemPrompt = `You are an AI Automation Agent. Analyze the email and identify ALL rules that apply.
|
|
353
359
|
|
|
354
360
|
Rules Context:
|
|
355
361
|
${rulesContext}
|
|
@@ -359,20 +365,25 @@ REQUIRED JSON STRUCTURE:
|
|
|
359
365
|
"summary": "A brief summary of the email content",
|
|
360
366
|
"category": "spam|newsletter|promotional|transactional|social|support|client|internal|personal|other",
|
|
361
367
|
"priority": "High|Medium|Low",
|
|
362
|
-
"
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
368
|
+
"matched_rules": [
|
|
369
|
+
{
|
|
370
|
+
"rule_id": "string",
|
|
371
|
+
"rule_name": "string",
|
|
372
|
+
"confidence": 0.0 to 1.0,
|
|
373
|
+
"reasoning": "Brief explanation"
|
|
374
|
+
}
|
|
375
|
+
],
|
|
376
|
+
"actions_to_execute": ["none"|"delete"|"archive"|"draft"|"star"],
|
|
369
377
|
"draft_content": "Suggested reply if drafting, otherwise null"
|
|
370
378
|
}
|
|
371
379
|
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
-
|
|
380
|
+
CRITICAL INSTRUCTIONS:
|
|
381
|
+
- Identify ALL rules that apply to this email (not just the best one)
|
|
382
|
+
- Return an empty array if no rules match
|
|
383
|
+
- Only include rules with confidence >= 0.7
|
|
384
|
+
- For each matched rule, explain why it applies
|
|
385
|
+
- Actions will be merged by the system - you don't need to resolve conflicts
|
|
386
|
+
- Use "draft" action only if a rule explicitly requests it`;
|
|
376
387
|
|
|
377
388
|
if (eventLogger) {
|
|
378
389
|
await eventLogger.info('Thinking', `Context-aware analysis: ${context.subject}`, {
|
|
@@ -20,6 +20,111 @@ export interface ProcessingResult {
|
|
|
20
20
|
errors: number;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Action classification for conflict resolution
|
|
25
|
+
*/
|
|
26
|
+
const ACTION_TYPES = {
|
|
27
|
+
// Exclusive: Only one can apply (highest priority wins)
|
|
28
|
+
EXCLUSIVE: ['delete', 'archive'] as const,
|
|
29
|
+
|
|
30
|
+
// Additive: All can apply (accumulate across rules)
|
|
31
|
+
ADDITIVE: ['star', 'unstar', 'important', 'pin'] as const,
|
|
32
|
+
|
|
33
|
+
// Semi-Exclusive: Only one makes sense, but non-conflicting
|
|
34
|
+
SEMI_EXCLUSIVE: ['draft'] as const
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
interface ResolvedActions {
|
|
38
|
+
exclusive?: string; // The winning exclusive action (delete or archive)
|
|
39
|
+
labels: string[]; // All labels to apply
|
|
40
|
+
additive: string[]; // Star, important, etc
|
|
41
|
+
draft?: {
|
|
42
|
+
content: string;
|
|
43
|
+
instructions: string;
|
|
44
|
+
attachments?: any[];
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolves conflicts between multiple matching rules
|
|
50
|
+
*
|
|
51
|
+
* Strategy:
|
|
52
|
+
* - EXCLUSIVE actions (delete, archive): Highest priority rule wins
|
|
53
|
+
* - ADDITIVE actions (labels, star): Apply ALL from ALL matching rules
|
|
54
|
+
* - SEMI-EXCLUSIVE (draft): Highest priority rule wins
|
|
55
|
+
* - If DELETE wins, skip all other actions (email is gone anyway)
|
|
56
|
+
*
|
|
57
|
+
* @param matchedRules - Rules sorted by priority (descending)
|
|
58
|
+
* @param allRules - Full rule objects for looking up actions
|
|
59
|
+
* @returns Resolved action set to execute
|
|
60
|
+
*/
|
|
61
|
+
function resolveRuleConflicts(
|
|
62
|
+
matchedRules: Array<{ rule_id: string; confidence: number }>,
|
|
63
|
+
allRules: Rule[]
|
|
64
|
+
): ResolvedActions {
|
|
65
|
+
const resolved: ResolvedActions = {
|
|
66
|
+
labels: [],
|
|
67
|
+
additive: []
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Process rules in priority order (already sorted)
|
|
71
|
+
for (const match of matchedRules) {
|
|
72
|
+
const rule = allRules.find(r => r.id === match.rule_id);
|
|
73
|
+
if (!rule) continue;
|
|
74
|
+
|
|
75
|
+
const actions = rule.actions || [];
|
|
76
|
+
|
|
77
|
+
for (const action of actions) {
|
|
78
|
+
// Handle label actions (always additive)
|
|
79
|
+
if (action.startsWith('label:')) {
|
|
80
|
+
const label = action.substring(6); // Remove 'label:' prefix
|
|
81
|
+
if (!resolved.labels.includes(label)) {
|
|
82
|
+
resolved.labels.push(label);
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle exclusive actions (delete/archive)
|
|
88
|
+
if (ACTION_TYPES.EXCLUSIVE.includes(action as any)) {
|
|
89
|
+
// Only set if not already set by higher priority rule
|
|
90
|
+
if (!resolved.exclusive) {
|
|
91
|
+
resolved.exclusive = action;
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Handle additive actions (star, important, etc)
|
|
97
|
+
if (ACTION_TYPES.ADDITIVE.includes(action as any)) {
|
|
98
|
+
if (!resolved.additive.includes(action)) {
|
|
99
|
+
resolved.additive.push(action);
|
|
100
|
+
}
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Handle draft (semi-exclusive)
|
|
105
|
+
if (action === 'draft' && !resolved.draft) {
|
|
106
|
+
resolved.draft = {
|
|
107
|
+
content: '', // Will be generated later
|
|
108
|
+
instructions: rule.instructions || '',
|
|
109
|
+
attachments: rule.attachments
|
|
110
|
+
};
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Nuclear option: If DELETE wins, discard everything else
|
|
117
|
+
if (resolved.exclusive === 'delete') {
|
|
118
|
+
return {
|
|
119
|
+
exclusive: 'delete',
|
|
120
|
+
labels: [],
|
|
121
|
+
additive: []
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return resolved;
|
|
126
|
+
}
|
|
127
|
+
|
|
23
128
|
export class EmailProcessorService {
|
|
24
129
|
private supabase: SupabaseClient;
|
|
25
130
|
private gmailService = getGmailService();
|
|
@@ -676,6 +781,7 @@ export class EmailProcessorService {
|
|
|
676
781
|
}
|
|
677
782
|
|
|
678
783
|
// 6. Update the email record with context-aware results
|
|
784
|
+
const primaryRule = analysis.matched_rules[0]; // Highest priority/confidence rule
|
|
679
785
|
await this.supabase
|
|
680
786
|
.from('emails')
|
|
681
787
|
.update({
|
|
@@ -683,51 +789,147 @@ export class EmailProcessorService {
|
|
|
683
789
|
ai_analysis: analysis as any,
|
|
684
790
|
suggested_actions: analysis.actions_to_execute || [],
|
|
685
791
|
suggested_action: analysis.actions_to_execute?.[0] || 'none',
|
|
686
|
-
matched_rule_id:
|
|
687
|
-
matched_rule_confidence:
|
|
792
|
+
matched_rule_id: primaryRule?.rule_id || null,
|
|
793
|
+
matched_rule_confidence: primaryRule?.confidence || 0,
|
|
688
794
|
processing_status: 'completed'
|
|
689
795
|
})
|
|
690
796
|
.eq('id', email.id);
|
|
691
797
|
|
|
692
|
-
// 7. Execute actions
|
|
693
|
-
if (account && analysis.
|
|
694
|
-
|
|
798
|
+
// 7. Execute actions with conflict resolution
|
|
799
|
+
if (account && analysis.matched_rules.length > 0 && rules) {
|
|
800
|
+
// Filter rules by minimum confidence threshold
|
|
801
|
+
const highConfidenceMatches = analysis.matched_rules.filter(m => m.confidence >= 0.7);
|
|
802
|
+
|
|
803
|
+
if (highConfidenceMatches.length > 0) {
|
|
804
|
+
// Sort matched rules by their priority (from rules table)
|
|
805
|
+
const sortedMatches = highConfidenceMatches
|
|
806
|
+
.map(match => ({
|
|
807
|
+
...match,
|
|
808
|
+
priority: rules.find(r => r.id === match.rule_id)?.priority || 0
|
|
809
|
+
}))
|
|
810
|
+
.sort((a, b) => b.priority - a.priority);
|
|
811
|
+
|
|
812
|
+
// Log all matched rules
|
|
813
|
+
if (eventLogger) {
|
|
814
|
+
const matchSummary = sortedMatches.map(m =>
|
|
815
|
+
`"${m.rule_name}" (${(m.confidence * 100).toFixed(0)}%)`
|
|
816
|
+
).join(', ');
|
|
817
|
+
await eventLogger.info('Rules Matched',
|
|
818
|
+
`${sortedMatches.length} rule(s) apply: ${matchSummary}`,
|
|
819
|
+
{ rules: sortedMatches.map(m => ({ name: m.rule_name, confidence: m.confidence, reasoning: m.reasoning })) },
|
|
820
|
+
email.id
|
|
821
|
+
);
|
|
822
|
+
}
|
|
695
823
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
`"${analysis.matched_rule.rule_name}" matched with ${(analysis.matched_rule.confidence * 100).toFixed(0)}% confidence`,
|
|
699
|
-
{ reasoning: analysis.matched_rule.reasoning },
|
|
700
|
-
email.id
|
|
701
|
-
);
|
|
702
|
-
}
|
|
824
|
+
// Resolve conflicts between rules
|
|
825
|
+
const resolved = resolveRuleConflicts(sortedMatches, rules);
|
|
703
826
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
await this.executeAction(
|
|
712
|
-
account,
|
|
713
|
-
email,
|
|
714
|
-
action as any,
|
|
715
|
-
draftContent,
|
|
716
|
-
eventLogger,
|
|
717
|
-
`Rule: ${matchedRule?.name || analysis.matched_rule.rule_name}`,
|
|
718
|
-
matchedRule?.attachments
|
|
719
|
-
);
|
|
827
|
+
if (eventLogger) {
|
|
828
|
+
await eventLogger.info('Actions Resolved',
|
|
829
|
+
`After conflict resolution: ${[resolved.exclusive, ...resolved.labels.map(l => `label:${l}`), ...resolved.additive, resolved.draft ? 'draft' : null].filter(Boolean).join(', ')}`,
|
|
830
|
+
{ resolved },
|
|
831
|
+
email.id
|
|
832
|
+
);
|
|
833
|
+
}
|
|
720
834
|
|
|
721
|
-
//
|
|
722
|
-
if (
|
|
723
|
-
|
|
724
|
-
|
|
835
|
+
// Execute exclusive action (delete or archive)
|
|
836
|
+
if (resolved.exclusive) {
|
|
837
|
+
await this.executeAction(
|
|
838
|
+
account,
|
|
839
|
+
email,
|
|
840
|
+
resolved.exclusive as any,
|
|
841
|
+
undefined,
|
|
842
|
+
eventLogger,
|
|
843
|
+
`Rules: ${sortedMatches.map(m => m.rule_name).join(', ')}`
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
if (result) {
|
|
847
|
+
if (resolved.exclusive === 'delete') result.deleted++;
|
|
848
|
+
}
|
|
725
849
|
}
|
|
850
|
+
|
|
851
|
+
// If deleted, skip other actions (email is gone)
|
|
852
|
+
if (resolved.exclusive === 'delete') {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Execute label actions
|
|
857
|
+
for (const label of resolved.labels) {
|
|
858
|
+
await this.executeAction(
|
|
859
|
+
account,
|
|
860
|
+
email,
|
|
861
|
+
`label:${label}` as any,
|
|
862
|
+
undefined,
|
|
863
|
+
eventLogger,
|
|
864
|
+
`Rules: ${sortedMatches.map(m => m.rule_name).join(', ')}`
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Execute additive actions (star, important, etc)
|
|
869
|
+
for (const action of resolved.additive) {
|
|
870
|
+
await this.executeAction(
|
|
871
|
+
account,
|
|
872
|
+
email,
|
|
873
|
+
action as any,
|
|
874
|
+
undefined,
|
|
875
|
+
eventLogger,
|
|
876
|
+
`Rules: ${sortedMatches.map(m => m.rule_name).join(', ')}`
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Execute draft action
|
|
881
|
+
if (resolved.draft) {
|
|
882
|
+
// Build rich context for draft generation
|
|
883
|
+
const emailDomain = account.email_address?.split('@')[1] || undefined;
|
|
884
|
+
const richContext = {
|
|
885
|
+
myEmail: account.email_address,
|
|
886
|
+
myName: undefined,
|
|
887
|
+
myRole: settings?.user_role || undefined,
|
|
888
|
+
myCompany: emailDomain,
|
|
889
|
+
category: analysis?.category,
|
|
890
|
+
sentiment: analysis?.sentiment,
|
|
891
|
+
priority: analysis?.priority,
|
|
892
|
+
keyPoints: analysis?.key_points,
|
|
893
|
+
language: analysis?.language,
|
|
894
|
+
senderEmail: email.sender || undefined,
|
|
895
|
+
senderName: email.sender || undefined,
|
|
896
|
+
receivedDate: email.date ? new Date(email.date) : undefined
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
const draftContent = await intelligenceService.generateDraftReply({
|
|
900
|
+
subject: email.subject || '',
|
|
901
|
+
sender: email.sender || '',
|
|
902
|
+
body: email.body_snippet || ''
|
|
903
|
+
}, resolved.draft.instructions, {
|
|
904
|
+
llm_provider: settings?.llm_provider,
|
|
905
|
+
llm_model: settings?.llm_model
|
|
906
|
+
}, richContext);
|
|
907
|
+
|
|
908
|
+
if (draftContent) {
|
|
909
|
+
await this.executeAction(
|
|
910
|
+
account,
|
|
911
|
+
email,
|
|
912
|
+
'draft' as any,
|
|
913
|
+
draftContent,
|
|
914
|
+
eventLogger,
|
|
915
|
+
`Rules: ${sortedMatches.map(m => m.rule_name).join(', ')}`,
|
|
916
|
+
resolved.draft.attachments
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
if (result) result.drafted++;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
} else if (eventLogger) {
|
|
923
|
+
await eventLogger.info('Low Confidence',
|
|
924
|
+
`${analysis.matched_rules.length} rule(s) matched but below 0.7 confidence threshold`,
|
|
925
|
+
{ rules: analysis.matched_rules },
|
|
926
|
+
email.id
|
|
927
|
+
);
|
|
726
928
|
}
|
|
727
929
|
} else if (eventLogger && rules && rules.length > 0) {
|
|
728
930
|
await eventLogger.info('No Match',
|
|
729
|
-
|
|
730
|
-
{
|
|
931
|
+
'No rules matched this email',
|
|
932
|
+
{ category: analysis.category },
|
|
731
933
|
email.id
|
|
732
934
|
);
|
|
733
935
|
}
|
|
@@ -30,16 +30,22 @@ export const ContextAwareAnalysisSchema = z.object({
|
|
|
30
30
|
summary: z.string().describe('A brief summary of the email content'),
|
|
31
31
|
category: z.enum(['spam', 'newsletter', 'promotional', 'transactional', 'social', 'support', 'client', 'internal', 'personal', 'other'])
|
|
32
32
|
.describe('The category of the email'),
|
|
33
|
+
sentiment: z.enum(['Positive', 'Neutral', 'Negative']).optional()
|
|
34
|
+
.describe('The emotional tone of the email'),
|
|
33
35
|
priority: z.enum(['High', 'Medium', 'Low'])
|
|
34
36
|
.describe('The urgency of the email'),
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
key_points: z.array(z.string()).optional()
|
|
38
|
+
.describe('Key points extracted from the email'),
|
|
39
|
+
language: z.string().optional()
|
|
40
|
+
.describe('The primary language of the email (e.g., "English", "Vietnamese", "Japanese")'),
|
|
41
|
+
matched_rules: z.array(z.object({
|
|
42
|
+
rule_id: z.string().describe('ID of the matched rule'),
|
|
43
|
+
rule_name: z.string().describe('Name of the matched rule'),
|
|
38
44
|
confidence: z.number().min(0).max(1).describe('Confidence score for the match (0-1)'),
|
|
39
|
-
reasoning: z.string().describe('
|
|
40
|
-
}),
|
|
41
|
-
actions_to_execute: z.array(z.enum(['none', 'delete', 'archive', 'draft', '
|
|
42
|
-
.describe('Actions to execute
|
|
45
|
+
reasoning: z.string().describe('Brief explanation of why this rule matched'),
|
|
46
|
+
})).describe('All rules that apply to this email (can be multiple)'),
|
|
47
|
+
actions_to_execute: z.array(z.enum(['none', 'delete', 'archive', 'draft', 'star']))
|
|
48
|
+
.describe('Actions to execute after conflict resolution'),
|
|
43
49
|
draft_content: z.string().nullable().optional()
|
|
44
50
|
.describe('Generated draft reply if the action includes drafting'),
|
|
45
51
|
});
|
|
@@ -258,7 +264,7 @@ REQUIRED JSON STRUCTURE:
|
|
|
258
264
|
else {
|
|
259
265
|
rulesContext = compiledRulesContext.map(r => `- ${r.name}: ${r.intent}`).join('\n');
|
|
260
266
|
}
|
|
261
|
-
const systemPrompt = `You are an AI Automation Agent. Analyze the email and
|
|
267
|
+
const systemPrompt = `You are an AI Automation Agent. Analyze the email and identify ALL rules that apply.
|
|
262
268
|
|
|
263
269
|
Rules Context:
|
|
264
270
|
${rulesContext}
|
|
@@ -268,20 +274,25 @@ REQUIRED JSON STRUCTURE:
|
|
|
268
274
|
"summary": "A brief summary of the email content",
|
|
269
275
|
"category": "spam|newsletter|promotional|transactional|social|support|client|internal|personal|other",
|
|
270
276
|
"priority": "High|Medium|Low",
|
|
271
|
-
"
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
277
|
+
"matched_rules": [
|
|
278
|
+
{
|
|
279
|
+
"rule_id": "string",
|
|
280
|
+
"rule_name": "string",
|
|
281
|
+
"confidence": 0.0 to 1.0,
|
|
282
|
+
"reasoning": "Brief explanation"
|
|
283
|
+
}
|
|
284
|
+
],
|
|
285
|
+
"actions_to_execute": ["none"|"delete"|"archive"|"draft"|"star"],
|
|
278
286
|
"draft_content": "Suggested reply if drafting, otherwise null"
|
|
279
287
|
}
|
|
280
288
|
|
|
281
|
-
|
|
282
|
-
-
|
|
283
|
-
-
|
|
284
|
-
-
|
|
289
|
+
CRITICAL INSTRUCTIONS:
|
|
290
|
+
- Identify ALL rules that apply to this email (not just the best one)
|
|
291
|
+
- Return an empty array if no rules match
|
|
292
|
+
- Only include rules with confidence >= 0.7
|
|
293
|
+
- For each matched rule, explain why it applies
|
|
294
|
+
- Actions will be merged by the system - you don't need to resolve conflicts
|
|
295
|
+
- Use "draft" action only if a rule explicitly requests it`;
|
|
285
296
|
if (eventLogger) {
|
|
286
297
|
await eventLogger.info('Thinking', `Context-aware analysis: ${context.subject}`, {
|
|
287
298
|
provider: `${provider}/${model}`,
|