@mastra/server 1.72.0-alpha.1 → 1.72.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/{a2a-CwxSk6H0.cjs → a2a-CAQVpVxH.cjs} +23 -3
  2. package/dist/a2a-CAQVpVxH.cjs.map +1 -0
  3. package/dist/{a2a-DQOqoU_q.js → a2a-D2BffGsK.js} +23 -3
  4. package/dist/a2a-D2BffGsK.js.map +1 -0
  5. package/dist/{agent-builder-BGTZux5y.js → agent-builder-BlD120ac.js} +2 -2
  6. package/dist/{agent-builder-BGTZux5y.js.map → agent-builder-BlD120ac.js.map} +1 -1
  7. package/dist/{agent-builder-D-LAj4SD.cjs → agent-builder-CvwtAD7k.cjs} +2 -2
  8. package/dist/{agent-builder-D-LAj4SD.cjs.map → agent-builder-CvwtAD7k.cjs.map} +1 -1
  9. package/dist/{api-schema-manifest-DJiUahwo.js → api-schema-manifest-B2qMxAnr.js} +2 -2
  10. package/dist/{api-schema-manifest-DJiUahwo.js.map → api-schema-manifest-B2qMxAnr.js.map} +1 -1
  11. package/dist/{api-schema-manifest-Maxewnw7.cjs → api-schema-manifest-gLtuKcZT.cjs} +2 -2
  12. package/dist/{api-schema-manifest-Maxewnw7.cjs.map → api-schema-manifest-gLtuKcZT.cjs.map} +1 -1
  13. package/dist/{channels-DMw4LEA-.cjs → channels-DJkXEpTr.cjs} +18 -7
  14. package/dist/channels-DJkXEpTr.cjs.map +1 -0
  15. package/dist/{channels-xqChW6Vw.js → channels-DzaCNtXe.js} +18 -7
  16. package/dist/channels-DzaCNtXe.js.map +1 -0
  17. package/dist/{dist-ABdCIqbD.js → dist-Bo9o3JNW.js} +745 -366
  18. package/dist/dist-Bo9o3JNW.js.map +1 -0
  19. package/dist/{dist-Cfr6t0cK.cjs → dist-_n0Mp6PP.cjs} +745 -366
  20. package/dist/dist-_n0Mp6PP.cjs.map +1 -0
  21. package/dist/docs/SKILL.md +1 -1
  22. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  23. package/dist/{editor-builder-BrkDnCEd.js → editor-builder-DJMAKDR5.js} +3 -2
  24. package/dist/editor-builder-DJMAKDR5.js.map +1 -0
  25. package/dist/{editor-builder-DIpISGSI.cjs → editor-builder-DrDs-t9i.cjs} +3 -2
  26. package/dist/editor-builder-DrDs-t9i.cjs.map +1 -0
  27. package/dist/{routes-D6sAEgtj.js → routes-CNwYNwrs.js} +4 -4
  28. package/dist/{routes-D6sAEgtj.js.map → routes-CNwYNwrs.js.map} +1 -1
  29. package/dist/{routes-C9CmwhbC.cjs → routes-DEdytSu5.cjs} +4 -4
  30. package/dist/{routes-C9CmwhbC.cjs.map → routes-DEdytSu5.cjs.map} +1 -1
  31. package/dist/server/handlers/a2a.cjs +1 -1
  32. package/dist/server/handlers/a2a.d.ts.map +1 -1
  33. package/dist/server/handlers/a2a.js +1 -1
  34. package/dist/server/handlers/agent-builder.cjs +3 -3
  35. package/dist/server/handlers/agent-builder.d.ts +4 -2
  36. package/dist/server/handlers/agent-builder.d.ts.map +1 -1
  37. package/dist/server/handlers/agent-builder.js +3 -3
  38. package/dist/server/handlers/agents.cjs +5 -2
  39. package/dist/server/handlers/agents.cjs.map +1 -1
  40. package/dist/server/handlers/agents.d.ts.map +1 -1
  41. package/dist/server/handlers/agents.js +5 -2
  42. package/dist/server/handlers/agents.js.map +1 -1
  43. package/dist/server/handlers/channels.cjs +1 -1
  44. package/dist/server/handlers/channels.d.ts.map +1 -1
  45. package/dist/server/handlers/channels.js +1 -1
  46. package/dist/server/handlers/editor-builder.cjs +1 -1
  47. package/dist/server/handlers/editor-builder.d.ts.map +1 -1
  48. package/dist/server/handlers/editor-builder.js +1 -1
  49. package/dist/server/handlers/favorites-enrichment.cjs +1 -1
  50. package/dist/server/handlers/favorites-enrichment.js +1 -1
  51. package/dist/server/handlers/stored-agent-favorites.cjs +1 -1
  52. package/dist/server/handlers/stored-agent-favorites.js +1 -1
  53. package/dist/server/handlers/stored-agents.cjs +6 -3
  54. package/dist/server/handlers/stored-agents.cjs.map +1 -1
  55. package/dist/server/handlers/stored-agents.d.ts.map +1 -1
  56. package/dist/server/handlers/stored-agents.js +6 -3
  57. package/dist/server/handlers/stored-agents.js.map +1 -1
  58. package/dist/server/handlers/stored-skill-favorites.cjs +1 -1
  59. package/dist/server/handlers/stored-skill-favorites.js +1 -1
  60. package/dist/server/handlers/stored-skills.cjs +1 -1
  61. package/dist/server/handlers/stored-skills.js +1 -1
  62. package/dist/server/handlers/system.cjs +1 -1
  63. package/dist/server/handlers/system.js +1 -1
  64. package/dist/server/handlers/workflows.cjs +28 -4
  65. package/dist/server/handlers/workflows.cjs.map +1 -1
  66. package/dist/server/handlers/workflows.d.ts +5 -2
  67. package/dist/server/handlers/workflows.d.ts.map +1 -1
  68. package/dist/server/handlers/workflows.js +28 -4
  69. package/dist/server/handlers/workflows.js.map +1 -1
  70. package/dist/server/handlers.cjs +1 -1
  71. package/dist/server/handlers.js +1 -1
  72. package/dist/server/schemas/index.cjs +2 -2
  73. package/dist/server/schemas/index.js +2 -2
  74. package/dist/server/schemas/workflows.d.ts +2 -1
  75. package/dist/server/schemas/workflows.d.ts.map +1 -1
  76. package/dist/server/server-adapter/index.cjs +1 -1
  77. package/dist/server/server-adapter/index.js +1 -1
  78. package/dist/server/server-adapter/routes/agent-builder.d.ts +4 -2
  79. package/dist/server/server-adapter/routes/agent-builder.d.ts.map +1 -1
  80. package/dist/server/server-adapter/routes/workflows.d.ts +4 -2
  81. package/dist/server/server-adapter/routes/workflows.d.ts.map +1 -1
  82. package/dist/{workflows-CvJ4c4_p.js → workflows-BEdLrZQZ.js} +3 -2
  83. package/dist/workflows-BEdLrZQZ.js.map +1 -0
  84. package/dist/{workflows-Cv6MGw9d.cjs → workflows-BdoErlkd.cjs} +3 -2
  85. package/dist/workflows-BdoErlkd.cjs.map +1 -0
  86. package/package.json +5 -5
  87. package/dist/a2a-CwxSk6H0.cjs.map +0 -1
  88. package/dist/a2a-DQOqoU_q.js.map +0 -1
  89. package/dist/channels-DMw4LEA-.cjs.map +0 -1
  90. package/dist/channels-xqChW6Vw.js.map +0 -1
  91. package/dist/dist-ABdCIqbD.js.map +0 -1
  92. package/dist/dist-Cfr6t0cK.cjs.map +0 -1
  93. package/dist/editor-builder-BrkDnCEd.js.map +0 -1
  94. package/dist/editor-builder-DIpISGSI.cjs.map +0 -1
  95. package/dist/workflows-Cv6MGw9d.cjs.map +0 -1
  96. package/dist/workflows-CvJ4c4_p.js.map +0 -1
@@ -4638,7 +4638,7 @@ function splitLines(text) {
4638
4638
  return result;
4639
4639
  }
4640
4640
  //#endregion
4641
- //#region ../memory/dist/src-B-_pkR2t.js
4641
+ //#region ../memory/dist/src-BikfoIdS.js
4642
4642
  var __defProp$3 = Object.defineProperty;
4643
4643
  var __exportAll = (all, no_symbols) => {
4644
4644
  let target = {};
@@ -23224,9 +23224,224 @@ async function applyExtractorHooks(opts) {
23224
23224
  failures: mergeExtractionFailures(failures)
23225
23225
  };
23226
23226
  }
23227
+ /**
23228
+ * Retry knobs for the internal OM transport-error retry wrapper.
23229
+ * Exported as a mutable object so tests can shrink the backoff schedule
23230
+ * without changing public API.
23231
+ *
23232
+ * With the defaults the per-retry pre-jitter backoff schedule is:
23233
+ * 1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s (cap)
23234
+ * giving 8 retries / 9 total attempts and ~247s (~4 minutes) of waiting
23235
+ * before the final attempt fails. Designed to ride out short provider /
23236
+ * network blips without holding the actor turn for much longer than that.
23237
+ *
23238
+ * @internal
23239
+ */
23240
+ const RETRY_CONFIG = {
23241
+ /** Maximum number of retry *attempts* (total tries = maxRetries + 1). */
23242
+ maxRetries: 8,
23243
+ /** Initial backoff delay in milliseconds. */
23244
+ initialDelayMs: 1e3,
23245
+ /** Multiplier applied to the delay after each failed attempt. */
23246
+ backoffFactor: 2,
23247
+ /** Cap on per-attempt delay (ms). */
23248
+ maxDelayMs: 12e4,
23249
+ /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
23250
+ jitter: .2
23251
+ };
23252
+ const TRANSIENT_MESSAGE_SUBSTRINGS = [
23253
+ "terminated",
23254
+ "fetch failed",
23255
+ "econnreset",
23256
+ "econnrefused",
23257
+ "enotfound",
23258
+ "eai_again",
23259
+ "socket hang up",
23260
+ "network error",
23261
+ "request timed out",
23262
+ "request timeout",
23263
+ "connection reset",
23264
+ "connection closed"
23265
+ ];
23227
23266
  function isRecord$5(value) {
23228
23267
  return typeof value === "object" && value !== null;
23229
23268
  }
