@mastra/memory 1.6.0 → 1.6.1

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/{chunk-5UYAHJVJ.cjs → chunk-D6II7EP4.cjs} +660 -531
  3. package/dist/chunk-D6II7EP4.cjs.map +1 -0
  4. package/dist/{chunk-A62BQK35.js → chunk-GBBQIJQF.js} +660 -531
  5. package/dist/chunk-GBBQIJQF.js.map +1 -0
  6. package/dist/docs/SKILL.md +1 -1
  7. package/dist/docs/assets/SOURCE_MAP.json +25 -25
  8. package/dist/docs/references/docs-agents-agent-approval.md +61 -31
  9. package/dist/docs/references/docs-agents-supervisor-agents.md +1 -1
  10. package/dist/docs/references/docs-memory-observational-memory.md +9 -0
  11. package/dist/docs/references/docs-memory-semantic-recall.md +17 -1
  12. package/dist/docs/references/reference-core-getMemory.md +2 -2
  13. package/dist/docs/references/reference-core-listMemory.md +1 -1
  14. package/dist/docs/references/reference-memory-clone-utilities.md +5 -5
  15. package/dist/docs/references/reference-memory-cloneThread.md +17 -21
  16. package/dist/docs/references/reference-memory-createThread.md +10 -10
  17. package/dist/docs/references/reference-memory-getThreadById.md +2 -2
  18. package/dist/docs/references/reference-memory-listThreads.md +5 -5
  19. package/dist/docs/references/reference-memory-memory-class.md +12 -14
  20. package/dist/docs/references/reference-memory-observational-memory.md +102 -94
  21. package/dist/docs/references/reference-processors-token-limiter-processor.md +11 -13
  22. package/dist/docs/references/reference-storage-dynamodb.md +9 -9
  23. package/dist/docs/references/reference-storage-libsql.md +2 -2
  24. package/dist/docs/references/reference-storage-mongodb.md +5 -5
  25. package/dist/docs/references/reference-storage-postgresql.md +25 -25
  26. package/dist/docs/references/reference-storage-upstash.md +3 -3
  27. package/dist/docs/references/reference-vectors-libsql.md +31 -31
  28. package/dist/docs/references/reference-vectors-mongodb.md +32 -32
  29. package/dist/docs/references/reference-vectors-pg.md +60 -44
  30. package/dist/docs/references/reference-vectors-upstash.md +25 -25
  31. package/dist/index.cjs +246 -57
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.ts +19 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +246 -57
  36. package/dist/index.js.map +1 -1
  37. package/dist/{observational-memory-MXI54VC7.cjs → observational-memory-AHVELJX4.cjs} +17 -17
  38. package/dist/{observational-memory-MXI54VC7.cjs.map → observational-memory-AHVELJX4.cjs.map} +1 -1
  39. package/dist/{observational-memory-SR6G4HN5.js → observational-memory-QFQUF5EY.js} +3 -3
  40. package/dist/{observational-memory-SR6G4HN5.js.map → observational-memory-QFQUF5EY.js.map} +1 -1
  41. package/dist/processors/index.cjs +15 -15
  42. package/dist/processors/index.js +1 -1
  43. package/dist/processors/observational-memory/date-utils.d.ts +35 -0
  44. package/dist/processors/observational-memory/date-utils.d.ts.map +1 -0
  45. package/dist/processors/observational-memory/markers.d.ts +94 -0
  46. package/dist/processors/observational-memory/markers.d.ts.map +1 -0
  47. package/dist/processors/observational-memory/observational-memory.d.ts +0 -76
  48. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  49. package/dist/processors/observational-memory/operation-registry.d.ts +14 -0
  50. package/dist/processors/observational-memory/operation-registry.d.ts.map +1 -0
  51. package/dist/processors/observational-memory/thresholds.d.ts +52 -0
  52. package/dist/processors/observational-memory/thresholds.d.ts.map +1 -0
  53. package/dist/processors/observational-memory/token-counter.d.ts +4 -0
  54. package/dist/processors/observational-memory/token-counter.d.ts.map +1 -1
  55. package/dist/tools/working-memory.d.ts.map +1 -1
  56. package/package.json +7 -7
  57. package/dist/chunk-5UYAHJVJ.cjs.map +0 -1
  58. package/dist/chunk-A62BQK35.js.map +0 -1
