@warlock.js/ai-panoptic 4.4.0 → 4.6.0

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 (93) hide show
  1. package/CHANGELOG.md +39 -31
  2. package/cjs/index.cjs +2393 -31
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/collector/collector.d.mts +19 -2
  5. package/esm/collector/collector.d.mts.map +1 -1
  6. package/esm/collector/collector.mjs +40 -6
  7. package/esm/collector/collector.mjs.map +1 -1
  8. package/esm/collector/content-capture.type.d.mts +10 -0
  9. package/esm/collector/content-capture.type.d.mts.map +1 -1
  10. package/esm/collector/extract-span-attributes.d.mts.map +1 -1
  11. package/esm/collector/extract-span-attributes.mjs +3 -0
  12. package/esm/collector/extract-span-attributes.mjs.map +1 -1
  13. package/esm/collector/index.d.mts +1 -1
  14. package/esm/collector/index.mjs +7 -0
  15. package/esm/collector/normalize-error.d.mts.map +1 -1
  16. package/esm/collector/normalize-error.mjs +6 -3
  17. package/esm/collector/normalize-error.mjs.map +1 -1
  18. package/esm/collector/report-to-span.mjs +31 -10
  19. package/esm/collector/report-to-span.mjs.map +1 -1
  20. package/esm/collector/report-to-trace.d.mts +8 -5
  21. package/esm/collector/report-to-trace.d.mts.map +1 -1
  22. package/esm/collector/report-to-trace.mjs +8 -5
  23. package/esm/collector/report-to-trace.mjs.map +1 -1
  24. package/esm/config/apply-panoptic-config.d.mts +27 -0
  25. package/esm/config/apply-panoptic-config.d.mts.map +1 -0
  26. package/esm/config/apply-panoptic-config.mjs +89 -0
  27. package/esm/config/apply-panoptic-config.mjs.map +1 -0
  28. package/esm/config/index.d.mts +2 -0
  29. package/esm/config/index.mjs +3 -0
  30. package/esm/config/panoptic-config.type.d.mts +84 -0
  31. package/esm/config/panoptic-config.type.d.mts.map +1 -0
  32. package/esm/contracts/trace.type.d.mts +9 -2
  33. package/esm/contracts/trace.type.d.mts.map +1 -1
  34. package/esm/dashboard/dashboard.d.mts +32 -0
  35. package/esm/dashboard/dashboard.d.mts.map +1 -0
  36. package/esm/dashboard/dashboard.mjs +132 -0
  37. package/esm/dashboard/dashboard.mjs.map +1 -0
  38. package/esm/dashboard/dashboard.type.d.mts +63 -0
  39. package/esm/dashboard/dashboard.type.d.mts.map +1 -0
  40. package/esm/dashboard/index.d.mts +3 -0
  41. package/esm/dashboard/parse-query.d.mts +1 -0
  42. package/esm/dashboard/parse-query.mjs +54 -0
  43. package/esm/dashboard/parse-query.mjs.map +1 -0
  44. package/esm/dashboard/serve.d.mts +1 -0
  45. package/esm/dashboard/serve.mjs +111 -0
  46. package/esm/dashboard/serve.mjs.map +1 -0
  47. package/esm/dashboard/trace-filter.d.mts +187 -0
  48. package/esm/dashboard/trace-filter.d.mts.map +1 -0
  49. package/esm/dashboard/trace-filter.mjs +270 -0
  50. package/esm/dashboard/trace-filter.mjs.map +1 -0
  51. package/esm/dashboard/ui.html.mjs +1330 -0
  52. package/esm/dashboard/ui.html.mjs.map +1 -0
  53. package/esm/dashboard/warlock-logo.mjs +13 -0
  54. package/esm/dashboard/warlock-logo.mjs.map +1 -0
  55. package/esm/exporters/file/file-exporter.mjs +5 -2
  56. package/esm/exporters/file/file-exporter.mjs.map +1 -1
  57. package/esm/exporters/langfuse/langfuse-exporter.d.mts.map +1 -1
  58. package/esm/exporters/langfuse/langfuse-exporter.mjs +5 -2
  59. package/esm/exporters/langfuse/langfuse-exporter.mjs.map +1 -1
  60. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts +3 -1
  61. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts.map +1 -1
  62. package/esm/exporters/otel/otel-api.shim.type.d.mts +60 -0
  63. package/esm/exporters/otel/otel-api.shim.type.d.mts.map +1 -0
  64. package/esm/exporters/otel/otel-exporter.d.mts.map +1 -1
  65. package/esm/exporters/otel/otel-exporter.mjs.map +1 -1
  66. package/esm/exporters/otel/otel-exporter.type.d.mts +7 -3
  67. package/esm/exporters/otel/otel-exporter.type.d.mts.map +1 -1
  68. package/esm/index.d.mts +9 -2
  69. package/esm/index.mjs +9 -1
  70. package/esm/panoptic/index.mjs +4 -0
  71. package/esm/panoptic/panoptic.d.mts.map +1 -1
  72. package/esm/panoptic/panoptic.mjs +6 -1
  73. package/esm/panoptic/panoptic.mjs.map +1 -1
  74. package/esm/panoptic/panoptic.type.d.mts +15 -0
  75. package/esm/panoptic/panoptic.type.d.mts.map +1 -1
  76. package/esm/register.mjs +12 -0
  77. package/esm/register.mjs.map +1 -0
  78. package/esm/store/cache-trace-store.d.mts +81 -0
  79. package/esm/store/cache-trace-store.d.mts.map +1 -0
  80. package/esm/store/cache-trace-store.mjs +290 -0
  81. package/esm/store/cache-trace-store.mjs.map +1 -0
  82. package/esm/store/in-memory-trace-store.mjs +9 -2
  83. package/esm/store/in-memory-trace-store.mjs.map +1 -1
  84. package/esm/store/index.d.mts +1 -0
  85. package/esm/store/index.mjs +1 -0
  86. package/llms-full.txt +351 -4
  87. package/llms.txt +4 -3
  88. package/package.json +6 -2
  89. package/skills/README.md +7 -3
  90. package/skills/export-traces/SKILL.md +33 -1
  91. package/skills/observe-with-panoptic/SKILL.md +49 -2
  92. package/skills/query-traces/SKILL.md +52 -1
  93. package/skills/use-local-dashboard/SKILL.md +213 -0
@@ -2,6 +2,23 @@ import { CollectorContract } from "../contracts/collector.contract.mjs";
2
2
  import { ContentCaptureOptions } from "./content-capture.type.mjs";
3
3
 
4
4
  //#region ../@warlock.js/ai-panoptic/src/collector/collector.d.ts
5
+ /**
6
+ * Notified when a registered exporter throws during `collect` / `flush` /
7
+ * `shutdown`. The failure stays isolated (the originating run never sees
8
+ * it) — this is purely a chance to surface it (route to your logger,
9
+ * bump a metric). Receives the exporter's `name` and the thrown error.
10
+ */
11
+ type ExporterErrorHandler = (exporterName: string, error: unknown) => void;
12
+ /** Options for {@link createCollector}. */
13
+ type CollectorOptions = ContentCaptureOptions & {
14
+ /**
15
+ * Called when an exporter throws (and is isolated). Overrides the default
16
+ * one-time `console.warn` per exporter — route the failure to your own
17
+ * logger / metrics instead. Errors thrown by the handler itself are
18
+ * swallowed so observability never crashes the run.
19
+ */
20
+ onError?: ExporterErrorHandler;
21
+ };
5
22
  /**
6
23
  * Create a Panoptic collector — the single integration point an app
7
24
  * wires into its agents/workflows (typically via the `onComplete`
@@ -14,7 +31,7 @@ import { ContentCaptureOptions } from "./content-capture.type.mjs";
14
31
  * // on shutdown:
15
32
  * await collector.shutdown();
16
33
  */
17
- declare function createCollector(options?: ContentCaptureOptions): CollectorContract;
34
+ declare function createCollector(options?: CollectorOptions): CollectorContract;
18
35
  //#endregion
19
- export { createCollector };
36
+ export { CollectorOptions, ExporterErrorHandler, createCollector };
20
37
  //# sourceMappingURL=collector.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"collector.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/collector.ts"],"mappings":";;;;;;AAsHA;;;;;;;;AAAuF;;iBAAvE,eAAA,CAAgB,OAAA,GAAS,qBAAA,GAA6B,iBAAiB"}
1
+ {"version":3,"file":"collector.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/collector.ts"],"mappings":";;;;;;AAcA;;;;KAAY,oBAAA,IAAwB,YAAA,UAAsB,KAAc;AAGxE;AAAA,KAAY,gBAAA,GAAmB,qBAAA;;;;;;;EAO7B,OAAA,GAAU,oBAAoB;AAAA;;;;;;;;AAyJkD;;;;;iBAAlE,eAAA,CAAgB,OAAA,GAAS,gBAAA,GAAwB,iBAAiB"}
@@ -16,17 +16,31 @@ import { reportToTrace } from "./report-to-trace.mjs";
16
16
  * into the originating run — `collect`, `flush`, and `shutdown` settle
17
17
  * every exporter independently (mirrors how the core event hooks
18
18
  * swallow consumer errors). One broken exporter can neither crash the
19
- * agent loop nor stop sibling exporters from receiving the trace.
19
+ * agent loop nor stop sibling exporters from receiving the trace. The
20
+ * failure is still **surfaced** — via the `onError` option, or a one-time
21
+ * `console.warn` per exporter — so a misconfiguration (e.g. a missing
22
+ * optional peer like `langfuse`) doesn't fail silently.
20
23
  */
