@warlock.js/ai-panoptic 4.8.1 → 4.9.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 (162) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/cjs/index.cjs +340 -42
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/collector/collector.d.mts +1 -1
  5. package/esm/collector/collector.d.mts.map +1 -1
  6. package/esm/collector/collector.mjs +1 -1
  7. package/esm/collector/collector.mjs.map +1 -1
  8. package/esm/collector/content-capture.type.d.mts +1 -1
  9. package/esm/collector/content-capture.type.d.mts.map +1 -1
  10. package/esm/collector/extract-span-attributes.d.mts +1 -1
  11. package/esm/collector/extract-span-attributes.d.mts.map +1 -1
  12. package/esm/collector/extract-span-attributes.mjs +1 -1
  13. package/esm/collector/extract-span-attributes.mjs.map +1 -1
  14. package/esm/collector/normalize-error.d.mts +1 -1
  15. package/esm/collector/normalize-error.d.mts.map +1 -1
  16. package/esm/collector/normalize-error.mjs +1 -1
  17. package/esm/collector/normalize-error.mjs.map +1 -1
  18. package/esm/collector/report-to-span.d.mts +1 -1
  19. package/esm/collector/report-to-span.d.mts.map +1 -1
  20. package/esm/collector/report-to-span.mjs +1 -1
  21. package/esm/collector/report-to-span.mjs.map +1 -1
  22. package/esm/collector/report-to-trace.d.mts +1 -1
  23. package/esm/collector/report-to-trace.d.mts.map +1 -1
  24. package/esm/collector/report-to-trace.mjs +1 -1
  25. package/esm/collector/report-to-trace.mjs.map +1 -1
  26. package/esm/config/apply-panoptic-config.d.mts +1 -1
  27. package/esm/config/apply-panoptic-config.d.mts.map +1 -1
  28. package/esm/config/apply-panoptic-config.mjs +4 -2
  29. package/esm/config/apply-panoptic-config.mjs.map +1 -1
  30. package/esm/config/panoptic-config.type.d.mts +12 -1
  31. package/esm/config/panoptic-config.type.d.mts.map +1 -1
  32. package/esm/contracts/collector.contract.d.mts +1 -1
  33. package/esm/contracts/collector.contract.d.mts.map +1 -1
  34. package/esm/contracts/exporter.contract.d.mts +1 -1
  35. package/esm/contracts/exporter.contract.d.mts.map +1 -1
  36. package/esm/contracts/trace.type.d.mts +1 -1
  37. package/esm/contracts/trace.type.d.mts.map +1 -1
  38. package/esm/dashboard/dashboard.d.mts +1 -1
  39. package/esm/dashboard/dashboard.d.mts.map +1 -1
  40. package/esm/dashboard/dashboard.mjs +3 -2
  41. package/esm/dashboard/dashboard.mjs.map +1 -1
  42. package/esm/dashboard/dashboard.type.d.mts +16 -3
  43. package/esm/dashboard/dashboard.type.d.mts.map +1 -1
  44. package/esm/dashboard/index.mjs +7 -0
  45. package/esm/dashboard/parse-query.mjs +1 -1
  46. package/esm/dashboard/parse-query.mjs.map +1 -1
  47. package/esm/dashboard/serve.mjs +124 -9
  48. package/esm/dashboard/serve.mjs.map +1 -1
  49. package/esm/dashboard/trace-filter.d.mts +1 -1
  50. package/esm/dashboard/trace-filter.d.mts.map +1 -1
  51. package/esm/dashboard/trace-filter.mjs +1 -1
  52. package/esm/dashboard/trace-filter.mjs.map +1 -1
  53. package/esm/dashboard/ui.html.mjs +134 -5
  54. package/esm/dashboard/ui.html.mjs.map +1 -1
  55. package/esm/dashboard/warlock-logo.mjs +1 -1
  56. package/esm/dashboard/warlock-logo.mjs.map +1 -1
  57. package/esm/evaluate/evaluate-system-prompt.d.mts +19 -0
  58. package/esm/evaluate/evaluate-system-prompt.d.mts.map +1 -0
  59. package/esm/evaluate/evaluate-system-prompt.mjs +22 -0
  60. package/esm/evaluate/evaluate-system-prompt.mjs.map +1 -0
  61. package/esm/evaluate/evaluate.type.d.mts +47 -0
  62. package/esm/evaluate/evaluate.type.d.mts.map +1 -0
  63. package/esm/evaluate/extract-last-system-prompt.d.mts +19 -0
  64. package/esm/evaluate/extract-last-system-prompt.d.mts.map +1 -0
  65. package/esm/evaluate/extract-last-system-prompt.mjs +24 -0
  66. package/esm/evaluate/extract-last-system-prompt.mjs.map +1 -0
  67. package/esm/evaluate/find-span-by-id.d.mts +8 -0
  68. package/esm/evaluate/find-span-by-id.d.mts.map +1 -0
  69. package/esm/evaluate/find-span-by-id.mjs +13 -0
  70. package/esm/evaluate/find-span-by-id.mjs.map +1 -0
  71. package/esm/evaluate/index.d.mts +4 -0
  72. package/esm/evaluate/index.mjs +5 -0
  73. package/esm/exporters/console/console-exporter.d.mts +1 -1
  74. package/esm/exporters/console/console-exporter.d.mts.map +1 -1
  75. package/esm/exporters/console/console-exporter.mjs +1 -1
  76. package/esm/exporters/console/console-exporter.mjs.map +1 -1
  77. package/esm/exporters/console/console-exporter.type.d.mts +1 -1
  78. package/esm/exporters/console/console-exporter.type.d.mts.map +1 -1
  79. package/esm/exporters/console/format-span-io.d.mts +1 -1
  80. package/esm/exporters/console/format-span-io.d.mts.map +1 -1
  81. package/esm/exporters/console/format-span-io.mjs +1 -1
  82. package/esm/exporters/console/format-span-io.mjs.map +1 -1
  83. package/esm/exporters/console/format-span-line.d.mts +1 -1
  84. package/esm/exporters/console/format-span-line.d.mts.map +1 -1
  85. package/esm/exporters/console/format-span-line.mjs +1 -1
  86. package/esm/exporters/console/format-span-line.mjs.map +1 -1
  87. package/esm/exporters/file/file-exporter.d.mts +1 -1
  88. package/esm/exporters/file/file-exporter.d.mts.map +1 -1
  89. package/esm/exporters/file/file-exporter.mjs +1 -1
  90. package/esm/exporters/file/file-exporter.mjs.map +1 -1
  91. package/esm/exporters/file/file-exporter.type.d.mts +1 -1
  92. package/esm/exporters/file/file-exporter.type.d.mts.map +1 -1
  93. package/esm/exporters/langfuse/langfuse-exporter.d.mts +1 -1
  94. package/esm/exporters/langfuse/langfuse-exporter.d.mts.map +1 -1
  95. package/esm/exporters/langfuse/langfuse-exporter.mjs +1 -1
  96. package/esm/exporters/langfuse/langfuse-exporter.mjs.map +1 -1
  97. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts +1 -1
  98. package/esm/exporters/langfuse/langfuse-exporter.type.d.mts.map +1 -1
  99. package/esm/exporters/otel/otel-api.shim.type.d.mts +1 -1
  100. package/esm/exporters/otel/otel-api.shim.type.d.mts.map +1 -1
  101. package/esm/exporters/otel/otel-exporter.d.mts +1 -1
  102. package/esm/exporters/otel/otel-exporter.d.mts.map +1 -1
  103. package/esm/exporters/otel/otel-exporter.mjs +1 -1
  104. package/esm/exporters/otel/otel-exporter.mjs.map +1 -1
  105. package/esm/exporters/otel/otel-exporter.type.d.mts +1 -1
  106. package/esm/exporters/otel/otel-exporter.type.d.mts.map +1 -1
  107. package/esm/exporters/utils/gen-ai-attributes.d.mts +1 -1
  108. package/esm/exporters/utils/gen-ai-attributes.d.mts.map +1 -1
  109. package/esm/exporters/utils/gen-ai-attributes.mjs +1 -1
  110. package/esm/exporters/utils/gen-ai-attributes.mjs.map +1 -1
  111. package/esm/exporters/utils/total-cost.d.mts +1 -1
  112. package/esm/exporters/utils/total-cost.d.mts.map +1 -1
  113. package/esm/exporters/utils/total-cost.mjs +1 -1
  114. package/esm/exporters/utils/total-cost.mjs.map +1 -1
  115. package/esm/exporters/utils/walk-spans.d.mts +1 -1
  116. package/esm/exporters/utils/walk-spans.d.mts.map +1 -1
  117. package/esm/exporters/utils/walk-spans.mjs +1 -1
  118. package/esm/exporters/utils/walk-spans.mjs.map +1 -1
  119. package/esm/index.d.mts +5 -1
  120. package/esm/index.mjs +6 -1
  121. package/esm/panoptic/panoptic-middleware.d.mts +1 -1
  122. package/esm/panoptic/panoptic-middleware.d.mts.map +1 -1
  123. package/esm/panoptic/panoptic-middleware.mjs +1 -1
  124. package/esm/panoptic/panoptic-middleware.mjs.map +1 -1
  125. package/esm/panoptic/panoptic-target.type.d.mts +1 -1
  126. package/esm/panoptic/panoptic-target.type.d.mts.map +1 -1
  127. package/esm/panoptic/panoptic.d.mts +1 -1
  128. package/esm/panoptic/panoptic.d.mts.map +1 -1
  129. package/esm/panoptic/panoptic.mjs +1 -1
  130. package/esm/panoptic/panoptic.mjs.map +1 -1
  131. package/esm/panoptic/panoptic.type.d.mts +1 -1
  132. package/esm/panoptic/panoptic.type.d.mts.map +1 -1
  133. package/esm/register.mjs +1 -1
  134. package/esm/register.mjs.map +1 -1
  135. package/esm/store/cache-trace-store.d.mts +1 -1
  136. package/esm/store/cache-trace-store.d.mts.map +1 -1
  137. package/esm/store/cache-trace-store.mjs +1 -1
  138. package/esm/store/cache-trace-store.mjs.map +1 -1
  139. package/esm/store/in-memory-trace-store.d.mts +1 -1
  140. package/esm/store/in-memory-trace-store.d.mts.map +1 -1
  141. package/esm/store/in-memory-trace-store.mjs +1 -1
  142. package/esm/store/in-memory-trace-store.mjs.map +1 -1
  143. package/esm/store/match-trace.d.mts +1 -1
  144. package/esm/store/match-trace.d.mts.map +1 -1
  145. package/esm/store/match-trace.mjs +1 -1
  146. package/esm/store/match-trace.mjs.map +1 -1
  147. package/esm/store/sum-usage.d.mts +1 -1
  148. package/esm/store/sum-usage.d.mts.map +1 -1
  149. package/esm/store/sum-usage.mjs +1 -1
  150. package/esm/store/sum-usage.mjs.map +1 -1
  151. package/esm/store/trace-aggregate.type.d.mts +1 -1
  152. package/esm/store/trace-aggregate.type.d.mts.map +1 -1
  153. package/esm/store/trace-query.type.d.mts +1 -1
  154. package/esm/store/trace-query.type.d.mts.map +1 -1
  155. package/esm/store/trace-store.contract.d.mts +1 -1
  156. package/esm/store/trace-store.contract.d.mts.map +1 -1
  157. package/llms-full.txt +95 -3
  158. package/llms.txt +1 -0
  159. package/package.json +4 -3
  160. package/skills/README.md +4 -0
  161. package/skills/evaluate-system-prompt/SKILL.md +86 -0
  162. package/skills/use-local-dashboard/SKILL.md +5 -3
