@mastra/memory 0.0.0-studio-deploy-20260404182525 → 0.0.0-studio-cli-20260504022012

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 (113) hide show
  1. package/CHANGELOG.md +461 -3
  2. package/dist/_types/@internal_ai-sdk-v4/dist/index.d.ts +51 -5
  3. package/dist/{chunk-4FMHSWZD.cjs → chunk-6ODYOMHN.cjs} +1279 -492
  4. package/dist/chunk-6ODYOMHN.cjs.map +1 -0
  5. package/dist/{chunk-C7PARRAD.js → chunk-Z5GWA7LB.js} +1280 -493
  6. package/dist/chunk-Z5GWA7LB.js.map +1 -0
  7. package/dist/{constants-HXOCZPB7.cjs → constants-PJXB7ZIS.cjs} +2 -2
  8. package/dist/{constants-HXOCZPB7.cjs.map → constants-PJXB7ZIS.cjs.map} +1 -1
  9. package/dist/{constants-BDOITAO3.js → constants-XEHPYRDJ.js} +2 -2
  10. package/dist/{constants-BDOITAO3.js.map → constants-XEHPYRDJ.js.map} +1 -1
  11. package/dist/docs/SKILL.md +3 -1
  12. package/dist/docs/assets/SOURCE_MAP.json +47 -59
  13. package/dist/docs/references/docs-agents-background-tasks.md +242 -0
  14. package/dist/docs/references/docs-agents-supervisor-agents.md +65 -0
  15. package/dist/docs/references/docs-memory-memory-processors.md +3 -3
  16. package/dist/docs/references/docs-memory-message-history.md +8 -2
  17. package/dist/docs/references/docs-memory-observational-memory.md +65 -8
  18. package/dist/docs/references/docs-memory-storage.md +1 -2
  19. package/dist/docs/references/docs-memory-working-memory.md +2 -2
  20. package/dist/docs/references/reference-memory-clone-utilities.md +4 -2
  21. package/dist/docs/references/reference-memory-cloneThread.md +4 -2
  22. package/dist/docs/references/reference-memory-observational-memory.md +4 -0
  23. package/dist/docs/references/reference-storage-redis.md +266 -0
  24. package/dist/docs/references/reference-vectors-mongodb.md +0 -2
  25. package/dist/index.cjs +1168 -553
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.ts +35 -4
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1158 -535
  30. package/dist/index.js.map +1 -1
  31. package/dist/{observational-memory-5YDQLKHE.cjs → observational-memory-OVSEK47Z.cjs} +26 -26
  32. package/dist/{observational-memory-5YDQLKHE.cjs.map → observational-memory-OVSEK47Z.cjs.map} +1 -1
  33. package/dist/{observational-memory-B7AUSTEY.js → observational-memory-UMDC4VC2.js} +3 -3
  34. package/dist/{observational-memory-B7AUSTEY.js.map → observational-memory-UMDC4VC2.js.map} +1 -1
  35. package/dist/processors/index.cjs +24 -24
  36. package/dist/processors/index.js +1 -1
  37. package/dist/processors/observational-memory/date-utils.d.ts +6 -0
  38. package/dist/processors/observational-memory/date-utils.d.ts.map +1 -1
  39. package/dist/processors/observational-memory/index.d.ts +1 -1
  40. package/dist/processors/observational-memory/index.d.ts.map +1 -1
  41. package/dist/processors/observational-memory/markers.d.ts +5 -0
  42. package/dist/processors/observational-memory/markers.d.ts.map +1 -1
  43. package/dist/processors/observational-memory/message-utils.d.ts.map +1 -1
  44. package/dist/processors/observational-memory/model-context.d.ts +10 -0
  45. package/dist/processors/observational-memory/model-context.d.ts.map +1 -0
  46. package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
  47. package/dist/processors/observational-memory/observation-strategies/base.d.ts +3 -3
  48. package/dist/processors/observational-memory/observation-strategies/base.d.ts.map +1 -1
  49. package/dist/processors/observational-memory/observation-strategies/types.d.ts +9 -0
  50. package/dist/processors/observational-memory/observation-strategies/types.d.ts.map +1 -1
  51. package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
  52. package/dist/processors/observational-memory/observation-turn/turn.d.ts +6 -2
  53. package/dist/processors/observational-memory/observation-turn/turn.d.ts.map +1 -1
  54. package/dist/processors/observational-memory/observation-turn/types.d.ts +1 -0
  55. package/dist/processors/observational-memory/observation-turn/types.d.ts.map +1 -1
  56. package/dist/processors/observational-memory/observational-memory.d.ts +54 -3
  57. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  58. package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
  59. package/dist/processors/observational-memory/processor.d.ts +5 -1
  60. package/dist/processors/observational-memory/processor.d.ts.map +1 -1
  61. package/dist/processors/observational-memory/reflector-runner.d.ts +14 -2
  62. package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
  63. package/dist/processors/observational-memory/repro-capture.d.ts +8 -0
  64. package/dist/processors/observational-memory/repro-capture.d.ts.map +1 -1
  65. package/dist/processors/observational-memory/string-utils.d.ts +13 -0
  66. package/dist/processors/observational-memory/string-utils.d.ts.map +1 -0
  67. package/dist/processors/observational-memory/temporal-markers.d.ts +4 -0
  68. package/dist/processors/observational-memory/temporal-markers.d.ts.map +1 -0
  69. package/dist/processors/observational-memory/tool-result-helpers.d.ts.map +1 -1
  70. package/dist/processors/observational-memory/types.d.ts +56 -2
  71. package/dist/processors/observational-memory/types.d.ts.map +1 -1
  72. package/dist/tools/om-tools.d.ts +14 -2
  73. package/dist/tools/om-tools.d.ts.map +1 -1
  74. package/dist/tools/working-memory.d.ts.map +1 -1
  75. package/package.json +15 -15
  76. package/dist/chunk-3PUO6DLX.js +0 -439
  77. package/dist/chunk-3PUO6DLX.js.map +0 -1
  78. package/dist/chunk-4FMHSWZD.cjs.map +0 -1
  79. package/dist/chunk-C7PARRAD.js.map +0 -1
  80. package/dist/chunk-CK4U3AYR.cjs +0 -84
  81. package/dist/chunk-CK4U3AYR.cjs.map +0 -1
  82. package/dist/chunk-DAJJSFVA.cjs +0 -441
  83. package/dist/chunk-DAJJSFVA.cjs.map +0 -1
  84. package/dist/chunk-DGUM43GV.js +0 -10
  85. package/dist/chunk-DGUM43GV.js.map +0 -1
  86. package/dist/chunk-EQ4M72KU.js +0 -439
  87. package/dist/chunk-EQ4M72KU.js.map +0 -1
  88. package/dist/chunk-IDRQZVB4.cjs +0 -84
  89. package/dist/chunk-IDRQZVB4.cjs.map +0 -1
  90. package/dist/chunk-JEQ2X3Z6.cjs +0 -12
  91. package/dist/chunk-JEQ2X3Z6.cjs.map +0 -1
  92. package/dist/chunk-RC6RZVYE.js +0 -79
  93. package/dist/chunk-RC6RZVYE.js.map +0 -1
  94. package/dist/chunk-SVPZMV27.js +0 -79
  95. package/dist/chunk-SVPZMV27.js.map +0 -1
  96. package/dist/chunk-ZD3BKU5O.cjs +0 -441
  97. package/dist/chunk-ZD3BKU5O.cjs.map +0 -1
  98. package/dist/token-APYSY3BW-2DN6RAUY.js +0 -61
  99. package/dist/token-APYSY3BW-2DN6RAUY.js.map +0 -1
  100. package/dist/token-APYSY3BW-POD4OUWN.cjs +0 -63
  101. package/dist/token-APYSY3BW-POD4OUWN.cjs.map +0 -1
  102. package/dist/token-APYSY3BW-YTVQELJT.js +0 -61
  103. package/dist/token-APYSY3BW-YTVQELJT.js.map +0 -1
  104. package/dist/token-APYSY3BW-ZQ7TMBY7.cjs +0 -63
  105. package/dist/token-APYSY3BW-ZQ7TMBY7.cjs.map +0 -1
  106. package/dist/token-util-RMHT2CPJ-6TGPE335.cjs +0 -10
  107. package/dist/token-util-RMHT2CPJ-6TGPE335.cjs.map +0 -1
  108. package/dist/token-util-RMHT2CPJ-77HHGIQN.cjs +0 -10
  109. package/dist/token-util-RMHT2CPJ-77HHGIQN.cjs.map +0 -1
  110. package/dist/token-util-RMHT2CPJ-RJEA3FAN.js +0 -8
  111. package/dist/token-util-RMHT2CPJ-RJEA3FAN.js.map +0 -1
  112. package/dist/token-util-RMHT2CPJ-WJZ2SYAR.js +0 -8
  113. package/dist/token-util-RMHT2CPJ-WJZ2SYAR.js.map +0 -1
@@ -12,9 +12,9 @@ var crypto$1 = require('crypto');
12
12
  var tokenx = require('tokenx');
13
13
  var agent = require('@mastra/core/agent');
14
14
  var observability = require('@mastra/core/observability');
15
+ var util = require('util');
15
16
  var async_hooks = require('async_hooks');
16
17
  var imageSize = require('image-size');
17
- var util = require('util');
18
18
 
19
19
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
20
20
 
@@ -208,6 +208,19 @@ var BufferingCoordinator = class _BufferingCoordinator {
208
208
  }
209
209
  };
210
210
 
211
+ // src/processors/observational-memory/model-context.ts
212
+ function didProviderChange(actorModel, lastModel) {
213
+ if (actorModel === void 0 || lastModel === void 0) return false;
214
+ const actorHasSlash = actorModel.includes("/");
215
+ const lastHasSlash = lastModel.includes("/");
216
+ if (actorHasSlash && lastHasSlash) {
217
+ return actorModel !== lastModel;
218
+ }
219
+ const actorModelId = actorHasSlash ? actorModel.slice(actorModel.indexOf("/") + 1) : actorModel;
220
+ const lastModelId = lastHasSlash ? lastModel.slice(lastModel.indexOf("/") + 1) : lastModel;
221
+ return actorModelId !== lastModelId;
222
+ }
223
+
211
224
  // src/processors/observational-memory/date-utils.ts
212
225
  function formatRelativeTime(date, currentDate) {
213
226
  const diffMs = currentDate.getTime() - date.getTime();
@@ -372,6 +385,67 @@ ${gap}
372
385
  result += withInlineDates.slice(lastIndex);
373
386
  return result;
374
387
  }
388
+ var MIN_TEMPORAL_GAP_MS = 10 * 60 * 1e3;
389
+ function formatTemporalGap(diffMs) {
390
+ if (diffMs < MIN_TEMPORAL_GAP_MS) return null;
391
+ const minute = 60 * 1e3;
392
+ const hour = 60 * minute;
393
+ const day = 24 * hour;
394
+ const week = 7 * day;
395
+ const month = 30 * day;
396
+ const year = 365 * day;
397
+ const formatUnit = (value, unit) => `${value} ${unit}${value === 1 ? "" : "s"}`;
398
+ if (diffMs < hour) {
399
+ const minutes = Math.max(1, Math.round(diffMs / minute));
400
+ return `${formatUnit(minutes, "minute")} later`;
401
+ }
402
+ const formatTwoUnits = (primaryMs, primaryUnit, secondaryMs, secondaryUnit) => {
403
+ const primary = Math.floor(diffMs / primaryMs);
404
+ const remainder = diffMs - primary * primaryMs;
405
+ const secondary = Math.floor(remainder / secondaryMs);
406
+ const parts = [formatUnit(primary, primaryUnit)];
407
+ if (secondary > 0) {
408
+ parts.push(formatUnit(secondary, secondaryUnit));
409
+ }
410
+ return `${parts.join(" ")} later`;
411
+ };
412
+ if (diffMs < day) {
413
+ return formatTwoUnits(hour, "hour", minute, "minute");
414
+ }
415
+ if (diffMs < week) {
416
+ return formatTwoUnits(day, "day", hour, "hour");
417
+ }
418
+ if (diffMs < month) {
419
+ return formatTwoUnits(week, "week", day, "day");
420
+ }
421
+ if (diffMs < year) {
422
+ return formatTwoUnits(month, "month", week, "week");
423
+ }
424
+ return formatTwoUnits(year, "year", month, "month");
425
+ }
426
+ function formatTemporalTimestamp(date) {
427
+ return date.toLocaleString("en-US", {
428
+ year: "numeric",
429
+ month: "2-digit",
430
+ day: "2-digit",
431
+ hour: "numeric",
432
+ minute: "2-digit",
433
+ hour12: true,
434
+ timeZoneName: "short"
435
+ });
436
+ }
437
+ function getMessagePartTimestamp(msg, position) {
438
+ const timestamps = msg.content?.parts?.map((part) => "createdAt" in part ? part.createdAt : void 0).filter((timestamp) => typeof timestamp === "number");
439
+ if (timestamps && timestamps.length > 0) {
440
+ const index = position === "first" ? 0 : timestamps.length - 1;
441
+ const timestamp = timestamps[index];
442
+ if (timestamp !== void 0) return timestamp;
443
+ }
444
+ return new Date(msg.createdAt).getTime();
445
+ }
446
+ function isTemporalGapMarker(msg) {
447
+ return msg.id.startsWith("__temporal_");
448
+ }
375
449
 
376
450
  // src/processors/observational-memory/markers.ts
377
451
  function createObservationStartMarker(params) {
@@ -491,7 +565,12 @@ function createActivationMarker(params) {
491
565
  threadId: params.threadId,
492
566
  generationCount: params.generationCount,
493
567
  config: params.config,
494
- observations: params.observations
568
+ observations: params.observations,
569
+ triggeredBy: params.triggeredBy,
570
+ lastActivityAt: params.lastActivityAt,
571
+ ttlExpiredMs: params.ttlExpiredMs,
572
+ previousModel: params.previousModel,
573
+ currentModel: params.currentModel
495
574
  }
496
575
  };
497
576
  }
@@ -641,7 +720,7 @@ function sortThreadsByOldestMessage(messagesByThread) {
641
720
  })).sort((a, b) => a.oldestTimestamp - b.oldestTimestamp).map((t) => t.threadId);
642
721
  }
643
722
  function stripThreadTags(observations) {
644
- return observations.replace(/<thread[^>]*>|<\/thread>/gi, "").trim();
723
+ return observations.replace(/<\/?thread\b[^>]{0,1024}>/gi, "").trim();
645
724
  }
646
725
 
647
726
  // src/processors/observational-memory/model-by-input-tokens.ts
@@ -941,6 +1020,8 @@ var ObservationStrategy = class _ObservationStrategy {
941
1020
  this.scope = deps.scope;
942
1021
  this.retrieval = deps.retrieval;
943
1022
  }
1023
+ deps;
1024
+ opts;
944
1025
  storage;
945
1026
  messageHistory;
946
1027
  tokenCounter;
@@ -952,7 +1033,7 @@ var ObservationStrategy = class _ObservationStrategy {
952
1033
  static create;
953
1034
  /**
954
1035
  * Run the full observation lifecycle.
955
- * @returns `true` if a full observation cycle completed; `false` if skipped (stale lock) or async-buffer failure was swallowed.
1036
+ * @returns Result with `observed` flag and optional `usage` from the observer LLM call.
956
1037
  * @throws On sync/resource-scoped observer failure after failed markers (same as pre–Option-A contract).
957
1038
  */
958
1039
  async run() {
@@ -962,7 +1043,7 @@ var ObservationStrategy = class _ObservationStrategy {
962
1043
  if (this.needsLock) {
963
1044
  const fresh = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
964
1045
  if (fresh?.lastObservedAt && record.lastObservedAt && fresh.lastObservedAt > record.lastObservedAt) {
965
- return false;
1046
+ return { observed: false };
966
1047
  }
967
1048
  }
968
1049
  const { messages, existingObservations } = await this.prepare();
@@ -983,7 +1064,7 @@ var ObservationStrategy = class _ObservationStrategy {
983
1064
  observabilityContext: this.opts.observabilityContext
984
1065
  });
985
1066
  }
986
- return true;
1067
+ return { observed: true, usage: output.usage };
987
1068
  } catch (error) {
988
1069
  await this.emitFailedMarkers(cycleId, error);
989
1070
  if (!this.rethrowOnFailure) {
@@ -1002,7 +1083,7 @@ var ObservationStrategy = class _ObservationStrategy {
1002
1083
  });
1003
1084
  if (abortSignal?.aborted) throw error;
1004
1085
  omError("[OM] Observation failed", error);
1005
- return false;
1086
+ return { observed: false };
1006
1087
  }
1007
1088
  omError("[OM] Observation failed", error);
1008
1089
  throw error;
@@ -1014,9 +1095,11 @@ var ObservationStrategy = class _ObservationStrategy {
1014
1095
  }