23269
+ function isAbortError$1(error) {
23270
+ if (!isRecord$5(error)) return false;
23271
+ if (error.name === "AbortError") return true;
23272
+ if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
23273
+ return false;
23274
+ }
23275
+ function hasTransientMessage(value) {
23276
+ if (!isRecord$5(value)) return false;
23277
+ const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
23278
+ if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
23279
+ if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
23280
+ return false;
23281
+ }
23282
+ function isRetryableHttpStatus(status) {
23283
+ if (status === 408 || status === 425 || status === 429) return true;
23284
+ return status >= 500 && status <= 599;
23285
+ }
23286
+ function hasRetryableHttpStatus(value) {
23287
+ if (!isRecord$5(value)) return false;
23288
+ if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
23289
+ if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
23290
+ return false;
23291
+ }
23292
+ function hasIsRetryableFlag(value) {
23293
+ if (!isRecord$5(value)) return false;
23294
+ return value.isRetryable === true;
23295
+ }
23296
+ /**
23297
+ * Returns true when a user-initiated cancellation appears anywhere in the
23298
+ * error's `cause`/`error` wrapper chain, so a wrapped abort can never be
23299
+ * mistaken for a retryable or survivable provider failure.
23300
+ *
23301
+ * @internal
23302
+ */
23303
+ function hasAbortInChain(error) {
23304
+ const seen = /* @__PURE__ */ new Set();
23305
+ function visit(candidate) {
23306
+ if (isAbortError$1(candidate)) return true;
23307
+ if (!isRecord$5(candidate)) return false;
23308
+ if (seen.has(candidate)) return false;
23309
+ seen.add(candidate);
23310
+ return visit(candidate.cause) || visit(candidate.error);
23311
+ }
23312
+ return visit(error);
23313
+ }
23314
+ /**
23315
+ * Returns true when the given error looks like a transient transport-class
23316
+ * failure that's worth retrying — undici `terminated`, `fetch failed`,
23317
+ * `UND_ERR_*` codes, AI SDK `APICallError` with `isRetryable: true`, and
23318
+ * common HTTP 408/425/429/5xx statuses. Walks the `error.cause` chain so
23319
+ * wrapper errors don't hide the real failure.
23320
+ *
23321
+ * Never retries on user-initiated aborts.
23322
+ *
23323
+ * @internal
23324
+ */
23325
+ function isTransientLLMError(error) {
23326
+ if (hasAbortInChain(error)) return false;
23327
+ const visited = /* @__PURE__ */ new WeakSet();
23328
+ function visit(candidate) {
23329
+ if (isRecord$5(candidate)) {
23330
+ if (visited.has(candidate)) return false;
23331
+ visited.add(candidate);
23332
+ }
23333
+ if (hasTransientMessage(candidate)) return true;
23334
+ if (hasRetryableHttpStatus(candidate)) return true;
23335
+ if (hasIsRetryableFlag(candidate)) return true;
23336
+ if (isRecord$5(candidate)) {
23337
+ if (visit(candidate.cause)) return true;
23338
+ if (visit(candidate.error)) return true;
23339
+ }
23340
+ return false;
23341
+ }
23342
+ return visit(error);
23343
+ }
23344
+ /**
23345
+ * Compute the backoff delay (ms) for the Nth retry (0-indexed).
23346
+ *
23347
+ * Exponential growth (`initialDelayMs * backoffFactor^attempt`) capped at
23348
+ * `maxDelayMs`, then nudged by ±`jitter` (fractional). Exported for unit
23349
+ * tests that lock the schedule against drift.
23350
+ *
23351
+ * @internal
23352
+ */
23353
+ function computeDelay(attempt) {
23354
+ const base = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt);
23355
+ const capped = Math.min(base, RETRY_CONFIG.maxDelayMs);
23356
+ if (RETRY_CONFIG.jitter <= 0) return capped;
23357
+ const jitterRange = capped * RETRY_CONFIG.jitter;
23358
+ const offset = (Math.random() * 2 - 1) * jitterRange;
23359
+ return Math.max(0, Math.round(capped + offset));
23360
+ }
23361
+ function sleep(ms, abortSignal) {
23362
+ if (ms <= 0) return Promise.resolve();
23363
+ return new Promise((resolve, reject) => {
23364
+ if (abortSignal?.aborted) {
23365
+ reject(/* @__PURE__ */ new Error("The operation was aborted."));
23366
+ return;
23367
+ }
23368
+ const timer = setTimeout(() => {
23369
+ abortSignal?.removeEventListener("abort", onAbort);
23370
+ resolve();
23371
+ }, ms);
23372
+ const onAbort = () => {
23373
+ clearTimeout(timer);
23374
+ abortSignal?.removeEventListener("abort", onAbort);
23375
+ reject(/* @__PURE__ */ new Error("The operation was aborted."));
23376
+ };
23377
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
23378
+ });
23379
+ }
23380
+ /**
23381
+ * Run `fn` with retries on transient transport-class errors.
23382
+ *
23383
+ * Non-transient errors (auth, validation, schema, etc.) are rethrown
23384
+ * immediately. User-initiated aborts are rethrown without delay.
23385
+ *
23386
+ * @internal
23387
+ */
23388
+ async function withRetry(fn, opts) {
23389
+ const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries } = opts;
23390
+ let attempt = 0;
23391
+ while (true) {
23392
+ if (abortSignal?.aborted) throw new Error("The operation was aborted.");
23393
+ try {
23394
+ return await fn();
23395
+ } catch (error) {
23396
+ if (hasAbortInChain(error) || abortSignal?.aborted) throw error;
23397
+ if (attempt >= maxRetries || !isTransientLLMError(error)) {
23398
+ if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
23399
+ throw error;
23400
+ }
23401
+ const delay = computeDelay(attempt);
23402
+ attempt++;
23403
+ omDebug(`[OM:retry:${label}] transient error on attempt ${attempt}, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`);
23404
+ await sleep(delay, abortSignal);
23405
+ }
23406
+ }
23407
+ }
23408
+ const AI_API_CALL_ERROR_MARKER = Symbol.for("vercel.ai.error.AI_APICallError");
23409
+ function isRecord$4(value) {
23410
+ return typeof value === "object" && value !== null;
23411
+ }
23412
+ function isOmModelExecutionFailure(error) {
23413
+ if (hasAbortInChain(error)) return false;
23414
+ if (isTransientLLMError(error)) return true;
23415
+ const visited = /* @__PURE__ */ new Set();
23416
+ let current = error;
23417
+ while (isRecord$4(current) && !visited.has(current)) {
23418
+ visited.add(current);
23419
+ if (Object.prototype.hasOwnProperty.call(current, AI_API_CALL_ERROR_MARKER)) return Reflect.get(current, AI_API_CALL_ERROR_MARKER) === true;
23420
+ current = current.cause ?? current.error;
23421
+ }
23422
+ return false;
23423
+ }
23424
+ var OmModelExecutionError = class extends Error {
23425
+ failureKind;
23426
+ constructor(failureKind, cause) {
23427
+ super(formatOmError(cause), { cause });
23428
+ this.failureKind = failureKind;
23429
+ this.name = "OmModelExecutionError";
23430
+ }
23431
+ };
23432
+ function isOmModelExecutionError(error) {
23433
+ try {
23434
+ return error instanceof OmModelExecutionError;
23435
+ } catch {
23436
+ return false;
23437
+ }
23438
+ }
23439
+ function getOmFailureMetadata(error, failurePolicy) {
23440
+ return {
23441
+ failurePolicy,
23442
+ ...isOmModelExecutionError(error) ? { failureKind: error.failureKind } : {}
23443
+ };
23444
+ }
23230
23445
  /** Keep provider diagnostics in streamed/persisted markers, not whole API request/response objects. */
