@ouro.bot/cli 0.1.0-alpha.484 → 0.1.0-alpha.486
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/changelog.json +17 -0
- package/dist/heart/active-work.js +89 -3
- package/dist/heart/background-operations.js +26 -3
- package/dist/heart/daemon/cli-exec.js +171 -9
- package/dist/heart/mail-import-discovery.js +37 -2
- package/dist/heart/providers/azure.js +1 -1
- package/dist/heart/providers/github-copilot.js +1 -1
- package/dist/heart/providers/openai-codex.js +1 -1
- package/dist/heart/session-events.js +40 -3
- package/dist/heart/streaming.js +13 -2
- package/dist/mailroom/blob-store.js +16 -10
- package/dist/mailroom/core.js +1 -1
- package/dist/mailroom/file-store.js +35 -9
- package/dist/mailroom/mbox-import.js +41 -0
- package/dist/mailroom/reader.js +22 -0
- package/dist/mailroom/search-cache.js +182 -0
- package/dist/mailroom/search-relevance.js +319 -0
- package/dist/mind/context.js +36 -4
- package/dist/mind/friends/resolver.js +16 -1
- package/dist/nerves/coverage/file-completeness.js +4 -0
- package/dist/repertoire/tools-mail.js +453 -68
- package/dist/senses/bluebubbles/inbound-log.js +13 -0
- package/dist/senses/bluebubbles/index.js +394 -236
- package/dist/senses/bluebubbles/processed-log.js +111 -0
- package/dist/senses/inner-dialog-worker.js +38 -2
- package/dist/senses/mail.js +19 -3
- package/dist/senses/trust-gate.js +96 -1
- package/package.json +1 -1
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Booking-intent relevance for mail search.
|
|
3
|
+
//
|
|
4
|
+
// Today the agent's mail search ranks by recency only — `searchText.includes(term)`
|
|
5
|
+
// then `receivedAt` desc. That works for "give me the latest message" but fails
|
|
6
|
+
// the workflow Slugger actually does most: "find the decisive booking message
|
|
7
|
+
// in this delegated mailbox so I can update a travel doc from real evidence."
|
|
8
|
+
// Recent newsletter / itinerary chatter from the same sender drowns the older
|
|
9
|
+
// confirmation.
|
|
10
|
+
//
|
|
11
|
+
// This module adds a small additive score per document. Signals are heuristic
|
|
12
|
+
// and intentionally legible — not learned. Each signal is also exposed so the
|
|
13
|
+
// renderer can surface a "matched on" hint to the agent for triage.
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.scoreMailSearchDocument = scoreMailSearchDocument;
|
|
16
|
+
exports.compareByRelevanceThenRecency = compareByRelevanceThenRecency;
|
|
17
|
+
exports.formatRelevanceHint = formatRelevanceHint;
|
|
18
|
+
const BOOKING_INTENT_TOKENS = [
|
|
19
|
+
"booking confirmation",
|
|
20
|
+
"booked",
|
|
21
|
+
"your booking",
|
|
22
|
+
"your reservation",
|
|
23
|
+
"your stay",
|
|
24
|
+
"your trip",
|
|
25
|
+
"reservation confirmation",
|
|
26
|
+
"reservation confirmed",
|
|
27
|
+
"confirmation number",
|
|
28
|
+
"e-ticket",
|
|
29
|
+
"eticket",
|
|
30
|
+
"itinerary",
|
|
31
|
+
"receipt",
|
|
32
|
+
"invoice",
|
|
33
|
+
"check-in",
|
|
34
|
+
"check in",
|
|
35
|
+
"departure",
|
|
36
|
+
"arrival",
|
|
37
|
+
"boarding pass",
|
|
38
|
+
"confirmed",
|
|
39
|
+
"confirmation",
|
|
40
|
+
"reservation",
|
|
41
|
+
];
|
|
42
|
+
// Subject-only tokens get an extra bump because subjects rarely include them
|
|
43
|
+
// for non-decisive mail.
|
|
44
|
+
const SUBJECT_DECISIVE_TOKENS = [
|
|
45
|
+
"booking confirmation",
|
|
46
|
+
"reservation confirmation",
|
|
47
|
+
"your booking",
|
|
48
|
+
"your reservation",
|
|
49
|
+
"your stay",
|
|
50
|
+
"e-ticket",
|
|
51
|
+
"eticket",
|
|
52
|
+
"boarding pass",
|
|
53
|
+
"confirmed",
|
|
54
|
+
"itinerary",
|
|
55
|
+
"receipt",
|
|
56
|
+
];
|
|
57
|
+
// Known travel-domain senders. Substring match against the from list. Kept
|
|
58
|
+
// short on purpose — the score still works without exhaustive coverage; this
|
|
59
|
+
// just reinforces obvious cases.
|
|
60
|
+
const KNOWN_TRAVEL_SENDER_PATTERNS = [
|
|
61
|
+
"booking.com",
|
|
62
|
+
"hotels.com",
|
|
63
|
+
"expedia",
|
|
64
|
+
"airbnb",
|
|
65
|
+
"marriott",
|
|
66
|
+
"hilton",
|
|
67
|
+
"hyatt",
|
|
68
|
+
"ihg",
|
|
69
|
+
"accorhotels",
|
|
70
|
+
"kayak",
|
|
71
|
+
"agoda",
|
|
72
|
+
"trivago",
|
|
73
|
+
"vrbo",
|
|
74
|
+
"swiss.com",
|
|
75
|
+
"lufthansa",
|
|
76
|
+
"ryanair",
|
|
77
|
+
"easyjet",
|
|
78
|
+
"ba.com",
|
|
79
|
+
"delta.com",
|
|
80
|
+
"united.com",
|
|
81
|
+
"aa.com",
|
|
82
|
+
"alaskaair",
|
|
83
|
+
"klm.com",
|
|
84
|
+
"airfrance",
|
|
85
|
+
"iberia",
|
|
86
|
+
"sas.se",
|
|
87
|
+
"norwegian.com",
|
|
88
|
+
"sbb.ch",
|
|
89
|
+
"sncf",
|
|
90
|
+
"trenitalia",
|
|
91
|
+
"renfe",
|
|
92
|
+
"eurail",
|
|
93
|
+
"trainline",
|
|
94
|
+
"omio",
|
|
95
|
+
"raileurope",
|
|
96
|
+
"amtrak",
|
|
97
|
+
"viarail",
|
|
98
|
+
"rentalcars",
|
|
99
|
+
"hertz",
|
|
100
|
+
"avis",
|
|
101
|
+
"europcar",
|
|
102
|
+
"sixt",
|
|
103
|
+
"uber.com",
|
|
104
|
+
"lyft.com",
|
|
105
|
+
"lime",
|
|
106
|
+
"tripit",
|
|
107
|
+
"kiwi.com",
|
|
108
|
+
"tap.pt",
|
|
109
|
+
];
|
|
110
|
+
// Confirmation-number-shaped tokens. Two flavors:
|
|
111
|
+
// - alphanumeric mix at least 6 long: PNRs, hotel confirmation codes
|
|
112
|
+
// - long pure-digit runs (>= 8): airline ref numbers, OTA references
|
|
113
|
+
const ALPHANUM_CONF_RE = /\b(?=[A-Z0-9]*[A-Z])(?=[A-Z0-9]*\d)[A-Z0-9]{6,12}\b/g;
|
|
114
|
+
const LONG_DIGIT_RE = /\b\d{8,}\b/g;
|
|
115
|
+
// Currency amounts. Symbol or ISO 3-letter currency immediately followed by
|
|
116
|
+
// (or following) a number. Catches "$420", "€189.50", "CHF 320", "USD 199".
|
|
117
|
+
const CURRENCY_RE = /(?:[$£€¥₣]\s?\d{1,3}(?:[,.\s]\d{3})*(?:[.,]\d{2})?|\b(?:USD|EUR|GBP|CHF|JPY|CAD|AUD|SEK|NOK|DKK)\s?\d{1,3}(?:[,.\s]\d{3})*(?:[.,]\d{2})?)/g;
|
|
118
|
+
// ISO-8601 dates and "January 2[, 2026]" / "2 January 2026" forms.
|
|
119
|
+
const ISO_DATE_RE = /\b\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])\b/g;
|
|
120
|
+
const MONTH_DAY_RE = /\b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\s+\d{1,2}(?:,?\s+\d{4})?\b/gi;
|
|
121
|
+
const DAY_MONTH_RE = /\b\d{1,2}\s+(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)(?:,?\s+\d{4})?\b/gi;
|
|
122
|
+
// Status tokens — decisive truth signals. These often determine whether a
|
|
123
|
+
// "this trip is on" reading is correct. Lowercased substring match.
|
|
124
|
+
const STATUS_TOKENS = [
|
|
125
|
+
"confirmed",
|
|
126
|
+
"booked",
|
|
127
|
+
"cancelled",
|
|
128
|
+
"canceled",
|
|
129
|
+
"changed",
|
|
130
|
+
"rescheduled",
|
|
131
|
+
"refunded",
|
|
132
|
+
"refund",
|
|
133
|
+
"pending",
|
|
134
|
+
"tentative",
|
|
135
|
+
"waitlist",
|
|
136
|
+
"no longer",
|
|
137
|
+
"rebooked",
|
|
138
|
+
"modified",
|
|
139
|
+
];
|
|
140
|
+
function uniqueLowercaseHits(text, regex) {
|
|
141
|
+
const hits = text.match(regex);
|
|
142
|
+
if (!hits)
|
|
143
|
+
return [];
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
const out = [];
|
|
146
|
+
for (const hit of hits) {
|
|
147
|
+
const norm = hit.toLowerCase();
|
|
148
|
+
if (seen.has(norm))
|
|
149
|
+
continue;
|
|
150
|
+
seen.add(norm);
|
|
151
|
+
out.push(hit);
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
function findKnownTravelSender(fromList) {
|
|
156
|
+
for (const from of fromList) {
|
|
157
|
+
const lower = from.toLowerCase();
|
|
158
|
+
for (const pattern of KNOWN_TRAVEL_SENDER_PATTERNS) {
|
|
159
|
+
if (lower.includes(pattern))
|
|
160
|
+
return pattern;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
function countOccurrences(haystack, needle) {
|
|
166
|
+
/* v8 ignore start -- defensive: BOOKING_INTENT_TOKENS are non-empty by construction */
|
|
167
|
+
if (!needle)
|
|
168
|
+
return 0;
|
|
169
|
+
/* v8 ignore stop */
|
|
170
|
+
let count = 0;
|
|
171
|
+
let from = 0;
|
|
172
|
+
while (true) {
|
|
173
|
+
const idx = haystack.indexOf(needle, from);
|
|
174
|
+
if (idx === -1)
|
|
175
|
+
break;
|
|
176
|
+
count++;
|
|
177
|
+
from = idx + needle.length;
|
|
178
|
+
}
|
|
179
|
+
return count;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Score a cached search document for booking-intent relevance against a list
|
|
183
|
+
* of (already lowercased, non-empty) query terms.
|
|
184
|
+
*
|
|
185
|
+
* Signals (additive, all small integers so the result stays interpretable):
|
|
186
|
+
* - +6 per query term hit in subject
|
|
187
|
+
* - +4 per query term hit in any from address
|
|
188
|
+
* - +2 per query term hit in body
|
|
189
|
+
* - +5 per booking-intent token in subject (extra +3 for decisive subject tokens)
|
|
190
|
+
* - +2 per booking-intent token in body
|
|
191
|
+
* - +4 if any confirmation-number-shaped token appears
|
|
192
|
+
* - +3 if any currency amount appears
|
|
193
|
+
* - +6 if any from address matches a known travel-sender pattern
|
|
194
|
+
*
|
|
195
|
+
* The numbers are tunable. They are chosen so that a noisy newsletter that
|
|
196
|
+
* mentions the query terms in body but has no booking signals scores below
|
|
197
|
+
* a decisive booking confirmation that has the query terms + booking tokens
|
|
198
|
+
* in subject + a confirmation code, even if the booking confirmation is older.
|
|
199
|
+
*/
|
|
200
|
+
function scoreMailSearchDocument(document, queryTerms) {
|
|
201
|
+
const subjectLower = document.subject.toLowerCase();
|
|
202
|
+
const bodyLower = document.textExcerpt.toLowerCase();
|
|
203
|
+
const fromLower = document.from.join(" ").toLowerCase();
|
|
204
|
+
let score = 0;
|
|
205
|
+
const matchedFields = new Set();
|
|
206
|
+
for (const term of queryTerms) {
|
|
207
|
+
/* v8 ignore start -- defensive: callers always normalize/filter empty terms */
|
|
208
|
+
if (!term)
|
|
209
|
+
continue;
|
|
210
|
+
/* v8 ignore stop */
|
|
211
|
+
if (subjectLower.includes(term)) {
|
|
212
|
+
score += 6;
|
|
213
|
+
matchedFields.add("subject");
|
|
214
|
+
}
|
|
215
|
+
if (fromLower.includes(term)) {
|
|
216
|
+
score += 4;
|
|
217
|
+
matchedFields.add("from");
|
|
218
|
+
}
|
|
219
|
+
if (bodyLower.includes(term)) {
|
|
220
|
+
score += 2;
|
|
221
|
+
matchedFields.add("body");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const bookingHits = new Set();
|
|
225
|
+
for (const token of BOOKING_INTENT_TOKENS) {
|
|
226
|
+
const inSubject = countOccurrences(subjectLower, token);
|
|
227
|
+
const inBody = countOccurrences(bodyLower, token);
|
|
228
|
+
if (inSubject > 0) {
|
|
229
|
+
score += 5 * inSubject;
|
|
230
|
+
bookingHits.add(token);
|
|
231
|
+
}
|
|
232
|
+
if (inBody > 0) {
|
|
233
|
+
score += 2 * inBody;
|
|
234
|
+
bookingHits.add(token);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
for (const decisive of SUBJECT_DECISIVE_TOKENS) {
|
|
238
|
+
if (subjectLower.includes(decisive))
|
|
239
|
+
score += 3;
|
|
240
|
+
}
|
|
241
|
+
const subjectAndBody = `${document.subject}\n${document.textExcerpt}`;
|
|
242
|
+
const subjectAndBodyLower = subjectAndBody.toLowerCase();
|
|
243
|
+
const confirmationTokens = [
|
|
244
|
+
...uniqueLowercaseHits(subjectAndBody, ALPHANUM_CONF_RE),
|
|
245
|
+
...uniqueLowercaseHits(subjectAndBody, LONG_DIGIT_RE),
|
|
246
|
+
];
|
|
247
|
+
if (confirmationTokens.length > 0)
|
|
248
|
+
score += 4;
|
|
249
|
+
const currencyTokens = uniqueLowercaseHits(subjectAndBody, CURRENCY_RE);
|
|
250
|
+
if (currencyTokens.length > 0)
|
|
251
|
+
score += 3;
|
|
252
|
+
const dateTokens = [
|
|
253
|
+
...uniqueLowercaseHits(subjectAndBody, ISO_DATE_RE),
|
|
254
|
+
...uniqueLowercaseHits(subjectAndBody, MONTH_DAY_RE),
|
|
255
|
+
...uniqueLowercaseHits(subjectAndBody, DAY_MONTH_RE),
|
|
256
|
+
];
|
|
257
|
+
const statusTokens = [];
|
|
258
|
+
for (const token of STATUS_TOKENS) {
|
|
259
|
+
if (subjectAndBodyLower.includes(token))
|
|
260
|
+
statusTokens.push(token);
|
|
261
|
+
}
|
|
262
|
+
const travelSenderHint = findKnownTravelSender(document.from);
|
|
263
|
+
if (travelSenderHint)
|
|
264
|
+
score += 6;
|
|
265
|
+
const signal = {
|
|
266
|
+
score,
|
|
267
|
+
matchedFields: Array.from(matchedFields),
|
|
268
|
+
bookingTokens: Array.from(bookingHits),
|
|
269
|
+
confirmationTokens,
|
|
270
|
+
currencyTokens,
|
|
271
|
+
statusTokens,
|
|
272
|
+
dateTokens,
|
|
273
|
+
};
|
|
274
|
+
if (travelSenderHint)
|
|
275
|
+
signal.travelSenderHint = travelSenderHint;
|
|
276
|
+
return signal;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Sort comparator: booking relevance first, recency as tiebreaker.
|
|
280
|
+
* Returns negative when `a` should come before `b`.
|
|
281
|
+
*/
|
|
282
|
+
function compareByRelevanceThenRecency(a, b) {
|
|
283
|
+
if (b.relevance.score !== a.relevance.score)
|
|
284
|
+
return b.relevance.score - a.relevance.score;
|
|
285
|
+
return b.document.receivedAt.localeCompare(a.document.receivedAt);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Render a short "matched on" hint for surfacing under a search result.
|
|
289
|
+
* Empty string when nothing notable to report (no signals, no fields). The
|
|
290
|
+
* caller decides whether to display the line.
|
|
291
|
+
*/
|
|
292
|
+
function formatRelevanceHint(signal) {
|
|
293
|
+
if (signal.score === 0)
|
|
294
|
+
return "";
|
|
295
|
+
const parts = [];
|
|
296
|
+
if (signal.matchedFields.length > 0) {
|
|
297
|
+
parts.push(`fields: ${signal.matchedFields.join("+")}`);
|
|
298
|
+
}
|
|
299
|
+
if (signal.bookingTokens.length > 0) {
|
|
300
|
+
const preview = signal.bookingTokens.slice(0, 3).join(", ");
|
|
301
|
+
parts.push(`booking signals: ${preview}`);
|
|
302
|
+
}
|
|
303
|
+
if (signal.statusTokens.length > 0) {
|
|
304
|
+
parts.push(`status: ${signal.statusTokens.slice(0, 3).join(", ")}`);
|
|
305
|
+
}
|
|
306
|
+
if (signal.confirmationTokens.length > 0) {
|
|
307
|
+
parts.push(`conf token: ${signal.confirmationTokens[0]}`);
|
|
308
|
+
}
|
|
309
|
+
if (signal.currencyTokens.length > 0) {
|
|
310
|
+
parts.push(`amount: ${signal.currencyTokens[0]}`);
|
|
311
|
+
}
|
|
312
|
+
if (signal.dateTokens.length > 0) {
|
|
313
|
+
parts.push(`dates: ${signal.dateTokens.slice(0, 3).join(", ")}`);
|
|
314
|
+
}
|
|
315
|
+
if (signal.travelSenderHint) {
|
|
316
|
+
parts.push(`sender: ${signal.travelSenderHint}`);
|
|
317
|
+
}
|
|
318
|
+
return parts.join(" | ");
|
|
319
|
+
}
|
package/dist/mind/context.js
CHANGED
|
@@ -315,11 +315,43 @@ function postTurnPersist(sessPath, prepared, usage, state) {
|
|
|
315
315
|
return envelope.events;
|
|
316
316
|
}
|
|
317
317
|
/**
|
|
318
|
-
*
|
|
319
|
-
*
|
|
318
|
+
* Per-sessPath serialization queue. Without this, two concurrent
|
|
319
|
+
* `deferPostTurnPersist` calls (e.g. two BlueBubbles webhooks for the same
|
|
320
|
+
* chat firing back-to-back, or a CLI postTurn racing the inner-dialog turn
|
|
321
|
+
* for the same MCP session) would each load the envelope, both compute the
|
|
322
|
+
* same "next sequence", and write events with colliding ids. The session
|
|
323
|
+
* file would silently accumulate duplicates and replay would diverge from
|
|
324
|
+
* what was actually sent on the wire.
|
|
325
|
+
*
|
|
326
|
+
* The queue is in-process only — it does not protect against multiple Node
|
|
327
|
+
* processes writing to the same file. The dedup-on-load behaviour in
|
|
328
|
+
* parseSessionEnvelope keeps cross-process races from leaving permanent
|
|
329
|
+
* corruption; this serializer just keeps the common (single-process) case
|
|
330
|
+
* race-free in the first place.
|
|
331
|
+
*/
|
|
332
|
+
const sessionPersistQueues = new Map();
|
|
333
|
+
function enqueueSessionPersist(sessPath, fn) {
|
|
334
|
+
const previous = sessionPersistQueues.get(sessPath) ?? Promise.resolve();
|
|
335
|
+
// Chain on the previous tail. fn runs whether previous resolved or rejected,
|
|
336
|
+
// so one failed turn cannot block subsequent turns on the same session.
|
|
337
|
+
const next = previous.then(fn, fn);
|
|
338
|
+
// Save a swallowed-rejection sentinel as the new tail so the next caller's
|
|
339
|
+
// `previous.then(fn, fn)` sees a clean resolution; the original `next` still
|
|
340
|
+
// propagates rejection to its own caller as expected.
|
|
341
|
+
/* v8 ignore start -- the swallow only matters when fn rejects, which is the failure path covered separately */
|
|
342
|
+
const sentinel = next.then(() => undefined, () => undefined);
|
|
343
|
+
/* v8 ignore stop */
|
|
344
|
+
sessionPersistQueues.set(sessPath, sentinel);
|
|
345
|
+
return next;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Deferred persist: same as postTurnPersist but runs on the next event loop
|
|
349
|
+
* tick AND serializes against any other deferred persist for the same
|
|
350
|
+
* sessPath, so concurrent turns cannot race and produce duplicate event ids
|
|
351
|
+
* in the saved session.
|
|
320
352
|
*/
|
|
321
353
|
function deferPostTurnPersist(sessPath, prepared, usage, state) {
|
|
322
|
-
return new Promise((resolve) => {
|
|
354
|
+
return enqueueSessionPersist(sessPath, () => new Promise((resolve) => {
|
|
323
355
|
setImmediate(() => {
|
|
324
356
|
try {
|
|
325
357
|
const events = postTurnPersist(sessPath, prepared, usage, state);
|
|
@@ -336,7 +368,7 @@ function deferPostTurnPersist(sessPath, prepared, usage, state) {
|
|
|
336
368
|
resolve([]);
|
|
337
369
|
}
|
|
338
370
|
});
|
|
339
|
-
});
|
|
371
|
+
}));
|
|
340
372
|
}
|
|
341
373
|
function deleteSession(filePath) {
|
|
342
374
|
try {
|
|
@@ -87,6 +87,21 @@ class FriendResolver {
|
|
|
87
87
|
hasAnyFriends = false;
|
|
88
88
|
}
|
|
89
89
|
const isFirstImprint = !hasAnyFriends;
|
|
90
|
+
// BlueBubbles group chats route through here as `imessage-handle` with an
|
|
91
|
+
// externalId of the form `group:any;+;<chatHash>`. When the harness auto-
|
|
92
|
+
// creates the group friend at stranger trust, we mark the record so that
|
|
93
|
+
// the trust gate can surface the relationship for explicit acknowledgment
|
|
94
|
+
// later instead of letting messages accumulate silently.
|
|
95
|
+
const isImessageGroup = this.params.provider === "imessage-handle" &&
|
|
96
|
+
typeof this.params.externalId === "string" &&
|
|
97
|
+
this.params.externalId.startsWith("group:");
|
|
98
|
+
const notes = {};
|
|
99
|
+
if (this.params.displayName !== "Unknown") {
|
|
100
|
+
notes.name = { value: this.params.displayName, savedAt: now };
|
|
101
|
+
}
|
|
102
|
+
if (isImessageGroup && !isFirstImprint) {
|
|
103
|
+
notes.autoCreatedGroup = { value: "true", savedAt: now };
|
|
104
|
+
}
|
|
90
105
|
const friend = {
|
|
91
106
|
id: (0, crypto_1.randomUUID)(),
|
|
92
107
|
name: this.params.displayName,
|
|
@@ -96,7 +111,7 @@ class FriendResolver {
|
|
|
96
111
|
externalIds: [externalId],
|
|
97
112
|
tenantMemberships,
|
|
98
113
|
toolPreferences: {},
|
|
99
|
-
notes
|
|
114
|
+
notes,
|
|
100
115
|
totalTokens: 0,
|
|
101
116
|
createdAt: now,
|
|
102
117
|
updatedAt: now,
|
|
@@ -97,6 +97,10 @@ const DISPATCH_EXEMPT_PATTERNS = [
|
|
|
97
97
|
// consumed by server readers and the UI. Outlook read/render modules own
|
|
98
98
|
// the observability for these projections.
|
|
99
99
|
"heart/outlook/outlook-types",
|
|
100
|
+
// Mail search relevance scorer: pure heuristic function (regex + counter
|
|
101
|
+
// arithmetic). The caller (search-cache.ts searchMailSearchCache) owns
|
|
102
|
+
// observability via senses.mail_search_cache_upserted and friends.
|
|
103
|
+
"mailroom/search-relevance",
|
|
100
104
|
// Outlook HTTP helper modules: route/static/transport/hook seams are
|
|
101
105
|
// dispatched by outlook-http.ts, whose server lifecycle owns observability.
|
|
102
106
|
"heart/outlook/outlook-http-transport",
|