1015
1096
  async streamMarker(marker) {
1016
1097
  if (this.opts.writer) {
1017
- await this.opts.writer.custom(marker).catch(() => {
1098
+ await this.opts.writer.custom({ ...marker, transient: true }).catch(() => {
1018
1099
  });
1019
1100
  }
1101
+ const markerThreadId = marker.data?.threadId ?? this.opts.threadId;
1102
+ await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
1020
1103
  }
1021
1104
  getObservationMarkerConfig() {
1022
1105
  return {
@@ -1522,7 +1605,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
1522
1605
  }
1523
1606
  }
1524
1607
  async emitEndMarkers(_cycleId, processed) {
1525
- if (!processed.observations || !this.opts.writer) return;
1608
+ if (!processed.observations) return;
1526
1609
  const { record, threadId, messages } = this.opts;
1527
1610
  const tokensBuffered = await this.tokenCounter.countMessagesAsync(messages);
1528
1611
  const updatedRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
@@ -1538,12 +1621,13 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
1538
1621
  threadId,
1539
1622
  observations: processed.observations
1540
1623
  });
1541
- void this.opts.writer.custom(endMarker).catch(() => {
1542
- });
1624
+ if (this.opts.writer) {
1625
+ void this.opts.writer.custom({ ...endMarker, transient: true }).catch(() => {
1626
+ });
1627
+ }
1543
1628
  await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0);
1544
1629
  }
1545
1630
  async emitFailedMarkers(_cycleId, error) {
1546
- if (!this.opts.writer) return;
1547
1631
  const { record, threadId, messages } = this.opts;
1548
1632
  const tokensAttempted = await this.tokenCounter.countMessagesAsync(messages);
1549
1633
  const failedMarker = createBufferingFailedMarker({
@@ -1555,8 +1639,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
1555
1639
  recordId: record.id,
1556
1640
  threadId
1557
1641
  });
1558
- void this.opts.writer.custom(failedMarker).catch(() => {
1559
- });
1642
+ if (this.opts.writer) {
1643
+ void this.opts.writer.custom({ ...failedMarker, transient: true }).catch(() => {
1644
+ });
1645
+ }
1560
1646
  await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0);
1561
1647
  }
1562
1648
  };
@@ -1947,6 +2033,8 @@ var ObservationStep = class {
1947
2033
  this.turn = turn;
1948
2034
  this.stepNumber = stepNumber;
1949
2035
  }
2036
+ turn;
2037
+ stepNumber;
1950
2038
  _prepared = false;
1951
2039
  _context;
1952
2040
  /** Whether this step has been prepared. */
@@ -1982,6 +2070,7 @@ var ObservationStep = class {
1982
2070
  resourceId,
1983
2071
  checkThreshold: true,
1984
2072
  messages: step0Messages,
2073
+ currentModel: this.turn.actorModelContext,
1985
2074
  writer: this.turn.writer,
1986
2075
  messageList
1987
2076
  });
@@ -2005,8 +2094,11 @@ var ObservationStep = class {
2005
2094
  observationTokens: obsTokens,
2006
2095
  threadId,
2007
2096
  writer: this.turn.writer,
2097
+ messageList,
2098
+ currentModel: this.turn.actorModelContext,
2008
2099
  requestContext: this.turn.requestContext,
2009
- observabilityContext: this.turn.observabilityContext
2100
+ observabilityContext: this.turn.observabilityContext,
2101
+ lastActivityAt: getLastActivityFromMessages(messageList.get.all.db())
2010
2102
  });
2011
2103
  await this.turn.refreshRecord();
2012
2104
  if (this.turn.record.generationCount > preReflectGeneration) {
@@ -2015,7 +2107,11 @@ var ObservationStep = class {
2015
2107
  }
2016
2108
  const allMsgsForToolCheck = messageList.get.all.db();
2017
2109
  const lastMessage = allMsgsForToolCheck[allMsgsForToolCheck.length - 1];
2018
- const latestStepParts = getLatestStepParts(lastMessage?.content?.parts ?? []);
2110
+ const pendingStepMessages = [...messageList.get.input.db(), ...messageList.get.response.db()];
2111
+ const latestStepParts = [
2112
+ ...getLatestStepParts(lastMessage?.content?.parts ?? []),
2113
+ ...pendingStepMessages.flatMap((msg) => getLatestStepParts(msg.content?.parts ?? []))
2114
+ ];
2019
2115
  const hasIncompleteToolCalls = latestStepParts.some(
2020
2116
  (part) => part?.type === "tool-invocation" && part.toolInvocation?.state === "call"
2021
2117
  );
@@ -2170,6 +2266,7 @@ var ObservationStep = class {
2170
2266
  threadId,
2171
2267
  resourceId,
2172
2268
  messages: messageList.get.all.db(),
2269
+ currentModel: this.turn.actorModelContext,
2173
2270
  writer: this.turn.writer,
2174
2271
  messageList
2175
2272
  });
@@ -2181,8 +2278,10 @@ var ObservationStep = class {
2181
2278
  threadId,
2182
2279
  writer: this.turn.writer,
2183
2280
  messageList,
2281
+ currentModel: this.turn.actorModelContext,
2184
2282
  requestContext: this.turn.requestContext,
2185
- observabilityContext: this.turn.observabilityContext
2283
+ observabilityContext: this.turn.observabilityContext,
2284
+ lastActivityAt: getLastActivityFromMessages(messageList.get.all.db())
2186
2285
  });
2187
2286
  return {
2188
2287
  succeeded: true,
@@ -2199,6 +2298,31 @@ var ObservationStep = class {
2199
2298
  writer: this.turn.writer,
2200
2299
  observabilityContext: this.turn.observabilityContext
2201
2300
  });
2301
+ if (obsResult.observed) {
2302
+ const observedMessageIds = new Set(obsResult.record.observedMessageIds ?? []);
2303
+ const liveMessages = messageList.get.all.db();
2304
+ let latestObservedIndex = -1;
2305
+ for (let i = liveMessages.length - 1; i >= 0; i--) {
2306
+ const message = liveMessages[i];
2307
+ if (message && observedMessageIds.has(message.id)) {
2308
+ latestObservedIndex = i;
2309
+ break;
2310
+ }
2311
+ }
2312
+ const messageToSeal = latestObservedIndex >= 0 ? liveMessages[latestObservedIndex] : void 0;
2313
+ const messagesToSeal = messageToSeal ? [messageToSeal] : [];
2314
+ om.sealMessagesForBuffering(messagesToSeal);
2315
+ try {
2316
+ await this.turn.hooks?.onSyncObservationComplete?.();
2317
+ } catch (error) {
2318
+ omDebug(
2319
+ `[OM:observe] onSyncObservationComplete hook failed: ${error instanceof Error ? error.message : String(error)}`
2320
+ );
2321
+ }
2322
+ if (messagesToSeal.length > 0) {
2323
+ await om.persistMessages(messagesToSeal, threadId, resourceId);
2324
+ }
2325
+ }
2202
2326
  return {
2203
2327
  succeeded: obsResult.observed,
2204
2328
  record: obsResult.record,
@@ -2224,7 +2348,9 @@ var ObservationTurn = class {
2224
2348
  requestContext;
2225
2349
  /** Optional observability context for nested OM spans. */
2226
2350
  observabilityContext;
2227
- /** Optional processor-provided hooks for turn/step lifecycle integration. */
2351
+ /** Current actor model for this step. Updated by the processor before prepare(). */
2352
+ actorModelContext;
2353
+ /** Processor-provided hooks for turn/step lifecycle integration. */
2228
2354
  hooks;
2229
2355
  constructor(opts) {
2230
2356
  this.om = opts.om;
@@ -2232,7 +2358,7 @@ var ObservationTurn = class {
2232
2358
  this.resourceId = opts.resourceId;
2233
2359
  this.messageList = opts.messageList;
2234
2360
  this.observabilityContext = opts.observabilityContext;
2235
- this.hooks = opts.hooks;
2361
+ this.hooks = opts.hooks ?? {};
2236
2362
  }
2237
2363
  om;
2238
2364
  threadId;
@@ -2252,6 +2378,10 @@ var ObservationTurn = class {
2252
2378
  get currentStep() {
2253
2379
  return this._currentStep;
2254
2380
  }
2381
+ addHooks(hooks) {
2382
+ if (!hooks) return;
2383
+ Object.assign(this.hooks, hooks);
2384
+ }
2255
2385
  /**
2256
2386
  * Load context and cache the record. Call once at the start of the turn.
2257
2387
  *
@@ -2408,6 +2538,15 @@ function stripEphemeralAnchorIds(observations) {
2408
2538
  }
2409
2539
  return observations.replace(/(^|\n)([^\S\n]*)\[(O\d+(?:-N\d+)?)\][^\S\n]*/g, "$1$2");
2410
2540
  }
2541
+
2542
+ // src/processors/observational-memory/string-utils.ts
2543
+ function safeSlice(str, end) {
2544
+ if (end <= 0) return "";
2545
+ if (end >= str.length) return str;
2546
+ const code = str.charCodeAt(end - 1);
2547
+ const safeEnd = code >= 55296 && code <= 56319 ? end - 1 : end;
2548
+ return str.slice(0, safeEnd);
2549
+ }
2411
2550
  var ENCRYPTED_CONTENT_KEY = "encryptedContent";
2412
2551
  var ENCRYPTED_CONTENT_REDACTION_THRESHOLD = 256;
2413
2552
  var DEFAULT_OBSERVER_TOOL_RESULT_MAX_TOKENS = 1e4;
@@ -2473,7 +2612,7 @@ function truncateStringByTokens(text, maxTokens) {
2473
2612
  return text;
2474
2613
  }
2475
2614
  const buildCandidate = (sliceEnd) => {
2476
- const visible = text.slice(0, sliceEnd);
2615
+ const visible = safeSlice(text, sliceEnd);
2477
2616
  return `${visible}
2478
2617
  ... [truncated ~${totalTokens - tokenx.estimateTokenCount(visible)} tokens]`;
2479
2618
  };
@@ -2929,11 +3068,13 @@ var OBSERVER_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
2929
3068
  "heif",
2930
3069
  "avif"
2931
3070
  ]);