21
24
  var Collector = class {
25
+ options;
22
26
  /**
23
- * Content-capture options threaded into every `toTrace` projection.
24
- * When `captureContent` is set, projected spans carry `input` /
25
- * `output`; otherwise they stay metadata-only.
27
+ * Registered exporters in insertion order. A `Map` keyed by
28
+ * `ExporterContract.name` gives O(1) dedupe on `use` while preserving
29
+ * registration order for deterministic fan-out.
30
+ */
31
+ exporters = /* @__PURE__ */ new Map();
32
+ /**
33
+ * Exporters already warned about on the default error path — so a
34
+ * persistent misconfiguration surfaces once, not on every trace.
35
+ */
36
+ warnedExporters = /* @__PURE__ */ new Set();
37
+ /**
38
+ * Collector options: content capture threaded into every `toTrace`
39
+ * projection (`captureContent` populates span `input` / `output`), plus
40
+ * an optional `onError` for isolated exporter failures.
26
41
  */
27
42
  constructor(options = {}) {
28
43
  this.options = options;
29
- this.exporters = /* @__PURE__ */ new Map();
30
44
  }
31
45
  use(exporter) {
32
46
  if (!this.exporters.has(exporter.name)) this.exporters.set(exporter.name, exporter);
@@ -70,9 +84,29 @@ var Collector = class {
70
84
  * others — the contract's failure-isolation requirement.
71
85
  */
72
86
  async settleAll(task) {
73
- const runs = [...this.exporters.values()].map((exporter) => Promise.resolve().then(() => task(exporter)));
87
+ const runs = [...this.exporters.entries()].map(([name, exporter]) => Promise.resolve().then(() => task(exporter)).catch((error) => this.reportExporterError(name, error)));
74
88
  await Promise.allSettled(runs);
75
89
  }
90
+ /**
91
+ * Surface an isolated exporter failure. The originating run never sees it
92
+ * (the isolation guarantee holds), but a silent failure is the wrong
93
+ * default for a config error — e.g. a missing optional peer like
94
+ * `langfuse` would otherwise drop every trace with no signal. The
95
+ * supplied `onError` is called, or — by default — a `console.warn` is
96
+ * emitted ONCE per exporter so the cause is visible without spamming.
97
+ */
98
+ reportExporterError(name, error) {
99
+ if (this.options.onError) {
100
+ try {
101
+ this.options.onError(name, error);
102
+ } catch {}
103
+ return;
104
+ }
105
+ if (this.warnedExporters.has(name)) return;
106
+ this.warnedExporters.add(name);
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ console.warn(`[panoptic] exporter "${name}" failed and was isolated: ${message}`);
109
+ }
76
110
  };
77
111
  /**
78
112
  * Create a Panoptic collector — the single integration point an app
@@ -1 +1 @@
1
- {"version":3,"file":"collector.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/collector.ts"],"sourcesContent":["import type { BaseReport } from \"@warlock.js/ai\";\nimport type { CollectorContract } from \"../contracts/collector.contract\";\nimport type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { walkSpans } from \"../exporters/utils/walk-spans\";\nimport type { ContentCaptureOptions } from \"./content-capture.type\";\nimport { reportToTrace } from \"./report-to-trace\";\n\n/**\n * Drive the source end of the Panoptic pipeline: ingest core\n * `@warlock.js/ai` {@link BaseReport} trees, project them into {@link\n * Trace}s, and fan each trace out to every registered exporter.\n *\n * Owns exporter registration (deduped by `ExporterContract.name`), the\n * report→trace projection, and graceful shutdown so exporters drain\n * before exit. Instantiated fresh per collector via {@link\n * createCollector}; callers never see `new`.\n *\n * **Failure isolation.** An exporter that throws never propagates back\n * into the originating run — `collect`, `flush`, and `shutdown` settle\n * every exporter independently (mirrors how the core event hooks\n * swallow consumer errors). One broken exporter can neither crash the\n * agent loop nor stop sibling exporters from receiving the trace.\n */\nclass Collector implements CollectorContract {\n /**\n * Registered exporters in insertion order. A `Map` keyed by\n * `ExporterContract.name` gives O(1) dedupe on `use` while preserving\n * registration order for deterministic fan-out.\n */\n private readonly exporters = new Map<string, ExporterContract>();\n\n /**\n * Content-capture options threaded into every `toTrace` projection.\n * When `captureContent` is set, projected spans carry `input` /\n * `output`; otherwise they stay metadata-only.\n */\n public constructor(private readonly options: ContentCaptureOptions = {}) {}\n\n public use(exporter: ExporterContract): this {\n if (!this.exporters.has(exporter.name)) {\n this.exporters.set(exporter.name, exporter);\n }\n\n return this;\n }\n\n public toTrace(report: BaseReport, rootError?: unknown): Trace {\n return reportToTrace(report, rootError, this.options);\n }\n\n public async collect(report: BaseReport, rootError?: unknown): Promise<void> {\n const trace = this.toTrace(report, rootError);\n\n await this.dispatch(trace);\n }\n\n public async flush(): Promise<void> {\n await this.settleAll((exporter) => exporter.flush?.());\n }\n\n public async shutdown(): Promise<void> {\n await this.flush();\n\n await this.settleAll((exporter) => exporter.shutdown?.());\n\n this.exporters.clear();\n }\n\n /**\n * Fan one trace out to every exporter and, when an exporter advertises\n * the per-span hook, deliver every span in the finalized tree to it as\n * well. `exportSpan` is a post-completion per-span hook (not a live /\n * streaming feed — the trace is already finalized): we walk the tree in\n * pre-order with {@link walkSpans} so the exporter sees the root and\n * every descendant exactly once. Every invocation is isolated so a\n * throwing exporter can't abort the dispatch to its siblings or escape\n * into the originating run.\n */\n private async dispatch(trace: Trace): Promise<void> {\n await this.settleAll(async (exporter) => {\n await exporter.export(trace);\n\n if (exporter.exportSpan !== undefined) {\n for (const span of walkSpans(trace.root)) {\n await exporter.exportSpan(span);\n }\n }\n });\n }\n\n /**\n * Run `task` against every registered exporter and wait for all of\n * them to settle, swallowing individual rejections. `Promise.allSettled`\n * guarantees one failure neither rejects the batch nor blocks the\n * others — the contract's failure-isolation requirement.\n */\n private async settleAll(\n task: (exporter: ExporterContract) => void | Promise<void>,\n ): Promise<void> {\n const runs = [...this.exporters.values()].map((exporter) => Promise.resolve().then(() => task(exporter)));\n\n await Promise.allSettled(runs);\n }\n}\n\n/**\n * Create a Panoptic collector — the single integration point an app\n * wires into its agents/workflows (typically via the `onComplete`\n * report hook). Register exporters with `use`, then feed finalized root\n * reports to `collect`.\n *\n * @example\n * const collector = createCollector().use(otelExporter).use(langfuseExporter);\n * agent.on(\"onComplete\", ({ result }) => collector.collect(result.report));\n * // on shutdown:\n * await collector.shutdown();\n */\nexport function createCollector(options: ContentCaptureOptions = {}): CollectorContract {\n return new Collector(options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwBA,IAAM,YAAN,MAA6C;;;;;;CAa3C,AAAO,YAAY,AAAiB,UAAiC,CAAC,GAAG;EAArC;mCAPP,IAAI,IAA8B;CAOW;CAE1E,AAAO,IAAI,UAAkC;EAC3C,IAAI,CAAC,KAAK,UAAU,IAAI,SAAS,IAAI,GACnC,KAAK,UAAU,IAAI,SAAS,MAAM,QAAQ;EAG5C,OAAO;CACT;CAEA,AAAO,QAAQ,QAAoB,WAA4B;EAC7D,OAAO,cAAc,QAAQ,WAAW,KAAK,OAAO;CACtD;CAEA,MAAa,QAAQ,QAAoB,WAAoC;EAC3E,MAAM,QAAQ,KAAK,QAAQ,QAAQ,SAAS;EAE5C,MAAM,KAAK,SAAS,KAAK;CAC3B;CAEA,MAAa,QAAuB;EAClC,MAAM,KAAK,WAAW,aAAa,SAAS,QAAQ,CAAC;CACvD;CAEA,MAAa,WAA0B;EACrC,MAAM,KAAK,MAAM;EAEjB,MAAM,KAAK,WAAW,aAAa,SAAS,WAAW,CAAC;EAExD,KAAK,UAAU,MAAM;CACvB;;;;;;;;;;;CAYA,MAAc,SAAS,OAA6B;EAClD,MAAM,KAAK,UAAU,OAAO,aAAa;GACvC,MAAM,SAAS,OAAO,KAAK;GAE3B,IAAI,SAAS,eAAe,QAC1B,KAAK,MAAM,QAAQ,UAAU,MAAM,IAAI,GACrC,MAAM,SAAS,WAAW,IAAI;EAGpC,CAAC;CACH;;;;;;;CAQA,MAAc,UACZ,MACe;EACf,MAAM,OAAO,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,CAAC,CAAC,KAAK,aAAa,QAAQ,QAAQ,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC;EAExG,MAAM,QAAQ,WAAW,IAAI;CAC/B;AACF;;;;;;;;;;;;;AAcA,SAAgB,gBAAgB,UAAiC,CAAC,GAAsB;CACtF,OAAO,IAAI,UAAU,OAAO;AAC9B"}
1
+ {"version":3,"file":"collector.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/collector.ts"],"sourcesContent":["import type { BaseReport } from \"@warlock.js/ai\";\nimport type { CollectorContract } from \"../contracts/collector.contract\";\nimport type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { walkSpans } from \"../exporters/utils/walk-spans\";\nimport type { ContentCaptureOptions } from \"./content-capture.type\";\nimport { reportToTrace } from \"./report-to-trace\";\n\n/**\n * Notified when a registered exporter throws during `collect` / `flush` /\n * `shutdown`. The failure stays isolated (the originating run never sees\n * it) — this is purely a chance to surface it (route to your logger,\n * bump a metric). Receives the exporter's `name` and the thrown error.\n */\nexport type ExporterErrorHandler = (exporterName: string, error: unknown) => void;\n\n/** Options for {@link createCollector}. */\nexport type CollectorOptions = ContentCaptureOptions & {\n /**\n * Called when an exporter throws (and is isolated). Overrides the default\n * one-time `console.warn` per exporter — route the failure to your own\n * logger / metrics instead. Errors thrown by the handler itself are\n * swallowed so observability never crashes the run.\n */\n onError?: ExporterErrorHandler;\n};\n\n/**\n * Drive the source end of the Panoptic pipeline: ingest core\n * `@warlock.js/ai` {@link BaseReport} trees, project them into {@link\n * Trace}s, and fan each trace out to every registered exporter.\n *\n * Owns exporter registration (deduped by `ExporterContract.name`), the\n * report→trace projection, and graceful shutdown so exporters drain\n * before exit. Instantiated fresh per collector via {@link\n * createCollector}; callers never see `new`.\n *\n * **Failure isolation.** An exporter that throws never propagates back\n * into the originating run — `collect`, `flush`, and `shutdown` settle\n * every exporter independently (mirrors how the core event hooks\n * swallow consumer errors). One broken exporter can neither crash the\n * agent loop nor stop sibling exporters from receiving the trace. The\n * failure is still **surfaced** — via the `onError` option, or a one-time\n * `console.warn` per exporter — so a misconfiguration (e.g. a missing\n * optional peer like `langfuse`) doesn't fail silently.\n */\nclass Collector implements CollectorContract {\n /**\n * Registered exporters in insertion order. A `Map` keyed by\n * `ExporterContract.name` gives O(1) dedupe on `use` while preserving\n * registration order for deterministic fan-out.\n */\n private readonly exporters = new Map<string, ExporterContract>();\n\n /**\n * Exporters already warned about on the default error path — so a\n * persistent misconfiguration surfaces once, not on every trace.\n */\n private readonly warnedExporters = new Set<string>();\n\n /**\n * Collector options: content capture threaded into every `toTrace`\n * projection (`captureContent` populates span `input` / `output`), plus\n * an optional `onError` for isolated exporter failures.\n */\n public constructor(private readonly options: CollectorOptions = {}) {}\n\n public use(exporter: ExporterContract): this {\n if (!this.exporters.has(exporter.name)) {\n this.exporters.set(exporter.name, exporter);\n }\n\n return this;\n }\n\n public toTrace(report: BaseReport, rootError?: unknown): Trace {\n return reportToTrace(report, rootError, this.options);\n }\n\n public async collect(report: BaseReport, rootError?: unknown): Promise<void> {\n const trace = this.toTrace(report, rootError);\n\n await this.dispatch(trace);\n }\n\n public async flush(): Promise<void> {\n await this.settleAll((exporter) => exporter.flush?.());\n }\n\n public async shutdown(): Promise<void> {\n await this.flush();\n\n await this.settleAll((exporter) => exporter.shutdown?.());\n\n this.exporters.clear();\n }\n\n /**\n * Fan one trace out to every exporter and, when an exporter advertises\n * the per-span hook, deliver every span in the finalized tree to it as\n * well. `exportSpan` is a post-completion per-span hook (not a live /\n * streaming feed — the trace is already finalized): we walk the tree in\n * pre-order with {@link walkSpans} so the exporter sees the root and\n * every descendant exactly once. Every invocation is isolated so a\n * throwing exporter can't abort the dispatch to its siblings or escape\n * into the originating run.\n */\n private async dispatch(trace: Trace): Promise<void> {\n await this.settleAll(async (exporter) => {\n await exporter.export(trace);\n\n if (exporter.exportSpan !== undefined) {\n for (const span of walkSpans(trace.root)) {\n await exporter.exportSpan(span);\n }\n }\n });\n }\n\n /**\n * Run `task` against every registered exporter and wait for all of\n * them to settle, swallowing individual rejections. `Promise.allSettled`\n * guarantees one failure neither rejects the batch nor blocks the\n * others — the contract's failure-isolation requirement.\n */\n private async settleAll(\n task: (exporter: ExporterContract) => void | Promise<void>,\n ): Promise<void> {\n const runs = [...this.exporters.entries()].map(([name, exporter]) =>\n Promise.resolve()\n .then(() => task(exporter))\n .catch((error: unknown) => this.reportExporterError(name, error)),\n );\n\n await Promise.allSettled(runs);\n }\n\n /**\n * Surface an isolated exporter failure. The originating run never sees it\n * (the isolation guarantee holds), but a silent failure is the wrong\n * default for a config error — e.g. a missing optional peer like\n * `langfuse` would otherwise drop every trace with no signal. The\n * supplied `onError` is called, or — by default — a `console.warn` is\n * emitted ONCE per exporter so the cause is visible without spamming.\n */\n private reportExporterError(name: string, error: unknown): void {\n if (this.options.onError) {\n try {\n this.options.onError(name, error);\n } catch {\n // Never let the error handler itself escape into the run.\n }\n return;\n }\n\n if (this.warnedExporters.has(name)) {\n return;\n }\n\n this.warnedExporters.add(name);\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[panoptic] exporter \"${name}\" failed and was isolated: ${message}`);\n }\n}\n\n/**\n * Create a Panoptic collector — the single integration point an app\n * wires into its agents/workflows (typically via the `onComplete`\n * report hook). Register exporters with `use`, then feed finalized root\n * reports to `collect`.\n *\n * @example\n * const collector = createCollector().use(otelExporter).use(langfuseExporter);\n * agent.on(\"onComplete\", ({ result }) => collector.collect(result.report));\n * // on shutdown:\n * await collector.shutdown();\n */\nexport function createCollector(options: CollectorOptions = {}): CollectorContract {\n return new Collector(options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA8CA,IAAM,YAAN,MAA6C;CAmBP;;;;;;CAbpC,AAAiB,4BAAY,IAAI,IAA8B;;;;;CAM/D,AAAiB,kCAAkB,IAAI,IAAY;;;;;;CAOnD,AAAO,YAAY,AAAiB,UAA4B,CAAC,GAAG;EAAhC;CAAiC;CAErE,AAAO,IAAI,UAAkC;EAC3C,IAAI,CAAC,KAAK,UAAU,IAAI,SAAS,IAAI,GACnC,KAAK,UAAU,IAAI,SAAS,MAAM,QAAQ;EAG5C,OAAO;CACT;CAEA,AAAO,QAAQ,QAAoB,WAA4B;EAC7D,OAAO,cAAc,QAAQ,WAAW,KAAK,OAAO;CACtD;CAEA,MAAa,QAAQ,QAAoB,WAAoC;EAC3E,MAAM,QAAQ,KAAK,QAAQ,QAAQ,SAAS;EAE5C,MAAM,KAAK,SAAS,KAAK;CAC3B;CAEA,MAAa,QAAuB;EAClC,MAAM,KAAK,WAAW,aAAa,SAAS,QAAQ,CAAC;CACvD;CAEA,MAAa,WAA0B;EACrC,MAAM,KAAK,MAAM;EAEjB,MAAM,KAAK,WAAW,aAAa,SAAS,WAAW,CAAC;EAExD,KAAK,UAAU,MAAM;CACvB;;;;;;;;;;;CAYA,MAAc,SAAS,OAA6B;EAClD,MAAM,KAAK,UAAU,OAAO,aAAa;GACvC,MAAM,SAAS,OAAO,KAAK;GAE3B,IAAI,SAAS,eAAe,QAC1B,KAAK,MAAM,QAAQ,UAAU,MAAM,IAAI,GACrC,MAAM,SAAS,WAAW,IAAI;EAGpC,CAAC;CACH;;;;;;;CAQA,MAAc,UACZ,MACe;EACf,MAAM,OAAO,CAAC,GAAG,KAAK,UAAU,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,cACrD,QAAQ,QAAQ,CAAC,CACd,WAAW,KAAK,QAAQ,CAAC,CAAC,CAC1B,OAAO,UAAmB,KAAK,oBAAoB,MAAM,KAAK,CAAC,CACpE;EAEA,MAAM,QAAQ,WAAW,IAAI;CAC/B;;;;;;;;;CAUA,AAAQ,oBAAoB,MAAc,OAAsB;EAC9D,IAAI,KAAK,QAAQ,SAAS;GACxB,IAAI;IACF,KAAK,QAAQ,QAAQ,MAAM,KAAK;GAClC,QAAQ,CAER;GACA;EACF;EAEA,IAAI,KAAK,gBAAgB,IAAI,IAAI,GAC/B;EAGF,KAAK,gBAAgB,IAAI,IAAI;EAC7B,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACrE,QAAQ,KAAK,wBAAwB,KAAK,6BAA6B,SAAS;CAClF;AACF;;;;;;;;;;;;;AAcA,SAAgB,gBAAgB,UAA4B,CAAC,GAAsB;CACjF,OAAO,IAAI,UAAU,OAAO;AAC9B"}
@@ -36,6 +36,16 @@ type ContentCaptureOptions = {
36
36
  */
37
37
  captureContent?: boolean; /** Optional masker applied to every captured value before it is stored. */
38
38
  redactContent?: ContentRedactor;
39
+ /**
40
+ * When `true`, emit the agent's complete `CapturedMessage[]` (every
41
+ * trip, every role) onto `span.input` instead of the `[system, user]`
42
+ * first-trip array. Requires the upstream run to set `captureMessages`
43
+ * (core `@warlock.js/ai`) so the report carries a `messages` array — a
44
+ * no-op falling back to first-trip capture when it doesn't. The whole
45
+ * array is still passed through the {@link ContentRedactor} as a single
46
+ * value, so masking applies to captured history too. Default `false`.
47
+ */
48
+ fullHistory?: boolean;
39
49
  };
40
50
  //#endregion
41
51
  export { ContentCaptureOptions, ContentRedactor };
@@ -1 +1 @@
1
- {"version":3,"file":"content-capture.type.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/content-capture.type.ts"],"mappings":";;;;;AAaA;;;;;;;;;KAAY,eAAA,IACV,KAAA,WACA,IAAA;EAAQ,IAAA;EAAc,IAAA,EAAM,UAAU;EAAE,KAAA;AAAA;;;;;;;AAsBT;;;;;;KAPrB,qBAAA;;;;;EAKV,cAAA;EAEA,aAAA,GAAgB,eAAe;AAAA"}
1
+ {"version":3,"file":"content-capture.type.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/content-capture.type.ts"],"mappings":";;;;;AAaA;;;;;;;;;KAAY,eAAA,IACV,KAAA,WACA,IAAA;EAAQ,IAAA;EAAc,IAAA,EAAM,UAAU;EAAE,KAAA;AAAA;;;;;;;;AAgC7B;;;;;KAjBD,qBAAA;;;;;EAKV,cAAA;EAEA,aAAA,GAAgB,eAAe;;;;;;;;;;EAU/B,WAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"extract-span-attributes.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/extract-span-attributes.ts"],"mappings":";;;;;AAqDA;;;;;;;;AAAiE;;;;;;;;;;;;iBAAjD,qBAAA,CAAsB,MAAA,EAAQ,UAAA,GAAa,MAAM"}
1
+ {"version":3,"file":"extract-span-attributes.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/extract-span-attributes.ts"],"mappings":";;;;;AAuDA;;;;;;;;AAAiE;;;;;;;;;;;;iBAAjD,qBAAA,CAAsB,MAAA,EAAQ,UAAA,GAAa,MAAM"}
@@ -33,6 +33,7 @@ function extractSpanAttributes(report) {
33
33
  addWorkflowAttributes(attributes, extensions);
34
34
  break;
35
35
  case "supervisor":
36
+ case "team":
36
37
  addSupervisorAttributes(attributes, extensions);
37
38
  break;
38
39
  case "orchestrator":
@@ -50,6 +51,8 @@ function addAgentAttributes(attributes, extensions) {
50
51
  if (Array.isArray(extensions.trips)) attributes["agent.trips"] = extensions.trips.length;
51
52
  if (extensions.model?.name !== void 0) attributes["agent.model.name"] = extensions.model.name;
52
53
  if (extensions.model?.provider !== void 0) attributes["agent.model.provider"] = extensions.model.provider;
54
+ if (extensions.promptName !== void 0) attributes["agent.promptName"] = extensions.promptName;
55
+ if (extensions.promptVersion !== void 0) attributes["agent.promptVersion"] = extensions.promptVersion;
53
56
  }
54
57
  function addWorkflowAttributes(attributes, extensions) {
55
58
  if (extensions.workflowName !== void 0) attributes["workflow.name"] = extensions.workflowName;
@@ -1 +1 @@
1
- {"version":3,"file":"extract-span-attributes.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/extract-span-attributes.ts"],"sourcesContent":["import type { BaseReport } from \"@warlock.js/ai\";\n\n/**\n * Per-primitive `BaseReport` extension fields the collector surfaces as\n * span attributes. Declared as a structural superset (every field\n * optional) so a single reader can pull whatever the concrete report\n * carried without narrowing on `type` first — a `BaseReport` widened to\n * this shape exposes `undefined` for the fields its primitive doesn't\n * set, and the extractor simply skips those.\n *\n * Mirrors the public per-primitive report types in `@warlock.js/ai`\n * (`AgentReport`, `WorkflowReport`, `SupervisorReport`,\n * `OrchestratorReport`, `ToolCall`) — kept structural rather than a\n * union import so adding a new optional report field upstream is a\n * non-breaking read here.\n */\ntype ReportExtensions = {\n model?: { name?: string; provider?: string };\n trips?: unknown[];\n workflowName?: string;\n supervisorName?: string;\n signature?: string;\n terminatedBy?: string;\n iterations?: number;\n steps?: Record<string, unknown>;\n turns?: unknown[];\n turnIndex?: number;\n tripIndex?: number;\n recoveredFrom?: string;\n};\n\n/**\n * Build the free-form `TraceSpan.attributes` bag for one report node.\n *\n * The collector keeps the first-class span fields (identity, timing,\n * status, usage, error) on the span itself and routes everything\n * primitive-specific here — trip/step/iteration counts, the model\n * identity an agent ran against, the tool's originating trip index, a\n * supervisor's termination reason. Exporters forward this verbatim as\n * backend span attributes (OTel attributes, Langfuse metadata).\n *\n * Only populated keys are emitted; the function returns `undefined`\n * when the node carried no extra detail, so the optional\n * `TraceSpan.attributes` field stays absent rather than holding an\n * empty object (matches the contract's \"absent when empty\" note).\n *\n * Retry count is surfaced from the shared `BaseReport.attempts` for\n * every primitive so cost dashboards see the real call count.\n *\n * @example\n * const attributes = extractSpanAttributes(agentReport);\n * // { \"agent.trips\": 3, \"agent.model.name\": \"gpt-4o\", \"agent.model.provider\": \"openai\" }\n */\nexport function extractSpanAttributes(report: BaseReport): Record<string, unknown> | undefined {\n const extensions = report as BaseReport & ReportExtensions;\n const attributes: Record<string, unknown> = {};\n\n if (report.attempts !== undefined && report.attempts.length > 0) {\n attributes[\"retries\"] = report.attempts.length;\n }\n\n switch (report.type) {\n case \"agent\": {\n addAgentAttributes(attributes, extensions);\n break;\n }\n\n case \"workflow\": {\n addWorkflowAttributes(attributes, extensions);\n break;\n }\n\n case \"supervisor\": {\n addSupervisorAttributes(attributes, extensions);\n break;\n }\n\n case \"orchestrator\": {\n addOrchestratorAttributes(attributes, extensions);\n break;\n }\n\n case \"tool\": {\n addToolAttributes(attributes, extensions);\n break;\n }\n\n default: {\n break;\n }\n }\n\n if (Object.keys(attributes).length === 0) {\n return undefined;\n }\n\n return attributes;\n}\n\nfunction addAgentAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (Array.isArray(extensions.trips)) {\n attributes[\"agent.trips\"] = extensions.trips.length;\n }\n\n if (extensions.model?.name !== undefined) {\n attributes[\"agent.model.name\"] = extensions.model.name;\n }\n\n if (extensions.model?.provider !== undefined) {\n attributes[\"agent.model.provider\"] = extensions.model.provider;\n }\n}\n\nfunction addWorkflowAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.workflowName !== undefined) {\n attributes[\"workflow.name\"] = extensions.workflowName;\n }\n\n if (extensions.signature !== undefined) {\n attributes[\"workflow.signature\"] = extensions.signature;\n }\n\n if (extensions.steps !== undefined) {\n attributes[\"workflow.steps\"] = Object.keys(extensions.steps).length;\n }\n}\n\nfunction addSupervisorAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.supervisorName !== undefined) {\n attributes[\"supervisor.name\"] = extensions.supervisorName;\n }\n\n if (extensions.terminatedBy !== undefined) {\n attributes[\"supervisor.terminatedBy\"] = extensions.terminatedBy;\n }\n\n if (extensions.iterations !== undefined) {\n attributes[\"supervisor.iterations\"] = extensions.iterations;\n }\n}\n\nfunction addOrchestratorAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.turnIndex !== undefined) {\n attributes[\"orchestrator.turnIndex\"] = extensions.turnIndex;\n }\n\n if (extensions.signature !== undefined) {\n attributes[\"orchestrator.signature\"] = extensions.signature;\n }\n\n if (Array.isArray(extensions.turns)) {\n attributes[\"orchestrator.turns\"] = extensions.turns.length;\n }\n}\n\nfunction addToolAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.tripIndex !== undefined) {\n attributes[\"tool.tripIndex\"] = extensions.tripIndex;\n }\n\n if (extensions.recoveredFrom !== undefined) {\n attributes[\"tool.recoveredFrom\"] = extensions.recoveredFrom;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqDA,SAAgB,sBAAsB,QAAyD;CAC7F,MAAM,aAAa;CACnB,MAAM,aAAsC,CAAC;CAE7C,IAAI,OAAO,aAAa,UAAa,OAAO,SAAS,SAAS,GAC5D,WAAW,aAAa,OAAO,SAAS;CAG1C,QAAQ,OAAO,MAAf;EACE,KAAK;GACH,mBAAmB,YAAY,UAAU;GACzC;EAGF,KAAK;GACH,sBAAsB,YAAY,UAAU;GAC5C;EAGF,KAAK;GACH,wBAAwB,YAAY,UAAU;GAC9C;EAGF,KAAK;GACH,0BAA0B,YAAY,UAAU;GAChD;EAGF,KAAK;GACH,kBAAkB,YAAY,UAAU;GACxC;EAGF,SACE;CAEJ;CAEA,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,WAAW,GACrC;CAGF,OAAO;AACT;AAEA,SAAS,mBAAmB,YAAqC,YAAoC;CACnG,IAAI,MAAM,QAAQ,WAAW,KAAK,GAChC,WAAW,iBAAiB,WAAW,MAAM;CAG/C,IAAI,WAAW,OAAO,SAAS,QAC7B,WAAW,sBAAsB,WAAW,MAAM;CAGpD,IAAI,WAAW,OAAO,aAAa,QACjC,WAAW,0BAA0B,WAAW,MAAM;AAE1D;AAEA,SAAS,sBAAsB,YAAqC,YAAoC;CACtG,IAAI,WAAW,iBAAiB,QAC9B,WAAW,mBAAmB,WAAW;CAG3C,IAAI,WAAW,cAAc,QAC3B,WAAW,wBAAwB,WAAW;CAGhD,IAAI,WAAW,UAAU,QACvB,WAAW,oBAAoB,OAAO,KAAK,WAAW,KAAK,CAAC,CAAC;AAEjE;AAEA,SAAS,wBAAwB,YAAqC,YAAoC;CACxG,IAAI,WAAW,mBAAmB,QAChC,WAAW,qBAAqB,WAAW;CAG7C,IAAI,WAAW,iBAAiB,QAC9B,WAAW,6BAA6B,WAAW;CAGrD,IAAI,WAAW,eAAe,QAC5B,WAAW,2BAA2B,WAAW;AAErD;AAEA,SAAS,0BAA0B,YAAqC,YAAoC;CAC1G,IAAI,WAAW,cAAc,QAC3B,WAAW,4BAA4B,WAAW;CAGpD,IAAI,WAAW,cAAc,QAC3B,WAAW,4BAA4B,WAAW;CAGpD,IAAI,MAAM,QAAQ,WAAW,KAAK,GAChC,WAAW,wBAAwB,WAAW,MAAM;AAExD;AAEA,SAAS,kBAAkB,YAAqC,YAAoC;CAClG,IAAI,WAAW,cAAc,QAC3B,WAAW,oBAAoB,WAAW;CAG5C,IAAI,WAAW,kBAAkB,QAC/B,WAAW,wBAAwB,WAAW;AAElD"}
1
+ {"version":3,"file":"extract-span-attributes.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/extract-span-attributes.ts"],"sourcesContent":["import type { BaseReport } from \"@warlock.js/ai\";\n\n/**\n * Per-primitive `BaseReport` extension fields the collector surfaces as\n * span attributes. Declared as a structural superset (every field\n * optional) so a single reader can pull whatever the concrete report\n * carried without narrowing on `type` first — a `BaseReport` widened to\n * this shape exposes `undefined` for the fields its primitive doesn't\n * set, and the extractor simply skips those.\n *\n * Mirrors the public per-primitive report types in `@warlock.js/ai`\n * (`AgentReport`, `WorkflowReport`, `SupervisorReport`,\n * `OrchestratorReport`, `ToolCall`) — kept structural rather than a\n * union import so adding a new optional report field upstream is a\n * non-breaking read here.\n */\ntype ReportExtensions = {\n model?: { name?: string; provider?: string };\n trips?: unknown[];\n promptName?: string;\n promptVersion?: string;\n workflowName?: string;\n supervisorName?: string;\n signature?: string;\n terminatedBy?: string;\n iterations?: number;\n steps?: Record<string, unknown>;\n turns?: unknown[];\n turnIndex?: number;\n tripIndex?: number;\n recoveredFrom?: string;\n};\n\n/**\n * Build the free-form `TraceSpan.attributes` bag for one report node.\n *\n * The collector keeps the first-class span fields (identity, timing,\n * status, usage, error) on the span itself and routes everything\n * primitive-specific here — trip/step/iteration counts, the model\n * identity an agent ran against, the tool's originating trip index, a\n * supervisor's termination reason. Exporters forward this verbatim as\n * backend span attributes (OTel attributes, Langfuse metadata).\n *\n * Only populated keys are emitted; the function returns `undefined`\n * when the node carried no extra detail, so the optional\n * `TraceSpan.attributes` field stays absent rather than holding an\n * empty object (matches the contract's \"absent when empty\" note).\n *\n * Retry count is surfaced from the shared `BaseReport.attempts` for\n * every primitive so cost dashboards see the real call count.\n *\n * @example\n * const attributes = extractSpanAttributes(agentReport);\n * // { \"agent.trips\": 3, \"agent.model.name\": \"gpt-4o\", \"agent.model.provider\": \"openai\" }\n */\nexport function extractSpanAttributes(report: BaseReport): Record<string, unknown> | undefined {\n const extensions = report as BaseReport & ReportExtensions;\n const attributes: Record<string, unknown> = {};\n\n if (report.attempts !== undefined && report.attempts.length > 0) {\n attributes[\"retries\"] = report.attempts.length;\n }\n\n switch (report.type) {\n case \"agent\": {\n addAgentAttributes(attributes, extensions);\n break;\n }\n\n case \"workflow\": {\n addWorkflowAttributes(attributes, extensions);\n break;\n }\n\n case \"supervisor\":\n case \"team\": {\n // ai.team reuses the supervisor engine, so a team report carries the\n // same supervisorName / terminatedBy / iterations fields.\n addSupervisorAttributes(attributes, extensions);\n break;\n }\n\n case \"orchestrator\": {\n addOrchestratorAttributes(attributes, extensions);\n break;\n }\n\n case \"tool\": {\n addToolAttributes(attributes, extensions);\n break;\n }\n\n default: {\n break;\n }\n }\n\n if (Object.keys(attributes).length === 0) {\n return undefined;\n }\n\n return attributes;\n}\n\nfunction addAgentAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (Array.isArray(extensions.trips)) {\n attributes[\"agent.trips\"] = extensions.trips.length;\n }\n\n if (extensions.model?.name !== undefined) {\n attributes[\"agent.model.name\"] = extensions.model.name;\n }\n\n if (extensions.model?.provider !== undefined) {\n attributes[\"agent.model.provider\"] = extensions.model.provider;\n }\n\n // Prompt-version linkage (core `AgentReport.promptName` / `promptVersion`).\n // Present only when the agent ran against a *named* `ai.prompts` builder;\n // surfaced so the dashboard can group / filter runs by `name@version` and\n // attribute behavior shifts to a specific prompt revision.\n if (extensions.promptName !== undefined) {\n attributes[\"agent.promptName\"] = extensions.promptName;\n }\n\n if (extensions.promptVersion !== undefined) {\n attributes[\"agent.promptVersion\"] = extensions.promptVersion;\n }\n}\n\nfunction addWorkflowAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.workflowName !== undefined) {\n attributes[\"workflow.name\"] = extensions.workflowName;\n }\n\n if (extensions.signature !== undefined) {\n attributes[\"workflow.signature\"] = extensions.signature;\n }\n\n if (extensions.steps !== undefined) {\n attributes[\"workflow.steps\"] = Object.keys(extensions.steps).length;\n }\n}\n\nfunction addSupervisorAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.supervisorName !== undefined) {\n attributes[\"supervisor.name\"] = extensions.supervisorName;\n }\n\n if (extensions.terminatedBy !== undefined) {\n attributes[\"supervisor.terminatedBy\"] = extensions.terminatedBy;\n }\n\n if (extensions.iterations !== undefined) {\n attributes[\"supervisor.iterations\"] = extensions.iterations;\n }\n}\n\nfunction addOrchestratorAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.turnIndex !== undefined) {\n attributes[\"orchestrator.turnIndex\"] = extensions.turnIndex;\n }\n\n if (extensions.signature !== undefined) {\n attributes[\"orchestrator.signature\"] = extensions.signature;\n }\n\n if (Array.isArray(extensions.turns)) {\n attributes[\"orchestrator.turns\"] = extensions.turns.length;\n }\n}\n\nfunction addToolAttributes(attributes: Record<string, unknown>, extensions: ReportExtensions): void {\n if (extensions.tripIndex !== undefined) {\n attributes[\"tool.tripIndex\"] = extensions.tripIndex;\n }\n\n if (extensions.recoveredFrom !== undefined) {\n attributes[\"tool.recoveredFrom\"] = extensions.recoveredFrom;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuDA,SAAgB,sBAAsB,QAAyD;CAC7F,MAAM,aAAa;CACnB,MAAM,aAAsC,CAAC;CAE7C,IAAI,OAAO,aAAa,UAAa,OAAO,SAAS,SAAS,GAC5D,WAAW,aAAa,OAAO,SAAS;CAG1C,QAAQ,OAAO,MAAf;EACE,KAAK;GACH,mBAAmB,YAAY,UAAU;GACzC;EAGF,KAAK;GACH,sBAAsB,YAAY,UAAU;GAC5C;EAGF,KAAK;EACL,KAAK;GAGH,wBAAwB,YAAY,UAAU;GAC9C;EAGF,KAAK;GACH,0BAA0B,YAAY,UAAU;GAChD;EAGF,KAAK;GACH,kBAAkB,YAAY,UAAU;GACxC;EAGF,SACE;CAEJ;CAEA,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,WAAW,GACrC;CAGF,OAAO;AACT;AAEA,SAAS,mBAAmB,YAAqC,YAAoC;CACnG,IAAI,MAAM,QAAQ,WAAW,KAAK,GAChC,WAAW,iBAAiB,WAAW,MAAM;CAG/C,IAAI,WAAW,OAAO,SAAS,QAC7B,WAAW,sBAAsB,WAAW,MAAM;CAGpD,IAAI,WAAW,OAAO,aAAa,QACjC,WAAW,0BAA0B,WAAW,MAAM;CAOxD,IAAI,WAAW,eAAe,QAC5B,WAAW,sBAAsB,WAAW;CAG9C,IAAI,WAAW,kBAAkB,QAC/B,WAAW,yBAAyB,WAAW;AAEnD;AAEA,SAAS,sBAAsB,YAAqC,YAAoC;CACtG,IAAI,WAAW,iBAAiB,QAC9B,WAAW,mBAAmB,WAAW;CAG3C,IAAI,WAAW,cAAc,QAC3B,WAAW,wBAAwB,WAAW;CAGhD,IAAI,WAAW,UAAU,QACvB,WAAW,oBAAoB,OAAO,KAAK,WAAW,KAAK,CAAC,CAAC;AAEjE;AAEA,SAAS,wBAAwB,YAAqC,YAAoC;CACxG,IAAI,WAAW,mBAAmB,QAChC,WAAW,qBAAqB,WAAW;CAG7C,IAAI,WAAW,iBAAiB,QAC9B,WAAW,6BAA6B,WAAW;CAGrD,IAAI,WAAW,eAAe,QAC5B,WAAW,2BAA2B,WAAW;AAErD;AAEA,SAAS,0BAA0B,YAAqC,YAAoC;CAC1G,IAAI,WAAW,cAAc,QAC3B,WAAW,4BAA4B,WAAW;CAGpD,IAAI,WAAW,cAAc,QAC3B,WAAW,4BAA4B,WAAW;CAGpD,IAAI,MAAM,QAAQ,WAAW,KAAK,GAChC,WAAW,wBAAwB,WAAW,MAAM;AAExD;AAEA,SAAS,kBAAkB,YAAqC,YAAoC;CAClG,IAAI,WAAW,cAAc,QAC3B,WAAW,oBAAoB,WAAW;CAG5C,IAAI,WAAW,kBAAkB,QAC/B,WAAW,wBAAwB,WAAW;AAElD"}
@@ -1,5 +1,5 @@
1
1
  import { ContentCaptureOptions, ContentRedactor } from "./content-capture.type.mjs";
2
- import { createCollector } from "./collector.mjs";
2
+ import { CollectorOptions, ExporterErrorHandler, createCollector } from "./collector.mjs";
3
3
  import { reportToTrace } from "./report-to-trace.mjs";
4
4
  import { reportToSpan } from "./report-to-span.mjs";
5
5
  import { extractSpanAttributes } from "./extract-span-attributes.mjs";
@@ -0,0 +1,7 @@
1
+ import { normalizeError } from "./normalize-error.mjs";
2
+ import { extractSpanAttributes } from "./extract-span-attributes.mjs";
3
+ import { reportToSpan } from "./report-to-span.mjs";
4
+ import { reportToTrace } from "./report-to-trace.mjs";
5
+ import { createCollector } from "./collector.mjs";
6
+
7
+ export { };
@@ -1 +1 @@
1
- {"version":3,"file":"normalize-error.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/normalize-error.ts"],"mappings":";;;;;AAsBA;;;;AAA8D;;;;;;;;;;;;;;iBAA9C,cAAA,CAAe,KAAA,YAAiB,cAAc"}
1
+ {"version":3,"file":"normalize-error.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/normalize-error.ts"],"mappings":";;;;;AAuBA;;;;AAA8D;;;;;;;;;;;;;;iBAA9C,cAAA,CAAe,KAAA,YAAiB,cAAc"}
@@ -1,3 +1,5 @@
1
+ import { redact, scrubSecrets } from "@warlock.js/ai";
2
+
1
3
  //#region ../@warlock.js/ai-panoptic/src/collector/normalize-error.ts
2
4
  /**
3
5
  * Project a captured execution error onto the structural
@@ -23,7 +25,7 @@ function normalizeError(error) {
23
25
  if (error === void 0 || error === null) return;
24
26
  if (typeof error !== "object") return {
25
27
  type: "Error",
26
- message: String(error)
28
+ message: scrubSecrets(String(error))
27
29
  };
28
30
  const candidate = error;
29
31
  const type = pickString(candidate.code) ?? pickString(candidate.name) ?? "Error";
@@ -31,9 +33,10 @@ function normalizeError(error) {
31
33
  const stack = pickString(candidate.stack);
32
34
  const normalized = {
33
35
  type,
34
- message
36
+ message: scrubSecrets(message)
35
37
  };
36
- if (stack !== void 0) normalized.stack = stack;
38
+ if (stack !== void 0) normalized.stack = scrubSecrets(stack);
39
+ if (candidate.cause !== void 0 && candidate.cause !== null) normalized.cause = typeof candidate.cause === "object" ? redact(candidate.cause) : scrubSecrets(String(candidate.cause));
37
40
  return normalized;
38
41
  }
39
42
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"normalize-error.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/normalize-error.ts"],"sourcesContent":["import type { TraceSpanError } from \"../contracts/trace.type\";\n\n/**\n * Project a captured execution error onto the structural\n * {@link TraceSpanError} shape used on a failed / cancelled span.\n *\n * Source errors are typically `AIError` instances (every error surfaced\n * by `@warlock.js/ai` is one), but the collector never depends on the\n * concrete class — it reads only the structural surface (`name` / `code`\n * / `message` / `stack`) so a plain `Error`, an `AIError`, or any\n * thrown value all normalize identically. The result is a JSON-safe\n * plain object so it survives serialization to a backend collector\n * unchanged.\n *\n * The error `type` prefers the stable `code` (e.g. `\"RATE_LIMIT\"`) over\n * the class `name`, falling back to `name` and finally to the generic\n * `\"Error\"` so the field is always populated.\n *\n * @example\n * const spanError = normalizeError(report.error);\n * // { type: \"RATE_LIMIT\", message: \"429 Too Many Requests\", stack: \"...\" }\n */\nexport function normalizeError(error: unknown): TraceSpanError | undefined {\n if (error === undefined || error === null) {\n return undefined;\n }\n\n if (typeof error !== \"object\") {\n return {\n type: \"Error\",\n message: String(error),\n };\n }\n\n const candidate = error as {\n code?: unknown;\n name?: unknown;\n message?: unknown;\n stack?: unknown;\n };\n\n const type = pickString(candidate.code) ?? pickString(candidate.name) ?? \"Error\";\n const message = pickString(candidate.message) ?? \"\";\n const stack = pickString(candidate.stack);\n\n const normalized: TraceSpanError = {\n type,\n message,\n };\n\n if (stack !== undefined) {\n normalized.stack = stack;\n }\n\n return normalized;\n}\n\n/**\n * Return the value when it is a non-empty string, otherwise `undefined`.\n * Keeps `normalizeError` from promoting empty / non-string fields.\n */\nfunction pickString(value: unknown): string | undefined {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,eAAe,OAA4C;CACzE,IAAI,UAAU,UAAa,UAAU,MACnC;CAGF,IAAI,OAAO,UAAU,UACnB,OAAO;EACL,MAAM;EACN,SAAS,OAAO,KAAK;CACvB;CAGF,MAAM,YAAY;CAOlB,MAAM,OAAO,WAAW,UAAU,IAAI,KAAK,WAAW,UAAU,IAAI,KAAK;CACzE,MAAM,UAAU,WAAW,UAAU,OAAO,KAAK;CACjD,MAAM,QAAQ,WAAW,UAAU,KAAK;CAExC,MAAM,aAA6B;EACjC;EACA;CACF;CAEA,IAAI,UAAU,QACZ,WAAW,QAAQ;CAGrB,OAAO;AACT;;;;;AAMA,SAAS,WAAW,OAAoC;CACtD,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAC9C,OAAO;AAIX"}
1
+ {"version":3,"file":"normalize-error.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/normalize-error.ts"],"sourcesContent":["import { redact, scrubSecrets } from \"@warlock.js/ai\";\nimport type { TraceSpanError } from \"../contracts/trace.type\";\n\n/**\n * Project a captured execution error onto the structural\n * {@link TraceSpanError} shape used on a failed / cancelled span.\n *\n * Source errors are typically `AIError` instances (every error surfaced\n * by `@warlock.js/ai` is one), but the collector never depends on the\n * concrete class — it reads only the structural surface (`name` / `code`\n * / `message` / `stack`) so a plain `Error`, an `AIError`, or any\n * thrown value all normalize identically. The result is a JSON-safe\n * plain object so it survives serialization to a backend collector\n * unchanged.\n *\n * The error `type` prefers the stable `code` (e.g. `\"RATE_LIMIT\"`) over\n * the class `name`, falling back to `name` and finally to the generic\n * `\"Error\"` so the field is always populated.\n *\n * @example\n * const spanError = normalizeError(report.error);\n * // { type: \"RATE_LIMIT\", message: \"429 Too Many Requests\", stack: \"...\" }\n */\nexport function normalizeError(error: unknown): TraceSpanError | undefined {\n if (error === undefined || error === null) {\n return undefined;\n }\n\n if (typeof error !== \"object\") {\n return {\n type: \"Error\",\n message: scrubSecrets(String(error)),\n };\n }\n\n const candidate = error as {\n code?: unknown;\n name?: unknown;\n message?: unknown;\n stack?: unknown;\n cause?: unknown;\n };\n\n const type = pickString(candidate.code) ?? pickString(candidate.name) ?? \"Error\";\n const message = pickString(candidate.message) ?? \"\";\n const stack = pickString(candidate.stack);\n\n // Scrub free-text secrets (Bearer tokens, api keys) from the message and\n // stack, and deep-redact the cause (a raw provider SDK error can carry\n // auth/cookie headers) before either is stored or exported (S4).\n const normalized: TraceSpanError = {\n type,\n message: scrubSecrets(message),\n };\n\n if (stack !== undefined) {\n normalized.stack = scrubSecrets(stack);\n }\n\n if (candidate.cause !== undefined && candidate.cause !== null) {\n normalized.cause =\n typeof candidate.cause === \"object\"\n ? redact(candidate.cause)\n : scrubSecrets(String(candidate.cause));\n }\n\n return normalized;\n}\n\n/**\n * Return the value when it is a non-empty string, otherwise `undefined`.\n * Keeps `normalizeError` from promoting empty / non-string fields.\n */\nfunction pickString(value: unknown): string | undefined {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,eAAe,OAA4C;CACzE,IAAI,UAAU,UAAa,UAAU,MACnC;CAGF,IAAI,OAAO,UAAU,UACnB,OAAO;EACL,MAAM;EACN,SAAS,aAAa,OAAO,KAAK,CAAC;CACrC;CAGF,MAAM,YAAY;CAQlB,MAAM,OAAO,WAAW,UAAU,IAAI,KAAK,WAAW,UAAU,IAAI,KAAK;CACzE,MAAM,UAAU,WAAW,UAAU,OAAO,KAAK;CACjD,MAAM,QAAQ,WAAW,UAAU,KAAK;CAKxC,MAAM,aAA6B;EACjC;EACA,SAAS,aAAa,OAAO;CAC/B;CAEA,IAAI,UAAU,QACZ,WAAW,QAAQ,aAAa,KAAK;CAGvC,IAAI,UAAU,UAAU,UAAa,UAAU,UAAU,MACvD,WAAW,QACT,OAAO,UAAU,UAAU,WACvB,OAAO,UAAU,KAAK,IACtB,aAAa,OAAO,UAAU,KAAK,CAAC;CAG5C,OAAO;AACT;;;;;AAMA,SAAS,WAAW,OAAoC;CACtD,IAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAC9C,OAAO;AAIX"}
@@ -51,7 +51,7 @@ function reportToSpan(report, options) {
51
51
  if (error !== void 0) span.error = error;
52
52
  const attributes = extractSpanAttributes(report);
53
53
  if (attributes !== void 0) span.attributes = attributes;
54
- if (options?.captureContent) captureContent(span, report, options.redactContent);
54
+ if (options?.captureContent) captureContent(span, report, options);
55
55
  return span;
56
56
  }
57
57
  /**
@@ -59,25 +59,46 @@ function reportToSpan(report, options) {
59
59
  *
60
60
  * - Tools carry the call arguments + return value directly on the report
61
61
  * (`ToolCall.input` / `ToolCall.output`).
62
- * - Agents carry a `trips[]` history; the first trip's `input` is the
63
- * real user prompt, and the last NON-EMPTY trip `output` is the final
64
- * response text. (A failed / max-trips run can end on a trip whose
65
- * `output` is `""` or tool-call-only, so we scan back for the last one
66
- * that carried text rather than surfacing an empty string. Intermediate
67
- * trips also store a `"[tool results]"` placeholder upstream.)
62
+ * - Agents carry a `trips[]` history plus the resolved `systemPrompt`. The
63
+ * input is emitted as a `[system, user]` chat array so backends like
64
+ * Langfuse render the full prompt as sent or the bare user string when
65
+ * there's no system prompt. The output is the last NON-EMPTY trip
66
+ * `output` (the final response text); a failed / max-trips run can end on
67
+ * a trip whose `output` is `""` or tool-call-only, so we scan back for the
68
+ * last one that carried text. Intermediate trips store a `"[tool results]"`
69
+ * placeholder upstream.
70
+ * - When {@link ContentCaptureOptions.fullHistory} is on AND the agent run
71
+ * opted into `captureMessages` (so the report carries a `messages`
72
+ * array), the *whole* `CapturedMessage[]` is emitted as `span.input`
73
+ * instead of the `[system, user]` first-trip array — every role, every
74
+ * trip. The output stays the last non-empty trip output. When `messages`
75
+ * is absent the branch falls back to today's first-trip logic, so a run
76
+ * that didn't opt in degrades gracefully.
68
77
  *
69
78
  * Each value is passed through the optional {@link ContentRedactor};
70
- * a redactor returning `undefined` drops the field.
79
+ * a redactor returning `undefined` drops the field. Under `fullHistory`
80
+ * the redactor receives the full array as a single value.
71
81
  */
72
- function captureContent(span, report, redact) {
82
+ function captureContent(span, report, options) {
73
83
  const node = report;
84
+ const redact = options.redactContent;
74
85
  let input;
75
86
  let output;
76
87
  if (report.type === "tool") {
77
88
  input = node.input;
78
89
  output = node.output;
90
+ } else if (options.fullHistory && Array.isArray(node.messages) && node.messages.length > 0) {
91
+ input = node.messages;
92
+ output = Array.isArray(node.trips) ? lastNonEmptyOutput(node.trips) : void 0;
79
93
  } else if (Array.isArray(node.trips) && node.trips.length > 0) {
80
- input = node.trips[0]?.input;
94
+ const userInput = node.trips[0]?.input;
95
+ input = typeof node.systemPrompt === "string" && node.systemPrompt.length > 0 ? [{
96
+ role: "system",
97
+ content: node.systemPrompt
98
+ }, {
99
+ role: "user",
100
+ content: userInput
101
+ }] : userInput;
81
102
  output = lastNonEmptyOutput(node.trips);
82
103
  }
83
104
  if (input !== void 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"report-to-span.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/report-to-span.ts"],"sourcesContent":["import type { BaseReport } from \"@warlock.js/ai\";\nimport type { TraceSpan } from \"../contracts/trace.type\";\nimport type { ContentCaptureOptions, ContentRedactor } from \"./content-capture.type\";\nimport { extractSpanAttributes } from \"./extract-span-attributes\";\nimport { normalizeError } from \"./normalize-error\";\n\n/**\n * Project one {@link BaseReport} node — and its entire subtree — into a\n * {@link TraceSpan}. Pure and recursive: identity, timing, status, and\n * the rolled-up `usage` map across 1:1 from the report; lineage maps\n * `runId → spanId`, `parentRunId → parentSpanId`, `rootRunId → traceId`;\n * children recurse in invocation order so the span tree mirrors the\n * report tree exactly.\n *\n * The error is normalized to the JSON-safe {@link\n * import(\"../contracts/trace.type\").TraceSpanError} shape only when the\n * node carried one (failed / cancelled). Primitive-specific detail that\n * has no first-class span field (trip / step / iteration counts, model\n * identity, tool trip index) is routed into the optional `attributes`\n * bag via {@link extractSpanAttributes}.\n *\n * When {@link ContentCaptureOptions.captureContent} is set, the raw\n * prompt/response (agents) and args/result (tools) are additionally\n * copied onto `span.input` / `span.output` — off by default because\n * payloads are large and often sensitive. A {@link ContentRedactor} can\n * mask each value first.\n *\n * No external lookup is needed — a `BaseReport` already carries\n * everything a span requires, so a collector can flatten a tree without\n * consulting any other source.\n *\n * @example\n * const root = reportToSpan(result.report);\n * console.log(root.spanId, root.traceId, root.children.length);\n */\nexport function reportToSpan(report: BaseReport, options?: ContentCaptureOptions): TraceSpan {\n const span: TraceSpan = {\n spanId: report.runId,\n traceId: report.rootRunId,\n name: report.name,\n type: report.type,\n status: report.status,\n startedAt: report.startedAt,\n endedAt: report.endedAt,\n duration: report.duration,\n usage: report.usage,\n children: report.children.map((child) => reportToSpan(child, options)),\n };\n\n if (report.parentRunId !== undefined) {\n span.parentSpanId = report.parentRunId;\n }\n\n if (report.sessionId !== undefined) {\n span.sessionId = report.sessionId;\n }\n\n if (report.version !== undefined) {\n span.version = report.version;\n }\n\n const error = normalizeError((report as { error?: unknown }).error);\n if (error !== undefined) {\n span.error = error;\n }\n\n const attributes = extractSpanAttributes(report);\n if (attributes !== undefined) {\n span.attributes = attributes;\n }\n\n if (options?.captureContent) {\n captureContent(span, report, options.redactContent);\n }\n\n return span;\n}\n\n/** Per-primitive content fields read off a widened report node. */\ntype ContentReport = {\n input?: unknown;\n output?: unknown;\n trips?: Array<{ input?: unknown; output?: unknown }>;\n};\n\n/**\n * Copy the node's raw content onto `span.input` / `span.output`.\n *\n * - Tools carry the call arguments + return value directly on the report\n * (`ToolCall.input` / `ToolCall.output`).\n * - Agents carry a `trips[]` history; the first trip's `input` is the\n * real user prompt, and the last NON-EMPTY trip `output` is the final\n * response text. (A failed / max-trips run can end on a trip whose\n * `output` is `\"\"` or tool-call-only, so we scan back for the last one\n * that carried text rather than surfacing an empty string. Intermediate\n * trips also store a `\"[tool results]\"` placeholder upstream.)\n *\n * Each value is passed through the optional {@link ContentRedactor};\n * a redactor returning `undefined` drops the field.\n */\nfunction captureContent(span: TraceSpan, report: BaseReport, redact?: ContentRedactor): void {\n const node = report as BaseReport & ContentReport;\n\n let input: unknown;\n let output: unknown;\n\n if (report.type === \"tool\") {\n input = node.input;\n output = node.output;\n } else if (Array.isArray(node.trips) && node.trips.length > 0) {\n input = node.trips[0]?.input;\n output = lastNonEmptyOutput(node.trips);\n }\n\n if (input !== undefined) {\n const value = redact ? redact(input, { name: span.name, type: span.type, field: \"input\" }) : input;\n if (value !== undefined) {\n span.input = value;\n }\n }\n\n if (output !== undefined) {\n const value = redact ? redact(output, { name: span.name, type: span.type, field: \"output\" }) : output;\n if (value !== undefined) {\n span.output = value;\n }\n }\n}\n\n/**\n * The last trip output that actually carries text — the agent's final\n * response on the happy path. A failed or max-trips run can end on a trip\n * whose `output` is `\"\"` or tool-call-only, so we scan backwards for the\n * last trip that produced text rather than blindly taking the final trip\n * (which would surface an empty string). Returns `undefined` when no trip\n * produced any output.\n */\nfunction lastNonEmptyOutput(trips: Array<{ output?: unknown }>): unknown {\n for (let i = trips.length - 1; i >= 0; i -= 1) {\n const out = trips[i]?.output;\n const hasText = typeof out === \"string\" ? out.length > 0 : out !== undefined;\n\n if (hasText) {\n return out;\n }\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,aAAa,QAAoB,SAA4C;CAC3F,MAAM,OAAkB;EACtB,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,MAAM,OAAO;EACb,MAAM,OAAO;EACb,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,UAAU,OAAO;EACjB,OAAO,OAAO;EACd,UAAU,OAAO,SAAS,KAAK,UAAU,aAAa,OAAO,OAAO,CAAC;CACvE;CAEA,IAAI,OAAO,gBAAgB,QACzB,KAAK,eAAe,OAAO;CAG7B,IAAI,OAAO,cAAc,QACvB,KAAK,YAAY,OAAO;CAG1B,IAAI,OAAO,YAAY,QACrB,KAAK,UAAU,OAAO;CAGxB,MAAM,QAAQ,eAAgB,OAA+B,KAAK;CAClE,IAAI,UAAU,QACZ,KAAK,QAAQ;CAGf,MAAM,aAAa,sBAAsB,MAAM;CAC/C,IAAI,eAAe,QACjB,KAAK,aAAa;CAGpB,IAAI,SAAS,gBACX,eAAe,MAAM,QAAQ,QAAQ,aAAa;CAGpD,OAAO;AACT;;;;;;;;;;;;;;;;AAwBA,SAAS,eAAe,MAAiB,QAAoB,QAAgC;CAC3F,MAAM,OAAO;CAEb,IAAI;CACJ,IAAI;CAEJ,IAAI,OAAO,SAAS,QAAQ;EAC1B,QAAQ,KAAK;EACb,SAAS,KAAK;CAChB,OAAO,IAAI,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS,GAAG;EAC7D,QAAQ,KAAK,MAAM,EAAE,EAAE;EACvB,SAAS,mBAAmB,KAAK,KAAK;CACxC;CAEA,IAAI,UAAU,QAAW;EACvB,MAAM,QAAQ,SAAS,OAAO,OAAO;GAAE,MAAM,KAAK;GAAM,MAAM,KAAK;GAAM,OAAO;EAAQ,CAAC,IAAI;EAC7F,IAAI,UAAU,QACZ,KAAK,QAAQ;CAEjB;CAEA,IAAI,WAAW,QAAW;EACxB,MAAM,QAAQ,SAAS,OAAO,QAAQ;GAAE,MAAM,KAAK;GAAM,MAAM,KAAK;GAAM,OAAO;EAAS,CAAC,IAAI;EAC/F,IAAI,UAAU,QACZ,KAAK,SAAS;CAElB;AACF;;;;;;;;;AAUA,SAAS,mBAAmB,OAA6C;CACvE,KAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;EAC7C,MAAM,MAAM,MAAM,EAAE,EAAE;EAGtB,IAFgB,OAAO,QAAQ,WAAW,IAAI,SAAS,IAAI,QAAQ,QAGjE,OAAO;CAEX;AAGF"}
1
+ {"version":3,"file":"report-to-span.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/report-to-span.ts"],"sourcesContent":["import type { BaseReport } from \"@warlock.js/ai\";\nimport type { TraceSpan } from \"../contracts/trace.type\";\nimport type { ContentCaptureOptions, ContentRedactor } from \"./content-capture.type\";\nimport { extractSpanAttributes } from \"./extract-span-attributes\";\nimport { normalizeError } from \"./normalize-error\";\n\n/**\n * Project one {@link BaseReport} node — and its entire subtree — into a\n * {@link TraceSpan}. Pure and recursive: identity, timing, status, and\n * the rolled-up `usage` map across 1:1 from the report; lineage maps\n * `runId → spanId`, `parentRunId → parentSpanId`, `rootRunId → traceId`;\n * children recurse in invocation order so the span tree mirrors the\n * report tree exactly.\n *\n * The error is normalized to the JSON-safe {@link\n * import(\"../contracts/trace.type\").TraceSpanError} shape only when the\n * node carried one (failed / cancelled). Primitive-specific detail that\n * has no first-class span field (trip / step / iteration counts, model\n * identity, tool trip index) is routed into the optional `attributes`\n * bag via {@link extractSpanAttributes}.\n *\n * When {@link ContentCaptureOptions.captureContent} is set, the raw\n * prompt/response (agents) and args/result (tools) are additionally\n * copied onto `span.input` / `span.output` — off by default because\n * payloads are large and often sensitive. A {@link ContentRedactor} can\n * mask each value first.\n *\n * No external lookup is needed — a `BaseReport` already carries\n * everything a span requires, so a collector can flatten a tree without\n * consulting any other source.\n *\n * @example\n * const root = reportToSpan(result.report);\n * console.log(root.spanId, root.traceId, root.children.length);\n */\nexport function reportToSpan(report: BaseReport, options?: ContentCaptureOptions): TraceSpan {\n const span: TraceSpan = {\n spanId: report.runId,\n traceId: report.rootRunId,\n name: report.name,\n type: report.type,\n status: report.status,\n startedAt: report.startedAt,\n endedAt: report.endedAt,\n duration: report.duration,\n usage: report.usage,\n children: report.children.map((child) => reportToSpan(child, options)),\n };\n\n if (report.parentRunId !== undefined) {\n span.parentSpanId = report.parentRunId;\n }\n\n if (report.sessionId !== undefined) {\n span.sessionId = report.sessionId;\n }\n\n if (report.version !== undefined) {\n span.version = report.version;\n }\n\n const error = normalizeError((report as { error?: unknown }).error);\n if (error !== undefined) {\n span.error = error;\n }\n\n const attributes = extractSpanAttributes(report);\n if (attributes !== undefined) {\n span.attributes = attributes;\n }\n\n if (options?.captureContent) {\n captureContent(span, report, options);\n }\n\n return span;\n}\n\n/** Per-primitive content fields read off a widened report node. */\ntype ContentReport = {\n input?: unknown;\n output?: unknown;\n systemPrompt?: string;\n trips?: Array<{ input?: unknown; output?: unknown }>;\n /**\n * The full assembled conversation an agent run captured when its\n * `captureMessages` option was set (core `AgentReport.messages`).\n * Present only on opted-in agent runs; absent otherwise.\n */\n messages?: unknown;\n};\n\n/**\n * Copy the node's raw content onto `span.input` / `span.output`.\n *\n * - Tools carry the call arguments + return value directly on the report\n * (`ToolCall.input` / `ToolCall.output`).\n * - Agents carry a `trips[]` history plus the resolved `systemPrompt`. The\n * input is emitted as a `[system, user]` chat array — so backends like\n * Langfuse render the full prompt as sent — or the bare user string when\n * there's no system prompt. The output is the last NON-EMPTY trip\n * `output` (the final response text); a failed / max-trips run can end on\n * a trip whose `output` is `\"\"` or tool-call-only, so we scan back for the\n * last one that carried text. Intermediate trips store a `\"[tool results]\"`\n * placeholder upstream.\n * - When {@link ContentCaptureOptions.fullHistory} is on AND the agent run\n * opted into `captureMessages` (so the report carries a `messages`\n * array), the *whole* `CapturedMessage[]` is emitted as `span.input`\n * instead of the `[system, user]` first-trip array — every role, every\n * trip. The output stays the last non-empty trip output. When `messages`\n * is absent the branch falls back to today's first-trip logic, so a run\n * that didn't opt in degrades gracefully.\n *\n * Each value is passed through the optional {@link ContentRedactor};\n * a redactor returning `undefined` drops the field. Under `fullHistory`\n * the redactor receives the full array as a single value.\n */\nfunction captureContent(\n span: TraceSpan,\n report: BaseReport,\n options: ContentCaptureOptions,\n): void {\n const node = report as BaseReport & ContentReport;\n const redact = options.redactContent;\n\n let input: unknown;\n let output: unknown;\n\n if (report.type === \"tool\") {\n input = node.input;\n output = node.output;\n } else if (\n options.fullHistory &&\n Array.isArray(node.messages) &&\n node.messages.length > 0\n ) {\n // Full-history capture: the entire assembled conversation as a single\n // input value. Output remains the agent's final response text.\n input = node.messages;\n output = Array.isArray(node.trips) ? lastNonEmptyOutput(node.trips) : undefined;\n } else if (Array.isArray(node.trips) && node.trips.length > 0) {\n const userInput = node.trips[0]?.input;\n // Emit a [system, user] chat array when the agent carried a system\n // prompt, so backends (Langfuse) render the full prompt as sent;\n // otherwise keep the bare user string.\n input =\n typeof node.systemPrompt === \"string\" && node.systemPrompt.length > 0\n ? [\n { role: \"system\", content: node.systemPrompt },\n { role: \"user\", content: userInput },\n ]\n : userInput;\n output = lastNonEmptyOutput(node.trips);\n }\n\n if (input !== undefined) {\n const value = redact ? redact(input, { name: span.name, type: span.type, field: \"input\" }) : input;\n if (value !== undefined) {\n span.input = value;\n }\n }\n\n if (output !== undefined) {\n const value = redact ? redact(output, { name: span.name, type: span.type, field: \"output\" }) : output;\n if (value !== undefined) {\n span.output = value;\n }\n }\n}\n\n/**\n * The last trip output that actually carries text — the agent's final\n * response on the happy path. A failed or max-trips run can end on a trip\n * whose `output` is `\"\"` or tool-call-only, so we scan backwards for the\n * last trip that produced text rather than blindly taking the final trip\n * (which would surface an empty string). Returns `undefined` when no trip\n * produced any output.\n */\nfunction lastNonEmptyOutput(trips: Array<{ output?: unknown }>): unknown {\n for (let i = trips.length - 1; i >= 0; i -= 1) {\n const out = trips[i]?.output;\n const hasText = typeof out === \"string\" ? out.length > 0 : out !== undefined;\n\n if (hasText) {\n return out;\n }\n }\n\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,aAAa,QAAoB,SAA4C;CAC3F,MAAM,OAAkB;EACtB,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,MAAM,OAAO;EACb,MAAM,OAAO;EACb,QAAQ,OAAO;EACf,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,UAAU,OAAO;EACjB,OAAO,OAAO;EACd,UAAU,OAAO,SAAS,KAAK,UAAU,aAAa,OAAO,OAAO,CAAC;CACvE;CAEA,IAAI,OAAO,gBAAgB,QACzB,KAAK,eAAe,OAAO;CAG7B,IAAI,OAAO,cAAc,QACvB,KAAK,YAAY,OAAO;CAG1B,IAAI,OAAO,YAAY,QACrB,KAAK,UAAU,OAAO;CAGxB,MAAM,QAAQ,eAAgB,OAA+B,KAAK;CAClE,IAAI,UAAU,QACZ,KAAK,QAAQ;CAGf,MAAM,aAAa,sBAAsB,MAAM;CAC/C,IAAI,eAAe,QACjB,KAAK,aAAa;CAGpB,IAAI,SAAS,gBACX,eAAe,MAAM,QAAQ,OAAO;CAGtC,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAS,eACP,MACA,QACA,SACM;CACN,MAAM,OAAO;CACb,MAAM,SAAS,QAAQ;CAEvB,IAAI;CACJ,IAAI;CAEJ,IAAI,OAAO,SAAS,QAAQ;EAC1B,QAAQ,KAAK;EACb,SAAS,KAAK;CAChB,OAAO,IACL,QAAQ,eACR,MAAM,QAAQ,KAAK,QAAQ,KAC3B,KAAK,SAAS,SAAS,GACvB;EAGA,QAAQ,KAAK;EACb,SAAS,MAAM,QAAQ,KAAK,KAAK,IAAI,mBAAmB,KAAK,KAAK,IAAI;CACxE,OAAO,IAAI,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS,GAAG;EAC7D,MAAM,YAAY,KAAK,MAAM,EAAE,EAAE;EAIjC,QACE,OAAO,KAAK,iBAAiB,YAAY,KAAK,aAAa,SAAS,IAChE,CACE;GAAE,MAAM;GAAU,SAAS,KAAK;EAAa,GAC7C;GAAE,MAAM;GAAQ,SAAS;EAAU,CACrC,IACA;EACN,SAAS,mBAAmB,KAAK,KAAK;CACxC;CAEA,IAAI,UAAU,QAAW;EACvB,MAAM,QAAQ,SAAS,OAAO,OAAO;GAAE,MAAM,KAAK;GAAM,MAAM,KAAK;GAAM,OAAO;EAAQ,CAAC,IAAI;EAC7F,IAAI,UAAU,QACZ,KAAK,QAAQ;CAEjB;CAEA,IAAI,WAAW,QAAW;EACxB,MAAM,QAAQ,SAAS,OAAO,QAAQ;GAAE,MAAM,KAAK;GAAM,MAAM,KAAK;GAAM,OAAO;EAAS,CAAC,IAAI;EAC/F,IAAI,UAAU,QACZ,KAAK,SAAS;CAElB;AACF;;;;;;;;;AAUA,SAAS,mBAAmB,OAA6C;CACvE,KAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;EAC7C,MAAM,MAAM,MAAM,EAAE,EAAE;EAGtB,IAFgB,OAAO,QAAQ,WAAW,IAAI,SAAS,IAAI,QAAQ,QAGjE,OAAO;CAEX;AAGF"}
@@ -20,11 +20,14 @@ import { BaseReport } from "@warlock.js/ai";
20
20
  * without dispatching to exporters.
21
21
  *
22
22
  * The optional `rootError` threads the failing run's envelope error
23
- * (`BaseResult.error`) onto the root span. The error lives on the result
24
- * envelope, never on the `BaseReport` tree, so without this a failed root
25
- * would export `status` with no error type/message. Applied only when the
26
- * root span carries none of its own; the subtree projection stays pure
27
- * (each child surfaces its own report-level error, if any).
23
+ * (`BaseResult.error`) onto the root span a fallback for callers that
24
+ * hold the result envelope (the `attach`/middleware path). Root primitives
25
+ * now also stamp their terminal error onto the report itself
26
+ * (`BaseReport.error`), so the observe path which delivers only the
27
+ * report, never the envelope — still surfaces a failed root's error.
28
+ * `rootError` is applied only when the projected root span carries none of
29
+ * its own; the subtree projection stays pure (each child surfaces its own
30
+ * report-level error, if any).
28
31
  *
29
32
  * @example
30
33
  * const trace = reportToTrace(result.report, result.error);
@@ -1 +1 @@
1
- {"version":3,"file":"report-to-trace.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/report-to-trace.ts"],"mappings":";;;;;;;AAiCA;;;;;;;;;;;;;;;AAIQ;;;;;;;;;;iBAJQ,aAAA,CACd,MAAA,EAAQ,UAAA,EACR,SAAA,YACA,OAAA,GAAU,qBAAA,GACT,KAAA"}
1
+ {"version":3,"file":"report-to-trace.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/report-to-trace.ts"],"mappings":";;;;;;;AAoCA;;;;;;;;;;;;;;;AAIQ;;;;;;;;;;;;;iBAJQ,aAAA,CACd,MAAA,EAAQ,UAAA,EACR,SAAA,YACA,OAAA,GAAU,qBAAA,GACT,KAAA"}
@@ -19,11 +19,14 @@ import { reportToSpan } from "./report-to-span.mjs";
19
19
  * without dispatching to exporters.
20
20
  *
21
21
  * The optional `rootError` threads the failing run's envelope error
22
- * (`BaseResult.error`) onto the root span. The error lives on the result
23
- * envelope, never on the `BaseReport` tree, so without this a failed root
24
- * would export `status` with no error type/message. Applied only when the
25
- * root span carries none of its own; the subtree projection stays pure
26
- * (each child surfaces its own report-level error, if any).
22
+ * (`BaseResult.error`) onto the root span a fallback for callers that
23
+ * hold the result envelope (the `attach`/middleware path). Root primitives
24
+ * now also stamp their terminal error onto the report itself
25
+ * (`BaseReport.error`), so the observe path which delivers only the
26
+ * report, never the envelope — still surfaces a failed root's error.
27
+ * `rootError` is applied only when the projected root span carries none of
28
+ * its own; the subtree projection stays pure (each child surfaces its own
29
+ * report-level error, if any).
27
30
  *
28
31
  * @example
29
32
  * const trace = reportToTrace(result.report, result.error);
@@ -1 +1 @@
1
- {"version":3,"file":"report-to-trace.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/report-to-trace.ts"],"sourcesContent":["import type { BaseReport } from \"@warlock.js/ai\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport type { ContentCaptureOptions } from \"./content-capture.type\";\nimport { normalizeError } from \"./normalize-error\";\nimport { reportToSpan } from \"./report-to-span\";\n\n/**\n * Project an outermost {@link BaseReport} (one whole `.execute()` /\n * `.invoke()` run) into a {@link Trace} — the root {@link\n * import(\"../contracts/trace.type\").TraceSpan} plus the trace-wide\n * rollups exporters need without re-walking the tree.\n *\n * The trace-level identity and rollups all read off the root span the\n * projection already built (`traceId`, `usage`, timing), so the trace\n * envelope never disagrees with its own root. `reportSchemaVersion` is\n * mirrored from the root report when present (it is stamped only on\n * root nodes upstream) so exporters can branch on the source shape.\n *\n * Pure — the same input always yields the same trace. The collector\n * exposes this as `toTrace` so callers can inspect the normalized shape\n * without dispatching to exporters.\n *\n * The optional `rootError` threads the failing run's envelope error\n * (`BaseResult.error`) onto the root span. The error lives on the result\n * envelope, never on the `BaseReport` tree, so without this a failed root\n * would export `status` with no error type/message. Applied only when the\n * root span carries none of its own; the subtree projection stays pure\n * (each child surfaces its own report-level error, if any).\n *\n * @example\n * const trace = reportToTrace(result.report, result.error);\n * console.log(trace.traceId, trace.usage.total, trace.duration);\n */\nexport function reportToTrace(\n report: BaseReport,\n rootError?: unknown,\n options?: ContentCaptureOptions,\n): Trace {\n const root = reportToSpan(report, options);\n\n if (root.error === undefined) {\n const error = normalizeError(rootError);\n\n if (error !== undefined) {\n root.error = error;\n }\n }\n\n const trace: Trace = {\n traceId: root.traceId,\n root,\n startedAt: root.startedAt,\n endedAt: root.endedAt,\n duration: root.duration,\n usage: root.usage,\n };\n\n if (root.sessionId !== undefined) {\n trace.sessionId = root.sessionId;\n }\n\n if (report.reportSchemaVersion !== undefined) {\n trace.reportSchemaVersion = report.reportSchemaVersion;\n }\n\n return trace;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,cACd,QACA,WACA,SACO;CACP,MAAM,OAAO,aAAa,QAAQ,OAAO;CAEzC,IAAI,KAAK,UAAU,QAAW;EAC5B,MAAM,QAAQ,eAAe,SAAS;EAEtC,IAAI,UAAU,QACZ,KAAK,QAAQ;CAEjB;CAEA,MAAM,QAAe;EACnB,SAAS,KAAK;EACd;EACA,WAAW,KAAK;EAChB,SAAS,KAAK;EACd,UAAU,KAAK;EACf,OAAO,KAAK;CACd;CAEA,IAAI,KAAK,cAAc,QACrB,MAAM,YAAY,KAAK;CAGzB,IAAI,OAAO,wBAAwB,QACjC,MAAM,sBAAsB,OAAO;CAGrC,OAAO;AACT"}
1
+ {"version":3,"file":"report-to-trace.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/collector/report-to-trace.ts"],"sourcesContent":["import type { BaseReport } from \"@warlock.js/ai\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport type { ContentCaptureOptions } from \"./content-capture.type\";\nimport { normalizeError } from \"./normalize-error\";\nimport { reportToSpan } from \"./report-to-span\";\n\n/**\n * Project an outermost {@link BaseReport} (one whole `.execute()` /\n * `.invoke()` run) into a {@link Trace} — the root {@link\n * import(\"../contracts/trace.type\").TraceSpan} plus the trace-wide\n * rollups exporters need without re-walking the tree.\n *\n * The trace-level identity and rollups all read off the root span the\n * projection already built (`traceId`, `usage`, timing), so the trace\n * envelope never disagrees with its own root. `reportSchemaVersion` is\n * mirrored from the root report when present (it is stamped only on\n * root nodes upstream) so exporters can branch on the source shape.\n *\n * Pure — the same input always yields the same trace. The collector\n * exposes this as `toTrace` so callers can inspect the normalized shape\n * without dispatching to exporters.\n *\n * The optional `rootError` threads the failing run's envelope error\n * (`BaseResult.error`) onto the root span a fallback for callers that\n * hold the result envelope (the `attach`/middleware path). Root primitives\n * now also stamp their terminal error onto the report itself\n * (`BaseReport.error`), so the observe path which delivers only the\n * report, never the envelope still surfaces a failed root's error.\n * `rootError` is applied only when the projected root span carries none of\n * its own; the subtree projection stays pure (each child surfaces its own\n * report-level error, if any).\n *\n * @example\n * const trace = reportToTrace(result.report, result.error);\n * console.log(trace.traceId, trace.usage.total, trace.duration);\n */\nexport function reportToTrace(\n report: BaseReport,\n rootError?: unknown,\n options?: ContentCaptureOptions,\n): Trace {\n const root = reportToSpan(report, options);\n\n if (root.error === undefined) {\n const error = normalizeError(rootError);\n\n if (error !== undefined) {\n root.error = error;\n }\n }\n\n const trace: Trace = {\n traceId: root.traceId,\n root,\n startedAt: root.startedAt,\n endedAt: root.endedAt,\n duration: root.duration,\n usage: root.usage,\n };\n\n if (root.sessionId !== undefined) {\n trace.sessionId = root.sessionId;\n }\n\n if (report.reportSchemaVersion !== undefined) {\n trace.reportSchemaVersion = report.reportSchemaVersion;\n }\n\n return trace;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,cACd,QACA,WACA,SACO;CACP,MAAM,OAAO,aAAa,QAAQ,OAAO;CAEzC,IAAI,KAAK,UAAU,QAAW;EAC5B,MAAM,QAAQ,eAAe,SAAS;EAEtC,IAAI,UAAU,QACZ,KAAK,QAAQ;CAEjB;CAEA,MAAM,QAAe;EACnB,SAAS,KAAK;EACd;EACA,WAAW,KAAK;EAChB,SAAS,KAAK;EACd,UAAU,KAAK;EACf,OAAO,KAAK;CACd;CAEA,IAAI,KAAK,cAAc,QACrB,MAAM,YAAY,KAAK;CAGzB,IAAI,OAAO,wBAAwB,QACjC,MAAM,sBAAsB,OAAO;CAGrC,OAAO;AACT"}
@@ -0,0 +1,27 @@
1
+ import { PanopticConfig } from "./panoptic-config.type.mjs";
2
+
3
+ //#region ../@warlock.js/ai-panoptic/src/config/apply-panoptic-config.d.ts
4
+ /**
5
+ * Read a {@link PanopticConfig} and wire panoptic onto the core observe
6
+ * seam — the bridge `ai.config({ panoptic })` resolves through. Builds a
7
+ * collector via the EXISTING {@link panoptic} factory (reusing its
8
+ * collection pipeline, not reinventing it), registers it once via core's
9
+ * `registerObserver` (the subscriber's `collect(report)` structurally IS
10
+ * an {@link Observer}, threaded through a thin wrapper), sets
11
+ * `observeAll`, and — when `config.dashboard` is set — starts the
12
+ * dashboard over the config's store (a store-shaped exporter, or a fresh
13
+ * in-memory store panoptic registers when none was supplied).
14
+ *
15
+ * **Idempotent.** Safe to call on every `onConfigApplied` notification:
16
+ * the observer is registered once, the dashboard started once. Repeat
17
+ * calls update `observeAll` to the latest value but never double-register
18
+ * or double-start. `undefined` config is a no-op (nothing wired).
19
+ *
20
+ * @returns the resolved store-shaped exporter when the dashboard needs
21
+ * one and a fresh store was created — primarily for tests; callers can
22
+ * ignore it.
23
+ */
24
+ declare function applyPanopticConfig(config?: PanopticConfig): void;
25
+ //#endregion
26
+ export { applyPanopticConfig };
27
+ //# sourceMappingURL=apply-panoptic-config.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-panoptic-config.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/config/apply-panoptic-config.ts"],"mappings":";;;;AAgDA;;;;AAA2D;;;;;;;;;;;;;;;iBAA3C,mBAAA,CAAoB,MAAuB,GAAd,cAAc"}
@@ -0,0 +1,89 @@
1
+ import { createCacheTraceStore } from "../store/cache-trace-store.mjs";
2
+ import { createInMemoryTraceStore } from "../store/in-memory-trace-store.mjs";
3
+ import { panoptic } from "../panoptic/panoptic.mjs";
4
+ import { dashboard } from "../dashboard/dashboard.mjs";
5
+ import { registerObserver, setObserveAll } from "@warlock.js/ai";
6
+
7
+ //#region ../@warlock.js/ai-panoptic/src/config/apply-panoptic-config.ts
8
+ let applied;
9
+ /**
10
+ * Read a {@link PanopticConfig} and wire panoptic onto the core observe
11
+ * seam — the bridge `ai.config({ panoptic })` resolves through. Builds a
12
+ * collector via the EXISTING {@link panoptic} factory (reusing its
13
+ * collection pipeline, not reinventing it), registers it once via core's
14
+ * `registerObserver` (the subscriber's `collect(report)` structurally IS
15
+ * an {@link Observer}, threaded through a thin wrapper), sets
16
+ * `observeAll`, and — when `config.dashboard` is set — starts the
17
+ * dashboard over the config's store (a store-shaped exporter, or a fresh
18
+ * in-memory store panoptic registers when none was supplied).
19
+ *
20
+ * **Idempotent.** Safe to call on every `onConfigApplied` notification:
21
+ * the observer is registered once, the dashboard started once. Repeat
22
+ * calls update `observeAll` to the latest value but never double-register
23
+ * or double-start. `undefined` config is a no-op (nothing wired).
24
+ *
25
+ * @returns the resolved store-shaped exporter when the dashboard needs
26
+ * one and a fresh store was created — primarily for tests; callers can
27
+ * ignore it.
28
+ */
29
+ function applyPanopticConfig(config) {
30
+ if (config === void 0) return;
31
+ setObserveAll(Boolean(config.observeAll));
32
+ if (applied === void 0) {
33
+ const exporters = [...config.exporters ?? []];
34
+ let store = findStore(exporters);
35
+ let cacheStore;
36
+ if (store === void 0 && config.dashboard) {
37
+ if (config.cache !== void 0) {
38
+ cacheStore = createCacheTraceStore(config.cache);
39
+ store = cacheStore;
40
+ } else store = createInMemoryTraceStore();
41
+ exporters.push(store);
42
+ }
43
+ const panopticInstance = panoptic({
44
+ exporters,
45
+ captureContent: config.captureContent,
46
+ fullHistory: config.fullHistory
47
+ });
48
+ registerObserver(toObserver(panopticInstance));
49
+ applied = { panopticInstance };
50
+ if (config.dashboard && store !== void 0) {
51
+ const resolvedStore = store;
52
+ (cacheStore !== void 0 ? cacheStore.ready() : Promise.resolve()).catch(() => {}).then(() => startDashboard(resolvedStore, config.dashboard ?? {})).then((handle) => {
53
+ if (applied !== void 0) applied.dashboardHandle = handle;
54
+ });
55
+ }
56
+ }
57
+ }
58
+ /**
59
+ * Wrap a {@link Panoptic} subscriber as a core {@link Observer}. The
60
+ * subscriber already exposes `collect(report)` with a matching signature,
61
+ * but the thin wrapper makes the structural adaptation explicit and keeps
62
+ * the registered object a minimal `Observer` rather than the whole
63
+ * subscriber surface.
64
+ */
65
+ function toObserver(instance) {
66
+ return { collect(report) {
67
+ return instance.collect(report);
68
+ } };
69
+ }
70
+ /**
71
+ * Find the first exporter that satisfies the {@link TraceStoreContract}
72
+ * read surface (`query` / `get` / `aggregate`) — the in-memory store
73
+ * doubles as an exporter, so a store passed via `exporters` is reused for
74
+ * the dashboard rather than creating a second one.
75
+ */
76
+ function findStore(exporters) {
77
+ for (const exporter of exporters) {
78
+ const candidate = exporter;
79
+ if (typeof candidate.query === "function" && typeof candidate.get === "function" && typeof candidate.aggregate === "function") return candidate;
80
+ }
81
+ }
82
+ /** Start the dashboard, normalizing the `true | DashboardOptions` switch. */
83
+ function startDashboard(store, dashboardConfig) {
84
+ return dashboard(store, typeof dashboardConfig === "object" ? dashboardConfig : {});
85
+ }
86
+
87
+ //#endregion
88
+ export { applyPanopticConfig };
89
+ //# sourceMappingURL=apply-panoptic-config.mjs.map