@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
@@ -4,15 +4,15 @@ import { resolveModelConfig } from '@mastra/core/llm';
4
4
  import { getThreadOMMetadata, setThreadOMMetadata, parseMemoryRequestContext } from '@mastra/core/memory';
5
5
  import { MessageHistory } from '@mastra/core/processors';
6
6
  import xxhash from 'xxhash-wasm';
7
- import { appendFileSync, mkdirSync, writeFileSync } from 'fs';
7
+ import { appendFileSync, writeFileSync, mkdirSync } from 'fs';
8
8
  import { join } from 'path';
9
9
  import { randomBytes, createHash, randomUUID } from 'crypto';
10
10
  import { estimateTokenCount } from 'tokenx';
11
11
  import { Agent } from '@mastra/core/agent';
12
12
  import { getOrCreateSpan, EntityType, SpanType, createObservabilityContext } from '@mastra/core/observability';
13
+ import { inspect } from 'util';
13
14
  import { AsyncLocalStorage } from 'async_hooks';
14
15
  import imageSize from 'image-size';
15
- import { inspect } from 'util';
16
16
 
17
17
  var OM_DEBUG_LOG = process.env.OM_DEBUG ? join(process.cwd(), "om-debug.log") : null;
18
18
  function omDebug(msg) {
@@ -201,6 +201,19 @@ var BufferingCoordinator = class _BufferingCoordinator {
201
201
  }
202
202
  };
203
203
 
204
+ // src/processors/observational-memory/model-context.ts
205
+ function didProviderChange(actorModel, lastModel) {
206
+ if (actorModel === void 0 || lastModel === void 0) return false;
207
+ const actorHasSlash = actorModel.includes("/");
208
+ const lastHasSlash = lastModel.includes("/");
209
+ if (actorHasSlash && lastHasSlash) {
210
+ return actorModel !== lastModel;
211
+ }
212
+ const actorModelId = actorHasSlash ? actorModel.slice(actorModel.indexOf("/") + 1) : actorModel;
213
+ const lastModelId = lastHasSlash ? lastModel.slice(lastModel.indexOf("/") + 1) : lastModel;
214
+ return actorModelId !== lastModelId;
215
+ }
216
+
204
217
  // src/processors/observational-memory/date-utils.ts
205
218
  function formatRelativeTime(date, currentDate) {
206
219
  const diffMs = currentDate.getTime() - date.getTime();
@@ -365,6 +378,67 @@ ${gap}
365
378
  result += withInlineDates.slice(lastIndex);
366
379
  return result;
367
380
  }
381
+ var MIN_TEMPORAL_GAP_MS = 10 * 60 * 1e3;
382
+ function formatTemporalGap(diffMs) {
383
+ if (diffMs < MIN_TEMPORAL_GAP_MS) return null;
384
+ const minute = 60 * 1e3;
385
+ const hour = 60 * minute;
386
+ const day = 24 * hour;
387
+ const week = 7 * day;
388
+ const month = 30 * day;
389
+ const year = 365 * day;
390
+ const formatUnit = (value, unit) => `${value} ${unit}${value === 1 ? "" : "s"}`;
391
+ if (diffMs < hour) {
392
+ const minutes = Math.max(1, Math.round(diffMs / minute));
393
+ return `${formatUnit(minutes, "minute")} later`;
394
+ }
395
+ const formatTwoUnits = (primaryMs, primaryUnit, secondaryMs, secondaryUnit) => {
396
+ const primary = Math.floor(diffMs / primaryMs);
397
+ const remainder = diffMs - primary * primaryMs;
398
+ const secondary = Math.floor(remainder / secondaryMs);
399
+ const parts = [formatUnit(primary, primaryUnit)];
400
+ if (secondary > 0) {
401
+ parts.push(formatUnit(secondary, secondaryUnit));
402
+ }
403
+ return `${parts.join(" ")} later`;
404
+ };
405
+ if (diffMs < day) {
406
+ return formatTwoUnits(hour, "hour", minute, "minute");
407
+ }
408
+ if (diffMs < week) {
409
+ return formatTwoUnits(day, "day", hour, "hour");
410
+ }
411
+ if (diffMs < month) {
412
+ return formatTwoUnits(week, "week", day, "day");
413
+ }
414
+ if (diffMs < year) {
415
+ return formatTwoUnits(month, "month", week, "week");
416
+ }
417
+ return formatTwoUnits(year, "year", month, "month");
418
+ }
419
+ function formatTemporalTimestamp(date) {
420
+ return date.toLocaleString("en-US", {
421
+ year: "numeric",
422
+ month: "2-digit",
423
+ day: "2-digit",
424
+ hour: "numeric",
425
+ minute: "2-digit",
426
+ hour12: true,
427
+ timeZoneName: "short"
428
+ });
429
+ }
430
+ function getMessagePartTimestamp(msg, position) {
431
+ const timestamps = msg.content?.parts?.map((part) => "createdAt" in part ? part.createdAt : void 0).filter((timestamp) => typeof timestamp === "number");
432
+ if (timestamps && timestamps.length > 0) {
433
+ const index = position === "first" ? 0 : timestamps.length - 1;
434
+ const timestamp = timestamps[index];
435
+ if (timestamp !== void 0) return timestamp;
436
+ }
437
+ return new Date(msg.createdAt).getTime();
438
+ }
439
+ function isTemporalGapMarker(msg) {
440
+ return msg.id.startsWith("__temporal_");
441
+ }
368
442
 
369
443
  // src/processors/observational-memory/markers.ts
370
444
  function createObservationStartMarker(params) {
@@ -484,7 +558,12 @@ function createActivationMarker(params) {
484
558
  threadId: params.threadId,
485
559
  generationCount: params.generationCount,
486
560
  config: params.config,
487
- observations: params.observations
561
+ observations: params.observations,
562
+ triggeredBy: params.triggeredBy,
563
+ lastActivityAt: params.lastActivityAt,
564
+ ttlExpiredMs: params.ttlExpiredMs,
565
+ previousModel: params.previousModel,
566
+ currentModel: params.currentModel
488
567
  }
489
568
  };
490
569
  }
@@ -634,7 +713,7 @@ function sortThreadsByOldestMessage(messagesByThread) {
634
713
  })).sort((a, b) => a.oldestTimestamp - b.oldestTimestamp).map((t) => t.threadId);
635
714
  }
636
715
  function stripThreadTags(observations) {
637
- return observations.replace(/<thread[^>]*>|<\/thread>/gi, "").trim();
716
+ return observations.replace(/<\/?thread\b[^>]{0,1024}>/gi, "").trim();
638
717
  }
639
718
 
640
719
  // src/processors/observational-memory/model-by-input-tokens.ts
@@ -934,6 +1013,8 @@ var ObservationStrategy = class _ObservationStrategy {
934
1013
  this.scope = deps.scope;
935
1014
  this.retrieval = deps.retrieval;
936
1015
  }
1016
+ deps;
1017
+ opts;
937
1018
  storage;
938
1019
  messageHistory;
939
1020
  tokenCounter;
@@ -945,7 +1026,7 @@ var ObservationStrategy = class _ObservationStrategy {
945
1026
  static create;
946
1027
  /**
947
1028
  * Run the full observation lifecycle.
948
- * @returns `true` if a full observation cycle completed; `false` if skipped (stale lock) or async-buffer failure was swallowed.
1029
+ * @returns Result with `observed` flag and optional `usage` from the observer LLM call.
949
1030
  * @throws On sync/resource-scoped observer failure after failed markers (same as pre–Option-A contract).
950
1031
  */
951
1032
  async run() {
@@ -955,7 +1036,7 @@ var ObservationStrategy = class _ObservationStrategy {
955
1036
  if (this.needsLock) {
956
1037
  const fresh = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
957
1038
  if (fresh?.lastObservedAt && record.lastObservedAt && fresh.lastObservedAt > record.lastObservedAt) {
958
- return false;
1039
+ return { observed: false };
959
1040
  }
960
1041
  }
961
1042
  const { messages, existingObservations } = await this.prepare();
@@ -976,7 +1057,7 @@ var ObservationStrategy = class _ObservationStrategy {
976
1057
  observabilityContext: this.opts.observabilityContext
977
1058
  });
978
1059
  }
979
- return true;
1060
+ return { observed: true, usage: output.usage };
980
1061
  } catch (error) {
981
1062
  await this.emitFailedMarkers(cycleId, error);
982
1063
  if (!this.rethrowOnFailure) {
@@ -995,7 +1076,7 @@ var ObservationStrategy = class _ObservationStrategy {
995
1076
  });
996
1077
  if (abortSignal?.aborted) throw error;
997
1078
  omError("[OM] Observation failed", error);
998
- return false;
1079
+ return { observed: false };
999
1080
  }
1000
1081
  omError("[OM] Observation failed", error);
1001
1082
  throw error;
@@ -1007,9 +1088,11 @@ var ObservationStrategy = class _ObservationStrategy {
1007
1088
  }
1008
1089
  async streamMarker(marker) {
1009
1090
  if (this.opts.writer) {
1010
- await this.opts.writer.custom(marker).catch(() => {
1091
+ await this.opts.writer.custom({ ...marker, transient: true }).catch(() => {
1011
1092
  });
1012
1093
  }
1094
+ const markerThreadId = marker.data?.threadId ?? this.opts.threadId;
1095
+ await this.persistMarkerToStorage(marker, markerThreadId, this.opts.resourceId);
1013
1096
  }
1014
1097
  getObservationMarkerConfig() {
1015
1098
  return {
@@ -1515,7 +1598,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
1515
1598
  }
1516
1599
  }
1517
1600
  async emitEndMarkers(_cycleId, processed) {
1518
- if (!processed.observations || !this.opts.writer) return;
1601
+ if (!processed.observations) return;
1519
1602
  const { record, threadId, messages } = this.opts;
1520
1603
  const tokensBuffered = await this.tokenCounter.countMessagesAsync(messages);
1521
1604
  const updatedRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
@@ -1531,12 +1614,13 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
1531
1614
  threadId,
1532
1615
  observations: processed.observations
1533
1616
  });
1534
- void this.opts.writer.custom(endMarker).catch(() => {
1535
- });
1617
+ if (this.opts.writer) {
1618
+ void this.opts.writer.custom({ ...endMarker, transient: true }).catch(() => {
1619
+ });
1620
+ }
1536
1621
  await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0);
1537
1622
  }
1538
1623
  async emitFailedMarkers(_cycleId, error) {
1539
- if (!this.opts.writer) return;
1540
1624
  const { record, threadId, messages } = this.opts;
1541
1625
  const tokensAttempted = await this.tokenCounter.countMessagesAsync(messages);
1542
1626
  const failedMarker = createBufferingFailedMarker({
@@ -1548,8 +1632,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
1548
1632
  recordId: record.id,
1549
1633
  threadId
1550
1634
  });
1551
- void this.opts.writer.custom(failedMarker).catch(() => {
1552
- });
1635
+ if (this.opts.writer) {
1636
+ void this.opts.writer.custom({ ...failedMarker, transient: true }).catch(() => {
1637
+ });
1638
+ }
1553
1639
  await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0);
1554
1640
  }
1555
1641
  };
@@ -1940,6 +2026,8 @@ var ObservationStep = class {
1940
2026
  this.turn = turn;
1941
2027
  this.stepNumber = stepNumber;
1942
2028
  }
2029
+ turn;
2030
+ stepNumber;
1943
2031
  _prepared = false;
1944
2032
  _context;
1945
2033
  /** Whether this step has been prepared. */
@@ -1975,6 +2063,7 @@ var ObservationStep = class {
1975
2063
  resourceId,
1976
2064
  checkThreshold: true,
1977
2065
  messages: step0Messages,
2066
+ currentModel: this.turn.actorModelContext,
1978
2067
  writer: this.turn.writer,
1979
2068
  messageList
1980
2069
  });
@@ -1998,8 +2087,11 @@ var ObservationStep = class {
1998
2087
  observationTokens: obsTokens,
1999
2088
  threadId,
2000
2089
  writer: this.turn.writer,
2090
+ messageList,
2091
+ currentModel: this.turn.actorModelContext,
2001
2092
  requestContext: this.turn.requestContext,
2002
- observabilityContext: this.turn.observabilityContext
2093
+ observabilityContext: this.turn.observabilityContext,
2094
+ lastActivityAt: getLastActivityFromMessages(messageList.get.all.db())
2003
2095
  });
2004
2096
  await this.turn.refreshRecord();
2005
2097
  if (this.turn.record.generationCount > preReflectGeneration) {
@@ -2008,7 +2100,11 @@ var ObservationStep = class {
2008
2100
  }
2009
2101
  const allMsgsForToolCheck = messageList.get.all.db();
2010
2102
  const lastMessage = allMsgsForToolCheck[allMsgsForToolCheck.length - 1];
2011
- const latestStepParts = getLatestStepParts(lastMessage?.content?.parts ?? []);
2103
+ const pendingStepMessages = [...messageList.get.input.db(), ...messageList.get.response.db()];
2104
+ const latestStepParts = [
2105
+ ...getLatestStepParts(lastMessage?.content?.parts ?? []),
2106
+ ...pendingStepMessages.flatMap((msg) => getLatestStepParts(msg.content?.parts ?? []))
2107
+ ];
2012
2108
  const hasIncompleteToolCalls = latestStepParts.some(
2013
2109
  (part) => part?.type === "tool-invocation" && part.toolInvocation?.state === "call"
2014
2110
  );
@@ -2163,6 +2259,7 @@ var ObservationStep = class {
2163
2259
  threadId,
2164
2260
  resourceId,
2165
2261
  messages: messageList.get.all.db(),
2262
+ currentModel: this.turn.actorModelContext,
2166
2263
  writer: this.turn.writer,
2167
2264
  messageList
2168
2265
  });
@@ -2174,8 +2271,10 @@ var ObservationStep = class {
2174
2271
  threadId,
2175
2272
  writer: this.turn.writer,
2176
2273
  messageList,
2274
+ currentModel: this.turn.actorModelContext,
2177
2275
  requestContext: this.turn.requestContext,
2178
- observabilityContext: this.turn.observabilityContext
2276
+ observabilityContext: this.turn.observabilityContext,
2277
+ lastActivityAt: getLastActivityFromMessages(messageList.get.all.db())
2179
2278
  });
2180
2279
  return {
2181
2280
  succeeded: true,
@@ -2192,6 +2291,31 @@ var ObservationStep = class {
2192
2291
  writer: this.turn.writer,
2193
2292
  observabilityContext: this.turn.observabilityContext
2194
2293
  });
