@mastra/memory 1.31.0-alpha.1 → 1.31.0-alpha.3

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 (35) hide show
  1. package/dist/docs/SKILL.md +1 -1
  2. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  3. package/dist/docs/references/docs-memory-observational-memory.md +33 -0
  4. package/dist/docs/references/integrations-channels-github.md +6 -2
  5. package/dist/docs/references/reference-memory-observational-memory.md +34 -4
  6. package/dist/hooks.cjs +158 -0
  7. package/dist/hooks.cjs.map +1 -0
  8. package/dist/hooks.d.ts +25 -0
  9. package/dist/hooks.d.ts.map +1 -0
  10. package/dist/hooks.js +156 -0
  11. package/dist/hooks.js.map +1 -0
  12. package/dist/index.cjs +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/processors/index.cjs +1 -1
  16. package/dist/processors/index.js +1 -1
  17. package/dist/processors/observational-memory/hooks.d.ts +63 -0
  18. package/dist/processors/observational-memory/hooks.d.ts.map +1 -0
  19. package/dist/processors/observational-memory/observation-groups.d.ts.map +1 -1
  20. package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
  21. package/dist/processors/observational-memory/observation-strategies/sync.d.ts.map +1 -1
  22. package/dist/processors/observational-memory/observation-turn/turn.d.ts +2 -0
  23. package/dist/processors/observational-memory/observation-turn/turn.d.ts.map +1 -1
  24. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  25. package/dist/processors/observational-memory/processor.d.ts.map +1 -1
  26. package/dist/processors/observational-memory/types.d.ts +6 -6
  27. package/dist/{src-Ju0u9OWc.js → src-CSVmHSng.js} +256 -52
  28. package/dist/src-CSVmHSng.js.map +1 -0
  29. package/dist/{src-BCvV2aGL.cjs → src-QqTmOY5X.cjs} +271 -67
  30. package/dist/src-QqTmOY5X.cjs.map +1 -0
  31. package/dist/tools/om-tools.d.ts +4 -2
  32. package/dist/tools/om-tools.d.ts.map +1 -1
  33. package/package.json +15 -5
  34. package/dist/src-BCvV2aGL.cjs.map +0 -1
  35. package/dist/src-Ju0u9OWc.js.map +0 -1
@@ -52,7 +52,6 @@ let path = require("path");
52
52
  let _mastra_core_tools = require("@mastra/core/tools");
53
53
  let _mastra_core_request_context = require("@mastra/core/request-context");
54
54
  let _mastra_core_llm = require("@mastra/core/llm");
55
- let tokenx = require("tokenx");
56
55
  let async_hooks = require("async_hooks");
57
56
  let probe_image_size_sync_js = require("probe-image-size/sync.js");
58
57
  probe_image_size_sync_js = __toESM$1(probe_image_size_sync_js, 1);
@@ -18326,6 +18325,62 @@ function safeSlice(str, end) {
18326
18325
  return str.slice(0, safeEnd);
18327
18326
  }
18328
18327
  //#endregion
18328
+ //#region ../../../../../setup-pnpm/node_modules/.bin/store/v11/links/@/tokenx/1.3.0/09093cdaeebfe25c39ba9052d6277b45fc99abf5e14e859f60f338f5c5160afd/node_modules/tokenx/dist/index.mjs
18329
+ const PATTERNS = {
18330
+ whitespace: /^\s+$/,
18331
+ cjk: /[\u4E00-\u9FFF\u3400-\u4DBF\u3000-\u303F\uFF00-\uFFEF\u30A0-\u30FF\u2E80-\u2EFF\u31C0-\u31EF\u3200-\u32FF\u3300-\u33FF\uAC00-\uD7AF\u1100-\u11FF\u3130-\u318F\uA960-\uA97F\uD7B0-\uD7FF]/,
18332
+ numeric: /^\d+(?:[.,]\d+)*$/,
18333
+ punctuation: /[.,!?;(){}[\]<>:/\\|@#$%^&*+=`~_-]/,
18334
+ alphanumeric: /^[a-zA-Z0-9\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]+$/
18335
+ };
18336
+ const TOKEN_SPLIT_PATTERN = /* @__PURE__ */ new RegExp(`(\\s+|${PATTERNS.punctuation.source}+)`);
18337
+ const DEFAULT_CHARS_PER_TOKEN = 6;
18338
+ const SHORT_TOKEN_THRESHOLD = 3;
18339
+ const DEFAULT_LANGUAGE_CONFIGS = [
18340
+ {
18341
+ pattern: /[äöüßẞ]/i,
18342
+ averageCharsPerToken: 3
18343
+ },
18344
+ {
18345
+ pattern: /[éèêëàâîïôûùüÿçœæáíóúñ]/i,
18346
+ averageCharsPerToken: 3
18347
+ },
18348
+ {
18349
+ pattern: /[ąćęłńóśźżěščřžýůúďťň]/i,
18350
+ averageCharsPerToken: 3.5
18351
+ }
18352
+ ];
18353
+ /**
18354
+ * Estimates the number of tokens in a text string using heuristic rules.
18355
+ */
18356
+ function estimateTokenCount(text, options = {}) {
18357
+ if (!text) return 0;
18358
+ const { defaultCharsPerToken = DEFAULT_CHARS_PER_TOKEN, languageConfigs = DEFAULT_LANGUAGE_CONFIGS } = options;
18359
+ const segments = text.split(TOKEN_SPLIT_PATTERN).filter(Boolean);
18360
+ let tokenCount = 0;
18361
+ for (const segment of segments) tokenCount += estimateSegmentTokens(segment, languageConfigs, defaultCharsPerToken);
18362
+ return tokenCount;
18363
+ }
18364
+ function estimateSegmentTokens(segment, languageConfigs, defaultCharsPerToken) {
18365
+ if (PATTERNS.whitespace.test(segment)) return 0;
18366
+ if (PATTERNS.cjk.test(segment)) return getCharacterCount(segment);
18367
+ if (PATTERNS.numeric.test(segment)) return 1;
18368
+ if (segment.length <= SHORT_TOKEN_THRESHOLD) return 1;
18369
+ if (PATTERNS.punctuation.test(segment)) return segment.length > 1 ? Math.ceil(segment.length / 2) : 1;
18370
+ if (PATTERNS.alphanumeric.test(segment)) {
18371
+ const charsPerToken$1 = getLanguageSpecificCharsPerToken(segment, languageConfigs) ?? defaultCharsPerToken;
18372
+ return Math.ceil(segment.length / charsPerToken$1);
18373
+ }
18374
+ const charsPerToken = getLanguageSpecificCharsPerToken(segment, languageConfigs) ?? defaultCharsPerToken;
18375
+ return Math.ceil(segment.length / charsPerToken);
18376
+ }
18377
+ function getLanguageSpecificCharsPerToken(segment, languageConfigs) {
18378
+ for (const config of languageConfigs) if (config.pattern.test(segment)) return config.averageCharsPerToken;
18379
+ }
18380
+ function getCharacterCount(text) {
18381
+ return Array.from(text).length;
18382
+ }
18383
+ //#endregion
18329
18384
  //#region src/processors/observational-memory/tool-argument-helpers.ts
18330
18385
  const DEFAULT_OBSERVER_TOOL_ARGUMENT_MAX_TOKENS = 2e3;
18331
18386
  const OBSERVER_TOOL_ARGUMENT_INLINE_STRING_MAX_CHARS = 160;
@@ -18459,7 +18514,7 @@ function readEntries(value) {
18459
18514
  }
18460
18515
  }
