@mastra/server 1.72.0-alpha.0 → 1.72.0-alpha.10

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 (142) 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-Maxewnw7.cjs → api-schema-manifest-CaZU5zVt.cjs} +2 -2
  10. package/dist/{api-schema-manifest-Maxewnw7.cjs.map → api-schema-manifest-CaZU5zVt.cjs.map} +1 -1
  11. package/dist/{api-schema-manifest-DJiUahwo.js → api-schema-manifest-wF1O8MCU.js} +2 -2
  12. package/dist/{api-schema-manifest-DJiUahwo.js.map → api-schema-manifest-wF1O8MCU.js.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-Cfr6t0cK.cjs → dist-CRMvLG_8.cjs} +746 -366
  18. package/dist/dist-CRMvLG_8.cjs.map +1 -0
  19. package/dist/{dist-ABdCIqbD.js → dist-CYk5xS3y.js} +746 -366
  20. package/dist/dist-CYk5xS3y.js.map +1 -0
  21. package/dist/docs/SKILL.md +1 -1
  22. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  23. package/dist/docs/references/reference-observability-tracing-trace-query.md +37 -16
  24. package/dist/{editor-builder-BrkDnCEd.js → editor-builder-DJMAKDR5.js} +3 -2
  25. package/dist/editor-builder-DJMAKDR5.js.map +1 -0
  26. package/dist/{editor-builder-DIpISGSI.cjs → editor-builder-DrDs-t9i.cjs} +3 -2
  27. package/dist/editor-builder-DrDs-t9i.cjs.map +1 -0
  28. package/dist/{observability-new-endpoints-Ct0CSLE4.cjs → observability-new-endpoints-7Zd6uEZf.cjs} +111 -3
  29. package/dist/observability-new-endpoints-7Zd6uEZf.cjs.map +1 -0
  30. package/dist/{observability-new-endpoints-DqS6dcR5.js → observability-new-endpoints-lNC3o66-.js} +107 -5
  31. package/dist/observability-new-endpoints-lNC3o66-.js.map +1 -0
  32. package/dist/route-builder-2yrD15TL.cjs.map +1 -1
  33. package/dist/route-builder-lBil8Cvo.js.map +1 -1
  34. package/dist/{routes-D6sAEgtj.js → routes-B6soWZap.js} +6 -5
  35. package/dist/routes-B6soWZap.js.map +1 -0
  36. package/dist/{routes-C9CmwhbC.cjs → routes-x4As7pym.cjs} +6 -5
  37. package/dist/routes-x4As7pym.cjs.map +1 -0
  38. package/dist/schema-e0VHpMt0-BS9KHnuT.cjs.map +1 -1
  39. package/dist/schema-e0VHpMt0-CvPvLS06.js.map +1 -1
  40. package/dist/server/handlers/a2a.cjs +1 -1
  41. package/dist/server/handlers/a2a.d.ts.map +1 -1
  42. package/dist/server/handlers/a2a.js +1 -1
  43. package/dist/server/handlers/agent-builder.cjs +3 -3
  44. package/dist/server/handlers/agent-builder.d.ts +4 -2
  45. package/dist/server/handlers/agent-builder.d.ts.map +1 -1
  46. package/dist/server/handlers/agent-builder.js +3 -3
  47. package/dist/server/handlers/agent-controller.cjs +2 -1
  48. package/dist/server/handlers/agent-controller.cjs.map +1 -1
  49. package/dist/server/handlers/agent-controller.d.ts.map +1 -1
  50. package/dist/server/handlers/agent-controller.js +2 -1
  51. package/dist/server/handlers/agent-controller.js.map +1 -1
  52. package/dist/server/handlers/agents.cjs +49 -20
  53. package/dist/server/handlers/agents.cjs.map +1 -1
  54. package/dist/server/handlers/agents.d.ts.map +1 -1
  55. package/dist/server/handlers/agents.js +49 -20
  56. package/dist/server/handlers/agents.js.map +1 -1
  57. package/dist/server/handlers/channels.cjs +1 -1
  58. package/dist/server/handlers/channels.d.ts.map +1 -1
  59. package/dist/server/handlers/channels.js +1 -1
  60. package/dist/server/handlers/editor-builder.cjs +1 -1
  61. package/dist/server/handlers/editor-builder.d.ts.map +1 -1
  62. package/dist/server/handlers/editor-builder.js +1 -1
  63. package/dist/server/handlers/favorites-enrichment.cjs +1 -1
  64. package/dist/server/handlers/favorites-enrichment.js +1 -1
  65. package/dist/server/handlers/observability-new-endpoints.cjs +2 -1
  66. package/dist/server/handlers/observability-new-endpoints.d.ts +100 -0
  67. package/dist/server/handlers/observability-new-endpoints.d.ts.map +1 -1
  68. package/dist/server/handlers/observability-new-endpoints.js +2 -2
  69. package/dist/server/handlers/observability-shared.cjs +46 -0
  70. package/dist/server/handlers/observability-shared.cjs.map +1 -1
  71. package/dist/server/handlers/observability-shared.d.ts +13 -0
  72. package/dist/server/handlers/observability-shared.d.ts.map +1 -1
  73. package/dist/server/handlers/observability-shared.js +42 -1
  74. package/dist/server/handlers/observability-shared.js.map +1 -1
  75. package/dist/server/handlers/observability.cjs +3 -1
  76. package/dist/server/handlers/observability.cjs.map +1 -1
  77. package/dist/server/handlers/observability.js +3 -2
  78. package/dist/server/handlers/observability.js.map +1 -1
  79. package/dist/server/handlers/stored-agent-favorites.cjs +1 -1
  80. package/dist/server/handlers/stored-agent-favorites.js +1 -1
  81. package/dist/server/handlers/stored-agents.cjs +6 -3
  82. package/dist/server/handlers/stored-agents.cjs.map +1 -1
  83. package/dist/server/handlers/stored-agents.d.ts.map +1 -1
  84. package/dist/server/handlers/stored-agents.js +6 -3
  85. package/dist/server/handlers/stored-agents.js.map +1 -1
  86. package/dist/server/handlers/stored-skill-favorites.cjs +1 -1
  87. package/dist/server/handlers/stored-skill-favorites.js +1 -1
  88. package/dist/server/handlers/stored-skills.cjs +1 -1
  89. package/dist/server/handlers/stored-skills.js +1 -1
  90. package/dist/server/handlers/system.cjs +2 -2
  91. package/dist/server/handlers/system.d.ts +2 -0
  92. package/dist/server/handlers/system.d.ts.map +1 -1
  93. package/dist/server/handlers/system.js +2 -2
  94. package/dist/server/handlers/workflows.cjs +28 -4
  95. package/dist/server/handlers/workflows.cjs.map +1 -1
  96. package/dist/server/handlers/workflows.d.ts +5 -2
  97. package/dist/server/handlers/workflows.d.ts.map +1 -1
  98. package/dist/server/handlers/workflows.js +28 -4
  99. package/dist/server/handlers/workflows.js.map +1 -1
  100. package/dist/server/handlers.cjs +1 -1
  101. package/dist/server/handlers.js +1 -1
  102. package/dist/server/schemas/index.cjs +3 -3
  103. package/dist/server/schemas/index.js +3 -3
  104. package/dist/server/schemas/system.d.ts +2 -0
  105. package/dist/server/schemas/system.d.ts.map +1 -1
  106. package/dist/server/schemas/workflows.d.ts +2 -1
  107. package/dist/server/schemas/workflows.d.ts.map +1 -1
  108. package/dist/server/server-adapter/index.cjs +1 -1
  109. package/dist/server/server-adapter/index.js +1 -1
  110. package/dist/server/server-adapter/routes/agent-builder.d.ts +4 -2
  111. package/dist/server/server-adapter/routes/agent-builder.d.ts.map +1 -1
  112. package/dist/server/server-adapter/routes/observability.d.ts +97 -0
  113. package/dist/server/server-adapter/routes/observability.d.ts.map +1 -1
  114. package/dist/server/server-adapter/routes/system.d.ts +2 -0
  115. package/dist/server/server-adapter/routes/system.d.ts.map +1 -1
  116. package/dist/server/server-adapter/routes/workflows.d.ts +4 -2
  117. package/dist/server/server-adapter/routes/workflows.d.ts.map +1 -1
  118. package/dist/{system-BE5tDMHq.js → system-6qFm79mm.js} +2 -1
  119. package/dist/system-6qFm79mm.js.map +1 -0
  120. package/dist/{system-BlGYb2fu.cjs → system-CskEzH1o.cjs} +2 -1
  121. package/dist/system-CskEzH1o.cjs.map +1 -0
  122. package/dist/{workflows-CvJ4c4_p.js → workflows-BEdLrZQZ.js} +3 -2
  123. package/dist/workflows-BEdLrZQZ.js.map +1 -0
  124. package/dist/{workflows-Cv6MGw9d.cjs → workflows-BdoErlkd.cjs} +3 -2
  125. package/dist/workflows-BdoErlkd.cjs.map +1 -0
  126. package/package.json +7 -7
  127. package/dist/a2a-CwxSk6H0.cjs.map +0 -1
  128. package/dist/a2a-DQOqoU_q.js.map +0 -1
  129. package/dist/channels-DMw4LEA-.cjs.map +0 -1
  130. package/dist/channels-xqChW6Vw.js.map +0 -1
  131. package/dist/dist-ABdCIqbD.js.map +0 -1
  132. package/dist/dist-Cfr6t0cK.cjs.map +0 -1
  133. package/dist/editor-builder-BrkDnCEd.js.map +0 -1
  134. package/dist/editor-builder-DIpISGSI.cjs.map +0 -1
  135. package/dist/observability-new-endpoints-Ct0CSLE4.cjs.map +0 -1
  136. package/dist/observability-new-endpoints-DqS6dcR5.js.map +0 -1
  137. package/dist/routes-C9CmwhbC.cjs.map +0 -1
  138. package/dist/routes-D6sAEgtj.js.map +0 -1
  139. package/dist/system-BE5tDMHq.js.map +0 -1
  140. package/dist/system-BlGYb2fu.cjs.map +0 -1
  141. package/dist/workflows-Cv6MGw9d.cjs.map +0 -1
  142. package/dist/workflows-CvJ4c4_p.js.map +0 -1
