@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.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SettleStreamer = exports.SettleParser = void 0;
3
+ exports.RESPONSES_FUNCTION_CALL_OUTPUT_CAP = exports.SettleStreamer = exports.SettleParser = void 0;
4
+ exports.truncateResponsesFunctionCallOutput = truncateResponsesFunctionCallOutput;
4
5
  exports.toResponsesInput = toResponsesInput;
5
6
  exports.toResponsesTools = toResponsesTools;
6
7
  exports.streamChatCompletion = streamChatCompletion;
@@ -112,6 +113,16 @@ class SettleStreamer {
112
113
  }
113
114
  }
114
115
  exports.SettleStreamer = SettleStreamer;
116
+ exports.RESPONSES_FUNCTION_CALL_OUTPUT_CAP = 200_000;
117
+ function truncateResponsesFunctionCallOutput(output, maxChars = exports.RESPONSES_FUNCTION_CALL_OUTPUT_CAP) {
118
+ if (output.length <= maxChars)
119
+ return output;
120
+ const marker = `\n\n[truncated — function_call_output exceeded ${maxChars} chars; original length ${output.length} chars]\n\n`;
121
+ const remainingBudget = Math.max(0, maxChars - marker.length);
122
+ const headLength = Math.ceil(remainingBudget * 0.75);
123
+ const tailLength = Math.max(0, remainingBudget - headLength);
124
+ return `${output.slice(0, headLength)}${marker}${output.slice(-tailLength)}`;
125
+ }
115
126
  function toResponsesUserContent(content) {
116
127
  if (typeof content === "string") {
117
128
  return content;
@@ -217,7 +228,7 @@ function toResponsesInput(messages) {
217
228
  input.push({
218
229
  type: "function_call_output",
219
230
  call_id: t.tool_call_id,
220
- output: typeof t.content === "string" ? t.content : "",
231
+ output: truncateResponsesFunctionCallOutput(typeof t.content === "string" ? t.content : ""),
221
232
  });
222
233
  continue;
223
234
  }
@@ -4,6 +4,7 @@ exports.AzureBlobMailroomStore = void 0;
4
4
  exports.decryptBlobMessages = decryptBlobMessages;
5
5
  const runtime_1 = require("../nerves/runtime");
6
6
  const core_1 = require("./core");
7
+ const search_cache_1 = require("./search-cache");
7
8
  const MESSAGE_INDEX_PREFIX = "message-index";
8
9
  const MESSAGE_INDEX_SORT_MAX_MS = 9_999_999_999_999;
9
10
  const MESSAGE_INDEX_SORT_WIDTH = 13;
@@ -22,6 +23,9 @@ function compareCandidatesNewestFirst(left, right) {
22
23
  function blobText(value) {
23
24
  return Buffer.from(`${JSON.stringify(value, null, 2)}\n`, "utf-8");
24
25
  }
26
+ function applyOptionalLimit(items, limit) {
27
+ return typeof limit === "number" ? items.slice(0, limit) : items;
28
+ }
25
29
  function positiveInteger(value, fallback) {
26
30
  if (typeof value !== "number" || !Number.isFinite(value))
27
31
  return fallback;
@@ -248,7 +252,6 @@ class AzureBlobMailroomStore {
248
252
  messageBlobNames.push(item.name);
249
253
  }
250
254
  const matches = [];
251
- const limit = filters.limit ?? 20;
252
255
  let nextIndex = 0;
253
256
  const worker = async () => {
254
257
  while (nextIndex < messageBlobNames.length) {
@@ -259,12 +262,12 @@ class AzureBlobMailroomStore {
259
262
  continue;
260
263
  matches.push(message);
261
264
  matches.sort(compareNewestFirst);
262
- if (matches.length > limit)
263
- matches.length = limit;
265
+ if (typeof filters.limit === "number" && matches.length > filters.limit)
266
+ matches.length = filters.limit;
264
267
  }
265
268
  };
266
269
  await Promise.all(Array.from({ length: Math.min(MESSAGE_LIST_SCAN_CONCURRENCY, Math.max(messageBlobNames.length, 1)) }, async () => worker()));
267
- return matches.sort(compareNewestFirst).slice(0, limit);
270
+ return applyOptionalLimit(matches.sort(compareNewestFirst), filters.limit);
268
271
  }
269
272
  async listMessagesFromIndexes(filters) {
270
273
  const messageIds = [];
@@ -275,18 +278,18 @@ class AzureBlobMailroomStore {
275
278
  if (!parsed || !messageMatchesFilters(parsed, filters))
276
279
  continue;
277
280
  messageIds.push(parsed.id);
278
- if (messageIds.length >= (filters.limit ?? 20))
281
+ if (typeof filters.limit === "number" && messageIds.length >= filters.limit)
279
282
  break;
280
283
  }
281
284
  if (!sawIndex)
282
285
  return null;
283
- return (await mapWithConcurrency(messageIds, this.messageFetchConcurrency, async (id) => {
286
+ const messages = (await mapWithConcurrency(messageIds, this.messageFetchConcurrency, async (id) => {
284
287
  return downloadJson(this.messageBlob(id), this.blobOperationTimeoutMs);
285
288
  }))
286
289
  .filter((message) => message !== null)
287
290
  .filter((message) => messageMatchesFilters(message, filters))
288
- .sort(compareNewestFirst)
289
- .slice(0, filters.limit ?? 20);
291
+ .sort(compareNewestFirst);
292
+ return applyOptionalLimit(messages, filters.limit);
290
293
  }
291
294
  async backfillMessageIndexes(agentId, onProgress) {
292
295
  await this.ensureContainer();
@@ -335,7 +338,7 @@ class AzureBlobMailroomStore {
335
338
  }
336
339
  async putRawMessage(input) {
337
340
  await this.ensureContainer();
338
- const { message, rawPayload, candidate } = await (0, core_1.buildStoredMailMessage)(input);
341
+ const { message, rawPayload, privateEnvelope, candidate } = await (0, core_1.buildStoredMailMessage)(input);
339
342
  const messageBlob = this.messageBlob(message.id);
340
343
  let existing = null;
341
344
  try {
@@ -359,6 +362,7 @@ class AzureBlobMailroomStore {
359
362
  throw error;
360
363
  }
361
364
  if (existing) {
365
+ (0, search_cache_1.upsertMailSearchCacheDocument)(existing, privateEnvelope);
362
366
  await this.putMessageIndex(existing);
363
367
  (0, runtime_1.emitNervesEvent)({
364
368
  component: "senses",
@@ -371,6 +375,7 @@ class AzureBlobMailroomStore {
371
375
  await this.rawBlob(message.rawObject).uploadData(blobText(rawPayload));
372
376
  await this.messageBlob(message.id).uploadData(blobText(message));
373
377
  await this.putMessageIndex(message);
378
+ (0, search_cache_1.upsertMailSearchCacheDocument)(message, privateEnvelope);
374
379
  if (candidate) {
375
380
  await this.candidateBlob(candidate.id).uploadData(blobText(candidate));
376
381
  }
@@ -426,6 +431,7 @@ class AzureBlobMailroomStore {
426
431
  await blob.uploadData(blobText(updated));
427
432
  await this.removeMessageIndex(message);
428
433
  await this.putMessageIndex(updated);
434
+ (0, search_cache_1.syncMailSearchCacheMetadata)(updated);
429
435
  (0, runtime_1.emitNervesEvent)({
430
436
  component: "senses",
431
437
  event: "senses.mail_blob_store_message_placement_updated",
@@ -578,7 +584,7 @@ class AzureBlobMailroomStore {
578
584
  async listAccessLog(agentId) {
579
585
  await this.ensureContainer();
580
586
  const entries = await downloadJson(this.accessLogBlob(agentId), this.blobOperationTimeoutMs);
581
- const safeEntries = Array.isArray(entries) ? entries : [];
587
+ const safeEntries = (Array.isArray(entries) ? entries : []);
582
588
  (0, runtime_1.emitNervesEvent)({
583
589
  component: "senses",
584
590
  event: "senses.mail_blob_access_log_listed",
@@ -477,7 +477,7 @@ async function buildStoredMailMessage(input) {
477
477
  message: "stored mail message envelope built",
478
478
  meta: { id, agentId: message.agentId, placement, compartmentKind: message.compartmentKind, candidate: candidate !== undefined },
479
479
  });
480
- return { message, rawPayload, ...(candidate ? { candidate } : {}) };
480
+ return { message, rawPayload, privateEnvelope, ...(candidate ? { candidate } : {}) };
481
481
  }
482
482
  function decryptStoredMailMessage(message, privateKeys) {
483
483
  const privateKey = privateKeys[message.privateEnvelope.keyId];
@@ -40,9 +40,13 @@ const fs = __importStar(require("node:fs"));
40
40
  const path = __importStar(require("node:path"));
41
41
  const runtime_1 = require("../nerves/runtime");
42
42
  const core_1 = require("./core");
43
+ const search_cache_1 = require("./search-cache");
43
44
  function ensureDir(dir) {
44
45
  fs.mkdirSync(dir, { recursive: true });
45
46
  }
47
+ function applyOptionalLimit(items, limit) {
48
+ return typeof limit === "number" ? items.slice(0, limit) : items;
49
+ }
46
50
  function readJson(filePath) {
47
51
  try {
48
52
  return JSON.parse(fs.readFileSync(filePath, "utf-8"));
@@ -122,9 +126,10 @@ class FileMailroomStore {
122
126
  return path.join(this.logsDir, `${agentId}.jsonl`);
123
127
  }
124
128
  async putRawMessage(input) {
125
- const { message, rawPayload, candidate } = await (0, core_1.buildStoredMailMessage)(input);
129
+ const { message, rawPayload, privateEnvelope, candidate } = await (0, core_1.buildStoredMailMessage)(input);
126
130
  const existing = readJson(this.messagePath(message.id));
127
131
  if (existing) {
132
+ (0, search_cache_1.upsertMailSearchCacheDocument)(existing, privateEnvelope);
128
133
  (0, runtime_1.emitNervesEvent)({
129
134
  component: "senses",
130
135
  event: "senses.mail_store_dedupe",
@@ -135,6 +140,7 @@ class FileMailroomStore {
135
140
  }
136
141
  writeJson(this.rawPath(message.rawObject), rawPayload);
137
142
  writeJson(this.messagePath(message.id), message);
143
+ (0, search_cache_1.upsertMailSearchCacheDocument)(message, privateEnvelope);
138
144
  if (candidate) {
139
145
  writeJson(this.candidatePath(candidate.id), candidate);
140
146
  }
@@ -165,15 +171,15 @@ class FileMailroomStore {
165
171
  .filter((message) => filters.placement ? message.placement === filters.placement : true)
166
172
  .filter((message) => filters.compartmentKind ? message.compartmentKind === filters.compartmentKind : true)
167
173
  .filter((message) => sourceMatchesFilter(message.source, filters.source))
168
- .sort(compareNewestFirst)
169
- .slice(0, filters.limit ?? 20);
174
+ .sort(compareNewestFirst);
175
+ const limited = applyOptionalLimit(messages, filters.limit);
170
176
  (0, runtime_1.emitNervesEvent)({
171
177
  component: "senses",
172
178
  event: "senses.mail_store_messages_listed",
173
179
  message: "mailroom store listed messages",
174
- meta: { agentId: filters.agentId, count: messages.length },
180
+ meta: { agentId: filters.agentId, count: limited.length },
175
181
  });
176
- return messages;
182
+ return limited;
177
183
  }
178
184
  async updateMessagePlacement(id, placement) {
179
185
  const message = readJson(this.messagePath(id));
@@ -188,6 +194,7 @@ class FileMailroomStore {
188
194
  }
189
195
  const updated = { ...message, placement };
190
196
  writeJson(this.messagePath(id), updated);
197
+ (0, search_cache_1.syncMailSearchCacheMetadata)(updated);
191
198
  (0, runtime_1.emitNervesEvent)({
192
199
  component: "senses",
193
200
  event: "senses.mail_store_message_placement_updated",
@@ -339,15 +346,34 @@ class FileMailroomStore {
339
346
  });
340
347
  return [];
341
348
  }
342
- const entries = fs.readFileSync(filePath, "utf-8")
349
+ const lines = fs.readFileSync(filePath, "utf-8")
343
350
  .split(/\r?\n/)
344
- .filter(Boolean)
345
- .map((line) => JSON.parse(line));
351
+ .filter(Boolean);
352
+ const entries = [];
353
+ let malformedEntriesSkipped = 0;
354
+ for (const line of lines) {
355
+ try {
356
+ entries.push(JSON.parse(line));
357
+ }
358
+ catch {
359
+ malformedEntriesSkipped += 1;
360
+ }
361
+ }
362
+ if (malformedEntriesSkipped > 0) {
363
+ entries.malformedEntriesSkipped = malformedEntriesSkipped;
364
+ (0, runtime_1.emitNervesEvent)({
365
+ level: "warn",
366
+ component: "senses",
367
+ event: "senses.mail_access_log_malformed_lines_skipped",
368
+ message: "skipped malformed file-backed mail access log lines",
369
+ meta: { agentId, malformedEntriesSkipped },
370
+ });
371
+ }
346
372
  (0, runtime_1.emitNervesEvent)({
347
373
  component: "senses",
348
374
  event: "senses.mail_access_log_listed",
349
375
  message: "mail access log listed",
350
- meta: { agentId, count: entries.length },
376
+ meta: { agentId, count: entries.length, malformedEntriesSkipped },
351
377
  });
352
378
  return entries;
353
379
  }
@@ -36,9 +36,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.splitMboxMessages = splitMboxMessages;
37
37
  exports.importMboxToStore = importMboxToStore;
38
38
  exports.importMboxFileToStore = importMboxFileToStore;
39
+ exports.cacheMatchingMailSearchDocumentsFromMboxFile = cacheMatchingMailSearchDocumentsFromMboxFile;
39
40
  const fs = __importStar(require("node:fs"));
40
41
  const runtime_1 = require("../nerves/runtime");
41
42
  const core_1 = require("./core");
43
+ const search_cache_1 = require("./search-cache");
44
+ const search_relevance_1 = require("./search-relevance");
42
45
  function splitMboxMessages(rawMbox) {
43
46
  const text = rawMbox.toString("utf-8");
44
47
  const separators = [...text.matchAll(/^From [^\r\n]*(?:\r?\n)/gm)];
@@ -208,6 +211,11 @@ function extractHeaderDate(rawMessage, headerName) {
208
211
  const parsed = new Date(value);
209
212
  return Number.isFinite(parsed.getTime()) ? parsed : undefined;
210
213
  }
214
+ function normalizeSearchTerms(queryTerms) {
215
+ return queryTerms
216
+ .map((term) => term.trim().toLowerCase())
217
+ .filter((term) => term.length > 0);
218
+ }
211
219
  function historicalImportClassification(resolvedPlacement, sourceGrant) {
212
220
  return {
213
221
  placement: resolvedPlacement,
@@ -339,3 +347,36 @@ async function importMboxFileToStore(input) {
339
347
  onProgress: input.onProgress,
340
348
  });
341
349
  }
350
+ async function cacheMatchingMailSearchDocumentsFromMboxFile(input) {
351
+ const target = resolveImportTarget(input);
352
+ const queryTerms = normalizeSearchTerms(input.queryTerms);
353
+ if (queryTerms.length === 0 || input.limit <= 0)
354
+ return [];
355
+ const matches = [];
356
+ for await (const rawMessage of streamMboxMessagesFromFile(input.filePath)) {
357
+ const parsedMessage = parseMboxMessage(rawMessage, target.sourceGrant);
358
+ const { message, privateEnvelope } = await (0, core_1.buildStoredMailMessage)({
359
+ resolved: target.resolved,
360
+ envelope: parsedMessage.envelope,
361
+ rawMime: parsedMessage.rawMessage,
362
+ receivedAt: parsedMessage.messageDate,
363
+ ingest: {
364
+ schemaVersion: 1,
365
+ kind: "mbox-import",
366
+ attentionSuppressed: true,
367
+ },
368
+ classification: historicalImportClassification(target.resolved.defaultPlacement, target.sourceGrant),
369
+ });
370
+ const document = (0, search_cache_1.buildMailSearchCacheDocument)(message, privateEnvelope);
371
+ if (!queryTerms.some((term) => document.searchText.includes(term)))
372
+ continue;
373
+ (0, search_cache_1.upsertMailSearchCacheDocument)(message, privateEnvelope);
374
+ matches.push(document);
375
+ if (matches.length >= input.limit)
376
+ break;
377
+ }
378
+ return matches
379
+ .map((document) => ({ document, relevance: (0, search_relevance_1.scoreMailSearchDocument)(document, queryTerms) }))
380
+ .sort(search_relevance_1.compareByRelevanceThenRecency)
381
+ .map((entry) => entry.document);
382
+ }
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.parseMailroomConfig = parseMailroomConfig;
37
37
  exports.readMailroomRegistry = readMailroomRegistry;
38
+ exports.writeMailroomRegistry = writeMailroomRegistry;
38
39
  exports.resolveMailroomReader = resolveMailroomReader;
39
40
  const fs = __importStar(require("node:fs"));
40
41
  const path = __importStar(require("node:path"));
@@ -145,6 +146,27 @@ async function readMailroomRegistry(config) {
145
146
  }
146
147
  return JSON.parse((await blobClient.downloadToBuffer()).toString("utf-8"));
147
148
  }
149
+ async function writeMailroomRegistry(config, registry) {
150
+ const serialized = `${JSON.stringify(registry, null, 2)}\n`;
151
+ if (config.registryPath) {
152
+ fs.mkdirSync(path.dirname(config.registryPath), { recursive: true });
153
+ fs.writeFileSync(config.registryPath, serialized, "utf-8");
154
+ return;
155
+ }
156
+ const registryAzureAccountUrl = config.registryAzureAccountUrl ?? config.azureAccountUrl;
157
+ const registryContainer = config.registryContainer ?? config.azureContainer ?? "mailroom";
158
+ const registryBlob = config.registryBlob;
159
+ if (!registryAzureAccountUrl || !registryBlob) {
160
+ throw new Error("mailroom config is missing registryPath or hosted registry coordinates");
161
+ }
162
+ const serviceClient = new storage_blob_1.BlobServiceClient(registryAzureAccountUrl, createBlobCredential(config));
163
+ const blobClient = serviceClient.getContainerClient(registryContainer).getBlockBlobClient(registryBlob);
164
+ await blobClient.upload(serialized, Buffer.byteLength(serialized), {
165
+ blobHTTPHeaders: {
166
+ blobContentType: "application/json; charset=utf-8",
167
+ },
168
+ });
169
+ }
148
170
  function resolveMailroomReader(agentName = (0, identity_2.getAgentName)()) {
149
171
  const runtime = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agentName);
150
172
  if (!runtime.ok) {
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildMailSearchCacheDocument = buildMailSearchCacheDocument;
37
+ exports.upsertMailSearchCacheDocument = upsertMailSearchCacheDocument;
38
+ exports.syncMailSearchCacheMetadata = syncMailSearchCacheMetadata;
39
+ exports.searchMailSearchCache = searchMailSearchCache;
40
+ exports.resetMailSearchCacheForTests = resetMailSearchCacheForTests;
41
+ const fs = __importStar(require("node:fs"));
42
+ const path = __importStar(require("node:path"));
43
+ const identity_1 = require("../heart/identity");
44
+ const runtime_1 = require("../nerves/runtime");
45
+ const search_relevance_1 = require("./search-relevance");
46
+ const SEARCH_TEXT_EXCERPT_LIMIT = 16_384;
47
+ const cacheStates = new Map();
48
+ function cacheDir(agentId) {
49
+ return path.join((0, identity_1.getAgentRoot)(agentId), "state", "mail-search");
50
+ }
51
+ function cachePath(agentId, messageId) {
52
+ return path.join(cacheDir(agentId), `${messageId}.json`);
53
+ }
54
+ function normalizeSearchText(privateEnvelope) {
55
+ return [
56
+ privateEnvelope.subject,
57
+ privateEnvelope.snippet,
58
+ privateEnvelope.text.slice(0, SEARCH_TEXT_EXCERPT_LIMIT),
59
+ privateEnvelope.from.join(" "),
60
+ ].join("\n").toLowerCase();
61
+ }
62
+ function readJsonDocument(filePath) {
63
+ try {
64
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ }
70
+ function cacheState(agentId) {
71
+ const key = `${agentId}:${cacheDir(agentId)}`;
72
+ let state = cacheStates.get(key);
73
+ if (state)
74
+ return state;
75
+ state = { loaded: false, docs: new Map() };
76
+ cacheStates.set(key, state);
77
+ return state;
78
+ }
79
+ function loadCache(agentId) {
80
+ const state = cacheState(agentId);
81
+ if (state.loaded)
82
+ return state.docs;
83
+ state.loaded = true;
84
+ const dir = cacheDir(agentId);
85
+ if (!fs.existsSync(dir))
86
+ return state.docs;
87
+ for (const entry of fs.readdirSync(dir)) {
88
+ if (!entry.endsWith(".json"))
89
+ continue;
90
+ const document = readJsonDocument(path.join(dir, entry));
91
+ if (!document || document.agentId !== agentId)
92
+ continue;
93
+ state.docs.set(document.messageId, document);
94
+ }
95
+ return state.docs;
96
+ }
97
+ function buildMailSearchCacheDocument(message, privateEnvelope) {
98
+ return {
99
+ schemaVersion: 1,
100
+ messageId: message.id,
101
+ agentId: message.agentId,
102
+ receivedAt: message.receivedAt,
103
+ placement: message.placement,
104
+ compartmentKind: message.compartmentKind,
105
+ ...(message.ownerEmail ? { ownerEmail: message.ownerEmail } : {}),
106
+ ...(message.source ? { source: message.source } : {}),
107
+ from: [...privateEnvelope.from],
108
+ subject: privateEnvelope.subject,
109
+ snippet: privateEnvelope.snippet,
110
+ textExcerpt: privateEnvelope.text.slice(0, SEARCH_TEXT_EXCERPT_LIMIT),
111
+ untrustedContentWarning: privateEnvelope.untrustedContentWarning,
112
+ searchText: normalizeSearchText(privateEnvelope),
113
+ attachmentCount: privateEnvelope.attachments.length,
114
+ };
115
+ }
116
+ function upsertMailSearchCacheDocument(message, privateEnvelope) {
117
+ const document = buildMailSearchCacheDocument(message, privateEnvelope);
118
+ const dir = cacheDir(message.agentId);
119
+ fs.mkdirSync(dir, { recursive: true });
120
+ fs.writeFileSync(cachePath(message.agentId, message.id), `${JSON.stringify(document)}\n`, "utf-8");
121
+ const docs = loadCache(message.agentId);
122
+ docs.set(document.messageId, document);
123
+ (0, runtime_1.emitNervesEvent)({
124
+ component: "senses",
125
+ event: "senses.mail_search_cache_upserted",
126
+ message: "mail search cache entry written",
127
+ meta: {
128
+ agentId: message.agentId,
129
+ messageId: document.messageId,
130
+ placement: document.placement,
131
+ compartmentKind: document.compartmentKind,
132
+ },
133
+ });
134
+ return document;
135
+ }
136
+ function syncMailSearchCacheMetadata(message) {
137
+ const existing = readJsonDocument(cachePath(message.agentId, message.id));
138
+ if (!existing)
139
+ return;
140
+ const updated = {
141
+ ...existing,
142
+ receivedAt: message.receivedAt,
143
+ placement: message.placement,
144
+ compartmentKind: message.compartmentKind,
145
+ ...(message.ownerEmail ? { ownerEmail: message.ownerEmail } : {}),
146
+ ...(message.source ? { source: message.source } : {}),
147
+ };
148
+ fs.writeFileSync(cachePath(message.agentId, message.id), `${JSON.stringify(updated)}\n`, "utf-8");
149
+ const docs = loadCache(message.agentId);
150
+ docs.set(updated.messageId, updated);
151
+ }
152
+ function sourceMatches(source, filter) {
153
+ if (!filter)
154
+ return true;
155
+ if (!source)
156
+ return false;
157
+ return source.toLowerCase() === filter.toLowerCase();
158
+ }
159
+ function searchMailSearchCache(filters) {
160
+ const queryTerms = filters.queryTerms ?? [];
161
+ const docs = [...loadCache(filters.agentId).values()]
162
+ .filter((document) => filters.placement ? document.placement === filters.placement : true)
163
+ .filter((document) => filters.compartmentKind ? document.compartmentKind === filters.compartmentKind : true)
164
+ .filter((document) => sourceMatches(document.source, filters.source))
165
+ .filter((document) => queryTerms.length
166
+ ? queryTerms.some((term) => document.searchText.includes(term))
167
+ : true);
168
+ let ordered;
169
+ if (queryTerms.length > 0) {
170
+ ordered = docs
171
+ .map((document) => ({ document, relevance: (0, search_relevance_1.scoreMailSearchDocument)(document, queryTerms) }))
172
+ .sort(search_relevance_1.compareByRelevanceThenRecency)
173
+ .map((entry) => entry.document);
174
+ }
175
+ else {
176
+ ordered = docs.sort((left, right) => right.receivedAt.localeCompare(left.receivedAt));
177
+ }
178
+ return typeof filters.limit === "number" ? ordered.slice(0, filters.limit) : ordered;
179
+ }
180
+ function resetMailSearchCacheForTests() {
181
+ cacheStates.clear();
182
+ }