{"version":3,"file":"resolve-observers.mjs","names":[],"sources":["../../../../../../../ai/src/observe/resolve-observers.ts"],"sourcesContent":["import { isNestedRun } from \"../utils/run-context\";\nimport { getObservers, isObserveAll } from \"./observer-registry\";\nimport type { Observer } from \"./observer.contract\";\n\n/**\n * The value a flow's `observe` config option may take. Additive and\n * gated — when `undefined` (the default), behavior follows the global\n * observe-all flag, so a flow that never sets `observe` behaves exactly\n * as before unless an observability tool turned observe-all on.\n *\n * - `true`  → route this flow to the globally registered observers,\n *   even when observe-all is off.\n * - `false` → opt this flow out entirely, even when observe-all is on.\n * - an {@link Observer} object → a flow-local collector; only this flow's\n *   report is routed, and only to it (the global observers are skipped).\n *   A panoptic flow-local collector implements `Observer`, so it can be\n *   passed here directly — core stays panoptic-agnostic.\n * - `undefined` → follow the global observe-all flag.\n */\nexport type FlowObserveOption = boolean | Observer;\n\n/**\n * Resolve a flow's `observe` option into the concrete list of\n * {@link Observer}s to notify with that flow's completed report:\n *\n * - `false` → `[]` (opted out).\n * - `true` → the globally registered observers.\n * - an `Observer` object → just that one (flow-local).\n * - `undefined` → the globally registered observers when observe-all is\n *   on AND this is a ROOT run, otherwise `[]`.\n *\n * Reads the ambient {@link currentRunFrame} for the observe-all path:\n * flows call it at completion, so a present frame means the run is nested\n * inside an orchestration callback and is already attached to its parent's\n * report tree — self-routing it again would double-count it as a separate\n * top-level trace (and double its tokens/cost in the aggregate). Explicit\n * `observe: true` / an `Observer` still route regardless of nesting.\n */\nexport function resolveObservers(observe: FlowObserveOption | undefined): readonly Observer[] {\n  if (observe === false) {\n    return [];\n  }\n\n  if (observe === true) {\n    return getObservers();\n  }\n\n  if (observe !== undefined) {\n    return [observe];\n  }\n\n  // Observe-all captures ROOT runs only. A run nested inside any parent\n  // capture (orchestration callback, supervisor member dispatch, workflow\n  // step) already nests in its parent's report, so routing it here too would\n  // duplicate it as a standalone top-level trace.\n  return isObserveAll() && !isNestedRun() ? getObservers() : [];\n}\n\n/**\n * Observers whose `collect()` already threw once — so the isolate-but-\n * surface warning fires at most once per observer object, never spamming\n * the log when every flow report hits the same broken exporter. Keyed by\n * object identity via a {@link WeakSet} so a discarded observer is GC'd\n * without leaking. Mirrors panoptic's per-exporter `warnedExporters`.\n */\nconst warnedObservers = new WeakSet<Observer>();\n\n/**\n * Route a completed flow report to every observer the flow's `observe`\n * option resolves to. Each `collect` is awaited so async exporters\n * finish before the flow returns; a throw is **isolated** (never breaks\n * the run) but no longer **silent** — it is surfaced via `onError` when\n * supplied, otherwise a `console.warn` once per observer. A broken\n * observer/exporter must not disappear from production with no signal\n * (C5). Adopts the isolate-but-surface pattern panoptic's collector\n * already uses for exporters.\n */\nexport async function notifyObservers(\n  observe: FlowObserveOption | undefined,\n  report: Parameters<Observer[\"collect\"]>[0],\n  onError?: (error: unknown, observer: Observer) => void,\n): Promise<void> {\n  for (const observer of resolveObservers(observe)) {\n    try {\n      await observer.collect(report);\n    } catch (error) {\n      surfaceObserverError(observer, error, onError);\n    }\n  }\n}\n\n/**\n * Surface an isolated observer failure without ever rethrowing into the\n * flow. Prefers the caller-supplied `onError` (itself guarded so a\n * throwing handler can't escape); otherwise warns once per observer.\n */\nfunction surfaceObserverError(\n  observer: Observer,\n  error: unknown,\n  onError?: (error: unknown, observer: Observer) => void,\n): void {\n  if (onError) {\n    try {\n      onError(error, observer);\n    } catch {\n      // Never let the error handler itself escape into the flow.\n    }\n    return;\n  }\n\n  if (warnedObservers.has(observer)) return;\n  warnedObservers.add(observer);\n\n  const message = error instanceof Error ? error.message : String(error);\n  console.warn(`[warlock-ai] an observer's collect() threw and was isolated: ${message}`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,iBAAiB,SAA6D;CAC5F,IAAI,YAAY,OACd,OAAO,CAAC;CAGV,IAAI,YAAY,MACd,OAAO,aAAa;CAGtB,IAAI,YAAY,QACd,OAAO,CAAC,OAAO;CAOjB,OAAO,aAAa,KAAK,CAAC,YAAY,IAAI,aAAa,IAAI,CAAC;AAC9D;;;;;;;;AASA,MAAM,kCAAkB,IAAI,QAAkB;;;;;;;;;;;AAY9C,eAAsB,gBACpB,SACA,QACA,SACe;CACf,KAAK,MAAM,YAAY,iBAAiB,OAAO,GAC7C,IAAI;EACF,MAAM,SAAS,QAAQ,MAAM;CAC/B,SAAS,OAAO;EACd,qBAAqB,UAAU,OAAO,OAAO;CAC/C;AAEJ;;;;;;AAOA,SAAS,qBACP,UACA,OACA,SACM;CACN,IAAI,SAAS;EACX,IAAI;GACF,QAAQ,OAAO,QAAQ;EACzB,QAAQ,CAER;EACA;CACF;CAEA,IAAI,gBAAgB,IAAI,QAAQ,GAAG;CACnC,gBAAgB,IAAI,QAAQ;CAE5B,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CACrE,QAAQ,KAAK,gEAAgE,SAAS;AACxF"}