2932
- function formatObserverTimestamp(createdAt) {
2933
- return createdAt ? new Date(createdAt).toLocaleString("en-US", {
2934
- year: "numeric",
2935
- month: "short",
2936
- day: "numeric",
3071
+ function formatObserverDate(createdAt) {
3072
+ return createdAt ? `${createdAt.toLocaleDateString("en-US", {
3073
+ month: "short"
3074
+ })} ${createdAt.getDate()} ${createdAt.getFullYear()}` : "";
3075
+ }
3076
+ function formatObserverTime(createdAt) {
3077
+ return createdAt ? createdAt.toLocaleTimeString("en-US", {
2937
3078
  hour: "numeric",
2938
3079
  minute: "2-digit",
2939
3080
  hour12: true
@@ -3024,19 +3165,88 @@ function formatObserverAttachmentPlaceholder(part, counter) {
3024
3165
  const label = resolveObserverAttachmentLabel(part);
3025
3166
  return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
3026
3167
  }
3168
+ function formatObserverPartLine(title, body, time, previousTime) {
3169
+ const timeLabel = time && time !== previousTime ? `(${time})` : "";
3170
+ if (!title) {
3171
+ return timeLabel ? `${timeLabel}: ${body}` : body;
3172
+ }
3173
+ return `${title}${timeLabel ? ` ${timeLabel}` : ""}: ${body}`;
3174
+ }
3175
+ function normalizeObserverCreatedAt(createdAt) {
3176
+ if (createdAt instanceof Date) {
3177
+ if (Number.isNaN(createdAt.getTime())) {
3178
+ return void 0;
3179
+ }
3180
+ return createdAt;
3181
+ }
3182
+ if (typeof createdAt === "number" || typeof createdAt === "string") {
3183
+ const date = new Date(createdAt);
3184
+ if (Number.isNaN(date.getTime())) {
3185
+ return void 0;
3186
+ }
3187
+ return date;
3188
+ }
3189
+ return void 0;
3190
+ }
3191
+ function formatObserverLines(lines, context = {}) {
3192
+ const output = [];
3193
+ let previousDate = context.previousDate;
3194
+ let previousTime = context.previousTime;
3195
+ for (const line of lines) {
3196
+ if (line.date && line.date !== previousDate) {
3197
+ output.push(`${line.date}:`);
3198
+ previousDate = line.date;
3199
+ previousTime = void 0;
3200
+ }
3201
+ output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime));
3202
+ previousTime = line.time || previousTime;
3203
+ }
3204
+ return {
3205
+ text: output.join("\n"),
3206
+ context: { previousDate, previousTime }
3207
+ };
3208
+ }
3209
+ function getTemporalGapMarkerText(msg) {
3210
+ const metadata = typeof msg.content === "object" && msg.content && "metadata" in msg.content ? msg.content.metadata : void 0;
3211
+ if (metadata?.reminderType === "temporal-gap" && typeof metadata.gapText === "string") {
3212
+ return metadata.gapText;
3213
+ }
3214
+ if (typeof metadata?.systemReminder === "object" && metadata.systemReminder && "type" in metadata.systemReminder && metadata.systemReminder.type === "temporal-gap" && "gapText" in metadata.systemReminder && typeof metadata.systemReminder.gapText === "string") {
3215
+ return metadata.systemReminder.gapText;
3216
+ }
3217
+ return void 0;
3218
+ }
3027
3219
  function formatObserverMessage(msg, counter, options) {
3028
3220
  const maxLen = options?.maxPartLength;
3029
3221
  const maxToolResultTokens = options?.maxToolResultTokens ?? DEFAULT_OBSERVER_TOOL_RESULT_MAX_TOKENS;
3030
- const timestamp = formatObserverTimestamp(msg.createdAt);
3031
3222
  const role = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
3032
- const timestampStr = timestamp ? ` (${timestamp})` : "";
3033
3223
  const attachments = [];
3034
- let content = "";
3035
- if (typeof msg.content === "string") {
3036
- content = maybeTruncate(msg.content, maxLen);
3224
+ const messageCreatedAt = normalizeObserverCreatedAt(msg.createdAt);
3225
+ let lines = [];
3226
+ const temporalGapText = isTemporalGapMarker(msg) ? getTemporalGapMarkerText(msg) : void 0;
3227
+ const pushLine = (title, body, createdAt) => {
3228
+ if (!body) {
3229
+ return;
3230
+ }
3231
+ const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
3232
+ lines.push({
3233
+ date: formatObserverDate(normalizedCreatedAt),
3234
+ time: formatObserverTime(normalizedCreatedAt),
3235
+ title,
3236
+ body
3237
+ });
3238
+ };
3239
+ if (temporalGapText) {
3240
+ pushLine("", temporalGapText, messageCreatedAt);
3241
+ } else if (typeof msg.content === "string") {
3242
+ pushLine(role, maybeTruncate(msg.content, maxLen), messageCreatedAt);
3037
3243
  } else if (msg.content?.parts && Array.isArray(msg.content.parts) && msg.content.parts.length > 0) {
3038
- content = msg.content.parts.map((part) => {
3039
- if (part.type === "text") return maybeTruncate(part.text, maxLen);
3244
+ msg.content.parts.forEach((part) => {
3245
+ const partCreatedAt = normalizeObserverCreatedAt(part.createdAt) ?? messageCreatedAt;
3246
+ if (part.type === "text") {
3247
+ pushLine(role, maybeTruncate(part.text, maxLen), partCreatedAt);
3248
+ return;
3249
+ }
3040
3250
  if (part.type === "tool-invocation") {
3041
3251
  const inv = part.toolInvocation;
3042
3252
  if (inv.state === "result") {
@@ -3044,19 +3254,24 @@ function formatObserverMessage(msg, counter, options) {
3044
3254
  part,
3045
3255
  inv.result
3046
3256
  );
3047
- const resultStr = formatToolResultForObserver(resultForObserver, { maxTokens: maxToolResultTokens });
3048
- return `[Tool Result: ${inv.toolName}]
3049
- ${maybeTruncate(resultStr, maxLen)}`;
3257
+ pushLine(
3258
+ `Tool Result ${inv.toolName}`,
3259
+ maybeTruncate(formatToolResultForObserver(resultForObserver, { maxTokens: maxToolResultTokens }), maxLen),
3260
+ partCreatedAt
3261
+ );
3262
+ return;
3050
3263
  }
3051
- const argsStr = JSON.stringify(inv.args, null, 2);
3052
- return `[Tool Call: ${inv.toolName}]
3053
- ${maybeTruncate(argsStr, maxLen)}`;
3264
+ pushLine(`Tool Call ${inv.toolName}`, maybeTruncate(JSON.stringify(inv.args, null, 2), maxLen), partCreatedAt);
3265
+ return;
3054
3266
  }
3055
3267
  const partType = part.type;
3056
3268
  if (partType === "reasoning") {
3057
3269
  const reasoning = part.reasoning;
3058
- if (reasoning) return maybeTruncate(reasoning, maxLen);
3059
- return "";
3270
+ if (!reasoning) {
3271
+ return;
3272
+ }
3273
+ pushLine("Reasoning", maybeTruncate(reasoning, maxLen), partCreatedAt);
3274
+ return;
3060
3275
  }
3061
3276
  if (partType === "image" || partType === "file") {
3062
3277
  const attachment = part;
@@ -3064,40 +3279,58 @@ ${maybeTruncate(argsStr, maxLen)}`;
3064
3279
  if (inputAttachment) {
3065
3280
  attachments.push(inputAttachment);
3066
3281
  }
3067
- return formatObserverAttachmentPlaceholder(attachment, counter);
3282
+ pushLine(
3283
+ partType === "image" ? "Image" : "File",
3284
+ formatObserverAttachmentPlaceholder(attachment, counter),
3285
+ partCreatedAt
3286
+ );
3068
3287
  }
3069
- if (partType?.startsWith("data-")) return "";
3070
- return "";
3071
- }).filter(Boolean).join("\n");
3288
+ });
3072
3289
  } else if (msg.content?.content) {
3073
- content = maybeTruncate(msg.content.content, maxLen);
3290
+ pushLine(role, maybeTruncate(msg.content.content, maxLen), messageCreatedAt);
3074
3291
  }
3075
- if (!content && attachments.length === 0) {
3076
- return { text: "", attachments };
3292
+ if (lines.length === 0 && attachments.length === 0) {
3293
+ return { lines: [], attachments };
3077
3294
  }
3078
3295
  return {
3079
- text: `**${role}${timestampStr}:**
3080
- ${content}`,
3296
+ lines,
3081
3297
  attachments
3082
3298
  };
3083
3299
  }
3084
3300
  function formatMessagesForObserver(messages, options) {
3085
3301
  const counter = { nextImageId: 1, nextFileId: 1 };
3086
- return messages.map((msg) => formatObserverMessage(msg, counter, options).text).filter(Boolean).join("\n\n---\n\n");
3302
+ const sections = [];
3303
+ let context = {};
3304
+ for (const message of messages) {
3305
+ const formatted = formatObserverMessage(message, counter, options);
3306
+ if (formatted.lines.length === 0) {
3307
+ continue;
3308
+ }
3309
+ const rendered = formatObserverLines(formatted.lines, context);
3310
+ if (!rendered.text) {
3311
+ continue;
3312
+ }
3313
+ sections.push(rendered.text);
3314
+ context = rendered.context;
3315
+ }
3316
+ return sections.join("\n");
3317
+ }
3318
+ function appendFormattedObserverMessage(content, formatted, context) {
3319
+ const rendered = formatObserverLines(formatted.lines, context);
3320
+ if (rendered.text) {
3321
+ content.push({ type: "text", text: rendered.text });
3322
+ }
3323
+ content.push(...formatted.attachments);
3324
+ return rendered.context;
3087
3325
  }
3088
3326
  function buildObserverHistoryMessage(messages, options) {
3089
3327
  const counter = { nextImageId: 1, nextFileId: 1 };
3090
3328
  const content = [{ type: "text", text: "## New Message History to Observe\n\n" }];
3091
- let visibleCount = 0;
3329
+ let context = {};
3092
3330
  messages.forEach((message) => {
3093
3331
  const formatted = formatObserverMessage(message, counter, options);
3094
- if (!formatted.text && formatted.attachments.length === 0) return;
3095
- if (visibleCount > 0) {
3096
- content.push({ type: "text", text: "\n\n---\n\n" });
3097
- }
3098
- content.push({ type: "text", text: formatted.text });
3099
- content.push(...formatted.attachments);
3100
- visibleCount++;
3332
+ if (formatted.lines.length === 0 && formatted.attachments.length === 0) return;
3333
+ context = appendFormattedObserverMessage(content, formatted, context);
3101
3334
  });
3102
3335
  return {
3103
3336
  role: "user",
@@ -3106,8 +3339,8 @@ function buildObserverHistoryMessage(messages, options) {
3106
3339
  }
3107
3340
  function maybeTruncate(str, maxLen) {
3108
3341
  if (!maxLen || str.length <= maxLen) return str;
3109
- const truncated = str.slice(0, maxLen);
3110
- const remaining = str.length - maxLen;
3342
+ const truncated = safeSlice(str, maxLen);
3343
+ const remaining = str.length - truncated.length;
3111
3344
  return `${truncated}
3112
3345
  ... [truncated ${remaining} characters]`;
3113
3346
  }
@@ -3127,18 +3360,15 @@ The following messages are from ${threadOrder.length} different conversation thr
3127
3360
  const messages = messagesByThread.get(threadId);
3128
3361
  if (!messages || messages.length === 0) return;
3129
3362
  const threadContent = [];
3130
- let visibleCount = 0;
3363
+ let context = {};
3364
+ let hasVisibleContent = false;
3131
3365
  messages.forEach((message) => {
3132
3366
  const formatted = formatObserverMessage(message, counter, options);
3133
- if (!formatted.text && formatted.attachments.length === 0) return;
3134
- if (visibleCount > 0) {
3135
- threadContent.push({ type: "text", text: "\n\n---\n\n" });
3136
- }
3137
- threadContent.push({ type: "text", text: formatted.text });
3138
- threadContent.push(...formatted.attachments);
3139
- visibleCount++;
3367
+ if (formatted.lines.length === 0 && formatted.attachments.length === 0) return;
3368
+ context = appendFormattedObserverMessage(threadContent, formatted, context);
3369
+ hasVisibleContent = true;
3140
3370
  });
3141
- if (visibleCount === 0) return;
3371
+ if (!hasVisibleContent) return;
3142
3372
  content.push({ type: "text", text: `<thread id="${threadId}">
3143
3373
  ` });
3144
3374
  content.push(...threadContent);
@@ -3414,7 +3644,7 @@ function sanitizeObservationLines(observations) {
3414
3644
  let changed = false;
3415
3645
  for (let i = 0; i < lines.length; i++) {
3416
3646
  if (lines[i].length > MAX_OBSERVATION_LINE_CHARS) {
3417
- lines[i] = lines[i].slice(0, MAX_OBSERVATION_LINE_CHARS) + " \u2026 [truncated]";
3647
+ lines[i] = safeSlice(lines[i], MAX_OBSERVATION_LINE_CHARS) + " \u2026 [truncated]";
3418
3648
  changed = true;
3419
3649
  }
3420
3650
  }
@@ -4017,12 +4247,44 @@ function validateCompression(reflectedTokens, targetThreshold) {
4017
4247
  }
4018
4248
 
4019
4249
  // src/processors/observational-memory/reflector-runner.ts
4250
+ function formatModelContext(provider, modelId) {
4251
+ if (provider && modelId) {
4252
+ return `${provider}/${modelId}`;
4253
+ }
4254
+ return modelId;
4255
+ }
4256
+ function getCurrentModel(model) {
4257
+ return formatModelContext(model?.provider, model?.modelId);
4258
+ }
4259
+ function getLastModelFromMessageList(messageList) {
4260
+ const messages = messageList?.get.all.db();
4261
+ if (!messages) return void 0;
4262
+ for (let i = messages.length - 1; i >= 0; i--) {
4263
+ const message = messages[i];
4264
+ if (!message || message.role !== "assistant" || !message.content || typeof message.content === "string") {
4265
+ continue;
4266
+ }
4267
+ for (let j = message.content.parts.length - 1; j >= 0; j--) {
4268
+ const part = message.content.parts[j];
4269
+ if (part?.type === "step-start" && typeof part.model === "string" && part.model.length > 0) {
4270
+ return part.model;
4271
+ }
4272
+ }
4273
+ const metadata = message.content.metadata;
4274
+ const model = formatModelContext(metadata?.provider, metadata?.modelId);
4275
+ if (model) {
4276
+ return model;
4277
+ }
4278
+ }
4279
+ return void 0;
4280
+ }
4020
4281
  async function withAbortCheck(fn, abortSignal) {
4021
4282
  if (abortSignal?.aborted) throw new Error("The operation was aborted.");
4022
4283
  const result = await fn();
4023
4284
  if (abortSignal?.aborted) throw new Error("The operation was aborted.");
4024
4285
  return result;
4025
4286
  }
4287
+ var EARLY_ACTIVATION_SIZE_FLOOR_RATIO = 0.75;
4026
4288
  var ReflectorRunner = class {
4027
4289
  reflectionConfig;
4028
4290
  observationConfig;
@@ -4056,13 +4318,30 @@ var ReflectorRunner = class {
4056
4318
  model
4057
4319
  });
4058
4320
  }
4059
- getObservationMarkerConfig() {
4321
+ getObservationMarkerConfig(record) {
4060
4322
  return {
4061
4323
  messageTokens: getMaxThreshold(this.observationConfig.messageTokens),
4062
- observationTokens: getMaxThreshold(this.reflectionConfig.observationTokens),
4063
- scope: this.scope
4324
+ observationTokens: getMaxThreshold(
4325
+ record ? this.getEffectiveReflectionTokens(record) : this.reflectionConfig.observationTokens
4326
+ ),
4327
+ scope: this.scope,
4328
+ activateAfterIdle: this.reflectionConfig.activateAfterIdle
4064
4329
  };
4065
4330
  }
4331
+ /**
4332
+ * Resolve the effective reflection observationTokens for a record.
4333
+ * Only explicit per-record overrides (stored under `_overrides`) win;
4334
+ * the initial config snapshot is ignored so instance-level changes
4335
+ * still take effect for existing records.
4336
+ */
4337
+ getEffectiveReflectionTokens(record) {
4338
+ const overrides = record.config?._overrides;
4339
+ const recordTokens = overrides?.reflection?.observationTokens;
4340
+ if (recordTokens) {
4341
+ return recordTokens;
4342
+ }
4343
+ return this.reflectionConfig.observationTokens;
4344
+ }
4066
4345
  /**
4067
4346
  * Call the Reflector agent with escalating compression levels.
4068
4347
  */
@@ -4172,8 +4451,9 @@ var ReflectorRunner = class {
4172
4451
  recordId: streamContext.recordId,
4173
4452
  threadId: streamContext.threadId
4174
4453
  });
4175
- await streamContext.writer.custom(failedMarker).catch(() => {
4454
+ await streamContext.writer.custom({ ...failedMarker, transient: true }).catch(() => {
4176
4455
  });
4456
+ await this.persistMarkerToStorage(failedMarker, streamContext.threadId, streamContext.resourceId);
4177
4457
  const retryCycleId = crypto.randomUUID();
4178
4458
  streamContext.cycleId = retryCycleId;
4179
4459
  const startMarker = createObservationStartMarker({
@@ -4186,8 +4466,9 @@ var ReflectorRunner = class {
4186
4466
  config: this.getObservationMarkerConfig()
4187
4467
  });
4188
4468
  streamContext.startedAt = startMarker.data.startedAt;
4189
- await streamContext.writer.custom(startMarker).catch(() => {
4469
+ await streamContext.writer.custom({ ...startMarker, transient: true }).catch(() => {
4190
4470
  });
4471
+ await this.persistMarkerToStorage(startMarker, streamContext.threadId, streamContext.resourceId);
4191
4472
  }
4192
4473
  currentLevel = Math.min(currentLevel + 1, maxLevel);
4193
4474
  }
@@ -4200,7 +4481,7 @@ var ReflectorRunner = class {
4200
4481
  /**
4201
4482
  * Start an async buffered reflection in the background.
4202
4483
  */
4203
- startAsyncBufferedReflection(record, observationTokens, lockKey, writer, requestContext, observabilityContext) {
4484
+ startAsyncBufferedReflection(record, observationTokens, lockKey, writer, requestContext, observabilityContext, reflectionHooks) {
4204
4485
  const bufferKey = this.buffering.getReflectionBufferKey(lockKey);
4205
4486
  if (this.buffering.isAsyncBufferingInProgress(bufferKey)) {
4206
4487
  return;
@@ -4210,7 +4491,10 @@ var ReflectorRunner = class {
4210
4491
  this.storage.setBufferingReflectionFlag(record.id, true).catch((err) => {
4211
4492
  omError("[OM] Failed to set buffering reflection flag", err);
4212
4493
  });
4213
- const asyncOp = this.doAsyncBufferedReflection(record, bufferKey, writer, requestContext, observabilityContext).catch(async (error) => {
4494
+ reflectionHooks?.onReflectionStart?.();
4495
+ const asyncOp = this.doAsyncBufferedReflection(record, bufferKey, writer, requestContext, observabilityContext).then((usage) => {
4496
+ reflectionHooks?.onReflectionEnd?.({ usage });
4497
+ }).catch(async (error) => {
4214
4498
  if (writer) {
4215
4499
  const failedMarker = createBufferingFailedMarker({
4216
4500
  cycleId: `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
@@ -4221,11 +4505,15 @@ var ReflectorRunner = class {
4221
4505
  recordId: record.id,
4222
4506
  threadId: record.threadId ?? ""
4223
4507
  });
4224
- void writer.custom(failedMarker).catch(() => {
4508
+ void writer.custom({ ...failedMarker, transient: true }).catch(() => {
4225
4509
  });
4226
4510
  await this.persistMarkerToStorage(failedMarker, record.threadId ?? "", record.resourceId ?? void 0);
4227
4511
  }
4228
4512
  omError("[OM] Async buffered reflection failed", error);
4513
+ reflectionHooks?.onReflectionEnd?.({
4514
+ usage: void 0,
4515
+ error: error instanceof Error ? error : new Error(String(error))
4516
+ });
4229
4517
  BufferingCoordinator.lastBufferedBoundary.delete(bufferKey);
4230
4518
  }).finally(() => {
4231
4519
  BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
@@ -4244,7 +4532,7 @@ var ReflectorRunner = class {
4244
4532
  const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
4245
4533
  const currentRecord = freshRecord ?? record;
4246
4534
  const observationTokens = currentRecord.observationTokenCount ?? 0;
4247
- const reflectThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
4535
+ const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(currentRecord));
4248
4536
  const bufferActivation = this.reflectionConfig.bufferActivation ?? 0.5;
4249
4537
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
4250
4538
  const cycleId = `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
@@ -4273,10 +4561,15 @@ var ReflectorRunner = class {
4273
4561
  recordId: record.id,
4274
4562
  threadId: record.threadId ?? "",
4275
4563
  threadIds: record.threadId ? [record.threadId] : [],
4276
- config: this.getObservationMarkerConfig()
4564
+ config: this.getObservationMarkerConfig(currentRecord)
4277
4565
  });
4278
- void writer.custom(startMarker).catch(() => {
4566
+ void writer.custom({ ...startMarker, transient: true }).catch(() => {
4279
4567
  });
4568
+ await this.persistMarkerToStorage(
4569
+ startMarker,
4570
+ currentRecord.threadId ?? "",
4571
+ currentRecord.resourceId ?? void 0
4572
+ );
4280
4573
  }
4281
4574
  const compressionStartLevel = await this.getCompressionStartLevel(requestContext);
4282
4575
  const reflectResult = await this.call(
@@ -4315,26 +4608,35 @@ var ReflectorRunner = class {
4315
4608
  threadId: currentRecord.threadId ?? "",
4316
4609
  observations: reflectResult.observations
4317
4610
  });
4318
- void writer.custom(endMarker).catch(() => {
4611
+ void writer.custom({ ...endMarker, transient: true }).catch(() => {
4319
4612
  });
4320
4613
  await this.persistMarkerToStorage(endMarker, currentRecord.threadId ?? "", currentRecord.resourceId ?? void 0);
4321
4614
  }
4615
+ return reflectResult.usage;
4322
4616
  }
4323
4617
  /**
4324
4618
  * Try to activate buffered reflection when threshold is reached.
4325
- * Returns true if activation succeeded.
4619
+ * Returns a discriminated result so the caller can distinguish between
4620
+ * "activated", "no buffer present", and "suppressed by overshoot guard"
4621
+ * without re-deriving that state.
4326
4622
  */
4327
- async tryActivateBufferedReflection(record, lockKey, writer, messageList) {
4623
+ async tryActivateBufferedReflection(record, lockKey, writer, messageList, activationMetadata) {
4328
4624
  const bufferKey = this.buffering.getReflectionBufferKey(lockKey);
4329
4625
  const asyncOp = BufferingCoordinator.asyncBufferingOps.get(bufferKey);
4330
4626
  if (asyncOp) {
4331
- omDebug(`[OM:reflect] tryActivateBufferedReflection: waiting for in-progress op...`);
4332
- try {
4333
- await Promise.race([
4334
- asyncOp,
4335
- new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 6e4))
4336
- ]);
4337
- } catch {
4627
+ if (activationMetadata?.triggeredBy === "ttl" || activationMetadata?.triggeredBy === "provider_change") {
4628
+ omDebug(
4629
+ `[OM:reflect] tryActivateBufferedReflection: async op in progress, not blocking for ${activationMetadata.triggeredBy} trigger`
4630
+ );
4631
+ } else {
4632
+ omDebug(`[OM:reflect] tryActivateBufferedReflection: waiting for in-progress op...`);
4633
+ try {
4634
+ await Promise.race([
4635
+ asyncOp,
4636
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 5e3))
4637
+ ]);
4638
+ } catch {
4639
+ }
4338
4640
  }
4339
4641
  }
4340
4642
  const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
@@ -4345,8 +4647,8 @@ var ReflectorRunner = class {
4345
4647
  `[OM:reflect] tryActivateBufferedReflection: freshRecord.id=${freshRecord?.id}, freshBufferedReflection=${freshRecord?.bufferedReflection ? "present (" + freshRecord.bufferedReflection.length + " chars)" : "empty"}, freshObsTokens=${freshRecord?.observationTokenCount}`
4346
4648
  );
4347
4649
  if (!freshRecord?.bufferedReflection) {
4348
- omDebug(`[OM:reflect] tryActivateBufferedReflection: no buffered reflection after re-fetch, returning false`);
4349
- return false;
4650
+ omDebug(`[OM:reflect] tryActivateBufferedReflection: no buffered reflection after re-fetch`);
4651
+ return { status: "no-buffer" };
4350
4652
  }
4351
4653
  const beforeTokens = freshRecord.observationTokenCount ?? 0;
4352
4654
  const reflectedLineCount = freshRecord.reflectedObservationLineCount ?? 0;
@@ -4358,6 +4660,26 @@ var ReflectorRunner = class {
4358
4660
 
4359
4661
  ${unreflectedContent}` : freshRecord.bufferedReflection;
4360
4662
  const combinedTokenCount = this.tokenCounter.countObservations(combinedObservations);
4663
+ if (activationMetadata?.triggeredBy === "ttl" || activationMetadata?.triggeredBy === "provider_change") {
4664
+ const unreflectedTailTokens = unreflectedContent ? this.tokenCounter.countObservations(unreflectedContent) : 0;
4665
+ const bufferedReflectionTokens = freshRecord.bufferedReflectionTokens ?? 0;
4666
+ if (unreflectedTailTokens < bufferedReflectionTokens) {
4667
+ omDebug(
4668
+ `[OM:reflect] tryActivateBufferedReflection: suppressing early ${activationMetadata.triggeredBy} activation \u2014 unreflectedTailTokens=${unreflectedTailTokens} < bufferedReflectionTokens=${bufferedReflectionTokens}; keeping buffer for threshold activation`
4669
+ );
4670
+ return { status: "suppressed", reason: "composition" };
4671
+ }
4672
+ const bufferActivation = this.reflectionConfig.bufferActivation;
4673
+ const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(freshRecord));
4674
+ const regularActivationTarget = reflectThreshold * (1 - bufferActivation);
4675
+ const minCombinedTokens = Math.round(regularActivationTarget * EARLY_ACTIVATION_SIZE_FLOOR_RATIO);
4676
+ if (combinedTokenCount < minCombinedTokens) {
4677
+ omDebug(
4678
+ `[OM:reflect] tryActivateBufferedReflection: suppressing early ${activationMetadata.triggeredBy} activation \u2014 combinedTokenCount=${combinedTokenCount} < minCombinedTokens=${minCombinedTokens} (${EARLY_ACTIVATION_SIZE_FLOOR_RATIO * 100}% of regular activation target ${Math.round(regularActivationTarget)}, threshold=${reflectThreshold}, bufferActivation=${bufferActivation}); keeping buffer for threshold activation`
4679
+ );
4680
+ return { status: "suppressed", reason: "size" };
4681
+ }
4682
+ }
4361
4683
  omDebug(
4362
4684
  `[OM:reflect] tryActivateBufferedReflection: activating, beforeTokens=${beforeTokens}, combinedTokenCount=${combinedTokenCount}, reflectedLineCount=${reflectedLineCount}, unreflectedLines=${unreflectedLines.length}`
4363
4685
  );
@@ -4384,9 +4706,14 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4384
4706
  threadId: freshRecord.threadId ?? "",
4385
4707
  generationCount: afterRecord?.generationCount ?? freshRecord.generationCount ?? 0,
4386
4708
  observations: afterRecord?.activeObservations,
4387
- config: this.getObservationMarkerConfig()
4709
+ triggeredBy: activationMetadata?.triggeredBy,
4710
+ lastActivityAt: activationMetadata?.lastActivityAt,
4711
+ ttlExpiredMs: activationMetadata?.ttlExpiredMs,
4712
+ previousModel: activationMetadata?.previousModel,
4713
+ currentModel: activationMetadata?.currentModel,
4714
+ config: this.getObservationMarkerConfig(freshRecord)
4388
4715
  });
4389
- void writer.custom(activationMarker).catch(() => {
4716
+ void writer.custom({ ...activationMarker, transient: true }).catch(() => {
4390
4717
  });
4391
4718
  await this.persistMarkerToMessage(
4392
4719
  activationMarker,
@@ -4396,7 +4723,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4396
4723
  );
4397
4724
  }
4398
4725
  BufferingCoordinator.reflectionBufferCycleIds.delete(bufferKey);
4399
- return true;
4726
+ return { status: "activated" };
4400
4727
  }
4401
4728
  /**
4402
4729
  * Check if reflection needed and trigger if so.
@@ -4410,12 +4737,15 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4410
4737
  writer,
4411
4738
  abortSignal,
4412
4739
  messageList,
4740
+ currentModel,
4413
4741
  reflectionHooks,
4414
4742
  requestContext,
4415
- observabilityContext
4743
+ observabilityContext,
4744
+ lastActivityAt,
4745
+ threadId: requestedThreadId
4416
4746
  } = opts;
4417
4747
  const lockKey = this.buffering.getLockKey(record.threadId, record.resourceId);
4418
- const reflectThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
4748
+ const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
4419
4749
  if (this.buffering.isAsyncReflectionEnabled() && observationTokens < reflectThreshold) {
4420
4750
  const shouldTrigger = (() => {
4421
4751
  if (!this.buffering.isAsyncReflectionEnabled()) return false;
@@ -4439,13 +4769,28 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4439
4769
  lockKey,
4440
4770
  writer,
4441
4771
  requestContext,
4442
- observabilityContext
4772
+ observabilityContext,
4773
+ reflectionHooks
4443
4774
  );
4444
4775
  }
4445
4776
  }
4446
- if (observationTokens < reflectThreshold) {
4777
+ const activateAfterIdle = this.reflectionConfig.activateAfterIdle;
4778
+ const ttlExpiredMs = activateAfterIdle !== void 0 && lastActivityAt !== void 0 ? Date.now() - lastActivityAt : void 0;
4779
+ const ttlExpired = ttlExpiredMs !== void 0 && activateAfterIdle !== void 0 && ttlExpiredMs >= activateAfterIdle;
4780
+ const actorModel = getCurrentModel(currentModel);
4781
+ const lastModel = getLastModelFromMessageList(messageList);
4782
+ const providerChanged = this.reflectionConfig.activateOnProviderChange === true && didProviderChange(actorModel, lastModel);
4783
+ if (observationTokens < reflectThreshold && !ttlExpired && !providerChanged) {
4447
4784
  return;
4448
4785
  }
4786
+ const activationTriggeredBy = observationTokens >= reflectThreshold ? "threshold" : providerChanged ? "provider_change" : "ttl";
4787
+ const activationMetadata = {
4788
+ triggeredBy: activationTriggeredBy,
4789
+ lastActivityAt: activationTriggeredBy === "ttl" ? lastActivityAt : void 0,
4790
+ ttlExpiredMs: activationTriggeredBy === "ttl" ? ttlExpiredMs : void 0,
4791
+ previousModel: activationTriggeredBy === "provider_change" ? lastModel : void 0,
4792
+ currentModel: activationTriggeredBy === "provider_change" ? actorModel : void 0
4793
+ };
4449
4794
  if (record.isReflecting) {
4450
4795
  if (isOpActiveInProcess(record.id, "reflecting")) {
4451
4796
  omDebug(`[OM:reflect] isReflecting=true and active in this process, skipping`);
@@ -4455,8 +4800,20 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4455
4800
  await this.storage.setReflectingFlag(record.id, false);
4456
4801
  }
4457
4802
  if (this.buffering.isAsyncReflectionEnabled()) {
4458
- const activationSuccess = await this.tryActivateBufferedReflection(record, lockKey, writer, messageList);
4459
- if (activationSuccess) {
4803
+ const activationResult = await this.tryActivateBufferedReflection(
4804
+ record,
4805
+ lockKey,
4806
+ writer,
4807
+ messageList,
4808
+ activationMetadata
4809
+ );
4810
+ if (activationResult.status === "activated") {
4811
+ return;
4812
+ }
4813
+ if (activationResult.status === "suppressed") {
4814
+ omDebug(
4815
+ `[OM:reflect] skipping sync fallback / re-buffer after suppressed early ${activationMetadata.triggeredBy} activation (reason=${activationResult.reason})`
4816
+ );
4460
4817
  return;
4461
4818
  }
4462
4819
  if (this.reflectionConfig.blockAfter && observationTokens >= this.reflectionConfig.blockAfter) {
@@ -4464,6 +4821,13 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4464
4821
  `[OM:reflect] blockAfter exceeded (${observationTokens} >= ${this.reflectionConfig.blockAfter}), falling through to sync reflection`
4465
4822
  );
4466
4823
  } else {
4824
+ const activationPoint = reflectThreshold * this.reflectionConfig.bufferActivation;
4825
+ if (observationTokens < activationPoint) {
4826
+ omDebug(
4827
+ `[OM:reflect] skipping async reflection \u2014 observationTokens (${observationTokens}) below activation point (${activationPoint}), triggered by ${activationTriggeredBy}`
4828
+ );
4829
+ return;
4830
+ }
4467
4831
  omDebug(
4468
4832
  `[OM:reflect] async activation failed, no blockAfter or below it (obsTokens=${observationTokens}, blockAfter=${this.reflectionConfig.blockAfter}) \u2014 starting background reflection`
4469
4833
  );
@@ -4473,7 +4837,8 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4473
4837
  lockKey,
4474
4838
  writer,
4475
4839
  requestContext,
4476
- observabilityContext
4840
+ observabilityContext,
4841
+ reflectionHooks
4477
4842
  );
4478
4843
  return;
4479
4844
  }
@@ -4483,7 +4848,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4483
4848
  registerOp(record.id, "reflecting");
4484
4849
  const cycleId = crypto.randomUUID();
4485
4850
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
4486
- const threadId = opts.threadId ?? "unknown";
4851
+ const threadId = requestedThreadId ?? "unknown";
4487
4852
  if (writer) {
4488
4853
  const startMarker = createObservationStartMarker({
4489
4854
  cycleId,
@@ -4492,10 +4857,11 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4492
4857
  recordId: record.id,
4493
4858
  threadId,
4494
4859
  threadIds: [threadId],
4495
- config: this.getObservationMarkerConfig()
4860
+ config: this.getObservationMarkerConfig(record)
4496
4861
  });
4497
- await writer.custom(startMarker).catch(() => {
4862
+ await writer.custom({ ...startMarker, transient: true }).catch(() => {
4498
4863
  });
4864
+ await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0);
4499
4865
  }
4500
4866
  this.emitDebugEvent({
4501
4867
  type: "reflection_triggered",
@@ -4510,8 +4876,11 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4510
4876
  cycleId,
4511
4877
  startedAt,
4512
4878
  recordId: record.id,
4513
- threadId
4879
+ threadId,
4880
+ resourceId: record.resourceId ?? void 0
4514
4881
  } : void 0;
4882
+ let reflectionUsage;
4883
+ let reflectionError;
4515
4884
  try {
4516
4885
  const compressionStartLevel = await this.getCompressionStartLevel(requestContext);
4517
4886
  const reflectResult = await this.call(
@@ -4525,6 +4894,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4525
4894
  requestContext,
4526
4895
  observabilityContext
4527
4896
  );
4897
+ reflectionUsage = reflectResult.usage;
4528
4898
  const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
4529
4899
  await this.storage.createReflectionGeneration({
4530
4900
  currentRecord: record,
@@ -4542,8 +4912,9 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4542
4912
  recordId: record.id,
4543
4913
  threadId
4544
4914
  });
4545
- await writer.custom(endMarker).catch(() => {
4915
+ await writer.custom({ ...endMarker, transient: true }).catch(() => {
4546
4916
  });
4917
+ await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0);
4547
4918
  }
4548
4919
  this.emitDebugEvent({
4549
4920
  type: "reflection_complete",
@@ -4566,89 +4937,413 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4566
4937
  recordId: record.id,
4567
4938
  threadId
4568
4939
  });
4569
- await writer.custom(failedMarker).catch(() => {
4940
+ await writer.custom({ ...failedMarker, transient: true }).catch(() => {
4570
4941
  });
4942
+ await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0);
4571
4943
  }
4944
+ reflectionError = error instanceof Error ? error : new Error(String(error));
4572
4945
  if (abortSignal?.aborted) {
4573
4946
  throw error;
4574
4947
  }
4575
4948
  omError("[OM] Reflection failed", error);
4576
4949
  } finally {
4577
4950
  await this.storage.setReflectingFlag(record.id, false);
4578
- reflectionHooks?.onReflectionEnd?.();
4951
+ reflectionHooks?.onReflectionEnd?.({ usage: reflectionUsage, error: reflectionError });
4579
4952
  unregisterOp(record.id, "reflecting");
4580
4953
  }
4581
4954
  }
4582
4955
  };
4583
- var IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
4584
- "png",
4585
- "jpg",
4586
- "jpeg",
4587
- "webp",
4588
- "gif",
4589
- "bmp",
4590
- "tiff",
4591
- "tif",
4592
- "heic",
4593
- "heif",
4594
- "avif"
4595
- ]);
4596
- var TOKEN_ESTIMATE_CACHE_VERSION = 6;
4597
- var DEFAULT_IMAGE_ESTIMATOR = {
4598
- baseTokens: 85,
4599
- tileTokens: 170,
4600
- fallbackTiles: 4
4601
- };
4602
- var GOOGLE_LEGACY_IMAGE_TOKENS_PER_TILE = 258;
4603
- var GOOGLE_GEMINI_3_IMAGE_TOKENS_BY_RESOLUTION = {
4604
- low: 280,
4605
- medium: 560,
4606
- high: 1120,
4607
- ultra_high: 2240,
4608
- unspecified: 1120
4609
- };
4610
- var ANTHROPIC_IMAGE_TOKENS_PER_PIXEL = 1 / 750;
4611
- var ANTHROPIC_IMAGE_MAX_LONG_EDGE = 1568;
4612
- var GOOGLE_MEDIA_RESOLUTION_VALUES = /* @__PURE__ */ new Set([
4613
- "low",
4614
- "medium",
4615
- "high",
4616
- "ultra_high",
4617
- "unspecified"
4618
- ]);
4619
- var ATTACHMENT_COUNT_TIMEOUT_MS = 2e4;
4620
- var REMOTE_IMAGE_PROBE_TIMEOUT_MS = 2500;
4621
- var PROVIDER_API_KEY_ENV_VARS = {
4622
- openai: ["OPENAI_API_KEY"],
4623
- google: ["GOOGLE_GENERATIVE_AI_API_KEY", "GOOGLE_API_KEY"],
4624
- anthropic: ["ANTHROPIC_API_KEY"]
4625
- };
4626
- function getPartMastraMetadata(part) {
4627
- return part.providerMetadata?.mastra;
4956
+ function getOmReproCaptureDir() {
4957
+ return process.env.OM_REPRO_CAPTURE_DIR ?? ".mastra-om-repro";
4628
4958
  }
4629
- function ensurePartMastraMetadata(part) {
4630
- const typedPart = part;
4631
- typedPart.providerMetadata ??= {};
4632
- typedPart.providerMetadata.mastra ??= {};
4633
- return typedPart.providerMetadata.mastra;
4959
+ function sanitizeCapturePathSegment(value) {
4960
+ const sanitized = value.replace(/[\\/]+/g, "_").replace(/\.{2,}/g, "_").trim();
4961
+ return sanitized.length > 0 ? sanitized : "unknown-thread";
4634
4962
  }
4635
- function getContentMastraMetadata(content) {
4636
- if (!content || typeof content !== "object") {
4637
- return void 0;
4963
+ function isOmReproCaptureEnabled() {
4964
+ return process.env.OM_REPRO_CAPTURE === "1";
4965
+ }
4966
+ function safeCaptureJson(value) {
4967
+ return JSON.parse(
4968
+ JSON.stringify(value, (_key, current) => {
4969
+ if (typeof current === "bigint") return current.toString();
4970
+ if (typeof current === "function") return "[function]";
4971
+ if (typeof current === "symbol") return current.toString();
4972
+ if (current instanceof Error) return { name: current.name, message: current.message, stack: current.stack };
4973
+ if (current instanceof Set) return { __type: "Set", values: Array.from(current.values()) };
4974
+ if (current instanceof Map) return { __type: "Map", entries: Array.from(current.entries()) };
4975
+ return current;
4976
+ })
4977
+ );
4978
+ }
4979
+ function safeCaptureJsonOrError(value) {
4980
+ try {
4981
+ return { ok: true, value: safeCaptureJson(value) };
4982
+ } catch (error) {
4983
+ return {
4984
+ ok: false,
4985
+ error: safeCaptureJson({
4986
+ message: error instanceof Error ? error.message : String(error),
4987
+ stack: error instanceof Error ? error.stack : void 0,
4988
+ inspected: util.inspect(value, { depth: 3, maxArrayLength: 20, breakLength: 120 })
4989
+ })
4990
+ };
4638
4991
  }
4639
- return content.metadata?.mastra;
4640
4992
  }
4641
- function ensureContentMastraMetadata(content) {
4642
- if (!content || typeof content !== "object") {
4993
+ function formatCaptureDate(value) {
4994
+ if (!value) return void 0;
4995
+ if (value instanceof Date) return value.toISOString();
4996
+ try {
4997
+ return new Date(value).toISOString();
4998
+ } catch {
4643
4999
  return void 0;
4644
5000
  }
4645
- const typedContent = content;
4646
- typedContent.metadata ??= {};
4647
- typedContent.metadata.mastra ??= {};
4648
- return typedContent.metadata.mastra;
4649
- }
4650
- function getMessageMastraMetadata(message) {
4651
- return message.metadata?.mastra;
5001
+ }
5002
+ function summarizeOmTurn(value) {
5003
+ if (!value || typeof value !== "object") {
5004
+ return value;
5005
+ }
5006
+ const turn = value;
5007
+ return {
5008
+ __type: "ObservationTurn",
5009
+ threadId: turn.threadId,
5010
+ resourceId: turn.resourceId,
5011
+ started: turn._started,
5012
+ ended: turn._ended,
5013
+ generationCountAtStart: turn._generationCountAtStart,
5014
+ record: turn._record ? {
5015
+ id: turn._record.id,
5016
+ scope: turn._record.scope,
5017
+ threadId: turn._record.threadId,
5018
+ resourceId: turn._record.resourceId,
5019
+ createdAt: formatCaptureDate(turn._record.createdAt),
5020
+ updatedAt: formatCaptureDate(turn._record.updatedAt),
5021
+ lastObservedAt: formatCaptureDate(turn._record.lastObservedAt),
5022
+ generationCount: turn._record.generationCount,
5023
+ observationTokenCount: turn._record.observationTokenCount,
5024
+ pendingMessageTokens: turn._record.pendingMessageTokens,
5025
+ isBufferingObservation: turn._record.isBufferingObservation,
5026
+ isBufferingReflection: turn._record.isBufferingReflection
5027
+ } : void 0,
5028
+ context: turn._context ? {
5029
+ messageCount: Array.isArray(turn._context.messages) ? turn._context.messages.length : void 0,
5030
+ hasSystemMessage: Array.isArray(turn._context.systemMessage) ? turn._context.systemMessage.length > 0 : Boolean(turn._context.systemMessage),
5031
+ continuationId: turn._context.continuation?.id,
5032
+ hasOtherThreadsContext: Boolean(turn._context.otherThreadsContext),
5033
+ recordId: turn._context.record?.id
5034
+ } : void 0,
5035
+ currentStep: turn._currentStep ? {
5036
+ stepNumber: turn._currentStep.stepNumber,
5037
+ prepared: turn._currentStep._prepared,
5038
+ context: turn._currentStep._context ? {
5039
+ activated: turn._currentStep._context.activated,
5040
+ observed: turn._currentStep._context.observed,
5041
+ buffered: turn._currentStep._context.buffered,
5042
+ reflected: turn._currentStep._context.reflected,
5043
+ didThresholdCleanup: turn._currentStep._context.didThresholdCleanup,
5044
+ messageCount: Array.isArray(turn._currentStep._context.messages) ? turn._currentStep._context.messages.length : void 0,
5045
+ systemMessageCount: Array.isArray(turn._currentStep._context.systemMessage) ? turn._currentStep._context.systemMessage.length : void 0
5046
+ } : void 0
5047
+ } : void 0
5048
+ };
5049
+ }
5050
+ function sanitizeCaptureState(rawState) {
5051
+ return Object.fromEntries(
5052
+ Object.entries(rawState).map(([key, value]) => {
5053
+ if (key === "__omTurn") {
5054
+ return [key, summarizeOmTurn(value)];
5055
+ }
5056
+ return [key, value];
5057
+ })
5058
+ );
5059
+ }
5060
+ function buildReproMessageFingerprint(message) {
5061
+ const createdAt = message.createdAt instanceof Date ? message.createdAt.toISOString() : message.createdAt ? new Date(message.createdAt).toISOString() : "";
5062
+ return JSON.stringify({
5063
+ role: message.role,
5064
+ createdAt,
5065
+ content: message.content
5066
+ });
5067
+ }
5068
+ function inferReproIdRemap(preMessages, postMessages) {
5069
+ const preByFingerprint = /* @__PURE__ */ new Map();
5070
+ const postByFingerprint = /* @__PURE__ */ new Map();
5071
+ for (const message of preMessages) {
5072
+ if (!message.id) continue;
5073
+ const fingerprint = buildReproMessageFingerprint(message);
5074
+ const list = preByFingerprint.get(fingerprint) ?? [];
5075
+ list.push(message.id);
5076
+ preByFingerprint.set(fingerprint, list);
5077
+ }
5078
+ for (const message of postMessages) {
5079
+ if (!message.id) continue;
5080
+ const fingerprint = buildReproMessageFingerprint(message);
5081
+ const list = postByFingerprint.get(fingerprint) ?? [];
5082
+ list.push(message.id);
5083
+ postByFingerprint.set(fingerprint, list);
5084
+ }
5085
+ const remap = [];
5086
+ for (const [fingerprint, preIds] of preByFingerprint.entries()) {
5087
+ const postIds = postByFingerprint.get(fingerprint);
5088
+ if (!postIds || preIds.length !== 1 || postIds.length !== 1) continue;
5089
+ const fromId = preIds[0];
5090
+ const toId = postIds[0];
5091
+ if (!fromId || !toId || fromId === toId) {
5092
+ continue;
5093
+ }
5094
+ remap.push({ fromId, toId, fingerprint });
5095
+ }
5096
+ return remap;
5097
+ }
5098
+ function createOmReproCaptureDir(threadId, label) {
5099
+ const sanitizedThreadId = sanitizeCapturePathSegment(threadId);
5100
+ const captureDir = path.join(
5101
+ process.cwd(),
5102
+ getOmReproCaptureDir(),
5103
+ sanitizedThreadId,
5104
+ `${Date.now()}-${label}-${crypto$1.randomUUID()}`
5105
+ );
5106
+ fs.mkdirSync(captureDir, { recursive: true });
5107
+ return captureDir;
5108
+ }
5109
+ function writeObserverExchangeReproCapture(params) {
5110
+ if (!isOmReproCaptureEnabled() || !params.observerExchange) {
5111
+ return;
5112
+ }
5113
+ try {
5114
+ const captureDir = createOmReproCaptureDir(params.threadId, params.label);
5115
+ const payloads = [
5116
+ {
5117
+ fileName: "input.json",
5118
+ data: {
5119
+ threadId: params.threadId,
5120
+ resourceId: params.resourceId,
5121
+ label: params.label
5122
+ }
5123
+ },
5124
+ {
5125
+ fileName: "output.json",
5126
+ data: {
5127
+ details: params.details ?? {}
5128
+ }
5129
+ },
5130
+ {
5131
+ fileName: "observer-exchange.json",
5132
+ data: params.observerExchange
5133
+ }
5134
+ ];
5135
+ const captureErrors = [];
5136
+ for (const payload of payloads) {
5137
+ const serialized = safeCaptureJsonOrError(payload.data);
5138
+ if (serialized.ok) {
5139
+ fs.writeFileSync(path.join(captureDir, payload.fileName), `${JSON.stringify(serialized.value, null, 2)}
5140
+ `);
5141
+ continue;
5142
+ }
5143
+ captureErrors.push({ fileName: payload.fileName, error: serialized.error });
5144
+ fs.writeFileSync(
5145
+ path.join(captureDir, payload.fileName),
5146
+ `${JSON.stringify({ __captureError: serialized.error }, null, 2)}
5147
+ `
5148
+ );
5149
+ }
5150
+ if (captureErrors.length > 0) {
5151
+ fs.writeFileSync(path.join(captureDir, "capture-error.json"), `${JSON.stringify(captureErrors, null, 2)}
5152
+ `);
5153
+ params.debug?.(
5154
+ `[OM:repro-capture] wrote ${params.label} capture with ${captureErrors.length} serialization error(s) to ${captureDir}`
5155
+ );
5156
+ return;
5157
+ }
5158
+ params.debug?.(`[OM:repro-capture] wrote ${params.label} capture to ${captureDir}`);
5159
+ } catch (error) {
5160
+ params.debug?.(`[OM:repro-capture] failed to write ${params.label} capture: ${String(error)}`);
5161
+ }
5162
+ }
5163
+ function writeProcessInputStepReproCapture(params) {
5164
+ if (!isOmReproCaptureEnabled()) {
5165
+ return;
5166
+ }
5167
+ try {
5168
+ const captureDir = createOmReproCaptureDir(params.threadId, `step-${params.stepNumber}`);
5169
+ const contextMessages = params.messageList.get.all.db();
5170
+ const memoryContext = memory.parseMemoryRequestContext(params.args.requestContext);
5171
+ const preMessageIds = new Set(params.preMessages.map((message) => message.id));
5172
+ const postMessageIds = new Set(contextMessages.map((message) => message.id));
5173
+ const removedMessageIds = params.preMessages.map((message) => message.id).filter((id) => Boolean(id) && !postMessageIds.has(id));
5174
+ const addedMessageIds = contextMessages.map((message) => message.id).filter((id) => Boolean(id) && !preMessageIds.has(id));
5175
+ const idRemap = inferReproIdRemap(params.preMessages, contextMessages);
5176
+ const rawState = params.args.state ?? {};
5177
+ const sanitizedState = sanitizeCaptureState(rawState);
5178
+ const payloads = [
5179
+ {
5180
+ fileName: "input.json",
5181
+ data: {
5182
+ stepNumber: params.stepNumber,
5183
+ threadId: params.threadId,
5184
+ resourceId: params.resourceId,
5185
+ readOnly: memoryContext?.memoryConfig?.readOnly,
5186
+ messageCount: contextMessages.length,
5187
+ messageIds: contextMessages.map((message) => message.id),
5188
+ stateKeys: Object.keys(rawState),
5189
+ state: sanitizedState,
5190
+ args: {
5191
+ messages: params.args.messages,
5192
+ steps: params.args.steps,
5193
+ systemMessages: params.args.systemMessages,
5194
+ retryCount: params.args.retryCount,
5195
+ toolChoice: params.args.toolChoice,
5196
+ activeTools: params.args.activeTools,
5197
+ modelSettings: params.args.modelSettings,
5198
+ structuredOutput: params.args.structuredOutput
5199
+ }
5200
+ }
5201
+ },
5202
+ {
5203
+ fileName: "pre-state.json",
5204
+ data: {
5205
+ record: params.preRecord,
5206
+ bufferedChunks: params.preBufferedChunks,
5207
+ contextTokenCount: params.preContextTokenCount,
5208
+ messages: params.preMessages,
5209
+ messageList: params.preSerializedMessageList
5210
+ }
5211
+ },
5212
+ {
5213
+ fileName: "output.json",
5214
+ data: {
5215
+ details: params.details,
5216
+ messageDiff: {
5217
+ removedMessageIds,
5218
+ addedMessageIds,
5219
+ idRemap
5220
+ }
5221
+ }
5222
+ },
5223
+ {
5224
+ fileName: "post-state.json",
5225
+ data: {
5226
+ record: params.postRecord,
5227
+ bufferedChunks: params.postBufferedChunks,
5228
+ contextTokenCount: params.postContextTokenCount,
5229
+ messageCount: contextMessages.length,
5230
+ messageIds: contextMessages.map((message) => message.id),
5231
+ messages: contextMessages,
5232
+ messageList: params.messageList.serialize()
5233
+ }
5234
+ }
5235
+ ];
5236
+ const captureErrors = [];
5237
+ for (const payload of payloads) {
5238
+ const serialized = safeCaptureJsonOrError(payload.data);
5239
+ if (serialized.ok) {
5240
+ fs.writeFileSync(path.join(captureDir, payload.fileName), `${JSON.stringify(serialized.value, null, 2)}
5241
+ `);
5242
+ continue;
5243
+ }
5244
+ captureErrors.push({ fileName: payload.fileName, error: serialized.error });
5245
+ fs.writeFileSync(
5246
+ path.join(captureDir, payload.fileName),
5247
+ `${JSON.stringify({ __captureError: serialized.error }, null, 2)}
5248
+ `
5249
+ );
5250
+ }
5251
+ if (params.observerExchange) {
5252
+ const serialized = safeCaptureJsonOrError(params.observerExchange);
5253
+ if (serialized.ok) {
5254
+ fs.writeFileSync(path.join(captureDir, "observer-exchange.json"), `${JSON.stringify(serialized.value, null, 2)}
5255
+ `);
5256
+ } else {
5257
+ captureErrors.push({ fileName: "observer-exchange.json", error: serialized.error });
5258
+ fs.writeFileSync(
5259
+ path.join(captureDir, "observer-exchange.json"),
5260
+ `${JSON.stringify({ __captureError: serialized.error }, null, 2)}
5261
+ `
5262
+ );
5263
+ }
5264
+ }
5265
+ if (captureErrors.length > 0) {
5266
+ fs.writeFileSync(path.join(captureDir, "capture-error.json"), `${JSON.stringify(captureErrors, null, 2)}
5267
+ `);
5268
+ params.debug?.(
5269
+ `[OM:repro-capture] wrote processInputStep capture with ${captureErrors.length} serialization error(s) to ${captureDir}`
5270
+ );
5271
+ return;
5272
+ }
5273
+ params.debug?.(`[OM:repro-capture] wrote processInputStep capture to ${captureDir}`);
5274
+ } catch (error) {
5275
+ params.debug?.(`[OM:repro-capture] failed to write processInputStep capture: ${String(error)}`);
5276
+ }
5277
+ }
5278
+ var IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
5279
+ "png",
5280
+ "jpg",
5281
+ "jpeg",
5282
+ "webp",
5283
+ "gif",
5284
+ "bmp",
5285
+ "tiff",
5286
+ "tif",
5287
+ "heic",
5288
+ "heif",
5289
+ "avif"
5290
+ ]);
5291
+ var TOKEN_ESTIMATE_CACHE_VERSION = 6;
5292
+ var DEFAULT_IMAGE_ESTIMATOR = {
5293
+ baseTokens: 85,
5294
+ tileTokens: 170,
5295
+ fallbackTiles: 4
5296
+ };
5297
+ var GOOGLE_LEGACY_IMAGE_TOKENS_PER_TILE = 258;
5298
+ var GOOGLE_GEMINI_3_IMAGE_TOKENS_BY_RESOLUTION = {
5299
+ low: 280,
5300
+ medium: 560,
5301
+ high: 1120,
5302
+ ultra_high: 2240,
5303
+ unspecified: 1120
5304
+ };
5305
+ var ANTHROPIC_IMAGE_TOKENS_PER_PIXEL = 1 / 750;
5306
+ var ANTHROPIC_IMAGE_MAX_LONG_EDGE = 1568;
5307
+ var GOOGLE_MEDIA_RESOLUTION_VALUES = /* @__PURE__ */ new Set([
5308
+ "low",
5309
+ "medium",
5310
+ "high",
5311
+ "ultra_high",
5312
+ "unspecified"
5313
+ ]);
5314
+ var ATTACHMENT_COUNT_TIMEOUT_MS = 2e4;
5315
+ var REMOTE_IMAGE_PROBE_TIMEOUT_MS = 2500;
5316
+ var PROVIDER_API_KEY_ENV_VARS = {
5317
+ openai: ["OPENAI_API_KEY"],
5318
+ google: ["GOOGLE_GENERATIVE_AI_API_KEY", "GOOGLE_API_KEY"],
5319
+ anthropic: ["ANTHROPIC_API_KEY"]
5320
+ };
5321
+ function getPartMastraMetadata(part) {
5322
+ return part.providerMetadata?.mastra;
5323
+ }
5324
+ function ensurePartMastraMetadata(part) {
5325
+ const typedPart = part;
5326
+ typedPart.providerMetadata ??= {};
5327
+ typedPart.providerMetadata.mastra ??= {};
5328
+ return typedPart.providerMetadata.mastra;
5329
+ }
5330
+ function getContentMastraMetadata(content) {
5331
+ if (!content || typeof content !== "object") {
5332
+ return void 0;
5333
+ }
5334
+ return content.metadata?.mastra;
5335
+ }
5336
+ function ensureContentMastraMetadata(content) {
5337
+ if (!content || typeof content !== "object") {
5338
+ return void 0;
5339
+ }
5340
+ const typedContent = content;
5341
+ typedContent.metadata ??= {};
5342
+ typedContent.metadata.mastra ??= {};
5343
+ return typedContent.metadata.mastra;
5344
+ }
5345
+ function getMessageMastraMetadata(message) {
5346
+ return message.metadata?.mastra;
4652
5347
  }
4653
5348
  function ensureMessageMastraMetadata(message) {
4654
5349
  const typedMessage = message;
@@ -5807,6 +6502,88 @@ function buildMessageRange(messages) {
5807
6502
  const last = [...messages].reverse().find(messageHasVisibleContent) ?? messages[messages.length - 1];
5808
6503
  return `${first.id}:${last.id}`;
5809
6504
  }
6505
+ function getLastActivityFromMessages(messages) {
6506
+ if (!messages) return void 0;
6507
+ for (let i = messages.length - 1; i >= 0; i--) {
6508
+ const message = messages[i];
6509
+ if (!message || message.role !== "assistant") {
6510
+ continue;
6511
+ }
6512
+ if (!message.content || typeof message.content === "string") {
6513
+ return message.createdAt ? new Date(message.createdAt).getTime() : void 0;
6514
+ }
6515
+ for (let j = message.content.parts.length - 1; j >= 0; j--) {
6516
+ const part = message.content.parts[j];
6517
+ if (!part || part.type?.startsWith("data-")) {
6518
+ continue;
6519
+ }
6520
+ if (part.createdAt !== void 0) {
6521
+ return part.createdAt;
6522
+ }
6523
+ }
6524
+ return message.createdAt ? new Date(message.createdAt).getTime() : void 0;
6525
+ }
6526
+ return void 0;
6527
+ }
6528
+ function formatModelContext2(provider, modelId) {
6529
+ if (provider && modelId) {
6530
+ return `${provider}/${modelId}`;
6531
+ }
6532
+ return modelId;
6533
+ }
6534
+ function getLastModelFromMessages(messages) {
6535
+ if (!messages) return void 0;
6536
+ for (let i = messages.length - 1; i >= 0; i--) {
6537
+ const message = messages[i];
6538
+ if (!message || message.role !== "assistant" || !message.content || typeof message.content === "string") {
6539
+ continue;
6540
+ }
6541
+ for (let j = message.content.parts.length - 1; j >= 0; j--) {
6542
+ const part = message.content.parts[j];
6543
+ if (part?.type === "step-start" && typeof part.model === "string" && part.model.length > 0) {
6544
+ return part.model;
6545
+ }
6546
+ }
6547
+ const metadata = message.content.metadata;
6548
+ const model = formatModelContext2(metadata?.provider, metadata?.modelId);
6549
+ if (model) {
6550
+ return model;
6551
+ }
6552
+ }
6553
+ return void 0;
6554
+ }
6555
+ function getCurrentModel2(model) {
6556
+ return formatModelContext2(model?.provider, model?.modelId);
6557
+ }
6558
+ function parseActivationTTL(value, fieldPath) {
6559
+ if (value === void 0) {
6560
+ return void 0;
6561
+ }
6562
+ if (typeof value === "number") {
6563
+ if (!Number.isFinite(value) || value < 0) {
6564
+ throw new Error(`${fieldPath} must be a non-negative number of milliseconds or a duration string like "5m".`);
6565
+ }
6566
+ return value;
6567
+ }
6568
+ const trimmed = value.trim();
6569
+ const match = trimmed.match(
6570
+ /^(\d+(?:\.\d+)?)\s*(ms|msec|msecs|millisecond|milliseconds|s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours)$/i
6571
+ );
6572
+ if (!match) {
6573
+ throw new Error(
6574
+ `${fieldPath} must be a non-negative number of milliseconds or a duration string like "5m" or "1hr".`
6575
+ );
6576
+ }
6577
+ const rawAmount = match[1];
6578
+ const rawUnit = match[2];
6579
+ const amount = Number(rawAmount);
6580
+ const unit = rawUnit.toLowerCase();
6581
+ if (!Number.isFinite(amount) || amount < 0) {
6582
+ throw new Error(`${fieldPath} must be a non-negative number of milliseconds or a duration string like "5m".`);
6583
+ }
6584
+ const multiplier = unit === "ms" || unit === "msec" || unit === "msecs" || unit === "millisecond" || unit === "milliseconds" ? 1 : unit === "s" || unit === "sec" || unit === "secs" || unit === "second" || unit === "seconds" ? 1e3 : unit === "m" || unit === "min" || unit === "mins" || unit === "minute" || unit === "minutes" ? 6e4 : 36e5;
6585
+ return amount * multiplier;
6586
+ }
5810
6587
  var ObservationalMemory = class _ObservationalMemory {
5811
6588
  storage;
5812
6589
  tokenCounter;
@@ -5953,6 +6730,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
5953
6730
  config.observation?.messageTokens ?? chunkD4J4XPGM_cjs.OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens
5954
6731
  ),
5955
6732
  bufferActivation: asyncBufferingDisabled ? void 0 : config.observation?.bufferActivation ?? chunkD4J4XPGM_cjs.OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferActivation,
6733
+ activateAfterIdle: parseActivationTTL(config.activateAfterIdle, "activateAfterIdle"),
6734
+ activateOnProviderChange: config.activateOnProviderChange ?? false,
5956
6735
  blockAfter: asyncBufferingDisabled ? void 0 : resolveBlockAfter(
5957
6736
  config.observation?.blockAfter ?? (config.observation?.bufferTokens ?? chunkD4J4XPGM_cjs.OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens ? 1.2 : void 0),
5958
6737
  config.observation?.messageTokens ?? chunkD4J4XPGM_cjs.OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens
@@ -5971,6 +6750,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
5971
6750
  },
5972
6751
  providerOptions: config.reflection?.providerOptions ?? chunkD4J4XPGM_cjs.OBSERVATIONAL_MEMORY_DEFAULTS.reflection.providerOptions,
5973
6752
  bufferActivation: asyncBufferingDisabled ? void 0 : config?.reflection?.bufferActivation ?? chunkD4J4XPGM_cjs.OBSERVATIONAL_MEMORY_DEFAULTS.reflection.bufferActivation,
6753
+ activateAfterIdle: parseActivationTTL(config.activateAfterIdle, "activateAfterIdle"),
6754
+ activateOnProviderChange: config.activateOnProviderChange ?? false,
5974
6755
  blockAfter: asyncBufferingDisabled ? void 0 : resolveBlockAfter(
5975
6756
  config.reflection?.blockAfter ?? (config.reflection?.bufferActivation ?? chunkD4J4XPGM_cjs.OBSERVATIONAL_MEMORY_DEFAULTS.reflection.bufferActivation ? 1.2 : void 0),
5976
6757
  config.reflection?.observationTokens ?? chunkD4J4XPGM_cjs.OBSERVATIONAL_MEMORY_DEFAULTS.reflection.observationTokens
@@ -6251,6 +7032,47 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
6251
7032
  }
6252
7033
  }
6253
7034
  }
7035
+ /**
7036
+ * Resolve the effective messageTokens for a record.
7037
+ * Only explicit per-record overrides (stored under `_overrides`) win;
7038
+ * the initial config snapshot written by getOrCreateRecord() is ignored
7039
+ * so that later instance-level changes still take effect.
7040
+ *
7041
+ * Overrides that fall below the instance-level buffering floor
7042
+ * (bufferTokens / absolute bufferActivation) are clamped to the
7043
+ * instance threshold to preserve buffering invariants.
7044
+ */
7045
+ getEffectiveMessageTokens(record) {
7046
+ const overrides = record.config?._overrides;
7047
+ const recordTokens = overrides?.observation?.messageTokens;
7048
+ if (recordTokens) {
7049
+ const maxOverride = getMaxThreshold(recordTokens);
7050
+ const bufferTokens = this.observationConfig.bufferTokens;
7051
+ if (bufferTokens && maxOverride <= bufferTokens) {
7052
+ return this.observationConfig.messageTokens;
7053
+ }
7054
+ const bufferActivation = this.observationConfig.bufferActivation;
7055
+ if (bufferActivation && bufferActivation >= 1e3 && maxOverride <= bufferActivation) {
7056
+ return this.observationConfig.messageTokens;
7057
+ }
7058
+ return recordTokens;
7059
+ }
7060
+ return this.observationConfig.messageTokens;
7061
+ }
7062
+ /**
7063
+ * Resolve the effective reflection observationTokens for a record.
7064
+ * Only explicit per-record overrides (stored under `_overrides`) win;
7065
+ * the initial config snapshot is ignored so instance-level changes
7066
+ * still take effect for existing records.
7067
+ */
7068
+ getEffectiveReflectionTokens(record) {
7069
+ const overrides = record.config?._overrides;
7070
+ const recordTokens = overrides?.reflection?.observationTokens;
7071
+ if (recordTokens) {
7072
+ return recordTokens;
7073
+ }
7074
+ return this.reflectionConfig.observationTokens;
7075
+ }
6254
7076
  /**
6255
7077
  * Check whether the unobserved message tokens meet the observation threshold.
6256
7078
  */
@@ -6258,7 +7080,7 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
6258
7080
  const { record, unobservedTokens, extraTokens = 0 } = opts;
6259
7081
  const pendingTokens = (record.pendingMessageTokens ?? 0) + unobservedTokens + extraTokens;
6260
7082
  const currentObservationTokens = record.observationTokenCount ?? 0;
6261
- const threshold = calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
7083
+ const threshold = calculateDynamicThreshold(this.getEffectiveMessageTokens(record), currentObservationTokens);
6262
7084
  return pendingTokens >= threshold;
6263
7085
  }
6264
7086
  /**
@@ -6324,7 +7146,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
6324
7146
  return {
6325
7147
  messageTokens: getMaxThreshold(this.observationConfig.messageTokens),
6326
7148
  observationTokens: getMaxThreshold(this.reflectionConfig.observationTokens),
6327
- scope: this.scope
7149
+ scope: this.scope,
7150
+ activateAfterIdle: this.observationConfig.activateAfterIdle
6328
7151
  };
6329
7152
  }
6330
7153
  /**
@@ -7045,17 +7868,18 @@ ${grouped}` : grouped;
7045
7868
  const cycleId = `buffer-obs-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
7046
7869
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7047
7870
  const tokensToBuffer = await this.tokenCounter.countMessagesAsync(messagesToBuffer);
7871
+ const startMarker = createBufferingStartMarker({
7872
+ cycleId,
7873
+ operationType: "observation",
7874
+ tokensToBuffer,
7875
+ recordId: freshRecord.id,
7876
+ threadId,
7877
+ threadIds: [threadId],
7878
+ config: this.getObservationMarkerConfig()
7879
+ });
7880
+ await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0);
7048
7881
  if (writer) {
7049
- const startMarker = createBufferingStartMarker({
7050
- cycleId,
7051
- operationType: "observation",
7052
- tokensToBuffer,
7053
- recordId: freshRecord.id,
7054
- threadId,
7055
- threadIds: [threadId],
7056
- config: this.getObservationMarkerConfig()
7057
- });
7058
- void writer.custom(startMarker).catch(() => {
7882
+ void writer.custom({ ...startMarker, transient: true }).catch(() => {
7059
7883
  });
7060
7884
  }
7061
7885
  omDebug(
@@ -7351,10 +8175,12 @@ ${grouped}` : grouped;
7351
8175
  async getOtherThreadsContext(resourceId, currentThreadId) {
7352
8176
  const { threads: allThreads } = await this.storage.listThreads({ filter: { resourceId } });
7353
8177
  const messagesByThread = /* @__PURE__ */ new Map();
8178
+ const record = await this.getRecord(currentThreadId, resourceId);
8179
+ const recordLastObservedAt = record?.lastObservedAt;
7354
8180
  for (const thread of allThreads) {
7355
8181
  if (thread.id === currentThreadId) continue;
7356
8182
  const omMetadata = memory.getThreadOMMetadata(thread.metadata);
7357
- const threadLastObservedAt = omMetadata?.lastObservedAt;
8183
+ const threadLastObservedAt = omMetadata?.lastObservedAt ?? recordLastObservedAt;
7358
8184
  const startDate = threadLastObservedAt ? new Date(new Date(threadLastObservedAt).getTime() + 1) : void 0;
7359
8185
  const result = await this.storage.listMessages({
7360
8186
  threadId: thread.id,
@@ -7407,7 +8233,7 @@ ${grouped}` : grouped;
7407
8233
  const projectedMessageRemoval = calculateProjectedMessageRemoval(
7408
8234
  bufferedChunks,
7409
8235
  this.observationConfig.bufferActivation ?? 1,
7410
- getMaxThreshold(this.observationConfig.messageTokens),
8236
+ getMaxThreshold(this.getEffectiveMessageTokens(record)),
7411
8237
  pendingTokens
7412
8238
  );
7413
8239
  let obsBufferStatus = "idle";
@@ -7496,7 +8322,7 @@ ${grouped}` : grouped;
7496
8322
  otherThreadTokens = otherContext ? this.tokenCounter.countString(otherContext) : 0;
7497
8323
  }
7498
8324
  const pendingTokens = Math.max(0, contextWindowTokens + otherThreadTokens);
7499
- const threshold = calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
8325
+ const threshold = calculateDynamicThreshold(this.getEffectiveMessageTokens(record), currentObservationTokens);
7500
8326
  const bufferedChunks = getBufferedChunks(record);
7501
8327
  const bufferedChunkCount = bufferedChunks.length;
7502
8328
  const bufferedChunkTokens = bufferedChunks.reduce((sum, chunk) => sum + (chunk.messageTokens ?? 0), 0);
@@ -7513,11 +8339,12 @@ ${grouped}` : grouped;
7513
8339
  );
7514
8340
  }
7515
8341
  const shouldObserve = pendingTokens >= threshold;
7516
- const reflectThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
8342
+ const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
7517
8343
  const shouldReflect = currentObservationTokens >= reflectThreshold;
7518
8344
  const canActivate = bufferedChunkCount > 0;
7519
- const isSharedBudget = typeof this.observationConfig.messageTokens !== "number";
7520
- const totalBudget = isSharedBudget ? this.observationConfig.messageTokens.max : 0;
8345
+ const effectiveMessageTokens = this.getEffectiveMessageTokens(record);
8346
+ const isSharedBudget = typeof effectiveMessageTokens !== "number";
8347
+ const totalBudget = isSharedBudget ? effectiveMessageTokens.max : 0;
7521
8348
  const effectiveObservationTokensThreshold = isSharedBudget ? Math.max(totalBudget - threshold, 1e3) : reflectThreshold;
7522
8349
  const unbufferedPendingTokens = Math.max(0, pendingTokens - bufferedChunkTokens);
7523
8350
  return {
@@ -7561,7 +8388,7 @@ ${grouped}` : grouped;
7561
8388
  await BufferingCoordinator.awaitBuffering(threadId, resourceId ?? null, this.scope);
7562
8389
  const preStatus = await this.getStatus({ threadId, resourceId, messages });
7563
8390
  if (preStatus.canActivate) {
7564
- const actResult = await this.activate({ threadId, resourceId });
8391
+ const actResult = await this.activate({ threadId, resourceId, messages });
7565
8392
  activated = actResult.activated;
7566
8393
  }
7567
8394
  const postStatus = await this.getStatus({ threadId, resourceId, messages });
@@ -7712,18 +8539,19 @@ ${grouped}` : grouped;
7712
8539
  }
7713
8540
  const cycleId = `buffer-obs-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
7714
8541
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
8542
+ const startMarker = createBufferingStartMarker({
8543
+ cycleId,
8544
+ operationType: "observation",
8545
+ tokensToBuffer: newTokens,
8546
+ recordId: record.id,
8547
+ threadId,
8548
+ threadIds: [threadId],
8549
+ config: this.getObservationMarkerConfig()
8550
+ });
8551
+ await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0);
7715
8552
  const writer = opts.writer;
7716
8553
  if (writer) {
7717
- const startMarker = createBufferingStartMarker({
7718
- cycleId,
7719
- operationType: "observation",
7720
- tokensToBuffer: newTokens,
7721
- recordId: record.id,
7722
- threadId,
7723
- threadIds: [threadId],
7724
- config: this.getObservationMarkerConfig()
7725
- });
7726
- void writer.custom(startMarker).catch(() => {
8554
+ void writer.custom({ ...startMarker, transient: true }).catch(() => {
7727
8555
  });
7728
8556
  }
7729
8557
  await ObservationStrategy.create(this, {
@@ -7737,6 +8565,23 @@ ${grouped}` : grouped;
7737
8565
  requestContext,
7738
8566
  observabilityContext
7739
8567
  }).run();
8568
+ if (isOmReproCaptureEnabled()) {
8569
+ writeObserverExchangeReproCapture({
8570
+ threadId,
8571
+ resourceId: record.resourceId ?? void 0,
8572
+ label: `buffer-${cycleId}`,
8573
+ observerExchange: this.observer.lastExchange,
8574
+ details: {
8575
+ cycleId,
8576
+ startedAt,
8577
+ buffered: true,
8578
+ candidateMessageIds: candidateMessages.map((message) => message.id),
8579
+ candidateMessageCount: candidateMessages.length,
8580
+ pendingTokens: currentTokens,
8581
+ newTokens
8582
+ }
8583
+ });
8584
+ }
7740
8585
  await this.storage.setBufferingObservationFlag(record.id, false, newTokens).catch(() => {
7741
8586
  });
7742
8587
  flagCleared = true;
@@ -7804,10 +8649,37 @@ ${grouped}` : grouped;
7804
8649
  if (!chunks.length) {
7805
8650
  return { activated: false, record };
7806
8651
  }
8652
+ let activationTriggeredBy = "threshold";
8653
+ let activationLastActivityAt;
8654
+ let activateAfterIdleExpiredMs;
8655
+ let previousModel;
8656
+ let currentModel;
7807
8657
  if (opts.checkThreshold) {
7808
- const status = await this.getStatus({ threadId, resourceId, messages: opts.messages });
7809
- if (status.pendingTokens < status.threshold) {
7810
- return { activated: false, record };
8658
+ const thresholdMessages = opts.messages ?? await this.loadMessagesFromStorage(
8659
+ threadId,
8660
+ resourceId,
8661
+ record.lastObservedAt ? new Date(record.lastObservedAt) : void 0
8662
+ );
8663
+ const activateAfterIdle = this.observationConfig.activateAfterIdle;
8664
+ const lastActivityAt = getLastActivityFromMessages(thresholdMessages);
8665
+ const ttlExpiredMs = activateAfterIdle !== void 0 && lastActivityAt !== void 0 ? Date.now() - lastActivityAt : void 0;
8666
+ const ttlExpired = ttlExpiredMs !== void 0 && activateAfterIdle !== void 0 && ttlExpiredMs >= activateAfterIdle;
8667
+ const actorModel = getCurrentModel2(opts.currentModel);
8668
+ const lastModel = getLastModelFromMessages(thresholdMessages);
8669
+ const providerChanged = this.observationConfig.activateOnProviderChange === true && didProviderChange(actorModel, lastModel);
8670
+ if (providerChanged) {
8671
+ activationTriggeredBy = "provider_change";
8672
+ previousModel = lastModel;
8673
+ currentModel = actorModel;
8674
+ } else if (ttlExpired) {
8675
+ activationTriggeredBy = "ttl";
8676
+ activationLastActivityAt = lastActivityAt;
8677
+ activateAfterIdleExpiredMs = ttlExpiredMs;
8678
+ } else {
8679
+ const status = await this.getStatus({ threadId, resourceId, messages: thresholdMessages });
8680
+ if (status.pendingTokens < status.threshold) {
8681
+ return { activated: false, record };
8682
+ }
7811
8683
  }
7812
8684
  }
7813
8685
  if (record.isBufferingObservation) {
@@ -7832,7 +8704,7 @@ ${grouped}` : grouped;
7832
8704
  if (!freshChunks.length) {
7833
8705
  return { activated: false, record };
7834
8706
  }
7835
- const messageTokensThreshold = getMaxThreshold(this.observationConfig.messageTokens);
8707
+ const messageTokensThreshold = getMaxThreshold(this.getEffectiveMessageTokens(freshRecord));
7836
8708
  const bufferActivation = this.observationConfig.bufferActivation ?? 0.7;
7837
8709
  const activationRatio = resolveActivationRatio(bufferActivation, messageTokensThreshold);
7838
8710
  const totalChunkMessageTokens = freshChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
@@ -7865,9 +8737,14 @@ ${grouped}` : grouped;
7865
8737
  threadId: postSwapRecord.threadId ?? record.threadId ?? "",
7866
8738
  generationCount: postSwapRecord.generationCount ?? 0,
7867
8739
  observations: chunkData?.observations ?? activationResult.observations,
8740
+ triggeredBy: activationTriggeredBy,
8741
+ lastActivityAt: activationLastActivityAt,
8742
+ ttlExpiredMs: activateAfterIdleExpiredMs,
8743
+ previousModel,
8744
+ currentModel,
7868
8745
  config: this.getObservationMarkerConfig()
7869
8746
  });
7870
- void opts.writer.custom(activationMarker).catch(() => {
8747
+ void opts.writer.custom({ ...activationMarker, transient: true }).catch(() => {
7871
8748
  });
7872
8749
  await this.persistMarkerToMessage(
7873
8750
  activationMarker,
@@ -7920,6 +8797,7 @@ ${grouped}` : grouped;
7920
8797
  const lockKey = this.buffering.getLockKey(threadId, resourceId);
7921
8798
  const reflectionHooks = hooks ? { onReflectionStart: hooks.onReflectionStart, onReflectionEnd: hooks.onReflectionEnd } : void 0;
7922
8799
  let observed = false;
8800
+ let observationUsage;
7923
8801
  let generationBefore = -1;
7924
8802
  await this.withLock(lockKey, async () => {
7925
8803
  const freshRecord = await this.getOrCreateRecord(threadId, resourceId);
@@ -7936,8 +8814,9 @@ ${grouped}` : grouped;
7936
8814
  return;
7937
8815
  }
7938
8816
  hooks?.onObservationStart?.();
8817
+ let observationError;
7939
8818
  try {
7940
- observed = await ObservationStrategy.create(this, {
8819
+ const result = await ObservationStrategy.create(this, {
7941
8820
  record: freshRecord,
7942
8821
  threadId,
7943
8822
  resourceId,
@@ -7947,8 +8826,13 @@ ${grouped}` : grouped;
7947
8826
  writer: opts.writer,
7948
8827
  observabilityContext: opts.observabilityContext
7949
8828
  }).run();
8829
+ observed = result.observed;
8830
+ observationUsage = result.usage;
8831
+ } catch (error) {
8832
+ observationError = error instanceof Error ? error : new Error(String(error));
8833
+ throw error;
7950
8834
  } finally {
7951
- hooks?.onObservationEnd?.();
8835
+ hooks?.onObservationEnd?.({ usage: observationUsage, error: observationError });
7952
8836
  }
7953
8837
  });
7954
8838
  const record = await this.getOrCreateRecord(threadId, resourceId);
@@ -7969,12 +8853,12 @@ ${grouped}` : grouped;
7969
8853
  async reflect(threadId, resourceId, prompt, requestContext, observabilityContext) {
7970
8854
  const record = await this.getOrCreateRecord(threadId, resourceId);
7971
8855
  if (!record.activeObservations) {
7972
- return { reflected: false, record };
8856
+ return { reflected: false, record, usage: void 0 };
7973
8857
  }
7974
8858
  await this.storage.setReflectingFlag(record.id, true);
7975
8859
  registerOp(record.id, "reflecting");
7976
8860
  try {
7977
- const reflectThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
8861
+ const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
7978
8862
  const reflectResult = await this.reflector.call(
7979
8863
  record.activeObservations,
7980
8864
  prompt,
@@ -7994,11 +8878,11 @@ ${grouped}` : grouped;
7994
8878
  tokenCount: reflectionTokenCount
7995
8879
  });
7996
8880
  const updatedRecord = await this.getOrCreateRecord(threadId, resourceId);
7997
- return { reflected: true, record: updatedRecord };
8881
+ return { reflected: true, record: updatedRecord, usage: reflectResult.usage };
7998
8882
  } catch (error) {
7999
8883
  omError("[OM] reflect() failed", error);
8000
8884
  const latestRecord = await this.getOrCreateRecord(threadId, resourceId);
8001
- return { reflected: false, record: latestRecord };
8885
+ return { reflected: false, record: latestRecord, usage: void 0 };
8002
8886
  } finally {
8003
8887
  await this.storage.setReflectingFlag(record.id, false);
8004
8888
  unregisterOp(record.id, "reflecting");
@@ -8019,12 +8903,40 @@ ${grouped}` : grouped;
8019
8903
  const ids = this.getStorageIds(threadId, resourceId);
8020
8904
  return this.storage.getObservationalMemory(ids.threadId, ids.resourceId);
8021
8905
  }
8906
+ /**
8907
+ * Update per-record config overrides for observation and/or reflection thresholds.
8908
+ * The provided config is deep-merged into the record's `_overrides` key,
8909
+ * so you only need to specify the fields you want to change.
8910
+ *
8911
+ * Overrides that violate buffering invariants (e.g. messageTokens below
8912
+ * bufferTokens) are silently ignored at read time — the helpers fall back
8913
+ * to the instance-level config.
8914
+ *
8915
+ * @example
8916
+ * ```ts
8917
+ * await om.updateRecordConfig('thread-1', undefined, {
8918
+ * observation: { messageTokens: 2000 },
8919
+ * reflection: { observationTokens: 8000 },
8920
+ * });
8921
+ * ```
8922
+ */
8923
+ async updateRecordConfig(threadId, resourceId, config) {
8924
+ const ids = this.getStorageIds(threadId, resourceId);
8925
+ const record = await this.storage.getObservationalMemory(ids.threadId, ids.resourceId);
8926
+ if (!record) {
8927
+ throw new Error(`No observational memory record found for thread ${ids.threadId}`);
8928
+ }
8929
+ await this.storage.updateObservationalMemoryConfig({
8930
+ id: record.id,
8931
+ config: { _overrides: config }
8932
+ });
8933
+ }
8022
8934
  /**
8023
8935
  * Get observation history (previous generations)
8024
8936
  */
8025
- async getHistory(threadId, resourceId, limit) {
8937
+ async getHistory(threadId, resourceId, limit, options) {
8026
8938
  const ids = this.getStorageIds(threadId, resourceId);
8027
- return this.storage.getObservationalMemoryHistory(ids.threadId, ids.resourceId, limit);
8939
+ return this.storage.getObservationalMemoryHistory(ids.threadId, ids.resourceId, limit, options);
8028
8940
  }
8029
8941
  /**
8030
8942
  * Clear all memory for a specific thread/resource
@@ -8067,293 +8979,137 @@ ${grouped}` : grouped;
8067
8979
  /**
8068
8980
  * Get whether thread IDs should be obscured in observations.
8069
8981
  */
8070
- getObscureThreadIds() {
8071
- return this.shouldObscureThreadIds;
8072
- }
8073
- /**
8074
- * Begin a new observation turn — the high-level API for managing the
8075
- * observe/buffer/activate/reflect lifecycle across agentic loop steps.
8076
- *
8077
- * @example
8078
- * ```ts
8079
- * const turn = om.beginTurn({ threadId, resourceId, messageList });
8080
- * await turn.start(memory);
8081
- *
8082
- * const step0 = turn.step(0);
8083
- * const ctx = await step0.prepare();
8084
- * // ... agent generates ...
8085
- *
8086
- * await turn.end();
8087
- * ```
8088
- */
8089
- beginTurn(opts) {
8090
- return new ObservationTurn({
8091
- om: this,
8092
- threadId: opts.threadId,
8093
- resourceId: opts.resourceId,
8094
- messageList: opts.messageList,
8095
- observabilityContext: opts.observabilityContext,
8096
- hooks: opts.hooks
8097
- });
8098
- }
8099
- };
8100
- var OM_REPRO_CAPTURE_DIR = process.env.OM_REPRO_CAPTURE_DIR ?? ".mastra-om-repro";
8101
- function sanitizeCapturePathSegment(value) {
8102
- const sanitized = value.replace(/[\\/]+/g, "_").replace(/\.{2,}/g, "_").trim();
8103
- return sanitized.length > 0 ? sanitized : "unknown-thread";
8104
- }
8105
- function isOmReproCaptureEnabled() {
8106
- return process.env.OM_REPRO_CAPTURE === "1";
8107
- }
8108
- function safeCaptureJson(value) {
8109
- return JSON.parse(
8110
- JSON.stringify(value, (_key, current) => {
8111
- if (typeof current === "bigint") return current.toString();
8112
- if (typeof current === "function") return "[function]";
8113
- if (typeof current === "symbol") return current.toString();
8114
- if (current instanceof Error) return { name: current.name, message: current.message, stack: current.stack };
8115
- if (current instanceof Set) return { __type: "Set", values: Array.from(current.values()) };
8116
- if (current instanceof Map) return { __type: "Map", entries: Array.from(current.entries()) };
8117
- return current;
8118
- })
8119
- );
8120
- }
8121
- function safeCaptureJsonOrError(value) {
8122
- try {
8123
- return { ok: true, value: safeCaptureJson(value) };
8124
- } catch (error) {
8125
- return {
8126
- ok: false,
8127
- error: safeCaptureJson({
8128
- message: error instanceof Error ? error.message : String(error),
8129
- stack: error instanceof Error ? error.stack : void 0,
8130
- inspected: util.inspect(value, { depth: 3, maxArrayLength: 20, breakLength: 120 })
8131
- })
8132
- };
8133
- }
8134
- }
8135
- function formatCaptureDate(value) {
8136
- if (!value) return void 0;
8137
- if (value instanceof Date) return value.toISOString();
8138
- try {
8139
- return new Date(value).toISOString();
8140
- } catch {
8141
- return void 0;
8982
+ getObscureThreadIds() {
8983
+ return this.shouldObscureThreadIds;
8142
8984
  }
8143
- }
8144
- function summarizeOmTurn(value) {
8145
- if (!value || typeof value !== "object") {
8146
- return value;
8985
+ /**
8986
+ * Begin a new observation turn — the high-level API for managing the
8987
+ * observe/buffer/activate/reflect lifecycle across agentic loop steps.
8988
+ *
8989
+ * @example
8990
+ * ```ts
8991
+ * const turn = om.beginTurn({ threadId, resourceId, messageList });
8992
+ * await turn.start(memory);
8993
+ *
8994
+ * const step0 = turn.step(0);
8995
+ * const ctx = await step0.prepare();
8996
+ * // ... agent generates ...
8997
+ *
8998
+ * await turn.end();
8999
+ * ```
9000
+ */
9001
+ beginTurn(opts) {
9002
+ return new ObservationTurn({
9003
+ om: this,
9004
+ threadId: opts.threadId,
9005
+ resourceId: opts.resourceId,
9006
+ messageList: opts.messageList,
9007
+ observabilityContext: opts.observabilityContext,
9008
+ hooks: opts.hooks
9009
+ });
8147
9010
  }
8148
- const turn = value;
9011
+ };
9012
+
9013
+ // src/processors/observational-memory/temporal-markers.ts
9014
+ var TEMPORAL_GAP_REMINDER_TYPE = "temporal-gap";
9015
+ function getTemporalGapReminderText(gapText, timestamp) {
9016
+ return `${gapText} \u2014 ${formatTemporalTimestamp(new Date(timestamp))}`;
9017
+ }
9018
+ function getTemporalGapReminderMetadata(message, gapText, gapMs, timestamp) {
9019
+ const formattedTimestamp = formatTemporalTimestamp(new Date(timestamp));
8149
9020
  return {
8150
- __type: "ObservationTurn",
8151
- threadId: turn.threadId,
8152
- resourceId: turn.resourceId,
8153
- started: turn._started,
8154
- ended: turn._ended,
8155
- generationCountAtStart: turn._generationCountAtStart,
8156
- record: turn._record ? {
8157
- id: turn._record.id,
8158
- scope: turn._record.scope,
8159
- threadId: turn._record.threadId,
8160
- resourceId: turn._record.resourceId,
8161
- createdAt: formatCaptureDate(turn._record.createdAt),
8162
- updatedAt: formatCaptureDate(turn._record.updatedAt),
8163
- lastObservedAt: formatCaptureDate(turn._record.lastObservedAt),
8164
- generationCount: turn._record.generationCount,
8165
- observationTokenCount: turn._record.observationTokenCount,
8166
- pendingMessageTokens: turn._record.pendingMessageTokens,
8167
- isBufferingObservation: turn._record.isBufferingObservation,
8168
- isBufferingReflection: turn._record.isBufferingReflection
8169
- } : void 0,
8170
- context: turn._context ? {
8171
- messageCount: Array.isArray(turn._context.messages) ? turn._context.messages.length : void 0,
8172
- hasSystemMessage: Array.isArray(turn._context.systemMessage) ? turn._context.systemMessage.length > 0 : Boolean(turn._context.systemMessage),
8173
- continuationId: turn._context.continuation?.id,
8174
- hasOtherThreadsContext: Boolean(turn._context.otherThreadsContext),
8175
- recordId: turn._context.record?.id
8176
- } : void 0,
8177
- currentStep: turn._currentStep ? {
8178
- stepNumber: turn._currentStep.stepNumber,
8179
- prepared: turn._currentStep._prepared,
8180
- context: turn._currentStep._context ? {
8181
- activated: turn._currentStep._context.activated,
8182
- observed: turn._currentStep._context.observed,
8183
- buffered: turn._currentStep._context.buffered,
8184
- reflected: turn._currentStep._context.reflected,
8185
- didThresholdCleanup: turn._currentStep._context.didThresholdCleanup,
8186
- messageCount: Array.isArray(turn._currentStep._context.messages) ? turn._currentStep._context.messages.length : void 0,
8187
- systemMessageCount: Array.isArray(turn._currentStep._context.systemMessage) ? turn._currentStep._context.systemMessage.length : void 0
8188
- } : void 0
8189
- } : void 0
9021
+ reminderType: TEMPORAL_GAP_REMINDER_TYPE,
9022
+ gapText,
9023
+ gapMs,
9024
+ timestamp: formattedTimestamp,
9025
+ timestampMs: timestamp,
9026
+ precedesMessageId: message.id,
9027
+ systemReminder: {
9028
+ type: TEMPORAL_GAP_REMINDER_TYPE,
9029
+ message: getTemporalGapReminderText(gapText, timestamp),
9030
+ gapText,
9031
+ gapMs,
9032
+ timestamp: formattedTimestamp,
9033
+ timestampMs: timestamp,
9034
+ precedesMessageId: message.id
9035
+ }
8190
9036
  };
8191
9037
  }
8192
- function sanitizeCaptureState(rawState) {
8193
- return Object.fromEntries(
8194
- Object.entries(rawState).map(([key, value]) => {
8195
- if (key === "__omTurn") {
8196
- return [key, summarizeOmTurn(value)];
8197
- }
8198
- return [key, value];
8199
- })
8200
- );
9038
+ function isTemporalGapMarkerForMessage(message, targetMessageId) {
9039
+ if (!isTemporalGapMarker(message)) {
9040
+ return false;
9041
+ }
9042
+ const metadata = message.content.metadata;
9043
+ if (metadata?.precedesMessageId === targetMessageId) {
9044
+ return true;
9045
+ }
9046
+ return metadata?.systemReminder?.type === TEMPORAL_GAP_REMINDER_TYPE && metadata.systemReminder.precedesMessageId === targetMessageId;
8201
9047
  }
8202
- function buildReproMessageFingerprint(message) {
8203
- const createdAt = message.createdAt instanceof Date ? message.createdAt.toISOString() : message.createdAt ? new Date(message.createdAt).toISOString() : "";
8204
- return JSON.stringify({
8205
- role: message.role,
8206
- createdAt,
8207
- content: message.content
8208
- });
9048
+ function createTemporalGapMarker(message, gapText, gapMs, timestamp) {
9049
+ const metadata = getTemporalGapReminderMetadata(message, gapText, gapMs, timestamp);
9050
+ return {
9051
+ id: `__temporal_gap_${crypto.randomUUID()}`,
9052
+ role: "user",
9053
+ createdAt: new Date(timestamp - 1),
9054
+ threadId: message.threadId,
9055
+ resourceId: message.resourceId,
9056
+ content: {
9057
+ format: 2,
9058
+ parts: [
9059
+ {
9060
+ type: "text",
9061
+ text: `<system-reminder type="${TEMPORAL_GAP_REMINDER_TYPE}" precedesMessageId="${message.id}">${getTemporalGapReminderText(gapText, timestamp)}</system-reminder>`
9062
+ }
9063
+ ],
9064
+ metadata
9065
+ }
9066
+ };
8209
9067
  }
8210
- function inferReproIdRemap(preMessages, postMessages) {
8211
- const preByFingerprint = /* @__PURE__ */ new Map();
8212
- const postByFingerprint = /* @__PURE__ */ new Map();
8213
- for (const message of preMessages) {
8214
- if (!message.id) continue;
8215
- const fingerprint = buildReproMessageFingerprint(message);
8216
- const list = preByFingerprint.get(fingerprint) ?? [];
8217
- list.push(message.id);
8218
- preByFingerprint.set(fingerprint, list);
9068
+ async function insertTemporalGapMarkers({
9069
+ messageList,
9070
+ writer
9071
+ }) {
9072
+ const inputMessages = messageList.get.input.db().filter((message) => Boolean(message));
9073
+ const latestInputMessage = inputMessages.at(-1);
9074
+ if (!latestInputMessage || isTemporalGapMarker(latestInputMessage)) {
9075
+ return;
8219
9076
  }
8220
- for (const message of postMessages) {
8221
- if (!message.id) continue;
8222
- const fingerprint = buildReproMessageFingerprint(message);
8223
- const list = postByFingerprint.get(fingerprint) ?? [];
8224
- list.push(message.id);
8225
- postByFingerprint.set(fingerprint, list);
9077
+ const allMessages = messageList.get.all.db().filter((message) => Boolean(message));
9078
+ const latestInputIndex = allMessages.findIndex((message) => message.id === latestInputMessage.id);
9079
+ if (latestInputIndex <= 0) {
9080
+ return;
8226
9081
  }
8227
- const remap = [];
8228
- for (const [fingerprint, preIds] of preByFingerprint.entries()) {
8229
- const postIds = postByFingerprint.get(fingerprint);
8230
- if (!postIds || preIds.length !== 1 || postIds.length !== 1) continue;
8231
- const fromId = preIds[0];
8232
- const toId = postIds[0];
8233
- if (!fromId || !toId || fromId === toId) {
8234
- continue;
9082
+ if (allMessages.some((message) => isTemporalGapMarkerForMessage(message, latestInputMessage.id))) {
9083
+ return;
9084
+ }
9085
+ let previousNonMarker;
9086
+ for (let index = latestInputIndex - 1; index >= 0; index--) {
9087
+ const candidate = allMessages[index];
9088
+ if (candidate && !isTemporalGapMarker(candidate)) {
9089
+ previousNonMarker = candidate;
9090
+ break;
8235
9091
  }
8236
- remap.push({ fromId, toId, fingerprint });
8237
9092
  }
8238
- return remap;
8239
- }
8240
- function writeProcessInputStepReproCapture(params) {
8241
- if (!isOmReproCaptureEnabled()) {
9093
+ if (!previousNonMarker) {
8242
9094
  return;
8243
9095
  }
8244
- try {
8245
- const sanitizedThreadId = sanitizeCapturePathSegment(params.threadId);
8246
- const runId = `${Date.now()}-step-${params.stepNumber}-${crypto$1.randomUUID()}`;
8247
- const captureDir = path.join(process.cwd(), OM_REPRO_CAPTURE_DIR, sanitizedThreadId, runId);
8248
- fs.mkdirSync(captureDir, { recursive: true });
8249
- const contextMessages = params.messageList.get.all.db();
8250
- const memoryContext = memory.parseMemoryRequestContext(params.args.requestContext);
8251
- const preMessageIds = new Set(params.preMessages.map((message) => message.id));
8252
- const postMessageIds = new Set(contextMessages.map((message) => message.id));
8253
- const removedMessageIds = params.preMessages.map((message) => message.id).filter((id) => Boolean(id) && !postMessageIds.has(id));
8254
- const addedMessageIds = contextMessages.map((message) => message.id).filter((id) => Boolean(id) && !preMessageIds.has(id));
8255
- const idRemap = inferReproIdRemap(params.preMessages, contextMessages);
8256
- const rawState = params.args.state ?? {};
8257
- const sanitizedState = sanitizeCaptureState(rawState);
8258
- const payloads = [
8259
- {
8260
- fileName: "input.json",
8261
- data: {
8262
- stepNumber: params.stepNumber,
8263
- threadId: params.threadId,
8264
- resourceId: params.resourceId,
8265
- readOnly: memoryContext?.memoryConfig?.readOnly,
8266
- messageCount: contextMessages.length,
8267
- messageIds: contextMessages.map((message) => message.id),
8268
- stateKeys: Object.keys(rawState),
8269
- state: sanitizedState,
8270
- args: {
8271
- messages: params.args.messages,
8272
- steps: params.args.steps,
8273
- systemMessages: params.args.systemMessages,
8274
- retryCount: params.args.retryCount,
8275
- toolChoice: params.args.toolChoice,
8276
- activeTools: params.args.activeTools,
8277
- modelSettings: params.args.modelSettings,
8278
- structuredOutput: params.args.structuredOutput
8279
- }
8280
- }
8281
- },
8282
- {
8283
- fileName: "pre-state.json",
8284
- data: {
8285
- record: params.preRecord,
8286
- bufferedChunks: params.preBufferedChunks,
8287
- contextTokenCount: params.preContextTokenCount,
8288
- messages: params.preMessages,
8289
- messageList: params.preSerializedMessageList
8290
- }
8291
- },
8292
- {
8293
- fileName: "output.json",
8294
- data: {
8295
- details: params.details,
8296
- messageDiff: {
8297
- removedMessageIds,
8298
- addedMessageIds,
8299
- idRemap
8300
- }
8301
- }
8302
- },
8303
- {
8304
- fileName: "post-state.json",
8305
- data: {
8306
- record: params.postRecord,
8307
- bufferedChunks: params.postBufferedChunks,
8308
- contextTokenCount: params.postContextTokenCount,
8309
- messageCount: contextMessages.length,
8310
- messageIds: contextMessages.map((message) => message.id),
8311
- messages: contextMessages,
8312
- messageList: params.messageList.serialize()
8313
- }
8314
- }
8315
- ];
8316
- const captureErrors = [];
8317
- for (const payload of payloads) {
8318
- const serialized = safeCaptureJsonOrError(payload.data);
8319
- if (serialized.ok) {
8320
- fs.writeFileSync(path.join(captureDir, payload.fileName), `${JSON.stringify(serialized.value, null, 2)}
8321
- `);
8322
- continue;
8323
- }
8324
- captureErrors.push({ fileName: payload.fileName, error: serialized.error });
8325
- fs.writeFileSync(
8326
- path.join(captureDir, payload.fileName),
8327
- `${JSON.stringify({ __captureError: serialized.error }, null, 2)}
8328
- `
8329
- );
8330
- }
8331
- if (params.observerExchange) {
8332
- const serialized = safeCaptureJsonOrError(params.observerExchange);
8333
- if (serialized.ok) {
8334
- fs.writeFileSync(path.join(captureDir, "observer-exchange.json"), `${JSON.stringify(serialized.value, null, 2)}
8335
- `);
8336
- } else {
8337
- captureErrors.push({ fileName: "observer-exchange.json", error: serialized.error });
8338
- fs.writeFileSync(
8339
- path.join(captureDir, "observer-exchange.json"),
8340
- `${JSON.stringify({ __captureError: serialized.error }, null, 2)}
8341
- `
8342
- );
8343
- }
8344
- }
8345
- if (captureErrors.length > 0) {
8346
- fs.writeFileSync(path.join(captureDir, "capture-error.json"), `${JSON.stringify(captureErrors, null, 2)}
8347
- `);
8348
- params.debug?.(
8349
- `[OM:repro-capture] wrote processInputStep capture with ${captureErrors.length} serialization error(s) to ${captureDir}`
8350
- );
8351
- return;
8352
- }
8353
- params.debug?.(`[OM:repro-capture] wrote processInputStep capture to ${captureDir}`);
8354
- } catch (error) {
8355
- params.debug?.(`[OM:repro-capture] failed to write processInputStep capture: ${String(error)}`);
9096
+ const timestamp = getMessagePartTimestamp(latestInputMessage, "first");
9097
+ const gapMs = timestamp - getMessagePartTimestamp(previousNonMarker, "last");
9098
+ const gapText = formatTemporalGap(gapMs);
9099
+ if (!gapText) {
9100
+ return;
8356
9101
  }
9102
+ const reminderMetadata = getTemporalGapReminderMetadata(latestInputMessage, gapText, gapMs, timestamp);
9103
+ await writer?.custom({
9104
+ type: "data-system-reminder",
9105
+ data: {
9106
+ message: getTemporalGapReminderText(gapText, timestamp),
9107
+ ...reminderMetadata
9108
+ },
9109
+ transient: true
9110
+ });
9111
+ const marker = createTemporalGapMarker(latestInputMessage, gapText, gapMs, timestamp);
9112
+ messageList.add(marker, "input");
8357
9113
  }
8358
9114
 
8359
9115
  // src/processors/observational-memory/processor.ts
@@ -8368,6 +9124,10 @@ function getOmObservabilityContext(args) {
8368
9124
  metrics: args.metrics
8369
9125
  };
8370
9126
  }
9127
+ var GATEWAY_STATE_KEY = "__isGatewayModel";
9128
+ function isMastraGatewayModel(model) {
9129
+ return typeof model === "object" && model !== null && "gatewayId" in model && model.gatewayId === "mastra";
9130
+ }
8371
9131
  var ObservationalMemoryProcessor = class {
8372
9132
  id = "observational-memory";
8373
9133
  name = "Observational Memory";
@@ -8375,11 +9135,14 @@ var ObservationalMemoryProcessor = class {
8375
9135
  engine;
8376
9136
  /** Memory instance for loading context. */
8377
9137
  memory;
9138
+ /** Whether temporal-gap reminder markers should be inserted. */
9139
+ temporalMarkers;
8378
9140
  /** Active turn — created on first processInputStep, ended on processOutputResult. */
8379
9141
  turn;
8380
- constructor(engine, memory) {
9142
+ constructor(engine, memory, options) {
8381
9143
  this.engine = engine;
8382
9144
  this.memory = memory;
9145
+ this.temporalMarkers = options?.temporalMarkers ?? false;
8383
9146
  }
8384
9147
  // ─── Processor lifecycle hooks ──────────────────────────────────────────
8385
9148
  async processInputStep(args) {
@@ -8403,6 +9166,11 @@ var ObservationalMemoryProcessor = class {
8403
9166
  omDebug(`[OM:processInputStep:NO-CONTEXT] getThreadContext returned null \u2014 returning early`);
8404
9167
  return messageList;
8405
9168
  }
9169
+ if (isMastraGatewayModel(model)) {
9170
+ state[GATEWAY_STATE_KEY] = true;
9171
+ omDebug(`[OM:processInputStep:GATEWAY] gateway handles OM \u2014 skipping local processing`);
9172
+ return messageList;
9173
+ }
8406
9174
  const { threadId, resourceId } = context;
8407
9175
  const memoryContext = memory.parseMemoryRequestContext(requestContext);
8408
9176
  const readOnly = memoryContext?.memoryConfig?.readOnly;
@@ -8416,6 +9184,15 @@ var ObservationalMemoryProcessor = class {
8416
9184
  if (readOnly) {
8417
9185
  return messageList;
8418
9186
  }
9187
+ const activeTurn = state.__omTurn ?? this.turn;
9188
+ if (activeTurn && activeTurn.messageList !== messageList) {
9189
+ await activeTurn.end().catch(() => {
9190
+ });
9191
+ if (this.turn === activeTurn) {
9192
+ this.turn = void 0;
9193
+ }
9194
+ state.__omTurn = void 0;
9195
+ }
8419
9196
  if (!this.turn || !state.__omTurn) {
8420
9197
  if (this.turn && !state.__omTurn) {
8421
9198
  await this.turn.end().catch(() => {
@@ -8427,17 +9204,26 @@ var ObservationalMemoryProcessor = class {
8427
9204
  messageList,
8428
9205
  observabilityContext: getOmObservabilityContext(args),
8429
9206
  hooks: {
8430
- onBufferChunkSealed: rotateResponseMessageId
9207
+ onBufferChunkSealed: rotateResponseMessageId,
9208
+ onSyncObservationComplete: rotateResponseMessageId
8431
9209
  }
8432
9210
  });
8433
9211
  this.turn.writer = writer;
8434
9212
  this.turn.requestContext = requestContext;
8435
9213
  await this.turn.start(this.memory);
9214
+ if (stepNumber === 0 && this.temporalMarkers) {
9215
+ await insertTemporalGapMarkers({ messageList, writer });
9216
+ }
8436
9217
  state.__omTurn = this.turn;
8437
9218
  }
9219
+ this.turn.addHooks({
9220
+ onBufferChunkSealed: rotateResponseMessageId,
9221
+ onSyncObservationComplete: rotateResponseMessageId
9222
+ });
8438
9223
  const observabilityContext = getOmObservabilityContext(args);
8439
9224
  state.__omObservabilityContext = observabilityContext;
8440
9225
  this.turn.observabilityContext = observabilityContext;
9226
+ this.turn.actorModelContext = actorModelContext;
8441
9227
  {
8442
9228
  const step = this.turn.step(stepNumber);
8443
9229
  let ctx;
@@ -8519,6 +9305,7 @@ var ObservationalMemoryProcessor = class {
8519
9305
  const state = _state ?? {};
8520
9306
  const context = this.engine.getThreadContext(requestContext, messageList);
8521
9307
  if (!context) return messageList;
9308
+ if (state[GATEWAY_STATE_KEY]) return messageList;
8522
9309
  const observabilityContext = getOmObservabilityContext(args);
8523
9310
  state.__omObservabilityContext = observabilityContext;
8524
9311
  return this.engine.getTokenCounter().runWithModelContext(state.__omActorModelContext, async () => {
@@ -8595,5 +9382,5 @@ exports.stripEphemeralAnchorIds = stripEphemeralAnchorIds;
8595
9382
  exports.stripObservationGroups = stripObservationGroups;
8596
9383
  exports.truncateStringByTokens = truncateStringByTokens;
8597
9384
  exports.wrapInObservationGroup = wrapInObservationGroup;
8598
- //# sourceMappingURL=chunk-4FMHSWZD.cjs.map
8599
- //# sourceMappingURL=chunk-4FMHSWZD.cjs.map
9385
+ //# sourceMappingURL=chunk-6ODYOMHN.cjs.map
9386
+ //# sourceMappingURL=chunk-6ODYOMHN.cjs.map