@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
@@ -1,6 +1,6 @@
1
1
  import { WARLOCK_LOGO_DATA_URI } from "./warlock-logo.mjs";
2
2
 
3
- //#region ../@warlock.js/ai-panoptic/src/dashboard/ui.html.ts
3
+ //#region ../ai-panoptic/src/dashboard/ui.html.ts
4
4
  /**
5
5
  * Build the single self-contained dashboard HTML page. No external
6
6
  * assets, no bundler, no framework — one inlined string that polls the
@@ -40,7 +40,7 @@ import { WARLOCK_LOGO_DATA_URI } from "./warlock-logo.mjs";
40
40
  * @param basePath Normalized mount path ending in `/` (e.g. `"/"`).
41
41
  * @param title Header title shown in the page.
42
42
  */
43
- function dashboardHtml(basePath, title) {
43
+ function dashboardHtml(basePath, title, evaluateEnabled = false, evaluateDefaultInstructions = "") {
44
44
  const apiBase = `${basePath}api`;
45
45
  const safeTitle = escapeHtml(title);
46
46
  return `<!doctype html>
@@ -221,6 +221,18 @@ function dashboardHtml(basePath, title) {
221
221
  .legend { margin-left: auto; display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--dim); }
222
222
  .legend .grad { width: 64px; height: 8px; border-radius: 4px; background: linear-gradient(90deg, var(--surface2), var(--cost)); border: 1px solid var(--border); }
223
223
  .gantt { padding: 8px 10px; }
224
+
225
+ /* Evaluate — the drawer's one write action (config-gated). */
226
+ .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; }
227
+ .eval-btn:hover:not(:disabled) { color: var(--text); border-color: var(--border2); }
228
+ .eval-btn:disabled { opacity: .6; cursor: default; }
229
+ .eval-panel { margin-top: 8px; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface2); }
230
+ .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; }
231
+ .eval-actions { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
232
+ .eval-error { color: var(--fail); font-size: 12px; }
233
+ .eval-result { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); font-size: 12px; }
234
+ .eval-score { font-weight: 600; }
235
+ .eval-issues { margin: 6px 0 0; padding-left: 18px; }
224
236
  .grow { display: flex; align-items: center; gap: 8px; padding: 2px 0; font-size: 12px; cursor: pointer; border-radius: 4px; }
225
237
  .grow:hover { background: var(--surface); }
226
238
  .grow.selected { background: var(--sel-bg); }
@@ -285,7 +297,21 @@ function dashboardHtml(basePath, title) {
285
297
  <script>
286
298
  (function () {
287
299
  var API = ${JSON.stringify(apiBase)};
300
+ var EVALUATE_ENABLED = ${JSON.stringify(evaluateEnabled)};
288
301
  var BT = String.fromCharCode(96);
302
+ var EVALUATE_DEFAULT_INSTRUCTIONS = ${encodeForInlineScript(evaluateDefaultInstructions)};
303
+
304
+ // Carry the ?token= the page itself was loaded with onto every
305
+ // subsequent poll — otherwise the API calls below inherit no auth and
306
+ // 401 forever once authToken is configured (the initial page load is
307
+ // the only request the URL's query string naturally reaches).
308
+ var TOKEN = new URLSearchParams(window.location.search).get("token");
309
+ function fetchAuthed(url, options) {
310
+ var opts = options || {};
311
+ var headers = opts.headers || {};
312
+ if (TOKEN) headers = Object.assign({}, headers, { Authorization: "Bearer " + TOKEN });
313
+ return fetch(url, Object.assign({}, opts, { headers: headers }));
314
+ }
289
315
 
290
316
  var state = {
291
317
  traces: [], selectedId: null, selectedSpanId: null, collapsed: {}, sig: null,
@@ -297,7 +323,8 @@ function dashboardHtml(basePath, title) {
297
323
  showStats: false, // per-type aggregate-stats panel toggle (independent of grouping)
298
324
  collapsedGroups: {}, // collapsed group headers (session, prompt, or type)
299
325
  view: "tree", // drawer left pane: "tree" | "timeline"
300
- hashApplied: false // guards one-time deep-link open on load
326
+ hashApplied: false, // guards one-time deep-link open on load
327
+ evaluate: {} // per-span evaluate UI state, keyed by spanId
301
328
  };
302
329
 
303
330
  var STATUS_FILTERS = ["completed", "failed", "cancelled"];
@@ -1006,6 +1033,80 @@ function dashboardHtml(basePath, title) {
1006
1033
  return '<div class="meta-sec"><div class="meta-title">metadata</div><div class="kv">' + kv + "</div></div>";
1007
1034
  }
1008
1035
 
1036
+ // --- Evaluate: grade a span's last captured system prompt -------------
1037
+ // Config-gated (EVALUATE_ENABLED) — the drawer's only write action, POSTing
1038
+ // to a route that itself only exists when the server was configured with
1039
+ // evaluate. UI state lives in state.evaluate, keyed by spanId, so it
1040
+ // survives a re-render (e.g. switching to a sibling span and back).
1041
+ function extractLastSystemPrompt(span) {
1042
+ if (!Array.isArray(span.input)) return null;
1043
+ for (var i = span.input.length - 1; i >= 0; i--) {
1044
+ var m = span.input[i];
1045
+ if (m && typeof m === "object" && m.role === "system" && typeof m.content === "string") return m.content;
1046
+ }
1047
+ return null;
1048
+ }
1049
+ function evalState(spanId) {
1050
+ return state.evaluate[spanId] || (state.evaluate[spanId] = {
1051
+ open: false, instructions: EVALUATE_DEFAULT_INSTRUCTIONS, status: "idle", result: null, error: null
1052
+ });
1053
+ }
1054
+ function evalResultHtml(result) {
1055
+ var score = typeof result.score === "number" ? Math.round(result.score * 100) + "%" : "n/a";
1056
+ var issues = (result.issues || []).map(function (i) { return "<li>" + esc(i) + "</li>"; }).join("");
1057
+ return '<div class="eval-result"><span class="eval-score">Score: ' + score + "</span>"
1058
+ + (issues ? '<ul class="eval-issues">' + issues + "</ul>" : "") + "</div>";
1059
+ }
1060
+ function evalSectionHtml(span) {
1061
+ if (!EVALUATE_ENABLED) return "";
1062
+ var sysPrompt = extractLastSystemPrompt(span);
1063
+ if (!sysPrompt) return "";
1064
+ var st = evalState(span.spanId);
1065
+ var btn = '<button type="button" class="eval-btn" data-evaluate-toggle="' + esc(span.spanId) + '">'
1066
+ + (st.open ? "Hide evaluate" : "Evaluate system prompt") + "</button>";
1067
+ if (!st.open) return btn;
1068
+ var running = st.status === "running";
1069
+ return btn + '<div class="eval-panel">'
1070
+ + '<textarea id="eval-instructions" placeholder="Grading instructions (optional — falls back to the configured default)"' + (running ? " disabled" : "") + ">" + esc(st.instructions || "") + "</textarea>"
1071
+ + '<div class="eval-actions">'
1072
+ + '<button type="button" class="eval-btn" data-evaluate-run="' + esc(span.spanId) + '"' + (running ? " disabled" : "") + ">" + (running ? "Evaluating…" : "Run") + "</button>"
1073
+ + (st.error ? '<span class="eval-error">' + esc(st.error) + "</span>" : "")
1074
+ + "</div>"
1075
+ + (st.result ? evalResultHtml(st.result) : "")
1076
+ + "</div>";
1077
+ }
1078
+ function rerenderDetail() {
1079
+ var t = findTrace(state.selectedId);
1080
+ if (!t) return;
1081
+ var sel = findSpan(t.root, state.selectedSpanId) || t.root;
1082
+ document.getElementById("drawer-detail").innerHTML = renderDetail(sel, t);
1083
+ }
1084
+ function toggleEvalPanel(spanId) {
1085
+ evalState(spanId).open = !evalState(spanId).open;
1086
+ rerenderDetail();
1087
+ }
1088
+ function runEvaluate(traceId, spanId) {
1089
+ var st = evalState(spanId);
1090
+ var textarea = document.getElementById("eval-instructions");
1091
+ if (textarea) st.instructions = textarea.value;
1092
+ st.status = "running"; st.error = null;
1093
+ rerenderDetail();
1094
+ fetchAuthed(API + "/traces/" + encodeURIComponent(traceId) + "/spans/" + encodeURIComponent(spanId) + "/evaluate", {
1095
+ method: "POST",
1096
+ headers: { "content-type": "application/json" },
1097
+ body: JSON.stringify({ instructions: st.instructions })
1098
+ }).then(function (r) {
1099
+ return r.json().then(function (data) { return { ok: r.ok, data: data }; });
1100
+ }).then(function (res) {
1101
+ if (res.ok) { st.status = "done"; st.result = res.data; st.error = null; }
1102
+ else { st.status = "error"; st.error = (res.data && (res.data.message || res.data.error)) || "evaluate failed"; st.result = null; }
1103
+ rerenderDetail();
1104
+ }).catch(function () {
1105
+ st.status = "error"; st.error = "network error"; st.result = null;
1106
+ rerenderDetail();
1107
+ });
1108
+ }
1109
+
1009
1110
  function renderDetail(span, trace) {
1010
1111
  var path = findPath(trace.root, span.spanId) || [span];
1011
1112
  var crumb = path.map(function (p, i) { return (i ? '<span class="sep"> › </span>' : "") + "<span>" + esc(p.name) + "</span>"; }).join("");
@@ -1028,6 +1129,7 @@ function dashboardHtml(basePath, title) {
1028
1129
  + '<span class="dim">· ' + dur(span.duration) + "</span></div>"
1029
1130
  + metaLine
1030
1131
  + io
1132
+ + evalSectionHtml(span, trace)
1031
1133
  + renderMeta(span, trace);
1032
1134
  }
1033
1135
 
@@ -1190,9 +1292,20 @@ function dashboardHtml(basePath, title) {
1190
1292
  if (vb) { setView(vb.getAttribute("data-view")); return; }
1191
1293
  var tog = e.target.closest ? e.target.closest("[data-toggle]") : null;
1192
1294
  if (tog) { toggleSpan(tog.getAttribute("data-toggle")); return; }
1295
+ var evalToggle = e.target.closest ? e.target.closest("[data-evaluate-toggle]") : null;
1296
+ if (evalToggle) { toggleEvalPanel(evalToggle.getAttribute("data-evaluate-toggle")); return; }
1297
+ var evalRun = e.target.closest ? e.target.closest("[data-evaluate-run]") : null;
1298
+ if (evalRun && state.selectedId) { runEvaluate(state.selectedId, evalRun.getAttribute("data-evaluate-run")); return; }
1193
1299
  var node = e.target.closest ? e.target.closest(".tnode[data-span], .grow[data-span]") : null;
1194
1300
  if (node) selectSpan(node.getAttribute("data-span"));
1195
1301
  });
1302
+ // Track the instructions textarea live (no re-render on keystroke, so
1303
+ // typing never loses focus/cursor position).
1304
+ document.getElementById("drawer-body").addEventListener("input", function (e) {
1305
+ if (e.target && e.target.id === "eval-instructions" && state.selectedSpanId) {
1306
+ evalState(state.selectedSpanId).instructions = e.target.value;
1307
+ }
1308
+ });
1196
1309
  document.getElementById("backdrop").addEventListener("click", closeDrawer);
1197
1310
  document.addEventListener("keydown", function (e) { if (e.key === "Escape" || e.keyCode === 27) closeDrawer(); });
1198
1311
 
@@ -1288,8 +1401,8 @@ function dashboardHtml(basePath, title) {
1288
1401
 
1289
1402
  function poll() {
1290
1403
  Promise.all([
1291
- fetch(API + "/aggregate").then(function (r) { return r.json(); }),
1292
- fetch(API + "/traces").then(function (r) { return r.json(); })
1404
+ fetchAuthed(API + "/aggregate").then(function (r) { return r.json(); }),
1405
+ fetchAuthed(API + "/traces").then(function (r) { return r.json(); })
1293
1406
  ]).then(function (res) {
1294
1407
  renderStats(res[0]);
1295
1408
  state.traces = res[1] || [];
@@ -1324,6 +1437,22 @@ function dashboardHtml(basePath, title) {
1324
1437
  function escapeHtml(value) {
1325
1438
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1326
1439
  }
1440
+ /**
1441
+ * Encode free-form text (e.g. `evaluate.instructions`, which an operator
1442
+ * could type anything into) as a JS EXPRESSION that reconstructs it at
1443
+ * runtime, WITHOUT ever emitting a literal backtick into the served page —
1444
+ * the client script is itself built from a TS template literal, so a raw
1445
+ * backtick in the output would be a real syntax hazard (see the `BT =
1446
+ * String.fromCharCode(96)` construction already in the client script for
1447
+ * the same reason). `JSON.stringify` alone doesn't escape backticks (they
1448
+ * aren't JSON-significant), so a value containing one is split around it
1449
+ * and rejoined with the client's own `BT` constant. No backticks in the
1450
+ * input ⇒ a single plain `JSON.stringify(value)` — no unnecessary
1451
+ * concatenation in the common case.
1452
+ */
1453
+ function encodeForInlineScript(value) {
1454
+ return value.split("`").map((part) => JSON.stringify(part)).join(" + BT + ");
1455
+ }
1327
1456
 
1328
1457
  //#endregion
1329
1458
  export { dashboardHtml };