23231
23446
  function formatOmError(error) {
23232
23447
  const details = /* @__PURE__ */ new Set();
@@ -23236,7 +23451,7 @@ function formatOmError(error) {
23236
23451
  };
23237
23452
  function collectErrorDetails(value, depth) {
23238
23453
  if (depth > 5) return;
23239
- if (!isRecord$5(value)) {
23454
+ if (!isRecord$4(value)) {
23240
23455
  if (value !== void 0) add(String(value));
23241
23456
  return;
23242
23457
  }
@@ -23246,10 +23461,10 @@ function formatOmError(error) {
23246
23461
  if (typeof value.statusCode === "number") add(`HTTP ${value.statusCode}`);
23247
23462
  if (typeof value.responseBody === "string") try {
23248
23463
  const body = JSON.parse(value.responseBody);
23249
- if (isRecord$5(body)) {
23464
+ if (isRecord$4(body)) {
23250
23465
  add(body.message);
23251
23466
  add(body.detail);
23252
- if (isRecord$5(body.error)) add(body.error.message);
23467
+ if (isRecord$4(body.error)) add(body.error.message);
23253
23468
  else add(body.error);
23254
23469
  }
23255
23470
  } catch {}
@@ -23262,8 +23477,8 @@ function formatOmError(error) {
23262
23477
  const message = [...details].join(": ") || "Unknown error";
23263
23478
  return message.length > 2e3 ? `${message.slice(0, 1997)}...` : message;
23264
23479
  }
23265
- function isAbortError$1(error, abortSignal) {
23266
- return abortSignal?.aborted === true || error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
23480
+ function isAbortError$5(error, abortSignal) {
23481
+ return abortSignal?.aborted === true || hasAbortInChain(error);
23267
23482
  }
23268
23483
  function shouldRetryEmptyStructuredObject(object, extractors) {
23269
23484
  return Object.keys(object).length === 0 && extractors.some((extractor) => extractor.retryStructuredExtractionOnEmptyObject);
@@ -23289,7 +23504,12 @@ If a prior value is still applicable and carry-forward is enabled, return that p
23289
23504
  ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values\n\n${priorLines.join("\n\n")}` : ""}`;
23290
23505
  const values = {};
23291
23506
  const failures = [];
23292
- const streamWithStructuredOutput = async (jsonPromptInjection) => {
23507
+ const streamWithStructuredOutput = async (jsonPromptInjection) => withRetry(() => streamOnce(jsonPromptInjection), {
23508
+ label: `om-${opts.source}-structured-extraction`,
23509
+ abortSignal: opts.abortSignal,
23510
+ maxRetries: 1
23511
+ });
23512
+ const streamOnce = async (jsonPromptInjection) => {
23293
23513
  const object = await (await opts.agent.stream(prompt, {
23294
23514
  structuredOutput: {
23295
23515
  schema,
@@ -23309,11 +23529,21 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
23309
23529
  object = await streamWithStructuredOutput();
23310
23530
  retryEmptyObject = shouldRetryEmptyStructuredObject(object, structuredExtractors);
23311
23531
  } catch (error) {
23312
- if (isAbortError$1(error, opts.abortSignal)) throw error;
23532
+ if (isAbortError$5(error, opts.abortSignal)) throw error;
23533
+ if (isTransientLLMError(error)) {
23534
+ const message = error instanceof Error ? error.message : String(error);
23535
+ return {
23536
+ values,
23537
+ failures: structuredExtractors.map((extractor) => ({
23538
+ slug: extractor.slug,
23539
+ error: message
23540
+ }))
23541
+ };
23542
+ }
23313
23543
  try {
23314
23544
  object = await streamWithStructuredOutput(_mastra_core_features.coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
23315
23545
  } catch (fallbackError) {
23316
- if (isAbortError$1(fallbackError, opts.abortSignal)) throw fallbackError;
23546
+ if (isAbortError$5(fallbackError, opts.abortSignal)) throw fallbackError;
23317
23547
  const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
23318
23548
  return {
23319
23549
  values,
@@ -23327,7 +23557,7 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
23327
23557
  if (retryEmptyObject) try {
23328
23558
  object = await streamWithStructuredOutput(_mastra_core_features.coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
23329
23559
  } catch (fallbackError) {
23330
- if (isAbortError$1(fallbackError, opts.abortSignal)) throw fallbackError;
23560
+ if (isAbortError$5(fallbackError, opts.abortSignal)) throw fallbackError;
23331
23561
  const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
23332
23562
  return {
23333
23563
  values,
@@ -23353,15 +23583,15 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
23353
23583
  };
23354
23584
  }
23355
23585
  const LEGACY_SYSTEM_REMINDER_METADATA_KEY = "dynamicAgentsMdReminder";
23356
- function isRecord$4(value) {
23586
+ function isRecord$3(value) {
23357
23587
  return typeof value === "object" && value !== null;
23358
23588
  }
23359
23589
  function isSystemReminderMessage(message) {
23360
- if (!isRecord$4(message.content)) return false;
23590
+ if (!isRecord$3(message.content)) return false;
23361
23591
  const metadata = message.content.metadata;
23362
- if (message.role === "signal") return isRecord$4(metadata) && isRecord$4(metadata.signal) && (metadata.signal.type === "system-reminder" || metadata.signal.type === "reactive");
23592
+ if (message.role === "signal") return isRecord$3(metadata) && isRecord$3(metadata.signal) && (metadata.signal.type === "system-reminder" || metadata.signal.type === "reactive");
23363
23593
  if (message.role !== "user") return false;
23364
- if (isRecord$4(metadata) && (isRecord$4(metadata.systemReminder) || LEGACY_SYSTEM_REMINDER_METADATA_KEY in metadata)) return true;
23594
+ if (isRecord$3(metadata) && (isRecord$3(metadata.systemReminder) || LEGACY_SYSTEM_REMINDER_METADATA_KEY in metadata)) return true;
23365
23595
  const firstTextPart = message.content.parts.find((part) => part.type === "text");
23366
23596
  return typeof firstTextPart?.text === "string" && firstTextPart.text.startsWith("<system-reminder");
23367
23597
  }
@@ -23420,12 +23650,41 @@ function stripEphemeralAnchorIds(observations) {
23420
23650
  * Date/time utility functions for Observational Memory.
23421
23651
  * Pure functions for formatting relative timestamps and annotating observations.
23422
23652
  */
23423
- /**
23424
- * Format a relative time string like "5 days ago", "2 weeks ago", "today", etc.
23425
- */
23426
- function formatRelativeTime(date, currentDate) {
23427
- const diffMs = currentDate.getTime() - date.getTime();
23428
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
23653
+ const DAY_MS = 1440 * 60 * 1e3;
23654
+ /** Day number of the calendar date a parsed date names. Parsed dates are built from local-time fields. */
23655
+ function calendarDay(date) {
23656
+ return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / DAY_MS;
23657
+ }
23658
+ const knownTimeZones = /* @__PURE__ */ new Map();
23659
+ /** `timeZone` when this runtime recognises it; otherwise undefined, which means the process time zone. */
23660
+ function resolveTimeZone(timeZone) {
23661
+ if (!timeZone) return void 0;
23662
+ let known = knownTimeZones.get(timeZone);
23663
+ if (known === void 0) {
23664
+ try {
23665
+ new Intl.DateTimeFormat("en-US", { timeZone });
23666
+ known = true;
23667
+ } catch {
23668
+ known = false;
23669
+ }
23670
+ knownTimeZones.set(timeZone, known);
23671
+ }
23672
+ return known ? timeZone : void 0;
23673
+ }
23674
+ /** Day number of the calendar date `instant` falls on in `timeZone` (the process time zone when unset). */
23675
+ function calendarDayIn(instant, timeZone) {
23676
+ const zone = resolveTimeZone(timeZone);
23677
+ if (!zone) return calendarDay(instant);
23678
+ const parts = new Intl.DateTimeFormat("en-US", {
23679
+ timeZone: zone,
23680
+ year: "numeric",
23681
+ month: "numeric",
23682
+ day: "numeric"
23683
+ }).formatToParts(instant);
23684
+ const part = (type) => Number(parts.find((p) => p.type === type)?.value);
23685
+ return Date.UTC(part("year"), part("month") - 1, part("day")) / DAY_MS;
23686
+ }
23687
+ function formatRelativeDays(diffDays) {
23429
23688
  if (diffDays < 0) {
23430
23689
  const futureDays = Math.abs(diffDays);
23431
23690
  if (futureDays === 1) return "tomorrow";
@@ -23451,8 +23710,7 @@ function formatRelativeTime(date, currentDate) {
23451
23710
  * Returns null for consecutive days (no gap marker needed).
23452
23711
  */
23453
23712
  function formatGapBetweenDates(prevDate, currDate) {
23454
- const diffMs = currDate.getTime() - prevDate.getTime();
23455
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
23713
+ const diffDays = calendarDay(currDate) - calendarDay(prevDate);
23456
23714
  if (diffDays <= 1) return null;
23457
23715
  else if (diffDays < 7) return `[${diffDays} days later]`;
23458
23716
  else if (diffDays < 14) return `[1 week later]`;
@@ -23460,45 +23718,156 @@ function formatGapBetweenDates(prevDate, currDate) {
23460
23718
  else if (diffDays < 60) return `[1 month later]`;
23461
23719
  else return `[${Math.floor(diffDays / 30)} months later]`;
23462
23720
  }
23721
+ const MONTH_INDEX = {
23722
+ jan: 0,
23723
+ feb: 1,
23724
+ mar: 2,
23725
+ apr: 3,
23726
+ may: 4,
23727
+ jun: 5,
23728
+ jul: 6,
23729
+ aug: 7,
23730
+ sep: 8,
23731
+ oct: 9,
23732
+ nov: 10,
23733
+ dec: 11
23734
+ };
23735
+ 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)?)\.?`;
23736
+ const DAY = String.raw`\d{1,2}(?:st|nd|rd|th)?`;
23737
+ const YEAR = String.raw`(?:19|20|21)\d{2}(?!\d)`;
23738
+ const MERIDIEM = String.raw`(?:[AaPp]\.[Mm]\.|[AaPp][Mm]\b)`;
23739
+ const TIME = String.raw`(?:,?\s+(?:at\s+)?\d{1,2}:\d{2}(?:\s*${MERIDIEM})?|\s+at\s+\d{1,2}\s*${MERIDIEM})`;
23740
+ const QUALIFIER = String.raw`(?:(?:early|mid|late)[- ](?:to[- ](?:early|mid|late)[- ])?)`;
23741
+ const RANGE_SEP = String.raw`(?:\s*[–—]\s*|\s*-\s*|\s+(?:to|through|until)\s+)`;
23742
+ /**
23743
+ * Dates written into observation text that carry their own year, longest forms first:
23744
+ * "Mar 1 - Mar 18, 2025", "May 27-28, 2023", "January 10, 2024 at 3:00 PM",
23745
+ * "June–July 2022", "Dec 2021 – June 2023", "late April 2023".
23746
+ * Bare years, dates without a year, and ISO dates (often part of model IDs, API versions,
23747
+ * paths and branch names) are deliberately not matched.
23748
+ */
23749
+ const FREE_TEXT_DATE = new RegExp([
23750
+ String.raw`\b${MONTH}\s+${DAY}(?:,?\s+${YEAR})?${RANGE_SEP}${MONTH}\s+${DAY},?\s+${YEAR}${TIME}?`,
23751
+ String.raw`\b${MONTH}\s+${DAY}${RANGE_SEP}${DAY},?\s+${YEAR}`,
23752
+ String.raw`\b${MONTH}\s+${DAY},?\s+${YEAR}${TIME}?`,
23753
+ String.raw`\b${QUALIFIER}?${MONTH}(?:\s+${YEAR})?${RANGE_SEP}${QUALIFIER}?${MONTH}\s+${YEAR}`,
23754
+ String.raw`\b${QUALIFIER}?${MONTH}\s+${YEAR}`
23755
+ ].join("|"), "g");
23756
+ const ENDPOINT = new RegExp(String.raw`^(?:(early|mid|late)\s*)?(?:(${MONTH})\s*)?(?:(\d{1,2})(?:st|nd|rd|th)?)?,?\s*(?:(${YEAR}))?$`, "i");
23757
+ function parseEndpoint(text) {
23758
+ const match = ENDPOINT.exec(text.trim());
23759
+ if (!match || !match[0]) return null;
23760
+ const [, qualifier, month, day, year] = match;
23761
+ return {
23762
+ ...qualifier ? { qualifier: qualifier.toLowerCase() } : {},
23763
+ ...month ? { month: MONTH_INDEX[month.slice(0, 3).toLowerCase()] } : {},
23764
+ ...day ? { day: Number(day) } : {},
23765
+ ...year ? { year: Number(year) } : {}
23766
+ };
23767
+ }
23768
+ const QUALIFIER_DAY = {
23769
+ early: 7,
23770
+ mid: 15,
23771
+ late: 23
23772
+ };
23773
+ const QUALIFIER_MONTH = {
23774
+ early: [1, 15],
23775
+ mid: [6, 1],
23776
+ late: [10, 15]
23777
+ };
23778
+ /** Earliest and latest local day an endpoint can mean; null when it has no year or is not a real date. */
23779
+ function endpointBounds(date) {
23780
+ const { qualifier, month, day, year } = date;
23781
+ if (year === void 0) return null;
23782
+ if (month === void 0) {
23783
+ if (day !== void 0) return null;
23784
+ if (qualifier) {
23785
+ const [m, d] = QUALIFIER_MONTH[qualifier];
23786
+ return [new Date(year, m, d), new Date(year, m, d)];
23787
+ }
23788
+ return [new Date(year, 0, 1), new Date(year, 11, 31)];
23789
+ }
23790
+ if (day !== void 0) {
23791
+ const point = new Date(year, month, day);
23792
+ if (point.getMonth() !== month) return null;
23793
+ return [point, point];
23794
+ }
23795
+ if (qualifier) {
23796
+ const point = new Date(year, month, QUALIFIER_DAY[qualifier]);
23797
+ return [point, point];
23798
+ }
23799
+ return [new Date(year, month, 1), new Date(year, month + 1, 0)];
23800
+ }
23801
+ /** Parses one alternative: a single date or a two-ended range, filling each end's missing parts from the other. */
23802
+ function parseSpanAlternative(text) {
23803
+ const ends = text.split(new RegExp(`${RANGE_SEP}(?=\\S)`)).filter(Boolean);
23804
+ if (ends.length === 0 || ends.length > 2) return null;
23805
+ const parsed = ends.map(parseEndpoint);
23806
+ if (parsed.some((end) => end === null)) return null;
23807
+ const [first, second = first] = parsed;
23808
+ const from = { ...first };
23809
+ const to = { ...second };
23810
+ const fromYearInferred = from.year === void 0;
23811
+ const toYearInferred = to.year === void 0;
23812
+ from.year ??= to.year;
23813
+ to.year ??= from.year;
23814
+ if (from.month === void 0 && (from.day !== void 0 || from.qualifier) && to.month !== void 0) from.month = to.month;
23815
+ if (to.month === void 0 && to.day !== void 0 && from.month !== void 0) to.month = from.month;
23816
+ if (from.month !== void 0 && to.month !== void 0 && from.month > to.month && from.year === to.year) {
23817
+ if (fromYearInferred) from.year -= 1;
23818
+ else if (toYearInferred) to.year += 1;
23819
+ }
23820
+ const fromBounds = endpointBounds(from);
23821
+ const toBounds = endpointBounds(to);
23822
+ if (!fromBounds || !toBounds) return null;
23823
+ const span = {
23824
+ start: fromBounds[0],
23825
+ end: toBounds[1]
23826
+ };
23827
+ return span.start <= span.end ? span : null;
23828
+ }
23829
+ function normalizeDateText(text) {
23830
+ 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) => {
23831
+ const month = Object.keys(MONTH_INDEX)[Number(m) - 1];
23832
+ return month ? `${month} ${Number(d)}, ${y}` : whole;
23833
+ }).replace(new RegExp(TIME, "g"), "").replace(/\b(early|mid|late|to)-/gi, "$1 ").replace(/ {2,}/g, " ").trim();
23834
+ }
23463
23835
  /**
23464
- * Parses a date string like "May 30, 2023", "May 27-28, 2023", "late April 2023", etc.
23465
- * Returns the parsed Date or null if unparseable.
23836
+ * Parses the whole of `text` as a date or date range. Accepts "May 30, 2023",
23837
+ * "Mar 22, 2025 at 18:08", "May 27-28, 2023", "Dec 27–31, 2021", "Mar 1 - Mar 18, 2025",
23838
+ * "Aug 1, 2024 - Feb 28, 2025", "August 2024", "June–July 2022", "late April 2023",
23839
+ * "mid-to-late May 2023", "late 2023", "2035", "2021–2026", an "approx." prefix, and
23840
+ * "A or B" alternatives (their union). Returns null when no year is known.
23466
23841
  */
23467
- function parseDateFromContent(dateContent) {
23468
- let targetDate = null;
23469
- const simpleDateMatch = dateContent.match(/([A-Z][a-z]+)\s+(\d{1,2}),?\s+(\d{4})/);
23470
- if (simpleDateMatch) {
23471
- const parsed = /* @__PURE__ */ new Date(`${simpleDateMatch[1]} ${simpleDateMatch[2]}, ${simpleDateMatch[3]}`);
23472
- if (!isNaN(parsed.getTime())) targetDate = parsed;
23473
- }
23474
- if (!targetDate) {
23475
- const rangeMatch = dateContent.match(/([A-Z][a-z]+)\s+(\d{1,2})-\d{1,2},?\s+(\d{4})/);
23476
- if (rangeMatch) {
23477
- const parsed = /* @__PURE__ */ new Date(`${rangeMatch[1]} ${rangeMatch[2]}, ${rangeMatch[3]}`);
23478
- if (!isNaN(parsed.getTime())) targetDate = parsed;
23479
- }
23480
- }
23481
- if (!targetDate) {
23482
- const vagueMatch = dateContent.match(/(late|early|mid)[- ]?(?:to[- ]?(?:late|early|mid)[- ]?)?([A-Z][a-z]+)\s+(\d{4})/i);
23483
- if (vagueMatch) {
23484
- const month = vagueMatch[2];
23485
- const year = vagueMatch[3];
23486
- const modifier = vagueMatch[1].toLowerCase();
23487
- let day = 15;
23488
- if (modifier === "early") day = 7;
23489
- if (modifier === "late") day = 23;
23490
- const parsed = /* @__PURE__ */ new Date(`${month} ${day}, ${year}`);
23491
- if (!isNaN(parsed.getTime())) targetDate = parsed;
23492
- }
23493
- }
23494
- if (!targetDate) {
23495
- const crossMonthMatch = dateContent.match(/([A-Z][a-z]+)\s+to\s+(?:early\s+)?([A-Z][a-z]+)\s+(\d{4})/i);
23496
- if (crossMonthMatch) {
23497
- const parsed = /* @__PURE__ */ new Date(`${crossMonthMatch[2]} 1, ${crossMonthMatch[3]}`);
23498
- if (!isNaN(parsed.getTime())) targetDate = parsed;
23499
- }
23500
- }
23501
- return targetDate;
23842
+ function parseDateSpan(text) {
23843
+ const alternatives = normalizeDateText(text).split(/\s+or\s+/i).map(parseSpanAlternative);
23844
+ if (alternatives.length === 0 || alternatives.some((span) => span === null)) return null;
23845
+ const spans = alternatives;
23846
+ return {
23847
+ start: new Date(Math.min(...spans.map((span) => span.start.getTime()))),
23848
+ end: new Date(Math.max(...spans.map((span) => span.end.getTime())))
23849
+ };
23850
+ }
23851
+ 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");
23852
+ /**
23853
+ * Like `parseDateSpan`, but falls back to the first dated expression inside longer text,
23854
+ * then to a year on its own ("by late 2027", "$500 in 2024"). Only for text known to be a date.
23855
+ */
23856
+ function findDateSpan(text) {
23857
+ const whole = parseDateSpan(text);
23858
+ if (whole) return whole;
23859
+ for (const pattern of [FREE_TEXT_DATE, QUALIFIED_YEAR]) for (const match of text.matchAll(pattern)) {
23860
+ const span = parseDateSpan(match[0]);
23861
+ if (span) return span;
23862
+ }
23863
+ return null;
23864
+ }
23865
+ function relativeSpan(span, today) {
23866
+ const start = formatRelativeDays(today - calendarDay(span.start));
23867
+ const end = formatRelativeDays(today - calendarDay(span.end));
23868
+ if (start === end) return start;
23869
+ if (!end.startsWith("in ")) return `${start} to ${end}`;
23870
+ return start.startsWith("in ") ? `${start} to ${end.slice(3)}` : `${start} to ${end.slice(3)} from now`;
23502
23871
  }
23503
23872
  /**
23504
23873
  * Detects if an observation line indicates future intent (will do, plans to, looking forward to, etc.)
@@ -23518,40 +23887,59 @@ function isFutureIntentObservation(line) {
23518
23887
  }
23519
23888
  /**
23520
23889
  * Expand inline estimated dates with relative time.
23521
- * Matches patterns like "(estimated May 27-28, 2023)" or "(meaning May 30, 2023)"
23522
- * and expands them to "(meaning May 30, 2023 - which was 3 weeks ago)"
23890
+ * Matches patterns like "(estimated May 27-28, 2023)" or "(meaning May 30, 2023 at 3:00 PM)"
23891
+ * and expands them to "(meaning May 30, 2023 - 3 weeks ago)". Notes without a year are left alone.
23523
23892
  */
23524
- function expandInlineEstimatedDates(observations, currentDate) {
23525
- return observations.replace(/\((estimated|meaning)\s+([^)]+\d{4})\)/gi, (match, prefix, dateContent, offset) => {
23526
- const targetDate = parseDateFromContent(dateContent);
23527
- if (targetDate) {
23528
- const relative = formatRelativeTime(targetDate, currentDate);
23529
- const lineStart = observations.lastIndexOf("\n", offset) + 1;
23530
- const lineBeforeDate = observations.slice(lineStart, offset);
23531
- const isPastDate = targetDate < currentDate;
23532
- const isFutureIntent = isFutureIntentObservation(lineBeforeDate);
23533
- if (isPastDate && isFutureIntent) return `(${prefix} ${dateContent} - ${relative}, likely already happened)`;
23534
- return `(${prefix} ${dateContent} - ${relative})`;
23535
- }
23536
- return match;
23893
+ function expandInlineEstimatedDates(observations, currentDate, timeZone) {
23894
+ const inlineDateRegex = /\((estimated|meaning)\s([^()]*)\)/gi;
23895
+ const today = calendarDayIn(currentDate, timeZone);
23896
+ return observations.replace(inlineDateRegex, (match, prefix, noteText, offset) => {
23897
+ const dateContent = noteText.trimStart();
23898
+ const span = findDateSpan(dateContent);
23899
+ if (!span) return match;
23900
+ const relative = relativeSpan(span, today);
23901
+ const lineStart = observations.lastIndexOf("\n", offset) + 1;
23902
+ const lineBeforeDate = observations.slice(lineStart, offset);
23903
+ if (calendarDay(span.end) < today && isFutureIntentObservation(lineBeforeDate)) return `(${prefix} ${dateContent} - ${relative}, likely already happened)`;
23904
+ return `(${prefix} ${dateContent} - ${relative})`;
23905
+ });
23906
+ }
23907
+ /**
23908
+ * Annotates dates written into observation text, e.g. "exam on January 10, 2024" becomes
23909
+ * "exam on January 10, 2024 (5 months ago)". Only dates that state their year are annotated;
23910
+ * "Date:" headers and "(meaning/estimated …)" notes are handled separately and skipped here.
23911
+ */
23912
+ function annotateObservationTextDates(observations, currentDate, timeZone) {
23913
+ const regex = new RegExp(String.raw`^Date:.*$|\((?:[Ee]stimated|[Mm]eaning)\b[^()]*\)|${FREE_TEXT_DATE.source}`, "gm");
23914
+ const today = calendarDayIn(currentDate, timeZone);
23915
+ return observations.replace(regex, (match, offset) => {
23916
+ if (match.startsWith("Date:") || match.startsWith("(")) return match;
23917
+ const span = parseDateSpan(match);
23918
+ if (!span) return match;
23919
+ const relative = relativeSpan(span, today);
23920
+ return observations[offset + match.length] === ")" ? `${match} - ${relative}` : `${match} (${relative})`;
23537
23921
  });
23538
23922
  }
23539
23923
  /**
23540
23924
  * Add relative time annotations to observations.
23541
- * Transforms "Date: May 15, 2023" headers to "Date: May 15, 2023 (5 days ago)"
23542
- * and expands inline estimated dates with relative time context.
23925
+ * Transforms "Date: May 15, 2023" headers to "Date: May 15, 2023 (5 days ago)", range headers such as
23926
+ * "Date: Aug 1, 2024 - Feb 28, 2025" to "(7 months ago to 4 weeks ago)", and annotates inline dates.
23927
+ *
23928
+ * `timeZone` is the zone the Observer wrote its dates in (the record's `observedTimezone`). "Today" is
23929
+ * `currentDate`'s calendar date in that zone, so the result doesn't depend on the zone of the process rendering it.
23543
23930
  */
23544
- function addRelativeTimeToObservations(observations, currentDate) {
23545
- const withInlineDates = expandInlineEstimatedDates(observations, currentDate);
23546
- const dateHeaderRegex = /^(Date:\s*)([A-Z][a-z]+ \d{1,2}, \d{4})$/gm;
23931
+ function addRelativeTimeToObservations(observations, currentDate, timeZone) {
23932
+ const today = calendarDayIn(currentDate, timeZone);
23933
+ const withInlineDates = annotateObservationTextDates(expandInlineEstimatedDates(observations, currentDate, timeZone), currentDate, timeZone);
23934
+ const dateHeaderRegex = /^(Date:[ \t]*)(.*)$/gm;
23547
23935
  const dates = [];
23548
23936
  let regexMatch;
23549
23937
  while ((regexMatch = dateHeaderRegex.exec(withInlineDates)) !== null) {
23550
- const dateStr = regexMatch[2];
23551
- const parsed = new Date(dateStr);
23552
- if (!isNaN(parsed.getTime())) dates.push({
23938
+ const dateStr = regexMatch[2].trimEnd();
23939
+ const span = parseDateSpan(dateStr);
23940
+ if (span) dates.push({
23553
23941
  index: regexMatch.index,
23554
- date: parsed,
23942
+ span,
23555
23943
  match: regexMatch[0],
23556
23944
  prefix: regexMatch[1],
23557
23945
  dateStr
@@ -23565,11 +23953,10 @@ function addRelativeTimeToObservations(observations, currentDate) {
23565
23953
  const prev = i > 0 ? dates[i - 1] : null;
23566
23954
  result += withInlineDates.slice(lastIndex, curr.index);
23567
23955
  if (prev) {
23568
- const gap = formatGapBetweenDates(prev.date, curr.date);
23956
+ const gap = formatGapBetweenDates(prev.span.end, curr.span.start);
23569
23957
  if (gap) result += `\n${gap}\n\n`;
23570
23958
  }
23571
- const relative = formatRelativeTime(curr.date, currentDate);
23572
- result += `${curr.prefix}${curr.dateStr} (${relative})`;
23959
+ result += `${curr.prefix}${curr.dateStr} (${relativeSpan(curr.span, today)})`;
23573
23960
  lastIndex = curr.index + curr.match.length;
23574
23961
  }
23575
23962
  result += withInlineDates.slice(lastIndex);
@@ -24574,11 +24961,20 @@ const OBSERVER_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
24574
24961
  "heif",
24575
24962
  "avif"
24576
24963
  ]);
24577
- function formatObserverDate(createdAt) {
24578
- return createdAt ? `${createdAt.toLocaleDateString("en-US", { month: "short" })} ${createdAt.getDate()} ${createdAt.getFullYear()}` : "";
24964
+ function formatObserverDate(createdAt, timeZone) {
24965
+ if (!createdAt) return "";
24966
+ const parts = new Intl.DateTimeFormat("en-US", {
24967
+ timeZone,
24968
+ year: "numeric",
24969
+ month: "short",
24970
+ day: "numeric"
24971
+ }).formatToParts(createdAt);
24972
+ const part = (type) => parts.find((p) => p.type === type)?.value;
24973
+ return `${part("month")} ${part("day")} ${part("year")}`;
24579
24974
  }
24580
- function formatObserverTime(createdAt) {
24975
+ function formatObserverTime(createdAt, timeZone) {
24581
24976
  return createdAt ? createdAt.toLocaleTimeString("en-US", {
24977
+ timeZone,
24582
24978
  hour: "numeric",
24583
24979
  minute: "2-digit",
24584
24980
  hour12: true
@@ -24668,11 +25064,11 @@ function formatObserverAttachmentPlaceholder(part, counter) {
24668
25064
  const label = resolveObserverAttachmentLabel(part);
24669
25065
  return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
24670
25066
  }
24671
- function isRecord$3(value) {
25067
+ function isRecord$2(value) {
24672
25068
  return !!value && typeof value === "object";
24673
25069
  }
24674
25070
  function mapToolResultBlockToAttachment(block) {
24675
- if (!isRecord$3(block) || typeof block.type !== "string") return;
25071
+ if (!isRecord$2(block) || typeof block.type !== "string") return;
24676
25072
  const mediaType = typeof block.mediaType === "string" ? block.mediaType : void 0;
24677
25073
  const filename = typeof block.filename === "string" ? block.filename : void 0;
24678
25074
  switch (block.type) {
@@ -24733,7 +25129,7 @@ function mapToolResultBlockToAttachment(block) {
24733
25129
  }
24734
25130
  }
24735
25131
  function extractToolResultAttachments(result, counter, attachmentFilter) {
24736
- if (!isRecord$3(result) || result.type !== "content" || !Array.isArray(result.value)) return {
25132
+ if (!isRecord$2(result) || result.type !== "content" || !Array.isArray(result.value)) return {
24737
25133
  resultWithoutAttachments: result,
24738
25134
  attachments: []
24739
25135
  };
@@ -24747,7 +25143,7 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
24747
25143
  if (shouldIncludeObserverAttachment(attachment, attachmentFilter)) attachments.push(toObserverInputAttachmentPart(attachment));
24748
25144
  const placeholder = formatObserverAttachmentPlaceholder(attachment, counter);
24749
25145
  return {
24750
- type: isRecord$3(block) ? block.type : void 0,
25146
+ type: isRecord$2(block) ? block.type : void 0,
24751
25147
  placeholder
24752
25148
  };
24753
25149
  });
@@ -24864,6 +25260,7 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
24864
25260
  const maxLen = options?.maxPartLength;
24865
25261
  const maxToolResultTokens = options?.maxToolResultTokens ?? 5e3;
24866
25262
  const attachmentFilter = options?.attachmentFilter;
25263
+ const timeZone = resolveTimeZone(options?.timeZone);
24867
25264
  const role = getObserverMessageLabel(msg);
24868
25265
  const attachments = [];
24869
25266
  const messageCreatedAt = normalizeObserverCreatedAt(msg.createdAt);
@@ -24873,8 +25270,8 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
24873
25270
  if (!body) return;
24874
25271
  const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
24875
25272
  lines.push({
24876
- date: formatObserverDate(normalizedCreatedAt),
24877
- time: formatObserverTime(normalizedCreatedAt),
25273
+ date: formatObserverDate(normalizedCreatedAt, timeZone),
25274
+ time: formatObserverTime(normalizedCreatedAt, timeZone),
24878
25275
  title,
24879
25276
  body
24880
25277
  });
@@ -25427,169 +25824,6 @@ function optimizeObservationsForContext(observations) {
25427
25824
  optimized = optimized.replace(/\n{3,}/g, "\n\n");
25428
25825
  return optimized.trim();
25429
25826
  }
25430
- /**
25431
- * Retry knobs for the internal OM transport-error retry wrapper.
25432
- * Exported as a mutable object so tests can shrink the backoff schedule
25433
- * without changing public API.
25434
- *
25435
- * With the defaults the per-retry pre-jitter backoff schedule is:
25436
- * 1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s (cap)
25437
- * giving 8 retries / 9 total attempts and ~247s (~4 minutes) of waiting
25438
- * before the final attempt fails. Designed to ride out short provider /
25439
- * network blips without holding the actor turn for much longer than that.
25440
- *
25441
- * @internal
25442
- */
25443
- const RETRY_CONFIG = {
25444
- /** Maximum number of retry *attempts* (total tries = maxRetries + 1). */
25445
- maxRetries: 8,
25446
- /** Initial backoff delay in milliseconds. */
25447
- initialDelayMs: 1e3,
25448
- /** Multiplier applied to the delay after each failed attempt. */
25449
- backoffFactor: 2,
25450
- /** Cap on per-attempt delay (ms). */
25451
- maxDelayMs: 12e4,
25452
- /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
25453
- jitter: .2
25454
- };
25455
- const TRANSIENT_MESSAGE_SUBSTRINGS = [
25456
- "terminated",
25457
- "fetch failed",
25458
- "econnreset",
25459
- "econnrefused",
25460
- "enotfound",
25461
- "eai_again",
25462
- "socket hang up",
25463
- "network error",
25464
- "request timed out",
25465
- "request timeout",
25466
- "connection reset",
25467
- "connection closed"
25468
- ];
25469
- function isRecord$2(value) {
25470
- return typeof value === "object" && value !== null;
25471
- }
25472
- function isAbortError$5(error) {
25473
- if (!isRecord$2(error)) return false;
25474
- if (error.name === "AbortError") return true;
25475
- if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
25476
- return false;
25477
- }
25478
- function hasTransientMessage(value) {
25479
- if (!isRecord$2(value)) return false;
25480
- const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
25481
- if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
25482
- if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
25483
- return false;
25484
- }
25485
- function isRetryableHttpStatus(status) {
25486
- if (status === 408 || status === 425 || status === 429) return true;
25487
- return status >= 500 && status <= 599;
25488
- }
25489
- function hasRetryableHttpStatus(value) {
25490
- if (!isRecord$2(value)) return false;
25491
- if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
25492
- if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
25493
- return false;
25494
- }
25495
- function hasIsRetryableFlag(value) {
25496
- if (!isRecord$2(value)) return false;
25497
- return value.isRetryable === true;
25498
- }
25499
- /**
25500
- * Returns true when the given error looks like a transient transport-class
25501
- * failure that's worth retrying — undici `terminated`, `fetch failed`,
25502
- * `UND_ERR_*` codes, AI SDK `APICallError` with `isRetryable: true`, and
25503
- * common HTTP 408/425/429/5xx statuses. Walks the `error.cause` chain so
25504
- * wrapper errors don't hide the real failure.
25505
- *
25506
- * Never retries on user-initiated aborts.
25507
- *
25508
- * @internal
25509
- */
25510
- function isTransientLLMError(error) {
25511
- if (isAbortError$5(error)) return false;
25512
- const visited = /* @__PURE__ */ new WeakSet();
25513
- function visit(candidate) {
25514
- if (isRecord$2(candidate)) {
25515
- if (visited.has(candidate)) return false;
25516
- visited.add(candidate);
25517
- }
25518
- if (hasTransientMessage(candidate)) return true;
25519
- if (hasRetryableHttpStatus(candidate)) return true;
25520
- if (hasIsRetryableFlag(candidate)) return true;
25521
- if (isRecord$2(candidate)) {
25522
- if (visit(candidate.cause)) return true;
25523
- if (visit(candidate.error)) return true;
25524
- }
25525
- return false;
25526
- }
25527
- return visit(error);
25528
- }
25529
- /**
25530
- * Compute the backoff delay (ms) for the Nth retry (0-indexed).
25531
- *
25532
- * Exponential growth (`initialDelayMs * backoffFactor^attempt`) capped at
25533
- * `maxDelayMs`, then nudged by ±`jitter` (fractional). Exported for unit
25534
- * tests that lock the schedule against drift.
25535
- *
25536
- * @internal
25537
- */
25538
- function computeDelay(attempt) {
25539
- const base = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt);
25540
- const capped = Math.min(base, RETRY_CONFIG.maxDelayMs);
25541
- if (RETRY_CONFIG.jitter <= 0) return capped;
25542
- const jitterRange = capped * RETRY_CONFIG.jitter;
25543
- const offset = (Math.random() * 2 - 1) * jitterRange;
25544
- return Math.max(0, Math.round(capped + offset));
25545
- }
25546
- function sleep(ms, abortSignal) {
25547
- if (ms <= 0) return Promise.resolve();
25548
- return new Promise((resolve, reject) => {
25549
- if (abortSignal?.aborted) {
25550
- reject(/* @__PURE__ */ new Error("The operation was aborted."));
25551
- return;
25552
- }
25553
- const timer = setTimeout(() => {
25554
- abortSignal?.removeEventListener("abort", onAbort);
25555
- resolve();
25556
- }, ms);
25557
- const onAbort = () => {
25558
- clearTimeout(timer);
25559
- abortSignal?.removeEventListener("abort", onAbort);
25560
- reject(/* @__PURE__ */ new Error("The operation was aborted."));
25561
- };
25562
- abortSignal?.addEventListener("abort", onAbort, { once: true });
25563
- });
25564
- }
25565
- /**
25566
- * Run `fn` with retries on transient transport-class errors.
25567
- *
25568
- * Non-transient errors (auth, validation, schema, etc.) are rethrown
25569
- * immediately. User-initiated aborts are rethrown without delay.
25570
- *
25571
- * @internal
25572
- */
25573
- async function withRetry(fn, opts) {
25574
- const { label, abortSignal } = opts;
25575
- let attempt = 0;
25576
- while (true) {
25577
- if (abortSignal?.aborted) throw new Error("The operation was aborted.");
25578
- try {
25579
- return await fn();
25580
- } catch (error) {
25581
- if (isAbortError$5(error) || abortSignal?.aborted) throw error;
25582
- if (attempt >= RETRY_CONFIG.maxRetries || !isTransientLLMError(error)) {
25583
- if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
25584
- throw error;
25585
- }
25586
- const delay = computeDelay(attempt);
25587
- attempt++;
25588
- omDebug(`[OM:retry:${label}] transient error on attempt ${attempt}, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`);
25589
- await sleep(delay, abortSignal);
25590
- }
25591
- }
25592
- }
25593
25827
  function createTemporaryOmMemoryContext(prefix) {
25594
25828
  const options = {
25595
25829
  thread: `${prefix}-${(0, crypto$1.randomUUID)()}`,
@@ -25724,11 +25958,27 @@ var ObserverRunner = class {
25724
25958
  this.mastra = mastra;
25725
25959
  }
25726
25960
  createAgent(model, isMultiThread = false, memory, extractors = this.observationConfig.extractors ?? []) {
25961
+ let agentModel = model;
25962
+ if (Array.isArray(agentModel)) agentModel = agentModel.map((fallback) => ({
25963
+ ...fallback,
25964
+ maxRetries: 0
25965
+ }));
25966
+ else if (typeof agentModel === "function") {
25967
+ const resolveDynamicModel = agentModel;
25968
+ agentModel = (async (args) => {
25969
+ const resolvedModel = await resolveDynamicModel(args);
25970
+ return Array.isArray(resolvedModel) ? resolvedModel.map((fallback) => ({
25971
+ ...fallback,
25972
+ maxRetries: 0
25973
+ })) : resolvedModel;
25974
+ });
25975
+ }
25727
25976
  return new _mastra_core_agent.Agent({
25728
25977
  id: isMultiThread ? "multi-thread-observer" : "observational-memory-observer",
25729
25978
  name: isMultiThread ? "multi-thread-observer" : "Observer",
25979
+ maxRetries: 0,
25730
25980
  instructions: buildObserverSystemPrompt(isMultiThread, this.observationConfig.instruction, this.observationConfig.threadTitle, extractors),
25731
- model,
25981
+ model: agentModel,
25732
25982
  ...memory ? { memory } : {},
25733
25983
  ...this.mastra ? { mastra: this.mastra } : {}
25734
25984
  });
@@ -25820,7 +26070,10 @@ var ObserverRunner = class {
25820
26070
  ...options,
25821
26071
  includeThreadTitle: this.observationConfig.threadTitle,
25822
26072
  extractors: activeExtractors
25823
- }, { attachmentFilter })];
26073
+ }, {
26074
+ attachmentFilter,
26075
+ timeZone: options?.timeZone
26076
+ })];
25824
26077
  const doGenerate = async () => {
25825
26078
  return withRetry(() => withOmTracingSpan({
25826
26079
  phase: "observer",
@@ -25858,12 +26111,14 @@ var ObserverRunner = class {
25858
26111
  hasRequestContext: Boolean(internalRequestContext),
25859
26112
  aborted: abortSignal?.aborted ?? false
25860
26113
  });
25861
- throw error;
26114
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
26115
+ throw new OmModelExecutionError("observer-model", error);
25862
26116
  }
25863
26117
  }, abortSignal)
25864
26118
  }), {
25865
26119
  label: "observer",
25866
- abortSignal
26120
+ abortSignal,
26121
+ maxRetries: this.observationConfig.maxRetries
25867
26122
  });
25868
26123
  };
25869
26124
  let result = await doGenerate();
@@ -25931,7 +26186,7 @@ var ObserverRunner = class {
25931
26186
  /**
25932
26187
  * Call the Observer agent for multiple threads in a single batched request.
25933
26188
  */
25934
- async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext) {
26189
+ async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone) {
25935
26190
  const contextFor = (threadId) => ({
25936
26191
  threadId,
25937
26192
  ...hookContext
@@ -25942,7 +26197,7 @@ var ObserverRunner = class {
25942
26197
  if (filtered.length > 0) messagesByThread.set(threadId, filtered);
25943
26198
  }
25944
26199
  const threadOrder = allThreadOrder.filter((threadId) => messagesByThread.has(threadId));
25945
- const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model);
26200
+ const output = await this.callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone);
25946
26201
  for (const threadId of allThreadOrder) {
25947
26202
  const threadResult = output.results.get(threadId);
25948
26203
  if (!threadResult) {
@@ -25953,7 +26208,7 @@ var ObserverRunner = class {
25953
26208
  }
25954
26209
  return output;
25955
26210
  }
25956
- async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model) {
26211
+ async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone) {
25957
26212
  if (threadOrder.length === 0) return { results: /* @__PURE__ */ new Map() };
25958
26213
  const inputTokens = Array.from(messagesByThread.values()).reduce((total, messages) => total + this.tokenCounter.countMessages(messages), 0);
25959
26214
  const resolvedModel = (() => {
@@ -25995,7 +26250,8 @@ var ObserverRunner = class {
25995
26250
  priorSuggestedResponse: priorMetadataByThread?.get(threadId)?.suggestedResponse,
25996
26251
  priorThreadTitle: priorMetadataByThread?.get(threadId)?.threadTitle,
25997
26252
  priorExtractedValues: priorMetadataByThread?.get(threadId)?.extracted,
25998
- model: resolvedModel.model
26253
+ model: resolvedModel.model,
26254
+ timeZone
25999
26255
  });
26000
26256
  results.set(threadId, {
26001
26257
  observations: threadResult.observations,
@@ -26012,6 +26268,7 @@ var ObserverRunner = class {
26012
26268
  totalUsage.totalTokens += threadResult.usage.totalTokens ?? 0;
26013
26269
  }
26014
26270
  }
26271
+ for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
26015
26272
  return {
26016
26273
  results,
26017
26274
  usage: totalUsage
@@ -26020,8 +26277,10 @@ var ObserverRunner = class {
26020
26277
  const agent = this.createAgent(resolvedModel.model, true, void 0, activeExtractors);
26021
26278
  const internalRequestContext = withOmInternalThreadId(requestContext, agent.id);
26022
26279
  const multiThreadAttachmentFilter = this.resolveAttachmentFilter(resolvedModel.model, requestContext);
26023
- const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, void 0, this.observationConfig.threadTitle, activeExtractors, { attachmentFilter: multiThreadAttachmentFilter })];
26024
- for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
26280
+ const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, void 0, this.observationConfig.threadTitle, activeExtractors, {
26281
+ attachmentFilter: multiThreadAttachmentFilter,
26282
+ timeZone
26283
+ })];
26025
26284
  const doGenerate = async () => {
26026
26285
  return withRetry(() => withOmTracingSpan({
26027
26286
  phase: "observer-multi-thread",
@@ -26059,12 +26318,14 @@ var ObserverRunner = class {
26059
26318
  hasRequestContext: Boolean(internalRequestContext),
26060
26319
  aborted: abortSignal?.aborted ?? false
26061
26320
  });
26062
- throw error;
26321
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
26322
+ throw new OmModelExecutionError("observer-model", error);
26063
26323
  }
26064
26324
  }, abortSignal)
26065
26325
  }), {
26066
26326
  label: "observer-multi-thread",
26067
- abortSignal
26327
+ abortSignal,
26328
+ maxRetries: this.observationConfig.maxRetries
26068
26329
  });
26069
26330
  };
26070
26331
  let result = await doGenerate();
@@ -26120,6 +26381,7 @@ var ObserverRunner = class {
26120
26381
  });
26121
26382
  }
26122
26383
  for (const threadId of threadOrder) if (!results.has(threadId)) results.set(threadId, { observations: "" });
26384
+ for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
26123
26385
  const usage = result.totalUsage ?? result.usage;
26124
26386
  return {
26125
26387
  results,
@@ -27607,6 +27869,8 @@ async function summarizeConversation(opts) {
27607
27869
  const runner = new ObserverRunner({
27608
27870
  observationConfig: {
27609
27871
  model: opts.model,
27872
+ maxRetries: 8,
27873
+ failurePolicy: "abort",
27610
27874
  messageTokens: OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens,
27611
27875
  shareTokenBudget: false,
27612
27876
  modelSettings: { ...OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings },
@@ -29417,6 +29681,7 @@ function createObservationFailedMarker(params) {
29417
29681
  durationMs,
29418
29682
  tokensAttempted: params.tokensAttempted,
29419
29683
  error: formatOmError(params.error),
29684
+ ...getOmFailureMetadata(params.error, params.failurePolicy ?? "abort"),
29420
29685
  recordId: params.recordId,
29421
29686
  threadId: params.threadId
29422
29687
  }
@@ -29478,6 +29743,7 @@ function createBufferingFailedMarker(params) {
29478
29743
  durationMs,
29479
29744
  tokensAttempted: params.tokensAttempted,
29480
29745
  error: formatOmError(params.error),
29746
+ ...getOmFailureMetadata(params.error, params.failurePolicy ?? "abort"),
29481
29747
  recordId: params.recordId,
29482
29748
  threadId: params.threadId
29483
29749
  }
@@ -29532,7 +29798,10 @@ function createThreadUpdateMarker(params) {
29532
29798
  function findLastCompletedObservationBoundary(message) {
29533
29799
  const parts = message.content?.parts;
29534
29800
  if (!parts || !Array.isArray(parts)) return -1;
29535
- for (let i = parts.length - 1; i >= 0; i--) if (parts[i]?.type === "data-om-observation-end") return i;
29801
+ for (let i = parts.length - 1; i >= 0; i--) {
29802
+ const part = parts[i];
29803
+ if (part?.type === "data-om-observation-end" && part.data?.operationType !== "reflection") return i;
29804
+ }
29536
29805
  return -1;
29537
29806
  }
29538
29807
  /**
@@ -29782,6 +30051,7 @@ function scanObservationGroupTags(observations) {
29782
30051
  const incompleteOpenings = [];
29783
30052
  let cursor = 0;
29784
30053
  let closeFloor = 0;
30054
+ let nestedFloor = -1;
29785
30055
  while (cursor < observations.length) {
29786
30056
  const start = observations.indexOf(OBSERVATION_GROUP_OPEN, cursor);
29787
30057
  if (start === -1) break;
@@ -29802,8 +30072,12 @@ function scanObservationGroupTags(observations) {
29802
30072
  cursor = openEnd + 1;
29803
30073
  continue;
29804
30074
  }
29805
- const nestedStart = observations.indexOf(OBSERVATION_GROUP_OPEN, openEnd + 1);
29806
- if (nestedStart !== -1 && nestedStart < closeStart && isLineStart(observations, nestedStart)) {
30075
+ if (nestedFloor <= openEnd) {
30076
+ const lineOpen = observations.indexOf(`\n${OBSERVATION_GROUP_OPEN}`, openEnd);
30077
+ nestedFloor = lineOpen === -1 ? observations.length : lineOpen + 1;
30078
+ }
30079
+ const nestedStart = nestedFloor;
30080
+ if (nestedStart < closeStart) {
29807
30081
  incompleteOpenings.push({
29808
30082
  start,
29809
30083
  end: openEnd + 1
@@ -30186,6 +30460,7 @@ var ObservationStrategy = class ObservationStrategy {
30186
30460
  observationTokens: processed.observationTokens,
30187
30461
  threadId,
30188
30462
  writer,
30463
+ messageList: this.opts.messageList,
30189
30464
  abortSignal,
30190
30465
  mainAgent: this.opts.agent,
30191
30466
  sendSignal: this.opts.sendSignal,
@@ -30210,6 +30485,7 @@ var ObservationStrategy = class ObservationStrategy {
30210
30485
  operationType: "observation",
30211
30486
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
30212
30487
  error: formatOmError(error),
30488
+ ...getOmFailureMetadata(error, this.observationConfig.failurePolicy),
30213
30489
  recordId: record.id,
30214
30490
  threadId
30215
30491
  }
@@ -30223,6 +30499,10 @@ var ObservationStrategy = class ObservationStrategy {
30223
30499
  };
30224
30500
  }
30225
30501
  omError("[OM] Observation failed", error);
30502
+ if (this.observationConfig.failurePolicy === "continue" && isOmModelExecutionError(error) && error.failureKind === "observer-model") return {
30503
+ observed: false,
30504
+ error
30505
+ };
30226
30506
  throw error;
30227
30507
  }
30228
30508
  }
@@ -30488,7 +30768,8 @@ var SyncObservationStrategy = class extends ObservationStrategy {
30488
30768
  threadId: this.opts.threadId,
30489
30769
  resourceId: this.opts.resourceId,
30490
30770
  trigger: this.opts.trigger,
30491
- mainAgent: this.opts.agent
30771
+ mainAgent: this.opts.agent,
30772
+ timeZone: this.opts.record.observedTimezone
30492
30773
  });
30493
30774
  const hookedValues = await applyExtractorHooks({
30494
30775
  source: "observer",
@@ -30497,7 +30778,10 @@ var SyncObservationStrategy = class extends ObservationStrategy {
30497
30778
  failures: result.extractionFailures,
30498
30779
  previousValues: this.priorExtractedValues,
30499
30780
  rawObservations: result.observations,
30500
- recentMessages: formatMessagesForObserver(this.opts.messages, { maxPartLength: 500 }),
30781
+ recentMessages: formatMessagesForObserver(this.opts.messages, {
30782
+ maxPartLength: 500,
30783
+ timeZone: this.opts.record.observedTimezone
30784
+ }),
30501
30785
  threadId: this.opts.threadId,
30502
30786
  resourceId: this.opts.resourceId,
30503
30787
  mainAgent: this.opts.agent,
@@ -30628,6 +30912,7 @@ var SyncObservationStrategy = class extends ObservationStrategy {
30628
30912
  startedAt: this.startedAt,
30629
30913
  tokensAttempted: this.tokensToObserve,
30630
30914
  error,
30915
+ failurePolicy: this.observationConfig.failurePolicy,
30631
30916
  recordId: this.opts.record.id,
30632
30917
  threadId: this.opts.threadId
30633
30918
  });
@@ -30677,7 +30962,8 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
30677
30962
  threadId: this.opts.threadId,
30678
30963
  resourceId: this.opts.resourceId,
30679
30964
  trigger: this.opts.trigger,
30680
- mainAgent: this.opts.agent
30965
+ mainAgent: this.opts.agent,
30966
+ timeZone: this.opts.record.observedTimezone
30681
30967
  });
30682
30968
  const hookedValues = await applyExtractorHooks({
30683
30969
  source: "observer",
@@ -30686,7 +30972,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
30686
30972
  failures: result.extractionFailures,
30687
30973
  previousValues: this.priorExtractedValues,
30688
30974
  rawObservations: result.observations,
30689
- recentMessages: formatMessagesForObserver(messages, { maxPartLength: 500 }),
30975
+ recentMessages: formatMessagesForObserver(messages, {
30976
+ maxPartLength: 500,
30977
+ timeZone: this.opts.record.observedTimezone
30978
+ }),
30690
30979
  threadId: this.opts.threadId,
30691
30980
  resourceId: this.opts.resourceId,
30692
30981
  mainAgent: this.opts.agent,
@@ -30832,6 +31121,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
30832
31121
  startedAt: this.startedAt,
30833
31122
  tokensAttempted,
30834
31123
  error,
31124
+ failurePolicy: this.observationConfig.failurePolicy,
30835
31125
  recordId: record.id,
30836
31126
  threadId
30837
31127
  });
@@ -31006,7 +31296,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
31006
31296
  return this.deps.observer.callMultiThread(_existingObservations, batch.threadMap, batch.threadIds, this.opts.abortSignal, this.opts.requestContext, this.priorMetadataByThread, this.opts.observabilityContext, void 0, {
31007
31297
  resourceId: this.opts.resourceId,
31008
31298
  trigger: this.opts.trigger
31009
- });
31299
+ }, this.opts.record.observedTimezone);
31010
31300
  }));
31011
31301
  for (const batchResult of batchResults) {
31012
31302
  for (const [threadId, result] of batchResult.results) this.multiThreadResults.set(threadId, result);
@@ -31039,7 +31329,10 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
31039
31329
  failures: result.extractionFailures,
31040
31330
  previousValues,
31041
31331
  rawObservations: result.observations,
31042
- recentMessages: formatMessagesForObserver(threadMessages, { maxPartLength: 500 }),
31332
+ recentMessages: formatMessagesForObserver(threadMessages, {
31333
+ maxPartLength: 500,
31334
+ timeZone: this.opts.record.observedTimezone
31335
+ }),
31043
31336
  threadId,
31044
31337
  resourceId: this.resourceId,
31045
31338
  mainAgent: this.opts.agent,
@@ -31198,6 +31491,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
31198
31491
  startedAt: this.startedAt,
31199
31492
  tokensAttempted,
31200
31493
  error,
31494
+ failurePolicy: this.observationConfig.failurePolicy,
31201
31495
  recordId: this.opts.record.id,
31202
31496
  threadId
31203
31497
  });
@@ -31604,19 +31898,28 @@ var ObservationStep = class {
31604
31898
  };
31605
31899
  }
31606
31900
  }
31607
- const obsResult = await om.observe({
31608
- threadId,
31609
- resourceId,
31610
- messages: observableMessages,
31611
- messageList,
31612
- trigger: "turn-sync",
31613
- agent: this.turn.agent,
31614
- sendSignal: this.turn.sendSignal,
31615
- sendStateSignal: this.turn.sendStateSignal,
31616
- requestContext: this.turn.requestContext,
31617
- writer: this.turn.writer,
31618
- observabilityContext: this.turn.observabilityContext
31619
- });
31901
+ let obsResult;
31902
+ try {
31903
+ obsResult = await om.observe({
31904
+ threadId,
31905
+ resourceId,
31906
+ messages: observableMessages,
31907
+ messageList,
31908
+ trigger: "turn-sync",
31909
+ agent: this.turn.agent,
31910
+ sendSignal: this.turn.sendSignal,
31911
+ sendStateSignal: this.turn.sendStateSignal,
31912
+ requestContext: this.turn.requestContext,
31913
+ writer: this.turn.writer,
31914
+ observabilityContext: this.turn.observabilityContext
31915
+ });
31916
+ } catch (error) {
31917
+ if (om.config.observation.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "observer-model") throw error;
31918
+ return {
31919
+ succeeded: false,
31920
+ record: freshStatus.record
31921
+ };
31922
+ }
31620
31923
  if (obsResult.observed) {
31621
31924
  const observedMessageIds = new Set(obsResult.record.observedMessageIds ?? []);
31622
31925
  const liveMessages = getObservableMessages(messageList);
@@ -32268,11 +32571,27 @@ var ReflectorRunner = class {
32268
32571
  this.mastra = mastra;
32269
32572
  }
32270
32573
  createAgent(model, memory, extractors = this.reflectionConfig.extractors) {
32574
+ let agentModel = model;
32575
+ if (Array.isArray(agentModel)) agentModel = agentModel.map((fallback) => ({
32576
+ ...fallback,
32577
+ maxRetries: 0
32578
+ }));
32579
+ else if (typeof agentModel === "function") {
32580
+ const resolveDynamicModel = agentModel;
32581
+ agentModel = (async (args) => {
32582
+ const resolvedModel = await resolveDynamicModel(args);
32583
+ return Array.isArray(resolvedModel) ? resolvedModel.map((fallback) => ({
32584
+ ...fallback,
32585
+ maxRetries: 0
32586
+ })) : resolvedModel;
32587
+ });
32588
+ }
32271
32589
  return new _mastra_core_agent.Agent({
32272
32590
  id: "observational-memory-reflector",
32273
32591
  name: "Reflector",
32274
32592
  instructions: buildReflectorSystemPrompt(this.reflectionConfig.instruction, extractors),
32275
- model,
32593
+ model: agentModel,
32594
+ maxRetries: 0,
32276
32595
  ...memory ? { memory } : {},
32277
32596
  ...this.mastra ? { mastra: this.mastra } : {}
32278
32597
  });
@@ -32354,36 +32673,42 @@ var ReflectorRunner = class {
32354
32673
  },
32355
32674
  callback: (childObservabilityContext) => withAbortCheck(async () => {
32356
32675
  chunkCount = 0;
32357
- return (await agent.stream(prompt, {
32358
- modelSettings: { ...this.reflectionConfig.modelSettings },
32359
- providerOptions: this.reflectionConfig.providerOptions,
32360
- ...temporaryMemory ? { memory: temporaryMemory.options } : {},
32361
- ...abortSignal ? { abortSignal } : {},
32362
- ...internalRequestContext ? { requestContext: internalRequestContext } : {},
32363
- ...childObservabilityContext,
32364
- ...attemptNumber === 1 ? {
32365
- onChunk(chunk) {
32366
- chunkCount++;
32367
- if (chunkCount === 1 || chunkCount % 50 === 0) {
32368
- const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
32369
- omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
32676
+ try {
32677
+ return await (await agent.stream(prompt, {
32678
+ modelSettings: { ...this.reflectionConfig.modelSettings },
32679
+ providerOptions: this.reflectionConfig.providerOptions,
32680
+ ...temporaryMemory ? { memory: temporaryMemory.options } : {},
32681
+ ...abortSignal ? { abortSignal } : {},
32682
+ ...internalRequestContext ? { requestContext: internalRequestContext } : {},
32683
+ ...childObservabilityContext,
32684
+ ...attemptNumber === 1 ? {
32685
+ onChunk(chunk) {
32686
+ chunkCount++;
32687
+ if (chunkCount === 1 || chunkCount % 50 === 0) {
32688
+ const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
32689
+ omDebug(`[OM:callReflector] chunk#${chunkCount}: type=${chunk.type}${preview}`);
32690
+ }
32691
+ },
32692
+ onFinish(event) {
32693
+ omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
32694
+ },
32695
+ onAbort(event) {
32696
+ omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
32697
+ },
32698
+ onError({ error }) {
32699
+ omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
32370
32700
  }
32371
- },
32372
- onFinish(event) {
32373
- omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
32374
- },
32375
- onAbort(event) {
32376
- omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
32377
- },
32378
- onError({ error }) {
32379
- omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
32380
- }
32381
- } : {}
32382
- })).getFullOutput();
32701
+ } : {}
32702
+ })).getFullOutput();
32703
+ } catch (error) {
32704
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
32705
+ throw new OmModelExecutionError("reflector-model", error);
32706
+ }
32383
32707
  }, abortSignal)
32384
32708
  }), {
32385
32709
  label: "reflector",
32386
- abortSignal
32710
+ abortSignal,
32711
+ maxRetries: this.reflectionConfig.maxRetries
32387
32712
  });
32388
32713
  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}`);
32389
32714
  const usage = result.totalUsage ?? result.usage;
@@ -32507,6 +32832,7 @@ var ReflectorRunner = class {
32507
32832
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
32508
32833
  tokensAttempted: observationTokens,
32509
32834
  error,
32835
+ failurePolicy: this.reflectionConfig.failurePolicy,
32510
32836
  recordId: record.id,
32511
32837
  threadId: record.threadId ?? ""
32512
32838
  });
@@ -32919,24 +33245,43 @@ var ReflectorRunner = class {
32919
33245
  usage: reflectResult.usage
32920
33246
  });
32921
33247
  } catch (error) {
32922
- if (writer && streamContext) {
32923
- const failedMarker = createObservationFailedMarker({
32924
- cycleId: streamContext.cycleId,
32925
- operationType: "reflection",
32926
- startedAt: streamContext.startedAt,
32927
- tokensAttempted: observationTokens,
32928
- error,
32929
- recordId: record.id,
32930
- threadId
32931
- });
32932
- await writer.custom({
32933
- ...failedMarker,
32934
- transient: true
32935
- }).catch(() => {});
33248
+ reflectionError = error instanceof Error ? error : new Error(String(error));
33249
+ const failedMarker = createObservationFailedMarker({
33250
+ cycleId: streamContext?.cycleId ?? cycleId,
33251
+ operationType: "reflection",
33252
+ startedAt: streamContext?.startedAt ?? startedAt,
33253
+ tokensAttempted: observationTokens,
33254
+ error,
33255
+ failurePolicy: this.reflectionConfig.failurePolicy,
33256
+ recordId: record.id,
33257
+ threadId
33258
+ });
33259
+ if (writer) await writer.custom({
33260
+ ...failedMarker,
33261
+ transient: true
33262
+ }).catch(() => {});
33263
+ let persistedToList = false;
33264
+ try {
33265
+ persistedToList = await this.persistMarkerToMessage(failedMarker, messageList, threadId, record.resourceId ?? void 0);
33266
+ } catch {
33267
+ persistedToList = false;
33268
+ }
33269
+ if (!persistedToList) try {
32936
33270
  await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0);
33271
+ } catch (markerError) {
33272
+ omError("[OM] Failed to persist reflection-failed marker", markerError);
32937
33273
  }
32938
- reflectionError = error instanceof Error ? error : new Error(String(error));
32939
- if (lifecycleError !== void 0 || abortSignal?.aborted) throw error;
33274
+ this.emitDebugEvent({
33275
+ type: "reflection_failed",
33276
+ timestamp: /* @__PURE__ */ new Date(),
33277
+ threadId,
33278
+ resourceId: record.resourceId ?? "",
33279
+ inputTokens: observationTokens,
33280
+ failurePolicy: this.reflectionConfig.failurePolicy,
33281
+ failureKind: failedMarker.data.failureKind,
33282
+ error: reflectionError.message
33283
+ });
33284
+ if (lifecycleError !== void 0 || abortSignal?.aborted || this.reflectionConfig.failurePolicy !== "continue" || !isOmModelExecutionError(error) || error.failureKind !== "reflector-model") throw error;
32940
33285
  this.syncReflectionSuppression.set(lockKey, observationTokens);
32941
33286
  omError("[OM] Reflection failed", error);
32942
33287
  } finally {
@@ -33570,6 +33915,8 @@ var ObservationalMemory = class ObservationalMemory {
33570
33915
  const observationActivateAfterIdlePath = config.observation?.activateAfterIdle !== void 0 ? "observation.activateAfterIdle" : "activateAfterIdle";
33571
33916
  this.observationConfig = {
33572
33917
  model: observationModel,
33918
+ maxRetries: config.observation?.maxRetries ?? RETRY_CONFIG.maxRetries,
33919
+ failurePolicy: config.observation?.failurePolicy ?? "abort",
33573
33920
  messageTokens: isSharedBudget ? {
33574
33921
  min: messageTokens,
33575
33922
  max: totalBudget
@@ -33599,6 +33946,8 @@ var ObservationalMemory = class ObservationalMemory {
33599
33946
  };
33600
33947
  this.reflectionConfig = {
33601
33948
  model: reflectionModel,
33949
+ maxRetries: config.reflection?.maxRetries ?? RETRY_CONFIG.maxRetries,
33950
+ failurePolicy: config.reflection?.failurePolicy ?? "abort",
33602
33951
  observationTokens,
33603
33952
  shareTokenBudget: isSharedBudget,
33604
33953
  modelSettings: {
@@ -33672,9 +34021,15 @@ var ObservationalMemory = class ObservationalMemory {
33672
34021
  retrieval: this.retrieval,
33673
34022
  observation: {
33674
34023
  messageTokens: this.observationConfig.messageTokens,
33675
- previousObserverTokens: this.observationConfig.previousObserverTokens
34024
+ previousObserverTokens: this.observationConfig.previousObserverTokens,
34025
+ maxRetries: this.observationConfig.maxRetries,
34026
+ failurePolicy: this.observationConfig.failurePolicy
33676
34027
  },
33677
- reflection: { observationTokens: this.reflectionConfig.observationTokens }
34028
+ reflection: {
34029
+ observationTokens: this.reflectionConfig.observationTokens,
34030
+ maxRetries: this.reflectionConfig.maxRetries,
34031
+ failurePolicy: this.reflectionConfig.failurePolicy
34032
+ }
33678
34033
  };
33679
34034
  }
33680
34035
  /**
@@ -33778,11 +34133,15 @@ var ObservationalMemory = class ObservationalMemory {
33778
34133
  messageTokens: this.observationConfig.messageTokens,
33779
34134
  model: observationResolved.model,
33780
34135
  previousObserverTokens: this.observationConfig.previousObserverTokens,
34136
+ maxRetries: this.observationConfig.maxRetries,
34137
+ failurePolicy: this.observationConfig.failurePolicy,
33781
34138
  routing: observationResolved.routing
33782
34139
  },
33783
34140
  reflection: {
33784
34141
  observationTokens: this.reflectionConfig.observationTokens,
33785
34142
  model: reflectionResolved.model,
34143
+ maxRetries: this.reflectionConfig.maxRetries,
34144
+ failurePolicy: this.reflectionConfig.failurePolicy,
33786
34145
  routing: reflectionResolved.routing
33787
34146
  }
33788
34147
  };
@@ -33799,6 +34158,7 @@ var ObservationalMemory = class ObservationalMemory {
33799
34158
  * Ensures bufferTokens is less than the threshold and bufferActivation is valid.
33800
34159
  */
33801
34160
  validateBufferConfig() {
34161
+ for (const [path$8, value] of [["observation.maxRetries", this.observationConfig.maxRetries], ["reflection.maxRetries", this.reflectionConfig.maxRetries]]) if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${path$8} must be a finite non-negative integer, got ${value}`);
33802
34162
  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.");
33803
34163
  const observationThreshold = getMaxThreshold(this.observationConfig.messageTokens);
33804
34164
  if (this.observationConfig.bufferTokens !== void 0) {
@@ -33926,18 +34286,19 @@ var ObservationalMemory = class ObservationalMemory {
33926
34286
  };
33927
34287
  }
33928
34288
  /**
33929
- * Persist a data-om-* marker part on the last assistant message in messageList
33930
- * AND save the updated message to the DB so it survives page reload.
33931
- * (data-* parts are filtered out before sending to the LLM, so they don't affect model calls.)
34289
+ * Persist a data-om-* marker part on the message that owns the operation in
34290
+ * messageList and save it to the DB. Observation markers belong to the latest
34291
+ * assistant message; reflection markers belong to the current user input.
33932
34292
  * @internal Used by ReflectorRunner. Do not call directly.
33933
34293
  */
33934
34294
  async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
33935
- if (!messageList) return;
33936
- const allMsgs = getObservableMessages(messageList);
34295
+ if (!messageList) return false;
34296
+ const allMsgs = messageList.get.all.db();
34297
+ const markerData = marker.data;
34298
+ const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
33937
34299
  for (let i = allMsgs.length - 1; i >= 0; i--) {
33938
34300
  const msg = allMsgs[i];
33939
- if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
33940
- const markerData = marker.data;
34301
+ if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
33941
34302
  if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
33942
34303
  try {
33943
34304
  await this.messageHistory.persistMessages({
@@ -33947,16 +34308,19 @@ var ObservationalMemory = class ObservationalMemory {
33947
34308
  });
33948
34309
  } catch (e) {
33949
34310
  omDebug(`[OM:persistMarker] failed to save marker to DB: ${e}`);
34311
+ return false;
33950
34312
  }
33951
- return;
34313
+ return true;
33952
34314
  }
33953
34315
  }
34316
+ return false;
33954
34317
  }
33955
34318
  /**
33956
- * Persist a marker to the last assistant message in storage.
33957
- * Unlike persistMarkerToMessage, this fetches messages directly from the DB
33958
- * so it works even when no MessageList is available (e.g. async buffering ops).
33959
- * @internal Used by observation strategies. Do not call directly.
34319
+ * Persist a marker to the message that owns the operation in storage.
34320
+ * Observation markers belong to the latest assistant message. Reflection runs
34321
+ * before the current assistant reply exists, so its failure marker belongs to
34322
+ * the latest user input that triggered it.
34323
+ * @internal Used by observation strategies and the reflector. Do not call directly.
33960
34324
  */
33961
34325
  async persistMarkerToStorage(marker, threadId, resourceId) {
33962
34326
  try {
@@ -33968,8 +34332,9 @@ var ObservationalMemory = class ObservationalMemory {
33968
34332
  direction: "DESC"
33969
34333
  }
33970
34334
  }))?.messages ?? [];
33971
- for (const msg of messages) if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
33972
- const markerData = marker.data;
34335
+ const markerData = marker.data;
34336
+ const targetRole = markerData?.operationType === "reflection" ? "user" : "assistant";
34337
+ for (const msg of messages) if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
33973
34338
  if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
33974
34339
  await this.messageHistory.persistMessages({
33975
34340
  messages: [msg],
@@ -34211,9 +34576,9 @@ var ObservationalMemory = class ObservationalMemory {
34211
34576
  * In resource scope mode, filters continuity messages to only show
34212
34577
  * the message for the current thread.
34213
34578
  */
34214
- formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false) {
34579
+ formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false, timeZone) {
34215
34580
  let optimized = retrieval ? renderObservationGroupsForReflection(observations) ?? optimizeObservationsForContext(observations) : optimizeObservationsForContext(observations);
34216
- if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate);
34581
+ if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate, timeZone);
34217
34582
  const messages = [`${getObservationContextPrompt(this.scope)}\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}${retrieval ? `\n\n${getRetrievalInstructions(this.retrievalScope, this.retrievalInstructions, this.retrievalSearch)}` : ""}`];
34218
34583
  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`);
34219
34584
  const observationChunks = this.splitObservationContextChunks(optimized);
@@ -34313,12 +34678,15 @@ var ObservationalMemory = class ObservationalMemory {
34313
34678
  * These are injected into the Actor's context so it has awareness of activity
34314
34679
  * in other threads for the same resource.
34315
34680
  */
34316
- async formatUnobservedContextBlocks(messagesByThread, currentThreadId) {
34681
+ async formatUnobservedContextBlocks(messagesByThread, currentThreadId, timeZone) {
34317
34682
  const blocks = [];
34318
34683
  for (const [threadId, messages] of messagesByThread) {
34319
34684
  if (threadId === currentThreadId) continue;
34320
34685
  if (messages.length === 0) continue;
34321
- const formattedMessages = formatMessagesForObserver(messages, { maxPartLength: 500 });
34686
+ const formattedMessages = formatMessagesForObserver(messages, {
34687
+ maxPartLength: 500,
34688
+ timeZone
34689
+ });
34322
34690
  if (formattedMessages) {
34323
34691
  const obscuredId = await this.representThreadIDInContext(threadId);
34324
34692
  blocks.push(`<other-conversation id="${obscuredId}">
@@ -34495,7 +34863,7 @@ ${formattedMessages}
34495
34863
  transient: true
34496
34864
  }).catch(() => {});
