@mastra/memory 1.28.0-alpha.3 → 1.28.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.
@@ -57,8 +57,8 @@ probe_image_size_sync_js = __toESM$1(probe_image_size_sync_js, 1);
57
57
  let _mastra_core_schema = require("@mastra/core/schema");
58
58
  let _mastra_core_error = require("@mastra/core/error");
59
59
  let _mastra_core_processors = require("@mastra/core/processors");
60
- let util = require("util");
61
60
  let diff = require("diff");
61
+ let util = require("util");
62
62
  //#region ../_vendored/ai_v4/dist/dist-Cm5DZgxA.js
63
63
  var marker$6 = "vercel.ai.error";
64
64
  var symbol$6 = Symbol.for(marker$6);
@@ -22436,6 +22436,151 @@ function createWorkingMemoryTool(config, options = {}) {
22436
22436
  };
22437
22437
  }
22438
22438
  //#endregion
22439
+ //#region src/processors/working-memory-state/processor.ts
22440
+ /**
22441
+ * WorkingMemoryStateProcessor
22442
+ *
22443
+ * Experimental: delivers working memory to the model as a state signal instead
22444
+ * of folding it into the system message. Storage and the `setWorkingMemory`
22445
+ * tool are unchanged — this processor only changes the delivery path.
22446
+ *
22447
+ * Pattern matches `BrowserContextProcessor` in `@mastra/core/browser`:
22448
+ * - `stateId` namespaces the state lane on the thread.
22449
+ * - `cacheKey` is derived from the rendered payload so dedup is automatic.
22450
+ * - `contextWindow.hasSnapshot` re-injection ensures the model still sees the
22451
+ * current snapshot after older messages drop out of the window.
22452
+ *
22453
+ * Delta emission (markdown mode only): when a prior snapshot exists in the
22454
+ * context window, the processor emits a unified-diff delta against that
22455
+ * snapshot's contents. Schema mode and the snapshot fallback always emit a
22456
+ * full snapshot.
22457
+ *
22458
+ * @example
22459
+ * ```ts
22460
+ * new Memory({
22461
+ * options: {
22462
+ * workingMemory: {
22463
+ * enabled: true,
22464
+ * template: '...',
22465
+ * useStateSignals: true, // auto-attaches this processor
22466
+ * },
22467
+ * },
22468
+ * });
22469
+ * ```
22470
+ */
22471
+ const WORKING_MEMORY_STATE_ID = "working-memory";
22472
+ const WORKING_MEMORY_STATE_PROCESSOR_ID = "working-memory-state";
22473
+ var WorkingMemoryStateProcessor = class {
22474
+ memory;
22475
+ memoryConfig;
22476
+ id = WORKING_MEMORY_STATE_PROCESSOR_ID;
22477
+ stateId = WORKING_MEMORY_STATE_ID;
22478
+ constructor(memory, memoryConfig) {
22479
+ this.memory = memory;
22480
+ this.memoryConfig = memoryConfig;
22481
+ }
22482
+ async computeStateSignal(args) {
22483
+ const template = await this.memory.getWorkingMemoryTemplate({ memoryConfig: this.memoryConfig });
22484
+ if (!template) return;
22485
+ const contents = (await this.memory.getWorkingMemory({
22486
+ threadId: args.threadId,
22487
+ resourceId: args.resourceId,
22488
+ memoryConfig: this.memoryConfig
22489
+ }))?.trim();
22490
+ if (!contents) return;
22491
+ const cacheKey = stableWorkingMemoryCacheKey({
22492
+ format: template.format,
22493
+ data: contents
22494
+ });
22495
+ const shouldMakeSnapshot = !args.contextWindow.hasSnapshot;
22496
+ if (args.tracking?.currentCacheKey === cacheKey && !shouldMakeSnapshot) return;
22497
+ const scope = this.memory.getMergedThreadConfig(this.memoryConfig).workingMemory?.scope ?? "resource";
22498
+ const deltaCandidate = template.format === "markdown" && !shouldMakeSnapshot ? buildMarkdownDelta({
22499
+ lastSnapshot: args.lastSnapshot,
22500
+ deltasSinceSnapshot: args.deltasSinceSnapshot,
22501
+ nextContents: contents
22502
+ }) : void 0;
22503
+ if (deltaCandidate) return {
22504
+ id: WORKING_MEMORY_STATE_ID,
22505
+ mode: "delta",
22506
+ cacheKey,
22507
+ tagName: "working-memory",
22508
+ contents: deltaCandidate.contents,
22509
+ delta: deltaCandidate.contents,
22510
+ value: contents,
22511
+ attributes: {
22512
+ format: template.format,
22513
+ scope,
22514
+ patch: "unified-diff"
22515
+ }
22516
+ };
22517
+ return {
22518
+ id: WORKING_MEMORY_STATE_ID,
22519
+ mode: "snapshot",
22520
+ cacheKey,
22521
+ tagName: "working-memory",
22522
+ contents,
22523
+ value: contents,
22524
+ attributes: {
22525
+ format: template.format,
22526
+ scope
22527
+ }
22528
+ };
22529
+ }
22530
+ };
22531
+ /**
22532
+ * Stable cache key for the rendered working memory payload. Returns a SHA-256
22533
+ * digest so dedup metadata stays compact regardless of payload size (working
22534
+ * memory blobs can grow arbitrarily long).
22535
+ */
22536
+ function stableWorkingMemoryCacheKey(input) {
22537
+ const hash = (0, crypto$1.createHash)("sha256");
22538
+ hash.update(input.format);
22539
+ hash.update("\0");
22540
+ hash.update(input.data ?? "");
22541
+ return `sha256:${hash.digest("hex")}`;
22542
+ }
22543
+ /**
22544
+ * Build a unified-diff delta against the most recently emitted state. Prefers
22545
+ * the latest delta's `value` (the post-edit full text) when available, falling
22546
+ * back to the snapshot's `value` and finally the snapshot's `contents`. This
22547
+ * keeps deltas incremental (B→C) instead of cumulative against a stale
22548
+ * snapshot (A→C), which matters when many small edits land between snapshots.
22549
+ *
22550
+ * Returns undefined when:
22551
+ * - there's no prior state to diff against
22552
+ * - the prior state isn't a plain string (multimodal signal)
22553
+ *
22554
+ * In either case the caller falls back to emitting a full snapshot.
22555
+ */
22556
+ function buildMarkdownDelta(args) {
22557
+ const { lastSnapshot, deltasSinceSnapshot, nextContents } = args;
22558
+ const prior = pickStringValue(readSignalValue(deltasSinceSnapshot.at(-1))) ?? pickStringValue(readSignalValue(lastSnapshot)) ?? (typeof lastSnapshot?.contents === "string" ? lastSnapshot.contents : void 0);
22559
+ if (!prior) return;
22560
+ return { contents: renderHunksOnly(prior, nextContents) };
22561
+ }
22562
+ function pickStringValue(value) {
22563
+ return typeof value === "string" ? value : void 0;
22564
+ }
22565
+ function readSignalValue(signal) {
22566
+ return (signal?.metadata)?.value;
22567
+ }
22568
+ /**
22569
+ * Render a unified-diff-style patch body containing only `@@` hunks and their
22570
+ * lines — dropping the filename preamble (`Index:` / `===` / `---` / `+++`)
22571
+ * that `createPatch` emits and the `` trailer.
22572
+ * The preamble exists for tooling like `patch -p1` to know which file to
22573
+ * apply to; we only ever diff a single working-memory blob. The newline
22574
+ * trailer is semantically meaningless to the model and adds noise to the
22575
+ * state signal.
22576
+ */
22577
+ function renderHunksOnly(prior, next) {
22578
+ const { hunks } = (0, diff.structuredPatch)("", "", prior, next, "", "", { context: 0 });
22579
+ return hunks.map((hunk) => {
22580
+ return [`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`, ...hunk.lines.filter((line) => !line.startsWith("\"))].join("\n");
22581
+ }).join("\n");
22582
+ }
22583
+ //#endregion
22439
22584
  //#region src/processors/observational-memory/activation-ttl.ts
22440
22585
  const MINUTE = 6e4;
22441
22586
  const HOUR = 60 * MINUTE;
@@ -26454,10 +26599,24 @@ function getLatestStepParts(parts) {
26454
26599
  return parts;
26455
26600
  }
26456
26601
  /**
26457
- * Returns true when a message contains at least one part with visible user/assistant
26458
- * content (text, tool-invocation, reasoning, image, file). Messages that only carry
26459
- * internal `data-*` parts (buffering markers, observation markers, etc.) return false.
26602
+ * Returns true for persisted Working Memory state signals (`role: 'signal'`,
26603
+ * `signal.type: 'state'`, `state.id: 'working-memory'`). OM must not observe these:
26604
+ * they mirror memory OM itself manages, and re-observing them creates a self-feedback
26605
+ * loop that can overwrite stored working memory (#21961). Only the working-memory lane
26606
+ * is filtered — other state lanes are outside OM's write path, so re-observing them
26607
+ * cannot corrupt OM-managed state; excluding them broadly is a separate decision.
26460
26608
  */
26609
+ function isWorkingMemoryStateSignal(message) {
26610
+ if (message.role !== "signal") return false;
26611
+ const signal = message.content.metadata?.signal;
26612
+ if (!signal || typeof signal !== "object" || Array.isArray(signal)) return false;
26613
+ const signalRecord = signal;
26614
+ if ((signalRecord.type ?? message.type) !== "state") return false;
26615
+ const metadata = signalRecord.metadata;
26616
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return false;
26617
+ const state = metadata.state;
26618
+ return !!state && typeof state === "object" && !Array.isArray(state) && state.id === "working-memory";
26619
+ }
26461
26620
  function messageHasVisibleContent(msg) {
26462
26621
  const content = msg.content;
26463
26622
  if (content?.parts && Array.isArray(content.parts)) return content.parts.some((p) => {
@@ -27228,7 +27387,7 @@ var ObservationalMemory = class ObservationalMemory {
27228
27387
  }
27229
27388
  const result = [];
27230
27389
  for (const msg of allMessages) {
27231
- if (msg.role === "system") continue;
27390
+ if (msg.role === "system" || isWorkingMemoryStateSignal(msg)) continue;
27232
27391
  if (observedMessageIds?.has(msg.id)) continue;
27233
27392
  const endMarkerIndex = findLastCompletedObservationBoundary(msg);
27234
27393
  if (this.hasInProgressObservation(msg)) result.push(msg);
@@ -27450,7 +27609,7 @@ var ObservationalMemory = class ObservationalMemory {
27450
27609
  },
27451
27610
  filter: startDate ? { dateRange: { start: startDate } } : void 0
27452
27611
  });
27453
- return result.messages.filter((msg) => msg.role !== "system");
27612
+ return result.messages.filter((msg) => msg.role !== "system" && !isWorkingMemoryStateSignal(msg));
27454
27613
  }
27455
27614
  /**
27456
27615
  * Format unobserved messages from other threads as <unobserved-context> blocks.
@@ -27909,7 +28068,7 @@ ${formattedMessages}
27909
28068
  direction: "ASC"
27910
28069
  },
27911
28070
  filter: startDate ? { dateRange: { start: startDate } } : void 0
27912
- })).messages.filter((m) => !this.observedMessageIds.has(m.id));
28071
+ })).messages.filter((message) => !this.observedMessageIds.has(message.id) && !isWorkingMemoryStateSignal(message));
27913
28072
  if (filtered.length > 0) messagesByThread.set(thread.id, filtered);
27914
28073
  }
27915
28074
  if (messagesByThread.size === 0) return void 0;
@@ -29335,151 +29494,6 @@ var observational_memory_exports = /* @__PURE__ */ __exportAll({
29335
29494
  wrapInObservationGroup: () => wrapInObservationGroup
29336
29495
  });
29337
29496
  //#endregion
29338
- //#region src/processors/working-memory-state/processor.ts
29339
- /**
29340
- * WorkingMemoryStateProcessor
29341
- *
29342
- * Experimental: delivers working memory to the model as a state signal instead
29343
- * of folding it into the system message. Storage and the `setWorkingMemory`
29344
- * tool are unchanged — this processor only changes the delivery path.
29345
- *
29346
- * Pattern matches `BrowserContextProcessor` in `@mastra/core/browser`:
29347
- * - `stateId` namespaces the state lane on the thread.
29348
- * - `cacheKey` is derived from the rendered payload so dedup is automatic.
29349
- * - `contextWindow.hasSnapshot` re-injection ensures the model still sees the
29350
- * current snapshot after older messages drop out of the window.
29351
- *
29352
- * Delta emission (markdown mode only): when a prior snapshot exists in the
29353
- * context window, the processor emits a unified-diff delta against that
29354
- * snapshot's contents. Schema mode and the snapshot fallback always emit a
29355
- * full snapshot.
29356
- *
29357
- * @example
29358
- * ```ts
29359
- * new Memory({
29360
- * options: {
29361
- * workingMemory: {
29362
- * enabled: true,
29363
- * template: '...',
29364
- * useStateSignals: true, // auto-attaches this processor
29365
- * },
29366
- * },
29367
- * });
29368
- * ```
29369
- */
29370
- const WORKING_MEMORY_STATE_ID = "working-memory";
29371
- const WORKING_MEMORY_STATE_PROCESSOR_ID = "working-memory-state";
29372
- var WorkingMemoryStateProcessor = class {
29373
- memory;
29374
- memoryConfig;
29375
- id = WORKING_MEMORY_STATE_PROCESSOR_ID;
29376
- stateId = WORKING_MEMORY_STATE_ID;
29377
- constructor(memory, memoryConfig) {
29378
- this.memory = memory;
29379
- this.memoryConfig = memoryConfig;
29380
- }
29381
- async computeStateSignal(args) {
29382
- const template = await this.memory.getWorkingMemoryTemplate({ memoryConfig: this.memoryConfig });
29383
- if (!template) return;
29384
- const contents = (await this.memory.getWorkingMemory({
29385
- threadId: args.threadId,
29386
- resourceId: args.resourceId,
29387
- memoryConfig: this.memoryConfig
29388
- }))?.trim();
29389
- if (!contents) return;
29390
- const cacheKey = stableWorkingMemoryCacheKey({
29391
- format: template.format,
29392
- data: contents
29393
- });
29394
- const shouldMakeSnapshot = !args.contextWindow.hasSnapshot;
29395
- if (args.tracking?.currentCacheKey === cacheKey && !shouldMakeSnapshot) return;
29396
- const scope = this.memory.getMergedThreadConfig(this.memoryConfig).workingMemory?.scope ?? "resource";
29397
- const deltaCandidate = template.format === "markdown" && !shouldMakeSnapshot ? buildMarkdownDelta({
29398
- lastSnapshot: args.lastSnapshot,
29399
- deltasSinceSnapshot: args.deltasSinceSnapshot,
29400
- nextContents: contents
29401
- }) : void 0;
29402
- if (deltaCandidate) return {
29403
- id: WORKING_MEMORY_STATE_ID,
29404
- mode: "delta",
29405
- cacheKey,
29406
- tagName: "working-memory",
29407
- contents: deltaCandidate.contents,
29408
- delta: deltaCandidate.contents,
29409
- value: contents,
29410
- attributes: {
29411
- format: template.format,
29412
- scope,
29413
- patch: "unified-diff"
29414
- }
29415
- };
29416
- return {
29417
- id: WORKING_MEMORY_STATE_ID,
29418
- mode: "snapshot",
29419
- cacheKey,
29420
- tagName: "working-memory",
29421
- contents,
29422
- value: contents,
29423
- attributes: {
29424
- format: template.format,
29425
- scope
29426
- }
29427
- };
29428
- }
29429
- };
29430
- /**
29431
- * Stable cache key for the rendered working memory payload. Returns a SHA-256
29432
- * digest so dedup metadata stays compact regardless of payload size (working
29433
- * memory blobs can grow arbitrarily long).
29434
- */
29435
- function stableWorkingMemoryCacheKey(input) {
29436
- const hash = (0, crypto$1.createHash)("sha256");
29437
- hash.update(input.format);
29438
- hash.update("\0");
29439
- hash.update(input.data ?? "");
29440
- return `sha256:${hash.digest("hex")}`;
29441
- }
29442
- /**
29443
- * Build a unified-diff delta against the most recently emitted state. Prefers
29444
- * the latest delta's `value` (the post-edit full text) when available, falling
29445
- * back to the snapshot's `value` and finally the snapshot's `contents`. This
29446
- * keeps deltas incremental (B→C) instead of cumulative against a stale
29447
- * snapshot (A→C), which matters when many small edits land between snapshots.
29448
- *
29449
- * Returns undefined when:
29450
- * - there's no prior state to diff against
29451
- * - the prior state isn't a plain string (multimodal signal)
29452
- *
29453
- * In either case the caller falls back to emitting a full snapshot.
29454
- */
29455
- function buildMarkdownDelta(args) {
29456
- const { lastSnapshot, deltasSinceSnapshot, nextContents } = args;
29457
- const prior = pickStringValue(readSignalValue(deltasSinceSnapshot.at(-1))) ?? pickStringValue(readSignalValue(lastSnapshot)) ?? (typeof lastSnapshot?.contents === "string" ? lastSnapshot.contents : void 0);
29458
- if (!prior) return;
29459
- return { contents: renderHunksOnly(prior, nextContents) };
29460
- }
29461
- function pickStringValue(value) {
29462
- return typeof value === "string" ? value : void 0;
29463
- }
29464
- function readSignalValue(signal) {
29465
- return (signal?.metadata)?.value;
29466
- }
29467
- /**
29468
- * Render a unified-diff-style patch body containing only `@@` hunks and their
29469
- * lines — dropping the filename preamble (`Index:` / `===` / `---` / `+++`)
29470
- * that `createPatch` emits and the `` trailer.
29471
- * The preamble exists for tooling like `patch -p1` to know which file to
29472
- * apply to; we only ever diff a single working-memory blob. The newline
29473
- * trailer is semantically meaningless to the model and adds noise to the
29474
- * state signal.
29475
- */
29476
- function renderHunksOnly(prior, next) {
29477
- const { hunks } = (0, diff.structuredPatch)("", "", prior, next, "", "", { context: 0 });
29478
- return hunks.map((hunk) => {
29479
- return [`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`, ...hunk.lines.filter((line) => !line.startsWith("\"))].join("\n");
29480
- }).join("\n");
29481
- }
29482
- //#endregion
29483
29497
  //#region src/processors/working-memory-state/index.ts
29484
29498
  var working_memory_state_exports = /* @__PURE__ */ __exportAll({
29485
29499
  WORKING_MEMORY_STATE_ID: () => WORKING_MEMORY_STATE_ID,
@@ -31795,4 +31809,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
31795
31809
  }
31796
31810
  });
31797
31811
 
31798
- //# sourceMappingURL=src-D8byb8Ge.cjs.map
31812
+ //# sourceMappingURL=src-C7Ozl6Pf.cjs.map