@mastra/memory 1.32.2-alpha.0 → 1.33.0-alpha.2

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 (45) hide show
  1. package/dist/docs/SKILL.md +1 -1
  2. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  3. package/dist/docs/references/docs-evals-evals-with-memory.md +3 -1
  4. package/dist/docs/references/docs-memory-observational-memory.md +34 -0
  5. package/dist/docs/references/reference-memory-observational-memory.md +8 -0
  6. package/dist/index.cjs +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/processors/index.cjs +1 -1
  11. package/dist/processors/index.js +1 -1
  12. package/dist/processors/observational-memory/date-utils.d.ts +36 -15
  13. package/dist/processors/observational-memory/date-utils.d.ts.map +1 -1
  14. package/dist/processors/observational-memory/error.d.ts +12 -0
  15. package/dist/processors/observational-memory/error.d.ts.map +1 -1
  16. package/dist/processors/observational-memory/extraction-runner.d.ts.map +1 -1
  17. package/dist/processors/observational-memory/markers.d.ts +3 -0
  18. package/dist/processors/observational-memory/markers.d.ts.map +1 -1
  19. package/dist/processors/observational-memory/message-utils.d.ts.map +1 -1
  20. package/dist/processors/observational-memory/observation-groups.d.ts.map +1 -1
  21. package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
  22. package/dist/processors/observational-memory/observation-strategies/base.d.ts.map +1 -1
  23. package/dist/processors/observational-memory/observation-strategies/resource-scoped.d.ts.map +1 -1
  24. package/dist/processors/observational-memory/observation-strategies/sync.d.ts.map +1 -1
  25. package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
  26. package/dist/processors/observational-memory/observational-memory.d.ts +17 -8
  27. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  28. package/dist/processors/observational-memory/observer-agent.d.ts +2 -0
  29. package/dist/processors/observational-memory/observer-agent.d.ts.map +1 -1
  30. package/dist/processors/observational-memory/observer-runner.d.ts +3 -1
  31. package/dist/processors/observational-memory/observer-runner.d.ts.map +1 -1
  32. package/dist/processors/observational-memory/processor.d.ts +8 -0
  33. package/dist/processors/observational-memory/processor.d.ts.map +1 -1
  34. package/dist/processors/observational-memory/reflector-runner.d.ts +1 -1
  35. package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
  36. package/dist/processors/observational-memory/retry.d.ts +10 -0
  37. package/dist/processors/observational-memory/retry.d.ts.map +1 -1
  38. package/dist/processors/observational-memory/summarize.d.ts.map +1 -1
  39. package/dist/processors/observational-memory/types.d.ts +25 -1
  40. package/dist/processors/observational-memory/types.d.ts.map +1 -1
  41. package/dist/{src-B-_pkR2t.js → src-BikfoIdS.js} +747 -368
  42. package/dist/{src-B-_pkR2t.js.map → src-BikfoIdS.js.map} +1 -1
  43. package/dist/{src-CBxKXo2w.cjs → src-CeFdO1YZ.cjs} +747 -368
  44. package/dist/{src-CBxKXo2w.cjs.map → src-CeFdO1YZ.cjs.map} +1 -1
  45. package/package.json +5 -5
@@ -18718,10 +18718,227 @@ async function applyExtractorHooks(opts) {
18718
18718
  };
18719
18719
  }
18720
18720
  //#endregion
18721
- //#region src/processors/observational-memory/error.ts
18721
+ //#region src/processors/observational-memory/retry.ts
18722
+ /**
18723
+ * Retry knobs for the internal OM transport-error retry wrapper.
18724
+ * Exported as a mutable object so tests can shrink the backoff schedule
18725
+ * without changing public API.
18726
+ *
18727
+ * With the defaults the per-retry pre-jitter backoff schedule is:
18728
+ * 1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s (cap)
18729
+ * giving 8 retries / 9 total attempts and ~247s (~4 minutes) of waiting
18730
+ * before the final attempt fails. Designed to ride out short provider /
18731
+ * network blips without holding the actor turn for much longer than that.
18732
+ *
18733
+ * @internal
18734
+ */
18735
+ const RETRY_CONFIG = {
18736
+ /** Maximum number of retry *attempts* (total tries = maxRetries + 1). */
18737
+ maxRetries: 8,
18738
+ /** Initial backoff delay in milliseconds. */
18739
+ initialDelayMs: 1e3,
18740
+ /** Multiplier applied to the delay after each failed attempt. */
18741
+ backoffFactor: 2,
18742
+ /** Cap on per-attempt delay (ms). */
18743
+ maxDelayMs: 12e4,
18744
+ /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
18745
+ jitter: .2
18746
+ };
18747
+ const TRANSIENT_MESSAGE_SUBSTRINGS = [
18748
+ "terminated",
18749
+ "fetch failed",
18750
+ "econnreset",
18751
+ "econnrefused",
18752
+ "enotfound",
18753
+ "eai_again",
18754
+ "socket hang up",
18755
+ "network error",
18756
+ "request timed out",
18757
+ "request timeout",
18758
+ "connection reset",
18759
+ "connection closed"
18760
+ ];
18722
18761
  function isRecord$5(value) {
18723
18762
  return typeof value === "object" && value !== null;
18724
18763
  }
18764
+ function isAbortError$1(error) {
18765
+ if (!isRecord$5(error)) return false;
18766
+ if (error.name === "AbortError") return true;
18767
+ if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
18768
+ return false;
18769
+ }
18770
+ function hasTransientMessage(value) {
18771
+ if (!isRecord$5(value)) return false;
18772
+ const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
18773
+ if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
18774
+ if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
18775
+ return false;
18776
+ }
18777
+ function isRetryableHttpStatus(status) {
18778
+ if (status === 408 || status === 425 || status === 429) return true;
18779
+ return status >= 500 && status <= 599;
18780
+ }
18781
+ function hasRetryableHttpStatus(value) {
18782
+ if (!isRecord$5(value)) return false;
18783
+ if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
18784
+ if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
18785
+ return false;
18786
+ }
18787
+ function hasIsRetryableFlag(value) {
18788
+ if (!isRecord$5(value)) return false;
18789
+ return value.isRetryable === true;
18790
+ }
18791
+ /**
18792
+ * Returns true when a user-initiated cancellation appears anywhere in the
18793
+ * error's `cause`/`error` wrapper chain, so a wrapped abort can never be
18794
+ * mistaken for a retryable or survivable provider failure.
18795
+ *
18796
+ * @internal
18797
+ */
18798
+ function hasAbortInChain(error) {
18799
+ const seen = /* @__PURE__ */ new Set();
18800
+ function visit(candidate) {
18801
+ if (isAbortError$1(candidate)) return true;
18802
+ if (!isRecord$5(candidate)) return false;
18803
+ if (seen.has(candidate)) return false;
18804
+ seen.add(candidate);
18805
+ return visit(candidate.cause) || visit(candidate.error);
18806
+ }
18807
+ return visit(error);
18808
+ }
18809
+ /**
18810
+ * Returns true when the given error looks like a transient transport-class
18811
+ * failure that's worth retrying — undici `terminated`, `fetch failed`,
18812
+ * `UND_ERR_*` codes, AI SDK `APICallError` with `isRetryable: true`, and
18813
+ * common HTTP 408/425/429/5xx statuses. Walks the `error.cause` chain so
18814
+ * wrapper errors don't hide the real failure.
18815
+ *
18816
+ * Never retries on user-initiated aborts.
18817
+ *
18818
+ * @internal
18819
+ */
18820
+ function isTransientLLMError(error) {
18821
+ if (hasAbortInChain(error)) return false;
18822
+ const visited = /* @__PURE__ */ new WeakSet();
18823
+ function visit(candidate) {
18824
+ if (isRecord$5(candidate)) {
18825
+ if (visited.has(candidate)) return false;
18826
+ visited.add(candidate);
18827
+ }
18828
+ if (hasTransientMessage(candidate)) return true;
18829
+ if (hasRetryableHttpStatus(candidate)) return true;
18830
+ if (hasIsRetryableFlag(candidate)) return true;
18831
+ if (isRecord$5(candidate)) {
18832
+ if (visit(candidate.cause)) return true;
18833
+ if (visit(candidate.error)) return true;
18834
+ }
18835
+ return false;
18836
+ }
18837
+ return visit(error);
18838
+ }
18839
+ /**
18840
+ * Compute the backoff delay (ms) for the Nth retry (0-indexed).
18841
+ *
18842
+ * Exponential growth (`initialDelayMs * backoffFactor^attempt`) capped at
18843
+ * `maxDelayMs`, then nudged by ±`jitter` (fractional). Exported for unit
18844
+ * tests that lock the schedule against drift.
18845
+ *
18846
+ * @internal
18847
+ */
18848
+ function computeDelay(attempt) {
18849
+ const base = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt);
18850
+ const capped = Math.min(base, RETRY_CONFIG.maxDelayMs);
18851
+ if (RETRY_CONFIG.jitter <= 0) return capped;
18852
+ const jitterRange = capped * RETRY_CONFIG.jitter;
18853
+ const offset = (Math.random() * 2 - 1) * jitterRange;
18854
+ return Math.max(0, Math.round(capped + offset));
18855
+ }
18856
+ function sleep(ms, abortSignal) {
18857
+ if (ms <= 0) return Promise.resolve();
18858
+ return new Promise((resolve, reject) => {
18859
+ if (abortSignal?.aborted) {
18860
+ reject(/* @__PURE__ */ new Error("The operation was aborted."));
18861
+ return;
18862
+ }
18863
+ const timer = setTimeout(() => {
18864
+ abortSignal?.removeEventListener("abort", onAbort);
18865
+ resolve();
18866
+ }, ms);
18867
+ const onAbort = () => {
18868
+ clearTimeout(timer);
18869
+ abortSignal?.removeEventListener("abort", onAbort);
18870
+ reject(/* @__PURE__ */ new Error("The operation was aborted."));
18871
+ };
18872
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
18873
+ });
18874
+ }
18875
+ /**
18876
+ * Run `fn` with retries on transient transport-class errors.
18877
+ *
18878
+ * Non-transient errors (auth, validation, schema, etc.) are rethrown
18879
+ * immediately. User-initiated aborts are rethrown without delay.
18880
+ *
18881
+ * @internal
18882
+ */
18883
+ async function withRetry(fn, opts) {
18884
+ const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries } = opts;
18885
+ let attempt = 0;
18886
+ while (true) {
18887
+ if (abortSignal?.aborted) throw new Error("The operation was aborted.");
18888
+ try {
18889
+ return await fn();
18890
+ } catch (error) {
18891
+ if (hasAbortInChain(error) || abortSignal?.aborted) throw error;
18892
+ if (attempt >= maxRetries || !isTransientLLMError(error)) {
18893
+ if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
18894
+ throw error;
18895
+ }
18896
+ const delay = computeDelay(attempt);
18897
+ attempt++;
18898
+ omDebug(`[OM:retry:${label}] transient error on attempt ${attempt}, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`);
18899
+ await sleep(delay, abortSignal);
18900
+ }
18901
+ }
18902
+ }
18903
+ //#endregion
18904
+ //#region src/processors/observational-memory/error.ts
18905
+ const AI_API_CALL_ERROR_MARKER = Symbol.for("vercel.ai.error.AI_APICallError");
18906
+ function isRecord$4(value) {
18907
+ return typeof value === "object" && value !== null;
18908
+ }
18909
+ function isOmModelExecutionFailure(error) {
18910
+ if (hasAbortInChain(error)) return false;
18911
+ if (isTransientLLMError(error)) return true;
18912
+ const visited = /* @__PURE__ */ new Set();
18913
+ let current = error;
18914
+ while (isRecord$4(current) && !visited.has(current)) {
18915
+ visited.add(current);
18916
+ if (Object.prototype.hasOwnProperty.call(current, AI_API_CALL_ERROR_MARKER)) return Reflect.get(current, AI_API_CALL_ERROR_MARKER) === true;
18917
+ current = current.cause ?? current.error;
18918
+ }
18919
+ return false;
18920
+ }
18921
+ var OmModelExecutionError = class extends Error {
18922
+ failureKind;
18923
+ constructor(failureKind, cause) {
18924
+ super(formatOmError(cause), { cause });
18925
+ this.failureKind = failureKind;
18926
+ this.name = "OmModelExecutionError";
18927
+ }
18928
+ };
18929
+ function isOmModelExecutionError(error) {
18930
+ try {
18931
+ return error instanceof OmModelExecutionError;
18932
+ } catch {
18933
+ return false;
18934
+ }
18935
+ }
18936
+ function getOmFailureMetadata(error, failurePolicy) {
18937
+ return {
18938
+ failurePolicy,
18939
+ ...isOmModelExecutionError(error) ? { failureKind: error.failureKind } : {}
18940
+ };
18941
+ }
18725
18942
  /** Keep provider diagnostics in streamed/persisted markers, not whole API request/response objects. */