34497
34865
  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(",")}`);
34498
- await this.runBufferedObservationCycle({
34866
+ if ((await this.runBufferedObservationCycle({
34499
34867
  threadId,
34500
34868
  resourceId: freshRecord.resourceId ?? void 0,
34501
34869
  trigger: "async-buffer"
@@ -34510,10 +34878,11 @@ ${formattedMessages}
34510
34878
  requestContext,
34511
34879
  observabilityContext,
34512
34880
  trigger: "async-buffer"
34513
- }).run());
34514
- const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
34515
- const cursor = new Date(maxTs.getTime() + 1);
34516
- BufferingCoordinator.lastBufferedAtTime.set(bufferKey, cursor);
34881
+ }).run()))?.observed) {
34882
+ const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
34883
+ const cursor = new Date(maxTs.getTime() + 1);
34884
+ BufferingCoordinator.lastBufferedAtTime.set(bufferKey, cursor);
34885
+ }
34517
34886
  }
34518
34887
  /**
34519
34888
  * Trigger async buffered observation if the token count has crossed a new interval.
@@ -34741,7 +35110,7 @@ ${formattedMessages}
34741
35110
  const currentTask = activeExtractors.some((extractor) => extractor.slug === "current-task") ? omMetadata?.currentTask : void 0;
34742
35111
  const suggestedResponse = activeExtractors.some((extractor) => extractor.slug === "suggested-response") ? omMetadata?.suggestedResponse : void 0;
34743
35112
  const currentDate = opts.currentDate ?? /* @__PURE__ */ new Date();
34744
- return this.formatObservationsForContext(record.activeObservations, currentTask, suggestedResponse, omMetadata?.extracted, unobservedContextBlocks, currentDate, this.retrieval);
35113
+ return this.formatObservationsForContext(record.activeObservations, currentTask, suggestedResponse, omMetadata?.extracted, unobservedContextBlocks, currentDate, this.retrieval, record.observedTimezone);
34745
35114
  }
34746
35115
  /**
34747
35116
  * Get unobserved messages from other threads for resource-scoped observation.
@@ -34753,7 +35122,8 @@ ${formattedMessages}
34753
35122
  async getOtherThreadsContext(resourceId, currentThreadId) {
34754
35123
  const { threads: allThreads } = await this.storage.listThreads({ filter: { resourceId } });
34755
35124
  const messagesByThread = /* @__PURE__ */ new Map();
34756
- const recordLastObservedAt = (await this.getRecord(currentThreadId, resourceId))?.lastObservedAt;
35125
+ const record = await this.getRecord(currentThreadId, resourceId);
35126
+ const recordLastObservedAt = record?.lastObservedAt;
34757
35127
  for (const thread of allThreads) {
34758
35128
  if (thread.id === currentThreadId) continue;
34759
35129
  const threadLastObservedAt = (0, _mastra_core_memory.getThreadOMMetadata)(thread.metadata)?.lastObservedAt ?? recordLastObservedAt;
@@ -34770,7 +35140,7 @@ ${formattedMessages}
34770
35140
  if (filtered.length > 0) messagesByThread.set(thread.id, filtered);
34771
35141
  }
34772
35142
  if (messagesByThread.size === 0) return void 0;
34773
- return await this.formatUnobservedContextBlocks(messagesByThread, currentThreadId) || void 0;
35143
+ return await this.formatUnobservedContextBlocks(messagesByThread, currentThreadId, record?.observedTimezone) || void 0;
34774
35144
  }
34775
35145
  /**
34776
35146
  * Emit debug event and stream progress for UI feedback.
@@ -35113,7 +35483,7 @@ ${formattedMessages}
35113
35483
  ...startMarker,
35114
35484
  transient: true
35115
35485
  }).catch(() => {});
35116
- await this.runBufferedObservationCycle({
35486
+ if (!(await this.runBufferedObservationCycle({
35117
35487
  threadId,
35118
35488
  resourceId: record.resourceId ?? resourceId,
35119
35489
  trigger: "async-buffer"
@@ -35132,7 +35502,10 @@ ${formattedMessages}
35132
35502
  currentModel: opts.currentModel,
35133
35503
  observabilityContext,
35134
35504
  trigger: "async-buffer"
35135
- }).run());
35505
+ }).run()))?.observed) return {
35506
+ buffered: false,
35507
+ record
35508
+ };
35136
35509
  if (isOmReproCaptureEnabled()) writeObserverExchangeReproCapture({
35137
35510
  threadId,
35138
35511
  resourceId: record.resourceId ?? void 0,
@@ -35492,6 +35865,7 @@ ${formattedMessages}
35492
35865
  observed = result.observed;
35493
35866
  observationUsage = result.usage;
35494
35867
  observationProviderMetadata = result.providerMetadata;
35868
+ observationError = result.error;
35495
35869
  });
35496
35870
  } catch (error) {
35497
35871
  lifecycleError = error;
@@ -37402,6 +37776,7 @@ ${workingMemory}`;
37402
37776
  model: omConfig.model,