@@ -4637,7 +4637,7 @@ function splitLines(text) {
4637
4637
  return result;
4638
4638
  }
4639
4639
  //#endregion
4640
- //#region ../memory/dist/src-B-_pkR2t.js
4640
+ //#region ../memory/dist/src-BdfOygyn.js
4641
4641
  var __defProp$3 = Object.defineProperty;
4642
4642
  var __exportAll = (all, no_symbols) => {
4643
4643
  let target = {};
@@ -23223,9 +23223,224 @@ async function applyExtractorHooks(opts) {
23223
23223
  failures: mergeExtractionFailures(failures)
23224
23224
  };
23225
23225
  }
23226
+ /**
23227
+ * Retry knobs for the internal OM transport-error retry wrapper.
23228
+ * Exported as a mutable object so tests can shrink the backoff schedule
23229
+ * without changing public API.
23230
+ *
23231
+ * With the defaults the per-retry pre-jitter backoff schedule is:
23232
+ * 1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s (cap)
23233
+ * giving 8 retries / 9 total attempts and ~247s (~4 minutes) of waiting
23234
+ * before the final attempt fails. Designed to ride out short provider /
23235
+ * network blips without holding the actor turn for much longer than that.
23236
+ *
23237
+ * @internal
23238
+ */
23239
+ const RETRY_CONFIG = {
23240
+ /** Maximum number of retry *attempts* (total tries = maxRetries + 1). */
23241
+ maxRetries: 8,
23242
+ /** Initial backoff delay in milliseconds. */
23243
+ initialDelayMs: 1e3,
23244
+ /** Multiplier applied to the delay after each failed attempt. */
23245
+ backoffFactor: 2,
23246
+ /** Cap on per-attempt delay (ms). */
23247
+ maxDelayMs: 12e4,
23248
+ /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
23249
+ jitter: .2
23250
+ };
23251
+ const TRANSIENT_MESSAGE_SUBSTRINGS = [
23252
+ "terminated",
23253
+ "fetch failed",
23254
+ "econnreset",
23255
+ "econnrefused",
23256
+ "enotfound",
23257
+ "eai_again",
23258
+ "socket hang up",
23259
+ "network error",
23260
+ "request timed out",
23261
+ "request timeout",
23262
+ "connection reset",
23263
+ "connection closed"
23264
+ ];
23226
23265
  function isRecord$5(value) {
23227
23266
  return typeof value === "object" && value !== null;
23228
23267
  }
23268
+ function isAbortError$1(error) {
23269
+ if (!isRecord$5(error)) return false;
23270
+ if (error.name === "AbortError") return true;
23271
+ if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
23272
+ return false;
23273
+ }
23274
+ function hasTransientMessage(value) {
23275
+ if (!isRecord$5(value)) return false;
23276
+ const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
23277
+ if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
23278
+ if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
23279
+ return false;
23280
+ }
23281
+ function isRetryableHttpStatus(status) {
23282
+ if (status === 408 || status === 425 || status === 429) return true;
23283
+ return status >= 500 && status <= 599;
23284
+ }
23285
+ function hasRetryableHttpStatus(value) {
23286
+ if (!isRecord$5(value)) return false;
23287
+ if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
23288
+ if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
23289
+ return false;
23290
+ }
23291
+ function hasIsRetryableFlag(value) {
23292
+ if (!isRecord$5(value)) return false;
23293
+ return value.isRetryable === true;
23294
+ }
23295
+ /**
23296
+ * Returns true when a user-initiated cancellation appears anywhere in the
23297
+ * error's `cause`/`error` wrapper chain, so a wrapped abort can never be
23298
+ * mistaken for a retryable or survivable provider failure.
23299
+ *
23300
+ * @internal
23301
+ */
23302
+ function hasAbortInChain(error) {
23303
+ const seen = /* @__PURE__ */ new Set();
23304
+ function visit(candidate) {
23305
+ if (isAbortError$1(candidate)) return true;
23306
+ if (!isRecord$5(candidate)) return false;
23307
+ if (seen.has(candidate)) return false;
23308
+ seen.add(candidate);
23309
+ return visit(candidate.cause) || visit(candidate.error);
23310
+ }
23311
+ return visit(error);
23312
+ }
23313
+ /**
23314
+ * Returns true when the given error looks like a transient transport-class
23315
+ * failure that's worth retrying — undici `terminated`, `fetch failed`,
23316
+ * `UND_ERR_*` codes, AI SDK `APICallError` with `isRetryable: true`, and
23317
+ * common HTTP 408/425/429/5xx statuses. Walks the `error.cause` chain so
23318
+ * wrapper errors don't hide the real failure.
23319
+ *
23320
+ * Never retries on user-initiated aborts.
23321
+ *
23322
+ * @internal
23323
+ */
23324
+ function isTransientLLMError(error) {
23325
+ if (hasAbortInChain(error)) return false;
23326
+ const visited = /* @__PURE__ */ new WeakSet();
23327
+ function visit(candidate) {
23328
+ if (isRecord$5(candidate)) {
23329
+ if (visited.has(candidate)) return false;
23330
+ visited.add(candidate);
23331
+ }
23332
+ if (hasTransientMessage(candidate)) return true;
23333
+ if (hasRetryableHttpStatus(candidate)) return true;
23334
+ if (hasIsRetryableFlag(candidate)) return true;
23335
+ if (isRecord$5(candidate)) {
23336
+ if (visit(candidate.cause)) return true;
23337
+ if (visit(candidate.error)) return true;
23338
+ }
23339
+ return false;
23340
+ }
23341
+ return visit(error);
23342
+ }
23343
+ /**
23344
+ * Compute the backoff delay (ms) for the Nth retry (0-indexed).
23345
+ *
23346
+ * Exponential growth (`initialDelayMs * backoffFactor^attempt`) capped at
23347
+ * `maxDelayMs`, then nudged by ±`jitter` (fractional). Exported for unit
23348
+ * tests that lock the schedule against drift.
23349
+ *
23350
+ * @internal
23351
+ */
23352
+ function computeDelay(attempt) {
23353
+ const base = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt);
23354
+ const capped = Math.min(base, RETRY_CONFIG.maxDelayMs);
23355
+ if (RETRY_CONFIG.jitter <= 0) return capped;
23356
+ const jitterRange = capped * RETRY_CONFIG.jitter;
23357
+ const offset = (Math.random() * 2 - 1) * jitterRange;
23358
+ return Math.max(0, Math.round(capped + offset));
23359
+ }
23360
+ function sleep(ms, abortSignal) {
23361
+ if (ms <= 0) return Promise.resolve();
23362
+ return new Promise((resolve, reject) => {
23363
+ if (abortSignal?.aborted) {
23364
+ reject(/* @__PURE__ */ new Error("The operation was aborted."));
23365
+ return;
23366
+ }
23367
+ const timer = setTimeout(() => {
23368
+ abortSignal?.removeEventListener("abort", onAbort);
23369
+ resolve();
23370
+ }, ms);
23371
+ const onAbort = () => {
23372
+ clearTimeout(timer);
23373
+ abortSignal?.removeEventListener("abort", onAbort);
23374
+ reject(/* @__PURE__ */ new Error("The operation was aborted."));
23375
+ };
23376
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
23377
+ });
23378
+ }
23379
+ /**
23380
+ * Run `fn` with retries on transient transport-class errors.
23381
+ *
23382
+ * Non-transient errors (auth, validation, schema, etc.) are rethrown
23383
+ * immediately. User-initiated aborts are rethrown without delay.
23384
+ *
23385
+ * @internal
23386
+ */
23387
+ async function withRetry(fn, opts) {
23388
+ const { label, abortSignal, maxRetries = RETRY_CONFIG.maxRetries } = opts;
23389
+ let attempt = 0;
23390
+ while (true) {
23391
+ if (abortSignal?.aborted) throw new Error("The operation was aborted.");
23392
+ try {
23393
+ return await fn();
23394
+ } catch (error) {
23395
+ if (hasAbortInChain(error) || abortSignal?.aborted) throw error;
23396
+ if (attempt >= maxRetries || !isTransientLLMError(error)) {
23397
+ if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
23398
+ throw error;
23399
+ }
23400
+ const delay = computeDelay(attempt);
23401
+ attempt++;
23402
+ omDebug(`[OM:retry:${label}] transient error on attempt ${attempt}, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`);
23403
+ await sleep(delay, abortSignal);
23404
+ }
23405
+ }
23406
+ }
23407
+ const AI_API_CALL_ERROR_MARKER = Symbol.for("vercel.ai.error.AI_APICallError");
23408
+ function isRecord$4(value) {
23409
+ return typeof value === "object" && value !== null;
23410
+ }
23411
+ function isOmModelExecutionFailure(error) {
23412
+ if (hasAbortInChain(error)) return false;
23413
+ if (isTransientLLMError(error)) return true;
23414
+ const visited = /* @__PURE__ */ new Set();
23415
+ let current = error;
23416
+ while (isRecord$4(current) && !visited.has(current)) {
23417
+ visited.add(current);
23418
+ if (Object.prototype.hasOwnProperty.call(current, AI_API_CALL_ERROR_MARKER)) return Reflect.get(current, AI_API_CALL_ERROR_MARKER) === true;
23419
+ current = current.cause ?? current.error;
23420
+ }
23421
+ return false;
23422
+ }
23423
+ var OmModelExecutionError = class extends Error {
23424
+ failureKind;
23425
+ constructor(failureKind, cause) {
23426
+ super(formatOmError(cause), { cause });
23427
+ this.failureKind = failureKind;
23428
+ this.name = "OmModelExecutionError";
23429
+ }
23430
+ };
23431
+ function isOmModelExecutionError(error) {
23432
+ try {
23433
+ return error instanceof OmModelExecutionError;
23434
+ } catch {
23435
+ return false;
23436
+ }
23437
+ }
23438
+ function getOmFailureMetadata(error, failurePolicy) {
23439
+ return {
23440
+ failurePolicy,
23441
+ ...isOmModelExecutionError(error) ? { failureKind: error.failureKind } : {}
23442
+ };
23443
+ }
23229
23444
  /** Keep provider diagnostics in streamed/persisted markers, not whole API request/response objects. */
