ms365-mcp-server 1.1.16 → 1.1.18
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/dist/index.js +1122 -9
- package/dist/utils/batch-performance-monitor.js +106 -0
- package/dist/utils/batch-test-scenarios.js +277 -0
- package/dist/utils/context-aware-search.js +499 -0
- package/dist/utils/cross-reference-detector.js +352 -0
- package/dist/utils/document-workflow.js +433 -0
- package/dist/utils/enhanced-fuzzy-search.js +514 -0
- package/dist/utils/error-handler.js +337 -0
- package/dist/utils/intelligence-engine.js +71 -0
- package/dist/utils/intelligent-cache.js +379 -0
- package/dist/utils/large-mailbox-search.js +599 -0
- package/dist/utils/ms365-operations.js +730 -208
- package/dist/utils/performance-monitor.js +395 -0
- package/dist/utils/proactive-intelligence.js +390 -0
- package/dist/utils/rate-limiter.js +284 -0
- package/dist/utils/search-batch-pipeline.js +222 -0
- package/dist/utils/thread-reconstruction.js +700 -0
- package/package.json +1 -1
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { logger } from './api.js';
|
|
2
|
+
export class CrossReferenceDetector {
|
|
3
|
+
constructor(ms365Operations) {
|
|
4
|
+
this.ms365Operations = ms365Operations;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Find all emails related to a specific email
|
|
8
|
+
*/
|
|
9
|
+
async findRelatedEmails(targetEmail, allEmails, options = {}) {
|
|
10
|
+
const opts = { ...CrossReferenceDetector.DEFAULT_OPTIONS, ...options };
|
|
11
|
+
const results = [];
|
|
12
|
+
logger.log(`🔍 Finding related emails for: ${targetEmail.subject}`);
|
|
13
|
+
// Filter emails by time window
|
|
14
|
+
const timeFilteredEmails = this.filterByTimeWindow(allEmails, targetEmail, opts.timeWindowDays);
|
|
15
|
+
// 1. Find conversation thread emails
|
|
16
|
+
if (opts.includeConversationThreads) {
|
|
17
|
+
const conversationEmails = this.findConversationThreads(targetEmail, timeFilteredEmails);
|
|
18
|
+
if (conversationEmails.length > 0) {
|
|
19
|
+
results.push({
|
|
20
|
+
originalEmail: targetEmail,
|
|
21
|
+
relatedEmails: conversationEmails,
|
|
22
|
+
relationshipType: 'conversation',
|
|
23
|
+
confidence: 0.95,
|
|
24
|
+
reason: `Found ${conversationEmails.length} emails in same conversation thread`
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// 2. Find forwarded chains
|
|
29
|
+
if (opts.includeForwardedChains) {
|
|
30
|
+
const forwardedEmails = this.findForwardedChains(targetEmail, timeFilteredEmails);
|
|
31
|
+
if (forwardedEmails.length > 0) {
|
|
32
|
+
results.push({
|
|
33
|
+
originalEmail: targetEmail,
|
|
34
|
+
relatedEmails: forwardedEmails,
|
|
35
|
+
relationshipType: 'forwarded',
|
|
36
|
+
confidence: 0.9,
|
|
37
|
+
reason: `Found ${forwardedEmails.length} emails in forwarded chain`
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// 3. Find emails with references
|
|
42
|
+
if (opts.includeReferences) {
|
|
43
|
+
const referencedEmails = this.findReferencedEmails(targetEmail, timeFilteredEmails);
|
|
44
|
+
if (referencedEmails.length > 0) {
|
|
45
|
+
results.push({
|
|
46
|
+
originalEmail: targetEmail,
|
|
47
|
+
relatedEmails: referencedEmails,
|
|
48
|
+
relationshipType: 'reference',
|
|
49
|
+
confidence: 0.85,
|
|
50
|
+
reason: `Found ${referencedEmails.length} emails with cross-references`
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// 4. Find content similarity
|
|
55
|
+
if (opts.includeContentSimilarity) {
|
|
56
|
+
const similarEmails = this.findSimilarContent(targetEmail, timeFilteredEmails, opts.similarityThreshold);
|
|
57
|
+
if (similarEmails.length > 0) {
|
|
58
|
+
results.push({
|
|
59
|
+
originalEmail: targetEmail,
|
|
60
|
+
relatedEmails: similarEmails,
|
|
61
|
+
relationshipType: 'similarity',
|
|
62
|
+
confidence: 0.8,
|
|
63
|
+
reason: `Found ${similarEmails.length} emails with similar content`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Remove duplicates and limit results
|
|
68
|
+
const uniqueResults = this.removeDuplicateResults(results);
|
|
69
|
+
return uniqueResults.slice(0, opts.maxResults);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Find emails in the same conversation thread
|
|
73
|
+
*/
|
|
74
|
+
findConversationThreads(targetEmail, emails) {
|
|
75
|
+
return emails.filter(email => email.id !== targetEmail.id &&
|
|
76
|
+
email.conversationId === targetEmail.conversationId);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Find forwarded email chains using subject patterns and content analysis
|
|
80
|
+
*/
|
|
81
|
+
findForwardedChains(targetEmail, emails) {
|
|
82
|
+
const forwardedEmails = [];
|
|
83
|
+
// Look for forwarded subject patterns
|
|
84
|
+
const subjectPatterns = [
|
|
85
|
+
/^(fw|fwd|forward):\s*/i,
|
|
86
|
+
/^re:\s*fw:/i,
|
|
87
|
+
/^re:\s*fwd:/i
|
|
88
|
+
];
|
|
89
|
+
const cleanSubject = this.cleanSubject(targetEmail.subject);
|
|
90
|
+
for (const email of emails) {
|
|
91
|
+
if (email.id === targetEmail.id)
|
|
92
|
+
continue;
|
|
93
|
+
const emailCleanSubject = this.cleanSubject(email.subject);
|
|
94
|
+
// Check if subjects match after cleaning
|
|
95
|
+
if (this.subjectsMatch(cleanSubject, emailCleanSubject)) {
|
|
96
|
+
// Check for forwarded patterns in body
|
|
97
|
+
if (this.hasForwardedContent(email.bodyPreview, targetEmail.bodyPreview)) {
|
|
98
|
+
forwardedEmails.push(email);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return forwardedEmails;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Find emails with cross-references (mentions, attachments, etc.)
|
|
106
|
+
*/
|
|
107
|
+
findReferencedEmails(targetEmail, emails) {
|
|
108
|
+
const referencedEmails = [];
|
|
109
|
+
// Extract reference patterns from target email
|
|
110
|
+
const referencePatterns = this.extractReferencePatterns(targetEmail);
|
|
111
|
+
for (const email of emails) {
|
|
112
|
+
if (email.id === targetEmail.id)
|
|
113
|
+
continue;
|
|
114
|
+
// Check if this email contains any reference patterns
|
|
115
|
+
if (this.containsReferences(email, referencePatterns)) {
|
|
116
|
+
referencedEmails.push(email);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return referencedEmails;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Find emails with similar content using various similarity metrics
|
|
123
|
+
*/
|
|
124
|
+
findSimilarContent(targetEmail, emails, threshold) {
|
|
125
|
+
const similarEmails = [];
|
|
126
|
+
const targetContent = this.extractContentFeatures(targetEmail);
|
|
127
|
+
for (const email of emails) {
|
|
128
|
+
if (email.id === targetEmail.id)
|
|
129
|
+
continue;
|
|
130
|
+
const emailContent = this.extractContentFeatures(email);
|
|
131
|
+
const similarity = this.calculateContentSimilarity(targetContent, emailContent);
|
|
132
|
+
if (similarity >= threshold) {
|
|
133
|
+
similarEmails.push(email);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return similarEmails;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Extract content features for similarity comparison
|
|
140
|
+
*/
|
|
141
|
+
extractContentFeatures(email) {
|
|
142
|
+
const text = `${email.subject} ${email.bodyPreview}`.toLowerCase();
|
|
143
|
+
return {
|
|
144
|
+
keywords: this.extractKeywords(text),
|
|
145
|
+
entities: this.extractEntities(text),
|
|
146
|
+
subjects: [email.subject.toLowerCase()],
|
|
147
|
+
senders: [email.from.address.toLowerCase(), email.from.name.toLowerCase()]
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Extract keywords from text
|
|
152
|
+
*/
|
|
153
|
+
extractKeywords(text) {
|
|
154
|
+
// Remove common stop words and extract meaningful keywords
|
|
155
|
+
const stopWords = new Set(['the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might', 'can', 'a', 'an', 'this', 'that', 'these', 'those']);
|
|
156
|
+
const words = text.match(/\b\w{3,}\b/g) || [];
|
|
157
|
+
return words.filter(word => !stopWords.has(word.toLowerCase()));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Extract entities (emails, dates, numbers, etc.)
|
|
161
|
+
*/
|
|
162
|
+
extractEntities(text) {
|
|
163
|
+
const entities = [];
|
|
164
|
+
// Extract email addresses
|
|
165
|
+
const emailRegex = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g;
|
|
166
|
+
const emails = text.match(emailRegex) || [];
|
|
167
|
+
entities.push(...emails);
|
|
168
|
+
// Extract dates
|
|
169
|
+
const dateRegex = /\b\d{1,2}[/-]\d{1,2}[/-]\d{2,4}\b/g;
|
|
170
|
+
const dates = text.match(dateRegex) || [];
|
|
171
|
+
entities.push(...dates);
|
|
172
|
+
// Extract numbers that might be reference numbers
|
|
173
|
+
const numberRegex = /\b\d{4,}\b/g;
|
|
174
|
+
const numbers = text.match(numberRegex) || [];
|
|
175
|
+
entities.push(...numbers);
|
|
176
|
+
return entities;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Calculate content similarity between two emails
|
|
180
|
+
*/
|
|
181
|
+
calculateContentSimilarity(content1, content2) {
|
|
182
|
+
let totalScore = 0;
|
|
183
|
+
let maxScore = 0;
|
|
184
|
+
// Keyword similarity
|
|
185
|
+
const keywordSimilarity = this.calculateArraySimilarity(content1.keywords, content2.keywords);
|
|
186
|
+
totalScore += keywordSimilarity * 0.4;
|
|
187
|
+
maxScore += 0.4;
|
|
188
|
+
// Entity similarity
|
|
189
|
+
const entitySimilarity = this.calculateArraySimilarity(content1.entities, content2.entities);
|
|
190
|
+
totalScore += entitySimilarity * 0.3;
|
|
191
|
+
maxScore += 0.3;
|
|
192
|
+
// Subject similarity
|
|
193
|
+
const subjectSimilarity = this.calculateArraySimilarity(content1.subjects, content2.subjects);
|
|
194
|
+
totalScore += subjectSimilarity * 0.2;
|
|
195
|
+
maxScore += 0.2;
|
|
196
|
+
// Sender similarity
|
|
197
|
+
const senderSimilarity = this.calculateArraySimilarity(content1.senders, content2.senders);
|
|
198
|
+
totalScore += senderSimilarity * 0.1;
|
|
199
|
+
maxScore += 0.1;
|
|
200
|
+
return maxScore > 0 ? totalScore / maxScore : 0;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Calculate similarity between two arrays of strings
|
|
204
|
+
*/
|
|
205
|
+
calculateArraySimilarity(arr1, arr2) {
|
|
206
|
+
if (arr1.length === 0 && arr2.length === 0)
|
|
207
|
+
return 1;
|
|
208
|
+
if (arr1.length === 0 || arr2.length === 0)
|
|
209
|
+
return 0;
|
|
210
|
+
const set1 = new Set(arr1);
|
|
211
|
+
const set2 = new Set(arr2);
|
|
212
|
+
const intersection = new Set([...set1].filter(x => set2.has(x)));
|
|
213
|
+
const union = new Set([...set1, ...set2]);
|
|
214
|
+
return intersection.size / union.size;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Clean subject line by removing prefixes and normalization
|
|
218
|
+
*/
|
|
219
|
+
cleanSubject(subject) {
|
|
220
|
+
return subject
|
|
221
|
+
.replace(/^(re|fw|fwd|forward):\s*/gi, '')
|
|
222
|
+
.replace(/\s+/g, ' ')
|
|
223
|
+
.trim()
|
|
224
|
+
.toLowerCase();
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Check if two subjects match after cleaning
|
|
228
|
+
*/
|
|
229
|
+
subjectsMatch(subject1, subject2) {
|
|
230
|
+
return subject1 === subject2 ||
|
|
231
|
+
this.calculateStringSimilarity(subject1, subject2) > 0.8;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Check if email contains forwarded content
|
|
235
|
+
*/
|
|
236
|
+
hasForwardedContent(bodyPreview1, bodyPreview2) {
|
|
237
|
+
// Look for forwarded email patterns
|
|
238
|
+
const forwardedPatterns = [
|
|
239
|
+
/from:\s*.*\s*sent:/i,
|
|
240
|
+
/forwarded message/i,
|
|
241
|
+
/original message/i,
|
|
242
|
+
/---------- forwarded message/i
|
|
243
|
+
];
|
|
244
|
+
return forwardedPatterns.some(pattern => pattern.test(bodyPreview1) || pattern.test(bodyPreview2));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Extract reference patterns from an email
|
|
248
|
+
*/
|
|
249
|
+
extractReferencePatterns(email) {
|
|
250
|
+
const patterns = [];
|
|
251
|
+
const text = `${email.subject} ${email.bodyPreview}`;
|
|
252
|
+
// Extract reference numbers, case numbers, etc.
|
|
253
|
+
const refRegex = /\b(ref|reference|case|ticket|order|invoice|id)[\s#:]*([a-z0-9\-]+)/gi;
|
|
254
|
+
const matches = text.match(refRegex) || [];
|
|
255
|
+
patterns.push(...matches);
|
|
256
|
+
return patterns;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Check if email contains reference patterns
|
|
260
|
+
*/
|
|
261
|
+
containsReferences(email, patterns) {
|
|
262
|
+
const text = `${email.subject} ${email.bodyPreview}`.toLowerCase();
|
|
263
|
+
return patterns.some(pattern => text.includes(pattern.toLowerCase()));
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Filter emails by time window
|
|
267
|
+
*/
|
|
268
|
+
filterByTimeWindow(emails, targetEmail, days) {
|
|
269
|
+
const targetDate = new Date(targetEmail.receivedDateTime);
|
|
270
|
+
const windowStart = new Date(targetDate.getTime() - (days * 24 * 60 * 60 * 1000));
|
|
271
|
+
const windowEnd = new Date(targetDate.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
272
|
+
return emails.filter(email => {
|
|
273
|
+
const emailDate = new Date(email.receivedDateTime);
|
|
274
|
+
return emailDate >= windowStart && emailDate <= windowEnd;
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Remove duplicate results
|
|
279
|
+
*/
|
|
280
|
+
removeDuplicateResults(results) {
|
|
281
|
+
const seen = new Set();
|
|
282
|
+
const uniqueResults = [];
|
|
283
|
+
for (const result of results) {
|
|
284
|
+
const key = `${result.relationshipType}-${result.relatedEmails.map(e => e.id).join(',')}`;
|
|
285
|
+
if (!seen.has(key)) {
|
|
286
|
+
seen.add(key);
|
|
287
|
+
uniqueResults.push(result);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return uniqueResults;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Calculate string similarity using simple algorithm
|
|
294
|
+
*/
|
|
295
|
+
calculateStringSimilarity(str1, str2) {
|
|
296
|
+
if (str1 === str2)
|
|
297
|
+
return 1;
|
|
298
|
+
const longer = str1.length > str2.length ? str1 : str2;
|
|
299
|
+
const shorter = str1.length > str2.length ? str2 : str1;
|
|
300
|
+
if (longer.length === 0)
|
|
301
|
+
return 1;
|
|
302
|
+
const editDistance = this.calculateEditDistance(longer, shorter);
|
|
303
|
+
return (longer.length - editDistance) / longer.length;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Calculate edit distance between two strings
|
|
307
|
+
*/
|
|
308
|
+
calculateEditDistance(str1, str2) {
|
|
309
|
+
const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
|
|
310
|
+
for (let i = 0; i <= str1.length; i++)
|
|
311
|
+
matrix[0][i] = i;
|
|
312
|
+
for (let j = 0; j <= str2.length; j++)
|
|
313
|
+
matrix[j][0] = j;
|
|
314
|
+
for (let j = 1; j <= str2.length; j++) {
|
|
315
|
+
for (let i = 1; i <= str1.length; i++) {
|
|
316
|
+
const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
|
317
|
+
matrix[j][i] = Math.min(matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + indicator);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return matrix[str2.length][str1.length];
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Batch process multiple emails for cross-reference detection
|
|
324
|
+
*/
|
|
325
|
+
async findAllCrossReferences(emails, options = {}) {
|
|
326
|
+
const results = new Map();
|
|
327
|
+
const opts = { ...CrossReferenceDetector.DEFAULT_OPTIONS, ...options };
|
|
328
|
+
logger.log(`🔍 Processing ${emails.length} emails for cross-references`);
|
|
329
|
+
for (const email of emails) {
|
|
330
|
+
try {
|
|
331
|
+
const crossRefs = await this.findRelatedEmails(email, emails, opts);
|
|
332
|
+
if (crossRefs.length > 0) {
|
|
333
|
+
results.set(email.id, crossRefs);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
logger.error(`Error processing cross-references for email ${email.id}:`, error);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
logger.log(`🔍 Found cross-references for ${results.size} emails`);
|
|
341
|
+
return results;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
CrossReferenceDetector.DEFAULT_OPTIONS = {
|
|
345
|
+
includeConversationThreads: true,
|
|
346
|
+
includeForwardedChains: true,
|
|
347
|
+
includeContentSimilarity: true,
|
|
348
|
+
includeReferences: true,
|
|
349
|
+
similarityThreshold: 0.7,
|
|
350
|
+
maxResults: 50,
|
|
351
|
+
timeWindowDays: 365
|
|
352
|
+
};
|