18726
18943
  function formatOmError(error) {
18727
18944
  const details = /* @__PURE__ */ new Set();
@@ -18731,7 +18948,7 @@ function formatOmError(error) {
18731
18948
  };
18732
18949
  function collectErrorDetails(value, depth) {
18733
18950
  if (depth > 5) return;
18734
- if (!isRecord$5(value)) {
18951
+ if (!isRecord$4(value)) {
18735
18952
  if (value !== void 0) add(String(value));
18736
18953
  return;
18737
18954
  }
@@ -18741,10 +18958,10 @@ function formatOmError(error) {
18741
18958
  if (typeof value.statusCode === "number") add(`HTTP ${value.statusCode}`);
18742
18959
  if (typeof value.responseBody === "string") try {
18743
18960
  const body = JSON.parse(value.responseBody);
18744
- if (isRecord$5(body)) {
18961
+ if (isRecord$4(body)) {
18745
18962
  add(body.message);
18746
18963
  add(body.detail);
18747
- if (isRecord$5(body.error)) add(body.error.message);
18964
+ if (isRecord$4(body.error)) add(body.error.message);
18748
18965
  else add(body.error);
18749
18966
  }
18750
18967
  } catch {}
@@ -18759,8 +18976,8 @@ function formatOmError(error) {
18759
18976
  }
18760
18977
  //#endregion
18761
18978
  //#region src/processors/observational-memory/extraction-runner.ts
18762
- function isAbortError$1(error, abortSignal) {
18763
- return abortSignal?.aborted === true || error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
18979
+ function isAbortError(error, abortSignal) {
18980
+ return abortSignal?.aborted === true || hasAbortInChain(error);
18764
18981
  }
18765
18982
  function shouldRetryEmptyStructuredObject(object, extractors) {
18766
18983
  return Object.keys(object).length === 0 && extractors.some((extractor) => extractor.retryStructuredExtractionOnEmptyObject);
@@ -18786,7 +19003,12 @@ If a prior value is still applicable and carry-forward is enabled, return that p
18786
19003
  ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values\n\n${priorLines.join("\n\n")}` : ""}`;
18787
19004
  const values = {};
18788
19005
  const failures = [];
18789
- const streamWithStructuredOutput = async (jsonPromptInjection) => {
19006
+ const streamWithStructuredOutput = async (jsonPromptInjection) => withRetry(() => streamOnce(jsonPromptInjection), {
19007
+ label: `om-${opts.source}-structured-extraction`,
19008
+ abortSignal: opts.abortSignal,
19009
+ maxRetries: 1
19010
+ });
19011
+ const streamOnce = async (jsonPromptInjection) => {
18790
19012
  const object = await (await opts.agent.stream(prompt, {
18791
19013
  structuredOutput: {
18792
19014
  schema,
@@ -18806,11 +19028,21 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
18806
19028
  object = await streamWithStructuredOutput();
18807
19029
  retryEmptyObject = shouldRetryEmptyStructuredObject(object, structuredExtractors);
18808
19030
  } catch (error) {
18809
- if (isAbortError$1(error, opts.abortSignal)) throw error;
19031
+ if (isAbortError(error, opts.abortSignal)) throw error;
19032
+ if (isTransientLLMError(error)) {
19033
+ const message = error instanceof Error ? error.message : String(error);
19034
+ return {
19035
+ values,
19036
+ failures: structuredExtractors.map((extractor) => ({
19037
+ slug: extractor.slug,
19038
+ error: message
19039
+ }))
19040
+ };
19041
+ }
18810
19042
  try {
18811
19043
  object = await streamWithStructuredOutput(_mastra_core_features.coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
18812
19044
  } catch (fallbackError) {
18813
- if (isAbortError$1(fallbackError, opts.abortSignal)) throw fallbackError;
19045
+ if (isAbortError(fallbackError, opts.abortSignal)) throw fallbackError;
18814
19046
  const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
18815
19047
  return {
18816
19048
  values,
@@ -18824,7 +19056,7 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
18824
19056
  if (retryEmptyObject) try {
18825
19057
  object = await streamWithStructuredOutput(_mastra_core_features.coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
18826
19058
  } catch (fallbackError) {
18827
- if (isAbortError$1(fallbackError, opts.abortSignal)) throw fallbackError;
19059
+ if (isAbortError(fallbackError, opts.abortSignal)) throw fallbackError;
18828
19060
  const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
18829
19061
  return {
18830
19062
  values,
@@ -18852,15 +19084,15 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
18852
19084
  //#endregion
18853
19085
  //#region src/system-reminders.ts
18854
19086
  const LEGACY_SYSTEM_REMINDER_METADATA_KEY = "dynamicAgentsMdReminder";
18855
- function isRecord$4(value) {
19087
+ function isRecord$3(value) {
18856
19088
  return typeof value === "object" && value !== null;
18857
19089
  }
18858
19090
  function isSystemReminderMessage(message) {
18859
- if (!isRecord$4(message.content)) return false;
19091
+ if (!isRecord$3(message.content)) return false;
18860
19092
  const metadata = message.content.metadata;
18861
- if (message.role === "signal") return isRecord$4(metadata) && isRecord$4(metadata.signal) && (metadata.signal.type === "system-reminder" || metadata.signal.type === "reactive");
19093
+ if (message.role === "signal") return isRecord$3(metadata) && isRecord$3(metadata.signal) && (metadata.signal.type === "system-reminder" || metadata.signal.type === "reactive");
18862
19094
  if (message.role !== "user") return false;
18863
- if (isRecord$4(metadata) && (isRecord$4(metadata.systemReminder) || LEGACY_SYSTEM_REMINDER_METADATA_KEY in metadata)) return true;
19095
+ if (isRecord$3(metadata) && (isRecord$3(metadata.systemReminder) || LEGACY_SYSTEM_REMINDER_METADATA_KEY in metadata)) return true;
18864
19096
  const firstTextPart = message.content.parts.find((part) => part.type === "text");
18865
19097
  return typeof firstTextPart?.text === "string" && firstTextPart.text.startsWith("<system-reminder");
18866
19098
  }
@@ -18923,12 +19155,41 @@ function stripEphemeralAnchorIds(observations) {
18923
19155
  * Date/time utility functions for Observational Memory.
18924
19156
  * Pure functions for formatting relative timestamps and annotating observations.
18925
19157
  */
18926
- /**
18927
- * Format a relative time string like "5 days ago", "2 weeks ago", "today", etc.
18928
- */
18929
- function formatRelativeTime(date, currentDate) {
18930
- const diffMs = currentDate.getTime() - date.getTime();
18931
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
19158
+ const DAY_MS = 1440 * 60 * 1e3;
19159
+ /** Day number of the calendar date a parsed date names. Parsed dates are built from local-time fields. */
19160
+ function calendarDay(date) {
19161
+ return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / DAY_MS;
19162
+ }
19163
+ const knownTimeZones = /* @__PURE__ */ new Map();
19164
+ /** `timeZone` when this runtime recognises it; otherwise undefined, which means the process time zone. */
19165
+ function resolveTimeZone(timeZone) {
19166
+ if (!timeZone) return void 0;
19167
+ let known = knownTimeZones.get(timeZone);
19168
+ if (known === void 0) {
19169
+ try {
19170
+ new Intl.DateTimeFormat("en-US", { timeZone });
19171
+ known = true;
19172
+ } catch {
19173
+ known = false;
19174
+ }
19175
+ knownTimeZones.set(timeZone, known);
19176
+ }
19177
+ return known ? timeZone : void 0;
19178
+ }
19179
+ /** Day number of the calendar date `instant` falls on in `timeZone` (the process time zone when unset). */
19180
+ function calendarDayIn(instant, timeZone) {
19181
+ const zone = resolveTimeZone(timeZone);
19182
+ if (!zone) return calendarDay(instant);
19183
+ const parts = new Intl.DateTimeFormat("en-US", {
19184
+ timeZone: zone,
19185
+ year: "numeric",
19186
+ month: "numeric",
19187
+ day: "numeric"
19188
+ }).formatToParts(instant);
19189
+ const part = (type) => Number(parts.find((p) => p.type === type)?.value);
19190
+ return Date.UTC(part("year"), part("month") - 1, part("day")) / DAY_MS;
19191
+ }
19192
+ function formatRelativeDays(diffDays) {
18932
19193
  if (diffDays < 0) {
18933
19194
  const futureDays = Math.abs(diffDays);
18934
19195
  if (futureDays === 1) return "tomorrow";
@@ -18954,8 +19215,7 @@ function formatRelativeTime(date, currentDate) {
18954
19215
  * Returns null for consecutive days (no gap marker needed).
18955
19216
  */
18956
19217
  function formatGapBetweenDates(prevDate, currDate) {
18957
- const diffMs = currDate.getTime() - prevDate.getTime();
18958
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
19218
+ const diffDays = calendarDay(currDate) - calendarDay(prevDate);
18959
19219
  if (diffDays <= 1) return null;
18960
19220
  else if (diffDays < 7) return `[${diffDays} days later]`;
18961
19221
  else if (diffDays < 14) return `[1 week later]`;
@@ -18963,45 +19223,156 @@ function formatGapBetweenDates(prevDate, currDate) {
18963
19223
  else if (diffDays < 60) return `[1 month later]`;
18964
19224
  else return `[${Math.floor(diffDays / 30)} months later]`;
18965
19225
  }
19226
+ const MONTH_INDEX = {
19227
+ jan: 0,
19228
+ feb: 1,
19229
+ mar: 2,
19230
+ apr: 3,
19231
+ may: 4,
19232
+ jun: 5,
19233
+ jul: 6,
19234
+ aug: 7,
19235
+ sep: 8,
19236
+ oct: 9,
19237
+ nov: 10,
19238
+ dec: 11
19239
+ };
19240
+ const MONTH = String.raw`(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|June?|July?|Aug(?:ust)?|Sep(?:t(?:ember)?)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\.?`;
19241
+ const DAY = String.raw`\d{1,2}(?:st|nd|rd|th)?`;
19242
+ const YEAR = String.raw`(?:19|20|21)\d{2}(?!\d)`;
19243
+ const MERIDIEM = String.raw`(?:[AaPp]\.[Mm]\.|[AaPp][Mm]\b)`;
19244
+ const TIME = String.raw`(?:,?\s+(?:at\s+)?\d{1,2}:\d{2}(?:\s*${MERIDIEM})?|\s+at\s+\d{1,2}\s*${MERIDIEM})`;
19245
+ const QUALIFIER = String.raw`(?:(?:early|mid|late)[- ](?:to[- ](?:early|mid|late)[- ])?)`;
19246
+ const RANGE_SEP = String.raw`(?:\s*[–—]\s*|\s*-\s*|\s+(?:to|through|until)\s+)`;
19247
+ /**
19248
+ * Dates written into observation text that carry their own year, longest forms first:
19249
+ * "Mar 1 - Mar 18, 2025", "May 27-28, 2023", "January 10, 2024 at 3:00 PM",
19250
+ * "June–July 2022", "Dec 2021 – June 2023", "late April 2023".
19251
+ * Bare years, dates without a year, and ISO dates (often part of model IDs, API versions,
19252
+ * paths and branch names) are deliberately not matched.
19253
+ */
19254
+ const FREE_TEXT_DATE = new RegExp([
19255
+ String.raw`\b${MONTH}\s+${DAY}(?:,?\s+${YEAR})?${RANGE_SEP}${MONTH}\s+${DAY},?\s+${YEAR}${TIME}?`,
19256
+ String.raw`\b${MONTH}\s+${DAY}${RANGE_SEP}${DAY},?\s+${YEAR}`,
19257
+ String.raw`\b${MONTH}\s+${DAY},?\s+${YEAR}${TIME}?`,
19258
+ String.raw`\b${QUALIFIER}?${MONTH}(?:\s+${YEAR})?${RANGE_SEP}${QUALIFIER}?${MONTH}\s+${YEAR}`,
19259
+ String.raw`\b${QUALIFIER}?${MONTH}\s+${YEAR}`
19260
+ ].join("|"), "g");
19261
+ const ENDPOINT = new RegExp(String.raw`^(?:(early|mid|late)\s*)?(?:(${MONTH})\s*)?(?:(\d{1,2})(?:st|nd|rd|th)?)?,?\s*(?:(${YEAR}))?$`, "i");
19262
+ function parseEndpoint(text) {
19263
+ const match = ENDPOINT.exec(text.trim());
19264
+ if (!match || !match[0]) return null;
19265
+ const [, qualifier, month, day, year] = match;
19266
+ return {
19267
+ ...qualifier ? { qualifier: qualifier.toLowerCase() } : {},
19268
+ ...month ? { month: MONTH_INDEX[month.slice(0, 3).toLowerCase()] } : {},
19269
+ ...day ? { day: Number(day) } : {},
19270
+ ...year ? { year: Number(year) } : {}
19271
+ };
19272
+ }
19273
+ const QUALIFIER_DAY = {
19274
+ early: 7,
19275
+ mid: 15,
19276
+ late: 23
19277
+ };
19278
+ const QUALIFIER_MONTH = {
19279
+ early: [1, 15],
19280
+ mid: [6, 1],
19281
+ late: [10, 15]
19282
+ };
19283
+ /** Earliest and latest local day an endpoint can mean; null when it has no year or is not a real date. */
19284
+ function endpointBounds(date) {
19285
+ const { qualifier, month, day, year } = date;
19286
+ if (year === void 0) return null;
19287
+ if (month === void 0) {
19288
+ if (day !== void 0) return null;
19289
+ if (qualifier) {
19290
+ const [m, d] = QUALIFIER_MONTH[qualifier];
19291
+ return [new Date(year, m, d), new Date(year, m, d)];
19292
+ }
19293
+ return [new Date(year, 0, 1), new Date(year, 11, 31)];
19294
+ }
19295
+ if (day !== void 0) {
19296
+ const point = new Date(year, month, day);
19297
+ if (point.getMonth() !== month) return null;
19298
+ return [point, point];
19299
+ }
19300
+ if (qualifier) {
19301
+ const point = new Date(year, month, QUALIFIER_DAY[qualifier]);
19302
+ return [point, point];
19303
+ }
19304
+ return [new Date(year, month, 1), new Date(year, month + 1, 0)];
19305
+ }
19306
+ /** Parses one alternative: a single date or a two-ended range, filling each end's missing parts from the other. */
19307
+ function parseSpanAlternative(text) {
19308
+ const ends = text.split(new RegExp(`${RANGE_SEP}(?=\\S)`)).filter(Boolean);
19309
+ if (ends.length === 0 || ends.length > 2) return null;
19310
+ const parsed = ends.map(parseEndpoint);
19311
+ if (parsed.some((end) => end === null)) return null;
19312
+ const [first, second = first] = parsed;
19313
+ const from = { ...first };
19314
+ const to = { ...second };
19315
+ const fromYearInferred = from.year === void 0;
19316
+ const toYearInferred = to.year === void 0;
19317
+ from.year ??= to.year;
19318
+ to.year ??= from.year;
19319
+ if (from.month === void 0 && (from.day !== void 0 || from.qualifier) && to.month !== void 0) from.month = to.month;
19320
+ if (to.month === void 0 && to.day !== void 0 && from.month !== void 0) to.month = from.month;
19321
+ if (from.month !== void 0 && to.month !== void 0 && from.month > to.month && from.year === to.year) {
19322
+ if (fromYearInferred) from.year -= 1;
19323
+ else if (toYearInferred) to.year += 1;
19324
+ }
19325
+ const fromBounds = endpointBounds(from);
19326
+ const toBounds = endpointBounds(to);
19327
+ if (!fromBounds || !toBounds) return null;
19328
+ const span = {
19329
+ start: fromBounds[0],
19330
+ end: toBounds[1]
19331
+ };
19332
+ return span.start <= span.end ? span : null;
19333
+ }
19334
+ function normalizeDateText(text) {
19335
+ return text.replace(/\s+/g, " ").trim().replace(/^(?:approx(?:\.|imately)?|about|around|circa|c\.|~)\s*/i, "").replace(/^(?:by|in|until|before|after)\s+/i, "").replace(/\b(?:the\s+)?end\s+of\s+/gi, "late ").replace(/\b(?:the\s+)?(?:start|beginning)\s+of\s+/gi, "early ").replace(/\b(\d{4})-(\d{2})-(\d{2})\b/g, (whole, y, m, d) => {
19336
+ const month = Object.keys(MONTH_INDEX)[Number(m) - 1];
19337
+ return month ? `${month} ${Number(d)}, ${y}` : whole;
19338
+ }).replace(new RegExp(TIME, "g"), "").replace(/\b(early|mid|late|to)-/gi, "$1 ").replace(/ {2,}/g, " ").trim();
19339
+ }
19340
+ /**
19341
+ * Parses the whole of `text` as a date or date range. Accepts "May 30, 2023",
19342
+ * "Mar 22, 2025 at 18:08", "May 27-28, 2023", "Dec 27–31, 2021", "Mar 1 - Mar 18, 2025",
19343
+ * "Aug 1, 2024 - Feb 28, 2025", "August 2024", "June–July 2022", "late April 2023",
19344
+ * "mid-to-late May 2023", "late 2023", "2035", "2021–2026", an "approx." prefix, and
19345
+ * "A or B" alternatives (their union). Returns null when no year is known.
19346
+ */
19347
+ function parseDateSpan(text) {
19348
+ const alternatives = normalizeDateText(text).split(/\s+or\s+/i).map(parseSpanAlternative);
19349
+ if (alternatives.length === 0 || alternatives.some((span) => span === null)) return null;
19350
+ const spans = alternatives;
19351
+ return {
19352
+ start: new Date(Math.min(...spans.map((span) => span.start.getTime()))),
19353
+ end: new Date(Math.max(...spans.map((span) => span.end.getTime())))
19354
+ };
19355
+ }
19356
+ const QUALIFIED_YEAR = new RegExp(String.raw`\b(?:by|in|until|before|after|(?:the\s+)?(?:early|mid|late|end\s+of|start\s+of|beginning\s+of))\s+${YEAR}`, "gi");
18966
19357
  /**
18967
- * Parses a date string like "May 30, 2023", "May 27-28, 2023", "late April 2023", etc.
18968
- * Returns the parsed Date or null if unparseable.
19358
+ * Like `parseDateSpan`, but falls back to the first dated expression inside longer text,
19359
+ * then to a year on its own ("by late 2027", "$500 in 2024"). Only for text known to be a date.
18969
19360
  */
18970
- function parseDateFromContent(dateContent) {
18971
- let targetDate = null;
18972
- const simpleDateMatch = dateContent.match(/([A-Z][a-z]+)\s+(\d{1,2}),?\s+(\d{4})/);
18973
- if (simpleDateMatch) {
18974
- const parsed = /* @__PURE__ */ new Date(`${simpleDateMatch[1]} ${simpleDateMatch[2]}, ${simpleDateMatch[3]}`);
18975
- if (!isNaN(parsed.getTime())) targetDate = parsed;
18976
- }
18977
- if (!targetDate) {
18978
- const rangeMatch = dateContent.match(/([A-Z][a-z]+)\s+(\d{1,2})-\d{1,2},?\s+(\d{4})/);
18979
- if (rangeMatch) {
18980
- const parsed = /* @__PURE__ */ new Date(`${rangeMatch[1]} ${rangeMatch[2]}, ${rangeMatch[3]}`);
18981
- if (!isNaN(parsed.getTime())) targetDate = parsed;
18982
- }
18983
- }
18984
- if (!targetDate) {
18985
- const vagueMatch = dateContent.match(/(late|early|mid)[- ]?(?:to[- ]?(?:late|early|mid)[- ]?)?([A-Z][a-z]+)\s+(\d{4})/i);
18986
- if (vagueMatch) {
18987
- const month = vagueMatch[2];
18988
- const year = vagueMatch[3];
18989
- const modifier = vagueMatch[1].toLowerCase();
18990
- let day = 15;
18991
- if (modifier === "early") day = 7;
18992
- if (modifier === "late") day = 23;
18993
- const parsed = /* @__PURE__ */ new Date(`${month} ${day}, ${year}`);
18994
- if (!isNaN(parsed.getTime())) targetDate = parsed;
18995
- }
18996
- }
18997
- if (!targetDate) {
18998
- const crossMonthMatch = dateContent.match(/([A-Z][a-z]+)\s+to\s+(?:early\s+)?([A-Z][a-z]+)\s+(\d{4})/i);
18999
- if (crossMonthMatch) {
19000
- const parsed = /* @__PURE__ */ new Date(`${crossMonthMatch[2]} 1, ${crossMonthMatch[3]}`);
19001
- if (!isNaN(parsed.getTime())) targetDate = parsed;
19002
- }
19003
- }
19004
- return targetDate;
19361
+ function findDateSpan(text) {
19362
+ const whole = parseDateSpan(text);
19363
+ if (whole) return whole;
19364
+ for (const pattern of [FREE_TEXT_DATE, QUALIFIED_YEAR]) for (const match of text.matchAll(pattern)) {
19365
+ const span = parseDateSpan(match[0]);
19366
+ if (span) return span;
19367
+ }
19368
+ return null;
19369
+ }
19370
+ function relativeSpan(span, today) {
19371
+ const start = formatRelativeDays(today - calendarDay(span.start));
19372
+ const end = formatRelativeDays(today - calendarDay(span.end));
19373
+ if (start === end) return start;
19374
+ if (!end.startsWith("in ")) return `${start} to ${end}`;
19375
+ return start.startsWith("in ") ? `${start} to ${end.slice(3)}` : `${start} to ${end.slice(3)} from now`;
19005
19376
  }
19006
19377
  /**
19007
19378
  * Detects if an observation line indicates future intent (will do, plans to, looking forward to, etc.)
@@ -19021,40 +19392,59 @@ function isFutureIntentObservation(line) {
19021
19392
  }
19022
19393
  /**
19023
19394
  * Expand inline estimated dates with relative time.
19024
- * Matches patterns like "(estimated May 27-28, 2023)" or "(meaning May 30, 2023)"
19025
- * and expands them to "(meaning May 30, 2023 - which was 3 weeks ago)"
19395
+ * Matches patterns like "(estimated May 27-28, 2023)" or "(meaning May 30, 2023 at 3:00 PM)"
19396
+ * and expands them to "(meaning May 30, 2023 - 3 weeks ago)". Notes without a year are left alone.
19026
19397
  */
19027
- function expandInlineEstimatedDates(observations, currentDate) {
19028
- return observations.replace(/\((estimated|meaning)\s+([^)]+\d{4})\)/gi, (match, prefix, dateContent, offset) => {
19029
- const targetDate = parseDateFromContent(dateContent);
19030
- if (targetDate) {
19031
- const relative = formatRelativeTime(targetDate, currentDate);
19032
- const lineStart = observations.lastIndexOf("\n", offset) + 1;
19033
- const lineBeforeDate = observations.slice(lineStart, offset);
19034
- const isPastDate = targetDate < currentDate;
19035
- const isFutureIntent = isFutureIntentObservation(lineBeforeDate);
19036
- if (isPastDate && isFutureIntent) return `(${prefix} ${dateContent} - ${relative}, likely already happened)`;
19037
- return `(${prefix} ${dateContent} - ${relative})`;
19038
- }
19039
- return match;
19398
+ function expandInlineEstimatedDates(observations, currentDate, timeZone) {
19399
+ const inlineDateRegex = /\((estimated|meaning)\s([^()]*)\)/gi;
19400
+ const today = calendarDayIn(currentDate, timeZone);
19401
+ return observations.replace(inlineDateRegex, (match, prefix, noteText, offset) => {
19402
+ const dateContent = noteText.trimStart();
19403
+ const span = findDateSpan(dateContent);
19404
+ if (!span) return match;
19405
+ const relative = relativeSpan(span, today);
19406
+ const lineStart = observations.lastIndexOf("\n", offset) + 1;
19407
+ const lineBeforeDate = observations.slice(lineStart, offset);
19408
+ if (calendarDay(span.end) < today && isFutureIntentObservation(lineBeforeDate)) return `(${prefix} ${dateContent} - ${relative}, likely already happened)`;
19409
+ return `(${prefix} ${dateContent} - ${relative})`;
19410
+ });
19411
+ }
19412
+ /**
19413
+ * Annotates dates written into observation text, e.g. "exam on January 10, 2024" becomes
19414
+ * "exam on January 10, 2024 (5 months ago)". Only dates that state their year are annotated;
19415
+ * "Date:" headers and "(meaning/estimated …)" notes are handled separately and skipped here.
19416
+ */
19417
+ function annotateObservationTextDates(observations, currentDate, timeZone) {
19418
+ const regex = new RegExp(String.raw`^Date:.*$|\((?:[Ee]stimated|[Mm]eaning)\b[^()]*\)|${FREE_TEXT_DATE.source}`, "gm");
19419
+ const today = calendarDayIn(currentDate, timeZone);
19420
+ return observations.replace(regex, (match, offset) => {
19421
+ if (match.startsWith("Date:") || match.startsWith("(")) return match;
19422
+ const span = parseDateSpan(match);
19423
+ if (!span) return match;
19424
+ const relative = relativeSpan(span, today);
19425
+ return observations[offset + match.length] === ")" ? `${match} - ${relative}` : `${match} (${relative})`;
19040
19426
  });
19041
19427
  }
19042
19428
  /**
19043
19429
  * Add relative time annotations to observations.
19044
- * Transforms "Date: May 15, 2023" headers to "Date: May 15, 2023 (5 days ago)"
19045
- * and expands inline estimated dates with relative time context.
19430
+ * Transforms "Date: May 15, 2023" headers to "Date: May 15, 2023 (5 days ago)", range headers such as
19431
+ * "Date: Aug 1, 2024 - Feb 28, 2025" to "(7 months ago to 4 weeks ago)", and annotates inline dates.
19432
+ *
19433
+ * `timeZone` is the zone the Observer wrote its dates in (the record's `observedTimezone`). "Today" is
19434
+ * `currentDate`'s calendar date in that zone, so the result doesn't depend on the zone of the process rendering it.
19046
19435
  */
19047
- function addRelativeTimeToObservations(observations, currentDate) {
19048
- const withInlineDates = expandInlineEstimatedDates(observations, currentDate);
19049
- const dateHeaderRegex = /^(Date:\s*)([A-Z][a-z]+ \d{1,2}, \d{4})$/gm;
19436
+ function addRelativeTimeToObservations(observations, currentDate, timeZone) {
19437
+ const today = calendarDayIn(currentDate, timeZone);
19438
+ const withInlineDates = annotateObservationTextDates(expandInlineEstimatedDates(observations, currentDate, timeZone), currentDate, timeZone);
19439
+ const dateHeaderRegex = /^(Date:[ \t]*)(.*)$/gm;
19050
19440
  const dates = [];
19051
19441
  let regexMatch;
19052
19442
  while ((regexMatch = dateHeaderRegex.exec(withInlineDates)) !== null) {
19053
- const dateStr = regexMatch[2];
19054
- const parsed = new Date(dateStr);
19055
- if (!isNaN(parsed.getTime())) dates.push({
19443
+ const dateStr = regexMatch[2].trimEnd();
19444
+ const span = parseDateSpan(dateStr);
19445
+ if (span) dates.push({
19056
19446
  index: regexMatch.index,
19057
- date: parsed,
19447
+ span,
19058
19448
  match: regexMatch[0],
19059
19449
  prefix: regexMatch[1],
19060
19450
  dateStr
@@ -19068,11 +19458,10 @@ function addRelativeTimeToObservations(observations, currentDate) {
19068
19458
  const prev = i > 0 ? dates[i - 1] : null;
19069
19459
  result += withInlineDates.slice(lastIndex, curr.index);
19070
19460
  if (prev) {
19071
- const gap = formatGapBetweenDates(prev.date, curr.date);
19461
+ const gap = formatGapBetweenDates(prev.span.end, curr.span.start);
19072
19462
  if (gap) result += `\n${gap}\n\n`;
19073
19463
  }
19074
- const relative = formatRelativeTime(curr.date, currentDate);
19075
- result += `${curr.prefix}${curr.dateStr} (${relative})`;
19464
+ result += `${curr.prefix}${curr.dateStr} (${relativeSpan(curr.span, today)})`;
19076
19465
  lastIndex = curr.index + curr.match.length;
19077
19466
  }
19078
19467
  result += withInlineDates.slice(lastIndex);
@@ -20087,11 +20476,20 @@ const OBSERVER_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
20087
20476
  "heif",
20088
20477
  "avif"
20089
20478
  ]);
20090
- function formatObserverDate(createdAt) {
20091
- return createdAt ? `${createdAt.toLocaleDateString("en-US", { month: "short" })} ${createdAt.getDate()} ${createdAt.getFullYear()}` : "";
20479
+ function formatObserverDate(createdAt, timeZone) {
20480
+ if (!createdAt) return "";
20481
+ const parts = new Intl.DateTimeFormat("en-US", {
20482
+ timeZone,
20483
+ year: "numeric",
20484
+ month: "short",
20485
+ day: "numeric"
20486
+ }).formatToParts(createdAt);
20487
+ const part = (type) => parts.find((p) => p.type === type)?.value;
20488
+ return `${part("month")} ${part("day")} ${part("year")}`;
20092
20489
  }
20093
- function formatObserverTime(createdAt) {
20490
+ function formatObserverTime(createdAt, timeZone) {
20094
20491
  return createdAt ? createdAt.toLocaleTimeString("en-US", {
20492
+ timeZone,
20095
20493
  hour: "numeric",
20096
20494
  minute: "2-digit",
20097
20495
  hour12: true
@@ -20181,11 +20579,11 @@ function formatObserverAttachmentPlaceholder(part, counter) {
20181
20579
  const label = resolveObserverAttachmentLabel(part);
20182
20580
  return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
20183
20581
  }
20184
- function isRecord$3(value) {
20582
+ function isRecord$2(value) {
20185
20583
  return !!value && typeof value === "object";
20186
20584
  }
20187
20585
  function mapToolResultBlockToAttachment(block) {
20188
- if (!isRecord$3(block) || typeof block.type !== "string") return;
20586
+ if (!isRecord$2(block) || typeof block.type !== "string") return;
20189
20587
  const mediaType = typeof block.mediaType === "string" ? block.mediaType : void 0;
20190
20588
  const filename = typeof block.filename === "string" ? block.filename : void 0;
20191
20589
  switch (block.type) {
@@ -20246,7 +20644,7 @@ function mapToolResultBlockToAttachment(block) {
20246
20644
  }
20247
20645
  }
20248
20646
  function extractToolResultAttachments(result, counter, attachmentFilter) {
20249
- if (!isRecord$3(result) || result.type !== "content" || !Array.isArray(result.value)) return {
20647
+ if (!isRecord$2(result) || result.type !== "content" || !Array.isArray(result.value)) return {
20250
20648
  resultWithoutAttachments: result,
20251
20649
  attachments: []
20252
20650
  };
@@ -20260,7 +20658,7 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
20260
20658
  if (shouldIncludeObserverAttachment(attachment, attachmentFilter)) attachments.push(toObserverInputAttachmentPart(attachment));
20261
20659
  const placeholder = formatObserverAttachmentPlaceholder(attachment, counter);
20262
20660
  return {
20263
- type: isRecord$3(block) ? block.type : void 0,
20661
+ type: isRecord$2(block) ? block.type : void 0,
20264
20662
  placeholder
20265
20663
  };
20266
20664
  });
@@ -20377,6 +20775,7 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
20377
20775
  const maxLen = options?.maxPartLength;
20378
20776
  const maxToolResultTokens = options?.maxToolResultTokens ?? 5e3;
20379
20777
  const attachmentFilter = options?.attachmentFilter;
20778
+ const timeZone = resolveTimeZone(options?.timeZone);
20380
20779
  const role = getObserverMessageLabel(msg);
20381
20780
  const attachments = [];
20382
20781
  const messageCreatedAt = normalizeObserverCreatedAt(msg.createdAt);
@@ -20386,8 +20785,8 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
20386
20785
  if (!body) return;
20387
20786
  const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
20388
20787
  lines.push({
20389
- date: formatObserverDate(normalizedCreatedAt),
20390
- time: formatObserverTime(normalizedCreatedAt),
20788
+ date: formatObserverDate(normalizedCreatedAt, timeZone),
20789
+ time: formatObserverTime(normalizedCreatedAt, timeZone),
20391
20790
  title,
20392
20791
  body
20393
20792
  });
@@ -20941,171 +21340,6 @@ function optimizeObservationsForContext(observations) {
20941
21340
  return optimized.trim();
20942
21341
  }
20943
21342
  //#endregion
20944
- //#region src/processors/observational-memory/retry.ts
20945
- /**
20946
- * Retry knobs for the internal OM transport-error retry wrapper.
20947
- * Exported as a mutable object so tests can shrink the backoff schedule
20948
- * without changing public API.
20949
- *
20950
- * With the defaults the per-retry pre-jitter backoff schedule is:
20951
- * 1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s (cap)
20952
- * giving 8 retries / 9 total attempts and ~247s (~4 minutes) of waiting
20953
- * before the final attempt fails. Designed to ride out short provider /
20954
- * network blips without holding the actor turn for much longer than that.
20955
- *
20956
- * @internal
20957
- */
20958
- const RETRY_CONFIG = {
20959
- /** Maximum number of retry *attempts* (total tries = maxRetries + 1). */
20960
- maxRetries: 8,
20961
- /** Initial backoff delay in milliseconds. */
20962
- initialDelayMs: 1e3,
20963
- /** Multiplier applied to the delay after each failed attempt. */
20964
- backoffFactor: 2,
20965
- /** Cap on per-attempt delay (ms). */
20966
- maxDelayMs: 12e4,
20967
- /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
20968
- jitter: .2
20969
- };
20970
- const TRANSIENT_MESSAGE_SUBSTRINGS = [
20971
- "terminated",
20972
- "fetch failed",
20973
- "econnreset",
20974
- "econnrefused",
20975
- "enotfound",
20976
- "eai_again",
20977
- "socket hang up",
20978
- "network error",
20979
- "request timed out",
20980
- "request timeout",
20981
- "connection reset",
20982
- "connection closed"
20983
- ];
20984
- function isRecord$2(value) {
20985
- return typeof value === "object" && value !== null;
20986
- }
20987
- function isAbortError(error) {
20988
- if (!isRecord$2(error)) return false;
20989
- if (error.name === "AbortError") return true;
20990
- if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
20991
- return false;
20992
- }
20993
- function hasTransientMessage(value) {
20994
- if (!isRecord$2(value)) return false;
20995
- const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
20996
- if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
20997
- if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
20998
- return false;
20999
- }
21000
- function isRetryableHttpStatus(status) {
21001
- if (status === 408 || status === 425 || status === 429) return true;
21002
- return status >= 500 && status <= 599;
21003
- }
21004
- function hasRetryableHttpStatus(value) {
21005
- if (!isRecord$2(value)) return false;
21006
- if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
21007
- if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
21008
- return false;
21009
- }
21010
- function hasIsRetryableFlag(value) {
21011
- if (!isRecord$2(value)) return false;
21012
- return value.isRetryable === true;
21013
- }
21014
- /**
21015
- * Returns true when the given error looks like a transient transport-class
21016
- * failure that's worth retrying — undici `terminated`, `fetch failed`,
21017
- * `UND_ERR_*` codes, AI SDK `APICallError` with `isRetryable: true`, and
21018
- * common HTTP 408/425/429/5xx statuses. Walks the `error.cause` chain so
21019
- * wrapper errors don't hide the real failure.
21020
- *
21021
- * Never retries on user-initiated aborts.
21022
- *
21023
- * @internal
21024
- */
21025
- function isTransientLLMError(error) {
21026
- if (isAbortError(error)) return false;
21027
- const visited = /* @__PURE__ */ new WeakSet();
21028
- function visit(candidate) {
21029
- if (isRecord$2(candidate)) {
21030
- if (visited.has(candidate)) return false;
21031
- visited.add(candidate);
21032
- }
21033
- if (hasTransientMessage(candidate)) return true;
21034
- if (hasRetryableHttpStatus(candidate)) return true;
21035
- if (hasIsRetryableFlag(candidate)) return true;
21036
- if (isRecord$2(candidate)) {
21037
- if (visit(candidate.cause)) return true;
21038
- if (visit(candidate.error)) return true;
21039
- }
21040
- return false;
21041
- }
21042
- return visit(error);
21043
- }
21044
- /**
21045
- * Compute the backoff delay (ms) for the Nth retry (0-indexed).
21046
- *
21047
- * Exponential growth (`initialDelayMs * backoffFactor^attempt`) capped at
21048
- * `maxDelayMs`, then nudged by ±`jitter` (fractional). Exported for unit
21049
- * tests that lock the schedule against drift.
21050
- *
21051
- * @internal
21052
- */
21053
- function computeDelay(attempt) {
21054
- const base = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt);
21055
- const capped = Math.min(base, RETRY_CONFIG.maxDelayMs);
21056
- if (RETRY_CONFIG.jitter <= 0) return capped;
21057
- const jitterRange = capped * RETRY_CONFIG.jitter;
21058
- const offset = (Math.random() * 2 - 1) * jitterRange;
21059
- return Math.max(0, Math.round(capped + offset));
21060
- }
21061
- function sleep(ms, abortSignal) {
21062
- if (ms <= 0) return Promise.resolve();
21063
- return new Promise((resolve, reject) => {
21064
- if (abortSignal?.aborted) {
21065
- reject(/* @__PURE__ */ new Error("The operation was aborted."));
21066
- return;
21067
- }
21068
- const timer = setTimeout(() => {
21069
- abortSignal?.removeEventListener("abort", onAbort);
21070
- resolve();
21071
- }, ms);
21072
- const onAbort = () => {
21073
- clearTimeout(timer);
21074
- abortSignal?.removeEventListener("abort", onAbort);
21075
- reject(/* @__PURE__ */ new Error("The operation was aborted."));
21076
- };
21077
- abortSignal?.addEventListener("abort", onAbort, { once: true });
21078
- });
21079
- }
21080
- /**
21081
- * Run `fn` with retries on transient transport-class errors.
21082
- *
21083
- * Non-transient errors (auth, validation, schema, etc.) are rethrown
21084
- * immediately. User-initiated aborts are rethrown without delay.
21085
- *
21086
- * @internal
21087
- */
21088
- async function withRetry(fn, opts) {
21089
- const { label, abortSignal } = opts;
21090
- let attempt = 0;
21091
- while (true) {
21092
- if (abortSignal?.aborted) throw new Error("The operation was aborted.");
21093
- try {
21094
- return await fn();
21095
- } catch (error) {
21096
- if (isAbortError(error) || abortSignal?.aborted) throw error;
21097
- if (attempt >= RETRY_CONFIG.maxRetries || !isTransientLLMError(error)) {
21098
- if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
21099
- throw error;
21100
- }
21101
- const delay = computeDelay(attempt);
21102
- attempt++;
21103
- omDebug(`[OM:retry:${label}] transient error on attempt ${attempt}, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`);
21104
- await sleep(delay, abortSignal);
21105
- }
21106
- }
21107
- }
21108
- //#endregion
21109
21343
  //#region src/processors/observational-memory/temporary-memory.ts
21110
21344
  function createTemporaryOmMemoryContext(prefix) {
21111
21345
  const options = {
@@ -21247,11 +21481,27 @@ var ObserverRunner = class {
21247
21481
  this.mastra = mastra;
21248
21482
  }
21249
21483
  createAgent(model, isMultiThread = false, memory, extractors = this.observationConfig.extractors ?? []) {
21484
+ let agentModel = model;
21485
+ if (Array.isArray(agentModel)) agentModel = agentModel.map((fallback) => ({
21486
+ ...fallback,
21487
+ maxRetries: 0
21488
+ }));
21489
+ else if (typeof agentModel === "function") {
21490
+ const resolveDynamicModel = agentModel;
21491
+ agentModel = (async (args) => {
21492
+ const resolvedModel = await resolveDynamicModel(args);
21493
+ return Array.isArray(resolvedModel) ? resolvedModel.map((fallback) => ({
21494
+ ...fallback,
21495
+ maxRetries: 0
21496
+ })) : resolvedModel;
21497
+ });
21498
+ }
21250
21499
  return new _mastra_core_agent.Agent({
21251
21500
  id: isMultiThread ? "multi-thread-observer" : "observational-memory-observer",
21252
21501
  name: isMultiThread ? "multi-thread-observer" : "Observer",
21502
+ maxRetries: 0,
21253
21503
  instructions: buildObserverSystemPrompt(isMultiThread, this.observationConfig.instruction, this.observationConfig.threadTitle, extractors),
21254
- model,
21504
+ model: agentModel,
21255
21505
  ...memory ? { memory } : {},
21256
21506
  ...this.mastra ? { mastra: this.mastra } : {}
21257
21507
  });
@@ -21343,7 +21593,10 @@ var ObserverRunner = class {
21343
21593
  ...options,
21344
21594
  includeThreadTitle: this.observationConfig.threadTitle,
21345
21595
  extractors: activeExtractors
21346
- }, { attachmentFilter })];
21596
+ }, {
21597
+ attachmentFilter,
21598
+ timeZone: options?.timeZone
21599
+ })];
21347
21600
  const doGenerate = async () => {
21348
21601
  return withRetry(() => withOmTracingSpan({
21349
21602
  phase: "observer",
@@ -21381,12 +21634,14 @@ var ObserverRunner = class {
21381
21634
  hasRequestContext: Boolean(internalRequestContext),
21382
21635
  aborted: abortSignal?.aborted ?? false
21383
21636
  });
21384
- throw error;
21637
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
21638
+ throw new OmModelExecutionError("observer-model", error);
21385
21639
  }
21386
21640
  }, abortSignal)
21387
21641
  }), {
21388
21642
  label: "observer",
21389
- abortSignal
21643
+ abortSignal,
21644
+ maxRetries: this.observationConfig.maxRetries
21390
21645
  });
21391
21646
  };
21392
21647
  let result = await doGenerate();
@@ -21454,7 +21709,7 @@ var ObserverRunner = class {
21454
21709
  /**
21455
21710
  * Call the Observer agent for multiple threads in a single batched request.
21456
21711
  */
21457
- async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext) {
21712
+ async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone) {
21458
21713
  const contextFor = (threadId) => ({
21459
21714
  threadId,
21460
21715
  ...hookContext
@@ -21465,7 +21720,7 @@ var ObserverRunner = class {
21465
21720
  if (filtered.length > 0) messagesByThread.set(threadId, filtered);
21466
21721
  }
21467
21722
  const threadOrder = allThreadOrder.filter((threadId) => messagesByThread.has(threadId));
21468
- const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model);
21723
+ const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone);
21469
21724
  for (const threadId of allThreadOrder) {
21470
21725
  const threadResult = output.results.get(threadId);
21471
21726
  if (!threadResult) {
@@ -21476,7 +21731,7 @@ var ObserverRunner = class {
21476
21731
  }
21477
21732
  return output;
21478
21733
  }
21479
- async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model) {
21734
+ async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone) {
21480
21735
  if (threadOrder.length === 0) return { results: /* @__PURE__ */ new Map() };
21481
21736
  const inputTokens = Array.from(messagesByThread.values()).reduce((total, messages) => total + this.tokenCounter.countMessages(messages), 0);
21482
21737
  const resolvedModel = (() => {
@@ -21518,7 +21773,8 @@ var ObserverRunner = class {
21518
21773
  priorSuggestedResponse: priorMetadataByThread?.get(threadId)?.suggestedResponse,
21519
21774
  priorThreadTitle: priorMetadataByThread?.get(threadId)?.threadTitle,
21520
21775
  priorExtractedValues: priorMetadataByThread?.get(threadId)?.extracted,
21521
- model: resolvedModel.model
21776
+ model: resolvedModel.model,
21777
+ timeZone
21522
21778
  });
21523
21779
  results.set(threadId, {
21524
21780
  observations: threadResult.observations,
@@ -21535,6 +21791,7 @@ var ObserverRunner = class {
21535
21791
  totalUsage.totalTokens += threadResult.usage.totalTokens ?? 0;
21536
21792
  }
21537
21793
  }
21794
+ for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
21538
21795
  return {
21539
21796
  results,
21540
21797
  usage: totalUsage
@@ -21543,8 +21800,10 @@ var ObserverRunner = class {
21543
21800
  const agent = this.createAgent(resolvedModel.model, true, void 0, activeExtractors);
21544
21801
  const internalRequestContext = withOmInternalThreadId(requestContext, agent.id);
21545
21802
  const multiThreadAttachmentFilter = this.resolveAttachmentFilter(resolvedModel.model, requestContext);
21546
- const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, void 0, this.observationConfig.threadTitle, activeExtractors, { attachmentFilter: multiThreadAttachmentFilter })];
21547
- for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
21803
+ const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, void 0, this.observationConfig.threadTitle, activeExtractors, {
21804
+ attachmentFilter: multiThreadAttachmentFilter,
21805
+ timeZone
21806
+ })];
21548
21807
  const doGenerate = async () => {
21549
21808
  return withRetry(() => withOmTracingSpan({
21550
21809
  phase: "observer-multi-thread",
@@ -21582,12 +21841,14 @@ var ObserverRunner = class {
21582
21841
  hasRequestContext: Boolean(internalRequestContext),
21583
21842
  aborted: abortSignal?.aborted ?? false
21584
21843
  });
21585
- throw error;
21844
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
21845
+ throw new OmModelExecutionError("observer-model", error);
21586
21846
  }
21587
21847
  }, abortSignal)
21588
21848
  }), {
21589
21849
  label: "observer-multi-thread",
21590
- abortSignal
21850
+ abortSignal,
21851
+ maxRetries: this.observationConfig.maxRetries
21591
21852
  });
21592
21853
  };
21593
21854
  let result = await doGenerate();
@@ -21643,6 +21904,7 @@ var ObserverRunner = class {
21643
21904
  });
21644
21905
  }
21645
21906
  for (const threadId of threadOrder) if (!results.has(threadId)) results.set(threadId, { observations: "" });
21907
+ for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
21646
21908
  const usage = result.totalUsage ?? result.usage;
21647
21909
  return {
21648
21910
  results,
@@ -23136,6 +23398,8 @@ async function summarizeConversation(opts) {
23136
23398
  const runner = new ObserverRunner({
23137
23399
  observationConfig: {
23138
23400
  model: opts.model,
23401
+ maxRetries: 8,
23402
+ failurePolicy: "abort",
23139
23403
  messageTokens: OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens,
23140
23404
  shareTokenBudget: false,
23141
23405
  modelSettings: { ...OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings },
@@ -24966,6 +25230,7 @@ function createObservationFailedMarker(params) {
24966
25230
  durationMs,
24967
25231
  tokensAttempted: params.tokensAttempted,
24968
25232
  error: formatOmError(params.error),
25233
+ ...getOmFailureMetadata(params.error, params.failurePolicy ?? "abort"),
24969
25234
  recordId: params.recordId,
24970
25235
  threadId: params.threadId
24971
25236
  }
@@ -25027,6 +25292,7 @@ function createBufferingFailedMarker(params) {
25027
25292
  durationMs,
25028
25293
  tokensAttempted: params.tokensAttempted,
25029
25294
  error: formatOmError(params.error),
25295
+ ...getOmFailureMetadata(params.error, params.failurePolicy ?? "abort"),
25030
25296
  recordId: params.recordId,
25031
25297
  threadId: params.threadId
25032
25298
  }
@@ -25083,7 +25349,10 @@ function createThreadUpdateMarker(params) {
25083
25349
  function findLastCompletedObservationBoundary(message) {
25084
25350
  const parts = message.content?.parts;
25085
25351
  if (!parts || !Array.isArray(parts)) return -1;
25086
- for (let i = parts.length - 1; i >= 0; i--) if (parts[i]?.type === "data-om-observation-end") return i;
25352
+ for (let i = parts.length - 1; i >= 0; i--) {
25353
+ const part = parts[i];
25354
+ if (part?.type === "data-om-observation-end" && part.data?.operationType !== "reflection") return i;
25355
+ }
25087
25356
  return -1;
25088
25357
  }
25089
25358
  /**
@@ -25335,6 +25604,7 @@ function scanObservationGroupTags(observations) {
25335
25604
  const incompleteOpenings = [];
25336
25605
  let cursor = 0;
25337
25606
  let closeFloor = 0;
25607
+ let nestedFloor = -1;
25338
25608
  while (cursor < observations.length) {
25339
25609
  const start = observations.indexOf(OBSERVATION_GROUP_OPEN, cursor);
25340
25610
  if (start === -1) break;
@@ -25355,8 +25625,12 @@ function scanObservationGroupTags(observations) {
25355
25625
  cursor = openEnd + 1;
25356
25626
  continue;
25357
25627
  }
25358
- const nestedStart = observations.indexOf(OBSERVATION_GROUP_OPEN, openEnd + 1);
25359
- if (nestedStart !== -1 && nestedStart < closeStart && isLineStart(observations, nestedStart)) {
25628
+ if (nestedFloor <= openEnd) {
25629
+ const lineOpen = observations.indexOf(`\n${OBSERVATION_GROUP_OPEN}`, openEnd);
25630
+ nestedFloor = lineOpen === -1 ? observations.length : lineOpen + 1;
25631
+ }
25632
+ const nestedStart = nestedFloor;
25633
+ if (nestedStart < closeStart) {
25360
25634
  incompleteOpenings.push({
25361
25635
  start,
25362
25636
  end: openEnd + 1
@@ -25743,6 +26017,7 @@ var ObservationStrategy = class ObservationStrategy {
25743
26017
  observationTokens: processed.observationTokens,
25744
26018
  threadId,
25745
26019
  writer,
26020
+ messageList: this.opts.messageList,
25746
26021
  abortSignal,
25747
26022
  mainAgent: this.opts.agent,
25748
26023
  sendSignal: this.opts.sendSignal,
@@ -25767,6 +26042,7 @@ var ObservationStrategy = class ObservationStrategy {
25767
26042
  operationType: "observation",
25768
26043
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
25769
26044
  error: formatOmError(error),
26045
+ ...getOmFailureMetadata(error, this.observationConfig.failurePolicy),
25770
26046
  recordId: record.id,
25771
26047
  threadId
25772
26048
  }
@@ -25780,6 +26056,10 @@ var ObservationStrategy = class ObservationStrategy {
25780
26056
  };
25781
26057
  }
25782
26058
  omError("[OM] Observation failed", error);
26059
+ if (this.observationConfig.failurePolicy === "continue" && isOmModelExecutionError(error) && error.failureKind === "observer-model") return {
26060
+ observed: false,
26061
+ error
26062
+ };
25783
26063
  throw error;
25784
26064
  }
25785
26065
  }
@@ -26049,7 +26329,8 @@ var SyncObservationStrategy = class extends ObservationStrategy {
26049
26329
  threadId: this.opts.threadId,
26050
26330
  resourceId: this.opts.resourceId,
26051
26331
  trigger: this.opts.trigger,
26052
- mainAgent: this.opts.agent
26332
+ mainAgent: this.opts.agent,
26333
+ timeZone: this.opts.record.observedTimezone
26053
26334
  });
26054
26335
  const hookedValues = await applyExtractorHooks({
26055
26336
  source: "observer",
@@ -26058,7 +26339,10 @@ var SyncObservationStrategy = class extends ObservationStrategy {
26058
26339
  failures: result.extractionFailures,
26059
26340
  previousValues: this.priorExtractedValues,
26060
26341
  rawObservations: result.observations,
26061
- recentMessages: formatMessagesForObserver(this.opts.messages, { maxPartLength: 500 }),
26342
+ recentMessages: formatMessagesForObserver(this.opts.messages, {
26343
+ maxPartLength: 500,
26344
+ timeZone: this.opts.record.observedTimezone
26345
+ }),
26062
26346
  threadId: this.opts.threadId,
26063
26347
  resourceId: this.opts.resourceId,
26064
26348
  mainAgent: this.opts.agent,
@@ -26189,6 +26473,7 @@ var SyncObservationStrategy = class extends ObservationStrategy {
26189
26473
  startedAt: this.startedAt,
26190
26474
  tokensAttempted: this.tokensToObserve,
26191
26475
  error,
26476
+ failurePolicy: this.observationConfig.failurePolicy,
26192
26477
  recordId: this.opts.record.id,
26193
26478
  threadId: this.opts.threadId
26194
26479
  });
@@ -26240,7 +26525,8 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
26240
26525
  threadId: this.opts.threadId,
26241
26526
  resourceId: this.opts.resourceId,
26242
26527
  trigger: this.opts.trigger,
26243
- mainAgent: this.opts.agent
26528
+ mainAgent: this.opts.agent,
26529
+ timeZone: this.opts.record.observedTimezone
26244
26530
  });
26245
26531
  const hookedValues = await applyExtractorHooks({
26246
26532
  source: "observer",
@@ -26249,7 +26535,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
26249
26535
  failures: result.extractionFailures,
26250
26536
  previousValues: this.priorExtractedValues,
26251
26537
  rawObservations: result.observations,
26252
- recentMessages: formatMessagesForObserver(messages, { maxPartLength: 500 }),
26538
+ recentMessages: formatMessagesForObserver(messages, {
26539
+ maxPartLength: 500,
26540
+ timeZone: this.opts.record.observedTimezone
26541
+ }),
26253
26542
  threadId: this.opts.threadId,
26254
26543
  resourceId: this.opts.resourceId,
26255
26544
  mainAgent: this.opts.agent,
@@ -26395,6 +26684,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
26395
26684
  startedAt: this.startedAt,
26396
26685
  tokensAttempted,
26397
26686
  error,
26687
+ failurePolicy: this.observationConfig.failurePolicy,
26398
26688
  recordId: record.id,
26399
26689
  threadId
26400
26690
  });
@@ -26571,7 +26861,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
26571
26861
  return this.deps.observer.callMultiThread(_existingObservations, batch.threadMap, batch.threadIds, this.opts.abortSignal, this.opts.requestContext, this.priorMetadataByThread, this.opts.observabilityContext, void 0, {
26572
26862
  resourceId: this.opts.resourceId,
26573
26863
  trigger: this.opts.trigger
26574
- });
26864
+ }, this.opts.record.observedTimezone);
26575
26865
  }));
26576
26866
  for (const batchResult of batchResults) {
26577
26867
  for (const [threadId, result] of batchResult.results) this.multiThreadResults.set(threadId, result);
@@ -26604,7 +26894,10 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
26604
26894
  failures: result.extractionFailures,
26605
26895
  previousValues,
26606
26896
  rawObservations: result.observations,
26607
- recentMessages: formatMessagesForObserver(threadMessages, { maxPartLength: 500 }),
26897
+ recentMessages: formatMessagesForObserver(threadMessages, {
26898
+ maxPartLength: 500,
26899
+ timeZone: this.opts.record.observedTimezone
26900
+ }),
26608
26901
  threadId,
26609
26902
  resourceId: this.resourceId,
26610
26903
  mainAgent: this.opts.agent,
@@ -26763,6 +27056,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
26763
27056
  startedAt: this.startedAt,
26764
27057
  tokensAttempted,
26765
27058
  error,
27059
+ failurePolicy: this.observationConfig.failurePolicy,
26766
27060
  recordId: this.opts.record.id,
26767
27061
  threadId
26768
27062
  });
@@ -27177,19 +27471,28 @@ var ObservationStep = class {
27177
27471
  };
27178
27472
  }
27179
27473
  }
27180
- const obsResult = await om.observe({
27181
- threadId,
27182
- resourceId,
27183
- messages: observableMessages,
27184
- messageList,
27185
- trigger: "turn-sync",
27186
- agent: this.turn.agent,
27187
- sendSignal: this.turn.sendSignal,
27188
- sendStateSignal: this.turn.sendStateSignal,
27189
- requestContext: this.turn.requestContext,
27190
- writer: this.turn.writer,
27191
- observabilityContext: this.turn.observabilityContext
27192
- });
27474
+ let obsResult;
27475
+ try {
27476
+ obsResult = await om.observe({
27477
+ threadId,
27478
+ resourceId,
27479
+ messages: observableMessages,
27480
+ messageList,
27481
+ trigger: "turn-sync",
27482
+ agent: this.turn.agent,
27483
+ sendSignal: this.turn.sendSignal,
27484
+ sendStateSignal: this.turn.sendStateSignal,
27485
+ requestContext: this.turn.requestContext,
27486
+ writer: this.turn.writer,
27487
+ observabilityContext: this.turn.observabilityContext
27488
+ });
27489
+ } catch (error) {
27490
+ if (om.config.observation.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "observer-model") throw error;
27491
+ return {
27492
+ succeeded: false,
27493
+ record: freshStatus.record
27494
+ };
27495
+ }
27193
27496
  if (obsResult.observed) {
27194
27497
  const observedMessageIds = new Set(obsResult.record.observedMessageIds ?? []);
27195
27498
  const liveMessages = getObservableMessages(messageList);
@@ -27847,11 +28150,27 @@ var ReflectorRunner = class {
27847
28150
  this.mastra = mastra;
27848
28151
  }
27849
28152
  createAgent(model, memory, extractors = this.reflectionConfig.extractors) {
28153
+ let agentModel = model;
28154
+ if (Array.isArray(agentModel)) agentModel = agentModel.map((fallback) => ({
28155
+ ...fallback,
28156
+ maxRetries: 0
28157
+ }));
28158
+ else if (typeof agentModel === "function") {
28159
+ const resolveDynamicModel = agentModel;
28160
+ agentModel = (async (args) => {
28161
+ const resolvedModel = await resolveDynamicModel(args);
28162
+ return Array.isArray(resolvedModel) ? resolvedModel.map((fallback) => ({
28163
+ ...fallback,
28164
+ maxRetries: 0
28165
+ })) : resolvedModel;
28166
+ });
28167
+ }
27850
28168
  return new _mastra_core_agent.Agent({
27851
28169
  id: "observational-memory-reflector",
27852
28170
  name: "Reflector",
27853
28171
  instructions: buildReflectorSystemPrompt(this.reflectionConfig.instruction, extractors),
27854
- model,
28172
+ model: agentModel,
28173
+ maxRetries: 0,
27855
28174
  ...memory ? { memory } : {},
27856
28175
  ...this.mastra ? { mastra: this.mastra } : {}
27857
28176
  });
@@ -27933,36 +28252,42 @@ var ReflectorRunner = class {
27933
28252
  },
27934
28253
  callback: (childObservabilityContext) => withAbortCheck(async () => {
27935
28254
  chunkCount = 0;
27936
- return (await agent.stream(prompt, {
27937
- modelSettings: { ...this.reflectionConfig.modelSettings },
27938
- providerOptions: this.reflectionConfig.providerOptions,
27939
- ...temporaryMemory ? { memory: temporaryMemory.options } : {},
27940
- ...abortSignal ? { abortSignal } : {},
27941
- ...internalRequestContext ? { requestContext: internalRequestContext } : {},
27942
- ...childObservabilityContext,
27943
- ...attemptNumber === 1 ? {
27944
- onChunk(chunk) {
27945
- chunkCount++;
27946
- if (chunkCount === 1 || chunkCount % 50 === 0) {
27947
- const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
27948
- omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
28255
+ try {
28256
+ return await (await agent.stream(prompt, {
28257
+ modelSettings: { ...this.reflectionConfig.modelSettings },
28258
+ providerOptions: this.reflectionConfig.providerOptions,
28259
+ ...temporaryMemory ? { memory: temporaryMemory.options } : {},
28260
+ ...abortSignal ? { abortSignal } : {},
28261
+ ...internalRequestContext ? { requestContext: internalRequestContext } : {},
28262
+ ...childObservabilityContext,
28263
+ ...attemptNumber === 1 ? {
28264
+ onChunk(chunk) {
28265
+ chunkCount++;
28266
+ if (chunkCount === 1 || chunkCount % 50 === 0) {
28267
+ const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
28268
+ omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
28269
+ }
28270
+ },
28271
+ onFinish(event) {
28272
+ omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
28273
+ },
28274
+ onAbort(event) {
28275
+ omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
28276
+ },
28277
+ onError({ error }) {
28278
+ omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
27949
28279
  }
27950
- },
27951
- onFinish(event) {
27952
- omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
27953
- },
27954
- onAbort(event) {
27955
- omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
27956
- },
27957
- onError({ error }) {
27958
- omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
27959
- }
27960
- } : {}
27961
- })).getFullOutput();
28280
+ } : {}
28281
+ })).getFullOutput();
28282
+ } catch (error) {
28283
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
28284
+ throw new OmModelExecutionError("reflector-model", error);
28285
+ }
27962
28286
  }, abortSignal)
27963
28287
  }), {
27964
28288
  label: "reflector",
27965
- abortSignal
28289
+ abortSignal,
28290
+ maxRetries: this.reflectionConfig.maxRetries
27966
28291
  });
27967
28292
  omDebug(`[OM:callReflector] attempt #${attemptNumber} returned: textLen=${result.text?.length}, textPreview="${result.text?.slice(0, 120)}...", inputTokens=${result.usage?.inputTokens ?? result.totalUsage?.inputTokens}, outputTokens=${result.usage?.outputTokens ?? result.totalUsage?.outputTokens}`);
27968
28293
  const usage = result.totalUsage ?? result.usage;
@@ -28086,6 +28411,7 @@ var ReflectorRunner = class {
28086
28411
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
28087
28412
  tokensAttempted: observationTokens,
28088
28413
  error,
28414
+ failurePolicy: this.reflectionConfig.failurePolicy,
28089
28415
  recordId: record.id,
28090
28416
  threadId: record.threadId ?? ""
28091
28417
  });
@@ -28498,24 +28824,43 @@ var ReflectorRunner = class {
28498
28824
  usage: reflectResult.usage
28499
28825
  });
28500
28826
  } catch (error) {
28501
- if (writer && streamContext) {
28502
- const failedMarker = createObservationFailedMarker({
28503
- cycleId: streamContext.cycleId,
28504
- operationType: "reflection",
28505
- startedAt: streamContext.startedAt,
28506
- tokensAttempted: observationTokens,
28507
- error,
28508
- recordId: record.id,
28509
- threadId
28510
- });
28511
- await writer.custom({
28512
- ...failedMarker,
28513
- transient: true
28514
- }).catch(() => {});
28827
+ reflectionError = error instanceof Error ? error : new Error(String(error));
28828
+ const failedMarker = createObservationFailedMarker({
28829
+ cycleId: streamContext?.cycleId ?? cycleId,
28830
+ operationType: "reflection",
28831
+ startedAt: streamContext?.startedAt ?? startedAt,
28832
+ tokensAttempted: observationTokens,
28833
+ error,
28834
+ failurePolicy: this.reflectionConfig.failurePolicy,
28835
+ recordId: record.id,
28836
+ threadId
28837
+ });
28838
+ if (writer) await writer.custom({
28839
+ ...failedMarker,
28840
+ transient: true
28841
+ }).catch(() => {});
28842
+ let persistedToList = false;
28843
+ try {
28844
+ persistedToList = await this.persistMarkerToMessage(failedMarker, messageList, threadId, record.resourceId ?? void 0);
28845
+ } catch {
28846
+ persistedToList = false;
28847
+ }
28848
+ if (!persistedToList) try {
28515
28849
  await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0);
28850
+ } catch (markerError) {
28851
+ omError("[OM] Failed to persist reflection-failed marker", markerError);
28516
28852
  }
28517
- reflectionError = error instanceof Error ? error : new Error(String(error));
28518
- if (lifecycleError !== void 0 || abortSignal?.aborted) throw error;
28853
+ this.emitDebugEvent({
28854
+ type: "reflection_failed",
28855
+ timestamp: /* @__PURE__ */ new Date(),
28856
+ threadId,
28857
+ resourceId: record.resourceId ?? "",
28858
+ inputTokens: observationTokens,
28859
+ failurePolicy: this.reflectionConfig.failurePolicy,
28860
+ failureKind: failedMarker.data.failureKind,
28861
+ error: reflectionError.message
28862
+ });
28863
+ if (lifecycleError !== void 0 || abortSignal?.aborted || this.reflectionConfig.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "reflector-model") throw error;
28519
28864
  this.syncReflectionSuppression.set(lockKey, observationTokens);
28520
28865
  omError("[OM] Reflection failed", error);
28521
28866
  } finally {
@@ -29153,6 +29498,8 @@ var ObservationalMemory = class ObservationalMemory {
29153
29498
  const observationActivateAfterIdlePath = config.observation?.activateAfterIdle !== void 0 ? "observation.activateAfterIdle" : "activateAfterIdle";
29154
29499
  this.observationConfig = {
29155
29500
  model: observationModel,
29501
+ maxRetries: config.observation?.maxRetries ?? RETRY_CONFIG.maxRetries,
29502
+ failurePolicy: config.observation?.failurePolicy ?? "abort",
29156
29503
  messageTokens: isSharedBudget ? {
29157
29504
  min: messageTokens,
29158
29505
  max: totalBudget
@@ -29182,6 +29529,8 @@ var ObservationalMemory = class ObservationalMemory {
29182
29529
  };
29183
29530
  this.reflectionConfig = {
29184
29531
  model: reflectionModel,
29532
+ maxRetries: config.reflection?.maxRetries ?? RETRY_CONFIG.maxRetries,
29533
+ failurePolicy: config.reflection?.failurePolicy ?? "abort",
29185
29534
  observationTokens,
29186
29535
  shareTokenBudget: isSharedBudget,
29187
29536
  modelSettings: {
@@ -29255,9 +29604,15 @@ var ObservationalMemory = class ObservationalMemory {
29255
29604
  retrieval: this.retrieval,
29256
29605
  observation: {
29257
29606
  messageTokens: this.observationConfig.messageTokens,
29258
- previousObserverTokens: this.observationConfig.previousObserverTokens
29607
+ previousObserverTokens: this.observationConfig.previousObserverTokens,
29608
+ maxRetries: this.observationConfig.maxRetries,
29609
+ failurePolicy: this.observationConfig.failurePolicy
29259
29610
  },
29260
- reflection: { observationTokens: this.reflectionConfig.observationTokens }
29611
+ reflection: {
29612
+ observationTokens: this.reflectionConfig.observationTokens,
29613
+ maxRetries: this.reflectionConfig.maxRetries,
29614
+ failurePolicy: this.reflectionConfig.failurePolicy
29615
+ }
29261
29616
  };
29262
29617
  }
29263
29618
  /**
@@ -29361,11 +29716,15 @@ var ObservationalMemory = class ObservationalMemory {
29361
29716
  messageTokens: this.observationConfig.messageTokens,
29362
29717
  model: observationResolved.model,
29363
29718
  previousObserverTokens: this.observationConfig.previousObserverTokens,
29719
+ maxRetries: this.observationConfig.maxRetries,
29720
+ failurePolicy: this.observationConfig.failurePolicy,
29364
29721
  routing: observationResolved.routing
29365
29722
  },
29366
29723
  reflection: {
29367
29724
  observationTokens: this.reflectionConfig.observationTokens,
29368
29725
  model: reflectionResolved.model,
29726
+ maxRetries: this.reflectionConfig.maxRetries,
29727
+ failurePolicy: this.reflectionConfig.failurePolicy,
29369
29728
  routing: reflectionResolved.routing
29370
29729
  }
29371
29730
  };
@@ -29382,6 +29741,7 @@ var ObservationalMemory = class ObservationalMemory {
29382
29741
  * Ensures bufferTokens is less than the threshold and bufferActivation is valid.
29383
29742
  */
29384
29743
  validateBufferConfig() {
29744
+ for (const [path, value] of [["observation.maxRetries", this.observationConfig.maxRetries], ["reflection.maxRetries", this.reflectionConfig.maxRetries]]) if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${path} must be a finite non-negative integer, got ${value}`);
29385
29745
  if ((this.observationConfig.bufferTokens !== void 0 || this.observationConfig.bufferActivation !== void 0 || this.reflectionConfig.bufferActivation !== void 0) && this.scope === "resource") throw new Error("Async buffering is not yet supported with scope: 'resource'. Use scope: 'thread', or set observation: { bufferTokens: false } to disable async buffering.");
29386
29746
  const observationThreshold = getMaxThreshold(this.observationConfig.messageTokens);
29387
29747
  if (this.observationConfig.bufferTokens !== void 0) {
@@ -29509,18 +29869,19 @@ var ObservationalMemory = class ObservationalMemory {
29509
29869
  };
29510
29870
  }
29511
29871
  /**
29512
- * Persist a data-om-* marker part on the last assistant message in messageList
29513
- * AND save the updated message to the DB so it survives page reload.
29514
- * (data-* parts are filtered out before sending to the LLM, so they don't affect model calls.)
29872
+ * Persist a data-om-* marker part on the message that owns the operation in
29873
+ * messageList and save it to the DB. Observation markers belong to the latest
29874
+ * assistant message; reflection markers belong to the current user input.
29515
29875
  * @internal Used by ReflectorRunner. Do not call directly.
29516
29876
  */
29517
29877
  async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
29518
- if (!messageList) return;
29519
- const allMsgs = getObservableMessages(messageList);
29878
+ if (!messageList) return false;
29879
+ const allMsgs = messageList.get.all.db();
29880
+ const markerData = marker.data;
29881
+ const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
29520
29882
  for (let i = allMsgs.length - 1; i >= 0; i--) {
29521
29883
  const msg = allMsgs[i];
29522
- if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
29523
- const markerData = marker.data;
29884
+ if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
29524
29885
  if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
29525
29886
  try {
29526
29887
  await this.messageHistory.persistMessages({
@@ -29530,16 +29891,19 @@ var ObservationalMemory = class ObservationalMemory {
29530
29891
  });
29531
29892
  } catch (e) {
29532
29893
  omDebug(`[OM:persistMarker] failed to save marker to DB: ${e}`);
29894
+ return false;
29533
29895
  }
29534
- return;
29896
+ return true;
29535
29897
  }
29536
29898
  }
29899
+ return false;
29537
29900
  }
29538
29901
  /**
29539
- * Persist a marker to the last assistant message in storage.
29540
- * Unlike persistMarkerToMessage, this fetches messages directly from the DB
29541
- * so it works even when no MessageList is available (e.g. async buffering ops).
29542
- * @internal Used by observation strategies. Do not call directly.
29902
+ * Persist a marker to the message that owns the operation in storage.
29903
+ * Observation markers belong to the latest assistant message. Reflection runs
29904
+ * before the current assistant reply exists, so its failure marker belongs to
29905
+ * the latest user input that triggered it.
29906
+ * @internal Used by observation strategies and the reflector. Do not call directly.
29543
29907
  */
29544
29908
  async persistMarkerToStorage(marker, threadId, resourceId) {
29545
29909
  try {
@@ -29551,8 +29915,9 @@ var ObservationalMemory = class ObservationalMemory {
29551
29915
  direction: "DESC"
29552
29916
  }
29553
29917
  }))?.messages ?? [];
29554
- for (const msg of messages) if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
29555
- const markerData = marker.data;
29918
+ const markerData = marker.data;
29919
+ const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
29920
+ for (const msg of messages) if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
29556
29921
  if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
29557
29922
  await this.messageHistory.persistMessages({
29558
29923
  messages: [msg],
@@ -29794,9 +30159,9 @@ var ObservationalMemory = class ObservationalMemory {
29794
30159
  * In resource scope mode, filters continuity messages to only show
29795
30160
  * the message for the current thread.
29796
30161
  */
29797
- formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false) {
30162
+ formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false, timeZone) {
29798
30163
  let optimized = retrieval ? renderObservationGroupsForReflection(observations) ?? optimizeObservationsForContext(observations) : optimizeObservationsForContext(observations);
29799
- if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate);
30164
+ if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate, timeZone);
29800
30165
  const messages = [`${getObservationContextPrompt(this.scope)}\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}${retrieval ? `\n\n${getRetrievalInstructions(this.retrievalScope, this.retrievalInstructions, this.retrievalSearch)}` : ""}`];
29801
30166
  if (unobservedContextBlocks) messages.push(`The following content is from OTHER conversations different from the current conversation, they're here for reference, but they're not necessarily your focus:\nSTART_OTHER_CONVERSATIONS_BLOCK\n${unobservedContextBlocks}\nEND_OTHER_CONVERSATIONS_BLOCK`);
29802
30167
  const observationChunks = this.splitObservationContextChunks(optimized);
@@ -29896,12 +30261,15 @@ var ObservationalMemory = class ObservationalMemory {
29896
30261
  * These are injected into the Actor's context so it has awareness of activity
29897
30262
  * in other threads for the same resource.
29898
30263
  */
29899
- async formatUnobservedContextBlocks(messagesByThread, currentThreadId) {
30264
+ async formatUnobservedContextBlocks(messagesByThread, currentThreadId, timeZone) {
29900
30265
  const blocks = [];
29901
30266
  for (const [threadId, messages] of messagesByThread) {
29902
30267
  if (threadId === currentThreadId) continue;
29903
30268
  if (messages.length === 0) continue;
29904
- const formattedMessages = formatMessagesForObserver(messages, { maxPartLength: 500 });
30269
+ const formattedMessages = formatMessagesForObserver(messages, {
30270
+ maxPartLength: 500,
30271
+ timeZone
30272
+ });
29905
30273
  if (formattedMessages) {
29906
30274
  const obscuredId = await this.representThreadIDInContext(threadId);
29907
30275
  blocks.push(`<other-conversation id="${obscuredId}">
@@ -30078,7 +30446,7 @@ ${formattedMessages}
30078
30446
  transient: true
30079
30447
  }).catch(() => {});
30080
30448
  omDebug(`[OM:bufferInput] cycleId=${cycleId}, msgCount=${messagesToBuffer.length}, msgTokens=${tokensToBuffer}, ids=${messagesToBuffer.map((m) => `${m.id?.slice(0, 8)}@${m.createdAt ? new Date(m.createdAt).toISOString() : "none"}`).join(",")}`);
30081
- await this.runBufferedObservationCycle({
30449
+ if ((await this.runBufferedObservationCycle({
30082
30450
  threadId,
30083
30451
  resourceId: freshRecord.resourceId ?? void 0,
30084
30452
  trigger: "async-buffer"
@@ -30093,10 +30461,11 @@ ${formattedMessages}
30093
30461
  requestContext,
30094
30462
  observabilityContext,
30095
30463
  trigger: "async-buffer"
30096
- }).run());
30097
- const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
30098
- const cursor = new Date(maxTs.getTime() + 1);
30099
- BufferingCoordinator.lastBufferedAtTime.set(bufferKey, cursor);
30464
+ }).run()))?.observed) {
30465
+ const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
30466
+ const cursor = new Date(maxTs.getTime() + 1);
30467
+ BufferingCoordinator.lastBufferedAtTime.set(bufferKey, cursor);
30468
+ }
30100
30469
  }
30101
30470
  /**
30102
30471
  * Trigger async buffered observation if the token count has crossed a new interval.
@@ -30324,7 +30693,7 @@ ${formattedMessages}
30324
30693
  const currentTask = activeExtractors.some((extractor) => extractor.slug === "current-task") ? omMetadata?.currentTask : void 0;
30325
30694
  const suggestedResponse = activeExtractors.some((extractor) => extractor.slug === "suggested-response") ? omMetadata?.suggestedResponse : void 0;
30326
30695
  const currentDate = opts.currentDate ?? /* @__PURE__ */ new Date();
30327
- return this.formatObservationsForContext(record.activeObservations, currentTask, suggestedResponse, omMetadata?.extracted, unobservedContextBlocks, currentDate, this.retrieval);
30696
+ return this.formatObservationsForContext(record.activeObservations, currentTask, suggestedResponse, omMetadata?.extracted, unobservedContextBlocks, currentDate, this.retrieval, record.observedTimezone);
30328
30697
  }
30329
30698
  /**
30330
30699
  * Get unobserved messages from other threads for resource-scoped observation.
@@ -30336,7 +30705,8 @@ ${formattedMessages}
30336
30705
  async getOtherThreadsContext(resourceId, currentThreadId) {
30337
30706
  const { threads: allThreads } = await this.storage.listThreads({ filter: { resourceId } });
30338
30707
  const messagesByThread = /* @__PURE__ */ new Map();
30339
- const recordLastObservedAt = (await this.getRecord(currentThreadId, resourceId))?.lastObservedAt;
30708
+ const record = await this.getRecord(currentThreadId, resourceId);
30709
+ const recordLastObservedAt = record?.lastObservedAt;
30340
30710
  for (const thread of allThreads) {
30341
30711
  if (thread.id === currentThreadId) continue;
30342
30712
  const threadLastObservedAt = (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata)?.lastObservedAt ?? recordLastObservedAt;
@@ -30353,7 +30723,7 @@ ${formattedMessages}
30353
30723
  if (filtered.length > 0) messagesByThread.set(thread.id, filtered);
30354
30724
  }
30355
30725
  if (messagesByThread.size === 0) return void 0;
30356
- return await this.formatUnobservedContextBlocks(messagesByThread, currentThreadId) || void 0;
30726
+ return await this.formatUnobservedContextBlocks(messagesByThread, currentThreadId, record?.observedTimezone) || void 0;
30357
30727
  }
30358
30728
  /**
30359
30729
  * Emit debug event and stream progress for UI feedback.
@@ -30696,7 +31066,7 @@ ${formattedMessages}
30696
31066
  ...startMarker,
30697
31067
  transient: true
30698
31068
  }).catch(() => {});
30699
- await this.runBufferedObservationCycle({
31069
+ if (!(await this.runBufferedObservationCycle({
30700
31070
  threadId,
30701
31071
  resourceId: record.resourceId ?? resourceId,
30702
31072
  trigger: "async-buffer"
@@ -30715,7 +31085,10 @@ ${formattedMessages}
30715
31085
  currentModel: opts.currentModel,
30716
31086
  observabilityContext,
30717
31087
  trigger: "async-buffer"
30718
- }).run());
31088
+ }).run()))?.observed) return {
31089
+ buffered: false,
31090
+ record
31091
+ };
30719
31092
  if (isOmReproCaptureEnabled()) writeObserverExchangeReproCapture({
30720
31093
  threadId,
30721
31094
  resourceId: record.resourceId ?? void 0,
@@ -31075,6 +31448,7 @@ ${formattedMessages}
31075
31448
  observed = result.observed;
31076
31449
  observationUsage = result.usage;
31077
31450
  observationProviderMetadata = result.providerMetadata;
31451
+ observationError = result.error;
31078
31452
  });
31079
31453
  } catch (error) {
31080
31454
  lifecycleError = error;
@@ -33010,6 +33384,7 @@ ${workingMemory}`;
33010
33384
  model: omConfig.model,
33011
33385
  mastra: this._mastraInstance,
33012
33386
  onIndexObservations,
33387
+ onDebugEvent: omConfig.onDebugEvent,
33013
33388
  hooks: omConfig.hooks,
33014
33389
  observation: omConfig.observation ? {
33015
33390
  model: omConfig.observation.model,
@@ -33026,10 +33401,14 @@ ${workingMemory}`;
33026
33401
  threadTitle: omConfig.observation.threadTitle,
33027
33402
  observeAttachments: omConfig.observation.observeAttachments,
33028
33403
  continuationHints: omConfig.observation.continuationHints,
33404
+ maxRetries: omConfig.observation.maxRetries,
33405
+ failurePolicy: omConfig.observation.failurePolicy,
33029
33406
  extract: omConfig.observation.extract
33030
33407
  } : void 0,
33031
33408
  reflection: omConfig.reflection ? {
33032
33409
  model: omConfig.reflection.model,
33410
+ maxRetries: omConfig.reflection.maxRetries,
33411
+ failurePolicy: omConfig.reflection.failurePolicy,
33033
33412
  observationTokens: omConfig.reflection.observationTokens,
33034
33413
  modelSettings: omConfig.reflection.modelSettings,
33035
33414
  providerOptions: omConfig.reflection.providerOptions,
@@ -34394,4 +34773,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
34394
34773
  }
34395
34774
  });
34396
34775
 
34397
- //# sourceMappingURL=src-CBxKXo2w.cjs.map
34776
+ //# sourceMappingURL=src-CeFdO1YZ.cjs.map