package/cjs/index.cjs CHANGED
@@ -3,8 +3,9 @@ let _warlock_js_ai = require("@warlock.js/ai");
3
3
  let node_fs_promises = require("node:fs/promises");
4
4
  let node_path = require("node:path");
5
5
  let node_http = require("node:http");
6
+ let _warlock_js_logger = require("@warlock.js/logger");
6
7
 
7
- //#region ../@warlock.js/ai-panoptic/src/exporters/utils/walk-spans.ts
8
+ //#region ../ai-panoptic/src/exporters/utils/walk-spans.ts
8
9
  /**
9
10
  * Depth-first pre-order traversal of a {@link TraceSpan} tree, yielding
10
11
  * the root first and then each descendant in `children` (invocation)
@@ -23,7 +24,7 @@ function* walkSpans(root) {
23
24
  }
24
25
 
25
26
  //#endregion
26
- //#region ../@warlock.js/ai-panoptic/src/collector/normalize-error.ts
27
+ //#region ../ai-panoptic/src/collector/normalize-error.ts
27
28
  /**
28
29
  * Project a captured execution error onto the structural
29
30
  * {@link TraceSpanError} shape used on a failed / cancelled span.
@@ -71,7 +72,7 @@ function pickString(value) {
71
72
  }
72
73
 
73
74
  //#endregion
74
- //#region ../@warlock.js/ai-panoptic/src/collector/extract-span-attributes.ts
75
+ //#region ../ai-panoptic/src/collector/extract-span-attributes.ts
75
76
  /**
76
77
  * Build the free-form `TraceSpan.attributes` bag for one report node.
77
78
  *
@@ -148,7 +149,7 @@ function addToolAttributes(attributes, extensions) {
148
149
  }
149
150
 
150
151
  //#endregion
151
- //#region ../@warlock.js/ai-panoptic/src/collector/report-to-span.ts
152
+ //#region ../ai-panoptic/src/collector/report-to-span.ts
152
153
  /**
153
154
  * Project one {@link BaseReport} node — and its entire subtree — into a
154
155
  * {@link TraceSpan}. Pure and recursive: identity, timing, status, and
@@ -281,7 +282,7 @@ function lastNonEmptyOutput(trips) {
281
282
  }
282
283
 
283
284
  //#endregion
284
- //#region ../@warlock.js/ai-panoptic/src/collector/report-to-trace.ts
285
+ //#region ../ai-panoptic/src/collector/report-to-trace.ts
285
286
  /**
286
287
  * Project an outermost {@link BaseReport} (one whole `.execute()` /
287
288
  * `.invoke()` run) into a {@link Trace} — the root {@link
@@ -332,7 +333,7 @@ function reportToTrace(report, rootError, options) {
332
333
  }
333
334
 
334
335
  //#endregion
335
- //#region ../@warlock.js/ai-panoptic/src/collector/collector.ts
336
+ //#region ../ai-panoptic/src/collector/collector.ts
336
337
  /**
337
338
  * Drive the source end of the Panoptic pipeline: ingest core
338
339
  * `@warlock.js/ai` {@link BaseReport} trees, project them into {@link
@@ -456,7 +457,7 @@ function createCollector(options = {}) {
456
457
  }
457
458
 
458
459
  //#endregion
459
- //#region ../@warlock.js/ai-panoptic/src/store/match-trace.ts
460
+ //#region ../ai-panoptic/src/store/match-trace.ts
460
461
  /**
461
462
  * Test whether one trace satisfies a {@link TraceQuery}. Every declared
462
463
  * filter field must match (logical AND); absent fields are ignored, so
@@ -498,7 +499,7 @@ function toEpoch(bound) {
498
499
  }
499
500
 
500
501
  //#endregion
501
- //#region ../@warlock.js/ai-panoptic/src/store/sum-usage.ts
502
+ //#region ../ai-panoptic/src/store/sum-usage.ts
502
503
  /**
503
504
  * Fold a child {@link Usage} into a running accumulator. Token channels
504
505
  * (`input` / `output` / `total`) always sum; the optional cache /
@@ -559,7 +560,7 @@ function sumOptional(accumulator, next) {
559
560
  }
560
561
 
561
562
  //#endregion
562
- //#region ../@warlock.js/ai-panoptic/src/store/cache-trace-store.ts
563
+ //#region ../ai-panoptic/src/store/cache-trace-store.ts
563
564
  const DEFAULT_PREFIX = "panoptic";
564
565
  /**
565
566
  * Cache-backed {@link TraceStoreContract} with a **write-through** design
@@ -844,7 +845,7 @@ function createCacheTraceStore(cache, options = {}) {
844
845
  }
845
846
 
846
847
  //#endregion
847
- //#region ../@warlock.js/ai-panoptic/src/store/in-memory-trace-store.ts
848
+ //#region ../ai-panoptic/src/store/in-memory-trace-store.ts
848
849
  /**
849
850
  * In-memory {@link TraceStoreContract} that doubles as an
850
851
  * {@link ExporterContract} — register it on a collector
@@ -980,7 +981,7 @@ function createInMemoryTraceStore(options) {
980
981
  }
981
982
 
982
983
  //#endregion
983
- //#region ../@warlock.js/ai-panoptic/src/exporters/utils/total-cost.ts
984
+ //#region ../ai-panoptic/src/exporters/utils/total-cost.ts
984
985
  /**
985
986
  * Collapse a {@link Usage.cost} breakdown into a single USD scalar by
986
987
  * summing every populated field. Mirrors the formula documented on
@@ -1001,7 +1002,7 @@ function totalCostUsd(usage) {
1001
1002
  }
1002
1003
 
1003
1004
  //#endregion
1004
- //#region ../@warlock.js/ai-panoptic/src/exporters/utils/gen-ai-attributes.ts
1005
+ //#region ../ai-panoptic/src/exporters/utils/gen-ai-attributes.ts
1005
1006
  /**
1006
1007
  * Subset of the OpenTelemetry GenAI semantic-convention attribute keys
1007
1008
  * Panoptic emits. Kept as a named constant map (not inline string
@@ -1089,7 +1090,7 @@ function mergeScalarAttributes(target, source) {
1089
1090
  }
1090
1091
 
1091
1092
  //#endregion
1092
- //#region ../@warlock.js/ai-panoptic/src/exporters/console/format-span-io.ts
1093
+ //#region ../ai-panoptic/src/exporters/console/format-span-io.ts
1093
1094
  /** Default cap on how many characters of captured content a console line shows. */