2294
+ if (obsResult.observed) {
2295
+ const observedMessageIds = new Set(obsResult.record.observedMessageIds ?? []);
2296
+ const liveMessages = messageList.get.all.db();
2297
+ let latestObservedIndex = -1;
2298
+ for (let i = liveMessages.length - 1; i >= 0; i--) {
2299
+ const message = liveMessages[i];
2300
+ if (message && observedMessageIds.has(message.id)) {
2301
+ latestObservedIndex = i;
2302
+ break;
2303
+ }
2304
+ }
2305
+ const messageToSeal = latestObservedIndex >= 0 ? liveMessages[latestObservedIndex] : void 0;
2306
+ const messagesToSeal = messageToSeal ? [messageToSeal] : [];
2307
+ om.sealMessagesForBuffering(messagesToSeal);
2308
+ try {
2309
+ await this.turn.hooks?.onSyncObservationComplete?.();
2310
+ } catch (error) {
2311
+ omDebug(
2312
+ `[OM:observe] onSyncObservationComplete hook failed: ${error instanceof Error ? error.message : String(error)}`
2313
+ );
2314
+ }
2315
+ if (messagesToSeal.length > 0) {
2316
+ await om.persistMessages(messagesToSeal, threadId, resourceId);
2317
+ }
2318
+ }
2195
2319
  return {
2196
2320
  succeeded: obsResult.observed,
2197
2321
  record: obsResult.record,
@@ -2217,7 +2341,9 @@ var ObservationTurn = class {
2217
2341
  requestContext;
2218
2342
  /** Optional observability context for nested OM spans. */
2219
2343
  observabilityContext;
2220
- /** Optional processor-provided hooks for turn/step lifecycle integration. */
2344
+ /** Current actor model for this step. Updated by the processor before prepare(). */
2345
+ actorModelContext;
2346
+ /** Processor-provided hooks for turn/step lifecycle integration. */
2221
2347
  hooks;
2222
2348
  constructor(opts) {
2223
2349
  this.om = opts.om;
@@ -2225,7 +2351,7 @@ var ObservationTurn = class {
2225
2351
  this.resourceId = opts.resourceId;
2226
2352
  this.messageList = opts.messageList;
2227
2353
  this.observabilityContext = opts.observabilityContext;
2228
- this.hooks = opts.hooks;
2354
+ this.hooks = opts.hooks ?? {};
2229
2355
  }
2230
2356
  om;
2231
2357
  threadId;
@@ -2245,6 +2371,10 @@ var ObservationTurn = class {
2245
2371
  get currentStep() {
2246
2372
  return this._currentStep;
2247
2373
  }
2374
+ addHooks(hooks) {
2375
+ if (!hooks) return;
2376
+ Object.assign(this.hooks, hooks);
2377
+ }
2248
2378
  /**
2249
2379
  * Load context and cache the record. Call once at the start of the turn.
2250
2380
  *
@@ -2401,6 +2531,15 @@ function stripEphemeralAnchorIds(observations) {
2401
2531
  }
2402
2532
  return observations.replace(/(^|\n)([^\S\n]*)\[(O\d+(?:-N\d+)?)\][^\S\n]*/g, "$1$2");
2403
2533
  }
2534
+
2535
+ // src/processors/observational-memory/string-utils.ts
2536
+ function safeSlice(str, end) {
2537
+ if (end <= 0) return "";
2538
+ if (end >= str.length) return str;
2539
+ const code = str.charCodeAt(end - 1);
2540
+ const safeEnd = code >= 55296 && code <= 56319 ? end - 1 : end;
2541
+ return str.slice(0, safeEnd);
2542
+ }
2404
2543
  var ENCRYPTED_CONTENT_KEY = "encryptedContent";
2405
2544
  var ENCRYPTED_CONTENT_REDACTION_THRESHOLD = 256;
2406
2545
  var DEFAULT_OBSERVER_TOOL_RESULT_MAX_TOKENS = 1e4;
@@ -2466,7 +2605,7 @@ function truncateStringByTokens(text, maxTokens) {
2466
2605
  return text;
2467
2606
  }
2468
2607
  const buildCandidate = (sliceEnd) => {
2469
- const visible = text.slice(0, sliceEnd);
2608
+ const visible = safeSlice(text, sliceEnd);
2470
2609
  return `${visible}
2471
2610
  ... [truncated ~${totalTokens - estimateTokenCount(visible)} tokens]`;
2472
2611
  };
@@ -2922,11 +3061,13 @@ var OBSERVER_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
2922
3061
  "heif",
2923
3062
  "avif"
2924
3063
  ]);
