@warlock.js/ai-panoptic 4.4.0 → 4.5.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
package/esm/index.d.mts CHANGED
@@ -2,7 +2,7 @@ import { Trace, TraceSpan, TraceSpanError } from "./contracts/trace.type.mjs";
2
2
  import { ExporterContract } from "./contracts/exporter.contract.mjs";
3
3
  import { CollectorContract } from "./contracts/collector.contract.mjs";
4
4
  import { ContentCaptureOptions, ContentRedactor } from "./collector/content-capture.type.mjs";
5
- import { createCollector } from "./collector/collector.mjs";
5
+ import { CollectorOptions, ExporterErrorHandler, createCollector } from "./collector/collector.mjs";
6
6
  import { reportToTrace } from "./collector/report-to-trace.mjs";
7
7
  import { reportToSpan } from "./collector/report-to-span.mjs";
8
8
  import { extractSpanAttributes } from "./collector/extract-span-attributes.mjs";
@@ -10,6 +10,7 @@ import { normalizeError } from "./collector/normalize-error.mjs";
10
10
  import { TraceAggregate } from "./store/trace-aggregate.type.mjs";
11
11
  import { TraceQuery } from "./store/trace-query.type.mjs";
12
12
  import { TraceStoreContract } from "./store/trace-store.contract.mjs";
13
+ import { CacheDriverInput, CacheTraceStoreHandle, CacheTraceStoreOptions, createCacheTraceStore } from "./store/cache-trace-store.mjs";
13
14
  import { InMemoryTraceStoreOptions, createInMemoryTraceStore } from "./store/in-memory-trace-store.mjs";
14
15
  import { matchTrace } from "./store/match-trace.mjs";
15
16
  import { emptyUsage, sumUsage } from "./store/sum-usage.mjs";
@@ -30,4 +31,10 @@ import { CompletedEventPayload, PanopticTarget } from "./panoptic/panoptic-targe
30
31
  import { Panoptic, PanopticOptions } from "./panoptic/panoptic.type.mjs";
31
32
  import { panoptic } from "./panoptic/panoptic.mjs";
32
33
  import { createPanopticMiddleware } from "./panoptic/panoptic-middleware.mjs";
33
- export { type AttributeValue, type CollectorContract, type CompletedEventPayload, type ConsoleExporterOptions, type ConsoleLike, type ContentCaptureOptions, type ContentRedactor, type ExporterContract, type FileExporterOptions, GEN_AI_ATTRIBUTES, type InMemoryTraceStoreOptions, type LangfuseClientLike, type LangfuseExporterOptions, type LangfuseObservationBody, type LangfuseObservationEndBody, type LangfuseObservationLevel, type LangfuseObservationLike, type LangfuseTraceBody, type LangfuseTraceLike, type LangfuseUsageBody, type OtelExporterOptions, type Panoptic, type PanopticOptions, type PanopticTarget, type Trace, type TraceAggregate, type TraceQuery, type TraceRecord, type TraceSpan, type TraceSpanError, type TraceStoreContract, WARLOCK_ATTRIBUTES, consoleExporter, createCollector, createInMemoryTraceStore, createPanopticMiddleware, emptyUsage, extractSpanAttributes, fileExporter, formatSpanIO, formatSpanLine, langfuseExporter, matchTrace, normalizeError, otelExporter, panoptic, reportToSpan, reportToTrace, sumUsage, toGenAiAttributes, totalCostUsd, walkSpans };
34
+ import { DashboardHandle, DashboardOptions } from "./dashboard/dashboard.type.mjs";
35
+ import { dashboard } from "./dashboard/dashboard.mjs";
36
+ import { NO_PROMPT_KEY, NO_SESSION_KEY, NO_TYPE_KEY, PromptGroup, SessionGroup, TraceFilter, TypeGroup, TypeStat, aggregateByType, filterTraces, groupByPrompt, groupBySession, groupByType, heatIntensity, matchesFilter, maxNodeCost, percentile, rollupCost, traceCost, tracePromptKey } from "./dashboard/trace-filter.mjs";
37
+ import { PanopticConfig } from "./config/panoptic-config.type.mjs";
38
+ import { applyPanopticConfig } from "./config/apply-panoptic-config.mjs";
39
+ export { type AttributeValue, type CacheDriverInput, type CacheTraceStoreHandle, type CacheTraceStoreOptions, type CollectorContract, type CollectorOptions, type CompletedEventPayload, type ConsoleExporterOptions, type ConsoleLike, type ContentCaptureOptions, type ContentRedactor, type DashboardHandle, type DashboardOptions, type ExporterContract, type ExporterErrorHandler, type FileExporterOptions, GEN_AI_ATTRIBUTES, type InMemoryTraceStoreOptions, type LangfuseClientLike, type LangfuseExporterOptions, type LangfuseObservationBody, type LangfuseObservationEndBody, type LangfuseObservationLevel, type LangfuseObservationLike, type LangfuseTraceBody, type LangfuseTraceLike, type LangfuseUsageBody, NO_PROMPT_KEY, NO_SESSION_KEY, NO_TYPE_KEY, type OtelExporterOptions, type Panoptic, type PanopticConfig, type PanopticOptions, type PanopticTarget, type PromptGroup, type SessionGroup, type Trace, type TraceAggregate, type TraceFilter, type TraceQuery, type TraceRecord, type TraceSpan, type TraceSpanError, type TraceStoreContract, type TypeGroup, type TypeStat, WARLOCK_ATTRIBUTES, aggregateByType, applyPanopticConfig, consoleExporter, createCacheTraceStore, createCollector, createInMemoryTraceStore, createPanopticMiddleware, dashboard, emptyUsage, extractSpanAttributes, fileExporter, filterTraces, formatSpanIO, formatSpanLine, groupByPrompt, groupBySession, groupByType, heatIntensity, langfuseExporter, matchTrace, matchesFilter, maxNodeCost, normalizeError, otelExporter, panoptic, percentile, reportToSpan, reportToTrace, rollupCost, sumUsage, toGenAiAttributes, totalCostUsd, traceCost, tracePromptKey, walkSpans };
40
+ import "./config/panoptic-config.type.mjs";
package/esm/index.mjs CHANGED
@@ -4,8 +4,10 @@ import { extractSpanAttributes } from "./collector/extract-span-attributes.mjs";
4
4
  import { reportToSpan } from "./collector/report-to-span.mjs";
5
5
  import { reportToTrace } from "./collector/report-to-trace.mjs";
6
6
  import { createCollector } from "./collector/collector.mjs";
7
+ import "./collector/index.mjs";
7
8
  import { matchTrace } from "./store/match-trace.mjs";
8
9
  import { emptyUsage, sumUsage } from "./store/sum-usage.mjs";
10
+ import { createCacheTraceStore } from "./store/cache-trace-store.mjs";
9
11
  import { createInMemoryTraceStore } from "./store/in-memory-trace-store.mjs";
10
12
  import "./store/index.mjs";
11
13
  import { totalCostUsd } from "./exporters/utils/total-cost.mjs";
@@ -19,5 +21,11 @@ import { otelExporter } from "./exporters/otel/otel-exporter.mjs";
19
21
  import "./exporters/index.mjs";
20
22
  import { createPanopticMiddleware } from "./panoptic/panoptic-middleware.mjs";
21
23
  import { panoptic } from "./panoptic/panoptic.mjs";
24
+ import "./panoptic/index.mjs";
25
+ import { dashboard } from "./dashboard/dashboard.mjs";
26
+ import { NO_PROMPT_KEY, NO_SESSION_KEY, NO_TYPE_KEY, aggregateByType, filterTraces, groupByPrompt, groupBySession, groupByType, heatIntensity, matchesFilter, maxNodeCost, percentile, rollupCost, traceCost, tracePromptKey } from "./dashboard/trace-filter.mjs";
27
+ import { applyPanopticConfig } from "./config/apply-panoptic-config.mjs";
28
+ import "./config/index.mjs";
29
+ import "./register.mjs";
22
30
 