1094
1095
  const DEFAULT_IO_MAX_CHARS = 500;
1095
1096
  /**
@@ -1139,7 +1140,7 @@ function stringify(value) {
1139
1140
  }
1140
1141
 
1141
1142
  //#endregion
1142
- //#region ../@warlock.js/ai-panoptic/src/exporters/console/format-span-line.ts
1143
+ //#region ../ai-panoptic/src/exporters/console/format-span-line.ts
1143
1144
  /**
1144
1145
  * Render a single {@link TraceSpan} as one scannable console line:
1145
1146
  * `<status> <type> "<name>" — <duration>ms, <tokens> tok[, $<cost>]`.
@@ -1176,7 +1177,7 @@ function statusMarker(status) {
1176
1177
  }
1177
1178
 
1178
1179
  //#endregion
1179
- //#region ../@warlock.js/ai-panoptic/src/exporters/console/console-exporter.ts
1180
+ //#region ../ai-panoptic/src/exporters/console/console-exporter.ts
1180
1181
  const EXPORTER_NAME$3 = "console";
1181
1182
  /**
1182
1183
  * Zero-dependency {@link ExporterContract} that prints traces to a
@@ -1267,7 +1268,7 @@ function spanDepth(root, targetSpanId, depth = 0) {
1267
1268
  }
1268
1269
 
1269
1270
  //#endregion
1270
- //#region ../@warlock.js/ai-panoptic/src/exporters/file/file-exporter.ts
1271
+ //#region ../ai-panoptic/src/exporters/file/file-exporter.ts
1271
1272
  const EXPORTER_NAME$2 = "file";
1272
1273
  /**
1273
1274
  * Zero-dependency {@link ExporterContract} that appends completed traces
@@ -1358,7 +1359,7 @@ var FileTraceWriter = class {
1358
1359
  };
1359
1360
 
1360
1361
  //#endregion
1361
- //#region ../@warlock.js/ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts
1362
+ //#region ../ai-panoptic/src/exporters/langfuse/langfuse-exporter.ts
1362
1363
  const EXPORTER_NAME$1 = "langfuse";
1363
1364
  let LangfuseSdk;
1364
1365
  let isModuleExists$1 = null;
@@ -1562,7 +1563,7 @@ function toLevel(span) {
1562
1563
  }
1563
1564
 
1564
1565
  //#endregion
1565
- //#region ../@warlock.js/ai-panoptic/src/exporters/otel/otel-exporter.ts
1566
+ //#region ../ai-panoptic/src/exporters/otel/otel-exporter.ts
1566
1567
  const EXPORTER_NAME = "otel";
1567
1568
  const DEFAULT_TRACER_NAME = "@warlock.js/ai-panoptic";
1568
1569
  let OtelApi;
@@ -1715,7 +1716,7 @@ function toEpochMillis(isoTimestamp) {
1715
1716
  }
1716
1717
 
1717
1718
  //#endregion
1718
- //#region ../@warlock.js/ai-panoptic/src/panoptic/panoptic-middleware.ts
1719
+ //#region ../ai-panoptic/src/panoptic/panoptic-middleware.ts
1719
1720
  /**
1720
1721
  * Build an {@link AgentMiddleware} that feeds a collector from the
1721
1722
  * `execute`- and `supervisor`-level hooks. An alternative wiring to event
@@ -1781,7 +1782,7 @@ function isReport(value) {
1781
1782
  }
1782
1783
 
1783
1784
  //#endregion
1784
- //#region ../@warlock.js/ai-panoptic/src/panoptic/panoptic.ts
1785
+ //#region ../ai-panoptic/src/panoptic/panoptic.ts
1785
1786
  /**
1786
1787
  * Terminal `*.completed` events of every core primitive that carries the
1787
1788
  * finalized `result` (and therefore the `report` tree). These fire once
@@ -1918,7 +1919,58 @@ function panoptic(options = {}) {
1918
1919
  }
1919
1920
 
1920
1921
  //#endregion
1921
- //#region ../@warlock.js/ai-panoptic/src/dashboard/parse-query.ts
1922
+ //#region ../ai-panoptic/src/evaluate/evaluate-system-prompt.ts
1923
+ /**
1924
+ * Grade `systemPrompt` with the configured judge model, reusing
1925
+ * `ai.prompts().validate()`'s own `judgePromptBody` — never a second
1926
+ * judging implementation. `instructionsOverride` (the dashboard's
1927
+ * per-run textarea) wins over `config.instructions`; with neither, the
1928
+ * judge falls back to `judgePromptBody`'s built-in prompt-quality rubric.
1929
+ *
1930
+ * The judge itself never throws (`judgePromptBody` degrades to an
1931
+ * issues-only outcome on failure) — the only thing that CAN throw here is
1932
+ * resolving `config.model` (a factory constructing an SDK client), which
1933
+ * the caller (the dashboard route) is expected to catch.
1934
+ */
1935
+ async function evaluateSystemPrompt(systemPrompt, config, instructionsOverride) {
1936
+ return (0, _warlock_js_ai.judgePromptBody)(systemPrompt, typeof config.model === "function" ? await config.model() : config.model, instructionsOverride?.trim() || config.instructions);
1937
+ }
1938
+
1939
+ //#endregion
1940
+ //#region ../ai-panoptic/src/evaluate/extract-last-system-prompt.ts
1941
+ /**
1942
+ * Pull the LAST `{role: "system"}` message off a span's captured `input`.
1943
+ *
1944
+ * Only present under `captureContent` (off by default), and only shaped
1945
+ * this way for non-tool spans — either the `[system, user]` first-trip pair
1946
+ * or, under `fullHistory`, the full `CapturedMessage[]` conversation (see
1947
+ * `collector/report-to-span.ts`). "Last" (not "only") matters for
1948
+ * `fullHistory`: a long-running agent can carry more than one system-role
1949
+ * turn, and the most recent one is the one actually in effect. Returns
1950
+ * `undefined` for a tool span, an agent with no system prompt, or when
1951
+ * content capture is off — the caller treats that as "nothing to evaluate."
1952
+ */
1953
+ function extractLastSystemPrompt(span) {
1954
+ if (!Array.isArray(span.input)) return;
1955
+ for (let index = span.input.length - 1; index >= 0; index -= 1) {
1956
+ const entry = span.input[index];
1957
+ if (entry && typeof entry === "object" && entry.role === "system") return typeof entry.content === "string" ? entry.content : void 0;
1958
+ }
1959
+ }
1960
+
1961
+ //#endregion
1962
+ //#region ../ai-panoptic/src/evaluate/find-span-by-id.ts
1963
+ /** Depth-first search for the span with `spanId` inside a trace's span tree. */
1964
+ function findSpanById(root, spanId) {
1965
+ if (root.spanId === spanId) return root;
1966
+ for (const child of root.children) {
1967
+ const found = findSpanById(child, spanId);
1968
+ if (found !== void 0) return found;
1969
+ }
1970
+ }
1971
+
1972
+ //#endregion
1973
+ //#region ../ai-panoptic/src/dashboard/parse-query.ts
1922
1974
  /**
1923
1975
  * The set of terminal statuses a stored trace can carry. Used to keep
1924
1976
  * `parseQuery` from forwarding arbitrary `?status=` junk into the store.
@@ -1970,7 +2022,7 @@ function parseQuery(params) {
1970
2022
  }
1971
2023
 
1972
2024
  //#endregion
1973
- //#region ../@warlock.js/ai-panoptic/src/dashboard/warlock-logo.ts
2025
+ //#region ../ai-panoptic/src/dashboard/warlock-logo.ts
1974
2026
  /**
1975
2027
  * The Warlock logo as an inlined base64 PNG data URI (64x64, ~9KB).
1976
2028
  * Inlined so the dashboard stays a single self-contained, offline-capable
@@ -1981,7 +2033,7 @@ function parseQuery(params) {
1981
2033
  const WARLOCK_LOGO_DATA_URI = "data:image/png;base64,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";
1982
2034
 
1983
2035
  //#endregion
1984
- //#region ../@warlock.js/ai-panoptic/src/dashboard/ui.html.ts
2036
+ //#region ../ai-panoptic/src/dashboard/ui.html.ts
1985
2037
  /**
1986
2038
  * Build the single self-contained dashboard HTML page. No external
1987
2039
  * assets, no bundler, no framework — one inlined string that polls the
@@ -2021,7 +2073,7 @@ const WARLOCK_LOGO_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEA
2021
2073
  * @param basePath Normalized mount path ending in `/` (e.g. `"/"`).
2022
2074
  * @param title Header title shown in the page.
2023
2075
  */
