@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
@@ -18696,10 +18696,227 @@ async function applyExtractorHooks(opts) {
18696
18696
  };
18697
18697
  }
18698
18698
  //#endregion
18699
- //#region src/processors/observational-memory/error.ts
18699
+ //#region src/processors/observational-memory/retry.ts
18700
+ /**
18701
+ * Retry knobs for the internal OM transport-error retry wrapper.
18702
+ * Exported as a mutable object so tests can shrink the backoff schedule
18703
+ * without changing public API.
18704
+ *
18705
+ * With the defaults the per-retry pre-jitter backoff schedule is:
18706
+ * 1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s (cap)
18707
+ * giving 8 retries / 9 total attempts and ~247s (~4 minutes) of waiting
18708
+ * before the final attempt fails. Designed to ride out short provider /
18709
+ * network blips without holding the actor turn for much longer than that.
18710
+ *
18711
+ * @internal
18712
+ */
18713
+ const RETRY_CONFIG = {
18714
+ /** Maximum number of retry *attempts* (total tries = maxRetries + 1). */
18715
+ maxRetries: 8,
18716
+ /** Initial backoff delay in milliseconds. */
18717
+ initialDelayMs: 1e3,
18718
+ /** Multiplier applied to the delay after each failed attempt. */
18719
+ backoffFactor: 2,
18720
+ /** Cap on per-attempt delay (ms). */
18721
+ maxDelayMs: 12e4,
18722
+ /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
18723
+ jitter: .2
18724
+ };
18725
+ const TRANSIENT_MESSAGE_SUBSTRINGS = [
18726
+ "terminated",
18727
+ "fetch failed",
18728
+ "econnreset",
18729
+ "econnrefused",
18730
+ "enotfound",
18731
+ "eai_again",
18732
+ "socket hang up",
18733
+ "network error",
18734
+ "request timed out",
18735
+ "request timeout",
18736
+ "connection reset",
18737
+ "connection closed"
18738
+ ];
18700
18739
  function isRecord$5(value) {
18701
18740
  return typeof value === "object" && value !== null;
18702
18741
  }
18742
+ function isAbortError$1(error) {
18743
+ if (!isRecord$5(error)) return false;
18744
+ if (error.name === "AbortError") return true;
18745
+ if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
18746
+ return false;
18747
+ }
18748
+ function hasTransientMessage(value) {
18749
+ if (!isRecord$5(value)) return false;
18750
+ const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
18751
+ if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
18752
+ if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
18753
+ return false;
18754
+ }
18755
+ function isRetryableHttpStatus(status) {
18756
+ if (status === 408 || status === 425 || status === 429) return true;
18757
+ return status >= 500 && status <= 599;
18758
+ }
18759
+ function hasRetryableHttpStatus(value) {
18760
+ if (!isRecord$5(value)) return false;
18761
+ if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
18762
+ if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
18763
+ return false;
18764
+ }
18765
+ function hasIsRetryableFlag(value) {
18766
+ if (!isRecord$5(value)) return false;
18767
+ return value.isRetryable === true;
18768
+ }
18769
+ /**
18770
+ * Returns true when a user-initiated cancellation appears anywhere in the
18771
+ * error's `cause`/`error` wrapper chain, so a wrapped abort can never be
18772
+ * mistaken for a retryable or survivable provider failure.
18773
+ *
18774
+ * @internal
18775
+ */
18776
+ function hasAbortInChain(error) {
18777
+ const seen = /* @__PURE__ */ new Set();
18778
+ function visit(candidate) {
18779
+ if (isAbortError$1(candidate)) return true;
18780
+ if (!isRecord$5(candidate)) return false;
18781
+ if (seen.has(candidate)) return false;
18782
+ seen.add(candidate);
18783
+ return visit(candidate.cause) || visit(candidate.error);
18784
+ }
18785
+ return visit(error);
18786
+ }
18787
+ /**
18788
+ * Returns true when the given error looks like a transient transport-class
18789
+ * failure that's worth retrying — undici `terminated`, `fetch failed`,
18790
+ * `UND_ERR_*` codes, AI SDK `APICallError` with `isRetryable: true`, and
18791
+ * common HTTP 408/425/429/5xx statuses. Walks the `error.cause` chain so
18792
+ * wrapper errors don't hide the real failure.
18793
+ *
18794
+ * Never retries on user-initiated aborts.
18795
+ *
18796
+ * @internal
18797
+ */
18798
+ function isTransientLLMError(error) {
18799
+ if (hasAbortInChain(error)) return false;
18800
+ const visited = /* @__PURE__ */ new WeakSet();
18801
+ function visit(candidate) {
18802
+ if (isRecord$5(candidate)) {
18803
+ if (visited.has(candidate)) return false;
18804
+ visited.add(candidate);
18805
+ }
18806
+ if (hasTransientMessage(candidate)) return true;
18807
+ if (hasRetryableHttpStatus(candidate)) return true;
18808
+ if (hasIsRetryableFlag(candidate)) return true;
18809
+ if (isRecord$5(candidate)) {
18810
+ if (visit(candidate.cause)) return true;
18811
+ if (visit(candidate.error)) return true;
18812
+ }
18813
+ return false;
18814
+ }
18815
+ return visit(error);
18816
+ }
18817
+ /**
18818
+ * Compute the backoff delay (ms) for the Nth retry (0-indexed).
18819
+ *
18820
+ * Exponential growth (`initialDelayMs * backoffFactor^attempt`) capped at
18821
+ * `maxDelayMs`, then nudged by ±`jitter` (fractional). Exported for unit
18822
+ * tests that lock the schedule against drift.
18823
+ *
18824
+ * @internal
18825
+ */
18826
+ function computeDelay(attempt) {
18827
+ const base = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt);
18828
+ const capped = Math.min(base, RETRY_CONFIG.maxDelayMs);
18829
+ if (RETRY_CONFIG.jitter <= 0) return capped;
18830
+ const jitterRange = capped * RETRY_CONFIG.jitter;
18831
+ const offset = (Math.random() * 2 - 1) * jitterRange;
18832
+ return Math.max(0, Math.round(capped + offset));
18833
+ }
18834
+ function sleep(ms, abortSignal) {
18835
+ if (ms <= 0) return Promise.resolve();
18836
+ return new Promise((resolve, reject) => {
18837
+ if (abortSignal?.aborted) {
18838
+ reject(/* @__PURE__ */ new Error("The operation was aborted."));
18839
+ return;
18840
+ }
18841
+ const timer = setTimeout(() => {
18842
+ abortSignal?.removeEventListener("abort", onAbort);
18843
+ resolve();
18844
+ }, ms);
18845
+ const onAbort = () => {
18846
+ clearTimeout(timer);
18847
+ abortSignal?.removeEventListener("abort", onAbort);
18848
+ reject(/* @__PURE__ */ new Error("The operation was aborted."));
18849
+ };
18850
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
18851
+ });
18852
+ }
18853
+ /**
18854
+ * Run `fn` with retries on transient transport-class errors.
18855
+ *
18856
+ * Non-transient errors (auth, validation, schema, etc.) are rethrown
18857
+ * immediately. User-initiated aborts are rethrown without delay.
18858
+ *
18859
+ * @internal
18860
+ */
18861
+ async function withRetry(fn, opts) {
18862
+ const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries } = opts;
18863
+ let attempt = 0;
18864
+ while (true) {
18865
+ if (abortSignal?.aborted) throw new Error("The operation was aborted.");
18866
+ try {
18867
+ return await fn();
18868
+ } catch (error) {
18869
+ if (hasAbortInChain(error) || abortSignal?.aborted) throw error;
18870
+ if (attempt >= maxRetries || !isTransientLLMError(error)) {
18871
+ if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
18872
+ throw error;
18873
+ }
18874
+ const delay = computeDelay(attempt);
18875
+ attempt++;
18876
+ omDebug(`[OM:retry:${label}] transient error on attempt ${attempt}, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`);
18877
+ await sleep(delay, abortSignal);
18878
+ }
18879
+ }
18880
+ }
18881
+ //#endregion
18882
+ //#region src/processors/observational-memory/error.ts
18883
+ const AI_API_CALL_ERROR_MARKER = Symbol.for("vercel.ai.error.AI_APICallError");
18884
+ function isRecord$4(value) {
18885
+ return typeof value === "object" && value !== null;
18886
+ }
18887
+ function isOmModelExecutionFailure(error) {
18888
+ if (hasAbortInChain(error)) return false;
18889
+ if (isTransientLLMError(error)) return true;
18890
+ const visited = /* @__PURE__ */ new Set();
18891
+ let current = error;
18892
+ while (isRecord$4(current) && !visited.has(current)) {
18893
+ visited.add(current);
18894
+ if (Object.prototype.hasOwnProperty.call(current, AI_API_CALL_ERROR_MARKER)) return Reflect.get(current, AI_API_CALL_ERROR_MARKER) === true;
18895
+ current = current.cause ?? current.error;
18896
+ }
18897
+ return false;
18898
+ }
18899
+ var OmModelExecutionError = class extends Error {
18900
+ failureKind;
18901
+ constructor(failureKind, cause) {
18902
+ super(formatOmError(cause), { cause });
18903
+ this.failureKind = failureKind;
18904
+ this.name = "OmModelExecutionError";
18905
+ }
18906
+ };
18907
+ function isOmModelExecutionError(error) {
18908
+ try {
18909
+ return error instanceof OmModelExecutionError;
18910
+ } catch {
18911
+ return false;
18912
+ }
18913
+ }
18914
+ function getOmFailureMetadata(error, failurePolicy) {
18915
+ return {
18916
+ failurePolicy,
18917
+ ...isOmModelExecutionError(error) ? { failureKind: error.failureKind } : {}
18918
+ };
18919
+ }
18703
18920
  /** Keep provider diagnostics in streamed/persisted markers, not whole API request/response objects. */