23
- export { GEN_AI_ATTRIBUTES, WARLOCK_ATTRIBUTES, consoleExporter, createCollector, createInMemoryTraceStore, createPanopticMiddleware, emptyUsage, extractSpanAttributes, fileExporter, formatSpanIO, formatSpanLine, langfuseExporter, matchTrace, normalizeError, otelExporter, panoptic, reportToSpan, reportToTrace, sumUsage, toGenAiAttributes, totalCostUsd, walkSpans };
31
+ export { GEN_AI_ATTRIBUTES, NO_PROMPT_KEY, NO_SESSION_KEY, NO_TYPE_KEY, WARLOCK_ATTRIBUTES, aggregateByType, applyPanopticConfig, consoleExporter, createCacheTraceStore, createCollector, createInMemoryTraceStore, createPanopticMiddleware, dashboard, emptyUsage, extractSpanAttributes, fileExporter, filterTraces, formatSpanIO, formatSpanLine, groupByPrompt, groupBySession, groupByType, heatIntensity, langfuseExporter, matchTrace, matchesFilter, maxNodeCost, normalizeError, otelExporter, panoptic, percentile, reportToSpan, reportToTrace, rollupCost, sumUsage, toGenAiAttributes, totalCostUsd, traceCost, tracePromptKey, walkSpans };
@@ -0,0 +1,4 @@
1
+ import { createPanopticMiddleware } from "./panoptic-middleware.mjs";
2
+ import { panoptic } from "./panoptic.mjs";
3
+
4
+ export { };
@@ -1 +1 @@
1
- {"version":3,"file":"panoptic.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/panoptic/panoptic.ts"],"mappings":";;;;;AA6LA;;;;;;;;AAAiE;;;;;;;;;;;;;;;;;;;;iBAAjD,QAAA,CAAS,OAAA,GAAS,eAAA,GAAuB,QAAQ"}
1
+ {"version":3,"file":"panoptic.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/panoptic/panoptic.ts"],"mappings":";;;;;AA+LA;;;;;;;;AAAiE;;;;;;;;;;;;;;;;;;;;iBAAjD,QAAA,CAAS,OAAA,GAAS,eAAA,GAAuB,QAAQ"}
@@ -25,10 +25,15 @@ const DEFAULT_COMPLETED_EVENTS = [
25
25
  * {@link panoptic}; callers never see `new`.
26
26
  */
27
27
  var PanopticSubscriber = class {
28
+ collector;
29
+ completedEvents;
30
+ middlewareName;
28
31
  constructor(options = {}) {
29
32
  this.collector = options.collector ?? createCollector({
30
33
  captureContent: options.captureContent,
31
- redactContent: options.redactContent
34
+ redactContent: options.redactContent,
35
+ fullHistory: options.fullHistory,
36
+ onError: options.onError
32
37
  });
33
38
  for (const exporter of options.exporters ?? []) this.collector.use(exporter);
34
39
  this.completedEvents = options.completedEvents ?? [...DEFAULT_COMPLETED_EVENTS];
@@ -1 +1 @@
1
- {"version":3,"file":"panoptic.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/panoptic/panoptic.ts"],"sourcesContent":["import type { AgentMiddleware, BaseReport } from \"@warlock.js/ai\";\nimport { createCollector } from \"../collector/collector\";\nimport type { CollectorContract } from \"../contracts/collector.contract\";\nimport type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { createPanopticMiddleware } from \"./panoptic-middleware\";\nimport type { CompletedEventPayload, PanopticTarget } from \"./panoptic-target.type\";\nimport type { Panoptic, PanopticOptions } from \"./panoptic.type\";\n\n/**\n * Terminal `*.completed` events of every core primitive that carries the\n * finalized `result` (and therefore the `report` tree). These fire once\n * per run regardless of outcome — the matching `*.error` event fires\n * first on failure, then `*.completed` still fires — so subscribing here\n * captures completed, failed, and cancelled runs alike.\n *\n * The orchestrator is intentionally absent: its `orchestrator.turn.*`\n * events carry only session identity, not a result. Collect an\n * orchestrator turn via {@link Panoptic.collect} with\n * `result.report` instead.\n */\nconst DEFAULT_COMPLETED_EVENTS = [\n \"agent.completed\",\n \"workflow.completed\",\n \"supervisor.completed\",\n] as const;\n\n/**\n * The Panoptic subscriber — binds a collector + its exporters to the\n * three feed paths (events, middleware, direct). Instantiated via\n * {@link panoptic}; callers never see `new`.\n */\nclass PanopticSubscriber implements Panoptic {\n public readonly collector: CollectorContract;\n\n private readonly completedEvents: string[];\n\n private readonly middlewareName: string;\n\n public constructor(options: PanopticOptions = {}) {\n this.collector =\n options.collector ??\n createCollector({\n captureContent: options.captureContent,\n redactContent: options.redactContent,\n });\n\n for (const exporter of options.exporters ?? []) {\n this.collector.use(exporter);\n }\n\n this.completedEvents =\n options.completedEvents ?? [...DEFAULT_COMPLETED_EVENTS];\n this.middlewareName = options.middlewareName ?? \"panoptic\";\n }\n\n public use(exporter: ExporterContract): Panoptic {\n this.collector.use(exporter);\n\n return this;\n }\n\n public attach(target: PanopticTarget): () => void {\n const unsubscribes: Array<() => void> = [];\n\n for (const event of this.completedEvents) {\n const unsubscribe = target.on(event, (payload) => {\n this.handleCompleted(payload);\n });\n\n unsubscribes.push(unsubscribe);\n }\n\n return () => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe();\n }\n };\n }\n\n public middleware(): AgentMiddleware {\n return createPanopticMiddleware(this.collector, this.middlewareName);\n }\n\n public async collect(report: BaseReport): Promise<void> {\n await this.collector.collect(report);\n }\n\n public toTrace(report: BaseReport): Trace {\n return this.collector.toTrace(report);\n }\n\n public async flush(): Promise<void> {\n await this.collector.flush();\n }\n\n public async shutdown(): Promise<void> {\n await this.collector.shutdown();\n }\n\n /**\n * Project one terminal `*.completed` payload's report into the\n * collector. The fan-out is fire-and-forget relative to the emitting\n * run: the core swallows handler errors, the collector isolates\n * exporter failures, and we additionally guard the rejection here so an\n * observability fault never escapes the event handler.\n */\n private handleCompleted(payload: unknown): void {\n const report = readReport(payload);\n\n if (!report) {\n return;\n }\n\n // The failing run's typed error lives on the result envelope\n // (`BaseResult.error`), never on the report tree — thread it so a\n // failed root span carries its error type/message.\n const rootError = readResultError(payload);\n\n void this.collector.collect(report, rootError).catch(() => {\n // Swallow — see the JSDoc above. Never surface on the run.\n });\n }\n}\n\n/**\n * Read the envelope error off a primitive's completed-event payload\n * (`{ result: { error } }`). The error rides on the result envelope, not\n * the report tree, so the collector needs it separately to populate a\n * failed root span. Returns `undefined` when the run succeeded.\n */\nfunction readResultError(payload: unknown): unknown {\n const result = (payload as Partial<CompletedEventPayload>)?.result;\n\n return (result as { error?: unknown })?.error;\n}\n\n/**\n * Read the `report` tree off a primitive's completed-event payload.\n * Structural (no concrete-type import) so it accepts every primitive's\n * result subtype; returns `undefined` when the payload isn't the\n * expected `{ result: { report } }` shape.\n */\nfunction readReport(payload: unknown): BaseReport | undefined {\n const result = (payload as Partial<CompletedEventPayload>)?.result;\n const report = (result as { report?: unknown })?.report;\n\n if (\n typeof report === \"object\" &&\n report !== null &&\n typeof (report as { runId?: unknown }).runId === \"string\" &&\n typeof (report as { rootRunId?: unknown }).rootRunId === \"string\"\n ) {\n return report as BaseReport;\n }\n\n return undefined;\n}\n\n/**\n * Create a Panoptic subscriber — the one-call entry point that wires the\n * observability pipeline. Pass the exporters you want and Panoptic\n * builds a collector, registers them, and hands back a subscriber you can\n * `attach()` to any agent/workflow/supervisor, install as agent\n * `middleware()`, or feed reports to directly with `collect()`.\n *\n * @example\n * // Attach to a primitive's event stream (captures every run):\n * const observe = panoptic({\n * exporters: [consoleExporter(), otelExporter({ tracerName: \"app\" })],\n * });\n *\n * const agent = ai.agent({ model });\n * const detach = observe.attach(agent);\n *\n * await agent.execute(\"Summarize this\");\n * // ...later, on shutdown:\n * await observe.shutdown();\n *\n * @example\n * // Or wire it through the agent middleware pipeline:\n * const observe = panoptic({ exporters: [langfuseExporter({ ... })] });\n * const agent = ai.agent({ model, middleware: [observe.middleware()] });\n *\n * @example\n * // Orchestrator turns carry no result-bearing event — collect directly:\n * const result = await orchestrator.execute(input, { sessionId });\n * await observe.collect(result.report);\n */\nexport function panoptic(options: PanopticOptions = {}): Panoptic {\n return new PanopticSubscriber(options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,MAAM,2BAA2B;CAC/B;CACA;CACA;AACF;;;;;;AAOA,IAAM,qBAAN,MAA6C;CAO3C,AAAO,YAAY,UAA2B,CAAC,GAAG;EAChD,KAAK,YACH,QAAQ,aACR,gBAAgB;GACd,gBAAgB,QAAQ;GACxB,eAAe,QAAQ;EACzB,CAAC;EAEH,KAAK,MAAM,YAAY,QAAQ,aAAa,CAAC,GAC3C,KAAK,UAAU,IAAI,QAAQ;EAG7B,KAAK,kBACH,QAAQ,mBAAmB,CAAC,GAAG,wBAAwB;EACzD,KAAK,iBAAiB,QAAQ,kBAAkB;CAClD;CAEA,AAAO,IAAI,UAAsC;EAC/C,KAAK,UAAU,IAAI,QAAQ;EAE3B,OAAO;CACT;CAEA,AAAO,OAAO,QAAoC;EAChD,MAAM,eAAkC,CAAC;EAEzC,KAAK,MAAM,SAAS,KAAK,iBAAiB;GACxC,MAAM,cAAc,OAAO,GAAG,QAAQ,YAAY;IAChD,KAAK,gBAAgB,OAAO;GAC9B,CAAC;GAED,aAAa,KAAK,WAAW;EAC/B;EAEA,aAAa;GACX,KAAK,MAAM,eAAe,cACxB,YAAY;EAEhB;CACF;CAEA,AAAO,aAA8B;EACnC,OAAO,yBAAyB,KAAK,WAAW,KAAK,cAAc;CACrE;CAEA,MAAa,QAAQ,QAAmC;EACtD,MAAM,KAAK,UAAU,QAAQ,MAAM;CACrC;CAEA,AAAO,QAAQ,QAA2B;EACxC,OAAO,KAAK,UAAU,QAAQ,MAAM;CACtC;CAEA,MAAa,QAAuB;EAClC,MAAM,KAAK,UAAU,MAAM;CAC7B;CAEA,MAAa,WAA0B;EACrC,MAAM,KAAK,UAAU,SAAS;CAChC;;;;;;;;CASA,AAAQ,gBAAgB,SAAwB;EAC9C,MAAM,SAAS,WAAW,OAAO;EAEjC,IAAI,CAAC,QACH;EAMF,MAAM,YAAY,gBAAgB,OAAO;EAEzC,AAAK,KAAK,UAAU,QAAQ,QAAQ,SAAS,CAAC,CAAC,YAAY,CAE3D,CAAC;CACH;AACF;;;;;;;AAQA,SAAS,gBAAgB,SAA2B;CAGlD,QAFgB,SAA4C,OAE9C,EAA0B;AAC1C;;;;;;;AAQA,SAAS,WAAW,SAA0C;CAE5D,MAAM,UADU,SAA4C,OACtC,EAA2B;CAEjD,IACE,OAAO,WAAW,YAClB,WAAW,QACX,OAAQ,OAA+B,UAAU,YACjD,OAAQ,OAAmC,cAAc,UAEzD,OAAO;AAIX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,SAAS,UAA2B,CAAC,GAAa;CAChE,OAAO,IAAI,mBAAmB,OAAO;AACvC"}
1
+ {"version":3,"file":"panoptic.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/panoptic/panoptic.ts"],"sourcesContent":["import type { AgentMiddleware, BaseReport } from \"@warlock.js/ai\";\nimport { createCollector } from \"../collector/collector\";\nimport type { CollectorContract } from \"../contracts/collector.contract\";\nimport type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { createPanopticMiddleware } from \"./panoptic-middleware\";\nimport type { CompletedEventPayload, PanopticTarget } from \"./panoptic-target.type\";\nimport type { Panoptic, PanopticOptions } from \"./panoptic.type\";\n\n/**\n * Terminal `*.completed` events of every core primitive that carries the\n * finalized `result` (and therefore the `report` tree). These fire once\n * per run regardless of outcome — the matching `*.error` event fires\n * first on failure, then `*.completed` still fires — so subscribing here\n * captures completed, failed, and cancelled runs alike.\n *\n * The orchestrator is intentionally absent: its `orchestrator.turn.*`\n * events carry only session identity, not a result. Collect an\n * orchestrator turn via {@link Panoptic.collect} with\n * `result.report` instead.\n */\nconst DEFAULT_COMPLETED_EVENTS = [\n \"agent.completed\",\n \"workflow.completed\",\n \"supervisor.completed\",\n] as const;\n\n/**\n * The Panoptic subscriber — binds a collector + its exporters to the\n * three feed paths (events, middleware, direct). Instantiated via\n * {@link panoptic}; callers never see `new`.\n */\nclass PanopticSubscriber implements Panoptic {\n public readonly collector: CollectorContract;\n\n private readonly completedEvents: string[];\n\n private readonly middlewareName: string;\n\n public constructor(options: PanopticOptions = {}) {\n this.collector =\n options.collector ??\n createCollector({\n captureContent: options.captureContent,\n redactContent: options.redactContent,\n fullHistory: options.fullHistory,\n onError: options.onError,\n });\n\n for (const exporter of options.exporters ?? []) {\n this.collector.use(exporter);\n }\n\n this.completedEvents =\n options.completedEvents ?? [...DEFAULT_COMPLETED_EVENTS];\n this.middlewareName = options.middlewareName ?? \"panoptic\";\n }\n\n public use(exporter: ExporterContract): Panoptic {\n this.collector.use(exporter);\n\n return this;\n }\n\n public attach(target: PanopticTarget): () => void {\n const unsubscribes: Array<() => void> = [];\n\n for (const event of this.completedEvents) {\n const unsubscribe = target.on(event, (payload) => {\n this.handleCompleted(payload);\n });\n\n unsubscribes.push(unsubscribe);\n }\n\n return () => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe();\n }\n };\n }\n\n public middleware(): AgentMiddleware {\n return createPanopticMiddleware(this.collector, this.middlewareName);\n }\n\n public async collect(report: BaseReport): Promise<void> {\n await this.collector.collect(report);\n }\n\n public toTrace(report: BaseReport): Trace {\n return this.collector.toTrace(report);\n }\n\n public async flush(): Promise<void> {\n await this.collector.flush();\n }\n\n public async shutdown(): Promise<void> {\n await this.collector.shutdown();\n }\n\n /**\n * Project one terminal `*.completed` payload's report into the\n * collector. The fan-out is fire-and-forget relative to the emitting\n * run: the core swallows handler errors, the collector isolates\n * exporter failures, and we additionally guard the rejection here so an\n * observability fault never escapes the event handler.\n */\n private handleCompleted(payload: unknown): void {\n const report = readReport(payload);\n\n if (!report) {\n return;\n }\n\n // The failing run's typed error lives on the result envelope\n // (`BaseResult.error`), never on the report tree — thread it so a\n // failed root span carries its error type/message.\n const rootError = readResultError(payload);\n\n void this.collector.collect(report, rootError).catch(() => {\n // Swallow — see the JSDoc above. Never surface on the run.\n });\n }\n}\n\n/**\n * Read the envelope error off a primitive's completed-event payload\n * (`{ result: { error } }`). The error rides on the result envelope, not\n * the report tree, so the collector needs it separately to populate a\n * failed root span. Returns `undefined` when the run succeeded.\n */\nfunction readResultError(payload: unknown): unknown {\n const result = (payload as Partial<CompletedEventPayload>)?.result;\n\n return (result as { error?: unknown })?.error;\n}\n\n/**\n * Read the `report` tree off a primitive's completed-event payload.\n * Structural (no concrete-type import) so it accepts every primitive's\n * result subtype; returns `undefined` when the payload isn't the\n * expected `{ result: { report } }` shape.\n */\nfunction readReport(payload: unknown): BaseReport | undefined {\n const result = (payload as Partial<CompletedEventPayload>)?.result;\n const report = (result as { report?: unknown })?.report;\n\n if (\n typeof report === \"object\" &&\n report !== null &&\n typeof (report as { runId?: unknown }).runId === \"string\" &&\n typeof (report as { rootRunId?: unknown }).rootRunId === \"string\"\n ) {\n return report as BaseReport;\n }\n\n return undefined;\n}\n\n/**\n * Create a Panoptic subscriber — the one-call entry point that wires the\n * observability pipeline. Pass the exporters you want and Panoptic\n * builds a collector, registers them, and hands back a subscriber you can\n * `attach()` to any agent/workflow/supervisor, install as agent\n * `middleware()`, or feed reports to directly with `collect()`.\n *\n * @example\n * // Attach to a primitive's event stream (captures every run):\n * const observe = panoptic({\n * exporters: [consoleExporter(), otelExporter({ tracerName: \"app\" })],\n * });\n *\n * const agent = ai.agent({ model });\n * const detach = observe.attach(agent);\n *\n * await agent.execute(\"Summarize this\");\n * // ...later, on shutdown:\n * await observe.shutdown();\n *\n * @example\n * // Or wire it through the agent middleware pipeline:\n * const observe = panoptic({ exporters: [langfuseExporter({ ... })] });\n * const agent = ai.agent({ model, middleware: [observe.middleware()] });\n *\n * @example\n * // Orchestrator turns carry no result-bearing event — collect directly:\n * const result = await orchestrator.execute(input, { sessionId });\n * await observe.collect(result.report);\n */\nexport function panoptic(options: PanopticOptions = {}): Panoptic {\n return new PanopticSubscriber(options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,MAAM,2BAA2B;CAC/B;CACA;CACA;AACF;;;;;;AAOA,IAAM,qBAAN,MAA6C;CAC3C,AAAgB;CAEhB,AAAiB;CAEjB,AAAiB;CAEjB,AAAO,YAAY,UAA2B,CAAC,GAAG;EAChD,KAAK,YACH,QAAQ,aACR,gBAAgB;GACd,gBAAgB,QAAQ;GACxB,eAAe,QAAQ;GACvB,aAAa,QAAQ;GACrB,SAAS,QAAQ;EACnB,CAAC;EAEH,KAAK,MAAM,YAAY,QAAQ,aAAa,CAAC,GAC3C,KAAK,UAAU,IAAI,QAAQ;EAG7B,KAAK,kBACH,QAAQ,mBAAmB,CAAC,GAAG,wBAAwB;EACzD,KAAK,iBAAiB,QAAQ,kBAAkB;CAClD;CAEA,AAAO,IAAI,UAAsC;EAC/C,KAAK,UAAU,IAAI,QAAQ;EAE3B,OAAO;CACT;CAEA,AAAO,OAAO,QAAoC;EAChD,MAAM,eAAkC,CAAC;EAEzC,KAAK,MAAM,SAAS,KAAK,iBAAiB;GACxC,MAAM,cAAc,OAAO,GAAG,QAAQ,YAAY;IAChD,KAAK,gBAAgB,OAAO;GAC9B,CAAC;GAED,aAAa,KAAK,WAAW;EAC/B;EAEA,aAAa;GACX,KAAK,MAAM,eAAe,cACxB,YAAY;EAEhB;CACF;CAEA,AAAO,aAA8B;EACnC,OAAO,yBAAyB,KAAK,WAAW,KAAK,cAAc;CACrE;CAEA,MAAa,QAAQ,QAAmC;EACtD,MAAM,KAAK,UAAU,QAAQ,MAAM;CACrC;CAEA,AAAO,QAAQ,QAA2B;EACxC,OAAO,KAAK,UAAU,QAAQ,MAAM;CACtC;CAEA,MAAa,QAAuB;EAClC,MAAM,KAAK,UAAU,MAAM;CAC7B;CAEA,MAAa,WAA0B;EACrC,MAAM,KAAK,UAAU,SAAS;CAChC;;;;;;;;CASA,AAAQ,gBAAgB,SAAwB;EAC9C,MAAM,SAAS,WAAW,OAAO;EAEjC,IAAI,CAAC,QACH;EAMF,MAAM,YAAY,gBAAgB,OAAO;EAEzC,AAAK,KAAK,UAAU,QAAQ,QAAQ,SAAS,CAAC,CAAC,YAAY,CAE3D,CAAC;CACH;AACF;;;;;;;AAQA,SAAS,gBAAgB,SAA2B;CAGlD,QAFgB,SAA4C,OAE9C,EAA0B;AAC1C;;;;;;;AAQA,SAAS,WAAW,SAA0C;CAE5D,MAAM,UADU,SAA4C,OACtC,EAA2B;CAEjD,IACE,OAAO,WAAW,YAClB,WAAW,QACX,OAAQ,OAA+B,UAAU,YACjD,OAAQ,OAAmC,cAAc,UAEzD,OAAO;AAIX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,SAAS,UAA2B,CAAC,GAAa;CAChE,OAAO,IAAI,mBAAmB,OAAO;AACvC"}
@@ -2,6 +2,7 @@ import { Trace } from "../contracts/trace.type.mjs";
2
2
  import { ExporterContract } from "../contracts/exporter.contract.mjs";
3
3
  import { CollectorContract } from "../contracts/collector.contract.mjs";
4
4
  import { ContentRedactor } from "../collector/content-capture.type.mjs";
5
+ import { ExporterErrorHandler } from "../collector/collector.mjs";
5
6
  import { PanopticTarget } from "./panoptic-target.type.mjs";
6
7
  import { AgentMiddleware, BaseReport } from "@warlock.js/ai";
7
8
 
@@ -59,6 +60,20 @@ type PanopticOptions = {
59
60
  * environment. See {@link ContentRedactor}.
60
61
  */
61
62
  redactContent?: ContentRedactor;
63
+ /**
64
+ * Emit the agent's full message history (every trip, every role) onto
65
+ * `span.input` instead of just the `[system, user]` first-trip array.
66
+ * Requires the agent run to set `captureMessages`. Default `false`.
67
+ */
68
+ fullHistory?: boolean;
69
+ /**
70
+ * Called when an exporter throws (and is isolated). By default a failing
71
+ * exporter logs a one-time `console.warn`; supply this to route the
72
+ * failure to your own logger / metrics instead. Applies only when
73
+ * Panoptic builds the collector (ignored when you inject your own
74
+ * `collector`). See {@link ExporterErrorHandler}.
75
+ */
76
+ onError?: ExporterErrorHandler;
62
77
  };
63
78
  /**
64
79
  * The subscriber returned by {@link panoptic}. Wraps a collector +
@@ -1 +1 @@
1
- {"version":3,"file":"panoptic.type.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/panoptic/panoptic.type.ts"],"mappings":";;;;;;;;;;AAeA;;;;;;KAAY,eAAA;EA4CqB;;;;;EAtC/B,SAAA,GAAY,gBAAA;EAsBZ;;;;;AAgB+B;EA/B/B,SAAA,GAAY,iBAAA;EAmDM;;;;;;;;EA1ClB,eAAA;EA0EgB;;;;;EApEhB,cAAA;EAsCS;;;;;;;;;EA5BT,cAAA;EA+Cc;;;;;EAzCd,aAAA,GAAgB,eAAA;AAAA;;;;;;;;AAwDG;;;;;;;;;;KApCT,QAAA;mFAED,SAAA,EAAW,iBAAA;;;;;EAKpB,GAAA,CAAI,QAAA,EAAU,gBAAA,GAAmB,QAAA;;;;;;;EAOjC,MAAA,CAAO,MAAA,EAAQ,cAAA;;;;;;;EAOf,UAAA,IAAc,eAAA;;;;;;EAMd,OAAA,CAAQ,MAAA,EAAQ,UAAA,GAAa,OAAA;;;;;EAK7B,OAAA,CAAQ,MAAA,EAAQ,UAAA,GAAa,KAAA;EAE7B,KAAA,IAAS,OAAA;EAET,QAAA,IAAY,OAAA;AAAA"}
1
+ {"version":3,"file":"panoptic.type.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/panoptic/panoptic.type.ts"],"mappings":";;;;;;;;;;;AAgBA;;;;;;KAAY,eAAA;EA0DoB;;;;;EApD9B,SAAA,GAAY,gBAAA;EAgBZ;;;;;;EATA,SAAA,GAAY,iBAAA;EA6CF;;AAAoB;AAoBhC;;;;;EAxDE,eAAA;EAsEe;;;;;EAhEf,cAAA;EAoFS;;;;;;;;;EA1ET,cAAA;EAsDA;;;;;EAhDA,aAAA,GAAgB,eAAA;EA6DA;;;;;EAvDhB,WAAA;EA4D6B;;;;;;AAIV;EAxDnB,OAAA,GAAU,oBAAA;AAAA;;;;;;;;;;;;;;;;;;KAoBA,QAAA;mFAED,SAAA,EAAW,iBAAA;;;;;EAKpB,GAAA,CAAI,QAAA,EAAU,gBAAA,GAAmB,QAAA;;;;;;;EAOjC,MAAA,CAAO,MAAA,EAAQ,cAAA;;;;;;;EAOf,UAAA,IAAc,eAAA;;;;;;EAMd,OAAA,CAAQ,MAAA,EAAQ,UAAA,GAAa,OAAA;;;;;EAK7B,OAAA,CAAQ,MAAA,EAAQ,UAAA,GAAa,KAAA;EAE7B,KAAA,IAAS,OAAA;EAET,QAAA,IAAY,OAAA;AAAA"}
@@ -0,0 +1,12 @@
1
+ import { applyPanopticConfig } from "./config/apply-panoptic-config.mjs";
2
+ import { getAIConfig, onConfigApplied } from "@warlock.js/ai";
3
+
4
+ //#region ../@warlock.js/ai-panoptic/src/register.ts
5
+ onConfigApplied((config) => {
6
+ applyPanopticConfig(config.panoptic);
7
+ });
8
+ applyPanopticConfig(getAIConfig().panoptic);
9
+
10
+ //#endregion
11
+ export { };
12
+ //# sourceMappingURL=register.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-panoptic/src/register.ts"],"sourcesContent":["import { getAIConfig, onConfigApplied } from \"@warlock.js/ai\";\nimport { applyPanopticConfig } from \"./config/apply-panoptic-config\";\n\n// Side-effect wiring. Importing `@warlock.js/ai-panoptic` (even bare,\n// `import \"@warlock.js/ai-panoptic\"`) subscribes panoptic to the core\n// config seam so a later `ai.config({ panoptic })` wires the collector +\n// dashboard onto the observe registry — without app code calling\n// `applyPanopticConfig` by hand.\n//\n// 1. React to every future `ai.config(...)` merge.\nonConfigApplied((config) => {\n applyPanopticConfig(config.panoptic);\n});\n\n// 2. Catch config that was applied BEFORE this import ran (e.g. the app\n// called `ai.config({ panoptic })` and only then imported panoptic).\napplyPanopticConfig(getAIConfig().panoptic);\n"],"mappings":";;;;AAUA,iBAAiB,WAAW;CAC1B,oBAAoB,OAAO,QAAQ;AACrC,CAAC;AAID,oBAAoB,YAAY,CAAC,CAAC,QAAQ"}
@@ -0,0 +1,81 @@
1
+ import { ExporterContract } from "../contracts/exporter.contract.mjs";
2
+ import { TraceStoreContract } from "./trace-store.contract.mjs";
3
+ import { CacheDriver } from "@warlock.js/cache";
4
+
5
+ //#region ../@warlock.js/ai-panoptic/src/store/cache-trace-store.d.ts
6
+ /**
7
+ * A {@link CacheDriver} instance, or a (possibly async) factory that
8
+ * yields one on first use. The factory form lets production defer an
9
+ * expensive connect (e.g. the Redis handshake) until the first trace is
10
+ * actually written, and keeps the dashboard wiring free of a live driver
11
+ * at module-import time.
12
+ *
13
+ * Typed `CacheDriver<any, any>` because the store only ever touches the
14
+ * driver's `get` / `set` / `remove` surface and is agnostic to the
15
+ * concrete client + options of whichever driver backs it.
16
+ */
17
+ type CacheDriverInput = CacheDriver<any, any> | (() => CacheDriver<any, any> | Promise<CacheDriver<any, any>>);
18
+ /**
19
+ * Options for {@link createCacheTraceStore}.
20
+ */
21
+ type CacheTraceStoreOptions = {
22
+ /**
23
+ * Key prefix every cache entry this store writes is namespaced under.
24
+ * Per-trace keys are `${prefix}:trace:${traceId}`; the newest-first
25
+ * index lives at `${prefix}:index`. Default `"panoptic"`.
26
+ */
27
+ prefix?: string;
28
+ /**
29
+ * Maximum number of traces to retain. When set and exceeded, the
30
+ * oldest-ingested trace is evicted from both the cache and the in-memory
31
+ * mirror (insertion-order FIFO). Absent / `0` = unbounded.
32
+ */
33
+ capacity?: number;
34
+ };
35
+ /**
36
+ * The concrete store type returned by {@link createCacheTraceStore} — the
37
+ * standard {@link TraceStoreContract} + {@link ExporterContract} surface,
38
+ * plus a `ready()` to hydrate the in-memory mirror from the cache on
39
+ * startup so traces survive a process restart.
40
+ */
41
+ type CacheTraceStoreHandle = TraceStoreContract & ExporterContract & {
42
+ /**
43
+ * Hydrate the in-memory mirror from the cache. Await once at startup
44
+ * (the dashboard wiring does this for you) so previously-persisted
45
+ * traces are queryable after a restart.
46
+ */
47
+ ready(): Promise<void>;
48
+ };
49
+ /**
50
+ * Create a cache-backed trace store. Reads are served synchronously from an
51
+ * in-memory mirror; writes go through to the cache, and {@link
52
+ * CacheTraceStoreHandle.ready} re-hydrates the mirror on startup so traces
53
+ * survive a restart. See {@link CacheTraceStore} for the full write-through
54
+ * design.
55
+ *
56
+ * @param cache a {@link CacheDriver}, or a (possibly async) factory that
57
+ * yields one on first use — resolved lazily and memoized so a production
58
+ * Redis connect can be deferred until the first trace is collected.
59
+ * @param options `prefix` (default `"panoptic"`), `capacity` (FIFO cap),
60
+ * and an optional `onError` hook for swallowed cache write failures.
61
+ *
62
+ * @example
63
+ * import { RedisCacheDriver } from "@warlock.js/cache";
64
+ *
65
+ * // Lazy async factory — defers the Redis connect until first use.
66
+ * const store = createCacheTraceStore(async () => {
67
+ * const driver = new RedisCacheDriver();
68
+ * await driver.connect();
69
+ * return driver;
70
+ * });
71
+ *
72
+ * await store.ready(); // hydrate from a prior run
73
+ * collector.use(store); // fills as traces complete
74
+ * const failed = store.query({ status: "failed" });
75
+ */
76
+ declare function createCacheTraceStore(cache: CacheDriverInput, options?: CacheTraceStoreOptions & {
77
+ onError?: (error: unknown) => void;
78
+ }): CacheTraceStoreHandle;
79
+ //#endregion
80
+ export { CacheDriverInput, CacheTraceStoreHandle, CacheTraceStoreOptions, createCacheTraceStore };
81
+ //# sourceMappingURL=cache-trace-store.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-trace-store.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/store/cache-trace-store.ts"],"mappings":";;;;;;;AAoBA;;;;;;;;;KAAY,gBAAA,GAIR,WAAA,oBAEO,WAAA,aAAwB,OAAA,CAAQ,WAAA;;;;KAK/B,sBAAA;EAL0C;AAKtD;;;;EAME,MAAA;EA0YU;;;;;EApYV,QAAQ;AAAA;;;;;;;KAoYE,qBAAA,GAAwB,kBAAA,GAClC,gBAAA;EAoCc;;;;;EA9BZ,KAAA,IAAS,OAAA;AAAA;;;;;;;;;;AAiCW;;;;;;;;;;;;;;;;;;iBAHR,qBAAA,CACd,KAAA,EAAO,gBAAA,EACP,OAAA,GAAS,sBAAA;EAA2B,OAAA,IAAW,KAAA;AAAA,IAC9C,qBAAA"}
@@ -0,0 +1,290 @@
1
+ import { matchTrace } from "./match-trace.mjs";
2
+ import { emptyUsage, sumUsage } from "./sum-usage.mjs";
3
+
4
+ //#region ../@warlock.js/ai-panoptic/src/store/cache-trace-store.ts
5
+ const DEFAULT_PREFIX = "panoptic";
6
+ /**
7
+ * Cache-backed {@link TraceStoreContract} with a **write-through** design
8
+ * that reconciles the synchronous store contract with an asynchronous
9
+ * cache driver.
10
+ *
11
+ * **How the sync/async tension is resolved.** The contract's `get` /
12
+ * `query` / `aggregate` / `size` are synchronous (the dashboard polls them
13
+ * on every request and the in-memory store answers instantly). A cache
14
+ * driver is async. So this store keeps an **in-memory read mirror** — the
15
+ * same insertion-ordered `Map<traceId, Trace>` the in-memory store uses —
16
+ * and serves every read from it synchronously. Writes go **through** to the
17
+ * cache: `add` updates the mirror immediately, then asynchronously persists
18
+ * the trace + index to the cache (errors are swallowed via an optional
19
+ * `onError` hook so a flaky cache never throws into the collector's hot
20
+ * path). On process restart, {@link CacheTraceStore.ready} re-hydrates the
21
+ * mirror from the cache so traces survive the restart.
22
+ *
23
+ * **Durability is best-effort.** Reads never wait on the cache; the mirror
24
+ * is the source of truth at runtime and the cache is the durable backing
25
+ * store. A write that the cache rejects is still visible in the mirror for
26
+ * the life of the process — it just won't survive a restart.
27
+ *
28
+ * **Lazy driver resolution.** The driver (or its async factory) is resolved
29
+ * on first use and memoized, so a production deployment can defer the Redis
30
+ * connect until the first trace is collected, and the dashboard can be
31
+ * wired with a factory at import time without a live connection.
32
+ *
33
+ * Doubles as an {@link ExporterContract} (`export` ≡ `add`), so it drops
34
+ * straight into a collector via `collector.use(store)`.
35
+ *
36
+ * Instantiated via {@link createCacheTraceStore}; callers never see `new`.
37
+ */
38
+ var CacheTraceStore = class {
39
+ /** Stable exporter id so a collector can dedupe / log this sink. */
40
+ name = "cache-trace-store";
41
+ /**
42
+ * In-memory read mirror keyed by `traceId`. A `Map` preserves insertion
43
+ * order, which newest-first `query` ordering and FIFO eviction both rely
44
+ * on. Every read is served from here synchronously.
45
+ */
46
+ mirror = /* @__PURE__ */ new Map();
47
+ prefix;
48
+ capacity;
49
+ /**
50
+ * Monotonic insertion counter — the source of `IndexEntry.addedAt`.
51
+ * Deliberately NOT `Date.now()`: an internal counter guarantees a stable
52
+ * total order for the index even when many traces land in the same
53
+ * millisecond.
54
+ */
55
+ addCounter = 0;
56
+ /** The optional input — a driver, a factory, or `undefined`. */
57
+ input;
58
+ resolvedDriver;
59
+ driverPending;
60
+ onError;
61
+ constructor(input, options = {}) {
62
+ this.input = input;
63
+ this.prefix = options.prefix ?? DEFAULT_PREFIX;
64
+ this.capacity = options.capacity ?? 0;
65
+ this.onError = options.onError;
66
+ }
67
+ get size() {
68
+ return this.mirror.size;
69
+ }
70
+ /**
71
+ * Hydrate the in-memory mirror from the cache. Idempotent-safe to call
72
+ * once at startup (the dashboard / config wiring awaits it). Reads the
73
+ * persisted index, fetches each referenced trace, and replays them into
74
+ * the mirror in insertion order so newest-first ordering + eviction stay
75
+ * correct after a restart. A cache failure is routed to `onError` and
76
+ * leaves the mirror empty rather than throwing.
77
+ */
78
+ async ready() {
79
+ try {
80
+ const driver = await this.resolveDriver();
81
+ const index = await driver.get(this.indexKey());
82
+ if (!Array.isArray(index)) return;
83
+ const ordered = [...index].sort((left, right) => left.addedAt - right.addedAt);
84
+ for (const entry of ordered) {
85
+ const trace = await driver.get(this.traceKey(entry.id));
86
+ if (trace !== null && trace !== void 0) {
87
+ this.mirror.delete(trace.traceId);
88
+ this.mirror.set(trace.traceId, trace);
89
+ }
90
+ if (entry.addedAt >= this.addCounter) this.addCounter = entry.addedAt + 1;
91
+ }
92
+ this.evictOverflow();
93
+ } catch (error) {
94
+ this.reportError(error);
95
+ }
96
+ }
97
+ add(trace) {
98
+ this.mirror.delete(trace.traceId);
99
+ this.mirror.set(trace.traceId, trace);
100
+ const addedAt = this.addCounter;
101
+ this.addCounter += 1;
102
+ const evicted = this.evictOverflow();
103
+ this.persist(trace, addedAt, evicted);
104
+ }
105
+ /**
106
+ * `ExporterContract.export` — a collector dispatches a completed trace
107
+ * here, which is exactly an `add`.
108
+ */
109
+ export(trace) {
110
+ this.add(trace);
111
+ }
112
+ get(traceId) {
113
+ return this.mirror.get(traceId);
114
+ }
115
+ query(filter) {
116
+ const matched = [];
117
+ for (const trace of this.mirror.values()) if (matchTrace(trace, filter)) matched.push(trace);
118
+ return this.sortNewestFirst(matched);
119
+ }
120
+ aggregate(filter) {
121
+ const aggregate = {
122
+ traces: 0,
123
+ completed: 0,
124
+ failed: 0,
125
+ cancelled: 0,
126
+ usage: emptyUsage(),
127
+ totalDuration: 0
128
+ };
129
+ for (const trace of this.mirror.values()) {
130
+ if (!matchTrace(trace, filter)) continue;
131
+ aggregate.traces += 1;
132
+ aggregate.totalDuration += trace.duration;
133
+ aggregate.usage = sumUsage(aggregate.usage, trace.usage);
134
+ this.countStatus(aggregate, trace);
135
+ }
136
+ if (aggregate.usage.cost !== void 0) aggregate.cost = aggregate.usage.cost;
137
+ return aggregate;
138
+ }
139
+ clear() {
140
+ const ids = [...this.mirror.keys()];
141
+ this.mirror.clear();
142
+ this.purge(ids);
143
+ }
144
+ /**
145
+ * Persist one trace + the rebuilt index to the cache, optionally removing
146
+ * a trace evicted by the capacity cap. Best-effort: any cache failure is
147
+ * routed to `onError`, never thrown — the mirror already reflects the
148
+ * write so runtime reads are unaffected.
149
+ */
150
+ async persist(trace, addedAt, evictedId) {
151
+ try {
152
+ const driver = await this.resolveDriver();
153
+ await driver.set(this.traceKey(trace.traceId), trace);
154
+ if (evictedId !== void 0) await driver.remove(this.traceKey(evictedId));
155
+ await driver.set(this.indexKey(), this.buildIndex(addedAt));
156
+ } catch (error) {
157
+ this.reportError(error);
158
+ }
159
+ }
160
+ /** Best-effort removal of every persisted trace + the index on `clear`. */
161
+ async purge(ids) {
162
+ try {
163
+ const driver = await this.resolveDriver();
164
+ for (const id of ids) await driver.remove(this.traceKey(id));
165
+ await driver.remove(this.indexKey());
166
+ } catch (error) {
167
+ this.reportError(error);
168
+ }
169
+ }
170
+ /**
171
+ * Rebuild the newest-first index from the current mirror. The mirror's
172
+ * `Map` iteration is oldest-first insertion order; we walk it and assign
173
+ * `addedAt` from the surviving counter span so the persisted order
174
+ * matches the in-memory one. The freshest entry uses `latestAddedAt`.
175
+ */
176
+ buildIndex(latestAddedAt) {
177
+ const ids = [...this.mirror.keys()];
178
+ const base = latestAddedAt - (ids.length - 1);
179
+ return ids.map((id, offset) => ({
180
+ id,
181
+ addedAt: base + offset
182
+ }));
183
+ }
184
+ /**
185
+ * Resolve the driver once and memoize. Supports a bare driver, a sync
186
+ * factory, and an async factory. Concurrent first-callers share one
187
+ * in-flight resolution.
188
+ */
189
+ async resolveDriver() {
190
+ if (this.resolvedDriver !== void 0) return this.resolvedDriver;
191
+ if (this.driverPending !== void 0) return this.driverPending;
192
+ const candidate = typeof this.input === "function" ? this.input() : this.input;
193
+ this.driverPending = Promise.resolve(candidate);
194
+ try {
195
+ this.resolvedDriver = await this.driverPending;
196
+ return this.resolvedDriver;
197
+ } finally {
198
+ this.driverPending = void 0;
199
+ }
200
+ }
201
+ /** `${prefix}:trace:${traceId}` — the per-trace cache key. */
202
+ traceKey(traceId) {
203
+ return `${this.prefix}:trace:${traceId}`;
204
+ }
205
+ /** `${prefix}:index` — the newest-first index cache key. */
206
+ indexKey() {
207
+ return `${this.prefix}:index`;
208
+ }
209
+ /** Route a swallowed cache error to the optional handler. */
210
+ reportError(error) {
211
+ if (this.onError !== void 0) this.onError(error);
212
+ }
213
+ /**
214
+ * Increment the matching terminal-status counter for one trace.
215
+ * Mirrors the in-memory store: non-terminal statuses are counted in
216
+ * `traces` but tracked by none of the three headline counters.
217
+ */
218
+ countStatus(aggregate, trace) {
219
+ switch (trace.root.status) {
220
+ case "completed":
221
+ aggregate.completed += 1;
222
+ break;
223
+ case "failed":
224
+ aggregate.failed += 1;
225
+ break;
226
+ case "cancelled":
227
+ aggregate.cancelled += 1;
228
+ break;
229
+ default: break;
230
+ }
231
+ }
232
+ /**
233
+ * Sort matched traces newest-started first. A copy is sorted so the
234
+ * mirror's insertion order (which eviction depends on) is never disturbed.
235
+ */
236
+ sortNewestFirst(traces) {
237
+ return traces.sort((left, right) => Date.parse(right.startedAt) - Date.parse(left.startedAt));
238
+ }
239
+ /**
240
+ * Evict oldest-inserted traces from the mirror until within `capacity`.
241
+ * Returns the id of the single evicted trace (the common case — one `add`
242
+ * pushes at most one over the cap) so the caller can remove it from the
243
+ * cache too. No-op + `undefined` when unbounded or within the cap.
244
+ */
245
+ evictOverflow() {
246
+ if (this.capacity <= 0) return;
247
+ let evicted;
248
+ while (this.mirror.size > this.capacity) {
249
+ const oldest = this.mirror.keys().next().value;
250
+ if (oldest === void 0) return evicted;
251
+ this.mirror.delete(oldest);
252
+ evicted = oldest;
253
+ }
254
+ return evicted;
255
+ }
256
+ };
257
+ /**
258
+ * Create a cache-backed trace store. Reads are served synchronously from an
259
+ * in-memory mirror; writes go through to the cache, and {@link
260
+ * CacheTraceStoreHandle.ready} re-hydrates the mirror on startup so traces
261
+ * survive a restart. See {@link CacheTraceStore} for the full write-through
262
+ * design.
263
+ *
264
+ * @param cache a {@link CacheDriver}, or a (possibly async) factory that
265
+ * yields one on first use — resolved lazily and memoized so a production
266
+ * Redis connect can be deferred until the first trace is collected.
267
+ * @param options `prefix` (default `"panoptic"`), `capacity` (FIFO cap),
268
+ * and an optional `onError` hook for swallowed cache write failures.
269
+ *
270
+ * @example
271
+ * import { RedisCacheDriver } from "@warlock.js/cache";
272
+ *
273
+ * // Lazy async factory — defers the Redis connect until first use.
274
+ * const store = createCacheTraceStore(async () => {
275
+ * const driver = new RedisCacheDriver();
276
+ * await driver.connect();
277
+ * return driver;
278
+ * });
279
+ *
280
+ * await store.ready(); // hydrate from a prior run
281
+ * collector.use(store); // fills as traces complete
282
+ * const failed = store.query({ status: "failed" });
283
+ */
284
+ function createCacheTraceStore(cache, options = {}) {
285
+ return new CacheTraceStore(cache, options);
286
+ }
287
+
288
+ //#endregion
289
+ export { createCacheTraceStore };
290
+ //# sourceMappingURL=cache-trace-store.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-trace-store.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/store/cache-trace-store.ts"],"sourcesContent":["import type { CacheDriver } from \"@warlock.js/cache\";\nimport type { ExporterContract } from \"../contracts/exporter.contract\";\nimport type { Trace } from \"../contracts/trace.type\";\nimport { matchTrace } from \"./match-trace\";\nimport { emptyUsage, sumUsage } from \"./sum-usage\";\nimport type { TraceAggregate } from \"./trace-aggregate.type\";\nimport type { TraceQuery } from \"./trace-query.type\";\nimport type { TraceStoreContract } from \"./trace-store.contract\";\n\n/**\n * A {@link CacheDriver} instance, or a (possibly async) factory that\n * yields one on first use. The factory form lets production defer an\n * expensive connect (e.g. the Redis handshake) until the first trace is\n * actually written, and keeps the dashboard wiring free of a live driver\n * at module-import time.\n *\n * Typed `CacheDriver<any, any>` because the store only ever touches the\n * driver's `get` / `set` / `remove` surface and is agnostic to the\n * concrete client + options of whichever driver backs it.\n */\nexport type CacheDriverInput =\n // The store is driver-agnostic; it only uses get/set/remove, so the\n // concrete client/options generics are intentionally unconstrained.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | CacheDriver<any, any>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | (() => CacheDriver<any, any> | Promise<CacheDriver<any, any>>);\n\n/**\n * Options for {@link createCacheTraceStore}.\n */\nexport type CacheTraceStoreOptions = {\n /**\n * Key prefix every cache entry this store writes is namespaced under.\n * Per-trace keys are `${prefix}:trace:${traceId}`; the newest-first\n * index lives at `${prefix}:index`. Default `\"panoptic\"`.\n */\n prefix?: string;\n /**\n * Maximum number of traces to retain. When set and exceeded, the\n * oldest-ingested trace is evicted from both the cache and the in-memory\n * mirror (insertion-order FIFO). Absent / `0` = unbounded.\n */\n capacity?: number;\n};\n\n/** One entry in the persisted newest-first index. */\ntype IndexEntry = {\n /** The trace's `traceId` — the suffix of its `${prefix}:trace:` key. */\n id: string;\n /**\n * Monotonic insertion order from an internal counter — NOT a wall clock.\n * Used only to keep the index in stable insertion order across a restart\n * so FIFO eviction stays honest.\n */\n addedAt: number;\n};\n\nconst DEFAULT_PREFIX = \"panoptic\";\n\n/**\n * Cache-backed {@link TraceStoreContract} with a **write-through** design\n * that reconciles the synchronous store contract with an asynchronous\n * cache driver.\n *\n * **How the sync/async tension is resolved.** The contract's `get` /\n * `query` / `aggregate` / `size` are synchronous (the dashboard polls them\n * on every request and the in-memory store answers instantly). A cache\n * driver is async. So this store keeps an **in-memory read mirror** — the\n * same insertion-ordered `Map<traceId, Trace>` the in-memory store uses —\n * and serves every read from it synchronously. Writes go **through** to the\n * cache: `add` updates the mirror immediately, then asynchronously persists\n * the trace + index to the cache (errors are swallowed via an optional\n * `onError` hook so a flaky cache never throws into the collector's hot\n * path). On process restart, {@link CacheTraceStore.ready} re-hydrates the\n * mirror from the cache so traces survive the restart.\n *\n * **Durability is best-effort.** Reads never wait on the cache; the mirror\n * is the source of truth at runtime and the cache is the durable backing\n * store. A write that the cache rejects is still visible in the mirror for\n * the life of the process — it just won't survive a restart.\n *\n * **Lazy driver resolution.** The driver (or its async factory) is resolved\n * on first use and memoized, so a production deployment can defer the Redis\n * connect until the first trace is collected, and the dashboard can be\n * wired with a factory at import time without a live connection.\n *\n * Doubles as an {@link ExporterContract} (`export` ≡ `add`), so it drops\n * straight into a collector via `collector.use(store)`.\n *\n * Instantiated via {@link createCacheTraceStore}; callers never see `new`.\n */\nclass CacheTraceStore implements TraceStoreContract, ExporterContract {\n /** Stable exporter id so a collector can dedupe / log this sink. */\n public readonly name = \"cache-trace-store\";\n\n /**\n * In-memory read mirror keyed by `traceId`. A `Map` preserves insertion\n * order, which newest-first `query` ordering and FIFO eviction both rely\n * on. Every read is served from here synchronously.\n */\n private readonly mirror = new Map<string, Trace>();\n\n private readonly prefix: string;\n\n private readonly capacity: number;\n\n /**\n * Monotonic insertion counter — the source of `IndexEntry.addedAt`.\n * Deliberately NOT `Date.now()`: an internal counter guarantees a stable\n * total order for the index even when many traces land in the same\n * millisecond.\n */\n private addCounter = 0;\n\n /** The optional input — a driver, a factory, or `undefined`. */\n private readonly input: CacheDriverInput;\n\n // The store is driver-agnostic; only get/set/remove are used.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private resolvedDriver?: CacheDriver<any, any>;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private driverPending?: Promise<CacheDriver<any, any>>;\n\n private readonly onError?: (error: unknown) => void;\n\n public constructor(\n input: CacheDriverInput,\n options: CacheTraceStoreOptions & { onError?: (error: unknown) => void } = {},\n ) {\n this.input = input;\n this.prefix = options.prefix ?? DEFAULT_PREFIX;\n this.capacity = options.capacity ?? 0;\n this.onError = options.onError;\n }\n\n public get size(): number {\n return this.mirror.size;\n }\n\n /**\n * Hydrate the in-memory mirror from the cache. Idempotent-safe to call\n * once at startup (the dashboard / config wiring awaits it). Reads the\n * persisted index, fetches each referenced trace, and replays them into\n * the mirror in insertion order so newest-first ordering + eviction stay\n * correct after a restart. A cache failure is routed to `onError` and\n * leaves the mirror empty rather than throwing.\n */\n public async ready(): Promise<void> {\n try {\n const driver = await this.resolveDriver();\n const index = await driver.get<IndexEntry[]>(this.indexKey());\n\n if (!Array.isArray(index)) {\n return;\n }\n\n // Oldest-first replay so the mirror's insertion order matches the\n // original ingestion order.\n const ordered = [...index].sort((left, right) => left.addedAt - right.addedAt);\n\n for (const entry of ordered) {\n const trace = await driver.get<Trace>(this.traceKey(entry.id));\n\n if (trace !== null && trace !== undefined) {\n this.mirror.delete(trace.traceId);\n this.mirror.set(trace.traceId, trace);\n }\n\n if (entry.addedAt >= this.addCounter) {\n this.addCounter = entry.addedAt + 1;\n }\n }\n\n this.evictOverflow();\n } catch (error) {\n this.reportError(error);\n }\n }\n\n public add(trace: Trace): void {\n // Mirror update is synchronous and authoritative for runtime reads.\n this.mirror.delete(trace.traceId);\n this.mirror.set(trace.traceId, trace);\n\n const addedAt = this.addCounter;\n this.addCounter += 1;\n\n const evicted = this.evictOverflow();\n\n // Write through to the cache fire-and-forget; reads never wait on this.\n void this.persist(trace, addedAt, evicted);\n }\n\n /**\n * `ExporterContract.export` — a collector dispatches a completed trace\n * here, which is exactly an `add`.\n */\n public export(trace: Trace): void {\n this.add(trace);\n }\n\n public get(traceId: string): Trace | undefined {\n return this.mirror.get(traceId);\n }\n\n public query(filter?: TraceQuery): Trace[] {\n const matched: Trace[] = [];\n\n for (const trace of this.mirror.values()) {\n if (matchTrace(trace, filter)) {\n matched.push(trace);\n }\n }\n\n return this.sortNewestFirst(matched);\n }\n\n public aggregate(filter?: TraceQuery): TraceAggregate {\n const aggregate: TraceAggregate = {\n traces: 0,\n completed: 0,\n failed: 0,\n cancelled: 0,\n usage: emptyUsage(),\n totalDuration: 0,\n };\n\n for (const trace of this.mirror.values()) {\n if (!matchTrace(trace, filter)) {\n continue;\n }\n\n aggregate.traces += 1;\n aggregate.totalDuration += trace.duration;\n aggregate.usage = sumUsage(aggregate.usage, trace.usage);\n\n this.countStatus(aggregate, trace);\n }\n\n if (aggregate.usage.cost !== undefined) {\n aggregate.cost = aggregate.usage.cost;\n }\n\n return aggregate;\n }\n\n public clear(): void {\n const ids = [...this.mirror.keys()];\n this.mirror.clear();\n\n void this.purge(ids);\n }\n\n /**\n * Persist one trace + the rebuilt index to the cache, optionally removing\n * a trace evicted by the capacity cap. Best-effort: any cache failure is\n * routed to `onError`, never thrown — the mirror already reflects the\n * write so runtime reads are unaffected.\n */\n private async persist(\n trace: Trace,\n addedAt: number,\n evictedId: string | undefined,\n ): Promise<void> {\n try {\n const driver = await this.resolveDriver();\n\n await driver.set(this.traceKey(trace.traceId), trace);\n\n if (evictedId !== undefined) {\n await driver.remove(this.traceKey(evictedId));\n }\n\n await driver.set(this.indexKey(), this.buildIndex(addedAt));\n } catch (error) {\n this.reportError(error);\n }\n }\n\n /** Best-effort removal of every persisted trace + the index on `clear`. */\n private async purge(ids: string[]): Promise<void> {\n try {\n const driver = await this.resolveDriver();\n\n for (const id of ids) {\n await driver.remove(this.traceKey(id));\n }\n\n await driver.remove(this.indexKey());\n } catch (error) {\n this.reportError(error);\n }\n }\n\n /**\n * Rebuild the newest-first index from the current mirror. The mirror's\n * `Map` iteration is oldest-first insertion order; we walk it and assign\n * `addedAt` from the surviving counter span so the persisted order\n * matches the in-memory one. The freshest entry uses `latestAddedAt`.\n */\n private buildIndex(latestAddedAt: number): IndexEntry[] {\n const ids = [...this.mirror.keys()];\n const base = latestAddedAt - (ids.length - 1);\n\n return ids.map((id, offset) => ({ id, addedAt: base + offset }));\n }\n\n /**\n * Resolve the driver once and memoize. Supports a bare driver, a sync\n * factory, and an async factory. Concurrent first-callers share one\n * in-flight resolution.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private async resolveDriver(): Promise<CacheDriver<any, any>> {\n if (this.resolvedDriver !== undefined) {\n return this.resolvedDriver;\n }\n\n if (this.driverPending !== undefined) {\n return this.driverPending;\n }\n\n const candidate =\n typeof this.input === \"function\"\n ? (this.input as () =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | CacheDriver<any, any>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n | Promise<CacheDriver<any, any>>)()\n : this.input;\n\n this.driverPending = Promise.resolve(candidate);\n\n try {\n this.resolvedDriver = await this.driverPending;\n\n return this.resolvedDriver;\n } finally {\n this.driverPending = undefined;\n }\n }\n\n /** `${prefix}:trace:${traceId}` — the per-trace cache key. */\n private traceKey(traceId: string): string {\n return `${this.prefix}:trace:${traceId}`;\n }\n\n /** `${prefix}:index` — the newest-first index cache key. */\n private indexKey(): string {\n return `${this.prefix}:index`;\n }\n\n /** Route a swallowed cache error to the optional handler. */\n private reportError(error: unknown): void {\n if (this.onError !== undefined) {\n this.onError(error);\n }\n }\n\n /**\n * Increment the matching terminal-status counter for one trace.\n * Mirrors the in-memory store: non-terminal statuses are counted in\n * `traces` but tracked by none of the three headline counters.\n */\n private countStatus(aggregate: TraceAggregate, trace: Trace): void {\n switch (trace.root.status) {\n case \"completed\": {\n aggregate.completed += 1;\n break;\n }\n\n case \"failed\": {\n aggregate.failed += 1;\n break;\n }\n\n case \"cancelled\": {\n aggregate.cancelled += 1;\n break;\n }\n\n default: {\n break;\n }\n }\n }\n\n /**\n * Sort matched traces newest-started first. A copy is sorted so the\n * mirror's insertion order (which eviction depends on) is never disturbed.\n */\n private sortNewestFirst(traces: Trace[]): Trace[] {\n return traces.sort((left, right) => Date.parse(right.startedAt) - Date.parse(left.startedAt));\n }\n\n /**\n * Evict oldest-inserted traces from the mirror until within `capacity`.\n * Returns the id of the single evicted trace (the common case — one `add`\n * pushes at most one over the cap) so the caller can remove it from the\n * cache too. No-op + `undefined` when unbounded or within the cap.\n */\n private evictOverflow(): string | undefined {\n if (this.capacity <= 0) {\n return undefined;\n }\n\n let evicted: string | undefined;\n\n while (this.mirror.size > this.capacity) {\n const oldest = this.mirror.keys().next().value;\n\n if (oldest === undefined) {\n return evicted;\n }\n\n this.mirror.delete(oldest);\n evicted = oldest;\n }\n\n return evicted;\n }\n}\n\n/**\n * The concrete store type returned by {@link createCacheTraceStore} — the\n * standard {@link TraceStoreContract} + {@link ExporterContract} surface,\n * plus a `ready()` to hydrate the in-memory mirror from the cache on\n * startup so traces survive a process restart.\n */\nexport type CacheTraceStoreHandle = TraceStoreContract &\n ExporterContract & {\n /**\n * Hydrate the in-memory mirror from the cache. Await once at startup\n * (the dashboard wiring does this for you) so previously-persisted\n * traces are queryable after a restart.\n */\n ready(): Promise<void>;\n };\n\n/**\n * Create a cache-backed trace store. Reads are served synchronously from an\n * in-memory mirror; writes go through to the cache, and {@link\n * CacheTraceStoreHandle.ready} re-hydrates the mirror on startup so traces\n * survive a restart. See {@link CacheTraceStore} for the full write-through\n * design.\n *\n * @param cache a {@link CacheDriver}, or a (possibly async) factory that\n * yields one on first use — resolved lazily and memoized so a production\n * Redis connect can be deferred until the first trace is collected.\n * @param options `prefix` (default `\"panoptic\"`), `capacity` (FIFO cap),\n * and an optional `onError` hook for swallowed cache write failures.\n *\n * @example\n * import { RedisCacheDriver } from \"@warlock.js/cache\";\n *\n * // Lazy async factory — defers the Redis connect until first use.\n * const store = createCacheTraceStore(async () => {\n * const driver = new RedisCacheDriver();\n * await driver.connect();\n * return driver;\n * });\n *\n * await store.ready(); // hydrate from a prior run\n * collector.use(store); // fills as traces complete\n * const failed = store.query({ status: \"failed\" });\n */\nexport function createCacheTraceStore(\n cache: CacheDriverInput,\n options: CacheTraceStoreOptions & { onError?: (error: unknown) => void } = {},\n): CacheTraceStoreHandle {\n return new CacheTraceStore(cache, options);\n}\n"],"mappings":"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"}
@@ -19,9 +19,16 @@ import { emptyUsage, sumUsage } from "./sum-usage.mjs";
19
19
  * callers never see `new`.
20
20
  */
21
21
  var InMemoryTraceStore = class {
22
+ /** Stable exporter id so a collector can dedupe / log this sink. */
23
+ name = "in-memory-trace-store";
24
+ /**
25
+ * Retained traces keyed by `traceId`. A `Map` preserves insertion
26
+ * order, which is what FIFO eviction and newest-first `query` ordering
27
+ * both rely on.
28
+ */
29
+ traces = /* @__PURE__ */ new Map();
30
+ capacity;
22
31
  constructor(options) {
23
- this.name = "in-memory-trace-store";
24
- this.traces = /* @__PURE__ */ new Map();
25
32
  this.capacity = options?.capacity ?? 0;
26
33
  }
27
34
  get size() {