37403
37777
  mastra: this._mastraInstance,
37404
37778
  onIndexObservations,
37779
+ onDebugEvent: omConfig.onDebugEvent,
37405
37780
  hooks: omConfig.hooks,
37406
37781
  observation: omConfig.observation ? {
37407
37782
  model: omConfig.observation.model,
@@ -37418,10 +37793,14 @@ ${workingMemory}`;
37418
37793
  threadTitle: omConfig.observation.threadTitle,
37419
37794
  observeAttachments: omConfig.observation.observeAttachments,
37420
37795
  continuationHints: omConfig.observation.continuationHints,
37796
+ maxRetries: omConfig.observation.maxRetries,
37797
+ failurePolicy: omConfig.observation.failurePolicy,
37421
37798
  extract: omConfig.observation.extract
37422
37799
  } : void 0,
37423
37800
  reflection: omConfig.reflection ? {
37424
37801
  model: omConfig.reflection.model,
37802
+ maxRetries: omConfig.reflection.maxRetries,
37803
+ failurePolicy: omConfig.reflection.failurePolicy,
37425
37804
  observationTokens: omConfig.reflection.observationTokens,
37426
37805
  modelSettings: omConfig.reflection.modelSettings,
37427
37806
  providerOptions: omConfig.reflection.providerOptions,
@@ -49274,4 +49653,4 @@ const agentBuilderWorkflows = {
49274
49653
  //#endregion
49275
49654
  exports.agentBuilderWorkflows = agentBuilderWorkflows;
49276
49655
 
49277
- //# sourceMappingURL=dist-Cfr6t0cK.cjs.map
49656
+ //# sourceMappingURL=dist-_n0Mp6PP.cjs.map