2925
- function formatObserverTimestamp(createdAt) {
2926
- return createdAt ? new Date(createdAt).toLocaleString("en-US", {
2927
- year: "numeric",
2928
- month: "short",
2929
- day: "numeric",
3064
+ function formatObserverDate(createdAt) {
3065
+ return createdAt ? `${createdAt.toLocaleDateString("en-US", {
3066
+ month: "short"
3067
+ })} ${createdAt.getDate()} ${createdAt.getFullYear()}` : "";
3068
+ }
3069
+ function formatObserverTime(createdAt) {
3070
+ return createdAt ? createdAt.toLocaleTimeString("en-US", {
2930
3071
  hour: "numeric",
2931
3072
  minute: "2-digit",
2932
3073
  hour12: true
@@ -3017,19 +3158,88 @@ function formatObserverAttachmentPlaceholder(part, counter) {
3017
3158
  const label = resolveObserverAttachmentLabel(part);
3018
3159
  return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
3019
3160
  }
3161
+ function formatObserverPartLine(title, body, time, previousTime) {
3162
+ const timeLabel = time && time !== previousTime ? `(${time})` : "";
3163
+ if (!title) {
3164
+ return timeLabel ? `${timeLabel}: ${body}` : body;
3165
+ }
3166
+ return `${title}${timeLabel ? ` ${timeLabel}` : ""}: ${body}`;
3167
+ }
3168
+ function normalizeObserverCreatedAt(createdAt) {
3169
+ if (createdAt instanceof Date) {
3170
+ if (Number.isNaN(createdAt.getTime())) {
3171
+ return void 0;
3172
+ }
3173
+ return createdAt;
3174
+ }
3175
+ if (typeof createdAt === "number" || typeof createdAt === "string") {
3176
+ const date = new Date(createdAt);
3177
+ if (Number.isNaN(date.getTime())) {
3178
+ return void 0;
3179
+ }
3180
+ return date;
3181
+ }
3182
+ return void 0;
3183
+ }
3184
+ function formatObserverLines(lines, context = {}) {
3185
+ const output = [];
3186
+ let previousDate = context.previousDate;
3187
+ let previousTime = context.previousTime;
3188
+ for (const line of lines) {
3189
+ if (line.date && line.date !== previousDate) {
3190
+ output.push(`${line.date}:`);
3191
+ previousDate = line.date;
3192
+ previousTime = void 0;
3193
+ }
3194
+ output.push(formatObserverPartLine(line.title, line.body, line.time, previousTime));
3195
+ previousTime = line.time || previousTime;
3196
+ }
3197
+ return {
3198
+ text: output.join("\n"),
3199
+ context: { previousDate, previousTime }
3200
+ };
3201
+ }
3202
+ function getTemporalGapMarkerText(msg) {
3203
+ const metadata = typeof msg.content === "object" && msg.content && "metadata" in msg.content ? msg.content.metadata : void 0;
3204
+ if (metadata?.reminderType === "temporal-gap" && typeof metadata.gapText === "string") {
3205
+ return metadata.gapText;
3206
+ }
3207
+ 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") {
3208
+ return metadata.systemReminder.gapText;
3209
+ }
3210
+ return void 0;
3211
+ }
3020
3212
  function formatObserverMessage(msg, counter, options) {
3021
3213
  const maxLen = options?.maxPartLength;
3022
3214
  const maxToolResultTokens = options?.maxToolResultTokens ?? DEFAULT_OBSERVER_TOOL_RESULT_MAX_TOKENS;
3023
- const timestamp = formatObserverTimestamp(msg.createdAt);
3024
3215
  const role = msg.role.charAt(0).toUpperCase() + msg.role.slice(1);
3025
- const timestampStr = timestamp ? ` (${timestamp})` : "";
3026
3216
  const attachments = [];
3027
- let content = "";
3028
- if (typeof msg.content === "string") {
3029
- content = maybeTruncate(msg.content, maxLen);
3217
+ const messageCreatedAt = normalizeObserverCreatedAt(msg.createdAt);
3218
+ let lines = [];
3219
+ const temporalGapText = isTemporalGapMarker(msg) ? getTemporalGapMarkerText(msg) : void 0;
3220
+ const pushLine = (title, body, createdAt) => {
3221
+ if (!body) {
3222
+ return;
3223
+ }
3224
+ const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
3225
+ lines.push({
3226
+ date: formatObserverDate(normalizedCreatedAt),
3227
+ time: formatObserverTime(normalizedCreatedAt),
3228
+ title,
3229
+ body
3230
+ });
3231
+ };
3232
+ if (temporalGapText) {
3233
+ pushLine("", temporalGapText, messageCreatedAt);
3234
+ } else if (typeof msg.content === "string") {
3235
+ pushLine(role, maybeTruncate(msg.content, maxLen), messageCreatedAt);
3030
3236
  } else if (msg.content?.parts && Array.isArray(msg.content.parts) && msg.content.parts.length > 0) {
3031
- content = msg.content.parts.map((part) => {
3032
- if (part.type === "text") return maybeTruncate(part.text, maxLen);
3237
+ msg.content.parts.forEach((part) => {
3238
+ const partCreatedAt = normalizeObserverCreatedAt(part.createdAt) ?? messageCreatedAt;
3239
+ if (part.type === "text") {
3240
+ pushLine(role, maybeTruncate(part.text, maxLen), partCreatedAt);
3241
+ return;
3242
+ }
3033
3243
  if (part.type === "tool-invocation") {
3034
3244
  const inv = part.toolInvocation;
3035
3245
  if (inv.state === "result") {
@@ -3037,19 +3247,24 @@ function formatObserverMessage(msg, counter, options) {
3037
3247
  part,
3038
3248
  inv.result
3039
3249
  );
3040
- const resultStr = formatToolResultForObserver(resultForObserver, { maxTokens: maxToolResultTokens });
3041
- return `[Tool Result: ${inv.toolName}]
3042
- ${maybeTruncate(resultStr, maxLen)}`;
3250
+ pushLine(
3251
+ `Tool Result ${inv.toolName}`,
3252
+ maybeTruncate(formatToolResultForObserver(resultForObserver, { maxTokens: maxToolResultTokens }), maxLen),
3253
+ partCreatedAt
3254
+ );
3255
+ return;
3043
3256
  }
3044
- const argsStr = JSON.stringify(inv.args, null, 2);
3045
- return `[Tool Call: ${inv.toolName}]
3046
- ${maybeTruncate(argsStr, maxLen)}`;
3257
+ pushLine(`Tool Call ${inv.toolName}`, maybeTruncate(JSON.stringify(inv.args, null, 2), maxLen), partCreatedAt);
3258
+ return;
3047
3259
  }
3048
3260
  const partType = part.type;
3049
3261
  if (partType === "reasoning") {
3050
3262
  const reasoning = part.reasoning;
3051
- if (reasoning) return maybeTruncate(reasoning, maxLen);
3052
- return "";
3263
+ if (!reasoning) {
3264
+ return;
3265
+ }
3266
+ pushLine("Reasoning", maybeTruncate(reasoning, maxLen), partCreatedAt);
3267
+ return;
3053
3268
  }
3054
3269
  if (partType === "image" || partType === "file") {
3055
3270
  const attachment = part;
@@ -3057,40 +3272,58 @@ ${maybeTruncate(argsStr, maxLen)}`;
3057
3272
  if (inputAttachment) {
3058
3273
  attachments.push(inputAttachment);
3059
3274
  }
3060
- return formatObserverAttachmentPlaceholder(attachment, counter);
3275
+ pushLine(
3276
+ partType === "image" ? "Image" : "File",
3277
+ formatObserverAttachmentPlaceholder(attachment, counter),
3278
+ partCreatedAt
3279
+ );
3061
3280
  }
3062
- if (partType?.startsWith("data-")) return "";
3063
- return "";
3064
- }).filter(Boolean).join("\n");
3281
+ });
3065
3282
  } else if (msg.content?.content) {
3066
- content = maybeTruncate(msg.content.content, maxLen);
3283
+ pushLine(role, maybeTruncate(msg.content.content, maxLen), messageCreatedAt);
3067
3284
  }
3068
- if (!content && attachments.length === 0) {
3069
- return { text: "", attachments };
3285
+ if (lines.length === 0 && attachments.length === 0) {
3286
+ return { lines: [], attachments };
3070
3287
  }
3071
3288
  return {
3072
- text: `**${role}${timestampStr}:**
3073
- ${content}`,
3289
+ lines,
3074
3290
  attachments
3075
3291
  };
3076
3292
  }
3077
3293
  function formatMessagesForObserver(messages, options) {
3078
3294
  const counter = { nextImageId: 1, nextFileId: 1 };
3079
- return messages.map((msg) => formatObserverMessage(msg, counter, options).text).filter(Boolean).join("\n\n---\n\n");
3295
+ const sections = [];
3296
+ let context = {};
3297
+ for (const message of messages) {
3298
+ const formatted = formatObserverMessage(message, counter, options);
3299
+ if (formatted.lines.length === 0) {
3300
+ continue;
3301
+ }
3302
+ const rendered = formatObserverLines(formatted.lines, context);
3303
+ if (!rendered.text) {
3304
+ continue;
3305
+ }
3306
+ sections.push(rendered.text);
3307
+ context = rendered.context;
3308
+ }
3309
+ return sections.join("\n");
3310
+ }
3311
+ function appendFormattedObserverMessage(content, formatted, context) {
3312
+ const rendered = formatObserverLines(formatted.lines, context);
3313
+ if (rendered.text) {
3314
+ content.push({ type: "text", text: rendered.text });
3315
+ }
3316
+ content.push(...formatted.attachments);
3317
+ return rendered.context;
3080
3318
  }
3081
3319
  function buildObserverHistoryMessage(messages, options) {
3082
3320
  const counter = { nextImageId: 1, nextFileId: 1 };
3083
3321
  const content = [{ type: "text", text: "## New Message History to Observe\n\n" }];
3084
- let visibleCount = 0;
3322
+ let context = {};
3085
3323
  messages.forEach((message) => {
3086
3324
  const formatted = formatObserverMessage(message, counter, options);
3087
- if (!formatted.text && formatted.attachments.length === 0) return;
3088
- if (visibleCount > 0) {
3089
- content.push({ type: "text", text: "\n\n---\n\n" });
3090
- }
3091
- content.push({ type: "text", text: formatted.text });
3092
- content.push(...formatted.attachments);
3093
- visibleCount++;
3325
+ if (formatted.lines.length === 0 && formatted.attachments.length === 0) return;
3326
+ context = appendFormattedObserverMessage(content, formatted, context);
3094
3327
  });
3095
3328
  return {
3096
3329
  role: "user",
@@ -3099,8 +3332,8 @@ function buildObserverHistoryMessage(messages, options) {
3099
3332
  }
3100
3333
  function maybeTruncate(str, maxLen) {
3101
3334
  if (!maxLen || str.length <= maxLen) return str;
3102
- const truncated = str.slice(0, maxLen);
3103
- const remaining = str.length - maxLen;
3335
+ const truncated = safeSlice(str, maxLen);
3336
+ const remaining = str.length - truncated.length;
3104
3337
  return `${truncated}
3105
3338
  ... [truncated ${remaining} characters]`;
3106
3339
  }
@@ -3120,18 +3353,15 @@ The following messages are from ${threadOrder.length} different conversation thr
3120
3353
  const messages = messagesByThread.get(threadId);
3121
3354
  if (!messages || messages.length === 0) return;
3122
3355
  const threadContent = [];
3123
- let visibleCount = 0;
3356
+ let context = {};
3357
+ let hasVisibleContent = false;
3124
3358
  messages.forEach((message) => {
3125
3359
  const formatted = formatObserverMessage(message, counter, options);
3126
- if (!formatted.text && formatted.attachments.length === 0) return;
3127
- if (visibleCount > 0) {
3128
- threadContent.push({ type: "text", text: "\n\n---\n\n" });
3129
- }
3130
- threadContent.push({ type: "text", text: formatted.text });
3131
- threadContent.push(...formatted.attachments);
3132
- visibleCount++;
3360
+ if (formatted.lines.length === 0 && formatted.attachments.length === 0) return;
3361
+ context = appendFormattedObserverMessage(threadContent, formatted, context);
3362
+ hasVisibleContent = true;
3133
3363
  });
3134
- if (visibleCount === 0) return;
3364
+ if (!hasVisibleContent) return;
3135
3365
  content.push({ type: "text", text: `<thread id="${threadId}">
3136
3366
  ` });
3137
3367
  content.push(...threadContent);
@@ -3407,7 +3637,7 @@ function sanitizeObservationLines(observations) {
3407
3637
  let changed = false;
3408
3638
  for (let i = 0; i < lines.length; i++) {
3409
3639
  if (lines[i].length > MAX_OBSERVATION_LINE_CHARS) {
3410
- lines[i] = lines[i].slice(0, MAX_OBSERVATION_LINE_CHARS) + " \u2026 [truncated]";
3640
+ lines[i] = safeSlice(lines[i], MAX_OBSERVATION_LINE_CHARS) + " \u2026 [truncated]";
3411
3641
  changed = true;
3412
3642
  }
3413
3643
  }
@@ -4010,12 +4240,44 @@ function validateCompression(reflectedTokens, targetThreshold) {
4010
4240
  }
4011
4241
 
4012
4242
  // src/processors/observational-memory/reflector-runner.ts
4243
+ function formatModelContext(provider, modelId) {
4244
+ if (provider && modelId) {
4245
+ return `${provider}/${modelId}`;
4246
+ }
4247
+ return modelId;
4248
+ }
4249
+ function getCurrentModel(model) {
4250
+ return formatModelContext(model?.provider, model?.modelId);
4251
+ }
4252
+ function getLastModelFromMessageList(messageList) {
4253
+ const messages = messageList?.get.all.db();
4254
+ if (!messages) return void 0;
4255
+ for (let i = messages.length - 1; i >= 0; i--) {
4256
+ const message = messages[i];
4257
+ if (!message || message.role !== "assistant" || !message.content || typeof message.content === "string") {
4258
+ continue;
4259
+ }
4260
+ for (let j = message.content.parts.length - 1; j >= 0; j--) {
4261
+ const part = message.content.parts[j];
4262
+ if (part?.type === "step-start" && typeof part.model === "string" && part.model.length > 0) {
4263
+ return part.model;
4264
+ }
4265
+ }
4266
+ const metadata = message.content.metadata;
4267
+ const model = formatModelContext(metadata?.provider, metadata?.modelId);
4268
+ if (model) {
4269
+ return model;
4270
+ }
4271
+ }
4272
+ return void 0;
4273
+ }
4013
4274
  async function withAbortCheck(fn, abortSignal) {
4014
4275
  if (abortSignal?.aborted) throw new Error("The operation was aborted.");
4015
4276
  const result = await fn();
4016
4277
  if (abortSignal?.aborted) throw new Error("The operation was aborted.");
4017
4278
  return result;
4018
4279
  }
4280
+ var EARLY_ACTIVATION_SIZE_FLOOR_RATIO = 0.75;
4019
4281
  var ReflectorRunner = class {
4020
4282
  reflectionConfig;
4021
4283
  observationConfig;
@@ -4049,13 +4311,30 @@ var ReflectorRunner = class {
4049
4311
  model
4050
4312
  });
4051
4313
  }
4052
- getObservationMarkerConfig() {
4314
+ getObservationMarkerConfig(record) {
4053
4315
  return {
4054
4316
  messageTokens: getMaxThreshold(this.observationConfig.messageTokens),
4055
- observationTokens: getMaxThreshold(this.reflectionConfig.observationTokens),
4056
- scope: this.scope
4317
+ observationTokens: getMaxThreshold(
4318
+ record ? this.getEffectiveReflectionTokens(record) : this.reflectionConfig.observationTokens
4319
+ ),
4320
+ scope: this.scope,
4321
+ activateAfterIdle: this.reflectionConfig.activateAfterIdle
4057
4322
  };
4058
4323
  }
4324
+ /**
4325
+ * Resolve the effective reflection observationTokens for a record.
4326
+ * Only explicit per-record overrides (stored under `_overrides`) win;
4327
+ * the initial config snapshot is ignored so instance-level changes
4328
+ * still take effect for existing records.
4329
+ */
4330
+ getEffectiveReflectionTokens(record) {
4331
+ const overrides = record.config?._overrides;
4332
+ const recordTokens = overrides?.reflection?.observationTokens;
4333
+ if (recordTokens) {
4334
+ return recordTokens;
4335
+ }
4336
+ return this.reflectionConfig.observationTokens;
4337
+ }
4059
4338
  /**
4060
4339
  * Call the Reflector agent with escalating compression levels.
4061
4340
  */
@@ -4165,8 +4444,9 @@ var ReflectorRunner = class {
4165
4444
  recordId: streamContext.recordId,
4166
4445
  threadId: streamContext.threadId
4167
4446
  });
4168
- await streamContext.writer.custom(failedMarker).catch(() => {
4447
+ await streamContext.writer.custom({ ...failedMarker, transient: true }).catch(() => {
4169
4448
  });
4449
+ await this.persistMarkerToStorage(failedMarker, streamContext.threadId, streamContext.resourceId);
4170
4450
  const retryCycleId = crypto.randomUUID();
4171
4451
  streamContext.cycleId = retryCycleId;
4172
4452
  const startMarker = createObservationStartMarker({
@@ -4179,8 +4459,9 @@ var ReflectorRunner = class {
4179
4459
  config: this.getObservationMarkerConfig()
4180
4460
  });
4181
4461
  streamContext.startedAt = startMarker.data.startedAt;
4182
- await streamContext.writer.custom(startMarker).catch(() => {
4462
+ await streamContext.writer.custom({ ...startMarker, transient: true }).catch(() => {
4183
4463
  });
4464
+ await this.persistMarkerToStorage(startMarker, streamContext.threadId, streamContext.resourceId);
4184
4465
  }
4185
4466
  currentLevel = Math.min(currentLevel + 1, maxLevel);
4186
4467
  }
@@ -4193,7 +4474,7 @@ var ReflectorRunner = class {
4193
4474
  /**
4194
4475
  * Start an async buffered reflection in the background.
4195
4476
  */
4196
- startAsyncBufferedReflection(record, observationTokens, lockKey, writer, requestContext, observabilityContext) {
4477
+ startAsyncBufferedReflection(record, observationTokens, lockKey, writer, requestContext, observabilityContext, reflectionHooks) {
4197
4478
  const bufferKey = this.buffering.getReflectionBufferKey(lockKey);
4198
4479
  if (this.buffering.isAsyncBufferingInProgress(bufferKey)) {
4199
4480
  return;
@@ -4203,7 +4484,10 @@ var ReflectorRunner = class {
4203
4484
  this.storage.setBufferingReflectionFlag(record.id, true).catch((err) => {
4204
4485
  omError("[OM] Failed to set buffering reflection flag", err);
4205
4486
  });
4206
- const asyncOp = this.doAsyncBufferedReflection(record, bufferKey, writer, requestContext, observabilityContext).catch(async (error) => {
4487
+ reflectionHooks?.onReflectionStart?.();
4488
+ const asyncOp = this.doAsyncBufferedReflection(record, bufferKey, writer, requestContext, observabilityContext).then((usage) => {
4489
+ reflectionHooks?.onReflectionEnd?.({ usage });
4490
+ }).catch(async (error) => {
4207
4491
  if (writer) {
4208
4492
  const failedMarker = createBufferingFailedMarker({
4209
4493
  cycleId: `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
@@ -4214,11 +4498,15 @@ var ReflectorRunner = class {
4214
4498
  recordId: record.id,
4215
4499
  threadId: record.threadId ?? ""
4216
4500
  });
4217
- void writer.custom(failedMarker).catch(() => {
4501
+ void writer.custom({ ...failedMarker, transient: true }).catch(() => {
4218
4502
  });
4219
4503
  await this.persistMarkerToStorage(failedMarker, record.threadId ?? "", record.resourceId ?? void 0);
4220
4504
  }
4221
4505
  omError("[OM] Async buffered reflection failed", error);
4506
+ reflectionHooks?.onReflectionEnd?.({
4507
+ usage: void 0,
4508
+ error: error instanceof Error ? error : new Error(String(error))
4509
+ });
4222
4510
  BufferingCoordinator.lastBufferedBoundary.delete(bufferKey);
4223
4511
  }).finally(() => {
4224
4512
  BufferingCoordinator.asyncBufferingOps.delete(bufferKey);
@@ -4237,7 +4525,7 @@ var ReflectorRunner = class {
4237
4525
  const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
4238
4526
  const currentRecord = freshRecord ?? record;
4239
4527
  const observationTokens = currentRecord.observationTokenCount ?? 0;
4240
- const reflectThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
4528
+ const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(currentRecord));
4241
4529
  const bufferActivation = this.reflectionConfig.bufferActivation ?? 0.5;
4242
4530
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
4243
4531
  const cycleId = `reflect-buf-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
@@ -4266,10 +4554,15 @@ var ReflectorRunner = class {
4266
4554
  recordId: record.id,
4267
4555
  threadId: record.threadId ?? "",
4268
4556
  threadIds: record.threadId ? [record.threadId] : [],
4269
- config: this.getObservationMarkerConfig()
4557
+ config: this.getObservationMarkerConfig(currentRecord)
4270
4558
  });
4271
- void writer.custom(startMarker).catch(() => {
4559
+ void writer.custom({ ...startMarker, transient: true }).catch(() => {
4272
4560
  });
4561
+ await this.persistMarkerToStorage(
4562
+ startMarker,
4563
+ currentRecord.threadId ?? "",
4564
+ currentRecord.resourceId ?? void 0
4565
+ );
4273
4566
  }
4274
4567
  const compressionStartLevel = await this.getCompressionStartLevel(requestContext);
4275
4568
  const reflectResult = await this.call(
@@ -4308,26 +4601,35 @@ var ReflectorRunner = class {
4308
4601
  threadId: currentRecord.threadId ?? "",
4309
4602
  observations: reflectResult.observations
4310
4603
  });
4311
- void writer.custom(endMarker).catch(() => {
4604
+ void writer.custom({ ...endMarker, transient: true }).catch(() => {
4312
4605
  });
4313
4606
  await this.persistMarkerToStorage(endMarker, currentRecord.threadId ?? "", currentRecord.resourceId ?? void 0);
4314
4607
  }
4608
+ return reflectResult.usage;
4315
4609
  }
4316
4610
  /**
4317
4611
  * Try to activate buffered reflection when threshold is reached.
4318
- * Returns true if activation succeeded.
4612
+ * Returns a discriminated result so the caller can distinguish between
4613
+ * "activated", "no buffer present", and "suppressed by overshoot guard"
4614
+ * without re-deriving that state.
4319
4615
  */
4320
- async tryActivateBufferedReflection(record, lockKey, writer, messageList) {
4616
+ async tryActivateBufferedReflection(record, lockKey, writer, messageList, activationMetadata) {
4321
4617
  const bufferKey = this.buffering.getReflectionBufferKey(lockKey);
4322
4618
  const asyncOp = BufferingCoordinator.asyncBufferingOps.get(bufferKey);
4323
4619
  if (asyncOp) {
4324
- omDebug(`[OM:reflect] tryActivateBufferedReflection: waiting for in-progress op...`);
4325
- try {
4326
- await Promise.race([
4327
- asyncOp,
4328
- new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 6e4))
4329
- ]);
4330
- } catch {
4620
+ if (activationMetadata?.triggeredBy === "ttl" || activationMetadata?.triggeredBy === "provider_change") {
4621
+ omDebug(
4622
+ `[OM:reflect] tryActivateBufferedReflection: async op in progress, not blocking for ${activationMetadata.triggeredBy} trigger`
4623
+ );
4624
+ } else {
4625
+ omDebug(`[OM:reflect] tryActivateBufferedReflection: waiting for in-progress op...`);
4626
+ try {
4627
+ await Promise.race([
4628
+ asyncOp,
4629
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 5e3))
4630
+ ]);
4631
+ } catch {
4632
+ }
4331
4633
  }
4332
4634
  }
4333
4635
  const freshRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
@@ -4338,8 +4640,8 @@ var ReflectorRunner = class {
4338
4640
  `[OM:reflect] tryActivateBufferedReflection: freshRecord.id=${freshRecord?.id}, freshBufferedReflection=${freshRecord?.bufferedReflection ? "present (" + freshRecord.bufferedReflection.length + " chars)" : "empty"}, freshObsTokens=${freshRecord?.observationTokenCount}`
4339
4641
  );
4340
4642
  if (!freshRecord?.bufferedReflection) {
4341
- omDebug(`[OM:reflect] tryActivateBufferedReflection: no buffered reflection after re-fetch, returning false`);
4342
- return false;
4643
+ omDebug(`[OM:reflect] tryActivateBufferedReflection: no buffered reflection after re-fetch`);
4644
+ return { status: "no-buffer" };
4343
4645
  }
4344
4646
  const beforeTokens = freshRecord.observationTokenCount ?? 0;
4345
4647
  const reflectedLineCount = freshRecord.reflectedObservationLineCount ?? 0;
@@ -4351,6 +4653,26 @@ var ReflectorRunner = class {
4351
4653
 
4352
4654
  ${unreflectedContent}` : freshRecord.bufferedReflection;
4353
4655
  const combinedTokenCount = this.tokenCounter.countObservations(combinedObservations);
4656
+ if (activationMetadata?.triggeredBy === "ttl" || activationMetadata?.triggeredBy === "provider_change") {
4657
+ const unreflectedTailTokens = unreflectedContent ? this.tokenCounter.countObservations(unreflectedContent) : 0;
4658
+ const bufferedReflectionTokens = freshRecord.bufferedReflectionTokens ?? 0;
4659
+ if (unreflectedTailTokens < bufferedReflectionTokens) {
4660
+ omDebug(
4661
+ `[OM:reflect] tryActivateBufferedReflection: suppressing early ${activationMetadata.triggeredBy} activation \u2014 unreflectedTailTokens=${unreflectedTailTokens} < bufferedReflectionTokens=${bufferedReflectionTokens}; keeping buffer for threshold activation`
4662
+ );
4663
+ return { status: "suppressed", reason: "composition" };
4664
+ }
4665
+ const bufferActivation = this.reflectionConfig.bufferActivation;
4666
+ const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(freshRecord));
4667
+ const regularActivationTarget = reflectThreshold * (1 - bufferActivation);
4668
+ const minCombinedTokens = Math.round(regularActivationTarget * EARLY_ACTIVATION_SIZE_FLOOR_RATIO);
4669
+ if (combinedTokenCount < minCombinedTokens) {
4670
+ omDebug(
4671
+ `[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`
4672
+ );
4673
+ return { status: "suppressed", reason: "size" };
4674
+ }
4675
+ }
4354
4676
  omDebug(
4355
4677
  `[OM:reflect] tryActivateBufferedReflection: activating, beforeTokens=${beforeTokens}, combinedTokenCount=${combinedTokenCount}, reflectedLineCount=${reflectedLineCount}, unreflectedLines=${unreflectedLines.length}`
4356
4678
  );
@@ -4377,9 +4699,14 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4377
4699
  threadId: freshRecord.threadId ?? "",
4378
4700
  generationCount: afterRecord?.generationCount ?? freshRecord.generationCount ?? 0,
4379
4701
  observations: afterRecord?.activeObservations,
4380
- config: this.getObservationMarkerConfig()
4702
+ triggeredBy: activationMetadata?.triggeredBy,
4703
+ lastActivityAt: activationMetadata?.lastActivityAt,
4704
+ ttlExpiredMs: activationMetadata?.ttlExpiredMs,
4705
+ previousModel: activationMetadata?.previousModel,
4706
+ currentModel: activationMetadata?.currentModel,
4707
+ config: this.getObservationMarkerConfig(freshRecord)
4381
4708
  });
4382
- void writer.custom(activationMarker).catch(() => {
4709
+ void writer.custom({ ...activationMarker, transient: true }).catch(() => {
4383
4710
  });
4384
4711
  await this.persistMarkerToMessage(
4385
4712
  activationMarker,
@@ -4389,7 +4716,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4389
4716
  );
4390
4717
  }
4391
4718
  BufferingCoordinator.reflectionBufferCycleIds.delete(bufferKey);
4392
- return true;
4719
+ return { status: "activated" };
4393
4720
  }
4394
4721
  /**
4395
4722
  * Check if reflection needed and trigger if so.
@@ -4403,12 +4730,15 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4403
4730
  writer,
4404
4731
  abortSignal,
4405
4732
  messageList,
4733
+ currentModel,
4406
4734
  reflectionHooks,
4407
4735
  requestContext,
4408
- observabilityContext
4736
+ observabilityContext,
4737
+ lastActivityAt,
4738
+ threadId: requestedThreadId
4409
4739
  } = opts;
4410
4740
  const lockKey = this.buffering.getLockKey(record.threadId, record.resourceId);
4411
- const reflectThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
4741
+ const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
4412
4742
  if (this.buffering.isAsyncReflectionEnabled() && observationTokens < reflectThreshold) {
4413
4743
  const shouldTrigger = (() => {
4414
4744
  if (!this.buffering.isAsyncReflectionEnabled()) return false;
@@ -4432,13 +4762,28 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4432
4762
  lockKey,
4433
4763
  writer,
4434
4764
  requestContext,
4435
- observabilityContext
4765
+ observabilityContext,
4766
+ reflectionHooks
4436
4767
  );
4437
4768
  }
4438
4769
  }
4439
- if (observationTokens < reflectThreshold) {
4770
+ const activateAfterIdle = this.reflectionConfig.activateAfterIdle;
4771
+ const ttlExpiredMs = activateAfterIdle !== void 0 && lastActivityAt !== void 0 ? Date.now() - lastActivityAt : void 0;
4772
+ const ttlExpired = ttlExpiredMs !== void 0 && activateAfterIdle !== void 0 && ttlExpiredMs >= activateAfterIdle;
4773
+ const actorModel = getCurrentModel(currentModel);
4774
+ const lastModel = getLastModelFromMessageList(messageList);
4775
+ const providerChanged = this.reflectionConfig.activateOnProviderChange === true && didProviderChange(actorModel, lastModel);
4776
+ if (observationTokens < reflectThreshold && !ttlExpired && !providerChanged) {
4440
4777
  return;
4441
4778
  }
4779
+ const activationTriggeredBy = observationTokens >= reflectThreshold ? "threshold" : providerChanged ? "provider_change" : "ttl";
4780
+ const activationMetadata = {
4781
+ triggeredBy: activationTriggeredBy,
4782
+ lastActivityAt: activationTriggeredBy === "ttl" ? lastActivityAt : void 0,
4783
+ ttlExpiredMs: activationTriggeredBy === "ttl" ? ttlExpiredMs : void 0,
4784
+ previousModel: activationTriggeredBy === "provider_change" ? lastModel : void 0,
4785
+ currentModel: activationTriggeredBy === "provider_change" ? actorModel : void 0
4786
+ };
4442
4787
  if (record.isReflecting) {
4443
4788
  if (isOpActiveInProcess(record.id, "reflecting")) {
4444
4789
  omDebug(`[OM:reflect] isReflecting=true and active in this process, skipping`);
@@ -4448,8 +4793,20 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4448
4793
  await this.storage.setReflectingFlag(record.id, false);
4449
4794
  }
4450
4795
  if (this.buffering.isAsyncReflectionEnabled()) {
4451
- const activationSuccess = await this.tryActivateBufferedReflection(record, lockKey, writer, messageList);
4452
- if (activationSuccess) {
4796
+ const activationResult = await this.tryActivateBufferedReflection(
4797
+ record,
4798
+ lockKey,
4799
+ writer,
4800
+ messageList,
4801
+ activationMetadata
4802
+ );
4803
+ if (activationResult.status === "activated") {
4804
+ return;
4805
+ }
4806
+ if (activationResult.status === "suppressed") {
4807
+ omDebug(
4808
+ `[OM:reflect] skipping sync fallback / re-buffer after suppressed early ${activationMetadata.triggeredBy} activation (reason=${activationResult.reason})`
4809
+ );
4453
4810
  return;
4454
4811
  }
4455
4812
  if (this.reflectionConfig.blockAfter && observationTokens >= this.reflectionConfig.blockAfter) {
@@ -4457,6 +4814,13 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4457
4814
  `[OM:reflect] blockAfter exceeded (${observationTokens} >= ${this.reflectionConfig.blockAfter}), falling through to sync reflection`
4458
4815
  );
4459
4816
  } else {
4817
+ const activationPoint = reflectThreshold * this.reflectionConfig.bufferActivation;
4818
+ if (observationTokens < activationPoint) {
4819
+ omDebug(
4820
+ `[OM:reflect] skipping async reflection \u2014 observationTokens (${observationTokens}) below activation point (${activationPoint}), triggered by ${activationTriggeredBy}`
4821
+ );
4822
+ return;
4823
+ }
4460
4824
  omDebug(
4461
4825
  `[OM:reflect] async activation failed, no blockAfter or below it (obsTokens=${observationTokens}, blockAfter=${this.reflectionConfig.blockAfter}) \u2014 starting background reflection`
4462
4826
  );
@@ -4466,7 +4830,8 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4466
4830
  lockKey,
4467
4831
  writer,
4468
4832
  requestContext,
4469
- observabilityContext
4833
+ observabilityContext,
4834
+ reflectionHooks
4470
4835
  );
4471
4836
  return;
4472
4837
  }
@@ -4476,7 +4841,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4476
4841
  registerOp(record.id, "reflecting");
4477
4842
  const cycleId = crypto.randomUUID();
4478
4843
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
4479
- const threadId = opts.threadId ?? "unknown";
4844
+ const threadId = requestedThreadId ?? "unknown";
4480
4845
  if (writer) {
4481
4846
  const startMarker = createObservationStartMarker({
4482
4847
  cycleId,
@@ -4485,10 +4850,11 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4485
4850
  recordId: record.id,
4486
4851
  threadId,
4487
4852
  threadIds: [threadId],
4488
- config: this.getObservationMarkerConfig()
4853
+ config: this.getObservationMarkerConfig(record)
4489
4854
  });
4490
- await writer.custom(startMarker).catch(() => {
4855
+ await writer.custom({ ...startMarker, transient: true }).catch(() => {
4491
4856
  });
4857
+ await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0);
4492
4858
  }
4493
4859
  this.emitDebugEvent({
4494
4860
  type: "reflection_triggered",
@@ -4503,8 +4869,11 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4503
4869
  cycleId,
4504
4870
  startedAt,
4505
4871
  recordId: record.id,
4506
- threadId
4872
+ threadId,
4873
+ resourceId: record.resourceId ?? void 0
4507
4874
  } : void 0;
4875
+ let reflectionUsage;
4876
+ let reflectionError;
4508
4877
  try {
4509
4878
  const compressionStartLevel = await this.getCompressionStartLevel(requestContext);
4510
4879
  const reflectResult = await this.call(
@@ -4518,6 +4887,7 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4518
4887
  requestContext,
4519
4888
  observabilityContext
4520
4889
  );
4890
+ reflectionUsage = reflectResult.usage;
4521
4891
  const reflectionTokenCount = this.tokenCounter.countObservations(reflectResult.observations);
4522
4892
  await this.storage.createReflectionGeneration({
4523
4893
  currentRecord: record,
@@ -4535,8 +4905,9 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4535
4905
  recordId: record.id,
4536
4906
  threadId
4537
4907
  });
4538
- await writer.custom(endMarker).catch(() => {
4908
+ await writer.custom({ ...endMarker, transient: true }).catch(() => {
4539
4909
  });
4910
+ await this.persistMarkerToStorage(endMarker, threadId, record.resourceId ?? void 0);
4540
4911
  }
4541
4912
  this.emitDebugEvent({
4542
4913
  type: "reflection_complete",
@@ -4559,89 +4930,413 @@ ${unreflectedContent}` : freshRecord.bufferedReflection;
4559
4930
  recordId: record.id,
4560
4931
  threadId
4561
4932
  });
4562
- await writer.custom(failedMarker).catch(() => {
4933
+ await writer.custom({ ...failedMarker, transient: true }).catch(() => {
4563
4934
  });
4935
+ await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0);
4564
4936
  }
4937
+ reflectionError = error instanceof Error ? error : new Error(String(error));
4565
4938
  if (abortSignal?.aborted) {
4566
4939
  throw error;
4567
4940
  }
4568
4941
  omError("[OM] Reflection failed", error);
4569
4942
  } finally {
4570
4943
  await this.storage.setReflectingFlag(record.id, false);
4571
- reflectionHooks?.onReflectionEnd?.();
4944
+ reflectionHooks?.onReflectionEnd?.({ usage: reflectionUsage, error: reflectionError });
4572
4945
  unregisterOp(record.id, "reflecting");
4573
4946
  }
4574
4947
  }
4575
4948
  };
4576
- var IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
4577
- "png",
4578
- "jpg",
4579
- "jpeg",
4580
- "webp",
4581
- "gif",
4582
- "bmp",
4583
- "tiff",
4584
- "tif",
4585
- "heic",
4586
- "heif",
4587
- "avif"
4588
- ]);
4589
- var TOKEN_ESTIMATE_CACHE_VERSION = 6;
4590
- var DEFAULT_IMAGE_ESTIMATOR = {
4591
- baseTokens: 85,
4592
- tileTokens: 170,
4593
- fallbackTiles: 4
4594
- };
4595
- var GOOGLE_LEGACY_IMAGE_TOKENS_PER_TILE = 258;
4596
- var GOOGLE_GEMINI_3_IMAGE_TOKENS_BY_RESOLUTION = {
4597
- low: 280,
4598
- medium: 560,
4599
- high: 1120,
4600
- ultra_high: 2240,
4601
- unspecified: 1120
4602
- };
4603
- var ANTHROPIC_IMAGE_TOKENS_PER_PIXEL = 1 / 750;
4604
- var ANTHROPIC_IMAGE_MAX_LONG_EDGE = 1568;
4605
- var GOOGLE_MEDIA_RESOLUTION_VALUES = /* @__PURE__ */ new Set([
4606
- "low",
4607
- "medium",
4608
- "high",
4609
- "ultra_high",
4610
- "unspecified"
4611
- ]);
4612
- var ATTACHMENT_COUNT_TIMEOUT_MS = 2e4;
4613
- var REMOTE_IMAGE_PROBE_TIMEOUT_MS = 2500;
4614
- var PROVIDER_API_KEY_ENV_VARS = {
4615
- openai: ["OPENAI_API_KEY"],
4616
- google: ["GOOGLE_GENERATIVE_AI_API_KEY", "GOOGLE_API_KEY"],
4617
- anthropic: ["ANTHROPIC_API_KEY"]
4618
- };
4619
- function getPartMastraMetadata(part) {
4620
- return part.providerMetadata?.mastra;
4949
+ function getOmReproCaptureDir() {
4950
+ return process.env.OM_REPRO_CAPTURE_DIR ?? ".mastra-om-repro";
4621
4951
  }
4622
- function ensurePartMastraMetadata(part) {
4623
- const typedPart = part;
4624
- typedPart.providerMetadata ??= {};
4625
- typedPart.providerMetadata.mastra ??= {};
4626
- return typedPart.providerMetadata.mastra;
4952
+ function sanitizeCapturePathSegment(value) {
4953
+ const sanitized = value.replace(/[\\/]+/g, "_").replace(/\.{2,}/g, "_").trim();
4954
+ return sanitized.length > 0 ? sanitized : "unknown-thread";
4627
4955
  }
4628
- function getContentMastraMetadata(content) {
4629
- if (!content || typeof content !== "object") {
4630
- return void 0;
4956
+ function isOmReproCaptureEnabled() {
4957
+ return process.env.OM_REPRO_CAPTURE === "1";
4958
+ }
4959
+ function safeCaptureJson(value) {
4960
+ return JSON.parse(
4961
+ JSON.stringify(value, (_key, current) => {
4962
+ if (typeof current === "bigint") return current.toString();
4963
+ if (typeof current === "function") return "[function]";
4964
+ if (typeof current === "symbol") return current.toString();
4965
+ if (current instanceof Error) return { name: current.name, message: current.message, stack: current.stack };
4966
+ if (current instanceof Set) return { __type: "Set", values: Array.from(current.values()) };
4967
+ if (current instanceof Map) return { __type: "Map", entries: Array.from(current.entries()) };
4968
+ return current;
4969
+ })
4970
+ );
4971
+ }
4972
+ function safeCaptureJsonOrError(value) {
4973
+ try {
4974
+ return { ok: true, value: safeCaptureJson(value) };
4975
+ } catch (error) {
4976
+ return {
4977
+ ok: false,
4978
+ error: safeCaptureJson({
4979
+ message: error instanceof Error ? error.message : String(error),
4980
+ stack: error instanceof Error ? error.stack : void 0,
4981
+ inspected: inspect(value, { depth: 3, maxArrayLength: 20, breakLength: 120 })
4982
+ })
4983
+ };
4631
4984
  }
4632
- return content.metadata?.mastra;
4633
4985
  }
4634
- function ensureContentMastraMetadata(content) {
4635
- if (!content || typeof content !== "object") {
4986
+ function formatCaptureDate(value) {
4987
+ if (!value) return void 0;
4988
+ if (value instanceof Date) return value.toISOString();
4989
+ try {
4990
+ return new Date(value).toISOString();
4991
+ } catch {
4636
4992
  return void 0;
4637
4993
  }
4638
- const typedContent = content;
4639
- typedContent.metadata ??= {};
4640
- typedContent.metadata.mastra ??= {};
4641
- return typedContent.metadata.mastra;
4642
- }
4643
- function getMessageMastraMetadata(message) {
4644
- return message.metadata?.mastra;
4994
+ }
4995
+ function summarizeOmTurn(value) {
4996
+ if (!value || typeof value !== "object") {
4997
+ return value;
4998
+ }
4999
+ const turn = value;
5000
+ return {
5001
+ __type: "ObservationTurn",
5002
+ threadId: turn.threadId,
5003
+ resourceId: turn.resourceId,
5004
+ started: turn._started,
5005
+ ended: turn._ended,
5006
+ generationCountAtStart: turn._generationCountAtStart,
5007
+ record: turn._record ? {
5008
+ id: turn._record.id,
5009
+ scope: turn._record.scope,
5010
+ threadId: turn._record.threadId,
5011
+ resourceId: turn._record.resourceId,
5012
+ createdAt: formatCaptureDate(turn._record.createdAt),
5013
+ updatedAt: formatCaptureDate(turn._record.updatedAt),
5014
+ lastObservedAt: formatCaptureDate(turn._record.lastObservedAt),
5015
+ generationCount: turn._record.generationCount,
5016
+ observationTokenCount: turn._record.observationTokenCount,
5017
+ pendingMessageTokens: turn._record.pendingMessageTokens,
5018
+ isBufferingObservation: turn._record.isBufferingObservation,
5019
+ isBufferingReflection: turn._record.isBufferingReflection
5020
+ } : void 0,
5021
+ context: turn._context ? {
5022
+ messageCount: Array.isArray(turn._context.messages) ? turn._context.messages.length : void 0,
5023
+ hasSystemMessage: Array.isArray(turn._context.systemMessage) ? turn._context.systemMessage.length > 0 : Boolean(turn._context.systemMessage),
5024
+ continuationId: turn._context.continuation?.id,
5025
+ hasOtherThreadsContext: Boolean(turn._context.otherThreadsContext),
5026
+ recordId: turn._context.record?.id
5027
+ } : void 0,
5028
+ currentStep: turn._currentStep ? {
5029
+ stepNumber: turn._currentStep.stepNumber,
5030
+ prepared: turn._currentStep._prepared,
5031
+ context: turn._currentStep._context ? {
5032
+ activated: turn._currentStep._context.activated,
5033
+ observed: turn._currentStep._context.observed,
5034
+ buffered: turn._currentStep._context.buffered,
5035
+ reflected: turn._currentStep._context.reflected,
5036
+ didThresholdCleanup: turn._currentStep._context.didThresholdCleanup,
5037
+ messageCount: Array.isArray(turn._currentStep._context.messages) ? turn._currentStep._context.messages.length : void 0,
5038
+ systemMessageCount: Array.isArray(turn._currentStep._context.systemMessage) ? turn._currentStep._context.systemMessage.length : void 0
5039
+ } : void 0
5040
+ } : void 0
5041
+ };
5042
+ }
5043
+ function sanitizeCaptureState(rawState) {
5044
+ return Object.fromEntries(
5045
+ Object.entries(rawState).map(([key, value]) => {
5046
+ if (key === "__omTurn") {
5047
+ return [key, summarizeOmTurn(value)];
5048
+ }
5049
+ return [key, value];
5050
+ })
5051
+ );
5052
+ }
5053
+ function buildReproMessageFingerprint(message) {
5054
+ const createdAt = message.createdAt instanceof Date ? message.createdAt.toISOString() : message.createdAt ? new Date(message.createdAt).toISOString() : "";
5055
+ return JSON.stringify({
5056
+ role: message.role,
5057
+ createdAt,
5058
+ content: message.content
5059
+ });
5060
+ }
5061
+ function inferReproIdRemap(preMessages, postMessages) {
5062
+ const preByFingerprint = /* @__PURE__ */ new Map();
5063
+ const postByFingerprint = /* @__PURE__ */ new Map();
5064
+ for (const message of preMessages) {
5065
+ if (!message.id) continue;
5066
+ const fingerprint = buildReproMessageFingerprint(message);
5067
+ const list = preByFingerprint.get(fingerprint) ?? [];
5068
+ list.push(message.id);
5069
+ preByFingerprint.set(fingerprint, list);
5070
+ }
5071
+ for (const message of postMessages) {
5072
+ if (!message.id) continue;
5073
+ const fingerprint = buildReproMessageFingerprint(message);
5074
+ const list = postByFingerprint.get(fingerprint) ?? [];
5075
+ list.push(message.id);
5076
+ postByFingerprint.set(fingerprint, list);
5077
+ }
5078
+ const remap = [];
5079
+ for (const [fingerprint, preIds] of preByFingerprint.entries()) {
5080
+ const postIds = postByFingerprint.get(fingerprint);
5081
+ if (!postIds || preIds.length !== 1 || postIds.length !== 1) continue;
5082
+ const fromId = preIds[0];
5083
+ const toId = postIds[0];
5084
+ if (!fromId || !toId || fromId === toId) {
5085
+ continue;
5086
+ }
5087
+ remap.push({ fromId, toId, fingerprint });
5088
+ }
5089
+ return remap;
5090
+ }
5091
+ function createOmReproCaptureDir(threadId, label) {
5092
+ const sanitizedThreadId = sanitizeCapturePathSegment(threadId);
5093
+ const captureDir = join(
5094
+ process.cwd(),
5095
+ getOmReproCaptureDir(),
5096
+ sanitizedThreadId,
5097
+ `${Date.now()}-${label}-${randomUUID()}`
5098
+ );
5099
+ mkdirSync(captureDir, { recursive: true });
5100
+ return captureDir;
5101
+ }
5102
+ function writeObserverExchangeReproCapture(params) {
5103
+ if (!isOmReproCaptureEnabled() || !params.observerExchange) {
5104
+ return;
5105
+ }
5106
+ try {
5107
+ const captureDir = createOmReproCaptureDir(params.threadId, params.label);
5108
+ const payloads = [
5109
+ {
5110
+ fileName: "input.json",
5111
+ data: {
5112
+ threadId: params.threadId,
5113
+ resourceId: params.resourceId,
5114
+ label: params.label
5115
+ }
5116
+ },
5117
+ {
5118
+ fileName: "output.json",
5119
+ data: {
5120
+ details: params.details ?? {}
5121
+ }
5122
+ },
5123
+ {
5124
+ fileName: "observer-exchange.json",
5125
+ data: params.observerExchange
5126
+ }
5127
+ ];
5128
+ const captureErrors = [];
5129
+ for (const payload of payloads) {
5130
+ const serialized = safeCaptureJsonOrError(payload.data);
5131
+ if (serialized.ok) {
5132
+ writeFileSync(join(captureDir, payload.fileName), `${JSON.stringify(serialized.value, null, 2)}
5133
+ `);
5134
+ continue;
5135
+ }
5136
+ captureErrors.push({ fileName: payload.fileName, error: serialized.error });
5137
+ writeFileSync(
5138
+ join(captureDir, payload.fileName),
5139
+ `${JSON.stringify({ __captureError: serialized.error }, null, 2)}
5140
+ `
5141
+ );
5142
+ }
5143
+ if (captureErrors.length > 0) {
5144
+ writeFileSync(join(captureDir, "capture-error.json"), `${JSON.stringify(captureErrors, null, 2)}
5145
+ `);
5146
+ params.debug?.(
5147
+ `[OM:repro-capture] wrote ${params.label} capture with ${captureErrors.length} serialization error(s) to ${captureDir}`
5148
+ );
5149
+ return;
5150
+ }
5151
+ params.debug?.(`[OM:repro-capture] wrote ${params.label} capture to ${captureDir}`);
5152
+ } catch (error) {
5153
+ params.debug?.(`[OM:repro-capture] failed to write ${params.label} capture: ${String(error)}`);
5154
+ }
5155
+ }
5156
+ function writeProcessInputStepReproCapture(params) {
5157
+ if (!isOmReproCaptureEnabled()) {
5158
+ return;
5159
+ }
5160
+ try {
5161
+ const captureDir = createOmReproCaptureDir(params.threadId, `step-${params.stepNumber}`);
5162
+ const contextMessages = params.messageList.get.all.db();
5163
+ const memoryContext = parseMemoryRequestContext(params.args.requestContext);
5164
+ const preMessageIds = new Set(params.preMessages.map((message) => message.id));
5165
+ const postMessageIds = new Set(contextMessages.map((message) => message.id));
5166
+ const removedMessageIds = params.preMessages.map((message) => message.id).filter((id) => Boolean(id) && !postMessageIds.has(id));
5167
+ const addedMessageIds = contextMessages.map((message) => message.id).filter((id) => Boolean(id) && !preMessageIds.has(id));
5168
+ const idRemap = inferReproIdRemap(params.preMessages, contextMessages);
5169
+ const rawState = params.args.state ?? {};
5170
+ const sanitizedState = sanitizeCaptureState(rawState);
5171
+ const payloads = [
5172
+ {
5173
+ fileName: "input.json",
5174
+ data: {
5175
+ stepNumber: params.stepNumber,
5176
+ threadId: params.threadId,
5177
+ resourceId: params.resourceId,
5178
+ readOnly: memoryContext?.memoryConfig?.readOnly,
5179
+ messageCount: contextMessages.length,
5180
+ messageIds: contextMessages.map((message) => message.id),
5181
+ stateKeys: Object.keys(rawState),
5182
+ state: sanitizedState,
5183
+ args: {
5184
+ messages: params.args.messages,
5185
+ steps: params.args.steps,
5186
+ systemMessages: params.args.systemMessages,
5187
+ retryCount: params.args.retryCount,
5188
+ toolChoice: params.args.toolChoice,
5189
+ activeTools: params.args.activeTools,
5190
+ modelSettings: params.args.modelSettings,
5191
+ structuredOutput: params.args.structuredOutput
5192
+ }
5193
+ }
5194
+ },
5195
+ {
5196
+ fileName: "pre-state.json",
5197
+ data: {
5198
+ record: params.preRecord,
5199
+ bufferedChunks: params.preBufferedChunks,
5200
+ contextTokenCount: params.preContextTokenCount,
5201
+ messages: params.preMessages,
5202
+ messageList: params.preSerializedMessageList
5203
+ }
5204
+ },
5205
+ {
5206
+ fileName: "output.json",
5207
+ data: {
5208
+ details: params.details,
5209
+ messageDiff: {
5210
+ removedMessageIds,
5211
+ addedMessageIds,
5212
+ idRemap
5213
+ }
5214
+ }
5215
+ },
5216
+ {
5217
+ fileName: "post-state.json",
5218
+ data: {
5219
+ record: params.postRecord,
5220
+ bufferedChunks: params.postBufferedChunks,
5221
+ contextTokenCount: params.postContextTokenCount,
5222
+ messageCount: contextMessages.length,
5223
+ messageIds: contextMessages.map((message) => message.id),
5224
+ messages: contextMessages,
5225
+ messageList: params.messageList.serialize()
5226
+ }
5227
+ }
5228
+ ];
5229
+ const captureErrors = [];
5230
+ for (const payload of payloads) {
5231
+ const serialized = safeCaptureJsonOrError(payload.data);
5232
+ if (serialized.ok) {
5233
+ writeFileSync(join(captureDir, payload.fileName), `${JSON.stringify(serialized.value, null, 2)}
5234
+ `);
5235
+ continue;
5236
+ }
5237
+ captureErrors.push({ fileName: payload.fileName, error: serialized.error });
5238
+ writeFileSync(
5239
+ join(captureDir, payload.fileName),
5240
+ `${JSON.stringify({ __captureError: serialized.error }, null, 2)}
5241
+ `
5242
+ );
5243
+ }
5244
+ if (params.observerExchange) {
5245
+ const serialized = safeCaptureJsonOrError(params.observerExchange);
5246
+ if (serialized.ok) {
5247
+ writeFileSync(join(captureDir, "observer-exchange.json"), `${JSON.stringify(serialized.value, null, 2)}
5248
+ `);
5249
+ } else {
5250
+ captureErrors.push({ fileName: "observer-exchange.json", error: serialized.error });
5251
+ writeFileSync(
5252
+ join(captureDir, "observer-exchange.json"),
5253
+ `${JSON.stringify({ __captureError: serialized.error }, null, 2)}
5254
+ `
5255
+ );
5256
+ }
5257
+ }
5258
+ if (captureErrors.length > 0) {
5259
+ writeFileSync(join(captureDir, "capture-error.json"), `${JSON.stringify(captureErrors, null, 2)}
5260
+ `);
5261
+ params.debug?.(
5262
+ `[OM:repro-capture] wrote processInputStep capture with ${captureErrors.length} serialization error(s) to ${captureDir}`
5263
+ );
5264
+ return;
5265
+ }
5266
+ params.debug?.(`[OM:repro-capture] wrote processInputStep capture to ${captureDir}`);
5267
+ } catch (error) {
5268
+ params.debug?.(`[OM:repro-capture] failed to write processInputStep capture: ${String(error)}`);
5269
+ }
5270
+ }
5271
+ var IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
5272
+ "png",
5273
+ "jpg",
5274
+ "jpeg",
5275
+ "webp",
5276
+ "gif",
5277
+ "bmp",
5278
+ "tiff",
5279
+ "tif",
5280
+ "heic",
5281
+ "heif",
5282
+ "avif"
5283
+ ]);
5284
+ var TOKEN_ESTIMATE_CACHE_VERSION = 6;
5285
+ var DEFAULT_IMAGE_ESTIMATOR = {
5286
+ baseTokens: 85,
5287
+ tileTokens: 170,
5288
+ fallbackTiles: 4
5289
+ };
5290
+ var GOOGLE_LEGACY_IMAGE_TOKENS_PER_TILE = 258;
5291
+ var GOOGLE_GEMINI_3_IMAGE_TOKENS_BY_RESOLUTION = {
5292
+ low: 280,
5293
+ medium: 560,
5294
+ high: 1120,
5295
+ ultra_high: 2240,
5296
+ unspecified: 1120
5297
+ };
5298
+ var ANTHROPIC_IMAGE_TOKENS_PER_PIXEL = 1 / 750;
5299
+ var ANTHROPIC_IMAGE_MAX_LONG_EDGE = 1568;
5300
+ var GOOGLE_MEDIA_RESOLUTION_VALUES = /* @__PURE__ */ new Set([
5301
+ "low",
5302
+ "medium",
5303
+ "high",
5304
+ "ultra_high",
5305
+ "unspecified"
5306
+ ]);
5307
+ var ATTACHMENT_COUNT_TIMEOUT_MS = 2e4;
5308
+ var REMOTE_IMAGE_PROBE_TIMEOUT_MS = 2500;
5309
+ var PROVIDER_API_KEY_ENV_VARS = {
5310
+ openai: ["OPENAI_API_KEY"],
5311
+ google: ["GOOGLE_GENERATIVE_AI_API_KEY", "GOOGLE_API_KEY"],
5312
+ anthropic: ["ANTHROPIC_API_KEY"]
5313
+ };
5314
+ function getPartMastraMetadata(part) {
5315
+ return part.providerMetadata?.mastra;
5316
+ }
5317
+ function ensurePartMastraMetadata(part) {
5318
+ const typedPart = part;
5319
+ typedPart.providerMetadata ??= {};
5320
+ typedPart.providerMetadata.mastra ??= {};
5321
+ return typedPart.providerMetadata.mastra;
5322
+ }
5323
+ function getContentMastraMetadata(content) {
5324
+ if (!content || typeof content !== "object") {
5325
+ return void 0;
5326
+ }
5327
+ return content.metadata?.mastra;
5328
+ }
5329
+ function ensureContentMastraMetadata(content) {
5330
+ if (!content || typeof content !== "object") {
5331
+ return void 0;
5332
+ }
5333
+ const typedContent = content;
5334
+ typedContent.metadata ??= {};
5335
+ typedContent.metadata.mastra ??= {};
5336
+ return typedContent.metadata.mastra;
5337
+ }
5338
+ function getMessageMastraMetadata(message) {
5339
+ return message.metadata?.mastra;
4645
5340
  }
4646
5341
  function ensureMessageMastraMetadata(message) {
4647
5342
  const typedMessage = message;
@@ -5800,6 +6495,88 @@ function buildMessageRange(messages) {
5800
6495
  const last = [...messages].reverse().find(messageHasVisibleContent) ?? messages[messages.length - 1];
5801
6496
  return `${first.id}:${last.id}`;
5802
6497
  }
6498
+ function getLastActivityFromMessages(messages) {
6499
+ if (!messages) return void 0;
6500
+ for (let i = messages.length - 1; i >= 0; i--) {
6501
+ const message = messages[i];
6502
+ if (!message || message.role !== "assistant") {
6503
+ continue;
6504
+ }
6505
+ if (!message.content || typeof message.content === "string") {
6506
+ return message.createdAt ? new Date(message.createdAt).getTime() : void 0;
6507
+ }
6508
+ for (let j = message.content.parts.length - 1; j >= 0; j--) {
6509
+ const part = message.content.parts[j];
6510
+ if (!part || part.type?.startsWith("data-")) {
6511
+ continue;
6512
+ }
6513
+ if (part.createdAt !== void 0) {
6514
+ return part.createdAt;
6515
+ }
6516
+ }
6517
+ return message.createdAt ? new Date(message.createdAt).getTime() : void 0;
6518
+ }
6519
+ return void 0;
6520
+ }
6521
+ function formatModelContext2(provider, modelId) {
6522
+ if (provider && modelId) {
6523
+ return `${provider}/${modelId}`;
6524
+ }
6525
+ return modelId;
6526
+ }
6527
+ function getLastModelFromMessages(messages) {
6528
+ if (!messages) return void 0;
6529
+ for (let i = messages.length - 1; i >= 0; i--) {
6530
+ const message = messages[i];
6531
+ if (!message || message.role !== "assistant" || !message.content || typeof message.content === "string") {
6532
+ continue;
6533
+ }
6534
+ for (let j = message.content.parts.length - 1; j >= 0; j--) {
6535
+ const part = message.content.parts[j];
6536
+ if (part?.type === "step-start" && typeof part.model === "string" && part.model.length > 0) {
6537
+ return part.model;
6538
+ }
6539
+ }
6540
+ const metadata = message.content.metadata;
6541
+ const model = formatModelContext2(metadata?.provider, metadata?.modelId);
6542
+ if (model) {
6543
+ return model;
6544
+ }
6545
+ }
6546
+ return void 0;
6547
+ }
6548
+ function getCurrentModel2(model) {
6549
+ return formatModelContext2(model?.provider, model?.modelId);
6550
+ }
6551
+ function parseActivationTTL(value, fieldPath) {
6552
+ if (value === void 0) {
6553
+ return void 0;
6554
+ }
6555
+ if (typeof value === "number") {
6556
+ if (!Number.isFinite(value) || value < 0) {
6557
+ throw new Error(`${fieldPath} must be a non-negative number of milliseconds or a duration string like "5m".`);
6558
+ }
6559
+ return value;
6560
+ }
6561
+ const trimmed = value.trim();
6562
+ const match = trimmed.match(
6563
+ /^(\d+(?:\.\d+)?)\s*(ms|msec|msecs|millisecond|milliseconds|s|sec|secs|second|seconds|m|min|mins|minute|minutes|h|hr|hrs|hour|hours)$/i
6564
+ );
6565
+ if (!match) {
6566
+ throw new Error(
6567
+ `${fieldPath} must be a non-negative number of milliseconds or a duration string like "5m" or "1hr".`
6568
+ );
6569
+ }
6570
+ const rawAmount = match[1];
6571
+ const rawUnit = match[2];
6572
+ const amount = Number(rawAmount);
6573
+ const unit = rawUnit.toLowerCase();
6574
+ if (!Number.isFinite(amount) || amount < 0) {
6575
+ throw new Error(`${fieldPath} must be a non-negative number of milliseconds or a duration string like "5m".`);
6576
+ }
6577
+ 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;
6578
+ return amount * multiplier;
6579
+ }
5803
6580
  var ObservationalMemory = class _ObservationalMemory {
5804
6581
  storage;
5805
6582
  tokenCounter;
@@ -5946,6 +6723,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
5946
6723
  config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens
5947
6724
  ),
5948
6725
  bufferActivation: asyncBufferingDisabled ? void 0 : config.observation?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferActivation,
6726
+ activateAfterIdle: parseActivationTTL(config.activateAfterIdle, "activateAfterIdle"),
6727
+ activateOnProviderChange: config.activateOnProviderChange ?? false,
5949
6728
  blockAfter: asyncBufferingDisabled ? void 0 : resolveBlockAfter(
5950
6729
  config.observation?.blockAfter ?? (config.observation?.bufferTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.bufferTokens ? 1.2 : void 0),
5951
6730
  config.observation?.messageTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens
@@ -5964,6 +6743,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
5964
6743
  },
5965
6744
  providerOptions: config.reflection?.providerOptions ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.providerOptions,
5966
6745
  bufferActivation: asyncBufferingDisabled ? void 0 : config?.reflection?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.bufferActivation,
6746
+ activateAfterIdle: parseActivationTTL(config.activateAfterIdle, "activateAfterIdle"),
6747
+ activateOnProviderChange: config.activateOnProviderChange ?? false,
5967
6748
  blockAfter: asyncBufferingDisabled ? void 0 : resolveBlockAfter(
5968
6749
  config.reflection?.blockAfter ?? (config.reflection?.bufferActivation ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.bufferActivation ? 1.2 : void 0),
5969
6750
  config.reflection?.observationTokens ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.observationTokens
@@ -6244,6 +7025,47 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
6244
7025
  }
6245
7026
  }
6246
7027
  }
7028
+ /**
7029
+ * Resolve the effective messageTokens for a record.
7030
+ * Only explicit per-record overrides (stored under `_overrides`) win;
7031
+ * the initial config snapshot written by getOrCreateRecord() is ignored
7032
+ * so that later instance-level changes still take effect.
7033
+ *
7034
+ * Overrides that fall below the instance-level buffering floor
7035
+ * (bufferTokens / absolute bufferActivation) are clamped to the
7036
+ * instance threshold to preserve buffering invariants.
7037
+ */
7038
+ getEffectiveMessageTokens(record) {
7039
+ const overrides = record.config?._overrides;
7040
+ const recordTokens = overrides?.observation?.messageTokens;
7041
+ if (recordTokens) {
7042
+ const maxOverride = getMaxThreshold(recordTokens);
7043
+ const bufferTokens = this.observationConfig.bufferTokens;
7044
+ if (bufferTokens && maxOverride <= bufferTokens) {
7045
+ return this.observationConfig.messageTokens;
7046
+ }
7047
+ const bufferActivation = this.observationConfig.bufferActivation;
7048
+ if (bufferActivation && bufferActivation >= 1e3 && maxOverride <= bufferActivation) {
7049
+ return this.observationConfig.messageTokens;
7050
+ }
7051
+ return recordTokens;
7052
+ }
7053
+ return this.observationConfig.messageTokens;
7054
+ }
7055
+ /**
7056
+ * Resolve the effective reflection observationTokens for a record.
7057
+ * Only explicit per-record overrides (stored under `_overrides`) win;
7058
+ * the initial config snapshot is ignored so instance-level changes
7059
+ * still take effect for existing records.
7060
+ */
7061
+ getEffectiveReflectionTokens(record) {
7062
+ const overrides = record.config?._overrides;
7063
+ const recordTokens = overrides?.reflection?.observationTokens;
7064
+ if (recordTokens) {
7065
+ return recordTokens;
7066
+ }
7067
+ return this.reflectionConfig.observationTokens;
7068
+ }
6247
7069
  /**
6248
7070
  * Check whether the unobserved message tokens meet the observation threshold.
6249
7071
  */
@@ -6251,7 +7073,7 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
6251
7073
  const { record, unobservedTokens, extraTokens = 0 } = opts;
6252
7074
  const pendingTokens = (record.pendingMessageTokens ?? 0) + unobservedTokens + extraTokens;
6253
7075
  const currentObservationTokens = record.observationTokenCount ?? 0;
6254
- const threshold = calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
7076
+ const threshold = calculateDynamicThreshold(this.getEffectiveMessageTokens(record), currentObservationTokens);
6255
7077
  return pendingTokens >= threshold;
6256
7078
  }
6257
7079
  /**
@@ -6317,7 +7139,8 @@ Async buffering is enabled by default \u2014 this opt-out is only needed when us
6317
7139
  return {
6318
7140
  messageTokens: getMaxThreshold(this.observationConfig.messageTokens),
6319
7141
  observationTokens: getMaxThreshold(this.reflectionConfig.observationTokens),
6320
- scope: this.scope
7142
+ scope: this.scope,
7143
+ activateAfterIdle: this.observationConfig.activateAfterIdle
6321
7144
  };
6322
7145
  }
6323
7146
  /**
@@ -7038,17 +7861,18 @@ ${grouped}` : grouped;
7038
7861
  const cycleId = `buffer-obs-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
7039
7862
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7040
7863
  const tokensToBuffer = await this.tokenCounter.countMessagesAsync(messagesToBuffer);
7864
+ const startMarker = createBufferingStartMarker({
7865
+ cycleId,
7866
+ operationType: "observation",
7867
+ tokensToBuffer,
7868
+ recordId: freshRecord.id,
7869
+ threadId,
7870
+ threadIds: [threadId],
7871
+ config: this.getObservationMarkerConfig()
7872
+ });
7873
+ await this.persistMarkerToStorage(startMarker, threadId, freshRecord.resourceId ?? void 0);
7041
7874
  if (writer) {
7042
- const startMarker = createBufferingStartMarker({
7043
- cycleId,
7044
- operationType: "observation",
7045
- tokensToBuffer,
7046
- recordId: freshRecord.id,
7047
- threadId,
7048
- threadIds: [threadId],
7049
- config: this.getObservationMarkerConfig()
7050
- });
7051
- void writer.custom(startMarker).catch(() => {
7875
+ void writer.custom({ ...startMarker, transient: true }).catch(() => {
7052
7876
  });
7053
7877
  }
7054
7878
  omDebug(
@@ -7344,10 +8168,12 @@ ${grouped}` : grouped;
7344
8168
  async getOtherThreadsContext(resourceId, currentThreadId) {
7345
8169
  const { threads: allThreads } = await this.storage.listThreads({ filter: { resourceId } });
7346
8170
  const messagesByThread = /* @__PURE__ */ new Map();
8171
+ const record = await this.getRecord(currentThreadId, resourceId);
8172
+ const recordLastObservedAt = record?.lastObservedAt;
7347
8173
  for (const thread of allThreads) {
7348
8174
  if (thread.id === currentThreadId) continue;
7349
8175
  const omMetadata = getThreadOMMetadata(thread.metadata);
7350
- const threadLastObservedAt = omMetadata?.lastObservedAt;
8176
+ const threadLastObservedAt = omMetadata?.lastObservedAt ?? recordLastObservedAt;
7351
8177
  const startDate = threadLastObservedAt ? new Date(new Date(threadLastObservedAt).getTime() + 1) : void 0;
7352
8178
  const result = await this.storage.listMessages({
7353
8179
  threadId: thread.id,
@@ -7400,7 +8226,7 @@ ${grouped}` : grouped;
7400
8226
  const projectedMessageRemoval = calculateProjectedMessageRemoval(
7401
8227
  bufferedChunks,
7402
8228
  this.observationConfig.bufferActivation ?? 1,
7403
- getMaxThreshold(this.observationConfig.messageTokens),
8229
+ getMaxThreshold(this.getEffectiveMessageTokens(record)),
7404
8230
  pendingTokens
7405
8231
  );
7406
8232
  let obsBufferStatus = "idle";
@@ -7489,7 +8315,7 @@ ${grouped}` : grouped;
7489
8315
  otherThreadTokens = otherContext ? this.tokenCounter.countString(otherContext) : 0;
7490
8316
  }
7491
8317
  const pendingTokens = Math.max(0, contextWindowTokens + otherThreadTokens);
7492
- const threshold = calculateDynamicThreshold(this.observationConfig.messageTokens, currentObservationTokens);
8318
+ const threshold = calculateDynamicThreshold(this.getEffectiveMessageTokens(record), currentObservationTokens);
7493
8319
  const bufferedChunks = getBufferedChunks(record);
7494
8320
  const bufferedChunkCount = bufferedChunks.length;
7495
8321
  const bufferedChunkTokens = bufferedChunks.reduce((sum, chunk) => sum + (chunk.messageTokens ?? 0), 0);
@@ -7506,11 +8332,12 @@ ${grouped}` : grouped;
7506
8332
  );
7507
8333
  }
7508
8334
  const shouldObserve = pendingTokens >= threshold;
7509
- const reflectThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
8335
+ const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
7510
8336
  const shouldReflect = currentObservationTokens >= reflectThreshold;
7511
8337
  const canActivate = bufferedChunkCount > 0;
7512
- const isSharedBudget = typeof this.observationConfig.messageTokens !== "number";
7513
- const totalBudget = isSharedBudget ? this.observationConfig.messageTokens.max : 0;
8338
+ const effectiveMessageTokens = this.getEffectiveMessageTokens(record);
8339
+ const isSharedBudget = typeof effectiveMessageTokens !== "number";
8340
+ const totalBudget = isSharedBudget ? effectiveMessageTokens.max : 0;
7514
8341
  const effectiveObservationTokensThreshold = isSharedBudget ? Math.max(totalBudget - threshold, 1e3) : reflectThreshold;
7515
8342
  const unbufferedPendingTokens = Math.max(0, pendingTokens - bufferedChunkTokens);
7516
8343
  return {
@@ -7554,7 +8381,7 @@ ${grouped}` : grouped;
7554
8381
  await BufferingCoordinator.awaitBuffering(threadId, resourceId ?? null, this.scope);
7555
8382
  const preStatus = await this.getStatus({ threadId, resourceId, messages });
7556
8383
  if (preStatus.canActivate) {
7557
- const actResult = await this.activate({ threadId, resourceId });
8384
+ const actResult = await this.activate({ threadId, resourceId, messages });
7558
8385
  activated = actResult.activated;
7559
8386
  }
7560
8387
  const postStatus = await this.getStatus({ threadId, resourceId, messages });
@@ -7705,18 +8532,19 @@ ${grouped}` : grouped;
7705
8532
  }
7706
8533
  const cycleId = `buffer-obs-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
7707
8534
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
8535
+ const startMarker = createBufferingStartMarker({
8536
+ cycleId,
8537
+ operationType: "observation",
8538
+ tokensToBuffer: newTokens,
8539
+ recordId: record.id,
8540
+ threadId,
8541
+ threadIds: [threadId],
8542
+ config: this.getObservationMarkerConfig()
8543
+ });
8544
+ await this.persistMarkerToStorage(startMarker, threadId, record.resourceId ?? void 0);
7708
8545
  const writer = opts.writer;
7709
8546
  if (writer) {
7710
- const startMarker = createBufferingStartMarker({
7711
- cycleId,
7712
- operationType: "observation",
7713
- tokensToBuffer: newTokens,
7714
- recordId: record.id,
7715
- threadId,
7716
- threadIds: [threadId],
7717
- config: this.getObservationMarkerConfig()
7718
- });
7719
- void writer.custom(startMarker).catch(() => {
8547
+ void writer.custom({ ...startMarker, transient: true }).catch(() => {
7720
8548
  });
7721
8549
  }
7722
8550
  await ObservationStrategy.create(this, {
@@ -7730,6 +8558,23 @@ ${grouped}` : grouped;
7730
8558
  requestContext,
7731
8559
  observabilityContext
7732
8560
  }).run();
8561
+ if (isOmReproCaptureEnabled()) {
8562
+ writeObserverExchangeReproCapture({
8563
+ threadId,
8564
+ resourceId: record.resourceId ?? void 0,
8565
+ label: `buffer-${cycleId}`,
8566
+ observerExchange: this.observer.lastExchange,
8567
+ details: {
8568
+ cycleId,
8569
+ startedAt,
8570
+ buffered: true,
8571
+ candidateMessageIds: candidateMessages.map((message) => message.id),
8572
+ candidateMessageCount: candidateMessages.length,
8573
+ pendingTokens: currentTokens,
8574
+ newTokens
8575
+ }
8576
+ });
8577
+ }
7733
8578
  await this.storage.setBufferingObservationFlag(record.id, false, newTokens).catch(() => {
7734
8579
  });
7735
8580
  flagCleared = true;
@@ -7797,10 +8642,37 @@ ${grouped}` : grouped;
7797
8642
  if (!chunks.length) {
7798
8643
  return { activated: false, record };
7799
8644
  }
8645
+ let activationTriggeredBy = "threshold";
8646
+ let activationLastActivityAt;
8647
+ let activateAfterIdleExpiredMs;
8648
+ let previousModel;
8649
+ let currentModel;
7800
8650
  if (opts.checkThreshold) {
7801
- const status = await this.getStatus({ threadId, resourceId, messages: opts.messages });
7802
- if (status.pendingTokens < status.threshold) {
7803
- return { activated: false, record };
8651
+ const thresholdMessages = opts.messages ?? await this.loadMessagesFromStorage(
8652
+ threadId,
8653
+ resourceId,
8654
+ record.lastObservedAt ? new Date(record.lastObservedAt) : void 0
8655
+ );
8656
+ const activateAfterIdle = this.observationConfig.activateAfterIdle;
8657
+ const lastActivityAt = getLastActivityFromMessages(thresholdMessages);
8658
+ const ttlExpiredMs = activateAfterIdle !== void 0 && lastActivityAt !== void 0 ? Date.now() - lastActivityAt : void 0;
8659
+ const ttlExpired = ttlExpiredMs !== void 0 && activateAfterIdle !== void 0 && ttlExpiredMs >= activateAfterIdle;
8660
+ const actorModel = getCurrentModel2(opts.currentModel);
8661
+ const lastModel = getLastModelFromMessages(thresholdMessages);
8662
+ const providerChanged = this.observationConfig.activateOnProviderChange === true && didProviderChange(actorModel, lastModel);
8663
+ if (providerChanged) {
8664
+ activationTriggeredBy = "provider_change";
8665
+ previousModel = lastModel;
8666
+ currentModel = actorModel;
8667
+ } else if (ttlExpired) {
8668
+ activationTriggeredBy = "ttl";
8669
+ activationLastActivityAt = lastActivityAt;
8670
+ activateAfterIdleExpiredMs = ttlExpiredMs;
8671
+ } else {
8672
+ const status = await this.getStatus({ threadId, resourceId, messages: thresholdMessages });
8673
+ if (status.pendingTokens < status.threshold) {
8674
+ return { activated: false, record };
8675
+ }
7804
8676
  }
7805
8677
  }
7806
8678
  if (record.isBufferingObservation) {
@@ -7825,7 +8697,7 @@ ${grouped}` : grouped;
7825
8697
  if (!freshChunks.length) {
7826
8698
  return { activated: false, record };
7827
8699
  }
7828
- const messageTokensThreshold = getMaxThreshold(this.observationConfig.messageTokens);
8700
+ const messageTokensThreshold = getMaxThreshold(this.getEffectiveMessageTokens(freshRecord));
7829
8701
  const bufferActivation = this.observationConfig.bufferActivation ?? 0.7;
7830
8702
  const activationRatio = resolveActivationRatio(bufferActivation, messageTokensThreshold);
7831
8703
  const totalChunkMessageTokens = freshChunks.reduce((sum, c) => sum + (c.messageTokens ?? 0), 0);
@@ -7858,9 +8730,14 @@ ${grouped}` : grouped;
7858
8730
  threadId: postSwapRecord.threadId ?? record.threadId ?? "",
7859
8731
  generationCount: postSwapRecord.generationCount ?? 0,
7860
8732
  observations: chunkData?.observations ?? activationResult.observations,
8733
+ triggeredBy: activationTriggeredBy,
8734
+ lastActivityAt: activationLastActivityAt,
8735
+ ttlExpiredMs: activateAfterIdleExpiredMs,
8736
+ previousModel,
8737
+ currentModel,
7861
8738
  config: this.getObservationMarkerConfig()
7862
8739
  });
7863
- void opts.writer.custom(activationMarker).catch(() => {
8740
+ void opts.writer.custom({ ...activationMarker, transient: true }).catch(() => {
7864
8741
  });
7865
8742
  await this.persistMarkerToMessage(
7866
8743
  activationMarker,
@@ -7913,6 +8790,7 @@ ${grouped}` : grouped;
7913
8790
  const lockKey = this.buffering.getLockKey(threadId, resourceId);
7914
8791
  const reflectionHooks = hooks ? { onReflectionStart: hooks.onReflectionStart, onReflectionEnd: hooks.onReflectionEnd } : void 0;
7915
8792
  let observed = false;
8793
+ let observationUsage;
7916
8794
  let generationBefore = -1;
7917
8795
  await this.withLock(lockKey, async () => {
7918
8796
  const freshRecord = await this.getOrCreateRecord(threadId, resourceId);
@@ -7929,8 +8807,9 @@ ${grouped}` : grouped;
7929
8807
  return;
7930
8808
  }
7931
8809
  hooks?.onObservationStart?.();
8810
+ let observationError;
7932
8811
  try {
7933
- observed = await ObservationStrategy.create(this, {
8812
+ const result = await ObservationStrategy.create(this, {
7934
8813
  record: freshRecord,
7935
8814
  threadId,
7936
8815
  resourceId,
@@ -7940,8 +8819,13 @@ ${grouped}` : grouped;
7940
8819
  writer: opts.writer,
7941
8820
  observabilityContext: opts.observabilityContext
7942
8821
  }).run();
8822
+ observed = result.observed;
8823
+ observationUsage = result.usage;
8824
+ } catch (error) {
8825
+ observationError = error instanceof Error ? error : new Error(String(error));
8826
+ throw error;
7943
8827
  } finally {
7944
- hooks?.onObservationEnd?.();
8828
+ hooks?.onObservationEnd?.({ usage: observationUsage, error: observationError });
7945
8829
  }
7946
8830
  });
7947
8831
  const record = await this.getOrCreateRecord(threadId, resourceId);
@@ -7962,12 +8846,12 @@ ${grouped}` : grouped;
7962
8846
  async reflect(threadId, resourceId, prompt, requestContext, observabilityContext) {
7963
8847
  const record = await this.getOrCreateRecord(threadId, resourceId);
7964
8848
  if (!record.activeObservations) {
7965
- return { reflected: false, record };
8849
+ return { reflected: false, record, usage: void 0 };
7966
8850
  }
7967
8851
  await this.storage.setReflectingFlag(record.id, true);
7968
8852
  registerOp(record.id, "reflecting");
7969
8853
  try {
7970
- const reflectThreshold = getMaxThreshold(this.reflectionConfig.observationTokens);
8854
+ const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
7971
8855
  const reflectResult = await this.reflector.call(
7972
8856
  record.activeObservations,
7973
8857
  prompt,
@@ -7987,11 +8871,11 @@ ${grouped}` : grouped;
7987
8871
  tokenCount: reflectionTokenCount
7988
8872
  });
7989
8873
  const updatedRecord = await this.getOrCreateRecord(threadId, resourceId);
7990
- return { reflected: true, record: updatedRecord };
8874
+ return { reflected: true, record: updatedRecord, usage: reflectResult.usage };
7991
8875
  } catch (error) {
7992
8876
  omError("[OM] reflect() failed", error);
7993
8877
  const latestRecord = await this.getOrCreateRecord(threadId, resourceId);
7994
- return { reflected: false, record: latestRecord };
8878
+ return { reflected: false, record: latestRecord, usage: void 0 };
7995
8879
  } finally {
7996
8880
  await this.storage.setReflectingFlag(record.id, false);
7997
8881
  unregisterOp(record.id, "reflecting");
@@ -8012,12 +8896,40 @@ ${grouped}` : grouped;
8012
8896
  const ids = this.getStorageIds(threadId, resourceId);
8013
8897
  return this.storage.getObservationalMemory(ids.threadId, ids.resourceId);
8014
8898
  }
8899
+ /**
8900
+ * Update per-record config overrides for observation and/or reflection thresholds.
8901
+ * The provided config is deep-merged into the record's `_overrides` key,
8902
+ * so you only need to specify the fields you want to change.
8903
+ *
8904
+ * Overrides that violate buffering invariants (e.g. messageTokens below
8905
+ * bufferTokens) are silently ignored at read time — the helpers fall back
8906
+ * to the instance-level config.
8907
+ *
8908
+ * @example
8909
+ * ```ts
8910
+ * await om.updateRecordConfig('thread-1', undefined, {
8911
+ * observation: { messageTokens: 2000 },
8912
+ * reflection: { observationTokens: 8000 },
8913
+ * });
8914
+ * ```
8915
+ */
8916
+ async updateRecordConfig(threadId, resourceId, config) {
8917
+ const ids = this.getStorageIds(threadId, resourceId);
8918
+ const record = await this.storage.getObservationalMemory(ids.threadId, ids.resourceId);
8919
+ if (!record) {
8920
+ throw new Error(`No observational memory record found for thread ${ids.threadId}`);
8921
+ }
8922
+ await this.storage.updateObservationalMemoryConfig({
8923
+ id: record.id,
8924
+ config: { _overrides: config }
8925
+ });
8926
+ }
8015
8927
  /**
8016
8928
  * Get observation history (previous generations)
8017
8929
  */
8018
- async getHistory(threadId, resourceId, limit) {
8930
+ async getHistory(threadId, resourceId, limit, options) {
8019
8931
  const ids = this.getStorageIds(threadId, resourceId);
8020
- return this.storage.getObservationalMemoryHistory(ids.threadId, ids.resourceId, limit);
8932
+ return this.storage.getObservationalMemoryHistory(ids.threadId, ids.resourceId, limit, options);
8021
8933
  }
8022
8934
  /**
8023
8935
  * Clear all memory for a specific thread/resource
@@ -8060,293 +8972,137 @@ ${grouped}` : grouped;
8060
8972
  /**
8061
8973
  * Get whether thread IDs should be obscured in observations.
8062
8974
  */
8063
- getObscureThreadIds() {
8064
- return this.shouldObscureThreadIds;
8065
- }
8066
- /**
8067
- * Begin a new observation turn — the high-level API for managing the
8068
- * observe/buffer/activate/reflect lifecycle across agentic loop steps.
8069
- *
8070
- * @example
8071
- * ```ts
8072
- * const turn = om.beginTurn({ threadId, resourceId, messageList });
8073
- * await turn.start(memory);
8074
- *
8075
- * const step0 = turn.step(0);
8076
- * const ctx = await step0.prepare();
8077
- * // ... agent generates ...
8078
- *
8079
- * await turn.end();
8080
- * ```
8081
- */
8082
- beginTurn(opts) {
8083
- return new ObservationTurn({
8084
- om: this,
8085
- threadId: opts.threadId,
8086
- resourceId: opts.resourceId,
8087
- messageList: opts.messageList,
8088
- observabilityContext: opts.observabilityContext,
8089
- hooks: opts.hooks
8090
- });
8091
- }
8092
- };
8093
- var OM_REPRO_CAPTURE_DIR = process.env.OM_REPRO_CAPTURE_DIR ?? ".mastra-om-repro";
8094
- function sanitizeCapturePathSegment(value) {
8095
- const sanitized = value.replace(/[\\/]+/g, "_").replace(/\.{2,}/g, "_").trim();
8096
- return sanitized.length > 0 ? sanitized : "unknown-thread";
8097
- }
8098
- function isOmReproCaptureEnabled() {
8099
- return process.env.OM_REPRO_CAPTURE === "1";
8100
- }
8101
- function safeCaptureJson(value) {
8102
- return JSON.parse(
8103
- JSON.stringify(value, (_key, current) => {
8104
- if (typeof current === "bigint") return current.toString();
8105
- if (typeof current === "function") return "[function]";
8106
- if (typeof current === "symbol") return current.toString();
8107
- if (current instanceof Error) return { name: current.name, message: current.message, stack: current.stack };
8108
- if (current instanceof Set) return { __type: "Set", values: Array.from(current.values()) };
8109
- if (current instanceof Map) return { __type: "Map", entries: Array.from(current.entries()) };
8110
- return current;
8111
- })
8112
- );
8113
- }
8114
- function safeCaptureJsonOrError(value) {
8115
- try {
8116
- return { ok: true, value: safeCaptureJson(value) };
8117
- } catch (error) {
8118
- return {
8119
- ok: false,
8120
- error: safeCaptureJson({
8121
- message: error instanceof Error ? error.message : String(error),
8122
- stack: error instanceof Error ? error.stack : void 0,
8123
- inspected: inspect(value, { depth: 3, maxArrayLength: 20, breakLength: 120 })
8124
- })
8125
- };
8126
- }
8127
- }
8128
- function formatCaptureDate(value) {
8129
- if (!value) return void 0;
8130
- if (value instanceof Date) return value.toISOString();
8131
- try {
8132
- return new Date(value).toISOString();
8133
- } catch {
8134
- return void 0;
8975
+ getObscureThreadIds() {
8976
+ return this.shouldObscureThreadIds;
8135
8977
  }
8136
- }
8137
- function summarizeOmTurn(value) {
8138
- if (!value || typeof value !== "object") {
8139
- return value;
8978
+ /**
8979
+ * Begin a new observation turn — the high-level API for managing the
8980
+ * observe/buffer/activate/reflect lifecycle across agentic loop steps.
8981
+ *
8982
+ * @example
8983
+ * ```ts
8984
+ * const turn = om.beginTurn({ threadId, resourceId, messageList });
8985
+ * await turn.start(memory);
8986
+ *
8987
+ * const step0 = turn.step(0);
8988
+ * const ctx = await step0.prepare();
8989
+ * // ... agent generates ...
8990
+ *
8991
+ * await turn.end();
8992
+ * ```
8993
+ */
8994
+ beginTurn(opts) {
8995
+ return new ObservationTurn({
8996
+ om: this,
8997
+ threadId: opts.threadId,
8998
+ resourceId: opts.resourceId,
8999
+ messageList: opts.messageList,
9000
+ observabilityContext: opts.observabilityContext,
9001
+ hooks: opts.hooks
9002
+ });
8140
9003
  }
8141
- const turn = value;
9004
+ };
9005
+
9006
+ // src/processors/observational-memory/temporal-markers.ts
9007
+ var TEMPORAL_GAP_REMINDER_TYPE = "temporal-gap";
9008
+ function getTemporalGapReminderText(gapText, timestamp) {
9009
+ return `${gapText} \u2014 ${formatTemporalTimestamp(new Date(timestamp))}`;
9010
+ }
9011
+ function getTemporalGapReminderMetadata(message, gapText, gapMs, timestamp) {
9012
+ const formattedTimestamp = formatTemporalTimestamp(new Date(timestamp));
8142
9013
  return {
8143
- __type: "ObservationTurn",
8144
- threadId: turn.threadId,
8145
- resourceId: turn.resourceId,
8146
- started: turn._started,
8147
- ended: turn._ended,
8148
- generationCountAtStart: turn._generationCountAtStart,
8149
- record: turn._record ? {
8150
- id: turn._record.id,
8151
- scope: turn._record.scope,
8152
- threadId: turn._record.threadId,
8153
- resourceId: turn._record.resourceId,
8154
- createdAt: formatCaptureDate(turn._record.createdAt),
8155
- updatedAt: formatCaptureDate(turn._record.updatedAt),
8156
- lastObservedAt: formatCaptureDate(turn._record.lastObservedAt),
8157
- generationCount: turn._record.generationCount,
8158
- observationTokenCount: turn._record.observationTokenCount,
8159
- pendingMessageTokens: turn._record.pendingMessageTokens,
8160
- isBufferingObservation: turn._record.isBufferingObservation,
8161
- isBufferingReflection: turn._record.isBufferingReflection
8162
- } : void 0,
8163
- context: turn._context ? {
8164
- messageCount: Array.isArray(turn._context.messages) ? turn._context.messages.length : void 0,
8165
- hasSystemMessage: Array.isArray(turn._context.systemMessage) ? turn._context.systemMessage.length > 0 : Boolean(turn._context.systemMessage),
8166
- continuationId: turn._context.continuation?.id,
8167
- hasOtherThreadsContext: Boolean(turn._context.otherThreadsContext),
8168
- recordId: turn._context.record?.id
8169
- } : void 0,
8170
- currentStep: turn._currentStep ? {
8171
- stepNumber: turn._currentStep.stepNumber,
8172
- prepared: turn._currentStep._prepared,
8173
- context: turn._currentStep._context ? {
8174
- activated: turn._currentStep._context.activated,
8175
- observed: turn._currentStep._context.observed,
8176
- buffered: turn._currentStep._context.buffered,
8177
- reflected: turn._currentStep._context.reflected,
8178
- didThresholdCleanup: turn._currentStep._context.didThresholdCleanup,
8179
- messageCount: Array.isArray(turn._currentStep._context.messages) ? turn._currentStep._context.messages.length : void 0,
8180
- systemMessageCount: Array.isArray(turn._currentStep._context.systemMessage) ? turn._currentStep._context.systemMessage.length : void 0
8181
- } : void 0
8182
- } : void 0
9014
+ reminderType: TEMPORAL_GAP_REMINDER_TYPE,
9015
+ gapText,
9016
+ gapMs,
9017
+ timestamp: formattedTimestamp,
9018
+ timestampMs: timestamp,
9019
+ precedesMessageId: message.id,
9020
+ systemReminder: {
9021
+ type: TEMPORAL_GAP_REMINDER_TYPE,
9022
+ message: getTemporalGapReminderText(gapText, timestamp),
9023
+ gapText,
9024
+ gapMs,
9025
+ timestamp: formattedTimestamp,
9026
+ timestampMs: timestamp,
9027
+ precedesMessageId: message.id
9028
+ }
8183
9029
  };
8184
9030
  }
8185
- function sanitizeCaptureState(rawState) {
8186
- return Object.fromEntries(
8187
- Object.entries(rawState).map(([key, value]) => {
8188
- if (key === "__omTurn") {
8189
- return [key, summarizeOmTurn(value)];
8190
- }
8191
- return [key, value];
8192
- })
8193
- );
9031
+ function isTemporalGapMarkerForMessage(message, targetMessageId) {
9032
+ if (!isTemporalGapMarker(message)) {
9033
+ return false;
9034
+ }
9035
+ const metadata = message.content.metadata;
9036
+ if (metadata?.precedesMessageId === targetMessageId) {
9037
+ return true;
9038
+ }
9039
+ return metadata?.systemReminder?.type === TEMPORAL_GAP_REMINDER_TYPE && metadata.systemReminder.precedesMessageId === targetMessageId;
8194
9040
  }
8195
- function buildReproMessageFingerprint(message) {
8196
- const createdAt = message.createdAt instanceof Date ? message.createdAt.toISOString() : message.createdAt ? new Date(message.createdAt).toISOString() : "";
8197
- return JSON.stringify({
8198
- role: message.role,
8199
- createdAt,
8200
- content: message.content
8201
- });
9041
+ function createTemporalGapMarker(message, gapText, gapMs, timestamp) {
9042
+ const metadata = getTemporalGapReminderMetadata(message, gapText, gapMs, timestamp);
9043
+ return {
9044
+ id: `__temporal_gap_${crypto.randomUUID()}`,
9045
+ role: "user",
9046
+ createdAt: new Date(timestamp - 1),
9047
+ threadId: message.threadId,
9048
+ resourceId: message.resourceId,
9049
+ content: {
9050
+ format: 2,
9051
+ parts: [
9052
+ {
9053
+ type: "text",
9054
+ text: `<system-reminder type="${TEMPORAL_GAP_REMINDER_TYPE}" precedesMessageId="${message.id}">${getTemporalGapReminderText(gapText, timestamp)}</system-reminder>`
9055
+ }
9056
+ ],
9057
+ metadata
9058
+ }
9059
+ };
8202
9060
  }
8203
- function inferReproIdRemap(preMessages, postMessages) {
8204
- const preByFingerprint = /* @__PURE__ */ new Map();
8205
- const postByFingerprint = /* @__PURE__ */ new Map();
8206
- for (const message of preMessages) {
8207
- if (!message.id) continue;
8208
- const fingerprint = buildReproMessageFingerprint(message);
8209
- const list = preByFingerprint.get(fingerprint) ?? [];
8210
- list.push(message.id);
8211
- preByFingerprint.set(fingerprint, list);
9061
+ async function insertTemporalGapMarkers({
9062
+ messageList,
9063
+ writer
9064
+ }) {
9065
+ const inputMessages = messageList.get.input.db().filter((message) => Boolean(message));
9066
+ const latestInputMessage = inputMessages.at(-1);
9067
+ if (!latestInputMessage || isTemporalGapMarker(latestInputMessage)) {
9068
+ return;
8212
9069
  }
8213
- for (const message of postMessages) {
8214
- if (!message.id) continue;
8215
- const fingerprint = buildReproMessageFingerprint(message);
8216
- const list = postByFingerprint.get(fingerprint) ?? [];
8217
- list.push(message.id);
8218
- postByFingerprint.set(fingerprint, list);
9070
+ const allMessages = messageList.get.all.db().filter((message) => Boolean(message));
9071
+ const latestInputIndex = allMessages.findIndex((message) => message.id === latestInputMessage.id);
9072
+ if (latestInputIndex <= 0) {
9073
+ return;
8219
9074
  }
8220
- const remap = [];
8221
- for (const [fingerprint, preIds] of preByFingerprint.entries()) {
8222
- const postIds = postByFingerprint.get(fingerprint);
8223
- if (!postIds || preIds.length !== 1 || postIds.length !== 1) continue;
8224
- const fromId = preIds[0];
8225
- const toId = postIds[0];
8226
- if (!fromId || !toId || fromId === toId) {
8227
- continue;
9075
+ if (allMessages.some((message) => isTemporalGapMarkerForMessage(message, latestInputMessage.id))) {
9076
+ return;
9077
+ }
9078
+ let previousNonMarker;
9079
+ for (let index = latestInputIndex - 1; index >= 0; index--) {
9080
+ const candidate = allMessages[index];
9081
+ if (candidate && !isTemporalGapMarker(candidate)) {
9082
+ previousNonMarker = candidate;
9083
+ break;
8228
9084
  }
8229
- remap.push({ fromId, toId, fingerprint });
8230
9085
  }
8231
- return remap;
8232
- }
8233
- function writeProcessInputStepReproCapture(params) {
8234
- if (!isOmReproCaptureEnabled()) {
9086
+ if (!previousNonMarker) {
8235
9087
  return;
8236
9088
  }
8237
- try {
8238
- const sanitizedThreadId = sanitizeCapturePathSegment(params.threadId);
8239
- const runId = `${Date.now()}-step-${params.stepNumber}-${randomUUID()}`;
8240
- const captureDir = join(process.cwd(), OM_REPRO_CAPTURE_DIR, sanitizedThreadId, runId);
8241
- mkdirSync(captureDir, { recursive: true });
8242
- const contextMessages = params.messageList.get.all.db();
8243
- const memoryContext = parseMemoryRequestContext(params.args.requestContext);
8244
- const preMessageIds = new Set(params.preMessages.map((message) => message.id));
8245
- const postMessageIds = new Set(contextMessages.map((message) => message.id));
8246
- const removedMessageIds = params.preMessages.map((message) => message.id).filter((id) => Boolean(id) && !postMessageIds.has(id));
8247
- const addedMessageIds = contextMessages.map((message) => message.id).filter((id) => Boolean(id) && !preMessageIds.has(id));
8248
- const idRemap = inferReproIdRemap(params.preMessages, contextMessages);
8249
- const rawState = params.args.state ?? {};
8250
- const sanitizedState = sanitizeCaptureState(rawState);
8251
- const payloads = [
8252
- {
8253
- fileName: "input.json",
8254
- data: {
8255
- stepNumber: params.stepNumber,
8256
- threadId: params.threadId,
8257
- resourceId: params.resourceId,
8258
- readOnly: memoryContext?.memoryConfig?.readOnly,
8259
- messageCount: contextMessages.length,
8260
- messageIds: contextMessages.map((message) => message.id),
8261
- stateKeys: Object.keys(rawState),
8262
- state: sanitizedState,
8263
- args: {
8264
- messages: params.args.messages,
8265
- steps: params.args.steps,
8266
- systemMessages: params.args.systemMessages,
8267
- retryCount: params.args.retryCount,
8268
- toolChoice: params.args.toolChoice,
8269
- activeTools: params.args.activeTools,
8270
- modelSettings: params.args.modelSettings,
8271
- structuredOutput: params.args.structuredOutput
8272
- }
8273
- }
8274
- },
8275
- {
8276
- fileName: "pre-state.json",
8277
- data: {
8278
- record: params.preRecord,
8279
- bufferedChunks: params.preBufferedChunks,
8280
- contextTokenCount: params.preContextTokenCount,
8281
- messages: params.preMessages,
8282
- messageList: params.preSerializedMessageList
8283
- }
8284
- },
8285
- {
8286
- fileName: "output.json",
8287
- data: {
8288
- details: params.details,
8289
- messageDiff: {
8290
- removedMessageIds,
8291
- addedMessageIds,
8292
- idRemap
8293
- }
8294
- }
8295
- },
8296
- {
8297
- fileName: "post-state.json",
8298
- data: {
8299
- record: params.postRecord,
8300
- bufferedChunks: params.postBufferedChunks,
8301
- contextTokenCount: params.postContextTokenCount,
8302
- messageCount: contextMessages.length,
8303
- messageIds: contextMessages.map((message) => message.id),
8304
- messages: contextMessages,
8305
- messageList: params.messageList.serialize()
8306
- }
8307
- }
8308
- ];
8309
- const captureErrors = [];
8310
- for (const payload of payloads) {
8311
- const serialized = safeCaptureJsonOrError(payload.data);
8312
- if (serialized.ok) {
8313
- writeFileSync(join(captureDir, payload.fileName), `${JSON.stringify(serialized.value, null, 2)}
8314
- `);
8315
- continue;
8316
- }
8317
- captureErrors.push({ fileName: payload.fileName, error: serialized.error });
8318
- writeFileSync(
8319
- join(captureDir, payload.fileName),
8320
- `${JSON.stringify({ __captureError: serialized.error }, null, 2)}
8321
- `
8322
- );
8323
- }
8324
- if (params.observerExchange) {
8325
- const serialized = safeCaptureJsonOrError(params.observerExchange);
8326
- if (serialized.ok) {
8327
- writeFileSync(join(captureDir, "observer-exchange.json"), `${JSON.stringify(serialized.value, null, 2)}
8328
- `);
8329
- } else {
8330
- captureErrors.push({ fileName: "observer-exchange.json", error: serialized.error });
8331
- writeFileSync(
8332
- join(captureDir, "observer-exchange.json"),
8333
- `${JSON.stringify({ __captureError: serialized.error }, null, 2)}
8334
- `
8335
- );
8336
- }
8337
- }
8338
- if (captureErrors.length > 0) {
8339
- writeFileSync(join(captureDir, "capture-error.json"), `${JSON.stringify(captureErrors, null, 2)}
8340
- `);
8341
- params.debug?.(
8342
- `[OM:repro-capture] wrote processInputStep capture with ${captureErrors.length} serialization error(s) to ${captureDir}`
8343
- );
8344
- return;
8345
- }
8346
- params.debug?.(`[OM:repro-capture] wrote processInputStep capture to ${captureDir}`);
8347
- } catch (error) {
8348
- params.debug?.(`[OM:repro-capture] failed to write processInputStep capture: ${String(error)}`);
9089
+ const timestamp = getMessagePartTimestamp(latestInputMessage, "first");
9090
+ const gapMs = timestamp - getMessagePartTimestamp(previousNonMarker, "last");
9091
+ const gapText = formatTemporalGap(gapMs);
9092
+ if (!gapText) {
9093
+ return;
8349
9094
  }
9095
+ const reminderMetadata = getTemporalGapReminderMetadata(latestInputMessage, gapText, gapMs, timestamp);
9096
+ await writer?.custom({
9097
+ type: "data-system-reminder",
9098
+ data: {
9099
+ message: getTemporalGapReminderText(gapText, timestamp),
9100
+ ...reminderMetadata
9101
+ },
9102
+ transient: true
9103
+ });
9104
+ const marker = createTemporalGapMarker(latestInputMessage, gapText, gapMs, timestamp);
9105
+ messageList.add(marker, "input");
8350
9106
  }
8351
9107
 
8352
9108
  // src/processors/observational-memory/processor.ts
@@ -8361,6 +9117,10 @@ function getOmObservabilityContext(args) {
8361
9117
  metrics: args.metrics
8362
9118
  };
8363
9119
  }
9120
+ var GATEWAY_STATE_KEY = "__isGatewayModel";
9121
+ function isMastraGatewayModel(model) {
9122
+ return typeof model === "object" && model !== null && "gatewayId" in model && model.gatewayId === "mastra";
9123
+ }
8364
9124
  var ObservationalMemoryProcessor = class {
8365
9125
  id = "observational-memory";
8366
9126
  name = "Observational Memory";
@@ -8368,11 +9128,14 @@ var ObservationalMemoryProcessor = class {
8368
9128
  engine;
8369
9129
  /** Memory instance for loading context. */
8370
9130
  memory;
9131
+ /** Whether temporal-gap reminder markers should be inserted. */
9132
+ temporalMarkers;
8371
9133
  /** Active turn — created on first processInputStep, ended on processOutputResult. */
8372
9134
  turn;
8373
- constructor(engine, memory) {
9135
+ constructor(engine, memory, options) {
8374
9136
  this.engine = engine;
8375
9137
  this.memory = memory;
9138
+ this.temporalMarkers = options?.temporalMarkers ?? false;
8376
9139
  }
8377
9140
  // ─── Processor lifecycle hooks ──────────────────────────────────────────
8378
9141
  async processInputStep(args) {
@@ -8396,6 +9159,11 @@ var ObservationalMemoryProcessor = class {
8396
9159
  omDebug(`[OM:processInputStep:NO-CONTEXT] getThreadContext returned null \u2014 returning early`);
8397
9160
  return messageList;
8398
9161
  }
9162
+ if (isMastraGatewayModel(model)) {
9163
+ state[GATEWAY_STATE_KEY] = true;
9164
+ omDebug(`[OM:processInputStep:GATEWAY] gateway handles OM \u2014 skipping local processing`);
9165
+ return messageList;
9166
+ }
8399
9167
  const { threadId, resourceId } = context;
8400
9168
  const memoryContext = parseMemoryRequestContext(requestContext);
8401
9169
  const readOnly = memoryContext?.memoryConfig?.readOnly;
@@ -8409,6 +9177,15 @@ var ObservationalMemoryProcessor = class {
8409
9177
  if (readOnly) {
8410
9178
  return messageList;
8411
9179
  }
9180
+ const activeTurn = state.__omTurn ?? this.turn;
9181
+ if (activeTurn && activeTurn.messageList !== messageList) {
9182
+ await activeTurn.end().catch(() => {
9183
+ });
9184
+ if (this.turn === activeTurn) {
9185
+ this.turn = void 0;
9186
+ }
9187
+ state.__omTurn = void 0;
9188
+ }
8412
9189
  if (!this.turn || !state.__omTurn) {
8413
9190
  if (this.turn && !state.__omTurn) {
8414
9191
  await this.turn.end().catch(() => {
@@ -8420,17 +9197,26 @@ var ObservationalMemoryProcessor = class {
8420
9197
  messageList,
8421
9198
  observabilityContext: getOmObservabilityContext(args),
8422
9199
  hooks: {
8423
- onBufferChunkSealed: rotateResponseMessageId
9200
+ onBufferChunkSealed: rotateResponseMessageId,
9201
+ onSyncObservationComplete: rotateResponseMessageId
8424
9202
  }
8425
9203
  });
8426
9204
  this.turn.writer = writer;
8427
9205
  this.turn.requestContext = requestContext;
8428
9206
  await this.turn.start(this.memory);
9207
+ if (stepNumber === 0 && this.temporalMarkers) {
9208
+ await insertTemporalGapMarkers({ messageList, writer });
9209
+ }
8429
9210
  state.__omTurn = this.turn;
8430
9211
  }
9212
+ this.turn.addHooks({
9213
+ onBufferChunkSealed: rotateResponseMessageId,
9214
+ onSyncObservationComplete: rotateResponseMessageId
9215
+ });
8431
9216
  const observabilityContext = getOmObservabilityContext(args);
8432
9217
  state.__omObservabilityContext = observabilityContext;
8433
9218
  this.turn.observabilityContext = observabilityContext;
9219
+ this.turn.actorModelContext = actorModelContext;
8434
9220
  {
8435
9221
  const step = this.turn.step(stepNumber);
8436
9222
  let ctx;
@@ -8512,6 +9298,7 @@ var ObservationalMemoryProcessor = class {
8512
9298
  const state = _state ?? {};
8513
9299
  const context = this.engine.getThreadContext(requestContext, messageList);
8514
9300
  if (!context) return messageList;
9301
+ if (state[GATEWAY_STATE_KEY]) return messageList;
8515
9302
  const observabilityContext = getOmObservabilityContext(args);
8516
9303
  state.__omObservabilityContext = observabilityContext;
8517
9304
  return this.engine.getTokenCounter().runWithModelContext(state.__omActorModelContext, async () => {
@@ -8563,5 +9350,5 @@ function getObservationsAsOf(activeObservations, asOf) {
8563
9350
  }
8564
9351
 
8565
9352
  export { ModelByInputTokens, OBSERVER_SYSTEM_PROMPT, ObservationalMemory, ObservationalMemoryProcessor, TokenCounter, buildObserverPrompt, buildObserverSystemPrompt, combineObservationGroupRanges, deriveObservationGroupProvenance, extractCurrentTask, formatMessagesForObserver, formatToolResultForObserver, getObservationsAsOf, hasCurrentTaskSection, injectAnchorIds, optimizeObservationsForContext, parseAnchorId, parseObservationGroups, parseObserverOutput, reconcileObservationGroupsFromReflection, renderObservationGroupsForReflection, resolveToolResultValue, stripEphemeralAnchorIds, stripObservationGroups, truncateStringByTokens, wrapInObservationGroup };
8566
- //# sourceMappingURL=chunk-C7PARRAD.js.map
8567
- //# sourceMappingURL=chunk-C7PARRAD.js.map
9353
+ //# sourceMappingURL=chunk-Z5GWA7LB.js.map
9354
+ //# sourceMappingURL=chunk-Z5GWA7LB.js.map