18461
18516
  function fits(text, limits) {
18462
- return text.length <= limits.maxCharacters && (0, tokenx.estimateTokenCount)(text) <= limits.maxTokens;
18517
+ return text.length <= limits.maxCharacters && estimateTokenCount(text) <= limits.maxTokens;
18463
18518
  }
18464
18519
  function joinEntries(entries, marker) {
18465
18520
  const lines = [...entries].sort((a, b) => a.order - b.order).map((entry) => entry.text);
@@ -18473,7 +18528,7 @@ function selectOutlineEntries(entries, limits) {
18473
18528
  const selected = [];
18474
18529
  const candidates = [...entries].sort((a, b) => {
18475
18530
  if (a.depth !== b.depth) return a.depth - b.depth;
18476
- return (0, tokenx.estimateTokenCount)(a.text) - (0, tokenx.estimateTokenCount)(b.text) || a.text.length - b.text.length || a.order - b.order;
18531
+ return estimateTokenCount(a.text) - estimateTokenCount(b.text) || a.text.length - b.text.length || a.order - b.order;
18477
18532
  });
18478
18533
  for (const candidate of candidates) if (fits(joinEntries([...selected, candidate], marker), limits)) selected.push(candidate);
18479
18534
  const rendered = joinEntries(selected, marker);
@@ -18651,10 +18706,10 @@ function formatToolArgumentsForObserver(value, options) {
18651
18706
  let omittedPreviews = 0;
18652
18707
  for (let index = 0; index < previews.length; index++) {
18653
18708
  const remainingCount = previews.length - index;
18654
- const remainingTokens = Math.max(0, limits.maxTokens - (0, tokenx.estimateTokenCount)(rendered));
18709
+ const remainingTokens = Math.max(0, limits.maxTokens - estimateTokenCount(rendered));
18655
18710
  const remainingCharacters = Math.max(0, limits.maxCharacters - rendered.length - 1);
18656
18711
  const previewLimits = {
18657
- maxTokens: (0, tokenx.estimateTokenCount)(rendered) + Math.floor(remainingTokens / remainingCount),
18712
+ maxTokens: estimateTokenCount(rendered) + Math.floor(remainingTokens / remainingCount),
18658
18713
  maxCharacters: rendered.length + 1 + Math.floor(remainingCharacters / remainingCount)
18659
18714
  };
18660
18715
  const preview = formatPreview(previews[index].path, previews[index].value, previewLimits, rendered);
@@ -18736,11 +18791,11 @@ function resolveToolResultValue(part, invocationResult) {
18736
18791
  }
18737
18792
  function truncateStringByTokens(text, maxTokens) {
18738
18793
  if (!text || maxTokens <= 0) return "";
18739
- const totalTokens = (0, tokenx.estimateTokenCount)(text);
18794
+ const totalTokens = estimateTokenCount(text);
18740
18795
  if (totalTokens <= maxTokens) return text;
18741
18796
  const buildCandidate = (sliceEnd) => {
18742
18797
  const visible = safeSlice(text, sliceEnd);
18743
- return `${visible}\n... [truncated ~${totalTokens - (0, tokenx.estimateTokenCount)(visible)} tokens]`;
18798
+ return `${visible}\n... [truncated ~${totalTokens - estimateTokenCount(visible)} tokens]`;
18744
18799
  };
18745
18800
  let low = 0;
18746
18801
  let high = text.length;
@@ -18748,7 +18803,7 @@ function truncateStringByTokens(text, maxTokens) {
18748
18803
  while (low <= high) {
18749
18804
  const mid = Math.floor((low + high) / 2);
18750
18805
  const candidate = buildCandidate(mid);
18751
- if ((0, tokenx.estimateTokenCount)(candidate) <= maxTokens) {
18806
+ if (estimateTokenCount(candidate) <= maxTokens) {
18752
18807
  best = candidate;
18753
18808
  low = mid + 1;
18754
18809
  } else high = mid - 1;
@@ -21616,7 +21671,7 @@ var TokenCounter = class TokenCounter {
21616
21671
  */
21617
21672
  countString(text) {
21618
21673
  if (!text) return 0;
21619
- return (0, tokenx.estimateTokenCount)(text);
21674
+ return estimateTokenCount(text);
21620
21675
  }
21621
21676
  readOrPersistPartEstimate(part, kind, payload) {
21622
21677
  const key = buildEstimateKey(kind, payload);
@@ -22544,7 +22599,7 @@ function formatTimestamp(date) {
22544
22599
  return date.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z");
22545
22600
  }
22546
22601
  function truncateByTokens(text, maxTokens, hint) {
22547
- if ((0, tokenx.estimateTokenCount)(text) <= maxTokens) return {
22602
+ if (estimateTokenCount(text) <= maxTokens) return {
22548
22603
  text,
22549
22604
  wasTruncated: false
22550
22605
  };
@@ -22558,7 +22613,7 @@ function chunkTextByTokens(text, maxTokens, charOffset = 0) {
22558
22613
  const startCode = text.charCodeAt(startOffset);
22559
22614
  if (startCode >= 56320 && startCode <= 57343) startOffset += 1;
22560
22615
  const remaining = text.slice(startOffset);
22561
- if (!remaining || (0, tokenx.estimateTokenCount)(remaining) <= maxTokens) return {
22616
+ if (!remaining || estimateTokenCount(remaining) <= maxTokens) return {
22562
22617
  text: remaining,
22563
22618
  charOffset: startOffset,
22564
22619
  truncated: false
@@ -22569,7 +22624,7 @@ function chunkTextByTokens(text, maxTokens, charOffset = 0) {
22569
22624
  while (low <= high) {
22570
22625
  const mid = Math.floor((low + high) / 2);
22571
22626
  const candidate = safeSlice(remaining, mid);
22572
- const candidateTokens = (0, tokenx.estimateTokenCount)(candidate);
22627
+ const candidateTokens = estimateTokenCount(candidate);
22573
22628
  if (candidate && candidateTokens <= maxTokens) {
22574
22629
  best = candidate;
22575
22630
  low = mid + 1;
@@ -22745,7 +22800,7 @@ function expandPriority(part) {
22745
22800
  }
22746
22801
  function renderFormattedParts(parts, timestamps, options) {
22747
22802
  const text = buildRenderedText(parts, timestamps);
22748
- let totalTokens = (0, tokenx.estimateTokenCount)(text);
22803
+ let totalTokens = estimateTokenCount(text);
22749
22804
  if (totalTokens > options.maxTokens) return {
22750
22805
  text: truncateStringByTokens(text, options.maxTokens),
22751
22806
  truncated: true,
@@ -22764,8 +22819,8 @@ function renderFormattedParts(parts, timestamps, options) {
22764
22819
  for (const { part, index } of truncatedIndices) {
22765
22820
  if (remaining <= 0) break;
22766
22821
  const maxTokens = expandLimit(part);
22767
- const fullTokens = (0, tokenx.estimateTokenCount)(part.fullText);
22768
- const currentTokens = (0, tokenx.estimateTokenCount)(part.text);
22822
+ const fullTokens = estimateTokenCount(part.fullText);
22823
+ const currentTokens = estimateTokenCount(part.text);
22769
22824
  const targetTokens = Math.min(fullTokens, maxTokens);
22770
22825
  const delta = targetTokens - currentTokens;
22771
22826
  if (delta <= 0) continue;
@@ -22779,7 +22834,7 @@ function renderFormattedParts(parts, timestamps, options) {
22779
22834
  const expandedLimit = Math.min(currentTokens + remaining, maxTokens);
22780
22835
  const hint = `recall cursor="${part.messageId}" partIndex=${part.partIndex} detail="high"`;
22781
22836
  const { text: expanded } = truncateByTokens(part.fullText, expandedLimit, hint);
22782
- const expandedDelta = (0, tokenx.estimateTokenCount)(expanded) - currentTokens;
22837
+ const expandedDelta = estimateTokenCount(expanded) - currentTokens;
22783
22838
  parts[index] = {
22784
22839
  ...part,
22785
22840
  text: expanded
@@ -22788,7 +22843,7 @@ function renderFormattedParts(parts, timestamps, options) {
22788
22843
  }
22789
22844
  }
22790
22845
  const expanded = buildRenderedText(parts, timestamps);
22791
- const expandedTokens = (0, tokenx.estimateTokenCount)(expanded);
22846
+ const expandedTokens = estimateTokenCount(expanded);
22792
22847
  if (expandedTokens <= options.maxTokens) return {
22793
22848
  text: expanded,
22794
22849
  truncated: false,
@@ -22800,13 +22855,13 @@ function renderFormattedParts(parts, timestamps, options) {
22800
22855
  tokenOffset: expandedTokens - options.maxTokens
22801
22856
  };
22802
22857
  }
22803
- async function recallPart({ memory, threadId, resourceId, cursor, partIndex, charOffset, threadScope, maxTokens = DEFAULT_MAX_RESULT_TOKENS }) {
22858
+ async function recallPart({ memory, threadId, resourceId, cursor, partIndex, charOffset, threadScope, retrievalScope = "thread", maxTokens = DEFAULT_MAX_RESULT_TOKENS }) {
22804
22859
  if (!memory || typeof memory.getMemoryStore !== "function") throw new Error("Memory instance is required for recall");
22805
22860
  if (!threadId) throw new Error("Thread ID is required for recall");
22806
22861
  const resolved = await resolveCursorMessage(memory, cursor, {
22807
22862
  resourceId,
22808
22863
  threadScope,
22809
- enforceThreadScope: false
22864
+ enforceThreadScope: retrievalScope !== "resource"
22810
22865
  });
22811
22866
  if ("hint" in resolved) throw new Error(resolved.hint);
22812
22867
  const allParts = formatMessageParts(resolved, "high");
@@ -22817,7 +22872,7 @@ async function recallPart({ memory, threadId, resourceId, cursor, partIndex, cha
22817
22872
  if (partIndex > Math.max(...allParts.map((p) => p.partIndex))) {
22818
22873
  const nextMessage = await getNextVisibleMessage({
22819
22874
  memory,
22820
- threadId,
22875
+ threadId: resolved.threadId ?? threadId,
22821
22876
  resourceId,
22822
22877
  after: resolved.createdAt
22823
22878
  });
@@ -22856,7 +22911,7 @@ async function recallPart({ memory, threadId, resourceId, cursor, partIndex, cha
22856
22911
  note
22857
22912
  };
22858
22913
  }
22859
- async function recallMessages({ memory, threadId, resourceId, cursor, page = 1, limit = 20, detail = "low", partType, toolName, threadScope, maxTokens = DEFAULT_MAX_RESULT_TOKENS }) {
22914
+ async function recallMessages({ memory, threadId, resourceId, cursor, page = 1, limit = 20, detail = "low", partType, toolName, threadScope, retrievalScope = "thread", maxTokens = DEFAULT_MAX_RESULT_TOKENS }) {
22860
22915
  if (!memory) throw new Error("Memory instance is required for recall");
22861
22916
  if (!threadId) throw new Error("Thread ID is required for recall");
22862
22917
  if (typeof memory.getMemoryStore !== "function") throw new Error("recall requires a Memory instance with storage access");
@@ -22867,7 +22922,7 @@ async function recallMessages({ memory, threadId, resourceId, cursor, page = 1,
22867
22922
  const resolved = await resolveCursorMessage(memory, cursor, {
22868
22923
  resourceId,
22869
22924
  threadScope,
22870
- enforceThreadScope: false
22925
+ enforceThreadScope: retrievalScope === "thread"
22871
22926
  });
22872
22927
  if ("hint" in resolved) return {
22873
22928
  messages: resolved.hint,
@@ -22884,7 +22939,7 @@ async function recallMessages({ memory, threadId, resourceId, cursor, page = 1,
22884
22939
  const anchor = resolved;
22885
22940
  const crossThreadId = anchor.threadId && anchor.threadId !== threadId ? anchor.threadId : void 0;
22886
22941
  if (crossThreadId && threadScope) return {
22887
- messages: `Cursor does not belong to the active thread. Expected thread "${threadId}" but cursor "${cursor}" belongs to "${anchor.threadId}". Pass threadId="${anchor.threadId}" to browse that thread, or omit threadId and use this cursor directly in resource scope.`,
22942
+ messages: `Cursor does not belong to the active thread. Expected thread "${threadId}" but cursor "${cursor}" belongs to "${anchor.threadId}". Pass threadId="${anchor.threadId}" to browse that thread.`,
22888
22943
  count: 0,
22889
22944
  cursor,
22890
22945
  page: normalizedPage,
@@ -23043,7 +23098,8 @@ async function recallThreadFromStart({ memory, threadId, resourceId, page = 1, l
23043
23098
  };
23044
23099
  }
23045
23100
  const recallTool = (_memoryConfig, options) => {
23046
- const isResourceScope = (options?.retrievalScope ?? "thread") === "resource";
23101
+ const retrievalScope = options?.retrievalScope ?? "thread";
23102
+ const isResourceScope = retrievalScope === "resource";
23047
23103
  const searchEnabled = options?.searchEnabled ?? true;
23048
23104
  const description = isResourceScope ? `Browse conversation history. Use mode="threads" to list all threads for the current user. Use mode="messages" (default) to browse messages in the current thread or pass threadId to browse another thread in the active resource. When mode="messages" has no cursor or threadId, it defaults to the current thread and says so at the top of the result. If you pass only a cursor, it must belong to the current thread.${searchEnabled ? " Use mode=\"search\" to find messages by content across all threads." : ""}` : `Browse conversation history in the current thread. Use mode="messages" (default) to page through messages near a cursor.${searchEnabled ? " Use mode=\"search\" to find messages by content in this thread." : ""} Use mode="threads" to get the current thread's ID and title.`;
23049
23105
  const modeEnum = searchEnabled ? [
@@ -23254,23 +23310,25 @@ const recallTool = (_memoryConfig, options) => {
23254
23310
  if (partIndex !== void 0 && partIndex !== null) return recallPart({
23255
23311
  memory,
23256
23312
  threadId: targetThreadId,
23257
- resourceId: isResourceScope ? resourceId : void 0,
23313
+ resourceId,
23258
23314
  cursor,
23259
23315
  partIndex,
23260
23316
  charOffset,
23261
- threadScope
23317
+ threadScope,
23318
+ retrievalScope
23262
23319
  });
23263
23320
  return recallMessages({
23264
23321
  memory,
23265
23322
  threadId: targetThreadId,
23266
- resourceId: isResourceScope ? resourceId : void 0,
23323
+ resourceId,
23267
23324
  cursor,
23268
23325
  page,
23269
23326
  limit,
23270
23327
  detail: detail ?? "low",
23271
23328
  partType,
23272
23329
  toolName,
23273
- threadScope
23330
+ threadScope,
23331
+ retrievalScope
23274
23332
  });
23275
23333
  }
23276
23334
  });
@@ -24376,7 +24434,8 @@ function stripThreadTags(observations) {
24376
24434
  }
24377
24435
  //#endregion
24378
24436
  //#region src/processors/observational-memory/observation-groups.ts
24379
- const OBSERVATION_GROUP_PATTERN = /<observation-group\s([^>]*)>([\s\S]*?)<\/observation-group>/g;
24437
+ const OBSERVATION_GROUP_OPEN = "<observation-group";
24438
+ const OBSERVATION_GROUP_CLOSE = "</observation-group>";
24380
24439
  const ATTRIBUTE_PATTERN = /([\w][\w-]*)="([^"]*)"/g;
24381
24440
  const REFLECTION_GROUP_SPLIT_PATTERN = /^##\s+Group\s+/m;
24382
24441
  function parseObservationGroupAttributes(attributeString) {
@@ -24387,6 +24446,81 @@ function parseObservationGroupAttributes(attributeString) {
24387
24446
  }
24388
24447
  return attributes;
24389
24448
  }
24449
+ function isLineStart(observations, index) {
24450
+ return index === 0 || observations[index - 1] === "\n";
24451
+ }
24452
+ function scanObservationGroupTags(observations) {
24453
+ const tags = [];
24454
+ const incompleteOpenings = [];
24455
+ let cursor = 0;
24456
+ let closeFloor = 0;
24457
+ while (cursor < observations.length) {
24458
+ const start = observations.indexOf(OBSERVATION_GROUP_OPEN, cursor);
24459
+ if (start === -1) break;
24460
+ const attributesStart = start + 18;
24461
+ if (!/\s/.test(observations[attributesStart] ?? "")) {
24462
+ cursor = attributesStart;
24463
+ continue;
24464
+ }
24465
+ const openEnd = observations.indexOf(">", attributesStart);
24466
+ if (openEnd === -1) break;
24467
+ const closeStart = observations.indexOf(OBSERVATION_GROUP_CLOSE, Math.max(openEnd + 1, closeFloor));
24468
+ if (closeStart === -1) {
24469
+ if (isLineStart(observations, start)) incompleteOpenings.push({
24470
+ start,
24471
+ end: openEnd + 1
24472
+ });
24473
+ closeFloor = observations.length;
24474
+ cursor = openEnd + 1;
24475
+ continue;
24476
+ }
24477
+ const nestedStart = observations.indexOf(OBSERVATION_GROUP_OPEN, openEnd + 1);
24478
+ if (nestedStart !== -1 && nestedStart < closeStart && isLineStart(observations, nestedStart)) {
24479
+ incompleteOpenings.push({
24480
+ start,
24481
+ end: openEnd + 1
24482
+ });
24483
+ closeFloor = closeStart;
24484
+ cursor = nestedStart;
24485
+ continue;
24486
+ }
24487
+ tags.push({
24488
+ start,
24489
+ end: closeStart + 20,
24490
+ attributeString: observations.slice(attributesStart, openEnd),
24491
+ content: observations.slice(openEnd + 1, closeStart)
24492
+ });
24493
+ closeFloor = closeStart;
24494
+ cursor = closeStart + 20;
24495
+ }
24496
+ return {
24497
+ tags,
24498
+ incompleteOpenings
24499
+ };
24500
+ }
24501
+ function replaceObservationGroupTags(observations, replace) {
24502
+ const { tags, incompleteOpenings } = scanObservationGroupTags(observations);
24503
+ const parts = [];
24504
+ let cursor = 0;
24505
+ let openingIndex = 0;
24506
+ const skipIncompleteOpeningsBefore = (limit) => {
24507
+ while (openingIndex < incompleteOpenings.length && incompleteOpenings[openingIndex].start < limit) {
24508
+ const opening = incompleteOpenings[openingIndex];
24509
+ openingIndex++;
24510
+ if (opening.start < cursor) continue;
24511
+ parts.push(observations.slice(cursor, opening.start));
24512
+ cursor = opening.end;
24513
+ }
24514
+ };
24515
+ for (const tag of tags) {
24516
+ skipIncompleteOpeningsBefore(tag.start);
24517
+ parts.push(observations.slice(cursor, tag.start), replace(tag));
24518
+ cursor = tag.end;
24519
+ }
24520
+ skipIncompleteOpeningsBefore(Number.POSITIVE_INFINITY);
24521
+ parts.push(observations.slice(cursor));
24522
+ return parts.join("");
24523
+ }
24390
24524
  function parseReflectionObservationGroupSections(content) {
24391
24525
  const normalizedContent = content.trim();
24392
24526
  if (!normalizedContent || !REFLECTION_GROUP_SPLIT_PATTERN.test(normalizedContent)) return [];
@@ -24411,9 +24545,8 @@ function wrapInObservationGroup(observations, range, id = generateAnchorId(), _s
24411
24545
  function parseObservationGroups(observations) {
24412
24546
  if (!observations) return [];
24413
24547
  const groups = [];
24414
- let match;
24415
- while ((match = OBSERVATION_GROUP_PATTERN.exec(observations)) !== null) {
24416
- const attributes = parseObservationGroupAttributes(match[1] ?? "");
24548
+ for (const tag of scanObservationGroupTags(observations).tags) {
24549
+ const attributes = parseObservationGroupAttributes(tag.attributeString);
24417
24550
  const id = attributes.id;
24418
24551
  const range = attributes.range;
24419
24552
  if (!id || !range) continue;
@@ -24421,54 +24554,96 @@ function parseObservationGroups(observations) {
24421
24554
  id,
24422
24555
  range,
24423
24556
  kind: attributes.kind,
24424
- content: match[2].trim()
24557
+ content: tag.content.trim()
24425
24558
  });
24426
24559
  }
24427
24560
  return groups;
24428
24561
  }
24429
24562
  function stripObservationGroups(observations) {
24430
24563
  if (!observations) return observations;
24431
- return observations.replace(OBSERVATION_GROUP_PATTERN, (_match, _attributes, content) => content.trim()).replace(/\n{3,}/g, "\n\n").trim();
24564
+ return replaceObservationGroupTags(observations, (tag) => tag.content.trim()).replace(/\n{3,}/g, "\n\n").trim();
24432
24565
  }
24433
24566
  function getRangeSegments(range) {
24434
24567
  return range.split(",").map((segment) => segment.trim()).filter(Boolean);
24435
24568
  }
24569
+ function getRangeEndpoints(segment) {
24570
+ const parts = segment.split(":").map((part) => part.trim());
24571
+ if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
24572
+ return {
24573
+ start: parts[0],
24574
+ end: parts[1]
24575
+ };
24576
+ }
24577
+ /**
24578
+ * `buildMessageRange` produces opaque UUID endpoints, so those segments cannot be ordered by value.
24579
+ * Spanning from the first start to the last end keeps reflected metadata bounded — the full segment
24580
+ * list is deliberately not preserved (#14791). Segments that are not `start:end` pairs are left as
24581
+ * they are rather than being joined into a fabricated span.
24582
+ */
24583
+ function spanRangeSegments(segments) {
24584
+ const endpoints = segments.map(getRangeEndpoints);
24585
+ if (endpoints.some((endpoint) => endpoint === null)) return segments.join(",");
24586
+ const first = endpoints[0];
24587
+ const last = endpoints.at(-1);
24588
+ return `${first.start}:${last.end}`;
24589
+ }
24436
24590
  function combineObservationGroupRanges(groups) {
24437
- const segments = groups.flatMap((group) => getRangeSegments(group.range));
24591
+ const segments = Array.from(new Set(groups.flatMap((group) => getRangeSegments(group.range))));
24438
24592
  if (segments.length === 0) return "";
24439
- const firstSegment = segments[0];
24440
- const lastSegment = segments[segments.length - 1];
24441
- const firstStart = firstSegment?.split(":")[0]?.trim();
24442
- const lastEnd = lastSegment?.split(":").at(-1)?.trim();
24443
- if (firstStart && lastEnd) return `${firstStart}:${lastEnd}`;
24444
- return Array.from(new Set(segments)).join(",");
24593
+ const numericEndpoints = [];
24594
+ if (!segments.every((segment) => {
24595
+ const endpoints = getRangeEndpoints(segment);
24596
+ if (!endpoints || !/^\d+$/.test(endpoints.start) || !/^\d+$/.test(endpoints.end)) return false;
24597
+ const start = {
24598
+ label: endpoints.start,
24599
+ value: Number(endpoints.start)
24600
+ };
24601
+ const end = {
24602
+ label: endpoints.end,
24603
+ value: Number(endpoints.end)
24604
+ };
24605
+ if (!Number.isSafeInteger(start.value) || !Number.isSafeInteger(end.value)) return false;
24606
+ numericEndpoints.push(start.value <= end.value ? {
24607
+ start,
24608
+ end
24609
+ } : {
24610
+ start: end,
24611
+ end: start
24612
+ });
24613
+ return true;
24614
+ })) return spanRangeSegments(segments);
24615
+ const first = numericEndpoints.reduce((lowest, pair) => pair.start.value < lowest.start.value ? pair : lowest);
24616
+ const last = numericEndpoints.reduce((highest, pair) => pair.end.value > highest.end.value ? pair : highest);
24617
+ return `${first.start.label}:${last.end.label}`;
24445
24618
  }
24446
24619
  function renderObservationGroupsForReflection(observations) {
24447
- const groups = parseObservationGroups(observations);
24448
- if (groups.length === 0) return null;
24449
- const groupsByContent = new Map(groups.map((g) => [g.content.trim(), g]));
24450
- return observations.replace(OBSERVATION_GROUP_PATTERN, (_match, _attrs, content) => {
24451
- const group = groupsByContent.get(content.trim());
24452
- if (!group) return content.trim();
24453
- return `## Group \`${group.id}\`\n_range: \`${group.range}\`_\n\n${group.content}`;
24620
+ if (parseObservationGroups(observations).length === 0) return null;
24621
+ return replaceObservationGroupTags(observations, (tag) => {
24622
+ const attributes = parseObservationGroupAttributes(tag.attributeString);
24623
+ if (!attributes.id || !attributes.range) return tag.content.trim();
24624
+ return `## Group \`${attributes.id}\`\n_range: \`${attributes.range}\`_\n\n${tag.content.trim()}`;
24454
24625
  }).replace(/\n{3,}/g, "\n\n").trim();
24455
24626
  }
24456
24627
  function getCanonicalGroupId(sectionHeading, fallbackIndex) {
24457
24628
  return sectionHeading.match(/`([^`]+)`/)?.[1]?.trim() || `derived-group-${fallbackIndex + 1}`;
24458
24629
  }
24630
+ function getContentLines(content) {
24631
+ return content.split("\n").map((line) => line.trim()).filter(Boolean);
24632
+ }
24459
24633
  function deriveObservationGroupProvenance(content, groups) {
24460
24634
  const sections = parseReflectionObservationGroupSections(content);
24461
24635
  if (sections.length === 0 || groups.length === 0) return [];
24462
24636
  return sections.map((section, index) => {
24463
- const bodyLines = new Set(section.body.split("\n").map((line) => line.trim()).filter(Boolean));
24464
- const matchingGroups = groups.filter((group) => {
24465
- return group.content.split("\n").map((line) => line.trim()).filter(Boolean).some((line) => bodyLines.has(line));
24466
- });
24637
+ const canonicalGroupId = getCanonicalGroupId(section.heading, index);
24638
+ const identifiedGroup = groups.find((group) => group.id === canonicalGroupId);
24639
+ const bodyLines = new Set(getContentLines(section.body));
24640
+ const matchingGroups = groups.filter((group) => getContentLines(group.content).some((line) => bodyLines.has(line)));
24467
24641
  const fallbackGroup = groups[Math.min(index, groups.length - 1)];
24468
- const resolvedGroups = matchingGroups.length > 0 ? matchingGroups : fallbackGroup ? [fallbackGroup] : [];
24642
+ const identifiedLines = new Set(getContentLines(identifiedGroup?.content ?? ""));
24643
+ const contributingGroups = identifiedGroup ? matchingGroups.filter((group) => group.id !== identifiedGroup.id && getContentLines(group.content).some((line) => bodyLines.has(line) && !identifiedLines.has(line))) : [];
24469
24644
  return {
24470
- id: getCanonicalGroupId(section.heading, index),
24471
- range: combineObservationGroupRanges(resolvedGroups),
24645
+ id: canonicalGroupId,
24646
+ range: combineObservationGroupRanges(identifiedGroup ? groups.filter((group) => group === identifiedGroup || contributingGroups.includes(group)) : matchingGroups.length > 0 ? matchingGroups : fallbackGroup ? [fallbackGroup] : []),
24472
24647
  kind: "reflection",
24473
24648
  content: section.body
24474
24649
  };
@@ -25062,6 +25237,10 @@ var SyncObservationStrategy = class extends ObservationStrategy {
25062
25237
  async persist(processed) {
25063
25238
  const { record, threadId, resourceId, messages } = this.opts;
25064
25239
  const thread = await this.storage.getThreadById({ threadId });
25240
+ if (!await this.storage.getObservationalMemory(record.threadId, record.resourceId)) {
25241
+ omDebug(`[OM:sync-obs] skipping persist for thread ${threadId}: observational memory record is gone`);
25242
+ return;
25243
+ }
25065
25244
  let threadUpdateMarker;
25066
25245
  if (thread) {
25067
25246
  const oldTitle = thread.title?.trim();
@@ -25243,6 +25422,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
25243
25422
  async persist(processed) {
25244
25423
  if (!processed.observations) return;
25245
25424
  const { record, threadId, resourceId, messages } = this.opts;
25425
+ if (!await this.storage.getObservationalMemory(record.threadId, record.resourceId)) {
25426
+ omDebug(`[OM:asyncBuffer] skipping persist for thread ${threadId}: observational memory record is gone`);
25427
+ return;
25428
+ }
25246
25429
  const messageTokens = await this.tokenCounter.countMessagesAsync(messages);
25247
25430
  await withRetry(() => this.storage.updateBufferedObservations({
25248
25431
  id: record.id,
@@ -26236,6 +26419,10 @@ var ObservationTurn = class {
26236
26419
  if (!this._context) throw new Error("Turn not started — call start() first");
26237
26420
  return this._context;
26238
26421
  }
26422
+ /** Whether the turn has been ended and can no longer accept steps. */
26423
+ get ended() {
26424
+ return this._ended;
26425
+ }
26239
26426
  /** The current step, if one exists. */
26240
26427
  get currentStep() {
26241
26428
  return this._currentStep;
@@ -28063,11 +28250,19 @@ var ObservationalMemory = class ObservationalMemory {
28063
28250
  const messageTokens = config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens;
28064
28251
  const observationTokens = config.reflection?.observationTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.observationTokens;
28065
28252
  const isSharedBudget = config.shareTokenBudget ?? false;
28066
- const isDefaultModelSelection = (model) => model === void 0 || model === "default" || model instanceof ModelByInputTokens;
28253
+ const supportsDefaultTemperature = (model) => {
28254
+ if (model instanceof ModelByInputTokens || typeof model === "function") return false;
28255
+ if (typeof model === "string") return (0, _mastra_core_llm.modelSupportsTemperature)(model) === true;
28256
+ if (model && typeof model === "object" && "provider" in model && "modelId" in model && typeof model.provider === "string" && typeof model.modelId === "string") return (0, _mastra_core_llm.modelSupportsTemperature)(`${model.provider.split(".")[0]}/${model.modelId}`) === true;
28257
+ return false;
28258
+ };
28259
+ const usesDefaultOutputTokenBudget = (model) => model === void 0 || model === "default" || model instanceof ModelByInputTokens;
28067
28260
  const observationSelectedModel = topLevelModel ?? observationConfigModel ?? reflectionConfigModel;
28068
28261
  const reflectionSelectedModel = topLevelModel ?? reflectionConfigModel ?? observationConfigModel;
28069
- const observationDefaultMaxOutputTokens = config.observation?.modelSettings?.maxOutputTokens ?? (isDefaultModelSelection(observationSelectedModel) ? OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings.maxOutputTokens : void 0);
28070
- const reflectionDefaultMaxOutputTokens = config.reflection?.modelSettings?.maxOutputTokens ?? (isDefaultModelSelection(reflectionSelectedModel) ? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.modelSettings.maxOutputTokens : void 0);
28262
+ const observationTemperature = config.observation?.modelSettings?.temperature ?? (supportsDefaultTemperature(observationModel) ? OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings.temperature : void 0);
28263
+ const observationDefaultMaxOutputTokens = config.observation?.modelSettings?.maxOutputTokens ?? (usesDefaultOutputTokenBudget(observationSelectedModel) ? OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings.maxOutputTokens : void 0);
28264
+ const reflectionTemperature = config.reflection?.modelSettings?.temperature ?? (supportsDefaultTemperature(reflectionModel) ? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.modelSettings.temperature : void 0);
28265
+ const reflectionDefaultMaxOutputTokens = config.reflection?.modelSettings?.maxOutputTokens ?? (usesDefaultOutputTokenBudget(reflectionSelectedModel) ? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.modelSettings.maxOutputTokens : void 0);
28071
28266
  const totalBudget = messageTokens + observationTokens;
28072
28267
  const userExplicitlyConfiguredAsync = config.observation?.bufferTokens !== void 0 || config.observation?.bufferActivation !== void 0 || config.reflection?.bufferActivation !== void 0;
28073
28268
  const asyncBufferingDisabled = config.observation?.bufferTokens === false || config.scope === "resource" && !userExplicitlyConfiguredAsync;
@@ -28083,7 +28278,7 @@ var ObservationalMemory = class ObservationalMemory {
28083
28278
  } : messageTokens,
28084
28279
  shareTokenBudget: isSharedBudget,
28085
28280
  modelSettings: {
28086
- temperature: config.observation?.modelSettings?.temperature ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings.temperature,
28281
+ ...observationTemperature !== void 0 ? { temperature: observationTemperature } : {},
28087
28282
  ...observationDefaultMaxOutputTokens !== void 0 ? { maxOutputTokens: observationDefaultMaxOutputTokens } : {}
28088
28283
  },
28089
28284
  providerOptions: config.observation?.providerOptions ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.providerOptions,
@@ -28109,7 +28304,7 @@ var ObservationalMemory = class ObservationalMemory {
28109
28304
  observationTokens,
28110
28305
  shareTokenBudget: isSharedBudget,
28111
28306
  modelSettings: {
28112
- temperature: config.reflection?.modelSettings?.temperature ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.modelSettings.temperature,
28307
+ ...reflectionTemperature !== void 0 ? { temperature: reflectionTemperature } : {},
28113
28308
  ...reflectionDefaultMaxOutputTokens !== void 0 ? { maxOutputTokens: reflectionDefaultMaxOutputTokens } : {}
28114
28309
  },
28115
28310
  providerOptions: config.reflection?.providerOptions ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.providerOptions,
@@ -29964,9 +30159,11 @@ ${formattedMessages}
29964
30159
  let lifecycleError;
29965
30160
  let observationStarted = false;
29966
30161
  let generationBefore = -1;
30162
+ let recordInsideLock;
29967
30163
  try {
29968
30164
  await this.withLock(lockKey, async () => {
29969
30165
  const freshRecord = await this.getOrCreateRecord(threadId, resourceId);
30166
+ recordInsideLock = freshRecord;
29970
30167
  generationBefore = freshRecord.generationCount;
29971
30168
  const unobservedMessages = messages ? this.getUnobservedMessages(messages, freshRecord) : await this.loadMessagesFromStorage(threadId, resourceId, freshRecord.lastObservedAt ? new Date(freshRecord.lastObservedAt) : void 0);
29972
30169
  if (!this.meetsObservationThreshold({
@@ -30009,7 +30206,7 @@ ${formattedMessages}
30009
30206
  omDebug(`[OM:hooks] onObservationEnd hook failed after cycle failure: ${endHookError instanceof Error ? endHookError.message : String(endHookError)}`);
30010
30207
  }
30011
30208
  if (lifecycleError !== void 0) throw lifecycleError;
30012
- const record = await this.getOrCreateRecord(threadId, resourceId);
30209
+ const record = await this.getRecord(threadId, resourceId) ?? recordInsideLock;
30013
30210
  const reflected = record.generationCount > generationBefore && generationBefore >= 0;
30014
30211
  return {
30015
30212
  observed,
@@ -30490,6 +30687,10 @@ var ObservationalMemoryProcessor = class {
30490
30687
  if (this.turn === activeTurn) this.turn = void 0;
30491
30688
  state.__omTurn = void 0;
30492
30689
  }
30690
+ if (activeTurn?.ended || this.turn?.ended) {
30691
+ if (this.turn?.ended) this.turn = void 0;
30692
+ state.__omTurn = void 0;
30693
+ }
30493
30694
  if (!this.turn || !state.__omTurn) {
30494
30695
  if (this.turn && !state.__omTurn) await this.turn.end().catch(() => {});
30495
30696
  this.turn = this.engine.beginTurn({
@@ -30590,11 +30791,11 @@ var ObservationalMemoryProcessor = class {
30590
30791
  return this.engine.getTokenCounter().runWithModelContext(state.__omActorModelContext, async () => {
30591
30792
  if ((0, _mastra_core_memory.parseMemoryRequestContext)(requestContext)?.memoryConfig?.readOnly) return messageList;
30592
30793
  const turn = asLiveTurn(state.__omTurn) ?? this.turn;
30593
- if (turn) {
30594
- await turn.end();
30595
- this.turn = void 0;
30596
- state.__omTurn = void 0;
30597
- } else {
30794
+ const liveTurn = turn && !turn.ended ? turn : void 0;
30795
+ if (liveTurn) await liveTurn.end();
30796
+ this.turn = void 0;
30797
+ state.__omTurn = void 0;
30798
+ if (!liveTurn) {
30598
30799
  const newOutput = messageList.get.response.db();
30599
30800
  const messagesToSave = [...messageList.get.input.db(), ...newOutput];
30600
30801
  if (messagesToSave.length > 0 && context.threadId) await this.engine.persistMessages(messagesToSave, context.threadId, context.resourceId);
@@ -30804,6 +31005,7 @@ const DEFAULT_MESSAGE_RANGE = {
30804
31005
  };
30805
31006
  const DEFAULT_TOP_K = 4;
30806
31007
  const VECTOR_DELETE_BATCH_SIZE = 100;
31008
+ const OM_DELETE_DRAIN_TIMEOUT_MS = 1e4;
30807
31009
  const DEFAULT_EMBEDDING_CACHE_MAX_SIZE = 1e3;
30808
31010
  /**
30809
31011
  * Gives Mastra agents conversation history, with optional working memory,
@@ -31255,6 +31457,8 @@ var Memory = class Memory extends _mastra_core_memory.MastraMemory {
31255
31457
  await this.deleteStoredThread(memoryStore, threadId, thread?.resourceId);
31256
31458
  }
31257
31459
  async deleteStoredThread(memoryStore, threadId, resourceId) {
31460
+ const engine = this._omEngine ? await this._omEngine : this._omEngineInstance;
31461
+ if (engine && resourceId) await engine.waitForBuffering(threadId, resourceId, OM_DELETE_DRAIN_TIMEOUT_MS);
31258
31462
  await memoryStore.deleteThread({ threadId });
31259
31463
  if (resourceId && memoryStore.supportsObservationalMemory) await memoryStore.clearObservationalMemory(threadId, resourceId);
31260
31464
  if (this.vector) this.trackVectorCleanup(this.deleteThreadVectors(threadId));
@@ -33269,4 +33473,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
33269
33473
  }
33270
33474
  });
33271
33475
 
33272
- //# sourceMappingURL=src-BCvV2aGL.cjs.map
33476
+ //# sourceMappingURL=src-QqTmOY5X.cjs.map