2024
- function dashboardHtml(basePath, title) {
2076
+ function dashboardHtml(basePath, title, evaluateEnabled = false, evaluateDefaultInstructions = "") {
2025
2077
  const apiBase = `${basePath}api`;
2026
2078
  const safeTitle = escapeHtml(title);
2027
2079
  return `<!doctype html>
@@ -2202,6 +2254,18 @@ function dashboardHtml(basePath, title) {
2202
2254
  .legend { margin-left: auto; display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--dim); }
2203
2255
  .legend .grad { width: 64px; height: 8px; border-radius: 4px; background: linear-gradient(90deg, var(--surface2), var(--cost)); border: 1px solid var(--border); }
2204
2256
  .gantt { padding: 8px 10px; }
2257
+
2258
+ /* Evaluate — the drawer's one write action (config-gated). */
2259
+ .eval-btn { background: transparent; border: 1px solid var(--border); color: var(--text2); cursor: pointer; font-size: 12px; padding: 4px 10px; border-radius: 6px; margin: 10px 0 0; }
2260
+ .eval-btn:hover:not(:disabled) { color: var(--text); border-color: var(--border2); }
2261
+ .eval-btn:disabled { opacity: .6; cursor: default; }
2262
+ .eval-panel { margin-top: 8px; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface2); }
2263
+ .eval-panel textarea { width: 100%; min-height: 64px; background: var(--panel); color: var(--text); border: 1px solid var(--border); border-radius: 6px; padding: 8px; font: 12px/1.5 ui-sans-serif, system-ui, sans-serif; resize: vertical; }
2264
+ .eval-actions { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
2265
+ .eval-error { color: var(--fail); font-size: 12px; }
2266
+ .eval-result { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); font-size: 12px; }
2267
+ .eval-score { font-weight: 600; }
2268
+ .eval-issues { margin: 6px 0 0; padding-left: 18px; }
2205
2269
  .grow { display: flex; align-items: center; gap: 8px; padding: 2px 0; font-size: 12px; cursor: pointer; border-radius: 4px; }
2206
2270
  .grow:hover { background: var(--surface); }
2207
2271
  .grow.selected { background: var(--sel-bg); }
@@ -2266,7 +2330,21 @@ function dashboardHtml(basePath, title) {
2266
2330
  <script>
2267
2331
  (function () {
2268
2332
  var API = ${JSON.stringify(apiBase)};
2333
+ var EVALUATE_ENABLED = ${JSON.stringify(evaluateEnabled)};
2269
2334
  var BT = String.fromCharCode(96);
2335
+ var EVALUATE_DEFAULT_INSTRUCTIONS = ${encodeForInlineScript(evaluateDefaultInstructions)};
2336
+
2337
+ // Carry the ?token= the page itself was loaded with onto every
2338
+ // subsequent poll — otherwise the API calls below inherit no auth and
2339
+ // 401 forever once authToken is configured (the initial page load is
2340
+ // the only request the URL's query string naturally reaches).
2341
+ var TOKEN = new URLSearchParams(window.location.search).get("token");
2342
+ function fetchAuthed(url, options) {
2343
+ var opts = options || {};
2344
+ var headers = opts.headers || {};
2345
+ if (TOKEN) headers = Object.assign({}, headers, { Authorization: "Bearer " + TOKEN });
2346
+ return fetch(url, Object.assign({}, opts, { headers: headers }));
2347
+ }
2270
2348
 
2271
2349
  var state = {
2272
2350
  traces: [], selectedId: null, selectedSpanId: null, collapsed: {}, sig: null,
@@ -2278,7 +2356,8 @@ function dashboardHtml(basePath, title) {
2278
2356
  showStats: false, // per-type aggregate-stats panel toggle (independent of grouping)
2279
2357
  collapsedGroups: {}, // collapsed group headers (session, prompt, or type)
2280
2358
  view: "tree", // drawer left pane: "tree" | "timeline"
2281
- hashApplied: false // guards one-time deep-link open on load
2359
+ hashApplied: false, // guards one-time deep-link open on load
2360
+ evaluate: {} // per-span evaluate UI state, keyed by spanId
2282
2361
  };
2283
2362
 
2284
2363
  var STATUS_FILTERS = ["completed", "failed", "cancelled"];
@@ -2987,6 +3066,80 @@ function dashboardHtml(basePath, title) {
2987
3066
  return '<div class="meta-sec"><div class="meta-title">metadata</div><div class="kv">' + kv + "</div></div>";
2988
3067
  }
2989
3068
 
3069
+ // --- Evaluate: grade a span's last captured system prompt -------------
3070
+ // Config-gated (EVALUATE_ENABLED) — the drawer's only write action, POSTing
3071
+ // to a route that itself only exists when the server was configured with
3072
+ // evaluate. UI state lives in state.evaluate, keyed by spanId, so it
3073
+ // survives a re-render (e.g. switching to a sibling span and back).
3074
+ function extractLastSystemPrompt(span) {
3075
+ if (!Array.isArray(span.input)) return null;
3076
+ for (var i = span.input.length - 1; i >= 0; i--) {
3077
+ var m = span.input[i];
3078
+ if (m && typeof m === "object" && m.role === "system" && typeof m.content === "string") return m.content;
3079
+ }
3080
+ return null;
3081
+ }
3082
+ function evalState(spanId) {
3083
+ return state.evaluate[spanId] || (state.evaluate[spanId] = {
3084
+ open: false, instructions: EVALUATE_DEFAULT_INSTRUCTIONS, status: "idle", result: null, error: null
3085
+ });
3086
+ }
3087
+ function evalResultHtml(result) {
3088
+ var score = typeof result.score === "number" ? Math.round(result.score * 100) + "%" : "n/a";
3089
+ var issues = (result.issues || []).map(function (i) { return "<li>" + esc(i) + "</li>"; }).join("");
3090
+ return '<div class="eval-result"><span class="eval-score">Score: ' + score + "</span>"
3091
+ + (issues ? '<ul class="eval-issues">' + issues + "</ul>" : "") + "</div>";
3092
+ }
3093
+ function evalSectionHtml(span) {
3094
+ if (!EVALUATE_ENABLED) return "";
3095
+ var sysPrompt = extractLastSystemPrompt(span);
3096
+ if (!sysPrompt) return "";
3097
+ var st = evalState(span.spanId);
3098
+ var btn = '<button type="button" class="eval-btn" data-evaluate-toggle="' + esc(span.spanId) + '">'
3099
+ + (st.open ? "Hide evaluate" : "Evaluate system prompt") + "</button>";
3100
+ if (!st.open) return btn;
3101
+ var running = st.status === "running";
3102
+ return btn + '<div class="eval-panel">'
3103
+ + '<textarea id="eval-instructions" placeholder="Grading instructions (optional — falls back to the configured default)"' + (running ? " disabled" : "") + ">" + esc(st.instructions || "") + "</textarea>"
3104
+ + '<div class="eval-actions">'
3105
+ + '<button type="button" class="eval-btn" data-evaluate-run="' + esc(span.spanId) + '"' + (running ? " disabled" : "") + ">" + (running ? "Evaluating…" : "Run") + "</button>"
3106
+ + (st.error ? '<span class="eval-error">' + esc(st.error) + "</span>" : "")
3107
+ + "</div>"
3108
+ + (st.result ? evalResultHtml(st.result) : "")
3109
+ + "</div>";
3110
+ }
3111
+ function rerenderDetail() {
3112
+ var t = findTrace(state.selectedId);
3113
+ if (!t) return;
3114
+ var sel = findSpan(t.root, state.selectedSpanId) || t.root;
3115
+ document.getElementById("drawer-detail").innerHTML = renderDetail(sel, t);
3116
+ }
3117
+ function toggleEvalPanel(spanId) {
3118
+ evalState(spanId).open = !evalState(spanId).open;
3119
+ rerenderDetail();
3120
+ }
3121
+ function runEvaluate(traceId, spanId) {
3122
+ var st = evalState(spanId);
3123
+ var textarea = document.getElementById("eval-instructions");
3124
+ if (textarea) st.instructions = textarea.value;
3125
+ st.status = "running"; st.error = null;
3126
+ rerenderDetail();
3127
+ fetchAuthed(API + "/traces/" + encodeURIComponent(traceId) + "/spans/" + encodeURIComponent(spanId) + "/evaluate", {
3128
+ method: "POST",
3129
+ headers: { "content-type": "application/json" },
3130
+ body: JSON.stringify({ instructions: st.instructions })
3131
+ }).then(function (r) {
3132
+ return r.json().then(function (data) { return { ok: r.ok, data: data }; });
3133
+ }).then(function (res) {
3134
+ if (res.ok) { st.status = "done"; st.result = res.data; st.error = null; }
3135
+ else { st.status = "error"; st.error = (res.data && (res.data.message || res.data.error)) || "evaluate failed"; st.result = null; }
3136
+ rerenderDetail();
3137
+ }).catch(function () {
3138
+ st.status = "error"; st.error = "network error"; st.result = null;
3139
+ rerenderDetail();
3140
+ });
3141
+ }
3142
+
2990
3143
  function renderDetail(span, trace) {
2991
3144
  var path = findPath(trace.root, span.spanId) || [span];
2992
3145
  var crumb = path.map(function (p, i) { return (i ? '<span class="sep"> › </span>' : "") + "<span>" + esc(p.name) + "</span>"; }).join("");
@@ -3009,6 +3162,7 @@ function dashboardHtml(basePath, title) {
3009
3162
  + '<span class="dim">· ' + dur(span.duration) + "</span></div>"
3010
3163
  + metaLine
3011
3164
  + io
3165
+ + evalSectionHtml(span, trace)
3012
3166
  + renderMeta(span, trace);
3013
3167
  }
3014
3168
 
@@ -3171,9 +3325,20 @@ function dashboardHtml(basePath, title) {
3171
3325
  if (vb) { setView(vb.getAttribute("data-view")); return; }
3172
3326
  var tog = e.target.closest ? e.target.closest("[data-toggle]") : null;
3173
3327
  if (tog) { toggleSpan(tog.getAttribute("data-toggle")); return; }
3328
+ var evalToggle = e.target.closest ? e.target.closest("[data-evaluate-toggle]") : null;
3329
+ if (evalToggle) { toggleEvalPanel(evalToggle.getAttribute("data-evaluate-toggle")); return; }
3330
+ var evalRun = e.target.closest ? e.target.closest("[data-evaluate-run]") : null;
3331
+ if (evalRun && state.selectedId) { runEvaluate(state.selectedId, evalRun.getAttribute("data-evaluate-run")); return; }
3174
3332
  var node = e.target.closest ? e.target.closest(".tnode[data-span], .grow[data-span]") : null;
3175
3333
  if (node) selectSpan(node.getAttribute("data-span"));
3176
3334
  });
3335
+ // Track the instructions textarea live (no re-render on keystroke, so
3336
+ // typing never loses focus/cursor position).
3337
+ document.getElementById("drawer-body").addEventListener("input", function (e) {
3338
+ if (e.target && e.target.id === "eval-instructions" && state.selectedSpanId) {
3339
+ evalState(state.selectedSpanId).instructions = e.target.value;
3340
+ }
3341
+ });
3177
3342
  document.getElementById("backdrop").addEventListener("click", closeDrawer);
3178
3343
  document.addEventListener("keydown", function (e) { if (e.key === "Escape" || e.keyCode === 27) closeDrawer(); });
3179
3344
 
@@ -3269,8 +3434,8 @@ function dashboardHtml(basePath, title) {
3269
3434
 
3270
3435
  function poll() {
3271
3436
  Promise.all([
3272
- fetch(API + "/aggregate").then(function (r) { return r.json(); }),
3273
- fetch(API + "/traces").then(function (r) { return r.json(); })
3437
+ fetchAuthed(API + "/aggregate").then(function (r) { return r.json(); }),
3438
+ fetchAuthed(API + "/traces").then(function (r) { return r.json(); })
3274
3439
  ]).then(function (res) {
3275
3440
  renderStats(res[0]);
3276
3441
  state.traces = res[1] || [];
@@ -3305,9 +3470,27 @@ function dashboardHtml(basePath, title) {
3305
3470
  function escapeHtml(value) {
3306
3471
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3307
3472
  }
3473
+ /**
3474
+ * Encode free-form text (e.g. `evaluate.instructions`, which an operator
3475
+ * could type anything into) as a JS EXPRESSION that reconstructs it at
3476
+ * runtime, WITHOUT ever emitting a literal backtick into the served page —
3477
+ * the client script is itself built from a TS template literal, so a raw
3478
+ * backtick in the output would be a real syntax hazard (see the `BT =
3479
+ * String.fromCharCode(96)` construction already in the client script for
3480
+ * the same reason). `JSON.stringify` alone doesn't escape backticks (they
3481
+ * aren't JSON-significant), so a value containing one is split around it
3482
+ * and rejoined with the client's own `BT` constant. No backticks in the
3483
+ * input ⇒ a single plain `JSON.stringify(value)` — no unnecessary
3484
+ * concatenation in the common case.
3485
+ */
3486
+ function encodeForInlineScript(value) {
3487
+ return value.split("`").map((part) => JSON.stringify(part)).join(" + BT + ");
3488
+ }
3308
3489
 
3309
3490
  //#endregion
3310
- //#region ../@warlock.js/ai-panoptic/src/dashboard/serve.ts
3491
+ //#region ../ai-panoptic/src/dashboard/serve.ts
3492
+ /** Hard cap on the evaluate route's request body — well beyond a real instructions string. */
3493
+ const MAX_EVALUATE_BODY_BYTES = 64 * 1024;
3311
3494
  /**
3312
3495
  * Security response headers added to every dashboard response (S4):
3313
3496
  * block MIME-sniffing, framing, referrer leakage, and lock the page's
@@ -3337,15 +3520,22 @@ function isAuthorized(req, url, token) {
3337
3520
  * trace store. Kept separate from the server lifecycle so it can be unit
3338
3521
  * tested by feeding it a fake `req`/`res` without binding a port.
3339
3522
  *
3340
- * Routes (all under `config.basePath`, all read-only):
3523
+ * Routes (all under `config.basePath`):
3341
3524
  *
3342
3525
  * - `GET api/traces` → `store.query(parseQuery(searchParams))`
3343
3526
  * - `GET api/traces/:id` → `store.get(id)` or `404`
3344
3527
  * - `GET api/aggregate` → `store.aggregate(parseQuery(searchParams))`
3345
3528
  * - `GET {basePath}` → the self-contained HTML page
3529
+ * - `POST api/traces/:traceId/spans/:spanId/evaluate` → ONLY when
3530
+ * `config.evaluate` is set; grades the span's last captured system
3531
+ * prompt and returns a {@link EvaluateVerdict}. The dashboard's one
3532
+ * write route — absent config, POST 405s like it would against any
3533
+ * other route (the path is never even pattern-matched).
3346
3534
  *
3347
- * Anything else → `404`. Non-`GET` methods → `405`. The store shapes
3348
- * ({@link Trace}/{@link TraceSpan}) are already JSON-safe, so responses
3535
+ * Anything else → `404`. A method the matched route doesn't accept → `405`.
3536
+ * Host allowlist + bearer-token auth (S4) are checked for EVERY request,
3537
+ * regardless of method, before any routing. The store shapes
3538
+ * ({@link Trace}/{@link TraceSpan}) are already JSON-safe, so GET responses
3349
3539
  * are a plain `JSON.stringify` with no serializer.
3350
3540
  *
3351
3541
  * @example
@@ -3356,10 +3546,6 @@ function createRequestHandler(store, config) {
3356
3546
  const base = config.basePath;
3357
3547
  const apiPrefix = `${base}api`;
3358
3548
  return function handle(req, res) {
3359
- if (req.method !== "GET") {
3360
- sendJson(res, 405, { error: "method_not_allowed" });
3361
- return;
3362
- }
3363
3549
  const url = new URL(req.url ?? "/", "http://localhost");
3364
3550
  const pathname = url.pathname;
3365
3551
  const host = hostHeaderName(req.headers.host);
@@ -3371,6 +3557,17 @@ function createRequestHandler(store, config) {
3371
3557
  sendJson(res, 401, { error: "unauthorized" });
3372
3558
  return;
3373
3559
  }
3560
+ if (req.method === "POST" && config.evaluate) {
3561
+ const match = matchEvaluatePath(pathname, apiPrefix);
3562
+ if (match) {
3563
+ handleEvaluate(store, config.evaluate, match.traceId, match.spanId, req, res);
3564
+ return;
3565
+ }
3566
+ }
3567
+ if (req.method !== "GET") {
3568
+ sendJson(res, 405, { error: "method_not_allowed" });
3569
+ return;
3570
+ }
3374
3571
  if (pathname === `${apiPrefix}/traces`) {
3375
3572
  sendJson(res, 200, store.query(parseQuery(url.searchParams)));
3376
3573
  return;
@@ -3397,7 +3594,7 @@ function createRequestHandler(store, config) {
3397
3594
  "content-type": "text/html; charset=utf-8",
3398
3595
  ...SECURITY_HEADERS
3399
3596
  });
3400
- res.end(dashboardHtml(base, config.title));
3597
+ res.end(dashboardHtml(base, config.title, Boolean(config.evaluate), config.evaluate?.instructions ?? ""));
3401
3598
  return;
3402
3599
  }
3403
3600
  sendJson(res, 404, { error: "not_found" });
@@ -3411,9 +3608,105 @@ function sendJson(res, status, body) {
3411
3608
  });
3412
3609
  res.end(JSON.stringify(body));
3413
3610
  }
3611
+ /**
3612
+ * Match `{apiPrefix}/traces/:traceId/spans/:spanId/evaluate` — the one
3613
+ * write route. Returns `undefined` for anything else, including the plain
3614
+ * `{apiPrefix}/traces/:id` read route (no `/spans/.../evaluate` suffix),
3615
+ * so the two never collide.
3616
+ */
3617
+ function matchEvaluatePath(pathname, apiPrefix) {
3618
+ const prefix = `${apiPrefix}/traces/`;
3619
+ if (!pathname.startsWith(prefix)) return;
3620
+ const match = /^([^/]+)\/spans\/([^/]+)\/evaluate$/.exec(pathname.slice(prefix.length));
3621
+ if (!match) return;
3622
+ return {
3623
+ traceId: decodeURIComponent(match[1]),
3624
+ spanId: decodeURIComponent(match[2])
3625
+ };
3626
+ }
3627
+ /**
3628
+ * Collect and JSON-parse a request body, capped at
3629
+ * {@link MAX_EVALUATE_BODY_BYTES} so an oversized body can't hold the
3630
+ * connection open indefinitely. An empty body resolves to `undefined` —
3631
+ * the evaluate route treats that as "no per-run override".
3632
+ */
3633
+ function readJsonBody(req) {
3634
+ return new Promise((resolve, reject) => {
3635
+ const chunks = [];
3636
+ let size = 0;
3637
+ req.on("data", (chunk) => {
3638
+ size += chunk.length;
3639
+ if (size > MAX_EVALUATE_BODY_BYTES) {
3640
+ req.destroy();
3641
+ reject(/* @__PURE__ */ new Error("payload_too_large"));
3642
+ return;
3643
+ }
3644
+ chunks.push(chunk);
3645
+ });
3646
+ req.on("end", () => {
3647
+ if (chunks.length === 0) {
3648
+ resolve(void 0);
3649
+ return;
3650
+ }
3651
+ try {
3652
+ resolve(JSON.parse(Buffer.concat(chunks).toString("utf-8")));
3653
+ } catch {
3654
+ reject(/* @__PURE__ */ new Error("invalid_json"));
3655
+ }
3656
+ });
3657
+ req.on("error", reject);
3658
+ });
3659
+ }
3660
+ /**
3661
+ * Handle `POST {apiPrefix}/traces/:traceId/spans/:spanId/evaluate`. Looks
3662
+ * up the trace + span, extracts its last captured system prompt, and
3663
+ * grades it via {@link evaluateSystemPrompt}. `judgePromptBody` itself
3664
+ * never throws (a broken judge degrades to an issues-only outcome) — the
3665
+ * try/catch here only guards `config.evaluate.model` resolution, the one
3666
+ * step that CAN throw (e.g. a factory constructing an SDK client).
3667
+ */
3668
+ async function handleEvaluate(store, evaluate, traceId, spanId, req, res) {
3669
+ const trace = store.get(traceId);
3670
+ if (trace === void 0) {
3671
+ sendJson(res, 404, {
3672
+ error: "trace_not_found",
3673
+ traceId
3674
+ });
3675
+ return;
3676
+ }
3677
+ const span = findSpanById(trace.root, spanId);
3678
+ if (span === void 0) {
3679
+ sendJson(res, 404, {
3680
+ error: "span_not_found",
3681
+ spanId
3682
+ });
3683
+ return;
3684
+ }
3685
+ const systemPrompt = extractLastSystemPrompt(span);
3686
+ if (systemPrompt === void 0) {
3687
+ sendJson(res, 422, { error: "no_system_prompt" });
3688
+ return;
3689
+ }
3690
+ let body;
3691
+ try {
3692
+ body = await readJsonBody(req);
3693
+ } catch (error) {
3694
+ const message = error instanceof Error ? error.message : "invalid_json";
3695
+ sendJson(res, message === "payload_too_large" ? 413 : 400, { error: message });
3696
+ return;
3697
+ }
3698
+ try {
3699
+ sendJson(res, 200, await evaluateSystemPrompt(systemPrompt, evaluate, body?.instructions));
3700
+ } catch (error) {
3701
+ sendJson(res, 502, {
3702
+ error: "evaluate_failed",
3703
+ message: error instanceof Error ? error.message : String(error)
3704
+ });
3705
+ }
3706
+ }
3414
3707
 
3415
3708
  //#endregion
3416
- //#region ../@warlock.js/ai-panoptic/src/dashboard/dashboard.ts
3709
+ //#region ../ai-panoptic/src/dashboard/dashboard.ts
3417
3710
  const DEFAULT_PORT = 4319;
3418
3711
  const DEFAULT_HOST = "127.0.0.1";
3419
3712
  const DEFAULT_TITLE = "Panoptic";
@@ -3452,7 +3745,8 @@ function dashboard(store, options = {}) {
3452
3745
  basePath,
3453
3746
  title,
3454
3747
  authToken: options.authToken,
3455
- allowedHosts
3748
+ allowedHosts,
3749
+ evaluate: options.evaluate
3456
3750
  }));