23230
23445
  function formatOmError(error) {
23231
23446
  const details = /* @__PURE__ */ new Set();
@@ -23235,20 +23450,21 @@ function formatOmError(error) {
23235
23450
  };
23236
23451
  function collectErrorDetails(value, depth) {
23237
23452
  if (depth > 5) return;
23238
- if (!isRecord$5(value)) {
23453
+ if (!isRecord$4(value)) {
23239
23454
  if (value !== void 0) add(String(value));
23240
23455
  return;
23241
23456
  }
23242
23457
  if (visited.has(value)) return;
23243
23458
  visited.add(value);
23459
+ if (typeof value.id === "string" && typeof value.domain === "string" && typeof value.category === "string") add(value.id);
23244
23460
  add(value.message);
23245
23461
  if (typeof value.statusCode === "number") add(`HTTP ${value.statusCode}`);
23246
23462
  if (typeof value.responseBody === "string") try {
23247
23463
  const body = JSON.parse(value.responseBody);
23248
- if (isRecord$5(body)) {
23464
+ if (isRecord$4(body)) {
23249
23465
  add(body.message);
23250
23466
  add(body.detail);
23251
- if (isRecord$5(body.error)) add(body.error.message);
23467
+ if (isRecord$4(body.error)) add(body.error.message);
23252
23468
  else add(body.error);
23253
23469
  }
23254
23470
  } catch {}
@@ -23261,8 +23477,8 @@ function formatOmError(error) {
23261
23477
  const message = [...details].join(": ") || "Unknown error";
23262
23478
  return message.length > 2e3 ? `${message.slice(0, 1997)}...` : message;
23263
23479
  }
23264
- function isAbortError$1(error, abortSignal) {
23265
- 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);
23266
23482
  }
23267
23483
  function shouldRetryEmptyStructuredObject(object, extractors) {
23268
23484
  return Object.keys(object).length === 0 && extractors.some((extractor) => extractor.retryStructuredExtractionOnEmptyObject);
@@ -23288,7 +23504,12 @@ If a prior value is still applicable and carry-forward is enabled, return that p
23288
23504
  ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values\n\n${priorLines.join("\n\n")}` : ""}`;
23289
23505
  const values = {};
23290
23506
  const failures = [];
23291
- 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) => {
23292
23513
  const object = await (await opts.agent.stream(prompt, {
23293
23514
  structuredOutput: {
23294
23515
  schema,
@@ -23308,11 +23529,21 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
23308
23529
  object = await streamWithStructuredOutput();
23309
23530
  retryEmptyObject = shouldRetryEmptyStructuredObject(object, structuredExtractors);
23310
23531
  } catch (error) {
23311
- 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
+ }
23312
23543
  try {
23313
23544
  object = await streamWithStructuredOutput(coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
23314
23545
  } catch (fallbackError) {
23315
- if (isAbortError$1(fallbackError, opts.abortSignal)) throw fallbackError;
23546
+ if (isAbortError$5(fallbackError, opts.abortSignal)) throw fallbackError;
23316
23547
  const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
23317
23548
  return {
23318
23549
  values,
@@ -23326,7 +23557,7 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
23326
23557
  if (retryEmptyObject) try {
23327
23558
  object = await streamWithStructuredOutput(coreFeatures.has("json-prompt-injection:inline") ? "inline" : true);
23328
23559
  } catch (fallbackError) {
23329
- if (isAbortError$1(fallbackError, opts.abortSignal)) throw fallbackError;
23560
+ if (isAbortError$5(fallbackError, opts.abortSignal)) throw fallbackError;
23330
23561
  const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
23331
23562
  return {
23332
23563
  values,
@@ -23352,15 +23583,15 @@ ${extractorInstructions}${priorLines.length > 0 ? `\n\n## Prior Extracted Values
23352
23583
  };
23353
23584
  }
23354
23585
  const LEGACY_SYSTEM_REMINDER_METADATA_KEY = "dynamicAgentsMdReminder";
23355
- function isRecord$4(value) {
23586
+ function isRecord$3(value) {
23356
23587
  return typeof value === "object" && value !== null;
23357
23588
  }
23358
23589
  function isSystemReminderMessage(message) {
23359
- if (!isRecord$4(message.content)) return false;
23590
+ if (!isRecord$3(message.content)) return false;
23360
23591
  const metadata = message.content.metadata;
23361
- 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");
23362
23593
  if (message.role !== "user") return false;
23363
- 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;
23364
23595
  const firstTextPart = message.content.parts.find((part) => part.type === "text");
23365
23596
  return typeof firstTextPart?.text === "string" && firstTextPart.text.startsWith("<system-reminder");
23366
23597
  }
@@ -23419,12 +23650,41 @@ function stripEphemeralAnchorIds(observations) {
23419
23650
  * Date/time utility functions for Observational Memory.
23420
23651
  * Pure functions for formatting relative timestamps and annotating observations.
23421
23652
  */
23422
- /**
23423
- * Format a relative time string like "5 days ago", "2 weeks ago", "today", etc.
23424
- */
23425
- function formatRelativeTime(date, currentDate) {
23426
- const diffMs = currentDate.getTime() - date.getTime();
23427
- 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) {
23428
23688
  if (diffDays < 0) {
23429
23689
  const futureDays = Math.abs(diffDays);
23430
23690
  if (futureDays === 1) return "tomorrow";
@@ -23450,8 +23710,7 @@ function formatRelativeTime(date, currentDate) {
23450
23710
  * Returns null for consecutive days (no gap marker needed).
23451
23711
  */
23452
23712
  function formatGapBetweenDates(prevDate, currDate) {
23453
- const diffMs = currDate.getTime() - prevDate.getTime();
23454
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
23713
+ const diffDays = calendarDay(currDate) - calendarDay(prevDate);
23455
23714
  if (diffDays <= 1) return null;
23456
23715
  else if (diffDays < 7) return `[${diffDays} days later]`;
23457
23716
  else if (diffDays < 14) return `[1 week later]`;
@@ -23459,45 +23718,156 @@ function formatGapBetweenDates(prevDate, currDate) {
23459
23718
  else if (diffDays < 60) return `[1 month later]`;
23460
23719
  else return `[${Math.floor(diffDays / 30)} months later]`;
23461
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
+ }
23462
23835
  /**
23463
- * Parses a date string like "May 30, 2023", "May 27-28, 2023", "late April 2023", etc.
23464
- * 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.
23465
23841
  */
23466
- function parseDateFromContent(dateContent) {
23467
- let targetDate = null;
23468
- const simpleDateMatch = dateContent.match(/([A-Z][a-z]+)\s+(\d{1,2}),?\s+(\d{4})/);
23469
- if (simpleDateMatch) {
23470
- const parsed = /* @__PURE__ */ new Date(`${simpleDateMatch[1]} ${simpleDateMatch[2]}, ${simpleDateMatch[3]}`);
23471
- if (!isNaN(parsed.getTime())) targetDate = parsed;
23472
- }
23473
- if (!targetDate) {
23474
- const rangeMatch = dateContent.match(/([A-Z][a-z]+)\s+(\d{1,2})-\d{1,2},?\s+(\d{4})/);
23475
- if (rangeMatch) {
23476
- const parsed = /* @__PURE__ */ new Date(`${rangeMatch[1]} ${rangeMatch[2]}, ${rangeMatch[3]}`);
23477
- if (!isNaN(parsed.getTime())) targetDate = parsed;
23478
- }
23479
- }
23480
- if (!targetDate) {
23481
- const vagueMatch = dateContent.match(/(late|early|mid)[- ]?(?:to[- ]?(?:late|early|mid)[- ]?)?([A-Z][a-z]+)\s+(\d{4})/i);
23482
- if (vagueMatch) {
23483
- const month = vagueMatch[2];
23484
- const year = vagueMatch[3];
23485
- const modifier = vagueMatch[1].toLowerCase();
23486
- let day = 15;
23487
- if (modifier === "early") day = 7;
23488
- if (modifier === "late") day = 23;
23489
- const parsed = /* @__PURE__ */ new Date(`${month} ${day}, ${year}`);
23490
- if (!isNaN(parsed.getTime())) targetDate = parsed;
23491
- }
23492
- }
23493
- if (!targetDate) {
23494
- const crossMonthMatch = dateContent.match(/([A-Z][a-z]+)\s+to\s+(?:early\s+)?([A-Z][a-z]+)\s+(\d{4})/i);
23495
- if (crossMonthMatch) {
23496
- const parsed = /* @__PURE__ */ new Date(`${crossMonthMatch[2]} 1, ${crossMonthMatch[3]}`);
23497
- if (!isNaN(parsed.getTime())) targetDate = parsed;
23498
- }
23499
- }
23500
- 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`;
23501
23871
  }
23502
23872
  /**
23503
23873
  * Detects if an observation line indicates future intent (will do, plans to, looking forward to, etc.)
@@ -23517,40 +23887,59 @@ function isFutureIntentObservation(line) {
23517
23887
  }
23518
23888
  /**
23519
23889
  * Expand inline estimated dates with relative time.
23520
- * Matches patterns like "(estimated May 27-28, 2023)" or "(meaning May 30, 2023)"
23521
- * 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.
23522
23892
  */
23523
- function expandInlineEstimatedDates(observations, currentDate) {
23524
- return observations.replace(/\((estimated|meaning)\s+([^)]+\d{4})\)/gi, (match, prefix, dateContent, offset) => {
23525
- const targetDate = parseDateFromContent(dateContent);
23526
- if (targetDate) {
23527
- const relative = formatRelativeTime(targetDate, currentDate);
23528
- const lineStart = observations.lastIndexOf("\n", offset) + 1;
23529
- const lineBeforeDate = observations.slice(lineStart, offset);
23530
- const isPastDate = targetDate < currentDate;
23531
- const isFutureIntent = isFutureIntentObservation(lineBeforeDate);
23532
- if (isPastDate && isFutureIntent) return `(${prefix} ${dateContent} - ${relative}, likely already happened)`;
23533
- return `(${prefix} ${dateContent} - ${relative})`;
23534
- }
23535
- 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})`;
23536
23921
  });
23537
23922
  }
23538
23923
  /**
23539
23924
  * Add relative time annotations to observations.
23540
- * Transforms "Date: May 15, 2023" headers to "Date: May 15, 2023 (5 days ago)"
23541
- * 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.
23542
23930
  */
23543
- function addRelativeTimeToObservations(observations, currentDate) {
23544
- const withInlineDates = expandInlineEstimatedDates(observations, currentDate);
23545
- 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;
23546
23935
  const dates = [];
23547
23936
  let regexMatch;
23548
23937
  while ((regexMatch = dateHeaderRegex.exec(withInlineDates)) !== null) {
23549
- const dateStr = regexMatch[2];
23550
- const parsed = new Date(dateStr);
23551
- if (!isNaN(parsed.getTime())) dates.push({
23938
+ const dateStr = regexMatch[2].trimEnd();
23939
+ const span = parseDateSpan(dateStr);
23940
+ if (span) dates.push({
23552
23941
  index: regexMatch.index,
23553
- date: parsed,
23942
+ span,
23554
23943
  match: regexMatch[0],
23555
23944
  prefix: regexMatch[1],
23556
23945
  dateStr
@@ -23564,11 +23953,10 @@ function addRelativeTimeToObservations(observations, currentDate) {
23564
23953
  const prev = i > 0 ? dates[i - 1] : null;
23565
23954
  result += withInlineDates.slice(lastIndex, curr.index);
23566
23955
  if (prev) {
23567
- const gap = formatGapBetweenDates(prev.date, curr.date);
23956
+ const gap = formatGapBetweenDates(prev.span.end, curr.span.start);
23568
23957
  if (gap) result += `\n${gap}\n\n`;
23569
23958
  }
23570
- const relative = formatRelativeTime(curr.date, currentDate);
23571
- result += `${curr.prefix}${curr.dateStr} (${relative})`;
23959
+ result += `${curr.prefix}${curr.dateStr} (${relativeSpan(curr.span, today)})`;
23572
23960
  lastIndex = curr.index + curr.match.length;
23573
23961
  }
23574
23962
  result += withInlineDates.slice(lastIndex);
@@ -24573,11 +24961,20 @@ const OBSERVER_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
24573
24961
  "heif",
24574
24962
  "avif"
24575
24963
  ]);
24576
- function formatObserverDate(createdAt) {
24577
- 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")}`;
24578
24974
  }
24579
- function formatObserverTime(createdAt) {
24975
+ function formatObserverTime(createdAt, timeZone) {
24580
24976
  return createdAt ? createdAt.toLocaleTimeString("en-US", {
24977
+ timeZone,
24581
24978
  hour: "numeric",
24582
24979
  minute: "2-digit",
24583
24980
  hour12: true
@@ -24667,11 +25064,11 @@ function formatObserverAttachmentPlaceholder(part, counter) {
24667
25064
  const label = resolveObserverAttachmentLabel(part);
24668
25065
  return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
24669
25066
  }
24670
- function isRecord$3(value) {
25067
+ function isRecord$2(value) {
24671
25068
  return !!value && typeof value === "object";
24672
25069
  }
24673
25070
  function mapToolResultBlockToAttachment(block) {
24674
- if (!isRecord$3(block) || typeof block.type !== "string") return;
25071
+ if (!isRecord$2(block) || typeof block.type !== "string") return;
24675
25072
  const mediaType = typeof block.mediaType === "string" ? block.mediaType : void 0;
24676
25073
  const filename = typeof block.filename === "string" ? block.filename : void 0;
24677
25074
  switch (block.type) {
@@ -24732,7 +25129,7 @@ function mapToolResultBlockToAttachment(block) {
24732
25129
  }
24733
25130
  }
24734
25131
  function extractToolResultAttachments(result, counter, attachmentFilter) {
24735
- 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 {
24736
25133
  resultWithoutAttachments: result,
24737
25134
  attachments: []
24738
25135
  };
@@ -24746,7 +25143,7 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
24746
25143
  if (shouldIncludeObserverAttachment(attachment, attachmentFilter)) attachments.push(toObserverInputAttachmentPart(attachment));
24747
25144
  const placeholder = formatObserverAttachmentPlaceholder(attachment, counter);
24748
25145
  return {
24749
- type: isRecord$3(block) ? block.type : void 0,
25146
+ type: isRecord$2(block) ? block.type : void 0,
24750
25147
  placeholder
24751
25148
  };
24752
25149
  });
@@ -24863,6 +25260,7 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
24863
25260
  const maxLen = options?.maxPartLength;
24864
25261
  const maxToolResultTokens = options?.maxToolResultTokens ?? 5e3;
24865
25262
  const attachmentFilter = options?.attachmentFilter;
25263
+ const timeZone = resolveTimeZone(options?.timeZone);
24866
25264
  const role = getObserverMessageLabel(msg);
24867
25265
  const attachments = [];
24868
25266
  const messageCreatedAt = normalizeObserverCreatedAt(msg.createdAt);
@@ -24872,8 +25270,8 @@ function formatObserverMessage(msg, counter, toolFormatting, options) {
24872
25270
  if (!body) return;
24873
25271
  const normalizedCreatedAt = normalizeObserverCreatedAt(createdAt) ?? messageCreatedAt;
24874
25272
  lines.push({
24875
- date: formatObserverDate(normalizedCreatedAt),
24876
- time: formatObserverTime(normalizedCreatedAt),
25273
+ date: formatObserverDate(normalizedCreatedAt, timeZone),
25274
+ time: formatObserverTime(normalizedCreatedAt, timeZone),
24877
25275
  title,
24878
25276
  body
24879
25277
  });
@@ -25426,169 +25824,6 @@ function optimizeObservationsForContext(observations) {
25426
25824
  optimized = optimized.replace(/\n{3,}/g, "\n\n");
25427
25825
  return optimized.trim();
25428
25826
  }
25429
- /**
25430
- * Retry knobs for the internal OM transport-error retry wrapper.
25431
- * Exported as a mutable object so tests can shrink the backoff schedule
25432
- * without changing public API.
25433
- *
25434
- * With the defaults the per-retry pre-jitter backoff schedule is:
25435
- * 1s, 2s, 4s, 8s, 16s, 32s, 64s, 120s (cap)
25436
- * giving 8 retries / 9 total attempts and ~247s (~4 minutes) of waiting
25437
- * before the final attempt fails. Designed to ride out short provider /
25438
- * network blips without holding the actor turn for much longer than that.
25439
- *
25440
- * @internal
25441
- */
25442
- const RETRY_CONFIG = {
25443
- /** Maximum number of retry *attempts* (total tries = maxRetries + 1). */
25444
- maxRetries: 8,
25445
- /** Initial backoff delay in milliseconds. */
25446
- initialDelayMs: 1e3,
25447
- /** Multiplier applied to the delay after each failed attempt. */
25448
- backoffFactor: 2,
25449
- /** Cap on per-attempt delay (ms). */
25450
- maxDelayMs: 12e4,
25451
- /** Random jitter as a fraction of the computed delay (e.g. 0.2 = ±20%). */
25452
- jitter: .2
25453
- };
25454
- const TRANSIENT_MESSAGE_SUBSTRINGS = [
25455
- "terminated",
25456
- "fetch failed",
25457
- "econnreset",
25458
- "econnrefused",
25459
- "enotfound",
25460
- "eai_again",
25461
- "socket hang up",
25462
- "network error",
25463
- "request timed out",
25464
- "request timeout",
25465
- "connection reset",
25466
- "connection closed"
25467
- ];
25468
- function isRecord$2(value) {
25469
- return typeof value === "object" && value !== null;
25470
- }
25471
- function isAbortError$5(error) {
25472
- if (!isRecord$2(error)) return false;
25473
- if (error.name === "AbortError") return true;
25474
- if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
25475
- return false;
25476
- }
25477
- function hasTransientMessage(value) {
25478
- if (!isRecord$2(value)) return false;
25479
- const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
25480
- if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
25481
- if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
25482
- return false;
25483
- }
25484
- function isRetryableHttpStatus(status) {
25485
- if (status === 408 || status === 425 || status === 429) return true;
25486
- return status >= 500 && status <= 599;
25487
- }
25488
- function hasRetryableHttpStatus(value) {
25489
- if (!isRecord$2(value)) return false;
25490
- if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
25491
- if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
25492
- return false;
25493
- }
25494
- function hasIsRetryableFlag(value) {
25495
- if (!isRecord$2(value)) return false;
25496
- return value.isRetryable === true;
25497
- }
25498
- /**
25499
- * Returns true when the given error looks like a transient transport-class
25500
- * failure that's worth retrying — undici `terminated`, `fetch failed`,
25501
- * `UND_ERR_*` codes, AI SDK `APICallError` with `isRetryable: true`, and
25502
- * common HTTP 408/425/429/5xx statuses. Walks the `error.cause` chain so
25503
- * wrapper errors don't hide the real failure.
25504
- *
25505
- * Never retries on user-initiated aborts.
25506
- *
25507
- * @internal
25508
- */
25509
- function isTransientLLMError(error) {
25510
- if (isAbortError$5(error)) return false;
25511
- const visited = /* @__PURE__ */ new WeakSet();
25512
- function visit(candidate) {
25513
- if (isRecord$2(candidate)) {
25514
- if (visited.has(candidate)) return false;
25515
- visited.add(candidate);
25516
- }
25517
- if (hasTransientMessage(candidate)) return true;
25518
- if (hasRetryableHttpStatus(candidate)) return true;
25519
- if (hasIsRetryableFlag(candidate)) return true;
25520
- if (isRecord$2(candidate)) {
25521
- if (visit(candidate.cause)) return true;
25522
- if (visit(candidate.error)) return true;
25523
- }
25524
- return false;
25525
- }
25526
- return visit(error);
25527
- }
25528
- /**
25529
- * Compute the backoff delay (ms) for the Nth retry (0-indexed).
25530
- *
25531
- * Exponential growth (`initialDelayMs * backoffFactor^attempt`) capped at
25532
- * `maxDelayMs`, then nudged by ±`jitter` (fractional). Exported for unit
25533
- * tests that lock the schedule against drift.
25534
- *
25535
- * @internal
25536
- */
25537
- function computeDelay(attempt) {
25538
- const base = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt);
25539
- const capped = Math.min(base, RETRY_CONFIG.maxDelayMs);
25540
- if (RETRY_CONFIG.jitter <= 0) return capped;
25541
- const jitterRange = capped * RETRY_CONFIG.jitter;
25542
- const offset = (Math.random() * 2 - 1) * jitterRange;
25543
- return Math.max(0, Math.round(capped + offset));
25544
- }
25545
- function sleep(ms, abortSignal) {
25546
- if (ms <= 0) return Promise.resolve();
25547
- return new Promise((resolve, reject) => {
25548
- if (abortSignal?.aborted) {
25549
- reject(/* @__PURE__ */ new Error("The operation was aborted."));
25550
- return;
25551
- }
25552
- const timer = setTimeout(() => {
25553
- abortSignal?.removeEventListener("abort", onAbort);
25554
- resolve();
25555
- }, ms);
25556
- const onAbort = () => {
25557
- clearTimeout(timer);
25558
- abortSignal?.removeEventListener("abort", onAbort);
25559
- reject(/* @__PURE__ */ new Error("The operation was aborted."));
25560
- };
25561
- abortSignal?.addEventListener("abort", onAbort, { once: true });
25562
- });
25563
- }
25564
- /**
25565
- * Run `fn` with retries on transient transport-class errors.
25566
- *
25567
- * Non-transient errors (auth, validation, schema, etc.) are rethrown
25568
- * immediately. User-initiated aborts are rethrown without delay.
25569
- *
25570
- * @internal
25571
- */
25572
- async function withRetry(fn, opts) {
25573
- const { label, abortSignal } = opts;
25574
- let attempt = 0;
25575
- while (true) {
25576
- if (abortSignal?.aborted) throw new Error("The operation was aborted.");
25577
- try {
25578
- return await fn();
25579
- } catch (error) {
25580
- if (isAbortError$5(error) || abortSignal?.aborted) throw error;
25581
- if (attempt >= RETRY_CONFIG.maxRetries || !isTransientLLMError(error)) {
25582
- if (attempt > 0) omDebug(`[OM:retry:${label}] giving up after ${attempt} retry/retries: ${error instanceof Error ? error.message : String(error)}`);
25583
- throw error;
25584
- }
25585
- const delay = computeDelay(attempt);
25586
- attempt++;
25587
- omDebug(`[OM:retry:${label}] transient error on attempt ${attempt}, retrying in ${delay}ms: ${error instanceof Error ? error.message : String(error)}`);
25588
- await sleep(delay, abortSignal);
25589
- }
25590
- }
25591
- }
25592
25827
  function createTemporaryOmMemoryContext(prefix) {
25593
25828
  const options = {
25594
25829
  thread: `${prefix}-${randomUUID()}`,
@@ -25723,11 +25958,27 @@ var ObserverRunner = class {
25723
25958
  this.mastra = mastra;
25724
25959
  }
25725
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
+ }
25726
25976
  return new Agent({
25727
25977
  id: isMultiThread ? "multi-thread-observer" : "observational-memory-observer",
25728
25978
  name: isMultiThread ? "multi-thread-observer" : "Observer",
25979
+ maxRetries: 0,
25729
25980
  instructions: buildObserverSystemPrompt(isMultiThread, this.observationConfig.instruction, this.observationConfig.threadTitle, extractors),
25730
- model,
25981
+ model: agentModel,
25731
25982
  ...memory ? { memory } : {},
25732
25983
  ...this.mastra ? { mastra: this.mastra } : {}
25733
25984
  });
@@ -25819,7 +26070,10 @@ var ObserverRunner = class {
25819
26070
  ...options,
25820
26071
  includeThreadTitle: this.observationConfig.threadTitle,
25821
26072
  extractors: activeExtractors
25822
- }, { attachmentFilter })];
26073
+ }, {
26074
+ attachmentFilter,
26075
+ timeZone: options?.timeZone
26076
+ })];
25823
26077
  const doGenerate = async () => {
25824
26078
  return withRetry(() => withOmTracingSpan({
25825
26079
  phase: "observer",
@@ -25857,12 +26111,14 @@ var ObserverRunner = class {
25857
26111
  hasRequestContext: Boolean(internalRequestContext),
25858
26112
  aborted: abortSignal?.aborted ?? false
25859
26113
  });
25860
- throw error;
26114
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
26115
+ throw new OmModelExecutionError("observer-model", error);
25861
26116
  }
25862
26117
  }, abortSignal)
25863
26118
  }), {
25864
26119
  label: "observer",
25865
- abortSignal
26120
+ abortSignal,
26121
+ maxRetries: this.observationConfig.maxRetries
25866
26122
  });
25867
26123
  };
25868
26124
  let result = await doGenerate();
@@ -25930,7 +26186,7 @@ var ObserverRunner = class {
25930
26186
  /**
25931
26187
  * Call the Observer agent for multiple threads in a single batched request.
25932
26188
  */
25933
- async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext) {
26189
+ async callMultiThread(existingObservations, allMessagesByThread, allThreadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, hookContext, timeZone) {
25934
26190
  const contextFor = (threadId) => ({
25935
26191
  threadId,
25936
26192
  ...hookContext
@@ -25941,7 +26197,7 @@ var ObserverRunner = class {
25941
26197
  if (filtered.length > 0) messagesByThread.set(threadId, filtered);
25942
26198
  }
25943
26199
  const threadOrder = allThreadOrder.filter((threadId) => messagesByThread.has(threadId));
25944
- 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);
25945
26201
  for (const threadId of allThreadOrder) {
25946
26202
  const threadResult = output.results.get(threadId);
25947
26203
  if (!threadResult) {
@@ -25952,7 +26208,7 @@ var ObserverRunner = class {
25952
26208
  }
25953
26209
  return output;
25954
26210
  }
25955
- async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model) {
26211
+ async callMultiThreadObserver(existingObservations, messagesByThread, threadOrder, abortSignal, requestContext, priorMetadataByThread, observabilityContext, model, timeZone) {
25956
26212
  if (threadOrder.length === 0) return { results: /* @__PURE__ */ new Map() };
25957
26213
  const inputTokens = Array.from(messagesByThread.values()).reduce((total, messages) => total + this.tokenCounter.countMessages(messages), 0);
25958
26214
  const resolvedModel = (() => {
@@ -25994,7 +26250,8 @@ var ObserverRunner = class {
25994
26250
  priorSuggestedResponse: priorMetadataByThread?.get(threadId)?.suggestedResponse,
25995
26251
  priorThreadTitle: priorMetadataByThread?.get(threadId)?.threadTitle,
25996
26252
  priorExtractedValues: priorMetadataByThread?.get(threadId)?.extracted,
25997
- model: resolvedModel.model
26253
+ model: resolvedModel.model,
26254
+ timeZone
25998
26255
  });
25999
26256
  results.set(threadId, {
26000
26257
  observations: threadResult.observations,
@@ -26011,6 +26268,7 @@ var ObserverRunner = class {
26011
26268
  totalUsage.totalTokens += threadResult.usage.totalTokens ?? 0;
26012
26269
  }
26013
26270
  }
26271
+ for (const msgs of messagesByThread.values()) for (const msg of msgs) this.observedMessageIds.add(msg.id);
26014
26272
  return {
26015
26273
  results,
26016
26274
  usage: totalUsage
@@ -26019,8 +26277,10 @@ var ObserverRunner = class {
26019
26277
  const agent = this.createAgent(resolvedModel.model, true, void 0, activeExtractors);
26020
26278
  const internalRequestContext = withOmInternalThreadId(requestContext, agent.id);
26021
26279
  const multiThreadAttachmentFilter = this.resolveAttachmentFilter(resolvedModel.model, requestContext);
26022
- const observerMessages = [buildMultiThreadObserverRequestMessage(existingObservations, messagesByThread, threadOrder, priorMetadataByThread, void 0, this.observationConfig.threadTitle, activeExtractors, { attachmentFilter: multiThreadAttachmentFilter })];
26023
- 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
+ })];
26024
26284
  const doGenerate = async () => {
26025
26285
  return withRetry(() => withOmTracingSpan({
26026
26286
  phase: "observer-multi-thread",
@@ -26058,12 +26318,14 @@ var ObserverRunner = class {
26058
26318
  hasRequestContext: Boolean(internalRequestContext),
26059
26319
  aborted: abortSignal?.aborted ?? false
26060
26320
  });
26061
- throw error;
26321
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
26322
+ throw new OmModelExecutionError("observer-model", error);
26062
26323
  }
26063
26324
  }, abortSignal)
26064
26325
  }), {
26065
26326
  label: "observer-multi-thread",
26066
- abortSignal
26327
+ abortSignal,
26328
+ maxRetries: this.observationConfig.maxRetries
26067
26329
  });
26068
26330
  };
26069
26331
  let result = await doGenerate();
@@ -26119,6 +26381,7 @@ var ObserverRunner = class {
26119
26381
  });
26120
26382
  }
26121
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);
26122
26385
  const usage = result.totalUsage ?? result.usage;
26123
26386
  return {
26124
26387
  results,
@@ -27606,6 +27869,8 @@ async function summarizeConversation(opts) {
27606
27869
  const runner = new ObserverRunner({
27607
27870
  observationConfig: {
27608
27871
  model: opts.model,
27872
+ maxRetries: 8,
27873
+ failurePolicy: "abort",
27609
27874
  messageTokens: OBSERVATIONAL_MEMORY_DEFAULTS.observation.messageTokens,
27610
27875
  shareTokenBudget: false,
27611
27876
  modelSettings: { ...OBSERVATIONAL_MEMORY_DEFAULTS.observation.modelSettings },
@@ -29416,6 +29681,7 @@ function createObservationFailedMarker(params) {
29416
29681
  durationMs,
29417
29682
  tokensAttempted: params.tokensAttempted,
29418
29683
  error: formatOmError(params.error),
29684
+ ...getOmFailureMetadata(params.error, params.failurePolicy ?? "abort"),
29419
29685
  recordId: params.recordId,
29420
29686
  threadId: params.threadId
29421
29687
  }
@@ -29477,6 +29743,7 @@ function createBufferingFailedMarker(params) {
29477
29743
  durationMs,
29478
29744
  tokensAttempted: params.tokensAttempted,
29479
29745
  error: formatOmError(params.error),
29746
+ ...getOmFailureMetadata(params.error, params.failurePolicy ?? "abort"),
29480
29747
  recordId: params.recordId,
29481
29748
  threadId: params.threadId
29482
29749
  }
@@ -29531,7 +29798,10 @@ function createThreadUpdateMarker(params) {
29531
29798
  function findLastCompletedObservationBoundary(message) {
29532
29799
  const parts = message.content?.parts;
29533
29800
  if (!parts || !Array.isArray(parts)) return -1;
29534
- 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
+ }
29535
29805
  return -1;
29536
29806
  }
29537
29807
  /**
@@ -29781,6 +30051,7 @@ function scanObservationGroupTags(observations) {
29781
30051
  const incompleteOpenings = [];
29782
30052
  let cursor = 0;
29783
30053
  let closeFloor = 0;
30054
+ let nestedFloor = -1;
29784
30055
  while (cursor < observations.length) {
29785
30056
  const start = observations.indexOf(OBSERVATION_GROUP_OPEN, cursor);
29786
30057
  if (start === -1) break;
@@ -29801,8 +30072,12 @@ function scanObservationGroupTags(observations) {
29801
30072
  cursor = openEnd + 1;
29802
30073
  continue;
29803
30074
  }
29804
- const nestedStart = observations.indexOf(OBSERVATION_GROUP_OPEN, openEnd + 1);
29805
- 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) {
29806
30081
  incompleteOpenings.push({
29807
30082
  start,
29808
30083
  end: openEnd + 1
@@ -30185,6 +30460,7 @@ var ObservationStrategy = class ObservationStrategy {
30185
30460
  observationTokens: processed.observationTokens,
30186
30461
  threadId,
30187
30462
  writer,
30463
+ messageList: this.opts.messageList,
30188
30464
  abortSignal,
30189
30465
  mainAgent: this.opts.agent,
30190
30466
  sendSignal: this.opts.sendSignal,
@@ -30209,6 +30485,7 @@ var ObservationStrategy = class ObservationStrategy {
30209
30485
  operationType: "observation",
30210
30486
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
30211
30487
  error: formatOmError(error),
30488
+ ...getOmFailureMetadata(error, this.observationConfig.failurePolicy),
30212
30489
  recordId: record.id,
30213
30490
  threadId
30214
30491
  }
@@ -30222,6 +30499,10 @@ var ObservationStrategy = class ObservationStrategy {
30222
30499
  };
30223
30500
  }
30224
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
+ };
30225
30506
  throw error;
30226
30507
  }
30227
30508
  }
@@ -30487,7 +30768,8 @@ var SyncObservationStrategy = class extends ObservationStrategy {
30487
30768
  threadId: this.opts.threadId,
30488
30769
  resourceId: this.opts.resourceId,
30489
30770
  trigger: this.opts.trigger,
30490
- mainAgent: this.opts.agent
30771
+ mainAgent: this.opts.agent,
30772
+ timeZone: this.opts.record.observedTimezone
30491
30773
  });
30492
30774
  const hookedValues = await applyExtractorHooks({
30493
30775
  source: "observer",
@@ -30496,7 +30778,10 @@ var SyncObservationStrategy = class extends ObservationStrategy {
30496
30778
  failures: result.extractionFailures,
30497
30779
  previousValues: this.priorExtractedValues,
30498
30780
  rawObservations: result.observations,
30499
- recentMessages: formatMessagesForObserver(this.opts.messages, { maxPartLength: 500 }),
30781
+ recentMessages: formatMessagesForObserver(this.opts.messages, {
30782
+ maxPartLength: 500,
30783
+ timeZone: this.opts.record.observedTimezone
30784
+ }),
30500
30785
  threadId: this.opts.threadId,
30501
30786
  resourceId: this.opts.resourceId,
30502
30787
  mainAgent: this.opts.agent,
@@ -30627,6 +30912,7 @@ var SyncObservationStrategy = class extends ObservationStrategy {
30627
30912
  startedAt: this.startedAt,
30628
30913
  tokensAttempted: this.tokensToObserve,
30629
30914
  error,
30915
+ failurePolicy: this.observationConfig.failurePolicy,
30630
30916
  recordId: this.opts.record.id,
30631
30917
  threadId: this.opts.threadId
30632
30918
  });
@@ -30676,7 +30962,8 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
30676
30962
  threadId: this.opts.threadId,
30677
30963
  resourceId: this.opts.resourceId,
30678
30964
  trigger: this.opts.trigger,
30679
- mainAgent: this.opts.agent
30965
+ mainAgent: this.opts.agent,
30966
+ timeZone: this.opts.record.observedTimezone
30680
30967
  });
30681
30968
  const hookedValues = await applyExtractorHooks({
30682
30969
  source: "observer",
@@ -30685,7 +30972,10 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
30685
30972
  failures: result.extractionFailures,
30686
30973
  previousValues: this.priorExtractedValues,
30687
30974
  rawObservations: result.observations,
30688
- recentMessages: formatMessagesForObserver(messages, { maxPartLength: 500 }),
30975
+ recentMessages: formatMessagesForObserver(messages, {
30976
+ maxPartLength: 500,
30977
+ timeZone: this.opts.record.observedTimezone
30978
+ }),
30689
30979
  threadId: this.opts.threadId,
30690
30980
  resourceId: this.opts.resourceId,
30691
30981
  mainAgent: this.opts.agent,
@@ -30831,6 +31121,7 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
30831
31121
  startedAt: this.startedAt,
30832
31122
  tokensAttempted,
30833
31123
  error,
31124
+ failurePolicy: this.observationConfig.failurePolicy,
30834
31125
  recordId: record.id,
30835
31126
  threadId
30836
31127
  });
@@ -31005,7 +31296,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
31005
31296
  return this.deps.observer.callMultiThread(_existingObservations, batch.threadMap, batch.threadIds, this.opts.abortSignal, this.opts.requestContext, this.priorMetadataByThread, this.opts.observabilityContext, void 0, {
31006
31297
  resourceId: this.opts.resourceId,
31007
31298
  trigger: this.opts.trigger
31008
- });
31299
+ }, this.opts.record.observedTimezone);
31009
31300
  }));
31010
31301
  for (const batchResult of batchResults) {
31011
31302
  for (const [threadId, result] of batchResult.results) this.multiThreadResults.set(threadId, result);
@@ -31038,7 +31329,10 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
31038
31329
  failures: result.extractionFailures,
31039
31330
  previousValues,
31040
31331
  rawObservations: result.observations,
31041
- recentMessages: formatMessagesForObserver(threadMessages, { maxPartLength: 500 }),
31332
+ recentMessages: formatMessagesForObserver(threadMessages, {
31333
+ maxPartLength: 500,
31334
+ timeZone: this.opts.record.observedTimezone
31335
+ }),
31042
31336
  threadId,
31043
31337
  resourceId: this.resourceId,
31044
31338
  mainAgent: this.opts.agent,
@@ -31197,6 +31491,7 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
31197
31491
  startedAt: this.startedAt,
31198
31492
  tokensAttempted,
31199
31493
  error,
31494
+ failurePolicy: this.observationConfig.failurePolicy,
31200
31495
  recordId: this.opts.record.id,
31201
31496
  threadId
31202
31497
  });
@@ -31603,19 +31898,28 @@ var ObservationStep = class {
31603
31898
  };
31604
31899
  }
31605
31900
  }
31606
- const obsResult = await om.observe({
31607
- threadId,
31608
- resourceId,
31609
- messages: observableMessages,
31610
- messageList,
31611
- trigger: "turn-sync",
31612
- agent: this.turn.agent,
31613
- sendSignal: this.turn.sendSignal,
31614
- sendStateSignal: this.turn.sendStateSignal,
31615
- requestContext: this.turn.requestContext,
31616
- writer: this.turn.writer,
31617
- observabilityContext: this.turn.observabilityContext
31618
- });
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
+ }
31619
31923
  if (obsResult.observed) {
31620
31924
  const observedMessageIds = new Set(obsResult.record.observedMessageIds ?? []);
31621
31925
  const liveMessages = getObservableMessages(messageList);
@@ -32267,11 +32571,27 @@ var ReflectorRunner = class {
32267
32571
  this.mastra = mastra;
32268
32572
  }
32269
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
+ }
32270
32589
  return new Agent({
32271
32590
  id: "observational-memory-reflector",
32272
32591
  name: "Reflector",
32273
32592
  instructions: buildReflectorSystemPrompt(this.reflectionConfig.instruction, extractors),
32274
- model,
32593
+ model: agentModel,
32594
+ maxRetries: 0,
32275
32595
  ...memory ? { memory } : {},
32276
32596
  ...this.mastra ? { mastra: this.mastra } : {}
32277
32597
  });
@@ -32353,36 +32673,42 @@ var ReflectorRunner = class {
32353
32673
  },
32354
32674
  callback: (childObservabilityContext) => withAbortCheck(async () => {
32355
32675
  chunkCount = 0;
32356
- return (await agent.stream(prompt, {
32357
- modelSettings: { ...this.reflectionConfig.modelSettings },
32358
- providerOptions: this.reflectionConfig.providerOptions,
32359
- ...temporaryMemory ? { memory: temporaryMemory.options } : {},
32360
- ...abortSignal ? { abortSignal } : {},
32361
- ...internalRequestContext ? { requestContext: internalRequestContext } : {},
32362
- ...childObservabilityContext,
32363
- ...attemptNumber === 1 ? {
32364
- onChunk(chunk) {
32365
- chunkCount++;
32366
- if (chunkCount === 1 || chunkCount % 50 === 0) {
32367
- const preview = chunk.type === "text-delta" ? ` text="${chunk.textDelta?.slice(0, 80)}..."` : chunk.type === "tool-call" ? ` tool=${chunk.toolName}` : "";
32368
- 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);
32369
32700
  }
32370
- },
32371
- onFinish(event) {
32372
- omDebug(`[OM:callReflector] onFinish: chunks=${chunkCount}, finishReason=${event.finishReason}, inputTokens=${event.usage?.inputTokens}, outputTokens=${event.usage?.outputTokens}, textLen=${event.text?.length}`);
32373
- },
32374
- onAbort(event) {
32375
- omDebug(`[OM:callReflector] onAbort: chunks=${chunkCount}, reason=${event?.reason ?? "unknown"}`);
32376
- },
32377
- onError({ error }) {
32378
- omError(`[OM:callReflector] onError after ${chunkCount} chunks`, error);
32379
- }
32380
- } : {}
32381
- })).getFullOutput();
32701
+ } : {}
32702
+ })).getFullOutput();
32703
+ } catch (error) {
32704
+ if (abortSignal?.aborted || !isOmModelExecutionFailure(error)) throw error;
32705
+ throw new OmModelExecutionError("reflector-model", error);
32706
+ }
32382
32707
  }, abortSignal)
32383
32708
  }), {
32384
32709
  label: "reflector",
32385
- abortSignal
32710
+ abortSignal,
32711
+ maxRetries: this.reflectionConfig.maxRetries
32386
32712
  });
32387
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}`);
32388
32714
  const usage = result.totalUsage ?? result.usage;
@@ -32506,6 +32832,7 @@ var ReflectorRunner = class {
32506
32832
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
32507
32833
  tokensAttempted: observationTokens,
32508
32834
  error,
32835
+ failurePolicy: this.reflectionConfig.failurePolicy,
32509
32836
  recordId: record.id,
32510
32837
  threadId: record.threadId ?? ""
32511
32838
  });
@@ -32918,24 +33245,43 @@ var ReflectorRunner = class {
32918
33245
  usage: reflectResult.usage
32919
33246
  });
32920
33247
  } catch (error) {
32921
- if (writer && streamContext) {
32922
- const failedMarker = createObservationFailedMarker({
32923
- cycleId: streamContext.cycleId,
32924
- operationType: "reflection",
32925
- startedAt: streamContext.startedAt,
32926
- tokensAttempted: observationTokens,
32927
- error,
32928
- recordId: record.id,
32929
- threadId
32930
- });
32931
- await writer.custom({
32932
- ...failedMarker,
32933
- transient: true
32934
- }).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 {
32935
33270
  await this.persistMarkerToStorage(failedMarker, threadId, record.resourceId ?? void 0);
33271
+ } catch (markerError) {
33272
+ omError("[OM] Failed to persist reflection-failed marker", markerError);
32936
33273
  }
32937
- reflectionError = error instanceof Error ? error : new Error(String(error));
32938
- 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;
32939
33285
  this.syncReflectionSuppression.set(lockKey, observationTokens);
32940
33286
  omError("[OM] Reflection failed", error);
32941
33287
  } finally {
@@ -33569,6 +33915,8 @@ var ObservationalMemory = class ObservationalMemory {
33569
33915
  const observationActivateAfterIdlePath = config.observation?.activateAfterIdle !== void 0 ? "observation.activateAfterIdle" : "activateAfterIdle";
33570
33916
  this.observationConfig = {
33571
33917
  model: observationModel,
33918
+ maxRetries: config.observation?.maxRetries ?? RETRY_CONFIG.maxRetries,
33919
+ failurePolicy: config.observation?.failurePolicy ?? "abort",
33572
33920
  messageTokens: isSharedBudget ? {
33573
33921
  min: messageTokens,
33574
33922
  max: totalBudget
@@ -33598,6 +33946,8 @@ var ObservationalMemory = class ObservationalMemory {
33598
33946
  };
33599
33947
  this.reflectionConfig = {
33600
33948
  model: reflectionModel,
33949
+ maxRetries: config.reflection?.maxRetries ?? RETRY_CONFIG.maxRetries,
33950
+ failurePolicy: config.reflection?.failurePolicy ?? "abort",
33601
33951
  observationTokens,
33602
33952
  shareTokenBudget: isSharedBudget,
33603
33953
  modelSettings: {
@@ -33671,9 +34021,15 @@ var ObservationalMemory = class ObservationalMemory {
33671
34021
  retrieval: this.retrieval,
33672
34022
  observation: {
33673
34023
  messageTokens: this.observationConfig.messageTokens,
33674
- previousObserverTokens: this.observationConfig.previousObserverTokens
34024
+ previousObserverTokens: this.observationConfig.previousObserverTokens,
34025
+ maxRetries: this.observationConfig.maxRetries,
34026
+ failurePolicy: this.observationConfig.failurePolicy
33675
34027
  },
33676
- reflection: { observationTokens: this.reflectionConfig.observationTokens }
34028
+ reflection: {
34029
+ observationTokens: this.reflectionConfig.observationTokens,
34030
+ maxRetries: this.reflectionConfig.maxRetries,
34031
+ failurePolicy: this.reflectionConfig.failurePolicy
34032
+ }
33677
34033
  };
33678
34034
  }
33679
34035
  /**
@@ -33777,11 +34133,15 @@ var ObservationalMemory = class ObservationalMemory {
33777
34133
  messageTokens: this.observationConfig.messageTokens,
33778
34134
  model: observationResolved.model,
33779
34135
  previousObserverTokens: this.observationConfig.previousObserverTokens,
34136
+ maxRetries: this.observationConfig.maxRetries,
34137
+ failurePolicy: this.observationConfig.failurePolicy,
33780
34138
  routing: observationResolved.routing
33781
34139
  },
33782
34140
  reflection: {
33783
34141
  observationTokens: this.reflectionConfig.observationTokens,
33784
34142
  model: reflectionResolved.model,
34143
+ maxRetries: this.reflectionConfig.maxRetries,
34144
+ failurePolicy: this.reflectionConfig.failurePolicy,
33785
34145
  routing: reflectionResolved.routing
33786
34146
  }
33787
34147
  };
@@ -33798,6 +34158,7 @@ var ObservationalMemory = class ObservationalMemory {
33798
34158
  * Ensures bufferTokens is less than the threshold and bufferActivation is valid.
33799
34159
  */
33800
34160
  validateBufferConfig() {
34161
+ for (const [path, value] of [["observation.maxRetries", this.observationConfig.maxRetries], ["reflection.maxRetries", this.reflectionConfig.maxRetries]]) if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${path} must be a finite non-negative integer, got ${value}`);
33801
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.");
33802
34163
  const observationThreshold = getMaxThreshold(this.observationConfig.messageTokens);
33803
34164
  if (this.observationConfig.bufferTokens !== void 0) {
@@ -33925,18 +34286,19 @@ var ObservationalMemory = class ObservationalMemory {
33925
34286
  };
33926
34287
  }
33927
34288
  /**
33928
- * Persist a data-om-* marker part on the last assistant message in messageList
33929
- * AND save the updated message to the DB so it survives page reload.
33930
- * (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.
33931
34292
  * @internal Used by ReflectorRunner. Do not call directly.
33932
34293
  */
33933
34294
  async persistMarkerToMessage(marker, messageList, threadId, resourceId) {
33934
- if (!messageList) return;
33935
- 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";
33936
34299
  for (let i = allMsgs.length - 1; i >= 0; i--) {
33937
34300
  const msg = allMsgs[i];
33938
- if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
33939
- const markerData = marker.data;
34301
+ if (msg?.role === targetRole && msg.content?.parts && Array.isArray(msg.content.parts)) {
33940
34302
  if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
33941
34303
  try {
33942
34304
  await this.messageHistory.persistMessages({
@@ -33946,16 +34308,19 @@ var ObservationalMemory = class ObservationalMemory {
33946
34308
  });
33947
34309
  } catch (e) {
33948
34310
  omDebug(`[OM:persistMarker] failed to save marker to DB: ${e}`);
34311
+ return false;
33949
34312
  }
33950
- return;
34313
+ return true;
33951
34314
  }
33952
34315
  }
34316
+ return false;
33953
34317
  }
33954
34318
  /**
33955
- * Persist a marker to the last assistant message in storage.
33956
- * Unlike persistMarkerToMessage, this fetches messages directly from the DB
33957
- * so it works even when no MessageList is available (e.g. async buffering ops).
33958
- * @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.
33959
34324
  */
33960
34325
  async persistMarkerToStorage(marker, threadId, resourceId) {
33961
34326
  try {
@@ -33967,8 +34332,9 @@ var ObservationalMemory = class ObservationalMemory {
33967
34332
  direction: "DESC"
33968
34333
  }
33969
34334
  }))?.messages ?? [];
33970
- for (const msg of messages) if (msg?.role === "assistant" && msg.content?.parts && Array.isArray(msg.content.parts)) {
33971
- 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)) {
33972
34338
  if (!(markerData?.cycleId && msg.content.parts.some((p) => p?.type === marker.type && p?.data?.cycleId === markerData.cycleId))) msg.content.parts.push(marker);
33973
34339
  await this.messageHistory.persistMessages({
33974
34340
  messages: [msg],
@@ -34210,9 +34576,9 @@ var ObservationalMemory = class ObservationalMemory {
34210
34576
  * In resource scope mode, filters continuity messages to only show
34211
34577
  * the message for the current thread.
34212
34578
  */
34213
- formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false) {
34579
+ formatObservationsForContext(observations, currentTask, suggestedResponse, extractedValues, unobservedContextBlocks, currentDate, retrieval = false, timeZone) {
34214
34580
  let optimized = retrieval ? renderObservationGroupsForReflection(observations) ?? optimizeObservationsForContext(observations) : optimizeObservationsForContext(observations);
34215
- if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate);
34581
+ if (currentDate) optimized = addRelativeTimeToObservations(optimized, currentDate, timeZone);
34216
34582
  const messages = [`${getObservationContextPrompt(this.scope)}\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}${retrieval ? `\n\n${getRetrievalInstructions(this.retrievalScope, this.retrievalInstructions, this.retrievalSearch)}` : ""}`];
34217
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`);
34218
34584
  const observationChunks = this.splitObservationContextChunks(optimized);
@@ -34312,12 +34678,15 @@ var ObservationalMemory = class ObservationalMemory {
34312
34678
  * These are injected into the Actor's context so it has awareness of activity
34313
34679
  * in other threads for the same resource.
34314
34680
  */
34315
- async formatUnobservedContextBlocks(messagesByThread, currentThreadId) {
34681
+ async formatUnobservedContextBlocks(messagesByThread, currentThreadId, timeZone) {
34316
34682
  const blocks = [];
34317
34683
  for (const [threadId, messages] of messagesByThread) {
34318
34684
  if (threadId === currentThreadId) continue;
34319
34685
  if (messages.length === 0) continue;
34320
- const formattedMessages = formatMessagesForObserver(messages, { maxPartLength: 500 });
34686
+ const formattedMessages = formatMessagesForObserver(messages, {
34687
+ maxPartLength: 500,
34688
+ timeZone
34689
+ });
34321
34690
  if (formattedMessages) {
34322
34691
  const obscuredId = await this.representThreadIDInContext(threadId);
34323
34692
  blocks.push(`<other-conversation id="${obscuredId}">
@@ -34494,7 +34863,7 @@ ${formattedMessages}
34494
34863
  transient: true
34495
34864
  }).catch(() => {});
34496
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(",")}`);
34497
- await this.runBufferedObservationCycle({
34866
+ if ((await this.runBufferedObservationCycle({
34498
34867
  threadId,
34499
34868
  resourceId: freshRecord.resourceId ?? void 0,
34500
34869
  trigger: "async-buffer"
@@ -34509,10 +34878,11 @@ ${formattedMessages}
34509
34878
  requestContext,
34510
34879
  observabilityContext,
34511
34880
  trigger: "async-buffer"
34512
- }).run());
34513
- const maxTs = this.getMaxMessageTimestamp(messagesToBuffer);
34514
- const cursor = new Date(maxTs.getTime() + 1);
34515
- 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
+ }
34516
34886
  }
34517
34887
  /**
34518
34888
  * Trigger async buffered observation if the token count has crossed a new interval.
@@ -34740,7 +35110,7 @@ ${formattedMessages}
34740
35110
  const currentTask = activeExtractors.some((extractor) => extractor.slug === "current-task") ? omMetadata?.currentTask : void 0;
34741
35111
  const suggestedResponse = activeExtractors.some((extractor) => extractor.slug === "suggested-response") ? omMetadata?.suggestedResponse : void 0;
34742
35112
  const currentDate = opts.currentDate ?? /* @__PURE__ */ new Date();
34743
- 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);
34744
35114
  }
34745
35115
  /**
34746
35116
  * Get unobserved messages from other threads for resource-scoped observation.
@@ -34752,7 +35122,8 @@ ${formattedMessages}
34752
35122
  async getOtherThreadsContext(resourceId, currentThreadId) {
34753
35123
  const { threads: allThreads } = await this.storage.listThreads({ filter: { resourceId } });
34754
35124
  const messagesByThread = /* @__PURE__ */ new Map();
34755
- const recordLastObservedAt = (await this.getRecord(currentThreadId, resourceId))?.lastObservedAt;
35125
+ const record = await this.getRecord(currentThreadId, resourceId);
35126
+ const recordLastObservedAt = record?.lastObservedAt;
34756
35127
  for (const thread of allThreads) {
34757
35128
  if (thread.id === currentThreadId) continue;
34758
35129
  const threadLastObservedAt = getThreadOMMetadata(thread.metadata)?.lastObservedAt ?? recordLastObservedAt;
@@ -34769,7 +35140,7 @@ ${formattedMessages}
34769
35140
  if (filtered.length > 0) messagesByThread.set(thread.id, filtered);
34770
35141
  }
34771
35142
  if (messagesByThread.size === 0) return void 0;
34772
- return await this.formatUnobservedContextBlocks(messagesByThread, currentThreadId) || void 0;
35143
+ return await this.formatUnobservedContextBlocks(messagesByThread, currentThreadId, record?.observedTimezone) || void 0;
34773
35144
  }
34774
35145
  /**
34775
35146
  * Emit debug event and stream progress for UI feedback.
@@ -35112,7 +35483,7 @@ ${formattedMessages}
35112
35483
  ...startMarker,
35113
35484
  transient: true
35114
35485
  }).catch(() => {});
35115
- await this.runBufferedObservationCycle({
35486
+ if (!(await this.runBufferedObservationCycle({
35116
35487
  threadId,
35117
35488
  resourceId: record.resourceId ?? resourceId,
35118
35489
  trigger: "async-buffer"
@@ -35131,7 +35502,10 @@ ${formattedMessages}
35131
35502
  currentModel: opts.currentModel,
35132
35503
  observabilityContext,
35133
35504
  trigger: "async-buffer"
35134
- }).run());
35505
+ }).run()))?.observed) return {
35506
+ buffered: false,
35507
+ record
35508
+ };
35135
35509
  if (isOmReproCaptureEnabled()) writeObserverExchangeReproCapture({
35136
35510
  threadId,
35137
35511
  resourceId: record.resourceId ?? void 0,
@@ -35491,6 +35865,7 @@ ${formattedMessages}
35491
35865
  observed = result.observed;
35492
35866
  observationUsage = result.usage;
35493
35867
  observationProviderMetadata = result.providerMetadata;
35868
+ observationError = result.error;
35494
35869
  });
35495
35870
  } catch (error) {
35496
35871
  lifecycleError = error;
@@ -37401,6 +37776,7 @@ ${workingMemory}`;
37401
37776
  model: omConfig.model,
37402
37777
  mastra: this._mastraInstance,
37403
37778
  onIndexObservations,
37779
+ onDebugEvent: omConfig.onDebugEvent,
37404
37780
  hooks: omConfig.hooks,
37405
37781
  observation: omConfig.observation ? {
37406
37782
  model: omConfig.observation.model,
@@ -37417,10 +37793,14 @@ ${workingMemory}`;
37417
37793
  threadTitle: omConfig.observation.threadTitle,
37418
37794
  observeAttachments: omConfig.observation.observeAttachments,
37419
37795
  continuationHints: omConfig.observation.continuationHints,
37796
+ maxRetries: omConfig.observation.maxRetries,
37797
+ failurePolicy: omConfig.observation.failurePolicy,
37420
37798
  extract: omConfig.observation.extract
37421
37799
  } : void 0,
37422
37800
  reflection: omConfig.reflection ? {
37423
37801
  model: omConfig.reflection.model,
37802
+ maxRetries: omConfig.reflection.maxRetries,
37803
+ failurePolicy: omConfig.reflection.failurePolicy,
37424
37804
  observationTokens: omConfig.reflection.observationTokens,
37425
37805
  modelSettings: omConfig.reflection.modelSettings,
37426
37806
  providerOptions: omConfig.reflection.providerOptions,
@@ -49270,4 +49650,4 @@ const agentBuilderWorkflows = {
49270
49650
  //#endregion
49271
49651
  export { agentBuilderWorkflows };
49272
49652
 
49273
- //# sourceMappingURL=dist-ABdCIqbD.js.map
49653
+ //# sourceMappingURL=dist-CYk5xS3y.js.map