18704
18921
  function formatOmError(error) {
18705
18922
  const details = /* @__PURE__ */ new Set();
@@ -18709,7 +18926,7 @@ function formatOmError(error) {
18709
18926
  };
18710
18927
  function collectErrorDetails(value, depth) {
18711
18928
  if (depth > 5) return;
18712
- if (!isRecord$5(value)) {
18929
+ if (!isRecord$4(value)) {
18713
18930
  if (value !== void 0) add(String(value));
18714
18931
  return;
18715
18932
  }
@@ -18719,10 +18936,10 @@ function formatOmError(error) {
18719
18936
  if (typeof value.statusCode === "number") add(`HTTP ${value.statusCode}`);
18720
18937
  if (typeof value.responseBody === "string") try {
18721
18938
  const body = JSON.parse(value.responseBody);
18722
- if (isRecord$5(body)) {
18939
+ if (isRecord$4(body)) {
18723
18940
  add(body.message);
18724
18941
  add(body.detail);
18725
- if (isRecord$5(body.error)) add(body.error.message);
18942
+ if (isRecord$4(body.error)) add(body.error.message);
18726
18943
  else add(body.error);
18727
18944
  }
18728
18945
  } catch {}
@@ -18737,8 +18954,8 @@ function formatOmError(error) {
18737
18954
  }
18738
18955
  //#endregion
18739
18956
  //#region src/processors/observational-memory/extraction-runner.ts
18740
- function isAbortError$1(error, abortSignal) {
18741
- return abortSignal?.aborted === true || error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
18957
+ function isAbortError(error, abortSignal) {
18958
+ return abortSignal?.aborted === true || hasAbortInChain(error);
18742
18959
  }
18743
18960
  function shouldRetryEmptyStructuredObject(object, extractors) {
18744
18961
  return Object.keys(object).length === 0 && extractors.some((extractor) => extractor.retryStructuredExtractionOnEmptyObject);
@@ -18764,7 +18981,12 @@ If a prior value is still applicable and carry-forward is enabled, return that p
18764
18981
  ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values\n\n${priorLines.join("\n\n")}` : ""}`;
18765
18982
  const values = {};
18766
18983
  const failures = [];
18767
- const streamWithStructuredOutput = async (jsonPromptInjection) => {
18984
+ const streamWithStructuredOutput = async (jsonPromptInjection) => withRetry(() => streamOnce(jsonPromptInjection), {
18985
+ label: `om-${opts.source}-structured-extraction`,
18986
+ abortSignal: opts.abortSignal,
18987
+ maxRetries: 1
18988
+ });
18989
+ const streamOnce = async (jsonPromptInjection) => {
18768
18990
  const object = await (await opts.agent.stream(prompt, {
18769
18991
  structuredOutput: {
18770
18992
  schema,
@@ -18784,11 +19006,21 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
18784
19006
  object = await streamWithStructuredOutput();
18785
19007
  retryEmptyObject = shouldRetryEmptyStructuredObject(object, structuredExtractors);
18786
19008
  } catch (error) {
18787
- if (isAbortError$1(error, opts.abortSignal)) throw error;
19009
+ if (isAbortError(error, opts.abortSignal)) throw error;
19010
+ if (isTransientLLMError(error)) {
19011
+ const message = error instanceof Error ? error.message : String(error);
19012
+ return {
19013
+ values,
19014
+ failures: structuredExtractors.map((extractor) => ({
19015
+ slug: extractor.slug,
19016
+ error: message
19017
+ }))
19018
+ };
19019
+ }
18788
19020
  try {
18789
19021
  object = await streamWithStructuredOutput(coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
18790
19022
  } catch (fallbackError) {
18791
- if (isAbortError$1(fallbackError, opts.abortSignal)) throw fallbackError;
19023
+ if (isAbortError(fallbackError, opts.abortSignal)) throw fallbackError;
18792
19024
  const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
18793
19025
  return {
18794
19026
  values,
@@ -18802,7 +19034,7 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
18802
19034
  if (retryEmptyObject) try {
18803
19035
  object = await streamWithStructuredOutput(coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
18804
19036
  } catch (fallbackError) {
18805
- if (isAbortError$1(fallbackError, opts.abortSignal)) throw fallbackError;
19037
+ if (isAbortError(fallbackError, opts.abortSignal)) throw fallbackError;
18806
19038
  const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
18807
19039
  return {
18808
19040
  values,
@@ -18830,15 +19062,15 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
18830
19062
  //#endregion
18831
19063
  //#region src/system-reminders.ts
18832
19064
  const LEGACY_SYSTEM_REMINDER_METADATA_KEY = "dynamicAgentsMdReminder";
18833
- function isRecord$4(value) {
19065
+ function isRecord$3(value) {
18834
19066
  return typeof value === "object" && value !== null;
18835
19067
  }
18836
19068
  function isSystemReminderMessage(message) {
18837
- if (!isRecord$4(message.content)) return false;
19069
+ if (!isRecord$3(message.content)) return false;
18838
19070
  const metadata = message.content.metadata;
18839
- if (message.role === "signal") return isRecord$4(metadata) && isRecord$4(metadata.signal) && (metadata.signal.type === "system-reminder" || metadata.signal.type === "reactive");
19071
+ if (message.role === "signal") return isRecord$3(metadata) && isRecord$3(metadata.signal) && (metadata.signal.type === "system-reminder" || metadata.signal.type === "reactive");
18840
19072
  if (message.role !== "user") return false;
18841
- if (isRecord$4(metadata) && (isRecord$4(metadata.systemReminder) || LEGACY_SYSTEM_REMINDER_METADATA_KEY in metadata)) return true;
19073
+ if (isRecord$3(metadata) && (isRecord$3(metadata.systemReminder) || LEGACY_SYSTEM_REMINDER_METADATA_KEY in metadata)) return true;
18842
19074
  const firstTextPart = message.content.parts.find((part) => part.type === "text");
18843
19075
  return typeof firstTextPart?.text === "string" && firstTextPart.text.startsWith("<system-reminder");
18844
19076
  }
@@ -18901,12 +19133,41 @@ function stripEphemeralAnchorIds(observations) {
18901
19133
  * Date/time utility functions for Observational Memory.
18902
19134
  * Pure functions for formatting relative timestamps and annotating observations.
18903
19135
  */
18904
- /**
18905
- * Format a relative time string like "5 days ago", "2 weeks ago", "today", etc.
18906
- */
18907
- function formatRelativeTime(date, currentDate) {
18908
- const diffMs = currentDate.getTime() - date.getTime();
18909
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
19136
+ const DAY_MS = 1440 * 60 * 1e3;
19137
+ /** Day number of the calendar date a parsed date names. Parsed dates are built from local-time fields. */
19138
+ function calendarDay(date) {
19139
+ return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / DAY_MS;
19140
+ }
19141
+ const knownTimeZones = /* @__PURE__ */ new Map();
19142
+ /** `timeZone` when this runtime recognises it; otherwise undefined, which means the process time zone. */
19143
+ function resolveTimeZone(timeZone) {
19144
+ if (!timeZone) return void 0;
19145
+ let known = knownTimeZones.get(timeZone);
19146
+ if (known === void 0) {
19147
+ try {
19148
+ new Intl.DateTimeFormat("en-US", { timeZone });
19149
+ known = true;
19150
+ } catch {
19151
+ known = false;
19152
+ }
19153
+ knownTimeZones.set(timeZone, known);
19154
+ }
19155
+ return known ? timeZone : void 0;
19156
+ }
19157
+ /** Day number of the calendar date `instant` falls on in `timeZone` (the process time zone when unset). */
19158
+ function calendarDayIn(instant, timeZone) {
19159
+ const zone = resolveTimeZone(timeZone);
19160
+ if (!zone) return calendarDay(instant);
19161
+ const parts = new Intl.DateTimeFormat("en-US", {
19162
+ timeZone: zone,
19163
+ year: "numeric",
19164
+ month: "numeric",
19165
+ day: "numeric"
19166
+ }).formatToParts(instant);
19167
+ const part = (type) => Number(parts.find((p) => p.type === type)?.value);
19168
+ return Date.UTC(part("year"), part("month") - 1, part("day")) / DAY_MS;
19169
+ }
19170
+ function formatRelativeDays(diffDays) {
18910
19171
  if (diffDays < 0) {
18911
19172
  const futureDays = Math.abs(diffDays);
18912
19173
  if (futureDays === 1) return "tomorrow";
@@ -18932,8 +19193,7 @@ function formatRelativeTime(date, currentDate) {
18932
19193
  * Returns null for consecutive days (no gap marker needed).
18933
19194
  */
18934
19195
  function formatGapBetweenDates(prevDate, currDate) {
18935
- const diffMs = currDate.getTime() - prevDate.getTime();
18936
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
19196
+ const diffDays = calendarDay(currDate) - calendarDay(prevDate);
18937
19197
  if (diffDays <= 1) return null;
18938
19198
  else if (diffDays < 7) return `[${diffDays} days later]`;
18939
19199
  else if (diffDays < 14) return `[1 week later]`;
@@ -18941,45 +19201,156 @@ function formatGapBetweenDates(prevDate, currDate) {
18941
19201
  else if (diffDays < 60) return `[1 month later]`;
18942
19202
  else return `[${Math.floor(diffDays / 30)} months later]`;
18943
19203
  }
19204
+ const MONTH_INDEX = {
19205
+ jan: 0,
19206
+ feb: 1,
19207
+ mar: 2,
19208
+ apr: 3,
19209
+ may: 4,
19210
+ jun: 5,
19211
+ jul: 6,
19212
+ aug: 7,
19213
+ sep: 8,
19214
+ oct: 9,
19215
+ nov: 10,
19216
+ dec: 11
19217
+ };
19218
+ 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)?)\.?`;
19219
+ const DAY = String.raw`\d{1,2}(?:st|nd|rd|th)?`;
19220
+ const YEAR = String.raw`(?:19|20|21)\d{2}(?!\d)`;
19221
+ const MERIDIEM = String.raw`(?:[AaPp]\.[Mm]\.|[AaPp][Mm]\b)`;
19222
+ const TIME = String.raw`(?:,?\s+(?:at\s+)?\d{1,2}:\d{2}(?:\s*${MERIDIEM})?|\s+at\s+\d{1,2}\s*${MERIDIEM})`;
19223
+ const QUALIFIER = String.raw`(?:(?:early|mid|late)[- ](?:to[- ](?:early|mid|late)[- ])?)`;
19224
+ const RANGE_SEP = String.raw`(?:\s*[–—]\s*|\s*-\s*|\s+(?:to|through|until)\s+)`;
19225
+ /**
19226
+ * Dates written into observation text that carry their own year, longest forms first:
19227
+ * "Mar 1 - Mar 18, 2025", "May 27-28, 2023", "January 10, 2024 at 3:00 PM",
19228
+ * "June–July 2022", "Dec 2021 – June 2023", "late April 2023".
19229
+ * Bare years, dates without a year, and ISO dates (often part of model IDs, API versions,
19230
+ * paths and branch names) are deliberately not matched.
19231
+ */
19232
+ const FREE_TEXT_DATE = new RegExp([
19233
+ String.raw`\b${MONTH}\s+${DAY}(?:,?\s+${YEAR})?${RANGE_SEP}${MONTH}\s+${DAY},?\s+${YEAR}${TIME}?`,
19234
+ String.raw`\b${MONTH}\s+${DAY}${RANGE_SEP}${DAY},?\s+${YEAR}`,
19235
+ String.raw`\b${MONTH}\s+${DAY},?\s+${YEAR}${TIME}?`,
19236
+ String.raw`\b${QUALIFIER}?${MONTH}(?:\s+${YEAR})?${RANGE_SEP}${QUALIFIER}?${MONTH}\s+${YEAR}`,
19237
+ String.raw`\b${QUALIFIER}?${MONTH}\s+${YEAR}`
19238
+ ].join("|"), "g");
19239
+ const ENDPOINT = new RegExp(String.raw`^(?:(early|mid|late)\s*)?(?:(${MONTH})\s*)?(?:(\d{1,2})(?:st|nd|rd|th)?)?,?\s*(?:(${YEAR}))?$`, "i");
19240
+ function parseEndpoint(text) {
19241
+ const match = ENDPOINT.exec(text.trim());
19242
+ if (!match || !match[0]) return null;
19243
+ const [, qualifier, month, day, year] = match;
19244
+ return {
19245
+ ...qualifier ? { qualifier: qualifier.toLowerCase() } : {},
19246
+ ...month ? { month: MONTH_INDEX[month.slice(0, 3).toLowerCase()] } : {},
19247
+ ...day ? { day: Number(day) } : {},
19248
+ ...year ? { year: Number(year) } : {}
19249
+ };
19250
+ }
19251
+ const QUALIFIER_DAY = {
19252
+ early: 7,
19253
+ mid: 15,
19254
+ late: 23
19255
+ };
19256
+ const QUALIFIER_MONTH = {
19257
+ early: [1, 15],
19258
+ mid: [6, 1],
19259
+ late: [10, 15]
19260
+ };
19261
+ /** Earliest and latest local day an endpoint can mean; null when it has no year or is not a real date. */
19262
+ function endpointBounds(date) {
19263
+ const { qualifier, month, day, year } = date;
19264
+ if (year === void 0) return null;
19265
+ if (month === void 0) {
19266
+ if (day !== void 0) return null;
19267
+ if (qualifier) {
19268
+ const [m, d] = QUALIFIER_MONTH[qualifier];
19269
+ return [new Date(year, m, d), new Date(year, m, d)];
19270
+ }
19271
+ return [new Date(year, 0, 1), new Date(year, 11, 31)];
19272
+ }
19273
+ if (day !== void 0) {
19274
+ const point = new Date(year, month, day);
19275
+ if (point.getMonth() !== month) return null;
19276
+ return [point, point];
19277
+ }
19278
+ if (qualifier) {
19279
+ const point = new Date(year, month, QUALIFIER_DAY[qualifier]);
19280
+ return [point, point];
19281
+ }
19282
+ return [new Date(year, month, 1), new Date(year, month + 1, 0)];
19283
+ }
19284
+ /** Parses one alternative: a single date or a two-ended range, filling each end's missing parts from the other. */
19285
+ function parseSpanAlternative(text) {
19286
+ const ends = text.split(new RegExp(`${RANGE_SEP}(?=\\S)`)).filter(Boolean);
19287
+ if (ends.length === 0 || ends.length > 2) return null;
19288
+ const parsed = ends.map(parseEndpoint);
19289
+ if (parsed.some((end) => end === null)) return null;
19290
+ const [first, second = first] = parsed;
19291
+ const from = { ...first };
19292
+ const to = { ...second };
19293
+ const fromYearInferred = from.year === void 0;
19294
+ const toYearInferred = to.year === void 0;
19295
+ from.year ??= to.year;
19296
+ to.year ??= from.year;
19297
+ if (from.month === void 0 && (from.day !== void 0 || from.qualifier) && to.month !== void 0) from.month = to.month;
19298
+ if (to.month === void 0 && to.day !== void 0 && from.month !== void 0) to.month = from.month;
19299
+ if (from.month !== void 0 && to.month !== void 0 && from.month > to.month && from.year === to.year) {
19300
+ if (fromYearInferred) from.year -= 1;
19301
+ else if (toYearInferred) to.year += 1;
19302
+ }
19303
+ const fromBounds = endpointBounds(from);
19304
+ const toBounds = endpointBounds(to);
19305
+ if (!fromBounds || !toBounds) return null;
19306
+ const span = {
19307
+ start: fromBounds[0],
19308
+ end: toBounds[1]
19309
+ };
19310
+ return span.start <= span.end ? span : null;
19311
+ }
19312
+ function normalizeDateText(text) {
19313
+ 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) => {
19314
+ const month = Object.keys(MONTH_INDEX)[Number(m) - 1];
19315
+ return month ? `${month} ${Number(d)}, ${y}` : whole;
19316
+ }).replace(new RegExp(TIME, "g"), "").replace(/\b(early|mid|late|to)-/gi, "$1 ").replace(/ {2,}/g, " ").trim();
19317
+ }
19318
+ /**
19319
+ * Parses the whole of `text` as a date or date range. Accepts "May 30, 2023",
19320
+ * "Mar 22, 2025 at 18:08", "May 27-28, 2023", "Dec 27–31, 2021", "Mar 1 - Mar 18, 2025",
19321
+ * "Aug 1, 2024 - Feb 28, 2025", "August 2024", "June–July 2022", "late April 2023",
19322
+ * "mid-to-late May 2023", "late 2023", "2035", "2021–2026", an "approx." prefix, and
19323
+ * "A or B" alternatives (their union). Returns null when no year is known.
19324
+ */
19325
+ function parseDateSpan(text) {
19326
+ const alternatives = normalizeDateText(text).split(/\s+or\s+/i).map(parseSpanAlternative);
19327
+ if (alternatives.length === 0 || alternatives.some((span) => span === null)) return null;
19328
+ const spans = alternatives;
19329
+ return {
19330
+ start: new Date(Math.min(...spans.map((span) => span.start.getTime()))),
19331
+ end: new Date(Math.max(...spans.map((span) => span.end.getTime())))
19332
+ };
19333
+ }
19334
+ 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");
18944
19335
  /**
18945
- * Parses a date string like "May 30, 2023", "May 27-28, 2023", "late April 2023", etc.
18946
- * Returns the parsed Date or null if unparseable.
19336
+ * Like `parseDateSpan`, but falls back to the first dated expression inside longer text,
19337
+ * then to a year on its own ("by late 2027", "$500 in 2024"). Only for text known to be a date.
18947
19338
  */
18948
- function parseDateFromContent(dateContent) {
18949
- let targetDate = null;
18950
- const simpleDateMatch = dateContent.match(/([A-Z][a-z]+)\s+(\d{1,2}),?\s+(\d{4})/);
18951
- if (simpleDateMatch) {
18952
- const parsed = /* @__PURE__ */ new Date(`${simpleDateMatch[1]} ${simpleDateMatch[2]}, ${simpleDateMatch[3]}`);
18953
- if (!isNaN(parsed.getTime())) targetDate = parsed;
18954
- }
18955
- if (!targetDate) {
18956
- const rangeMatch = dateContent.match(/([A-Z][a-z]+)\s+(\d{1,2})-\d{1,2},?\s+(\d{4})/);
18957
- if (rangeMatch) {
18958
- const parsed = /* @__PURE__ */ new Date(`${rangeMatch[1]} ${rangeMatch[2]}, ${rangeMatch[3]}`);
18959
- if (!isNaN(parsed.getTime())) targetDate = parsed;
18960
- }
18961
- }
18962
- if (!targetDate) {
18963
- const vagueMatch = dateContent.match(/(late|early|mid)[- ]?(?:to[- ]?(?:late|early|mid)[- ]?)?([A-Z][a-z]+)\s+(\d{4})/i);
18964
- if (vagueMatch) {
18965
- const month = vagueMatch[2];
18966
- const year = vagueMatch[3];
18967
- const modifier = vagueMatch[1].toLowerCase();
18968
- let day = 15;
18969
- if (modifier === "early") day = 7;
18970
- if (modifier === "late") day = 23;
18971
- const parsed = /* @__PURE__ */ new Date(`${month} ${day}, ${year}`);
18972
- if (!isNaN(parsed.getTime())) targetDate = parsed;
18973
- }
18974
- }
18975
- if (!targetDate) {
18976
- const crossMonthMatch = dateContent.match(/([A-Z][a-z]+)\s+to\s+(?:early\s+)?([A-Z][a-z]+)\s+(\d{4})/i);
18977
- if (crossMonthMatch) {
18978
- const parsed = /* @__PURE__ */ new Date(`${crossMonthMatch[2]} 1, ${crossMonthMatch[3]}`);
18979
- if (!isNaN(parsed.getTime())) targetDate = parsed;
18980
- }
18981
- }
18982
- return targetDate;
19339
+ function findDateSpan(text) {
19340
+ const whole = parseDateSpan(text);
19341
+ if (whole) return whole;
19342
+ for (const pattern of [FREE_TEXT_DATE, QUALIFIED_YEAR]) for (const match of text.matchAll(pattern)) {
19343
+ const span = parseDateSpan(match[0]);
19344
+ if (span) return span;
19345
+ }
19346
+ return null;
19347
+ }
19348
+ function relativeSpan(span, today) {
19349
+ const start = formatRelativeDays(today - calendarDay(span.start));
19350
+ const end = formatRelativeDays(today - calendarDay(span.end));
19351
+ if (start === end) return start;
19352
+ if (!end.startsWith("in ")) return `${start} to ${end}`;
19353
+ return start.startsWith("in ") ? `${start} to ${end.slice(3)}` : `${start} to ${end.slice(3)} from now`;
18983
19354
  }
18984
19355
  /**
18985
19356
  * Detects if an observation line indicates future intent (will do, plans to, looking forward to, etc.)
@@ -18999,40 +19370,59 @@ function isFutureIntentObservation(line) {
18999
19370
  }
19000
19371
  /**
19001
19372
  * Expand inline estimated dates with relative time.
19002
- * Matches patterns like "(estimated May 27-28, 2023)" or "(meaning May 30, 2023)"
19003
- * and expands them to "(meaning May 30, 2023 - which was 3 weeks ago)"
19373
+ * Matches patterns like "(estimated May 27-28, 2023)" or "(meaning May 30, 2023 at 3:00 PM)"
19374
+ * and expands them to "(meaning May 30, 2023 - 3 weeks ago)". Notes without a year are left alone.
19004
19375
  */
19005
- function expandInlineEstimatedDates(observations, currentDate) {
19006
- return observations.replace(/\((estimated|meaning)\s+([^)]+\d{4})\)/gi, (match, prefix, dateContent, offset) => {
19007
- const targetDate = parseDateFromContent(dateContent);
19008
- if (targetDate) {
19009
- const relative = formatRelativeTime(targetDate, currentDate);
19010
- const lineStart = observations.lastIndexOf("\n", offset) + 1;
19011
- const lineBeforeDate = observations.slice(lineStart, offset);
19012
- const isPastDate = targetDate < currentDate;
19013
- const isFutureIntent = isFutureIntentObservation(lineBeforeDate);
19014
- if (isPastDate && isFutureIntent) return `(${prefix} ${dateContent} - ${relative}, likely already happened)`;
19015
- return `(${prefix} ${dateContent} - ${relative})`;
19016
- }
19017
- return match;
19376
+ function expandInlineEstimatedDates(observations, currentDate, timeZone) {
19377
+ const inlineDateRegex = /\((estimated|meaning)\s([^()]*)\)/gi;
19378
+ const today = calendarDayIn(currentDate, timeZone);
19379
+ return observations.replace(inlineDateRegex, (match, prefix, noteText, offset) => {
19380
+ const dateContent = noteText.trimStart();
19381
+ const span = findDateSpan(dateContent);
19382
+ if (!span) return match;
19383
+ const relative = relativeSpan(span, today);
19384
+ const lineStart = observations.lastIndexOf("\n", offset) + 1;
19385
+ const lineBeforeDate = observations.slice(lineStart, offset);
19386
+ if (calendarDay(span.end) < today && isFutureIntentObservation(lineBeforeDate)) return `(${prefix} ${dateContent} - ${relative}, likely already happened)`;
19387
+ return `(${prefix} ${dateContent} - ${relative})`;
19388
+ });
19389
+ }
19390
+ /**
19391
+ * Annotates dates written into observation text, e.g. "exam on January 10, 2024" becomes
19392
+ * "exam on January 10, 2024 (5 months ago)". Only dates that state their year are annotated;
19393
+ * "Date:" headers and "(meaning/estimated …)" notes are handled separately and skipped here.
19394
+ */
19395
+ function annotateObservationTextDates(observations, currentDate, timeZone) {
19396
+ const regex = new RegExp(String.raw`^Date:.*$|\((?:[Ee]stimated|[Mm]eaning)\b[^()]*\)|${FREE_TEXT_DATE.source}`, "gm");
19397
+ const today = calendarDayIn(currentDate, timeZone);
19398
+ return observations.replace(regex, (match, offset) => {
19399
+ if (match.startsWith("Date:") || match.startsWith("(")) return match;
19400
+ const span = parseDateSpan(match);
19401
+ if (!span) return match;
19402
+ const relative = relativeSpan(span, today);
19403
+ return observations[offset + match.length] === ")" ? `${match} - ${relative}` : `${match} (${relative})`;
19018
19404
  });
19019
19405
  }
19020
19406
  /**
19021
19407
  * Add relative time annotations to observations.
19022
- * Transforms "Date: May 15, 2023" headers to "Date: May 15, 2023 (5 days ago)"
19023
- * and expands inline estimated dates with relative time context.
19408
+ * Transforms "Date: May 15, 2023" headers to "Date: May 15, 2023 (5 days ago)", range headers such as
19409
+ * "Date: Aug 1, 2024 - Feb 28, 2025" to "(7 months ago to 4 weeks ago)", and annotates inline dates.
19410
+ *
19411
+ * `timeZone` is the zone the Observer wrote its dates in (the record's `observedTimezone`). "Today" is
19412
+ * `currentDate`'s calendar date in that zone, so the result doesn't depend on the zone of the process rendering it.
19024
19413
  */
19025
- function addRelativeTimeToObservations(observations, currentDate) {
19026
- const withInlineDates = expandInlineEstimatedDates(observations, currentDate);
19027
- const dateHeaderRegex = /^(Date:\s*)([A-Z][a-z]+ \d{1,2}, \d{4})$/gm;
19414
+ function addRelativeTimeToObservations(observations, currentDate, timeZone) {
19415
+ const today = calendarDayIn(currentDate, timeZone);
19416
+ const withInlineDates = annotateObservationTextDates(expandInlineEstimatedDates(observations, currentDate, timeZone), currentDate, timeZone);
19417
+ const dateHeaderRegex = /^(Date:[ \t]*)(.*)$/gm;
19028
19418
  const dates = [];
19029
19419
  let regexMatch;
19030
19420
  while ((regexMatch = dateHeaderRegex.exec(withInlineDates)) !== null) {
19031
- const dateStr = regexMatch[2];
19032
- const parsed = new Date(dateStr);
19033
- if (!isNaN(parsed.getTime())) dates.push({
19421
+ const dateStr = regexMatch[2].trimEnd();
19422
+ const span = parseDateSpan(dateStr);
19423
+ if (span) dates.push({
19034
19424
  index: regexMatch.index,
19035
- date: parsed,
19425
+ span,
19036
19426
  match: regexMatch[0],
19037
19427
  prefix: regexMatch[1],
19038
19428
  dateStr
@@ -19046,11 +19436,10 @@ function addRelativeTimeToObservations(observations, currentDate) {
19046
19436
  const prev = i > 0 ? dates[i - 1] : null;
19047
19437
  result += withInlineDates.slice(lastIndex, curr.index);
19048
19438
  if (prev) {
19049
- const gap = formatGapBetweenDates(prev.date, curr.date);
19439
+ const gap = formatGapBetweenDates(prev.span.end, curr.span.start);
19050
19440
  if (gap) result += `\n${gap}\n\n`;
19051
19441
  }
19052
- const relative = formatRelativeTime(curr.date, currentDate);
19053
- result += `${curr.prefix}${curr.dateStr} (${relative})`;
19442
+ result += `${curr.prefix}${curr.dateStr} (${relativeSpan(curr.span, today)})`;
19054
19443
  lastIndex = curr.index + curr.match.length;
19055
19444
  }
19056
19445
  result += withInlineDates.slice(lastIndex);
@@ -20065,11 +20454,20 @@ const OBSERVER_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
20065
20454
  "heif",
20066
20455
  "avif"
20067
20456
  ]);
20068
- function formatObserverDate(createdAt) {
20069
- return createdAt ? `${createdAt.toLocaleDateString("en-US", { month: "short" })} ${createdAt.getDate()} ${createdAt.getFullYear()}` : "";
20457
+ function formatObserverDate(createdAt, timeZone) {
20458
+ if (!createdAt) return "";
20459
+ const parts = new Intl.DateTimeFormat("en-US", {
20460
+ timeZone,
20461
+ year: "numeric",
20462
+ month: "short",
20463
+ day: "numeric"
20464
+ }).formatToParts(createdAt);
20465
+ const part = (type) => parts.find((p) => p.type === type)?.value;
20466
+ return `${part("month")} ${part("day")} ${part("year")}`;
20070
20467
  }
20071
- function formatObserverTime(createdAt) {
20468
+ function formatObserverTime(createdAt, timeZone) {
20072
20469
  return createdAt ? createdAt.toLocaleTimeString("en-US", {
20470
+ timeZone,
20073
20471
  hour: "numeric",
20074
20472
  minute: "2-digit",
20075
20473
  hour12: true
@@ -20159,11 +20557,11 @@ function formatObserverAttachmentPlaceholder(part, counter) {
20159
20557
  const label = resolveObserverAttachmentLabel(part);
20160
20558
  return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
20161
20559
  }
20162
- function isRecord$3(value) {
20560
+ function isRecord$2(value) {
20163
20561
  return !!value && typeof value === "object";
20164
20562
  }
20165
20563
  function mapToolResultBlockToAttachment(block) {
20166
- if (!isRecord$3(block) || typeof block.type !== "string") return;
20564
+ if (!isRecord$2(block) || typeof block.type !== "string") return;
20167
20565
  const mediaType = typeof block.mediaType === "string" ? block.mediaType : void 0;
20168
20566
  const filename = typeof block.filename === "string" ? block.filename : void 0;
20169
20567
  switch (block.type) {
@@ -20224,7 +20622,7 @@ function mapToolResultBlockToAttachment(block) {
20224
20622
  }
20225
20623
  }
20226
20624
  function extractToolResultAttachments(result, counter, attachmentFilter) {
20227
- if (!isRecord$3(result) || result.type !== "content" || !Array.isArray(result.value)) return {
20625
+ if (!isRecord$2(result) || result.type !== "content" || !Array.isArray(result.value)) return {
20228
20626
  resultWithoutAttachments: result,
20229
20627
  attachments: []
20230
20628
  };
@@ -20238,7 +20636,7 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
20238
20636
  if (shouldIncludeObserverAttachment(attachment, attachmentFilter)) attachments.push(toObserverInputAttachmentPart(attachment));
20239
20637
  const placeholder = formatObserverAttachmentPlaceholder(attachment, counter);
20240
20638
  return {
20241
- type: isRecord$3(block) ? block.type : void 0,
20639
+ type: isRecord$2(block) ? block.type : void 0,
20242
20640
  placeholder
20243
20641
  };
20244
20642
  });
@@ -20355,6 +20753,7 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
20355
20753
  const maxLen = options?.maxPartLength;
20356
20754
  const maxToolResultTokens = options?.maxToolResultTokens ?? 5e3;
20357
20755
  const attachmentFilter = options?.attachmentFilter;
20756
+ const timeZone = resolveTimeZone(options?.timeZone);
20358
20757
  const role = getObserverMessageLabel(msg);
20359
20758
  const attachments = [];
20360
20759
  const messageCreatedAt = normalizeObserverCreatedAt(msg.createdAt);
@@ -20364,8 +20763,8 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
20364
20763
  if (!body) return;
20365
20764
  const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
20366
20765
  lines.push({
20367
- date: formatObserverDate(normalizedCreatedAt),
20368
- time: formatObserverTime(normalizedCreatedAt),
20766
+ date: formatObserverDate(normalizedCreatedAt, timeZone),
20767
+ time: formatObserverTime(normalizedCreatedAt, timeZone),
20369
20768
  title,
20370
20769
  body
20371
20770
  });
@@ -20919,171 +21318,6 @@ function optimizeObservationsForContext(observations) {
20919
21318
  return optimized.trim();
20920
21319
  }
20921
21320
  //#endregion
20922
- //#region src/processors/observational-memory/retry.ts
20923
- /**
20924
- * Retry knobs for the internal OM transport-error retry wrapper.
20925
- * Exported as a mutable object so tests can shrink the backoff schedule
20926
- * without changing public API.
20927
- *
20928
- * With the defaults the per-retry pre-jitter backoff schedule is:
20929
- * 1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s (cap)
20930
- * giving 8 retries / 9 total attempts and ~247s (~4 minutes) of waiting
20931
- * before the final attempt fails. Designed to ride out short provider /
20932
- * network blips without holding the actor turn for much longer than that.
20933
- *
20934
- * @internal
20935
- */
20936
- const RETRY_CONFIG = {
20937
- /** Maximum number of retry *attempts* (total tries = maxRetries + 1). */
20938
- maxRetries: 8,
20939
- /** Initial backoff delay in milliseconds. */
20940
- initialDelayMs: 1e3,
20941
- /** Multiplier applied to the delay after each failed attempt. */
20942
- backoffFactor: 2,
20943
- /** Cap on per-attempt delay (ms). */
20944
- maxDelayMs: 12e4,
20945
- /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
20946
- jitter: .2
20947
- };
20948
- const TRANSIENT_MESSAGE_SUBSTRINGS = [
20949
- "terminated",
20950
- "fetch failed",
20951
- "econnreset",
20952
- "econnrefused",
20953
- "enotfound",
20954
- "eai_again",
20955
- "socket hang up",
20956
- "network error",
20957
- "request timed out",
20958
- "request timeout",
20959
- "connection reset",
20960
- "connection closed"
20961
- ];
20962
- function isRecord$2(value) {
20963
- return typeof value === "object" && value !== null;
20964
- }
20965
- function isAbortError(error) {
20966
- if (!isRecord$2(error)) return false;
20967
- if (error.name === "AbortError") return true;
20968
- if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
20969
- return false;
20970
- }
20971
- function hasTransientMessage(value) {
20972
- if (!isRecord$2(value)) return false;
20973
- const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
20974
- if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
20975
- if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
20976
- return false;
20977
- }
20978
- function isRetryableHttpStatus(status) {
20979
- if (status === 408 || status === 425 || status === 429) return true;
20980
- return status >= 500 && status <= 599;
20981
- }
20982
- function hasRetryableHttpStatus(value) {
20983
- if (!isRecord$2(value)) return false;
20984
- if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
20985
- if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
20986
- return false;
20987
- }
20988
- function hasIsRetryableFlag(value) {
20989
- if (!isRecord$2(value)) return false;
20990
- return value.isRetryable === true;
20991
- }
20992
- /**
20993
- * Returns true when the given error looks like a transient transport-class
20994
- * failure that's worth retrying — undici `terminated`, `fetch failed`,
20995
- * `UND_ERR_*` codes, AI SDK `APICallError` with `isRetryable: true`, and
20996
- * common HTTP 408/425/429/5xx statuses. Walks the `error.cause` chain so
20997
- * wrapper errors don't hide the real failure.
20998
- *
20999
- * Never retries on user-initiated aborts.
21000
- *
21001
- * @internal
21002
- */
21003
- function isTransientLLMError(error) {
21004
- if (isAbortError(error)) return false;
21005
- const visited = /* @__PURE__ */ new WeakSet();
21006
- function visit(candidate) {
21007
- if (isRecord$2(candidate)) {
21008
- if (visited.has(candidate)) return false;
21009
- visited.add(candidate);
21010
- }
21011
- if (hasTransientMessage(candidate)) return true;
21012
- if (hasRetryableHttpStatus(candidate)) return true;
21013
- if (hasIsRetryableFlag(candidate)) return true;
21014
- if (isRecord$2(candidate)) {
21015
- if (visit(candidate.cause)) return true;
21016
- if (visit(candidate.error)) return true;
21017
- }
21018
- return false;
21019
- }
21020
- return visit(error);
21021
- }
21022
- /**
21023
- * Compute the backoff delay (ms) for the Nth retry (0-indexed).
21024
- *
21025
- * Exponential growth (`initialDelayMs * backoffFactor^attempt`) capped at
21026
- * `maxDelayMs`, then nudged by ±`jitter` (fractional). Exported for unit
21027
- * tests that lock the schedule against drift.
21028
- *
21029
- * @internal
21030
- */
21031
- function computeDelay(attempt) {
21032
- const base = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt);
21033
- const capped = Math.min(base, RETRY_CONFIG.maxDelayMs);
21034
- if (RETRY_CONFIG.jitter <= 0) return capped;
21035
- const jitterRange = capped * RETRY_CONFIG.jitter;
21036
- const offset = (Math.random() * 2 - 1) * jitterRange;
21037
- return Math.max(0, Math.round(capped + offset));
21038
- }
21039
- function sleep(ms, abortSignal) {
21040
- if (ms <= 0) return Promise.resolve();
21041
- return new Promise((resolve, reject) => {
21042
- if (abortSignal?.aborted) {
21043
- reject(/* @__PURE__ */ new Error("The operation was aborted."));
21044
- return;
21045
- }
21046
- const timer = setTimeout(() => {
21047
- abortSignal?.removeEventListener("abort", onAbort);
21048
- resolve();
21049
- }, ms);
21050
- const onAbort = () => {
21051
- clearTimeout(timer);
21052
- abortSignal?.removeEventListener("abort", onAbort);
21053
- reject(/* @__PURE__ */ new Error("The operation was aborted."));
21054
- };
21055
- abortSignal?.addEventListener("abort", onAbort, { once: true });
21056
- });
21057
- }
21058
- /**
21059
- * Run `fn` with retries on transient transport-class errors.
21060
- *
21061
- * Non-transient errors (auth, validation, schema, etc.) are rethrown
21062
- * immediately. User-initiated aborts are rethrown without delay.
21063
- *
21064
- * @internal
21065
- */
21066
- async function withRetry(fn, opts) {
21067
- const { label, abortSignal } = opts;
21068
- let attempt = 0;
21069
- while (true) {
21070
- if (abortSignal?.aborted) throw new Error("The operation was aborted.");
21071
- try {
21072
- return await fn();
21073
- } catch (error) {
21074
- if (isAbortError(error) || abortSignal?.aborted) throw error;
21075
- if (attempt >= RETRY_CONFIG.maxRetries || !isTransientLLMError(error)) {
21076
- if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
21077
- throw error;
21078
- }
21079
- const delay = computeDelay(attempt);
21080
- attempt++;
21081
- omDebug(`[OM:retry:${label}] transient error on attempt ${attempt}, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`);
21082
- await sleep(delay, abortSignal);
21083
- }
21084
- }
21085
- }
21086
- //#endregion
21087
21321
  //#region src/processors/observational-memory/temporary-memory.ts
21088
21322
  function createTemporaryOmMemoryContext(prefix) {
21089
21323
  const options = {
@@ -21225,11 +21459,27 @@ var ObserverRunner = class {
21225
21459
  this.mastra = mastra;
21226
21460
  }
21227
21461
  createAgent(model, isMultiThread = false, memory, extractors = this.observationConfig.extractors ?? []) {
21462
+ let agentModel = model;
21463
+ if (Array.isArray(agentModel)) agentModel = agentModel.map((fallback) => ({
21464
+ ...fallback,
21465
+ maxRetries: 0
21466
+ }));
21467
+ else if (typeof agentModel === "function") {
21468
+ const resolveDynamicModel = agentModel;
21469
+ agentModel = (async (args) => {
21470
+ const resolvedModel = await resolveDynamicModel(args);
21471
+ return Array.isArray(resolvedModel) ? resolvedModel.map((fallback) => ({
21472
+ ...fallback,
21473
+ maxRetries: 0
21474
+ })) : resolvedModel;
21475
+ });
21476
+ }
21228
21477
  return new Agent({
21229
21478
  id: isMultiThread ? "multi-thread-observer" : "observational-memory-observer",
21230
21479
  name: isMultiThread ? "multi-thread-observer" : "Observer",
21480
+ maxRetries: 0,
21231
21481
  instructions: buildObserverSystemPrompt(isMultiThread, this.observationConfig.instruction, this.observationConfig.threadTitle, extractors),
21232
- model,
21482
+ model: agentModel,
21233
21483
  ...memory ? { memory } : {},
21234
21484
  ...this.mastra ? { mastra: this.mastra } : {}
21235
21485
  });
@@ -21321,7 +21571,10 @@ var ObserverRunner = class {
21321
21571
  ...options,
21322
21572
  includeThreadTitle: this.observationConfig.threadTitle,
21323
21573
  extractors: activeExtractors
21324
- }, { attachmentFilter })];
21574
+ }, {
21575
+ attachmentFilter,
21576
+ timeZone: options?.timeZone
21577
+ })];
21325
21578
  const doGenerate = async () => {
21326
21579
  return withRetry(() => withOmTracingSpan({
21327
21580
  phase: "observer",
@@ -21359,12 +21612,14 @@ var ObserverRunner = class {
21359
21612
  hasRequestContext: Boolean(internalRequestContext),
21360
21613
  aborted: abortSignal?.aborted ?? false
21361
21614
  });
21362
- throw error;
21615
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
21616
+ throw new OmModelExecutionError("observer-model", error);
21363
21617
  }
21364
21618
  }, abortSignal)
21365
21619
  }), {
21366
21620
  label: "observer",
21367
- abortSignal
21621
+ abortSignal,
21622
+ maxRetries: this.observationConfig.maxRetries
21368
21623
  });
21369
21624
  };
21370
21625
  let result = await doGenerate();
@@ -21432,7 +21687,7 @@ var ObserverRunner = class {
21432
21687
  /**
21433
21688
  * Call the Observer agent for multiple threads in a single batched request.
21434
21689
  */
21435
- async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext) {
21690
+ async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone) {
21436
21691
  const contextFor = (threadId) => ({
21437
21692
  threadId,
21438
21693
  ...hookContext
@@ -21443,7 +21698,7 @@ var ObserverRunner = class {
21443
21698
  if (filtered.length > 0) messagesByThread.set(threadId, filtered);
21444
21699
  }
21445
21700
  const threadOrder = allThreadOrder.filter((threadId) => messagesByThread.has(threadId));
21446
- const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model);
21701
+ const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone);
21447
21702
  for (const threadId of allThreadOrder) {
21448
21703
  const threadResult = output.results.get(threadId);
21449
21704
  if (!threadResult) {
@@ -21454,7 +21709,7 @@ var ObserverRunner = class {
21454
21709
  }
21455
21710
  return output;
21456
21711
  }
21457
- async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model) {
21712
+ async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone) {
21458
21713
  if (threadOrder.length === 0) return { results: /* @__PURE__ */ new Map() };
21459
21714
  const inputTokens = Array.from(messagesByThread.values()).reduce((total, messages) => total + this.tokenCounter.countMessages(messages), 0);
21460
21715
  const resolvedModel = (() => {
@@ -21496,7 +21751,8 @@ var ObserverRunner = class {
21496
21751
  priorSuggestedResponse: priorMetadataByThread?.get(threadId)?.suggestedResponse,
21497
21752
  priorThreadTitle: priorMetadataByThread?.get(threadId)?.threadTitle,
21498
21753
  priorExtractedValues: priorMetadataByThread?.get(threadId)?.extracted,
21499
- model: resolvedModel.model
21754
+ model: resolvedModel.model,
21755
+ timeZone
21500
21756
  });
21501
21757
  results.set(threadId, {
21502
21758
  observations: threadResult.observations,
@@ -21513,6 +21769,7 @@ var ObserverRunner = class {
21513
21769
  totalUsage.totalTokens += threadResult.usage.totalTokens ?? 0;
21514
21770
  }
21515
21771
  }
21772
+ for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
21516
21773
  return {
21517
21774
  results,
21518
21775
  usage: totalUsage
@@ -21521,8 +21778,10 @@ var ObserverRunner = class {
21521
21778
  const agent = this.createAgent(resolvedModel.model, true, void 0, activeExtractors);
21522
21779
  const internalRequestContext = withOmInternalThreadId(requestContext, agent.id);
21523
21780
  const multiThreadAttachmentFilter = this.resolveAttachmentFilter(resolvedModel.model, requestContext);
21524
- const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, void 0, this.observationConfig.threadTitle, activeExtractors, { attachmentFilter: multiThreadAttachmentFilter })];
21525
- for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
21781
+ const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, void 0, this.observationConfig.threadTitle, activeExtractors, {
21782
+ attachmentFilter: multiThreadAttachmentFilter,
21783
+ timeZone
21784
+ })];
21526
21785
  const doGenerate = async () => {
21527
21786
  return withRetry(() => withOmTracingSpan({
21528
21787
  phase: "observer-multi-thread",
@@ -21560,12 +21819,14 @@ var ObserverRunner = class {
21560
21819
  hasRequestContext: Boolean(internalRequestContext),
21561
21820
  aborted: abortSignal?.aborted ?? false
21562
21821
  });
21563
- throw error;
21822
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
21823
+ throw new OmModelExecutionError("observer-model", error);
21564
21824
  }
21565
21825
  }, abortSignal)
21566
21826
  }), {
21567
21827
  label: "observer-multi-thread",
21568
- abortSignal
21828
+ abortSignal,
21829
+ maxRetries: this.observationConfig.maxRetries
21569
21830
  });
21570
21831
  };
21571
21832
  let result = await doGenerate();
@@ -21621,6 +21882,7 @@ var ObserverRunner = class {
21621
21882
  });
21622
21883
  }
21623
21884
  for (const threadId of threadOrder) if (!results.has(threadId)) results.set(threadId, { observations: "" });
21885
+ for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
21624
21886
  const usage = result.totalUsage ?? result.usage;
21625
21887
  return {
21626
21888
  results,
@@ -23114,6 +23376,8 @@ async function summarizeConversation(opts) {
23114
23376
  const runner = new ObserverRunner({
23115
23377
  observationConfig: {
23116
23378
  model: opts.model,
23379
+ maxRetries: 8,
23380
+ failurePolicy: "abort",
23117
23381
  messageTokens: OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens,
23118
23382
  shareTokenBudget: false,
23119
23383
  modelSettings: { ...OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings },
@@ -24944,6 +25208,7 @@ function createObservationFailedMarker(params) {
24944
25208
  durationMs,
24945
25209
  tokensAttempted: params.tokensAttempted,
24946
25210
  error: formatOmError(params.error),
25211
+ ...getOmFailureMetadata(params.error, params.failurePolicy ?? "abort"),
24947
25212
  recordId: params.recordId,
24948
25213
  threadId: params.threadId
24949
25214
  }
@@ -25005,6 +25270,7 @@ function createBufferingFailedMarker(params) {
25005
25270
  durationMs,
25006
25271
  tokensAttempted: params.tokensAttempted,
25007
25272
  error: formatOmError(params.error),
25273
+ ...getOmFailureMetadata(params.error, params.failurePolicy ?? "abort"),
25008
25274
  recordId: params.recordId,
25009
25275
  threadId: params.threadId
25010
25276
  }
@@ -25061,7 +25327,10 @@ function createThreadUpdateMarker(params) {
25061
25327
  function findLastCompletedObservationBoundary(message) {
25062
25328
  const parts = message.content?.parts;
25063
25329
  if (!parts || !Array.isArray(parts)) return -1;
25064
- for (let i = parts.length - 1; i >= 0; i--) if (parts[i]?.type === "data-om-observation-end") return i;
25330
+ for (let i = parts.length - 1; i >= 0; i--) {
25331
+ const part = parts[i];
25332
+ if (part?.type === "data-om-observation-end" && part.data?.operationType !== "reflection") return i;
25333
+ }
25065
25334
  return -1;
25066
25335
  }
25067
25336
  /**
@@ -25313,6 +25582,7 @@ function scanObservationGroupTags(observations) {
25313
25582
  const incompleteOpenings = [];
25314
25583
  let cursor = 0;
25315
25584
  let closeFloor = 0;
25585
+ let nestedFloor = -1;
25316
25586
  while (cursor < observations.length) {
25317
25587
  const start = observations.indexOf(OBSERVATION_GROUP_OPEN, cursor);
25318
25588
  if (start === -1) break;
@@ -25333,8 +25603,12 @@ function scanObservationGroupTags(observations) {
25333
25603
  cursor = openEnd + 1;
25334
25604
  continue;
25335
25605
  }
25336
- const nestedStart = observations.indexOf(OBSERVATION_GROUP_OPEN, openEnd + 1);
25337
- if (nestedStart !== -1 && nestedStart < closeStart && isLineStart(observations, nestedStart)) {
25606
+ if (nestedFloor <= openEnd) {
25607
+ const lineOpen = observations.indexOf(`\n${OBSERVATION_GROUP_OPEN}`, openEnd);
25608
+ nestedFloor = lineOpen === -1 ? observations.length : lineOpen + 1;
25609
+ }
25610
+ const nestedStart = nestedFloor;
25611
+ if (nestedStart < closeStart) {
25338
25612
  incompleteOpenings.push({
25339
25613
  start,
25340
25614
  end: openEnd + 1
@@ -25721,6 +25995,7 @@ var ObservationStrategy = class ObservationStrategy {
25721
25995
  observationTokens: processed.observationTokens,
25722
25996
  threadId,
25723
25997
  writer,
25998
+ messageList: this.opts.messageList,
25724
25999
  abortSignal,
25725
26000
  mainAgent: this.opts.agent,
25726
26001
  sendSignal: this.opts.sendSignal,
@@ -25745,6 +26020,7 @@ var ObservationStrategy = class ObservationStrategy {
25745
26020
  operationType: "observation",
25746
26021
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
25747
26022
  error: formatOmError(error),
26023
+ ...getOmFailureMetadata(error, this.observationConfig.failurePolicy),
25748
26024
  recordId: record.id,
25749
26025
  threadId
25750
26026
  }
@@ -25758,6 +26034,10 @@ var ObservationStrategy = class ObservationStrategy {
25758
26034
  };
25759
26035
  }
25760
26036
  omError("[OM] Observation failed", error);
26037
+ if (this.observationConfig.failurePolicy === "continue" && isOmModelExecutionError(error) && error.failureKind === "observer-model") return {
26038
+ observed: false,
26039
+ error
26040
+ };
25761
26041
  throw error;
25762
26042
  }
25763
26043
  }
@@ -26027,7 +26307,8 @@ var SyncObservationStrategy = class extends ObservationStrategy {
26027
26307
  threadId: this.opts.threadId,
26028
26308
  resourceId: this.opts.resourceId,
26029
26309
  trigger: this.opts.trigger,
26030
- mainAgent: this.opts.agent
26310
+ mainAgent: this.opts.agent,
26311
+ timeZone: this.opts.record.observedTimezone
26031
26312
  });
26032
26313
  const hookedValues = await applyExtractorHooks({
26033
26314
  source: "observer",
@@ -26036,7 +26317,10 @@ var SyncObservationStrategy = class extends ObservationStrategy {
26036
26317
  failures: result.extractionFailures,
26037
26318
  previousValues: this.priorExtractedValues,
26038
26319
  rawObservations: result.observations,
26039
- recentMessages: formatMessagesForObserver(this.opts.messages, { maxPartLength: 500 }),
26320
+ recentMessages: formatMessagesForObserver(this.opts.messages, {
26321
+ maxPartLength: 500,
26322
+ timeZone: this.opts.record.observedTimezone
26323
+ }),
26040
26324
  threadId: this.opts.threadId,
26041
26325
  resourceId: this.opts.resourceId,
26042
26326
  mainAgent: this.opts.agent,
@@ -26167,6 +26451,7 @@ var SyncObservationStrategy = class extends ObservationStrategy {
26167
26451
  startedAt: this.startedAt,
26168
26452
  tokensAttempted: this.tokensToObserve,
26169
26453
  error,
26454
+ failurePolicy: this.observationConfig.failurePolicy,
26170
26455
  recordId: this.opts.record.id,
26171
26456
  threadId: this.opts.threadId
26172
26457
  });
@@ -26218,7 +26503,8 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
26218
26503
  threadId: this.opts.threadId,
26219
26504
  resourceId: this.opts.resourceId,
26220
26505
  trigger: this.opts.trigger,
26221
- mainAgent: this.opts.agent
26506
+ mainAgent: this.opts.agent,
26507
+ timeZone: this.opts.record.observedTimezone
26222
26508
  });
26223
26509
  const hookedValues = await applyExtractorHooks({
26224
26510
  source: "observer",
@@ -26227,7 +26513,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
26227
26513
  failures: result.extractionFailures,
26228
26514
  previousValues: this.priorExtractedValues,
26229
26515
  rawObservations: result.observations,
26230
- recentMessages: formatMessagesForObserver(messages, { maxPartLength: 500 }),
26516
+ recentMessages: formatMessagesForObserver(messages, {
26517
+ maxPartLength: 500,
26518
+ timeZone: this.opts.record.observedTimezone
26519
+ }),
26231
26520
  threadId: this.opts.threadId,
26232
26521
  resourceId: this.opts.resourceId,
26233
26522
  mainAgent: this.opts.agent,
@@ -26373,6 +26662,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
26373
26662
  startedAt: this.startedAt,
26374
26663
  tokensAttempted,
26375
26664
  error,
26665
+ failurePolicy: this.observationConfig.failurePolicy,
26376
26666
  recordId: record.id,
26377
26667
  threadId
26378
26668
  });
@@ -26549,7 +26839,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
26549
26839
  return this.deps.observer.callMultiThread(_existingObservations, batch.threadMap, batch.threadIds, this.opts.abortSignal, this.opts.requestContext, this.priorMetadataByThread, this.opts.observabilityContext, void 0, {
26550
26840
  resourceId: this.opts.resourceId,
26551
26841
  trigger: this.opts.trigger
26552
- });
26842
+ }, this.opts.record.observedTimezone);
26553
26843
  }));
26554
26844
  for (const batchResult of batchResults) {
26555
26845
  for (const [threadId, result] of batchResult.results) this.multiThreadResults.set(threadId, result);
@@ -26582,7 +26872,10 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
26582
26872
  failures: result.extractionFailures,
26583
26873
  previousValues,
26584
26874
  rawObservations: result.observations,
26585
- recentMessages: formatMessagesForObserver(threadMessages, { maxPartLength: 500 }),
26875
+ recentMessages: formatMessagesForObserver(threadMessages, {
26876
+ maxPartLength: 500,
26877
+ timeZone: this.opts.record.observedTimezone
26878
+ }),
26586
26879
  threadId,
26587
26880
  resourceId: this.resourceId,
26588
26881
  mainAgent: this.opts.agent,
@@ -26741,6 +27034,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
26741
27034
  startedAt: this.startedAt,
26742
27035
  tokensAttempted,
26743
27036
  error,
27037
+ failurePolicy: this.observationConfig.failurePolicy,
26744
27038
  recordId: this.opts.record.id,
26745
27039
  threadId
26746
27040
  });
@@ -27155,19 +27449,28 @@ var ObservationStep = class {
27155
27449
  };
27156
27450
  }
27157
27451
  }
27158
- const obsResult = await om.observe({
27159
- threadId,
27160
- resourceId,
27161
- messages: observableMessages,
27162
- messageList,
27163
- trigger: "turn-sync",
27164
- agent: this.turn.agent,
27165
- sendSignal: this.turn.sendSignal,
27166
- sendStateSignal: this.turn.sendStateSignal,
27167
- requestContext: this.turn.requestContext,
27168
- writer: this.turn.writer,
27169
- observabilityContext: this.turn.observabilityContext
27170
- });
27452
+ let obsResult;
27453
+ try {
27454
+ obsResult = await om.observe({
27455
+ threadId,
27456
+ resourceId,
27457
+ messages: observableMessages,
27458
+ messageList,
27459
+ trigger: "turn-sync",
27460
+ agent: this.turn.agent,
27461
+ sendSignal: this.turn.sendSignal,
27462
+ sendStateSignal: this.turn.sendStateSignal,
27463
+ requestContext: this.turn.requestContext,
27464
+ writer: this.turn.writer,
27465
+ observabilityContext: this.turn.observabilityContext
27466
+ });
27467
+ } catch (error) {
27468
+ if (om.config.observation.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "observer-model") throw error;
27469
+ return {
27470
+ succeeded: false,
27471
+ record: freshStatus.record
27472
+ };
27473
+ }
27171
27474
  if (obsResult.observed) {
27172
27475
  const observedMessageIds = new Set(obsResult.record.observedMessageIds ?? []);
27173
27476
  const liveMessages = getObservableMessages(messageList);
@@ -27825,11 +28128,27 @@ var ReflectorRunner = class {
27825
28128
  this.mastra = mastra;
27826
28129
  }
27827
28130
  createAgent(model, memory, extractors = this.reflectionConfig.extractors) {
28131
+ let agentModel = model;
28132
+ if (Array.isArray(agentModel)) agentModel = agentModel.map((fallback) => ({
28133
+ ...fallback,
28134
+ maxRetries: 0
28135
+ }));
28136
+ else if (typeof agentModel === "function") {
28137
+ const resolveDynamicModel = agentModel;
28138
+ agentModel = (async (args) => {
28139
+ const resolvedModel = await resolveDynamicModel(args);
28140
+ return Array.isArray(resolvedModel) ? resolvedModel.map((fallback) => ({
28141
+ ...fallback,
28142
+ maxRetries: 0
28143
+ })) : resolvedModel;
28144
+ });
28145
+ }
27828
28146
  return new Agent({
27829
28147
  id: "observational-memory-reflector",
27830
28148
  name: "Reflector",
27831
28149
  instructions: buildReflectorSystemPrompt(this.reflectionConfig.instruction, extractors),
27832
- model,
28150
+ model: agentModel,
28151
+ maxRetries: 0,
27833
28152
  ...memory ? { memory } : {},
27834
28153
  ...this.mastra ? { mastra: this.mastra } : {}
27835
28154
  });
@@ -27911,36 +28230,42 @@ var ReflectorRunner = class {
27911
28230
  },
27912
28231
  callback: (childObservabilityContext) => withAbortCheck(async () => {
27913
28232
  chunkCount = 0;
27914
- return (await agent.stream(prompt, {
27915
- modelSettings: { ...this.reflectionConfig.modelSettings },
27916
- providerOptions: this.reflectionConfig.providerOptions,
27917
- ...temporaryMemory ? { memory: temporaryMemory.options } : {},
27918
- ...abortSignal ? { abortSignal } : {},
27919
- ...internalRequestContext ? { requestContext: internalRequestContext } : {},
27920
- ...childObservabilityContext,
27921
- ...attemptNumber === 1 ? {
27922
- onChunk(chunk) {
27923
- chunkCount++;
27924
- if (chunkCount === 1 || chunkCount % 50 === 0) {
27925
- const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
27926
- omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
28233
+ try {
28234
+ return await (await agent.stream(prompt, {
28235
+ modelSettings: { ...this.reflectionConfig.modelSettings },
28236
+ providerOptions: this.reflectionConfig.providerOptions,
28237
+ ...temporaryMemory ? { memory: temporaryMemory.options } : {},
28238
+ ...abortSignal ? { abortSignal } : {},
28239
+ ...internalRequestContext ? { requestContext: internalRequestContext } : {},
28240
+ ...childObservabilityContext,
28241
+ ...attemptNumber === 1 ? {
28242
+ onChunk(chunk) {
28243
+ chunkCount++;
28244
+ if (chunkCount === 1 || chunkCount % 50 === 0) {
28245
+ const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
28246
+ omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
28247
+ }
28248
+ },
28249
+ onFinish(event) {
28250
+ omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
28251
+ },
28252
+ onAbort(event) {
28253
+ omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
28254
+ },
28255
+ onError({ error }) {
28256
+ omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
27927
28257
  }
27928
- },
27929
- onFinish(event) {
27930
- omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
27931
- },
27932
- onAbort(event) {
27933
- omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
27934
- },
27935
- onError({ error }) {
27936
- omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
27937
- }
27938
- } : {}
27939
- })).getFullOutput();
28258
+ } : {}
28259
+ })).getFullOutput();
28260
+ } catch (error) {
28261
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
28262
+ throw new OmModelExecutionError("reflector-model", error);
28263
+ }
27940
28264
  }, abortSignal)
27941
28265
  }), {
27942
28266
  label: "reflector",
27943
- abortSignal
28267
+ abortSignal,
28268
+ maxRetries: this.reflectionConfig.maxRetries
27944
28269
  });
27945
28270
  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}`);
27946
28271
  const usage = result.totalUsage ?? result.usage;
@@ -28064,6 +28389,7 @@ var ReflectorRunner = class {
28064
28389
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
28065
28390
  tokensAttempted: observationTokens,
28066
28391
  error,
28392
+ failurePolicy: this.reflectionConfig.failurePolicy,
28067
28393
  recordId: record.id,
28068
28394
  threadId: record.threadId ?? ""
28069
28395
  });
@@ -28476,24 +28802,43 @@ var ReflectorRunner = class {
28476
28802
  usage: reflectResult.usage
28477
28803
  });
28478
28804
  } catch (error) {
28479
- if (writer && streamContext) {
28480
- const failedMarker = createObservationFailedMarker({
28481
- cycleId: streamContext.cycleId,
28482
- operationType: "reflection",
28483
- startedAt: streamContext.startedAt,
28484
- tokensAttempted: observationTokens,
28485
- error,
28486
- recordId: record.id,
28487
- threadId
28488
- });
28489
- await writer.custom({
28490
- ...failedMarker,
28491
- transient: true
28492
- }).catch(() => {});
28805
+ reflectionError = error instanceof Error ? error : new Error(String(error));
28806
+ const failedMarker = createObservationFailedMarker({
28807
+ cycleId: streamContext?.cycleId ?? cycleId,
28808
+ operationType: "reflection",
28809
+ startedAt: streamContext?.startedAt ?? startedAt,
28810
+ tokensAttempted: observationTokens,
28811
+ error,
28812
+ failurePolicy: this.reflectionConfig.failurePolicy,
28813
+ recordId: record.id,
28814
+ threadId
28815
+ });
28816
+ if (writer) await writer.custom({
28817
+ ...failedMarker,
28818
+ transient: true
28819
+ }).catch(() => {});
28820
+ let persistedToList = false;
28821
+ try {
28822
+ persistedToList = await this.persistMarkerToMessage(failedMarker, messageList, threadId, record.resourceId ?? void 0);
28823
+ } catch {
28824
+ persistedToList = false;
28825
+ }
28826
+ if (!persistedToList) try {
28493
28827
  await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0);
28828
+ } catch (markerError) {
28829
+ omError("[OM] Failed to persist reflection-failed marker", markerError);
28494
28830
  }
28495
- reflectionError = error instanceof Error ? error : new Error(String(error));
28496
- if (lifecycleError !== void 0 || abortSignal?.aborted) throw error;
28831
+ this.emitDebugEvent({
28832
+ type: "reflection_failed",
28833
+ timestamp: /* @__PURE__ */ new Date(),
28834
+ threadId,
28835
+ resourceId: record.resourceId ?? "",
28836
+ inputTokens: observationTokens,
28837
+ failurePolicy: this.reflectionConfig.failurePolicy,
28838
+ failureKind: failedMarker.data.failureKind,
28839
+ error: reflectionError.message
28840
+ });
28841
+ if (lifecycleError !== void 0 || abortSignal?.aborted || this.reflectionConfig.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "reflector-model") throw error;
28497
28842
  this.syncReflectionSuppression.set(lockKey, observationTokens);
28498
28843
  omError("[OM] Reflection failed", error);
28499
28844
  } finally {
@@ -29131,6 +29476,8 @@ var ObservationalMemory = class ObservationalMemory {
29131
29476
  const observationActivateAfterIdlePath = config.observation?.activateAfterIdle !== void 0 ? "observation.activateAfterIdle" : "activateAfterIdle";
29132
29477
  this.observationConfig = {
29133
29478
  model: observationModel,
29479
+ maxRetries: config.observation?.maxRetries ?? RETRY_CONFIG.maxRetries,
29480
+ failurePolicy: config.observation?.failurePolicy ?? "abort",
29134
29481
  messageTokens: isSharedBudget ? {
29135
29482
  min: messageTokens,
29136
29483
  max: totalBudget
@@ -29160,6 +29507,8 @@ var ObservationalMemory = class ObservationalMemory {
29160
29507
  };
29161
29508
  this.reflectionConfig = {
29162
29509
  model: reflectionModel,
29510
+ maxRetries: config.reflection?.maxRetries ?? RETRY_CONFIG.maxRetries,
29511
+ failurePolicy: config.reflection?.failurePolicy ?? "abort",
29163
29512
  observationTokens,
29164
29513
  shareTokenBudget: isSharedBudget,
29165
29514
  modelSettings: {
@@ -29233,9 +29582,15 @@ var ObservationalMemory = class ObservationalMemory {
29233
29582
  retrieval: this.retrieval,
29234
29583
  observation: {
29235
29584
  messageTokens: this.observationConfig.messageTokens,
29236
- previousObserverTokens: this.observationConfig.previousObserverTokens
29585
+ previousObserverTokens: this.observationConfig.previousObserverTokens,
29586
+ maxRetries: this.observationConfig.maxRetries,
29587
+ failurePolicy: this.observationConfig.failurePolicy
29237
29588
  },
29238
- reflection: { observationTokens: this.reflectionConfig.observationTokens }
29589
+ reflection: {
29590
+ observationTokens: this.reflectionConfig.observationTokens,
29591
+ maxRetries: this.reflectionConfig.maxRetries,
29592
+ failurePolicy: this.reflectionConfig.failurePolicy
29593
+ }
29239
29594
  };
29240
29595
  }
29241
29596
  /**
@@ -29339,11 +29694,15 @@ var ObservationalMemory = class ObservationalMemory {
29339
29694
  messageTokens: this.observationConfig.messageTokens,
29340
29695
  model: observationResolved.model,
29341
29696
  previousObserverTokens: this.observationConfig.previousObserverTokens,
29697
+ maxRetries: this.observationConfig.maxRetries,
29698
+ failurePolicy: this.observationConfig.failurePolicy,
29342
29699
  routing: observationResolved.routing
29343
29700
  },
29344
29701
  reflection: {
29345
29702
  observationTokens: this.reflectionConfig.observationTokens,
29346
29703
  model: reflectionResolved.model,
29704
+ maxRetries: this.reflectionConfig.maxRetries,
29705
+ failurePolicy: this.reflectionConfig.failurePolicy,
29347
29706
  routing: reflectionResolved.routing
29348
29707
  }
29349
29708
  };
@@ -29360,6 +29719,7 @@ var ObservationalMemory = class ObservationalMemory {
29360
29719
  * Ensures bufferTokens is less than the threshold and bufferActivation is valid.
29361
29720
  */
29362
29721
  validateBufferConfig() {
29722
+ 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}`);
29363
29723
  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.");
29364
29724
  const observationThreshold = getMaxThreshold(this.observationConfig.messageTokens);
29365
29725
  if (this.observationConfig.bufferTokens !== void 0) {
@@ -29487,18 +29847,19 @@ var ObservationalMemory = class ObservationalMemory {
29487
29847
  };
29488
29848
  }
29489
29849
  /**
29490
- * Persist a data-om-* marker part on the last assistant message in messageList
29491
- * AND save the updated message to the DB so it survives page reload.
29492
- * (data-* parts are filtered out before sending to the LLM, so they don't affect model calls.)
29850
+ * Persist a data-om-* marker part on the message that owns the operation in
29851
+ * messageList and save it to the DB. Observation markers belong to the latest
29852
+ * assistant message; reflection markers belong to the current user input.
29493
29853
  * @internal Used by ReflectorRunner. Do not call directly.
29494
29854
  */
29495
29855
  async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
29496
- if (!messageList) return;
29497
- const allMsgs = getObservableMessages(messageList);
29856
+ if (!messageList) return false;
29857
+ const allMsgs = messageList.get.all.db();
29858
+ const markerData = marker.data;
29859
+ const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
29498
29860
  for (let i = allMsgs.length - 1; i >= 0; i--) {
29499
29861
  const msg = allMsgs[i];
29500
- if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
29501
- const markerData = marker.data;
29862
+ if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
29502
29863
  if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
29503
29864
  try {
29504
29865
  await this.messageHistory.persistMessages({
@@ -29508,16 +29869,19 @@ var ObservationalMemory = class ObservationalMemory {
29508
29869
  });
29509
29870
  } catch (e) {
29510
29871
  omDebug(`[OM:persistMarker] failed to save marker to DB: ${e}`);
29872
+ return false;
29511
29873
  }
29512
- return;
29874
+ return true;
29513
29875
  }
29514
29876
  }
29877
+ return false;
29515
29878
  }
29516
29879
  /**
29517
- * Persist a marker to the last assistant message in storage.
29518
- * Unlike persistMarkerToMessage, this fetches messages directly from the DB
29519
- * so it works even when no MessageList is available (e.g. async buffering ops).
29520
- * @internal Used by observation strategies. Do not call directly.
29880
+ * Persist a marker to the message that owns the operation in storage.
29881
+ * Observation markers belong to the latest assistant message. Reflection runs
29882
+ * before the current assistant reply exists, so its failure marker belongs to
29883
+ * the latest user input that triggered it.
29884
+ * @internal Used by observation strategies and the reflector. Do not call directly.
29521
29885
  */
29522
29886
  async persistMarkerToStorage(marker, threadId, resourceId) {
29523
29887
  try {
@@ -29529,8 +29893,9 @@ var ObservationalMemory = class ObservationalMemory {
29529
29893
  direction: "DESC"
29530
29894
  }
29531
29895
  }))?.messages ?? [];
29532
- for (const msg of messages) if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
29533
- const markerData = marker.data;
29896
+ const markerData = marker.data;
29897
+ const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
29898
+ for (const msg of messages) if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
29534
29899
  if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
29535
29900
  await this.messageHistory.persistMessages({
29536
29901
  messages: [msg],
@@ -29772,9 +30137,9 @@ var ObservationalMemory = class ObservationalMemory {
29772
30137
  * In resource scope mode, filters continuity messages to only show
29773
30138
  * the message for the current thread.
29774
30139
  */
29775
- formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false) {
30140
+ formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false, timeZone) {
29776
30141
  let optimized = retrieval ? renderObservationGroupsForReflection(observations) ?? optimizeObservationsForContext(observations) : optimizeObservationsForContext(observations);
29777
- if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate);
30142
+ if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate, timeZone);
29778
30143
  const messages = [`${getObservationContextPrompt(this.scope)}\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}${retrieval ? `\n\n${getRetrievalInstructions(this.retrievalScope, this.retrievalInstructions, this.retrievalSearch)}` : ""}`];
29779
30144
  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`);
29780
30145
  const observationChunks = this.splitObservationContextChunks(optimized);
@@ -29874,12 +30239,15 @@ var ObservationalMemory = class ObservationalMemory {
29874
30239
  * These are injected into the Actor's context so it has awareness of activity
29875
30240
  * in other threads for the same resource.
29876
30241
  */
29877
- async formatUnobservedContextBlocks(messagesByThread, currentThreadId) {
30242
+ async formatUnobservedContextBlocks(messagesByThread, currentThreadId, timeZone) {
29878
30243
  const blocks = [];
29879
30244
  for (const [threadId, messages] of messagesByThread) {
29880
30245
  if (threadId === currentThreadId) continue;
29881
30246
  if (messages.length === 0) continue;
29882
- const formattedMessages = formatMessagesForObserver(messages, { maxPartLength: 500 });
30247
+ const formattedMessages = formatMessagesForObserver(messages, {
30248
+ maxPartLength: 500,
30249
+ timeZone
30250
+ });
29883
30251
  if (formattedMessages) {
29884
30252
  const obscuredId = await this.representThreadIDInContext(threadId);
29885
30253
  blocks.push(`<other-conversation id="${obscuredId}">
@@ -30056,7 +30424,7 @@ ${formattedMessages}
30056
30424
  transient: true
30057
30425
  }).catch(() => {});
30058
30426
  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(",")}`);
30059
- await this.runBufferedObservationCycle({
30427
+ if ((await this.runBufferedObservationCycle({
30060
30428
  threadId,
30061
30429
  resourceId: freshRecord.resourceId ?? void 0,
30062
30430
  trigger: "async-buffer"
@@ -30071,10 +30439,11 @@ ${formattedMessages}
30071
30439
  requestContext,
30072
30440
  observabilityContext,
30073
30441
  trigger: "async-buffer"
30074
- }).run());
30075
- const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
30076
- const cursor = new Date(maxTs.getTime() + 1);
30077
- BufferingCoordinator.lastBufferedAtTime.set(bufferKey, cursor);
30442
+ }).run()))?.observed) {
30443
+ const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
30444
+ const cursor = new Date(maxTs.getTime() + 1);
30445
+ BufferingCoordinator.lastBufferedAtTime.set(bufferKey, cursor);
30446
+ }
30078
30447
  }
30079
30448
  /**
30080
30449
  * Trigger async buffered observation if the token count has crossed a new interval.
@@ -30302,7 +30671,7 @@ ${formattedMessages}
30302
30671
  const currentTask = activeExtractors.some((extractor) => extractor.slug === "current-task") ? omMetadata?.currentTask : void 0;
30303
30672
  const suggestedResponse = activeExtractors.some((extractor) => extractor.slug === "suggested-response") ? omMetadata?.suggestedResponse : void 0;
30304
30673
  const currentDate = opts.currentDate ?? /* @__PURE__ */ new Date();
30305
- return this.formatObservationsForContext(record.activeObservations, currentTask, suggestedResponse, omMetadata?.extracted, unobservedContextBlocks, currentDate, this.retrieval);
30674
+ return this.formatObservationsForContext(record.activeObservations, currentTask, suggestedResponse, omMetadata?.extracted, unobservedContextBlocks, currentDate, this.retrieval, record.observedTimezone);
30306
30675
  }
30307
30676
  /**
30308
30677
  * Get unobserved messages from other threads for resource-scoped observation.
@@ -30314,7 +30683,8 @@ ${formattedMessages}
30314
30683
  async getOtherThreadsContext(resourceId, currentThreadId) {
30315
30684
  const { threads: allThreads } = await this.storage.listThreads({ filter: { resourceId } });
30316
30685
  const messagesByThread = /* @__PURE__ */ new Map();
30317
- const recordLastObservedAt = (await this.getRecord(currentThreadId, resourceId))?.lastObservedAt;
30686
+ const record = await this.getRecord(currentThreadId, resourceId);
30687
+ const recordLastObservedAt = record?.lastObservedAt;
30318
30688
  for (const thread of allThreads) {
30319
30689
  if (thread.id === currentThreadId) continue;
30320
30690
  const threadLastObservedAt = getThreadOMMetadata(thread.metadata)?.lastObservedAt ?? recordLastObservedAt;
@@ -30331,7 +30701,7 @@ ${formattedMessages}
30331
30701
  if (filtered.length > 0) messagesByThread.set(thread.id, filtered);
30332
30702
  }
30333
30703
  if (messagesByThread.size === 0) return void 0;
30334
- return await this.formatUnobservedContextBlocks(messagesByThread, currentThreadId) || void 0;
30704
+ return await this.formatUnobservedContextBlocks(messagesByThread, currentThreadId, record?.observedTimezone) || void 0;
30335
30705
  }
30336
30706
  /**
30337
30707
  * Emit debug event and stream progress for UI feedback.
@@ -30674,7 +31044,7 @@ ${formattedMessages}
30674
31044
  ...startMarker,
30675
31045
  transient: true
30676
31046
  }).catch(() => {});
30677
- await this.runBufferedObservationCycle({
31047
+ if (!(await this.runBufferedObservationCycle({
30678
31048
  threadId,
30679
31049
  resourceId: record.resourceId ?? resourceId,
30680
31050
  trigger: "async-buffer"
@@ -30693,7 +31063,10 @@ ${formattedMessages}
30693
31063
  currentModel: opts.currentModel,
30694
31064
  observabilityContext,
30695
31065
  trigger: "async-buffer"
30696
- }).run());
31066
+ }).run()))?.observed) return {
31067
+ buffered: false,
31068
+ record
31069
+ };
30697
31070
  if (isOmReproCaptureEnabled()) writeObserverExchangeReproCapture({
30698
31071
  threadId,
30699
31072
  resourceId: record.resourceId ?? void 0,
@@ -31053,6 +31426,7 @@ ${formattedMessages}
31053
31426
  observed = result.observed;
31054
31427
  observationUsage = result.usage;
31055
31428
  observationProviderMetadata = result.providerMetadata;
31429
+ observationError = result.error;
31056
31430
  });
31057
31431
  } catch (error) {
31058
31432
  lifecycleError = error;
@@ -32988,6 +33362,7 @@ ${workingMemory}`;
32988
33362
  model: omConfig.model,
32989
33363
  mastra: this._mastraInstance,
32990
33364
  onIndexObservations,
33365
+ onDebugEvent: omConfig.onDebugEvent,
32991
33366
  hooks: omConfig.hooks,
32992
33367
  observation: omConfig.observation ? {
32993
33368
  model: omConfig.observation.model,
@@ -33004,10 +33379,14 @@ ${workingMemory}`;
33004
33379
  threadTitle: omConfig.observation.threadTitle,
33005
33380
  observeAttachments: omConfig.observation.observeAttachments,
33006
33381
  continuationHints: omConfig.observation.continuationHints,
33382
+ maxRetries: omConfig.observation.maxRetries,
33383
+ failurePolicy: omConfig.observation.failurePolicy,
33007
33384
  extract: omConfig.observation.extract
33008
33385
  } : void 0,
33009
33386
  reflection: omConfig.reflection ? {
33010
33387
  model: omConfig.reflection.model,
33388
+ maxRetries: omConfig.reflection.maxRetries,
33389
+ failurePolicy: omConfig.reflection.failurePolicy,
33011
33390
  observationTokens: omConfig.reflection.observationTokens,
33012
33391
  modelSettings: omConfig.reflection.modelSettings,
33013
33392
  providerOptions: omConfig.reflection.providerOptions,
@@ -34079,4 +34458,4 @@ Notes:
34079
34458
  //#endregion
34080
34459
  export { formatMessagesForObserver as A, OBSERVATION_CONTEXT_PROMPT_THREAD as B, SUMMARIZE_THREAD_DEFAULTS as C, buildObserverPrompt as D, OBSERVER_SYSTEM_PROMPT as E, parseAnchorId as F, StaleKnowledgeSemanticIndexError as G, getObservationContextPrompt as H, stripEphemeralAnchorIds as I, SUBCONSCIOUS_ACTIVITY_STATE_ID as J, SubconsciousRemindExtractor as K, OBSERVATIONAL_MEMORY_DEFAULTS as L, optimizeObservationsForContext as M, parseObserverOutput as N, buildObserverSystemPrompt as O, injectAnchorIds as P, Extractor as Q, OBSERVATION_CONTEXT_INSTRUCTIONS as R, deepMergeWorkingMemory as S, TokenCounter as T, Subconscious as U, OBSERVATION_CONTINUATION_HINT as V, KnowledgeSemanticIndexCoordinator as W, publishSubconsciousActivity as X, buildSubconsciousActivitySnapshot as Y, renderSubconsciousActivity as Z, wrapInObservationGroup as _, extractWorkingMemoryContent as a, WorkingMemoryStateProcessor as b, getObservationsAsOf as c, combineObservationGroupRanges as d, deriveObservationGroupProvenance as f, stripObservationGroups as g, renderObservationGroupsForReflection as h, WorkingMemory as i, hasCurrentTaskSection as j, extractCurrentTask as k, ObservationalMemoryProcessor as l, reconcileObservationGroupsFromReflection as m, MessageHistory$1 as n, extractWorkingMemoryTags as o, parseObservationGroups as p, ModelByInputTokens as q, SemanticRecall as r, removeWorkingMemoryTags as s, Memory as t, ObservationalMemory as u, WORKING_MEMORY_STATE_ID as v, summarizeConversation as w, WorkingMemoryExtractor as x, WORKING_MEMORY_STATE_PROCESSOR_ID as y, OBSERVATION_CONTEXT_PROMPT as z };
34081
34460
 
34082
- //# sourceMappingURL=src-B-_pkR2t.js.map
34461
+ //# sourceMappingURL=src-BikfoIdS.js.map