3457
3751
  return new Promise((resolve, reject) => {
3458
3752
  const onError = (error) => {
@@ -3540,7 +3834,7 @@ function openBrowser(url) {
3540
3834
  }
3541
3835
 
3542
3836
  //#endregion
3543
- //#region ../@warlock.js/ai-panoptic/src/dashboard/trace-filter.ts
3837
+ //#region ../ai-panoptic/src/dashboard/trace-filter.ts
3544
3838
  /** Statuses the errors-only toggle keeps. */
3545
3839
  const ERROR_STATUSES = ["failed", "cancelled"];
3546
3840
  /**
@@ -3808,7 +4102,7 @@ function maxNodeCost(root) {
3808
4102
  }
3809
4103
 
3810
4104
  //#endregion
3811
- //#region ../@warlock.js/ai-panoptic/src/config/apply-panoptic-config.ts
4105
+ //#region ../ai-panoptic/src/config/apply-panoptic-config.ts
3812
4106
  let applied;
3813
4107
  /**
3814
4108
  * Read a {@link PanopticConfig} and wire panoptic onto the core observe
@@ -3839,7 +4133,8 @@ function applyPanopticConfig(config) {
3839
4133
  let cacheStore;
3840
4134
  if (store === void 0 && config.dashboard) {
3841
4135
  if (config.cache !== void 0) {
3842
- cacheStore = createCacheTraceStore(config.cache);
4136
+ const onError = config.onError ?? ((error) => _warlock_js_logger.log.error("ai-panoptic", "cacheStore", error));
4137
+ cacheStore = createCacheTraceStore(config.cache, { onError });
3843
4138
  store = cacheStore;
3844
4139
  } else store = createInMemoryTraceStore();
3845
4140
  exporters.push(store);
@@ -3889,7 +4184,7 @@ function startDashboard(store, dashboardConfig) {
3889
4184
  }
3890
4185
 
3891
4186
  //#endregion
3892
- //#region ../@warlock.js/ai-panoptic/src/register.ts
4187
+ //#region ../ai-panoptic/src/register.ts
3893
4188
  (0, _warlock_js_ai.onConfigApplied)((config) => {
3894
4189
  applyPanopticConfig(config.panoptic);
3895
4190
  });
@@ -3910,9 +4205,12 @@ exports.createInMemoryTraceStore = createInMemoryTraceStore;
3910
4205
  exports.createPanopticMiddleware = createPanopticMiddleware;
3911
4206
  exports.dashboard = dashboard;
3912
4207
  exports.emptyUsage = emptyUsage;
4208
+ exports.evaluateSystemPrompt = evaluateSystemPrompt;
4209
+ exports.extractLastSystemPrompt = extractLastSystemPrompt;
3913
4210
  exports.extractSpanAttributes = extractSpanAttributes;
3914
4211
  exports.fileExporter = fileExporter;
3915
4212
  exports.filterTraces = filterTraces;
4213
+ exports.findSpanById = findSpanById;
3916
4214
  exports.formatSpanIO = formatSpanIO;
3917
4215
  exports.formatSpanLine = formatSpanLine;
3918
4216
  exports.groupByPrompt = groupByPrompt;