@@ -4,9 +4,9 @@ The UpstashVector class provides vector search using [Upstash Vector](https://up
4
4
 
5
5
  ## Constructor Options
6
6
 
7
- **url:** (`string`): Upstash Vector database URL
7
+ **url** (`string`): Upstash Vector database URL
8
8
 
9
- **token:** (`string`): Upstash Vector API token
9
+ **token** (`string`): Upstash Vector API token
10
10
 
11
11
  ## Methods
12
12
 
@@ -14,41 +14,41 @@ The UpstashVector class provides vector search using [Upstash Vector](https://up
14
14
 
15
15
  Note: This method is a no-op for Upstash as indexes are created automatically.
16
16
 
17
- **indexName:** (`string`): Name of the index to create
17
+ **indexName** (`string`): Name of the index to create
18
18
 
19
- **dimension:** (`number`): Vector dimension (must match your embedding model)
19
+ **dimension** (`number`): Vector dimension (must match your embedding model)
20
20
 
21
- **metric?:** (`'cosine' | 'euclidean' | 'dotproduct'`): Distance metric for similarity search (Default: `cosine`)
21
+ **metric** (`'cosine' | 'euclidean' | 'dotproduct'`): Distance metric for similarity search (Default: `cosine`)
22
22
 
23
23
  ### upsert()
24
24
 
25
- **indexName:** (`string`): Name of the index to upsert into
25
+ **indexName** (`string`): Name of the index to upsert into
26
26
 
27
- **vectors:** (`number[][]`): Array of embedding vectors
27
+ **vectors** (`number[][]`): Array of embedding vectors
28
28
 
29
- **sparseVectors?:** (`{ indices: number[], values: number[] }[]`): Array of sparse vectors for hybrid search. Each sparse vector must have matching indices and values arrays.
29
+ **sparseVectors** (`{ indices: number[], values: number[] }[]`): Array of sparse vectors for hybrid search. Each sparse vector must have matching indices and values arrays.
30
30
 
31
- **metadata?:** (`Record<string, any>[]`): Metadata for each vector
31
+ **metadata** (`Record<string, any>[]`): Metadata for each vector
32
32
 
33
- **ids?:** (`string[]`): Optional vector IDs (auto-generated if not provided)
33
+ **ids** (`string[]`): Optional vector IDs (auto-generated if not provided)
34
34
 
35
35
  ### query()
36
36
 
37
- **indexName:** (`string`): Name of the index to query
37
+ **indexName** (`string`): Name of the index to query
38
38
 
39
- **queryVector:** (`number[]`): Query vector to find similar vectors
39
+ **queryVector** (`number[]`): Query vector to find similar vectors
40
40
 
41
- **sparseVector?:** (`{ indices: number[], values: number[] }`): Optional sparse vector for hybrid search. Must have matching indices and values arrays.
41
+ **sparseVector** (`{ indices: number[], values: number[] }`): Optional sparse vector for hybrid search. Must have matching indices and values arrays.
42
42
 
43
- **topK?:** (`number`): Number of results to return (Default: `10`)
43
+ **topK** (`number`): Number of results to return (Default: `10`)
44
44
 
45
- **filter?:** (`Record<string, any>`): Metadata filters for the query
45
+ **filter** (`Record<string, any>`): Metadata filters for the query
46
46
 
47
- **includeVector?:** (`boolean`): Whether to include vectors in the results (Default: `false`)
47
+ **includeVector** (`boolean`): Whether to include vectors in the results (Default: `false`)
48
48
 
49
- **fusionAlgorithm?:** (`FusionAlgorithm`): Algorithm used to combine dense and sparse search results in hybrid search (e.g., RRF - Reciprocal Rank Fusion)
49
+ **fusionAlgorithm** (`FusionAlgorithm`): Algorithm used to combine dense and sparse search results in hybrid search (e.g., RRF - Reciprocal Rank Fusion)
50
50
 
51
- **queryMode?:** (`QueryMode`): Search mode: 'DENSE' for dense-only, 'SPARSE' for sparse-only, or 'HYBRID' for combined search
51
+ **queryMode** (`QueryMode`): Search mode: 'DENSE' for dense-only, 'SPARSE' for sparse-only, or 'HYBRID' for combined search
52
52
 
53
53
  ### listIndexes()
54
54
 
@@ -56,7 +56,7 @@ Returns an array of index names (namespaces) as strings.
56
56
 
57
57
  ### describeIndex()
58
58
 
59
- **indexName:** (`string`): Name of the index to describe
59
+ **indexName** (`string`): Name of the index to describe
60
60
 
61
61
  Returns:
62
62
 
@@ -70,15 +70,15 @@ interface IndexStats {
70
70
 
71
71
  ### deleteIndex()
72
72
 
73
- **indexName:** (`string`): Name of the index (namespace) to delete
73
+ **indexName** (`string`): Name of the index (namespace) to delete
74
74
 
75
75
  ### updateVector()
76
76
 
77
- **indexName:** (`string`): Name of the index to update
77
+ **indexName** (`string`): Name of the index to update
78
78
 
79
- **id:** (`string`): ID of the item to update
79
+ **id** (`string`): ID of the item to update
80
80
 
81
- **update:** (`object`): Update object containing vector, sparse vector, and/or metadata
81
+ **update** (`object`): Update object containing vector, sparse vector, and/or metadata
82
82
 
83
83
  The `update` object can have the following properties:
84
84
 
@@ -88,9 +88,9 @@ The `update` object can have the following properties:
88
88
 
89
89
  ### deleteVector()
90
90
 
91
- **indexName:** (`string`): Name of the index from which to delete the item
91
+ **indexName** (`string`): Name of the index from which to delete the item
92
92
 
93
- **id:** (`string`): ID of the item to delete
93
+ **id** (`string`): ID of the item to delete
94
94
 
95
95
  Attempts to delete an item by its ID from the specified index. Logs an error message if the deletion fails.
96
96
 
package/dist/index.cjs CHANGED
@@ -9587,7 +9587,7 @@ function withUserAgentSuffix2(headers, ...userAgentSuffixParts) {
9587
9587
  );
9588
9588
  return Object.fromEntries(normalizedHeaders.entries());
9589
9589
  }
9590
- var VERSION4 = "4.0.15";
9590
+ var VERSION4 = "4.0.19";
9591
9591
  var getOriginalFetch3 = () => globalThis.fetch;
9592
9592
  var getFromApi2 = async ({
9593
9593
  url,
@@ -9672,8 +9672,8 @@ function loadOptionalSetting2({
9672
9672
  }
9673
9673
  return settingValue;
9674
9674
  }
9675
- var suspectProtoRx2 = /"__proto__"\s*:/;
9676
- var suspectConstructorRx2 = /"constructor"\s*:/;
9675
+ var suspectProtoRx2 = /"(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])"\s*:/;
9676
+ var suspectConstructorRx2 = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
9677
9677
  function _parse2(text4) {
9678
9678
  const obj = JSON.parse(text4);
9679
9679
  if (obj === null || typeof obj !== "object") {
@@ -9693,7 +9693,7 @@ function filter2(obj) {
9693
9693
  if (Object.prototype.hasOwnProperty.call(node, "__proto__")) {
9694
9694
  throw new SyntaxError("Object contains forbidden prototype property");
9695
9695
  }
9696
- if (Object.prototype.hasOwnProperty.call(node, "constructor") && Object.prototype.hasOwnProperty.call(node.constructor, "prototype")) {
9696
+ if (Object.prototype.hasOwnProperty.call(node, "constructor") && node.constructor !== null && typeof node.constructor === "object" && Object.prototype.hasOwnProperty.call(node.constructor, "prototype")) {
9697
9697
  throw new SyntaxError("Object contains forbidden prototype property");
9698
9698
  }
9699
9699
  for (const key in node) {
@@ -12257,16 +12257,14 @@ var GatewayVideoModel = class {
12257
12257
  var _a932;
12258
12258
  const resolvedHeaders = await resolve2(this.config.headers());
12259
12259
  try {
12260
- const {
12261
- responseHeaders,
12262
- value: responseBody
12263
- } = await postJsonToApi2({
12260
+ const { responseHeaders, value: responseBody } = await postJsonToApi2({
12264
12261
  url: this.getUrl(),
12265
12262
  headers: combineHeaders2(
12266
12263
  resolvedHeaders,
12267
12264
  headers != null ? headers : {},
12268
12265
  this.getModelConfigHeaders(),
12269
- await resolve2(this.config.o11yHeaders)
12266
+ await resolve2(this.config.o11yHeaders),
12267
+ { accept: "text/event-stream" }
12270
12268
  ),
12271
12269
  body: {
12272
12270
  prompt,
@@ -12279,9 +12277,70 @@ var GatewayVideoModel = class {
12279
12277
  ...providerOptions && { providerOptions },
12280
12278
  ...image && { image: maybeEncodeVideoFile(image) }
12281
12279
  },
12282
- successfulResponseHandler: createJsonResponseHandler2(
12283
- gatewayVideoResponseSchema
12284
- ),
12280
+ successfulResponseHandler: async ({
12281
+ response,
12282
+ url,
12283
+ requestBodyValues
12284
+ }) => {
12285
+ if (response.body == null) {
12286
+ throw new APICallError3({
12287
+ message: "SSE response body is empty",
12288
+ url,
12289
+ requestBodyValues,
12290
+ statusCode: response.status
12291
+ });
12292
+ }
12293
+ const eventStream = parseJsonEventStream2({
12294
+ stream: response.body,
12295
+ schema: gatewayVideoEventSchema
12296
+ });
12297
+ const reader = eventStream.getReader();
12298
+ const { done, value: parseResult } = await reader.read();
12299
+ reader.releaseLock();
12300
+ if (done || !parseResult) {
12301
+ throw new APICallError3({
12302
+ message: "SSE stream ended without a data event",
12303
+ url,
12304
+ requestBodyValues,
12305
+ statusCode: response.status
12306
+ });
12307
+ }
12308
+ if (!parseResult.success) {
12309
+ throw new APICallError3({
12310
+ message: "Failed to parse video SSE event",
12311
+ cause: parseResult.error,
12312
+ url,
12313
+ requestBodyValues,
12314
+ statusCode: response.status
12315
+ });
12316
+ }
12317
+ const event = parseResult.value;
12318
+ if (event.type === "error") {
12319
+ throw new APICallError3({
12320
+ message: event.message,
12321
+ statusCode: event.statusCode,
12322
+ url,
12323
+ requestBodyValues,
12324
+ responseHeaders: Object.fromEntries([...response.headers]),
12325
+ responseBody: JSON.stringify(event),
12326
+ data: {
12327
+ error: {
12328
+ message: event.message,
12329
+ type: event.errorType,
12330
+ param: event.param
12331
+ }
12332
+ }
12333
+ });
12334
+ }
12335
+ return {
12336
+ value: {
12337
+ videos: event.videos,
12338
+ warnings: event.warnings,
12339
+ providerMetadata: event.providerMetadata
12340
+ },
12341
+ responseHeaders: Object.fromEntries([...response.headers])
12342
+ };
12343
+ },
12285
12344
  failedResponseHandler: createJsonErrorResponseHandler2({
12286
12345
  errorSchema: z4.z.any(),
12287
12346
  errorToMessage: (data) => data
@@ -12353,11 +12412,21 @@ var gatewayVideoWarningSchema = z4.z.discriminatedUnion("type", [
12353
12412
  message: z4.z.string()
12354
12413
  })
12355
12414
  ]);
12356
- var gatewayVideoResponseSchema = z4.z.object({
12357
- videos: z4.z.array(gatewayVideoDataSchema),
12358
- warnings: z4.z.array(gatewayVideoWarningSchema).optional(),
12359
- providerMetadata: z4.z.record(z4.z.string(), providerMetadataEntrySchema22).optional()
12360
- });
12415
+ var gatewayVideoEventSchema = z4.z.discriminatedUnion("type", [
12416
+ z4.z.object({
12417
+ type: z4.z.literal("result"),
12418
+ videos: z4.z.array(gatewayVideoDataSchema),
12419
+ warnings: z4.z.array(gatewayVideoWarningSchema).optional(),
12420
+ providerMetadata: z4.z.record(z4.z.string(), providerMetadataEntrySchema22).optional()
12421
+ }),
12422
+ z4.z.object({
12423
+ type: z4.z.literal("error"),
12424
+ message: z4.z.string(),
12425
+ errorType: z4.z.string(),
12426
+ statusCode: z4.z.number(),
12427
+ param: z4.z.unknown().nullable()
12428
+ })
12429
+ ]);
12361
12430
  var parallelSearchInputSchema = lazySchema(
12362
12431
  () => zodSchema3(
12363
12432
  zod.z.object({
@@ -12534,7 +12603,7 @@ async function getVercelRequestId2() {
12534
12603
  var _a932;
12535
12604
  return (_a932 = (0, import_oidc3.getContext)().headers) == null ? void 0 : _a932["x-vercel-id"];
12536
12605
  }
12537
- var VERSION5 = "3.0.52";
12606
+ var VERSION5 = "3.0.66";
12538
12607
  var AI_GATEWAY_PROTOCOL_VERSION2 = "0.0.1";
12539
12608
  function createGatewayProvider2(options = {}) {
12540
12609
  var _a932, _b92;
@@ -12577,13 +12646,18 @@ function createGatewayProvider2(options = {}) {
12577
12646
  settingValue: void 0,
12578
12647
  environmentVariableName: "VERCEL_REGION"
12579
12648
  });
12649
+ const projectId = loadOptionalSetting2({
12650
+ settingValue: void 0,
12651
+ environmentVariableName: "VERCEL_PROJECT_ID"
12652
+ });
12580
12653
  return async () => {
12581
12654
  const requestId = await getVercelRequestId2();
12582
12655
  return {
12583
12656
  ...deploymentId && { "ai-o11y-deployment-id": deploymentId },
12584
12657
  ...environment && { "ai-o11y-environment": environment },
12585
12658
  ...region && { "ai-o11y-region": region },
12586
- ...requestId && { "ai-o11y-request-id": requestId }
12659
+ ...requestId && { "ai-o11y-request-id": requestId },
12660
+ ...projectId && { "ai-o11y-project-id": projectId }
12587
12661
  };
12588
12662
  };
12589
12663
  };
@@ -13602,7 +13676,7 @@ function getTotalTimeoutMs(timeout) {
13602
13676
  }
13603
13677
  return timeout.totalMs;
13604
13678
  }
13605
- var VERSION33 = "6.0.94";
13679
+ var VERSION33 = "6.0.116";
13606
13680
  var dataContentSchema3 = z4.z.union([
13607
13681
  z4.z.string(),
13608
13682
  z4.z.instanceof(Uint8Array),
@@ -15152,6 +15226,25 @@ var updateWorkingMemoryTool = (memoryConfig) => {
15152
15226
  workingMemory = JSON.stringify(mergedData);
15153
15227
  } else {
15154
15228
  workingMemory = typeof inputData.memory === "string" ? inputData.memory : JSON.stringify(inputData.memory);
15229
+ const existingRaw = await memory.getWorkingMemory({
15230
+ threadId,
15231
+ resourceId,
15232
+ memoryConfig
15233
+ });
15234
+ if (existingRaw) {
15235
+ const template = await memory.getWorkingMemoryTemplate({ memoryConfig });
15236
+ if (template?.content) {
15237
+ const normalizedNew = workingMemory.replace(/\s+/g, " ").trim();
15238
+ const normalizedTemplate = template.content.replace(/\s+/g, " ").trim();
15239
+ const normalizedExisting = existingRaw.replace(/\s+/g, " ").trim();
15240
+ if (normalizedNew === normalizedTemplate && normalizedExisting !== normalizedTemplate) {
15241
+ return {
15242
+ success: false,
15243
+ message: "Attempted to replace existing working memory with empty template. Update skipped to prevent data loss."
15244
+ };
15245
+ }
15246
+ }
15247
+ }
15155
15248
  }
15156
15249
  await memory.updateWorkingMemory({
15157
15250
  threadId,
@@ -15242,6 +15335,7 @@ function normalizeObservationalMemoryConfig(config) {
15242
15335
  var CHARS_PER_TOKEN = 4;
15243
15336
  var DEFAULT_MESSAGE_RANGE = { before: 1, after: 1 };
15244
15337
  var DEFAULT_TOP_K = 4;
15338
+ var VECTOR_DELETE_BATCH_SIZE = 100;
15245
15339
  var isZodObject = (v) => v instanceof zod.ZodObject;
15246
15340
  var Memory = class extends memory.MastraMemory {
15247
15341
  constructor(config = {}) {
@@ -15431,6 +15525,45 @@ var Memory = class extends memory.MastraMemory {
15431
15525
  async deleteThread(threadId) {
15432
15526
  const memoryStore = await this.getMemoryStore();
15433
15527
  await memoryStore.deleteThread({ threadId });
15528
+ if (this.vector) {
15529
+ void this.deleteThreadVectors(threadId);
15530
+ }
15531
+ }
15532
+ /**
15533
+ * Lists all vector indexes that match the memory messages prefix.
15534
+ * Handles separator differences across vector store backends (e.g. '_' vs '-').
15535
+ */
15536
+ async getMemoryVectorIndexes() {
15537
+ if (!this.vector) return [];
15538
+ const separator = this.vector.indexSeparator ?? "_";
15539
+ const prefix = `memory${separator}messages`;
15540
+ const indexes = await this.vector.listIndexes();
15541
+ return indexes.filter((name21) => name21.startsWith(prefix));
15542
+ }
15543
+ /**
15544
+ * Deletes all vector embeddings associated with a thread.
15545
+ * This is called internally by deleteThread to clean up orphaned vectors.
15546
+ *
15547
+ * @param threadId - The ID of the thread whose vectors should be deleted
15548
+ */
15549
+ async deleteThreadVectors(threadId) {
15550
+ try {
15551
+ const memoryIndexes = await this.getMemoryVectorIndexes();
15552
+ await Promise.all(
15553
+ memoryIndexes.map(async (indexName) => {
15554
+ try {
15555
+ await this.vector.deleteVectors({
15556
+ indexName,
15557
+ filter: { thread_id: threadId }
15558
+ });
15559
+ } catch {
15560
+ this.logger.debug(`Failed to delete vectors for thread ${threadId} in ${indexName}, skipping`);
15561
+ }
15562
+ })
15563
+ );
15564
+ } catch {
15565
+ this.logger.debug(`Failed to clean up vectors for thread ${threadId}`);
15566
+ }
15434
15567
  }
15435
15568
  async updateWorkingMemory({
15436
15569
  threadId,
@@ -15448,25 +15581,33 @@ var Memory = class extends memory.MastraMemory {
15448
15581
  `Memory error: Resource-scoped working memory is enabled but no resourceId was provided. Either provide a resourceId or explicitly set workingMemory.scope to 'thread'.`
15449
15582
  );
15450
15583
  }
15451
- const memoryStore = await this.getMemoryStore();
15452
- if (scope === "resource" && resourceId) {
15453
- await memoryStore.updateResource({
15454
- resourceId,
15455
- workingMemory
15456
- });
15457
- } else {
15458
- const thread = await this.getThreadById({ threadId });
15459
- if (!thread) {
15460
- throw new Error(`Thread ${threadId} not found`);
15461
- }
15462
- await memoryStore.updateThread({
15463
- id: threadId,
15464
- title: thread.title || "",
15465
- metadata: {
15466
- ...thread.metadata,
15584
+ const mutexKey = scope === "resource" ? `resource-${resourceId}` : `thread-${threadId}`;
15585
+ const mutex = this.updateWorkingMemoryMutexes.has(mutexKey) ? this.updateWorkingMemoryMutexes.get(mutexKey) : new asyncMutex.Mutex();
15586
+ this.updateWorkingMemoryMutexes.set(mutexKey, mutex);
15587
+ const release = await mutex.acquire();
15588
+ try {
15589
+ const memoryStore = await this.getMemoryStore();
15590
+ if (scope === "resource" && resourceId) {
15591
+ await memoryStore.updateResource({
15592
+ resourceId,
15467
15593
  workingMemory
15594
+ });
15595
+ } else {
15596
+ const thread = await this.getThreadById({ threadId });
15597
+ if (!thread) {
15598
+ throw new Error(`Thread ${threadId} not found`);
15468
15599
  }
15469
- });
15600
+ await memoryStore.updateThread({
15601
+ id: threadId,
15602
+ title: thread.title || "",
15603
+ metadata: {
15604
+ ...thread.metadata,
15605
+ workingMemory
15606
+ }
15607
+ });
15608
+ }
15609
+ } finally {
15610
+ release();
15470
15611
  }
15471
15612
  }
15472
15613
  updateWorkingMemoryMutexes = /* @__PURE__ */ new Map();
@@ -15492,16 +15633,26 @@ var Memory = class extends memory.MastraMemory {
15492
15633
  const existingWorkingMemory = await this.getWorkingMemory({ threadId, resourceId, memoryConfig }) || "";
15493
15634
  const template = await this.getWorkingMemoryTemplate({ memoryConfig });
15494
15635
  let reason = "";
15636
+ const normalizeForComparison = (str) => str.replace(/\s+/g, " ").trim();
15637
+ const normalizedNewMemory = normalizeForComparison(workingMemory);
15638
+ const normalizedTemplate = template?.content ? normalizeForComparison(template.content) : "";
15495
15639
  if (existingWorkingMemory) {
15496
15640
  if (searchString && existingWorkingMemory?.includes(searchString)) {
15497
15641
  workingMemory = existingWorkingMemory.replace(searchString, workingMemory);
15498
15642
  reason = `found and replaced searchString with newMemory`;
15499
- } else if (existingWorkingMemory.includes(workingMemory) || template?.content?.trim() === workingMemory.trim()) {
15643
+ } else if (existingWorkingMemory.includes(workingMemory) || template?.content?.trim() === workingMemory.trim() || // Also check normalized versions to catch template variations with different whitespace
15644
+ normalizedNewMemory === normalizedTemplate) {
15500
15645
  return {
15501
15646
  success: false,
15502
15647
  reason: `attempted to insert duplicate data into working memory. this entry was skipped`
15503
15648
  };
15504
15649
  } else {
15650
+ if (normalizedNewMemory === normalizedTemplate) {
15651
+ return {
15652
+ success: false,
15653
+ reason: `attempted to append empty template to working memory. this entry was skipped`
15654
+ };
15655
+ }
15505
15656
  if (searchString) {
15506
15657
  reason = `attempted to replace working memory string that doesn't exist. Appending to working memory instead.`;
15507
15658
  } else {
@@ -15510,7 +15661,7 @@ var Memory = class extends memory.MastraMemory {
15510
15661
  workingMemory = existingWorkingMemory + `
15511
15662
  ${workingMemory}`;
15512
15663
  }
15513
- } else if (workingMemory === template?.content) {
15664
+ } else if (workingMemory === template?.content || normalizedNewMemory === normalizedTemplate) {
15514
15665
  return {
15515
15666
  success: false,
15516
15667
  reason: `try again when you have data to add. newMemory was equal to the working memory template`
@@ -15518,7 +15669,13 @@ ${workingMemory}`;
15518
15669
  } else {
15519
15670
  reason = `started new working memory`;
15520
15671
  }
15521
- workingMemory = template?.content ? workingMemory.replaceAll(template?.content, "") : workingMemory;
15672
+ if (template?.content) {
15673
+ workingMemory = workingMemory.replaceAll(template.content, "");
15674
+ const templateWithUnixLineEndings = template.content.replace(/\r\n/g, "\n");
15675
+ const templateWithWindowsLineEndings = template.content.replace(/\n/g, "\r\n");
15676
+ workingMemory = workingMemory.replaceAll(templateWithUnixLineEndings, "");
15677
+ workingMemory = workingMemory.replaceAll(templateWithWindowsLineEndings, "");
15678
+ }
15522
15679
  const scope = config.workingMemory.scope || "resource";
15523
15680
  if (scope === "resource" && !resourceId) {
15524
15681
  throw new Error(
@@ -15994,24 +16151,25 @@ Notes:
15994
16151
  const messageIdsNeedingDeletion = /* @__PURE__ */ new Set([...messageIdsWithClearedContent, ...messageIdsWithNewEmbeddings]);
15995
16152
  if (messageIdsNeedingDeletion.size > 0) {
15996
16153
  try {
15997
- const indexes = await this.vector.listIndexes();
15998
- const memoryIndexes = indexes.filter((name21) => name21.startsWith("memory_messages"));
15999
- for (const indexName of memoryIndexes) {
16000
- for (const messageId of messageIdsNeedingDeletion) {
16001
- try {
16002
- await this.vector.deleteVectors({
16003
- indexName,
16004
- filter: { message_id: messageId }
16005
- });
16006
- } catch {
16007
- this.logger.debug(
16008
- `No existing vectors found for message ${messageId} in ${indexName}, skipping delete`
16009
- );
16154
+ const memoryIndexes = await this.getMemoryVectorIndexes();
16155
+ const idsToDelete = [...messageIdsNeedingDeletion];
16156
+ await Promise.all(
16157
+ memoryIndexes.map(async (indexName) => {
16158
+ for (let i = 0; i < idsToDelete.length; i += VECTOR_DELETE_BATCH_SIZE) {
16159
+ const batch = idsToDelete.slice(i, i + VECTOR_DELETE_BATCH_SIZE);
16160
+ try {
16161
+ await this.vector.deleteVectors({
16162
+ indexName,
16163
+ filter: { message_id: { $in: batch } }
16164
+ });
16165
+ } catch {
16166
+ this.logger.debug(`Failed to delete vector batch in ${indexName} (batch offset ${i}), skipping`);
16167
+ }
16010
16168
  }
16011
- }
16012
- }
16169
+ })
16170
+ );
16013
16171
  } catch {
16014
- this.logger.debug(`No memory indexes found to delete from`);
16172
+ this.logger.debug(`Failed to clean up old vectors during message update`);
16015
16173
  }
16016
16174
  }
16017
16175
  if (embeddingData.length > 0 && dimension !== void 0) {
@@ -16062,6 +16220,37 @@ Notes:
16062
16220
  }
16063
16221
  const memoryStore = await this.getMemoryStore();
16064
16222
  await memoryStore.deleteMessages(messageIds);
16223
+ if (this.vector) {
16224
+ void this.deleteMessageVectors(messageIds);
16225
+ }
16226
+ }
16227
+ /**
16228
+ * Deletes vector embeddings for specific messages.
16229
+ * This is called internally by deleteMessages to clean up orphaned vectors.
16230
+ *
16231
+ * @param messageIds - The IDs of the messages whose vectors should be deleted
16232
+ */
16233
+ async deleteMessageVectors(messageIds) {
16234
+ try {
16235
+ const memoryIndexes = await this.getMemoryVectorIndexes();
16236
+ await Promise.all(
16237
+ memoryIndexes.map(async (indexName) => {
16238
+ for (let i = 0; i < messageIds.length; i += VECTOR_DELETE_BATCH_SIZE) {
16239
+ const batch = messageIds.slice(i, i + VECTOR_DELETE_BATCH_SIZE);
16240
+ try {
16241
+ await this.vector.deleteVectors({
16242
+ indexName,
16243
+ filter: { message_id: { $in: batch } }
16244
+ });
16245
+ } catch {
16246
+ this.logger.debug(`Failed to delete vector batch in ${indexName} (batch offset ${i}), skipping`);
16247
+ }
16248
+ }
16249
+ })
16250
+ );
16251
+ } catch {
16252
+ this.logger.debug(`Failed to clean up vectors for deleted messages`);
16253
+ }
16065
16254
  }
16066
16255
  /**
16067
16256
  * Clone a thread and its messages to create a new independent thread.
@@ -16476,7 +16665,7 @@ Notes:
16476
16665
  "Observational memory async buffering is enabled by default but the installed version of @mastra/core does not support it. Either upgrade @mastra/core, @mastra/memory, and your storage adapter (@mastra/libsql, @mastra/pg, or @mastra/mongodb) to the latest version, or explicitly disable async buffering by setting `observation: { bufferTokens: false }` in your observationalMemory config."
16477
16666
  );
16478
16667
  }
16479
- const { ObservationalMemory } = await import('./observational-memory-MXI54VC7.cjs');
16668
+ const { ObservationalMemory } = await import('./observational-memory-AHVELJX4.cjs');
16480
16669
  return new ObservationalMemory({
16481
16670
  storage: memoryStore,
16482
16671
  scope: omConfig.scope,