@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
@@ -0,0 +1,187 @@
1
+ import { Trace, TraceSpan } from "../contracts/trace.type.mjs";
2
+ import { ReportStatus, ReportType } from "@warlock.js/ai";
3
+
4
+ //#region ../@warlock.js/ai-panoptic/src/dashboard/trace-filter.d.ts
5
+ /**
6
+ * Client-side trace-list filter criteria. The dashboard polls the full
7
+ * trace list every 2s and narrows it in the browser with these — search
8
+ * box, filter chips, and the errors-only header toggle all feed one
9
+ * {@link TraceFilter}. Kept as a pure, framework-free module so the exact
10
+ * matching rules are unit-tested here and the inlined dashboard JS in
11
+ * {@link import("./ui.html").dashboardHtml} mirrors them 1:1.
12
+ *
13
+ * Every field is optional — an absent field is "don't care". An empty
14
+ * filter therefore matches every trace.
15
+ */
16
+ type TraceFilter = {
17
+ /**
18
+ * Free-text needle matched case-insensitively against a trace's root
19
+ * span name and session id. Whitespace-trimmed; an empty/blank string
20
+ * is treated as absent.
21
+ */
22
+ text?: string;
23
+ /**
24
+ * Restrict to these terminal statuses (root span status). Empty array
25
+ * is treated as "don't care" (matches every status).
26
+ */
27
+ statuses?: ReportStatus[];
28
+ /**
29
+ * Restrict to these executable types (root span type). Empty array is
30
+ * treated as "don't care".
31
+ */
32
+ types?: ReportType[];
33
+ /**
34
+ * Exact session-id chip. When set, only traces whose `sessionId`
35
+ * equals this value match. Distinct from {@link TraceFilter.text},
36
+ * which is a fuzzy substring across name + session.
37
+ */
38
+ sessionId?: string;
39
+ /**
40
+ * Exact prompt chip — a `name@version` key (see {@link tracePromptKey}).
41
+ * When set, only traces whose root span resolved that exact named prompt
42
+ * version match. Mirrors {@link TraceFilter.sessionId} but over the
43
+ * prompt-version-linkage attributes the collector stamps on agent spans.
44
+ */
45
+ promptKey?: string;
46
+ /**
47
+ * Errors-only header toggle. When `true`, only `failed` / `cancelled`
48
+ * traces match — independent of (and intersected with) `statuses`.
49
+ */
50
+ errorsOnly?: boolean;
51
+ };
52
+ /**
53
+ * The `name@version` prompt key a trace falls under, read from its root
54
+ * span's prompt-version-linkage attributes (`agent.promptName` /
55
+ * `agent.promptVersion`). Returns `undefined` when the run carried no named
56
+ * prompt (raw-string / anonymous / no prompt), so callers can bucket those
57
+ * under a synthetic "no prompt" key or skip them.
58
+ *
59
+ * The version is included so two runs of the same prompt name at different
60
+ * versions are distinct keys — that is the whole point of the linkage: to
61
+ * group / filter by the *exact* prompt revision that produced a run.
62
+ */
63
+ declare function tracePromptKey(trace: Trace): string | undefined;
64
+ /**
65
+ * Does a single trace pass the given filter? Pure predicate — all
66
+ * conditions are ANDed; an unset field never excludes. Used by
67
+ * {@link filterTraces} and mirrored by the dashboard's inlined JS.
68
+ */
69
+ declare function matchesFilter(trace: Trace, filter: TraceFilter): boolean;
70
+ /** Narrow a trace list to those matching the filter, order preserved. */
71
+ declare function filterTraces(traces: Trace[], filter: TraceFilter): Trace[];
72
+ /**
73
+ * The grouping key a trace falls under when the list is grouped by
74
+ * session. Traces without a `sessionId` collapse into one synthetic
75
+ * "(no session)" bucket so they remain visible.
76
+ */
77
+ declare const NO_SESSION_KEY = "(no session)";
78
+ /** One session bucket — its key plus the traces under it, order preserved. */
79
+ type SessionGroup = {
80
+ /** The `sessionId`, or {@link NO_SESSION_KEY} for sessionless traces. */sessionId: string; /** Traces in this group, in the order they appeared in the input. */
81
+ traces: Trace[];
82
+ };
83
+ /**
84
+ * Group an (already filtered, newest-first) trace list by session id,
85
+ * preserving first-seen order of both the groups and the traces within
86
+ * each. Sessionless traces bucket under {@link NO_SESSION_KEY}.
87
+ */
88
+ declare function groupBySession(traces: Trace[]): SessionGroup[];
89
+ /**
90
+ * The grouping key a trace falls under when the list is grouped by prompt
91
+ * version. Runs that resolved no named prompt collapse into one synthetic
92
+ * "(no prompt)" bucket so they remain visible.
93
+ */
94
+ declare const NO_PROMPT_KEY = "(no prompt)";
95
+ /** One prompt-version bucket — its `name@version` key plus its traces. */
96
+ type PromptGroup = {
97
+ /** The `name@version` key, or {@link NO_PROMPT_KEY} for unlinked traces. */promptKey: string; /** Traces in this group, in the order they appeared in the input. */
98
+ traces: Trace[];
99
+ };
100
+ /**
101
+ * Group an (already filtered, newest-first) trace list by prompt version —
102
+ * the `name@version` key {@link tracePromptKey} derives from each run's
103
+ * prompt-version-linkage attributes. Preserves first-seen order of both the
104
+ * groups and the traces within each. Runs with no named prompt bucket under
105
+ * {@link NO_PROMPT_KEY}.
106
+ *
107
+ * The dashboard offers this as a second group-by dimension beside session,
108
+ * so a reviewer can see every run of "support@2" together and compare its
109
+ * cost / failure rate against "support@3".
110
+ */
111
+ declare function groupByPrompt(traces: Trace[]): PromptGroup[];
112
+ /**
113
+ * The grouping key a trace falls under when the list is grouped by root
114
+ * type. `root.type` is always present, so this is only a defensive
115
+ * fallback for a malformed trace with no typed root.
116
+ */
117
+ declare const NO_TYPE_KEY = "(no type)";
118
+ /** One root-type bucket — its type discriminator plus its traces. */
119
+ type TypeGroup = {
120
+ /** The root span's `type` (e.g. `agent` / `workflow` / `planner`), or {@link NO_TYPE_KEY}. */type: string; /** Traces in this group, in the order they appeared in the input. */
121
+ traces: Trace[];
122
+ };
123
+ /**
124
+ * Group an (already filtered, newest-first) trace list by root type —
125
+ * the `agent` / `workflow` / `supervisor` / `orchestrator` / `planner` /
126
+ * `tool` / `batch` / `callback` discriminator on each trace's root span.
127
+ * Preserves first-seen order of both the groups and the traces within each.
128
+ *
129
+ * The dashboard offers this as the coarsest group-by dimension beside
130
+ * session and prompt: when a benchmark floods the flat list with hundreds
131
+ * of one primitive (e.g. standalone `agent` runs), this collapses them
132
+ * behind a per-type header so the structural primitives (the lone planner,
133
+ * the team, the supervisors) are one click away instead of a scroll past.
134
+ */
135
+ declare function groupByType(traces: Trace[]): TypeGroup[];
136
+ /** One row of the per-type aggregate-stats panel. */
137
+ type TypeStat = {
138
+ /** Root type discriminator, or {@link NO_TYPE_KEY}. */type: string; /** Number of traces of this type. */
139
+ count: number; /** Traces whose status is `failed` / `cancelled`. */
140
+ failed: number; /** `failed / count` in `[0, 1]`. */
141
+ failRate: number; /** Median (p50) root duration in ms. */
142
+ p50: number; /** p95 root duration in ms. */
143
+ p95: number; /** Summed `usage.total` tokens across the bucket. */
144
+ tokens: number; /** Summed per-trace USD cost across the bucket. */
145
+ cost: number;
146
+ };
147
+ /**
148
+ * Nearest-rank percentile of `values` at `p` (0–100). Returns 0 for an
149
+ * empty input and sorts a copy so the caller's array is left untouched.
150
+ */
151
+ declare function percentile(values: number[], p: number): number;
152
+ /**
153
+ * One trace's total USD cost: its explicit root-usage cost when priced,
154
+ * else the rollup of its subtree. Mirrors the dashboard's `traceCost` so the
155
+ * stats panel and the trace rows agree to the cent.
156
+ */
157
+ declare function traceCost(trace: Trace): number;
158
+ /**
159
+ * Aggregate an (already filtered) trace list into one {@link TypeStat} row
160
+ * per root type — count, failure rate, p50/p95 latency, total tokens, total
161
+ * cost — preserving first-seen type order. Powers the dashboard's per-type
162
+ * stats panel; computed over the same filtered list the trace view shows, so
163
+ * the panel always reflects the active filters.
164
+ */
165
+ declare function aggregateByType(traces: Trace[]): TypeStat[];
166
+ /**
167
+ * Rollup-aware subtree cost: a node's own cost when it carries one
168
+ * (it already rolls up its trips), otherwise the sum of its children.
169
+ * Avoids double-counting wrapper nodes (workflow/supervisor roots carry
170
+ * tokens but no cost). Mirrors the dashboard's `rollupCost`.
171
+ */
172
+ declare function rollupCost(span: TraceSpan): number;
173
+ /**
174
+ * Heatmap intensity in `[0, 1]` for a node's `rollupCost` relative to the
175
+ * trace's most-expensive node. The dashboard tints each tree node's left
176
+ * accent by this. `max <= 0` (a free trace) yields `0` for every node so
177
+ * the heatmap simply stays cold rather than dividing by zero.
178
+ */
179
+ declare function heatIntensity(nodeCost: number, maxCost: number): number;
180
+ /**
181
+ * The largest single-node {@link rollupCost} anywhere in a span tree —
182
+ * the denominator for {@link heatIntensity}. Walks the whole subtree.
183
+ */
184
+ declare function maxNodeCost(root: TraceSpan): number;
185
+ //#endregion
186
+ export { 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 };
187
+ //# sourceMappingURL=trace-filter.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-filter.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/dashboard/trace-filter.ts"],"mappings":";;;;;;AAcA;;;;;;;;;KAAY,WAAA;EA6BV;;;AAKU;AAyBZ;EArDE,IAAA;;;AAqDyC;AAwB3C;EAxEE,QAAA,GAAW,YAAA;;;;;EAKX,KAAA,GAAQ,UAAU;EAmEwB;;AAAmB;AA2C/D;;EAxGE,SAAA;EAwGmC;;;;;;EAjGnC,SAAA;EAiGoD;;;;EA5FpD,UAAA;AAAA;;;;AAqGyB;AAG3B;;;;;;;iBA/EgB,cAAA,CAAe,KAAY,EAAL,KAAK;AAmF5B;AAQf;;;;AARe,iBA3DC,aAAA,CAAc,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,WAAW;;iBA2C/C,YAAA,CAAa,MAAA,EAAQ,KAAA,IAAS,MAAA,EAAQ,WAAA,GAAc,KAAA;;AAwBP;AAyB7D;;;cAxCa,cAAA;AAwCa;AAAA,KArCd,YAAA;EAwCW,yEAtCrB,SAAA,UA0Ca;EAxCb,MAAA,EAAQ,KAAK;AAAA;;;AAwCA;AAcf;;iBA9CgB,cAAA,CAAe,MAAA,EAAQ,KAAA,KAAU,YAAY;;;;;;cAyBhD,aAAA;AA8Cb;AAAA,KA3CY,WAAA;8EAEV,SAAA,UAyCsB;EAvCtB,MAAA,EAAQ,KAAK;AAAA;;;;;;;AA8CA;AAef;;;;iBA/CgB,aAAA,CAAc,MAAA,EAAQ,KAAA,KAAU,WAAW;;;;AA+CJ;AAqBvD;cA3Ca,WAAA;;KAGD,SAAA;EA0CV,8FAxCA,IAAA,UA4CA;EA1CA,MAAA,EAAQ,KAAK;AAAA;;;;;AAoDT;AAON;;;;AAAsD;AAiBtD;;iBA7DgB,WAAA,CAAY,MAAA,EAAQ,KAAA,KAAU,SAAS;;KAqB3C,QAAA;EAoDI,uDAlDd,IAAA;EAEA,KAAA,UAgDsC;EA9CtC,MAAA,UA8CgD;EA5ChD,QAAA,UA4CwD;EA1CxD,GAAA,UAiHwB;EA/GxB,GAAA,UA+G+B;EA7G/B,MAAA,UAiIc;EA/Hd,IAAA;AAAA;;AA+H6D;AAc/D;;iBAtIgB,UAAA,CAAW,MAAA,YAAkB,CAAS;;AAsIX;;;;iBArH3B,SAAA,CAAU,KAAY,EAAL,KAAK;;;;;;;;iBAYtB,eAAA,CAAgB,MAAA,EAAQ,KAAA,KAAU,QAAQ;;;;;;;iBAuE1C,UAAA,CAAW,IAAe,EAAT,SAAS;;;;;;;iBAoB1B,aAAA,CAAc,QAAA,UAAkB,OAAe;;;;;iBAc/C,WAAA,CAAY,IAAe,EAAT,SAAS"}
@@ -0,0 +1,270 @@
1
+ //#region ../@warlock.js/ai-panoptic/src/dashboard/trace-filter.ts
2
+ /** Statuses the errors-only toggle keeps. */
3
+ const ERROR_STATUSES = ["failed", "cancelled"];
4
+ /**
5
+ * Span-attribute keys the collector stamps with prompt-version linkage when
6
+ * an agent ran against a *named* `ai.prompts` builder. Read here (not
7
+ * imported) so this pure module stays decoupled from the collector.
8
+ */
9
+ const PROMPT_NAME_ATTR = "agent.promptName";
10
+ const PROMPT_VERSION_ATTR = "agent.promptVersion";
11
+ /**
12
+ * The `name@version` prompt key a trace falls under, read from its root
13
+ * span's prompt-version-linkage attributes (`agent.promptName` /
14
+ * `agent.promptVersion`). Returns `undefined` when the run carried no named
15
+ * prompt (raw-string / anonymous / no prompt), so callers can bucket those
16
+ * under a synthetic "no prompt" key or skip them.
17
+ *
18
+ * The version is included so two runs of the same prompt name at different
19
+ * versions are distinct keys — that is the whole point of the linkage: to
20
+ * group / filter by the *exact* prompt revision that produced a run.
21
+ */
22
+ function tracePromptKey(trace) {
23
+ const attributes = trace.root.attributes;
24
+ if (attributes === void 0) return;
25
+ const name = attributes[PROMPT_NAME_ATTR];
26
+ if (typeof name !== "string" || name.length === 0) return;
27
+ const version = attributes[PROMPT_VERSION_ATTR];
28
+ return `${name}@${typeof version === "string" && version.length > 0 ? version : "1"}`;
29
+ }
30
+ /**
31
+ * Does a single trace pass the given filter? Pure predicate — all
32
+ * conditions are ANDed; an unset field never excludes. Used by
33
+ * {@link filterTraces} and mirrored by the dashboard's inlined JS.
34
+ */
35
+ function matchesFilter(trace, filter) {
36
+ const root = trace.root;
37
+ if (filter.errorsOnly === true && !ERROR_STATUSES.includes(root.status)) return false;
38
+ if (filter.statuses !== void 0 && filter.statuses.length > 0) {
39
+ if (!filter.statuses.includes(root.status)) return false;
40
+ }
41
+ if (filter.types !== void 0 && filter.types.length > 0) {
42
+ if (!filter.types.includes(root.type)) return false;
43
+ }
44
+ if (filter.sessionId !== void 0 && filter.sessionId.length > 0) {
45
+ if (trace.sessionId !== filter.sessionId) return false;
46
+ }
47
+ if (filter.promptKey !== void 0 && filter.promptKey.length > 0) {
48
+ if (tracePromptKey(trace) !== filter.promptKey) return false;
49
+ }
50
+ const text = filter.text?.trim().toLowerCase();
51
+ if (text !== void 0 && text.length > 0) {
52
+ if (!`${root.name} ${trace.sessionId ?? ""}`.toLowerCase().includes(text)) return false;
53
+ }
54
+ return true;
55
+ }
56
+ /** Narrow a trace list to those matching the filter, order preserved. */
57
+ function filterTraces(traces, filter) {
58
+ return traces.filter((trace) => matchesFilter(trace, filter));
59
+ }
60
+ /**
61
+ * The grouping key a trace falls under when the list is grouped by
62
+ * session. Traces without a `sessionId` collapse into one synthetic
63
+ * "(no session)" bucket so they remain visible.
64
+ */
65
+ const NO_SESSION_KEY = "(no session)";
66
+ /**
67
+ * Group an (already filtered, newest-first) trace list by session id,
68
+ * preserving first-seen order of both the groups and the traces within
69
+ * each. Sessionless traces bucket under {@link NO_SESSION_KEY}.
70
+ */
71
+ function groupBySession(traces) {
72
+ const order = [];
73
+ const byKey = /* @__PURE__ */ new Map();
74
+ for (const trace of traces) {
75
+ const key = trace.sessionId ?? "(no session)";
76
+ let bucket = byKey.get(key);
77
+ if (bucket === void 0) {
78
+ bucket = [];
79
+ byKey.set(key, bucket);
80
+ order.push(key);
81
+ }
82
+ bucket.push(trace);
83
+ }
84
+ return order.map((sessionId) => ({
85
+ sessionId,
86
+ traces: byKey.get(sessionId) ?? []
87
+ }));
88
+ }
89
+ /**
90
+ * The grouping key a trace falls under when the list is grouped by prompt
91
+ * version. Runs that resolved no named prompt collapse into one synthetic
92
+ * "(no prompt)" bucket so they remain visible.
93
+ */
94
+ const NO_PROMPT_KEY = "(no prompt)";
95
+ /**
96
+ * Group an (already filtered, newest-first) trace list by prompt version —
97
+ * the `name@version` key {@link tracePromptKey} derives from each run's
98
+ * prompt-version-linkage attributes. Preserves first-seen order of both the
99
+ * groups and the traces within each. Runs with no named prompt bucket under
100
+ * {@link NO_PROMPT_KEY}.
101
+ *
102
+ * The dashboard offers this as a second group-by dimension beside session,
103
+ * so a reviewer can see every run of "support@2" together and compare its
104
+ * cost / failure rate against "support@3".
105
+ */
106
+ function groupByPrompt(traces) {
107
+ const order = [];
108
+ const byKey = /* @__PURE__ */ new Map();
109
+ for (const trace of traces) {
110
+ const key = tracePromptKey(trace) ?? "(no prompt)";
111
+ let bucket = byKey.get(key);
112
+ if (bucket === void 0) {
113
+ bucket = [];
114
+ byKey.set(key, bucket);
115
+ order.push(key);
116
+ }
117
+ bucket.push(trace);
118
+ }
119
+ return order.map((promptKey) => ({
120
+ promptKey,
121
+ traces: byKey.get(promptKey) ?? []
122
+ }));
123
+ }
124
+ /**
125
+ * The grouping key a trace falls under when the list is grouped by root
126
+ * type. `root.type` is always present, so this is only a defensive
127
+ * fallback for a malformed trace with no typed root.
128
+ */
129
+ const NO_TYPE_KEY = "(no type)";
130
+ /**
131
+ * Group an (already filtered, newest-first) trace list by root type —
132
+ * the `agent` / `workflow` / `supervisor` / `orchestrator` / `planner` /
133
+ * `tool` / `batch` / `callback` discriminator on each trace's root span.
134
+ * Preserves first-seen order of both the groups and the traces within each.
135
+ *
136
+ * The dashboard offers this as the coarsest group-by dimension beside
137
+ * session and prompt: when a benchmark floods the flat list with hundreds
138
+ * of one primitive (e.g. standalone `agent` runs), this collapses them
139
+ * behind a per-type header so the structural primitives (the lone planner,
140
+ * the team, the supervisors) are one click away instead of a scroll past.
141
+ */
142
+ function groupByType(traces) {
143
+ const order = [];
144
+ const byKey = /* @__PURE__ */ new Map();
145
+ for (const trace of traces) {
146
+ const key = trace.root.type || "(no type)";
147
+ let bucket = byKey.get(key);
148
+ if (bucket === void 0) {
149
+ bucket = [];
150
+ byKey.set(key, bucket);
151
+ order.push(key);
152
+ }
153
+ bucket.push(trace);
154
+ }
155
+ return order.map((type) => ({
156
+ type,
157
+ traces: byKey.get(type) ?? []
158
+ }));
159
+ }
160
+ /**
161
+ * Nearest-rank percentile of `values` at `p` (0–100). Returns 0 for an
162
+ * empty input and sorts a copy so the caller's array is left untouched.
163
+ */
164
+ function percentile(values, p) {
165
+ if (values.length === 0) return 0;
166
+ const sorted = values.slice().sort((a, b) => a - b);
167
+ const rank = Math.ceil(p / 100 * sorted.length) - 1;
168
+ return sorted[Math.min(Math.max(rank, 0), sorted.length - 1)];
169
+ }
170
+ /**
171
+ * One trace's total USD cost: its explicit root-usage cost when priced,
172
+ * else the rollup of its subtree. Mirrors the dashboard's `traceCost` so the
173
+ * stats panel and the trace rows agree to the cent.
174
+ */
175
+ function traceCost(trace) {
176
+ const own = costSumObj(trace.usage.cost);
177
+ return own > 0 ? own : rollupCost(trace.root);
178
+ }
179
+ /**
180
+ * Aggregate an (already filtered) trace list into one {@link TypeStat} row
181
+ * per root type — count, failure rate, p50/p95 latency, total tokens, total
182
+ * cost — preserving first-seen type order. Powers the dashboard's per-type
183
+ * stats panel; computed over the same filtered list the trace view shows, so
184
+ * the panel always reflects the active filters.
185
+ */
186
+ function aggregateByType(traces) {
187
+ const order = [];
188
+ const byKey = /* @__PURE__ */ new Map();
189
+ for (const trace of traces) {
190
+ const key = trace.root.type || "(no type)";
191
+ let bucket = byKey.get(key);
192
+ if (bucket === void 0) {
193
+ bucket = [];
194
+ byKey.set(key, bucket);
195
+ order.push(key);
196
+ }
197
+ bucket.push(trace);
198
+ }
199
+ return order.map((type) => {
200
+ const bucket = byKey.get(type) ?? [];
201
+ const durations = bucket.map((trace) => trace.duration);
202
+ let failed = 0;
203
+ let tokens = 0;
204
+ let cost = 0;
205
+ for (const trace of bucket) {
206
+ if (ERROR_STATUSES.includes(trace.root.status)) failed += 1;
207
+ tokens += trace.usage.total ?? 0;
208
+ cost += traceCost(trace);
209
+ }
210
+ return {
211
+ type,
212
+ count: bucket.length,
213
+ failed,
214
+ failRate: bucket.length > 0 ? failed / bucket.length : 0,
215
+ p50: percentile(durations, 50),
216
+ p95: percentile(durations, 95),
217
+ tokens,
218
+ cost
219
+ };
220
+ });
221
+ }
222
+ /**
223
+ * Sum every priced lane of a usage cost object into one USD number.
224
+ * Mirrors the dashboard's `costSumObj` so heatmap intensities computed
225
+ * here match what the page renders.
226
+ */
227
+ function costSumObj(cost) {
228
+ if (cost === void 0) return 0;
229
+ return (cost.input ?? 0) + (cost.output ?? 0) + (cost.cachedInput ?? 0) + (cost.cachedOutput ?? 0) + (cost.reasoning ?? 0);
230
+ }
231
+ /**
232
+ * Rollup-aware subtree cost: a node's own cost when it carries one
233
+ * (it already rolls up its trips), otherwise the sum of its children.
234
+ * Avoids double-counting wrapper nodes (workflow/supervisor roots carry
235
+ * tokens but no cost). Mirrors the dashboard's `rollupCost`.
236
+ */
237
+ function rollupCost(span) {
238
+ const own = costSumObj(span.usage.cost);
239
+ if (own > 0) return own;
240
+ let sum = 0;
241
+ for (const child of span.children) sum += rollupCost(child);
242
+ return sum;
243
+ }
244
+ /**
245
+ * Heatmap intensity in `[0, 1]` for a node's `rollupCost` relative to the
246
+ * trace's most-expensive node. The dashboard tints each tree node's left
247
+ * accent by this. `max <= 0` (a free trace) yields `0` for every node so
248
+ * the heatmap simply stays cold rather than dividing by zero.
249
+ */
250
+ function heatIntensity(nodeCost, maxCost) {
251
+ if (maxCost <= 0 || nodeCost <= 0) return 0;
252
+ const ratio = nodeCost / maxCost;
253
+ return ratio > 1 ? 1 : ratio;
254
+ }
255
+ /**
256
+ * The largest single-node {@link rollupCost} anywhere in a span tree —
257
+ * the denominator for {@link heatIntensity}. Walks the whole subtree.
258
+ */
259
+ function maxNodeCost(root) {
260
+ let max = rollupCost(root);
261
+ for (const child of root.children) {
262
+ const childMax = maxNodeCost(child);
263
+ if (childMax > max) max = childMax;
264
+ }
265
+ return max;
266
+ }
267
+
268
+ //#endregion
269
+ export { NO_PROMPT_KEY, NO_SESSION_KEY, NO_TYPE_KEY, aggregateByType, filterTraces, groupByPrompt, groupBySession, groupByType, heatIntensity, matchesFilter, maxNodeCost, percentile, rollupCost, traceCost, tracePromptKey };
270
+ //# sourceMappingURL=trace-filter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-filter.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-panoptic/src/dashboard/trace-filter.ts"],"sourcesContent":["import type { ReportStatus, ReportType } from \"@warlock.js/ai\";\nimport type { Trace, TraceSpan } from \"../contracts/trace.type\";\n\n/**\n * Client-side trace-list filter criteria. The dashboard polls the full\n * trace list every 2s and narrows it in the browser with these — search\n * box, filter chips, and the errors-only header toggle all feed one\n * {@link TraceFilter}. Kept as a pure, framework-free module so the exact\n * matching rules are unit-tested here and the inlined dashboard JS in\n * {@link import(\"./ui.html\").dashboardHtml} mirrors them 1:1.\n *\n * Every field is optional — an absent field is \"don't care\". An empty\n * filter therefore matches every trace.\n */\nexport type TraceFilter = {\n /**\n * Free-text needle matched case-insensitively against a trace's root\n * span name and session id. Whitespace-trimmed; an empty/blank string\n * is treated as absent.\n */\n text?: string;\n /**\n * Restrict to these terminal statuses (root span status). Empty array\n * is treated as \"don't care\" (matches every status).\n */\n statuses?: ReportStatus[];\n /**\n * Restrict to these executable types (root span type). Empty array is\n * treated as \"don't care\".\n */\n types?: ReportType[];\n /**\n * Exact session-id chip. When set, only traces whose `sessionId`\n * equals this value match. Distinct from {@link TraceFilter.text},\n * which is a fuzzy substring across name + session.\n */\n sessionId?: string;\n /**\n * Exact prompt chip — a `name@version` key (see {@link tracePromptKey}).\n * When set, only traces whose root span resolved that exact named prompt\n * version match. Mirrors {@link TraceFilter.sessionId} but over the\n * prompt-version-linkage attributes the collector stamps on agent spans.\n */\n promptKey?: string;\n /**\n * Errors-only header toggle. When `true`, only `failed` / `cancelled`\n * traces match — independent of (and intersected with) `statuses`.\n */\n errorsOnly?: boolean;\n};\n\n/** Statuses the errors-only toggle keeps. */\nconst ERROR_STATUSES: readonly ReportStatus[] = [\"failed\", \"cancelled\"] as const;\n\n/**\n * Span-attribute keys the collector stamps with prompt-version linkage when\n * an agent ran against a *named* `ai.prompts` builder. Read here (not\n * imported) so this pure module stays decoupled from the collector.\n */\nconst PROMPT_NAME_ATTR = \"agent.promptName\";\nconst PROMPT_VERSION_ATTR = \"agent.promptVersion\";\n\n/**\n * The `name@version` prompt key a trace falls under, read from its root\n * span's prompt-version-linkage attributes (`agent.promptName` /\n * `agent.promptVersion`). Returns `undefined` when the run carried no named\n * prompt (raw-string / anonymous / no prompt), so callers can bucket those\n * under a synthetic \"no prompt\" key or skip them.\n *\n * The version is included so two runs of the same prompt name at different\n * versions are distinct keys — that is the whole point of the linkage: to\n * group / filter by the *exact* prompt revision that produced a run.\n */\nexport function tracePromptKey(trace: Trace): string | undefined {\n const attributes = trace.root.attributes;\n\n if (attributes === undefined) {\n return undefined;\n }\n\n const name = attributes[PROMPT_NAME_ATTR];\n\n if (typeof name !== \"string\" || name.length === 0) {\n return undefined;\n }\n\n const version = attributes[PROMPT_VERSION_ATTR];\n const versionLabel = typeof version === \"string\" && version.length > 0 ? version : \"1\";\n\n return `${name}@${versionLabel}`;\n}\n\n/**\n * Does a single trace pass the given filter? Pure predicate — all\n * conditions are ANDed; an unset field never excludes. Used by\n * {@link filterTraces} and mirrored by the dashboard's inlined JS.\n */\nexport function matchesFilter(trace: Trace, filter: TraceFilter): boolean {\n const root = trace.root;\n\n if (filter.errorsOnly === true && !ERROR_STATUSES.includes(root.status)) {\n return false;\n }\n\n if (filter.statuses !== undefined && filter.statuses.length > 0) {\n if (!filter.statuses.includes(root.status)) {\n return false;\n }\n }\n\n if (filter.types !== undefined && filter.types.length > 0) {\n if (!filter.types.includes(root.type)) {\n return false;\n }\n }\n\n if (filter.sessionId !== undefined && filter.sessionId.length > 0) {\n if (trace.sessionId !== filter.sessionId) {\n return false;\n }\n }\n\n if (filter.promptKey !== undefined && filter.promptKey.length > 0) {\n if (tracePromptKey(trace) !== filter.promptKey) {\n return false;\n }\n }\n\n const text = filter.text?.trim().toLowerCase();\n if (text !== undefined && text.length > 0) {\n const haystack = `${root.name} ${trace.sessionId ?? \"\"}`.toLowerCase();\n if (!haystack.includes(text)) {\n return false;\n }\n }\n\n return true;\n}\n\n/** Narrow a trace list to those matching the filter, order preserved. */\nexport function filterTraces(traces: Trace[], filter: TraceFilter): Trace[] {\n return traces.filter((trace) => matchesFilter(trace, filter));\n}\n\n/**\n * The grouping key a trace falls under when the list is grouped by\n * session. Traces without a `sessionId` collapse into one synthetic\n * \"(no session)\" bucket so they remain visible.\n */\nexport const NO_SESSION_KEY = \"(no session)\";\n\n/** One session bucket — its key plus the traces under it, order preserved. */\nexport type SessionGroup = {\n /** The `sessionId`, or {@link NO_SESSION_KEY} for sessionless traces. */\n sessionId: string;\n /** Traces in this group, in the order they appeared in the input. */\n traces: Trace[];\n};\n\n/**\n * Group an (already filtered, newest-first) trace list by session id,\n * preserving first-seen order of both the groups and the traces within\n * each. Sessionless traces bucket under {@link NO_SESSION_KEY}.\n */\nexport function groupBySession(traces: Trace[]): SessionGroup[] {\n const order: string[] = [];\n const byKey = new Map<string, Trace[]>();\n\n for (const trace of traces) {\n const key = trace.sessionId ?? NO_SESSION_KEY;\n let bucket = byKey.get(key);\n\n if (bucket === undefined) {\n bucket = [];\n byKey.set(key, bucket);\n order.push(key);\n }\n\n bucket.push(trace);\n }\n\n return order.map((sessionId) => ({ sessionId, traces: byKey.get(sessionId) ?? [] }));\n}\n\n/**\n * The grouping key a trace falls under when the list is grouped by prompt\n * version. Runs that resolved no named prompt collapse into one synthetic\n * \"(no prompt)\" bucket so they remain visible.\n */\nexport const NO_PROMPT_KEY = \"(no prompt)\";\n\n/** One prompt-version bucket — its `name@version` key plus its traces. */\nexport type PromptGroup = {\n /** The `name@version` key, or {@link NO_PROMPT_KEY} for unlinked traces. */\n promptKey: string;\n /** Traces in this group, in the order they appeared in the input. */\n traces: Trace[];\n};\n\n/**\n * Group an (already filtered, newest-first) trace list by prompt version —\n * the `name@version` key {@link tracePromptKey} derives from each run's\n * prompt-version-linkage attributes. Preserves first-seen order of both the\n * groups and the traces within each. Runs with no named prompt bucket under\n * {@link NO_PROMPT_KEY}.\n *\n * The dashboard offers this as a second group-by dimension beside session,\n * so a reviewer can see every run of \"support@2\" together and compare its\n * cost / failure rate against \"support@3\".\n */\nexport function groupByPrompt(traces: Trace[]): PromptGroup[] {\n const order: string[] = [];\n const byKey = new Map<string, Trace[]>();\n\n for (const trace of traces) {\n const key = tracePromptKey(trace) ?? NO_PROMPT_KEY;\n let bucket = byKey.get(key);\n\n if (bucket === undefined) {\n bucket = [];\n byKey.set(key, bucket);\n order.push(key);\n }\n\n bucket.push(trace);\n }\n\n return order.map((promptKey) => ({ promptKey, traces: byKey.get(promptKey) ?? [] }));\n}\n\n/**\n * The grouping key a trace falls under when the list is grouped by root\n * type. `root.type` is always present, so this is only a defensive\n * fallback for a malformed trace with no typed root.\n */\nexport const NO_TYPE_KEY = \"(no type)\";\n\n/** One root-type bucket — its type discriminator plus its traces. */\nexport type TypeGroup = {\n /** The root span's `type` (e.g. `agent` / `workflow` / `planner`), or {@link NO_TYPE_KEY}. */\n type: string;\n /** Traces in this group, in the order they appeared in the input. */\n traces: Trace[];\n};\n\n/**\n * Group an (already filtered, newest-first) trace list by root type —\n * the `agent` / `workflow` / `supervisor` / `orchestrator` / `planner` /\n * `tool` / `batch` / `callback` discriminator on each trace's root span.\n * Preserves first-seen order of both the groups and the traces within each.\n *\n * The dashboard offers this as the coarsest group-by dimension beside\n * session and prompt: when a benchmark floods the flat list with hundreds\n * of one primitive (e.g. standalone `agent` runs), this collapses them\n * behind a per-type header so the structural primitives (the lone planner,\n * the team, the supervisors) are one click away instead of a scroll past.\n */\nexport function groupByType(traces: Trace[]): TypeGroup[] {\n const order: string[] = [];\n const byKey = new Map<string, Trace[]>();\n\n for (const trace of traces) {\n const key = trace.root.type || NO_TYPE_KEY;\n let bucket = byKey.get(key);\n\n if (bucket === undefined) {\n bucket = [];\n byKey.set(key, bucket);\n order.push(key);\n }\n\n bucket.push(trace);\n }\n\n return order.map((type) => ({ type, traces: byKey.get(type) ?? [] }));\n}\n\n/** One row of the per-type aggregate-stats panel. */\nexport type TypeStat = {\n /** Root type discriminator, or {@link NO_TYPE_KEY}. */\n type: string;\n /** Number of traces of this type. */\n count: number;\n /** Traces whose status is `failed` / `cancelled`. */\n failed: number;\n /** `failed / count` in `[0, 1]`. */\n failRate: number;\n /** Median (p50) root duration in ms. */\n p50: number;\n /** p95 root duration in ms. */\n p95: number;\n /** Summed `usage.total` tokens across the bucket. */\n tokens: number;\n /** Summed per-trace USD cost across the bucket. */\n cost: number;\n};\n\n/**\n * Nearest-rank percentile of `values` at `p` (0–100). Returns 0 for an\n * empty input and sorts a copy so the caller's array is left untouched.\n */\nexport function percentile(values: number[], p: number): number {\n if (values.length === 0) {\n return 0;\n }\n\n const sorted = values.slice().sort((a, b) => a - b);\n const rank = Math.ceil((p / 100) * sorted.length) - 1;\n const index = Math.min(Math.max(rank, 0), sorted.length - 1);\n\n return sorted[index];\n}\n\n/**\n * One trace's total USD cost: its explicit root-usage cost when priced,\n * else the rollup of its subtree. Mirrors the dashboard's `traceCost` so the\n * stats panel and the trace rows agree to the cent.\n */\nexport function traceCost(trace: Trace): number {\n const own = costSumObj(trace.usage.cost);\n return own > 0 ? own : rollupCost(trace.root);\n}\n\n/**\n * Aggregate an (already filtered) trace list into one {@link TypeStat} row\n * per root type — count, failure rate, p50/p95 latency, total tokens, total\n * cost — preserving first-seen type order. Powers the dashboard's per-type\n * stats panel; computed over the same filtered list the trace view shows, so\n * the panel always reflects the active filters.\n */\nexport function aggregateByType(traces: Trace[]): TypeStat[] {\n const order: string[] = [];\n const byKey = new Map<string, Trace[]>();\n\n for (const trace of traces) {\n const key = trace.root.type || NO_TYPE_KEY;\n let bucket = byKey.get(key);\n\n if (bucket === undefined) {\n bucket = [];\n byKey.set(key, bucket);\n order.push(key);\n }\n\n bucket.push(trace);\n }\n\n return order.map((type) => {\n const bucket = byKey.get(type) ?? [];\n const durations = bucket.map((trace) => trace.duration);\n let failed = 0;\n let tokens = 0;\n let cost = 0;\n\n for (const trace of bucket) {\n if (ERROR_STATUSES.includes(trace.root.status)) {\n failed += 1;\n }\n\n tokens += trace.usage.total ?? 0;\n cost += traceCost(trace);\n }\n\n return {\n type,\n count: bucket.length,\n failed,\n failRate: bucket.length > 0 ? failed / bucket.length : 0,\n p50: percentile(durations, 50),\n p95: percentile(durations, 95),\n tokens,\n cost,\n };\n });\n}\n\n/**\n * Sum every priced lane of a usage cost object into one USD number.\n * Mirrors the dashboard's `costSumObj` so heatmap intensities computed\n * here match what the page renders.\n */\nfunction costSumObj(cost: TraceSpan[\"usage\"][\"cost\"]): number {\n if (cost === undefined) {\n return 0;\n }\n\n return (\n (cost.input ?? 0) +\n (cost.output ?? 0) +\n (cost.cachedInput ?? 0) +\n (cost.cachedOutput ?? 0) +\n (cost.reasoning ?? 0)\n );\n}\n\n/**\n * Rollup-aware subtree cost: a node's own cost when it carries one\n * (it already rolls up its trips), otherwise the sum of its children.\n * Avoids double-counting wrapper nodes (workflow/supervisor roots carry\n * tokens but no cost). Mirrors the dashboard's `rollupCost`.\n */\nexport function rollupCost(span: TraceSpan): number {\n const own = costSumObj(span.usage.cost);\n if (own > 0) {\n return own;\n }\n\n let sum = 0;\n for (const child of span.children) {\n sum += rollupCost(child);\n }\n\n return sum;\n}\n\n/**\n * Heatmap intensity in `[0, 1]` for a node's `rollupCost` relative to the\n * trace's most-expensive node. The dashboard tints each tree node's left\n * accent by this. `max <= 0` (a free trace) yields `0` for every node so\n * the heatmap simply stays cold rather than dividing by zero.\n */\nexport function heatIntensity(nodeCost: number, maxCost: number): number {\n if (maxCost <= 0 || nodeCost <= 0) {\n return 0;\n }\n\n const ratio = nodeCost / maxCost;\n\n return ratio > 1 ? 1 : ratio;\n}\n\n/**\n * The largest single-node {@link rollupCost} anywhere in a span tree —\n * the denominator for {@link heatIntensity}. Walks the whole subtree.\n */\nexport function maxNodeCost(root: TraceSpan): number {\n let max = rollupCost(root);\n\n for (const child of root.children) {\n const childMax = maxNodeCost(child);\n if (childMax > max) {\n max = childMax;\n }\n }\n\n return max;\n}\n"],"mappings":"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"}