@agimon-ai/po-log 0.0.0-alpha.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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -0
  3. package/dist/apiContracts.cjs +164 -0
  4. package/dist/apiContracts.cjs.map +1 -0
  5. package/dist/apiContracts.d.mts +53 -0
  6. package/dist/apiContracts.d.mts.map +1 -0
  7. package/dist/apiContracts.mjs +159 -0
  8. package/dist/apiContracts.mjs.map +1 -0
  9. package/dist/extensions/server.cjs +1034 -0
  10. package/dist/extensions/server.cjs.map +1 -0
  11. package/dist/extensions/server.d.mts +5 -0
  12. package/dist/extensions/server.d.mts.map +1 -0
  13. package/dist/extensions/server.mjs +1006 -0
  14. package/dist/extensions/server.mjs.map +1 -0
  15. package/dist/extensions/web.mjs +1542 -0
  16. package/dist/extensions/web.mjs.map +1 -0
  17. package/dist/index-CTZMUfTZ.d.mts +185 -0
  18. package/dist/index-CTZMUfTZ.d.mts.map +1 -0
  19. package/dist/index.cjs +13 -0
  20. package/dist/index.d.mts +36 -0
  21. package/dist/index.d.mts.map +1 -0
  22. package/dist/index.mjs +2 -0
  23. package/dist/metrics-DywCpDJ_.mjs +508 -0
  24. package/dist/metrics-DywCpDJ_.mjs.map +1 -0
  25. package/dist/metrics-jCZe13Xg.cjs +573 -0
  26. package/dist/metrics-jCZe13Xg.cjs.map +1 -0
  27. package/dist/metrics.cjs +13 -0
  28. package/dist/metrics.d.mts +2 -0
  29. package/dist/metrics.mjs +2 -0
  30. package/dist/metricsSource-DXZLawQu.mjs +130 -0
  31. package/dist/metricsSource-DXZLawQu.mjs.map +1 -0
  32. package/dist/metricsSource-DdwQNzoY.cjs +135 -0
  33. package/dist/metricsSource-DdwQNzoY.cjs.map +1 -0
  34. package/dist/metricsSource-cZnErdv4.d.mts +51 -0
  35. package/dist/metricsSource-cZnErdv4.d.mts.map +1 -0
  36. package/dist/metricsSource.cjs +3 -0
  37. package/dist/metricsSource.d.mts +18 -0
  38. package/dist/metricsSource.d.mts.map +1 -0
  39. package/dist/metricsSource.mjs +2 -0
  40. package/llms.txt +7 -0
  41. package/package.json +140 -0
  42. package/src/prompts/po-debug-agent/SKILL.md +28 -0
@@ -0,0 +1,1542 @@
1
+ import { apiResponse, createApiClient, defineApiRoutes, defineSettingsPanel, defineToolRenderer, defineWebPlugin, sessionApiAddress } from "@agimon-ai/po-core/web";
2
+ import { Button, EmptyState, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, MessageLines, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Spinner, collapseLines, toolTone } from "@agimon-ai/po-web-components";
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+ import { sealedTransport } from "@agimon-ai/po-web-security/browser";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/types/webMetrics.ts
7
+ /**
8
+ * The dimensions the page offers.
9
+ *
10
+ * log-sink-mcp also groups by 'workflow-run', 'workflow-name', 'job' and
11
+ * 'step'. Those are omitted on purpose: no Po package emits the matching
12
+ * attributes today, so offering them would draw an empty chart and read as a
13
+ * bug rather than as a gap.
14
+ */
15
+ const METRICS_DIMENSIONS = [
16
+ "session",
17
+ "agent",
18
+ "model",
19
+ "provider"
20
+ ];
21
+ const METRICS_PERIODS = [
22
+ "day",
23
+ "week",
24
+ "month",
25
+ "all"
26
+ ];
27
+ const METRICS_QUERY_PARAMS = {
28
+ dimension: "dimension",
29
+ period: "period",
30
+ /** Narrows the whole report to one value of the current dimension. */
31
+ focus: "focus"
32
+ };
33
+ function isMetricsUnavailable(response) {
34
+ return "unavailable" in response;
35
+ }
36
+ function isIssuesUnavailable(response) {
37
+ return "unavailable" in response;
38
+ }
39
+ //#endregion
40
+ //#region src/types/apiRoutes.ts
41
+ /**
42
+ * This package's routes, as data.
43
+ *
44
+ * No scope and no base path. The build reads both off
45
+ * `src/extensions/(backend)/api/log/`, so the mount is stated once, by the
46
+ * folder that creates it. They used to be written here, in the contract, and
47
+ * again inside every URL the page built, and the copies were free to disagree.
48
+ *
49
+ * This is the one file the routes, the Hono app and the browser all read, so a
50
+ * path cannot move on one side alone.
51
+ */
52
+ var apiRoutes_default = defineApiRoutes({
53
+ metrics: {
54
+ method: "GET",
55
+ path: "/metrics",
56
+ query: [
57
+ METRICS_QUERY_PARAMS.dimension,
58
+ METRICS_QUERY_PARAMS.period,
59
+ METRICS_QUERY_PARAMS.focus
60
+ ],
61
+ response: apiResponse()
62
+ },
63
+ /**
64
+ * Separate from the report because the hub answers it from a subprocess: the
65
+ * running daemon exposes no issues route, so folding it in would make every
66
+ * refresh wait on the slowest transport.
67
+ */
68
+ issues: {
69
+ method: "GET",
70
+ path: "/issues",
71
+ query: [METRICS_QUERY_PARAMS.focus],
72
+ response: apiResponse()
73
+ }
74
+ });
75
+ const api = createApiClient(apiRoutes_default, {
76
+ scopes: [
77
+ "global",
78
+ "workspace",
79
+ "session"
80
+ ],
81
+ basePath: "log",
82
+ transport: (input, init) => sealedTransport.fetch(input, init),
83
+ sessionAddress: sessionApiAddress
84
+ });
85
+ //#endregion
86
+ //#region src/extensions/(frontend)/setting/_lib/metricsApi.ts
87
+ /**
88
+ * The page's half of this package's metrics API. The only place the cockpit
89
+ * talks to the hub for metrics, so if the transport ever changes, it changes
90
+ * here alone.
91
+ *
92
+ * These are adapters now, not a transport. The generated client owns the URL
93
+ * and the sealed transport with it, so nothing here spells a route or reaches
94
+ * for `fetch`; a plugin calling `fetch` directly would send plaintext to the
95
+ * tunnel's relay. What stays is this package's own vocabulary: which of the
96
+ * empty states a reader is shown, and which answers are states rather than
97
+ * failures.
98
+ */
99
+ const UNREACHABLE = "The cockpit hub is unreachable.";
100
+ /**
101
+ * A hub with no package APIs mounted serves the SPA shell for this route, so
102
+ * the answer is a 200 of HTML rather than an error status. The client parses no
103
+ * JSON out of it and reports no body at all, which is read here as an
104
+ * uninstalled feature, because that is what it is.
105
+ */
106
+ const NO_API_DETAIL = "This cockpit is running a bundle without the log package API, so there are no metrics to read.";
107
+ const NO_API = {
108
+ unavailable: "no-api",
109
+ detail: NO_API_DETAIL
110
+ };
111
+ function isRecord(value) {
112
+ return typeof value === "object" && value !== null && !Array.isArray(value);
113
+ }
114
+ /**
115
+ * What the hub's answer means, before either route reads its own shape out of
116
+ * it.
117
+ *
118
+ * `status: 0` is the transport never answering, which covers both a dead hub
119
+ * and a caller that aborted. The client cannot tell those apart, so the signal
120
+ * is asked: an abort is the caller replacing this request, and reporting it
121
+ * would overwrite whatever the replacement renders.
122
+ */
123
+ function outcomeOf(result, signal) {
124
+ if (result.status === 0) return { error: signal?.aborted === true ? "" : UNREACHABLE };
125
+ const status = `The hub answered ${String(result.status)}.`;
126
+ if (result.data === void 0) return result.ok ? { noApi: true } : { error: status };
127
+ if (!isRecord(result.data)) return { error: "The hub returned a response the cockpit could not read." };
128
+ if (!result.ok) return { error: result.error === "" ? status : result.error };
129
+ return { body: result.data };
130
+ }
131
+ /**
132
+ * Reads one report.
133
+ *
134
+ * A missing sink is not an error here: the route answers it as an
135
+ * `unavailable` body, so the page can name which of the empty states it is in
136
+ * rather than showing one generic failure.
137
+ */
138
+ async function fetchMetrics(dimension, period, focus, signal) {
139
+ const outcome = outcomeOf(await api.global.metrics({
140
+ query: {
141
+ [METRICS_QUERY_PARAMS.dimension]: dimension,
142
+ [METRICS_QUERY_PARAMS.period]: period,
143
+ [METRICS_QUERY_PARAMS.focus]: focus === "" ? void 0 : focus
144
+ },
145
+ ...signal === void 0 ? {} : { signal }
146
+ }), signal);
147
+ if ("error" in outcome) return { error: outcome.error };
148
+ if ("noApi" in outcome) return { report: NO_API };
149
+ const report = outcome.body;
150
+ if (!isMetricsUnavailable(report) && !Array.isArray(report.groups)) return { error: "The hub returned a report the cockpit could not read." };
151
+ return { report };
152
+ }
153
+ /**
154
+ * Reads the detail behind the issue count.
155
+ *
156
+ * Separate call because the hub answers it from a subprocess: folding it into
157
+ * the report would make every refresh wait on the slowest transport.
158
+ */
159
+ async function fetchIssues(focus, signal, window = {}) {
160
+ const outcome = outcomeOf(await api.global.issues({
161
+ query: {
162
+ [METRICS_QUERY_PARAMS.focus]: focus === "" ? void 0 : focus,
163
+ ...window
164
+ },
165
+ ...signal === void 0 ? {} : { signal }
166
+ }), signal);
167
+ if ("error" in outcome) return { error: outcome.error };
168
+ if ("noApi" in outcome) return { issues: {
169
+ unavailable: "no-api",
170
+ detail: NO_API_DETAIL
171
+ } };
172
+ return { issues: outcome.body };
173
+ }
174
+ //#endregion
175
+ //#region src/extensions/(frontend)/setting/_lib/chartScale.ts
176
+ /**
177
+ * The arithmetic behind the charts, kept out of the components so it can be
178
+ * tested as plain functions rather than through a render.
179
+ *
180
+ * There is no charting library in the cockpit bundle and the plugin import
181
+ * allowlist would not admit one, so these are the primitives the SVG is drawn
182
+ * from. They are deliberately small: a bar length and a tick set, nothing that
183
+ * pretends to be a plotting engine.
184
+ */
185
+ /** Bar length as a fraction of the track, guarding the all-zero series. */
186
+ function barFraction(value, max) {
187
+ if (!Number.isFinite(value) || value <= 0) return 0;
188
+ if (!Number.isFinite(max) || max <= 0) return 0;
189
+ return Math.min(1, value / max);
190
+ }
191
+ /** The largest value in a series, or 0 for an empty one. */
192
+ function seriesMax(values) {
193
+ return values.reduce((highest, value) => Number.isFinite(value) && value > highest ? value : highest, 0);
194
+ }
195
+ /**
196
+ * Compact token counts. Charts label axes and bars in a few characters, and a
197
+ * raw nine-digit total makes every row the same illegible width.
198
+ */
199
+ function formatTokens(value) {
200
+ if (!Number.isFinite(value)) return "Not reported";
201
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
202
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
203
+ return String(Math.round(value));
204
+ }
205
+ //#endregion
206
+ //#region src/extensions/(frontend)/setting/_components/charts/GroupBars.tsx
207
+ const PREVIEW_GROUPS = 5;
208
+ /** Rank recorded consumers; an unknown attribution is not a selectable model. */
209
+ function GroupBars({ groups, totalTokens, focus, onFocus }) {
210
+ const [expanded, setExpanded] = useState(false);
211
+ const sorted = [...groups].sort((a, b) => b.totalTokens - a.totalTokens || a.key.localeCompare(b.key));
212
+ const rows = expanded ? sorted : sorted.slice(0, PREVIEW_GROUPS);
213
+ const max = totalTokens ?? seriesMax(groups.map((group) => group.totalTokens));
214
+ return /* @__PURE__ */ jsxs("div", {
215
+ className: "flex flex-col gap-3",
216
+ children: [
217
+ groups.length === 0 ? /* @__PURE__ */ jsx("p", {
218
+ className: "rounded-md border border-dashed border-doom-border p-5 text-sm text-doom-dim",
219
+ children: "No grouped usage was recorded for this selection."
220
+ }) : null,
221
+ /* @__PURE__ */ jsx("ul", {
222
+ className: "flex flex-col gap-1",
223
+ "data-testid": "metrics-group-bars",
224
+ children: rows.map((group) => {
225
+ const selected = focus === group.key;
226
+ const unknown = group.key === "unknown" || group.key === "";
227
+ const canFocus = onFocus !== void 0 && !unknown;
228
+ const percentage = barFraction(group.totalTokens, max) * 100;
229
+ const row = /* @__PURE__ */ jsxs(Fragment, { children: [
230
+ /* @__PURE__ */ jsxs("span", {
231
+ className: "flex items-start justify-between gap-3",
232
+ children: [/* @__PURE__ */ jsx("span", {
233
+ className: "min-w-0 break-all text-left font-medium text-doom-hi",
234
+ title: group.key,
235
+ children: unknown ? "Unattributed" : group.key
236
+ }), /* @__PURE__ */ jsx("span", {
237
+ className: "shrink-0 text-right font-semibold tabular-nums text-doom-hi",
238
+ title: String(group.totalTokens),
239
+ children: formatTokens(group.totalTokens)
240
+ })]
241
+ }),
242
+ /* @__PURE__ */ jsx("span", {
243
+ "aria-hidden": "true",
244
+ className: "mt-2 block h-1.5 overflow-hidden rounded-full bg-doom-border",
245
+ children: /* @__PURE__ */ jsx("span", {
246
+ className: `block h-full rounded-full ${unknown ? "bg-doom-yellow" : "bg-doom-blue"}`,
247
+ style: { width: `${percentage}%` }
248
+ })
249
+ }),
250
+ /* @__PURE__ */ jsxs("span", {
251
+ className: "mt-2 flex flex-wrap items-center justify-between gap-2 text-sm text-doom-dim",
252
+ children: [/* @__PURE__ */ jsx("span", { children: totalTokens !== void 0 && max > 0 ? `${percentage.toFixed(1)}% of total` : "Relative token volume" }), /* @__PURE__ */ jsxs("span", {
253
+ className: group.issueCount > 0 || group.failed ? "text-doom-yellow" : "text-doom-dim",
254
+ children: [
255
+ group.failed ? "Failed · " : "",
256
+ formatTokens(group.issueCount),
257
+ " ",
258
+ group.issueCount === 1 ? "issue" : "issues"
259
+ ]
260
+ })]
261
+ })
262
+ ] });
263
+ return /* @__PURE__ */ jsx("li", { children: canFocus ? /* @__PURE__ */ jsx("button", {
264
+ type: "button",
265
+ onClick: () => onFocus?.(selected ? "" : group.key),
266
+ "aria-pressed": selected,
267
+ "aria-label": `Filter to ${group.key}`,
268
+ "data-testid": `metrics-group-${group.key}`,
269
+ className: `block w-full rounded-md p-3 text-base hover:bg-doom-tint-blue focus-visible:outline focus-visible:outline-1 focus-visible:outline-doom-blue ${selected ? "bg-doom-tint-blue" : ""}`,
270
+ children: row
271
+ }) : /* @__PURE__ */ jsx("div", {
272
+ className: "rounded-md p-3 text-base",
273
+ children: row
274
+ }) }, group.key);
275
+ })
276
+ }),
277
+ groups.length > PREVIEW_GROUPS ? /* @__PURE__ */ jsx(Button, {
278
+ variant: "ghost",
279
+ size: "sm",
280
+ onClick: () => setExpanded(!expanded),
281
+ "aria-expanded": expanded,
282
+ children: expanded ? "Show top 5" : `Show all ${groups.length} returned groups`
283
+ }) : null
284
+ ]
285
+ });
286
+ }
287
+ //#endregion
288
+ //#region src/extensions/(frontend)/setting/_components/charts/TimelineChart.tsx
289
+ const SERIES = [
290
+ {
291
+ key: "totalTokens",
292
+ label: "Total"
293
+ },
294
+ {
295
+ key: "inputTokens",
296
+ label: "Input"
297
+ },
298
+ {
299
+ key: "outputTokens",
300
+ label: "Output"
301
+ }
302
+ ];
303
+ const PLOT_WIDTH = 600;
304
+ const PLOT_HEIGHT = 160;
305
+ const MAX_COLUMN_WIDTH = 48;
306
+ /** Plot the sink's buckets without filling missing history or stacking counters. */
307
+ function TimelineChart({ buckets, bucketUnit, onShorterPeriod }) {
308
+ const [series, setSeries] = useState("totalTokens");
309
+ const [activeLabel, setActiveLabel] = useState("");
310
+ const active = buckets.find((bucket) => bucket.label === activeLabel) ?? buckets.at(-1);
311
+ const max = seriesMax(buckets.map((bucket) => bucket[series]));
312
+ const unit = bucketUnit ? `${bucketUnit} ` : "";
313
+ const slot = PLOT_WIDTH / Math.max(1, buckets.length);
314
+ const width = Math.min(MAX_COLUMN_WIDTH, slot * .65);
315
+ if (buckets.length === 0) return /* @__PURE__ */ jsx("p", {
316
+ className: "rounded-md border border-dashed border-doom-border p-5 text-sm text-doom-dim",
317
+ "data-testid": "metrics-timeline-chart",
318
+ children: "No timeline buckets were recorded for this selection."
319
+ });
320
+ if (buckets.length === 1 && active !== void 0) return /* @__PURE__ */ jsx("div", {
321
+ className: "flex flex-col gap-4",
322
+ "data-testid": "metrics-timeline-chart",
323
+ children: /* @__PURE__ */ jsxs("div", {
324
+ className: "rounded-md border border-doom-border bg-doom-deep p-5",
325
+ children: [
326
+ /* @__PURE__ */ jsx("p", {
327
+ className: "text-sm text-doom-dim",
328
+ children: active.label
329
+ }),
330
+ /* @__PURE__ */ jsxs("p", {
331
+ className: "mt-2 text-lg font-semibold tabular-nums text-doom-blue",
332
+ title: String(active.totalTokens),
333
+ children: [formatTokens(active.totalTokens), " tokens"]
334
+ }),
335
+ /* @__PURE__ */ jsxs("p", {
336
+ className: "mt-3 text-sm leading-relaxed text-doom-dim",
337
+ children: [
338
+ "Only one ",
339
+ unit,
340
+ "bucket was recorded. There is not enough history to show a trend."
341
+ ]
342
+ }),
343
+ /* @__PURE__ */ jsx("p", {
344
+ className: "mt-2 text-sm text-doom-dim",
345
+ children: "For more detail, pick a shorter period when one is available."
346
+ }),
347
+ onShorterPeriod === void 0 ? null : /* @__PURE__ */ jsx(Button, {
348
+ variant: "ghost",
349
+ size: "sm",
350
+ className: "mt-3",
351
+ onClick: onShorterPeriod,
352
+ children: "View hourly detail"
353
+ })
354
+ ]
355
+ })
356
+ });
357
+ return /* @__PURE__ */ jsxs("div", {
358
+ className: "flex flex-col gap-3",
359
+ "data-testid": "metrics-timeline-chart",
360
+ children: [
361
+ /* @__PURE__ */ jsxs("div", {
362
+ className: "flex flex-wrap items-center justify-between gap-2",
363
+ children: [/* @__PURE__ */ jsx("div", {
364
+ className: "flex gap-1",
365
+ role: "group",
366
+ "aria-label": "Timeline token counter",
367
+ children: SERIES.map((item) => /* @__PURE__ */ jsx(Button, {
368
+ size: "sm",
369
+ variant: series === item.key ? "primary" : "ghost",
370
+ "aria-pressed": series === item.key,
371
+ onClick: () => setSeries(item.key),
372
+ children: item.label
373
+ }, item.key))
374
+ }), /* @__PURE__ */ jsxs("span", {
375
+ className: "text-sm text-doom-dim",
376
+ children: [
377
+ buckets.length,
378
+ " recorded ",
379
+ unit,
380
+ "buckets"
381
+ ]
382
+ })]
383
+ }),
384
+ /* @__PURE__ */ jsxs("div", {
385
+ className: "flex gap-3",
386
+ children: [/* @__PURE__ */ jsxs("div", {
387
+ "aria-hidden": "true",
388
+ className: "flex w-12 shrink-0 flex-col justify-between text-right text-sm tabular-nums text-doom-dim",
389
+ children: [
390
+ /* @__PURE__ */ jsx("span", { children: formatTokens(max) }),
391
+ /* @__PURE__ */ jsx("span", { children: formatTokens(max / 2) }),
392
+ /* @__PURE__ */ jsx("span", { children: "0" })
393
+ ]
394
+ }), /* @__PURE__ */ jsxs("svg", {
395
+ viewBox: `0 0 ${PLOT_WIDTH} ${PLOT_HEIGHT}`,
396
+ preserveAspectRatio: "none",
397
+ className: "h-40 min-w-0 flex-1 overflow-visible",
398
+ role: "group",
399
+ "aria-label": `tokens per bucket, peak ${formatTokens(max)}`,
400
+ children: [[
401
+ 0,
402
+ PLOT_HEIGHT / 2,
403
+ PLOT_HEIGHT
404
+ ].map((y) => /* @__PURE__ */ jsx("line", {
405
+ x1: "0",
406
+ x2: PLOT_WIDTH,
407
+ y1: y,
408
+ y2: y,
409
+ className: "text-doom-border",
410
+ stroke: "currentColor",
411
+ vectorEffect: "non-scaling-stroke"
412
+ }, y)), buckets.map((bucket, index) => {
413
+ const height = barFraction(bucket[series], max) * PLOT_HEIGHT;
414
+ const label = `${bucket.label}: ${formatTokens(bucket[series])} ${SERIES.find((item) => item.key === series)?.label.toLowerCase()} tokens`;
415
+ return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("rect", {
416
+ x: index * slot + (slot - width) / 2,
417
+ y: PLOT_HEIGHT - height,
418
+ width,
419
+ height,
420
+ rx: "2",
421
+ fill: "currentColor",
422
+ className: active?.label === bucket.label ? "text-doom-cyan" : "text-doom-blue"
423
+ }), /* @__PURE__ */ jsx("rect", {
424
+ x: index * slot,
425
+ y: "0",
426
+ width: slot,
427
+ height: PLOT_HEIGHT,
428
+ fill: "transparent",
429
+ tabIndex: 0,
430
+ role: "button",
431
+ "aria-label": label,
432
+ "aria-pressed": active?.label === bucket.label,
433
+ onMouseEnter: () => setActiveLabel(bucket.label),
434
+ onFocus: () => setActiveLabel(bucket.label),
435
+ onClick: () => setActiveLabel(bucket.label),
436
+ onKeyDown: (event) => {
437
+ if (event.key === "Enter" || event.key === " ") {
438
+ event.preventDefault();
439
+ setActiveLabel(bucket.label);
440
+ }
441
+ },
442
+ className: "cursor-pointer focus-visible:outline focus-visible:outline-1 focus-visible:outline-doom-cyan",
443
+ children: /* @__PURE__ */ jsx("title", { children: label })
444
+ })] }, bucket.label);
445
+ })]
446
+ })]
447
+ }),
448
+ /* @__PURE__ */ jsxs("div", {
449
+ className: "ml-15 flex justify-between gap-3 text-sm text-doom-dim",
450
+ children: [/* @__PURE__ */ jsx("span", { children: buckets[0]?.label }), /* @__PURE__ */ jsx("span", {
451
+ className: "text-right",
452
+ children: buckets.at(-1)?.label
453
+ })]
454
+ }),
455
+ /* @__PURE__ */ jsxs("div", {
456
+ className: "flex flex-wrap justify-between gap-2 rounded-md bg-doom-deep px-3 py-2 text-sm tabular-nums",
457
+ "aria-live": "polite",
458
+ "aria-atomic": "true",
459
+ children: [/* @__PURE__ */ jsx("span", {
460
+ className: "text-doom-dim",
461
+ children: active?.label
462
+ }), /* @__PURE__ */ jsxs("span", {
463
+ className: "text-doom-hi",
464
+ children: [
465
+ formatTokens(active?.[series] ?? NaN),
466
+ " ",
467
+ SERIES.find((item) => item.key === series)?.label.toLowerCase(),
468
+ " tokens"
469
+ ]
470
+ })]
471
+ }),
472
+ max === 0 ? /* @__PURE__ */ jsxs("p", {
473
+ className: "text-sm text-doom-dim",
474
+ children: [
475
+ "Recorded buckets contain zero ",
476
+ SERIES.find((item) => item.key === series)?.label.toLowerCase(),
477
+ " tokens."
478
+ ]
479
+ }) : null,
480
+ /* @__PURE__ */ jsxs("details", {
481
+ className: "text-sm text-doom-dim",
482
+ children: [/* @__PURE__ */ jsx("summary", {
483
+ className: "cursor-pointer rounded-sm focus-visible:outline focus-visible:outline-1 focus-visible:outline-doom-blue",
484
+ children: "View exact bucket values"
485
+ }), /* @__PURE__ */ jsx("div", {
486
+ className: "mt-3 overflow-x-auto",
487
+ children: /* @__PURE__ */ jsxs("table", {
488
+ className: "w-full text-right tabular-nums",
489
+ children: [
490
+ /* @__PURE__ */ jsx("caption", {
491
+ className: "sr-only",
492
+ children: "Independent token counters by recorded bucket"
493
+ }),
494
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
495
+ /* @__PURE__ */ jsx("th", {
496
+ scope: "col",
497
+ className: "py-2 text-left",
498
+ children: "Bucket"
499
+ }),
500
+ /* @__PURE__ */ jsx("th", {
501
+ scope: "col",
502
+ className: "pl-3",
503
+ children: "Total"
504
+ }),
505
+ /* @__PURE__ */ jsx("th", {
506
+ scope: "col",
507
+ className: "pl-3",
508
+ children: "Input"
509
+ }),
510
+ /* @__PURE__ */ jsx("th", {
511
+ scope: "col",
512
+ className: "pl-3",
513
+ children: "Output"
514
+ })
515
+ ] }) }),
516
+ /* @__PURE__ */ jsx("tbody", { children: buckets.map((bucket) => /* @__PURE__ */ jsxs("tr", {
517
+ className: "border-t border-doom-border",
518
+ children: [/* @__PURE__ */ jsx("th", {
519
+ scope: "row",
520
+ className: "py-2 text-left font-normal",
521
+ children: bucket.label
522
+ }), SERIES.map((item) => /* @__PURE__ */ jsx("td", {
523
+ className: "pl-3",
524
+ children: Number.isFinite(bucket[item.key]) ? bucket[item.key].toLocaleString("en-US") : "Not reported"
525
+ }, item.key))]
526
+ }, bucket.label)) })
527
+ ]
528
+ })
529
+ })]
530
+ })
531
+ ]
532
+ });
533
+ }
534
+ //#endregion
535
+ //#region src/types/issueGrouping.ts
536
+ /**
537
+ * A key that separates problems a reader would act on separately.
538
+ *
539
+ * The sink's fingerprint is the primary key, but it can be empty, and several
540
+ * distinct bash failures share 'tool_failure|pi|bash||pi.tool_result' because
541
+ * the record name is all they carry. Folding the detail in keeps two different
542
+ * failures apart while still merging repeats of the same one.
543
+ */
544
+ function groupingKey(sample) {
545
+ return `${sample.fingerprint === "" ? `${sample.category}|${sample.message}` : sample.fingerprint}|${sample.detail}`;
546
+ }
547
+ /** Incidents merged by problem and ranked by how often each happened. */
548
+ function groupIssues(samples) {
549
+ const groups = /* @__PURE__ */ new Map();
550
+ for (const sample of samples) {
551
+ const key = groupingKey(sample);
552
+ const existing = groups.get(key);
553
+ if (existing === void 0) {
554
+ groups.set(key, {
555
+ key,
556
+ occurrences: sample.occurrenceCount,
557
+ category: sample.category,
558
+ detail: sample.detail,
559
+ tool: sample.tool,
560
+ errorType: sample.errorType,
561
+ agentName: sample.agentName,
562
+ model: sample.model,
563
+ statusCode: sample.statusCode,
564
+ lastSeen: sample.timestamp,
565
+ members: [sample]
566
+ });
567
+ continue;
568
+ }
569
+ existing.occurrences += sample.occurrenceCount;
570
+ existing.members.push(sample);
571
+ if (sample.timestamp > existing.lastSeen) existing.lastSeen = sample.timestamp;
572
+ existing.tool ??= sample.tool;
573
+ existing.errorType ??= sample.errorType;
574
+ existing.agentName ??= sample.agentName;
575
+ existing.model ??= sample.model;
576
+ existing.statusCode ??= sample.statusCode;
577
+ }
578
+ return [...groups.values()].sort((left, right) => right.occurrences - left.occurrences || left.detail.localeCompare(right.detail));
579
+ }
580
+ //#endregion
581
+ //#region src/extensions/(frontend)/setting/_components/IssuesDetail.tsx
582
+ /**
583
+ * What is actually going wrong, ranked by how often.
584
+ *
585
+ * A count of 69 issues tells nobody what to change. The same 69 collapsed into
586
+ * "this spawn failed 20 times, this hook failed 3" is a work list, so the
587
+ * ranked bar is the primary view here and the totals are context beside it.
588
+ *
589
+ * Each bar expands to the incidents behind it, because the row states the
590
+ * problem and the reader still needs the session, model, and timestamps to go
591
+ * and look at one.
592
+ */
593
+ function countRows(counts) {
594
+ return Object.entries(counts).sort(([, left], [, right]) => right - left);
595
+ }
596
+ /** A short, human label for a problem; the detail can be a whole command line. */
597
+ function titleOf(group) {
598
+ if (group.errorType !== null) return group.errorType;
599
+ if (group.tool !== null) return `${group.tool} failed`;
600
+ return group.category;
601
+ }
602
+ function IssueRow({ group, max, tools }) {
603
+ const [open, setOpen] = useState(false);
604
+ const width = `${(barFraction(group.occurrences, max) * 100).toFixed(2)}%`;
605
+ const calls = tools.find((tool) => tool.name === group.tool)?.calls;
606
+ return /* @__PURE__ */ jsxs("li", {
607
+ className: "flex flex-col rounded-md border border-doom-border",
608
+ children: [/* @__PURE__ */ jsxs("div", {
609
+ className: "relative",
610
+ children: [/* @__PURE__ */ jsx("span", {
611
+ "aria-hidden": "true",
612
+ className: "absolute bottom-0 left-0 h-1 rounded-xs bg-doom-yellow/40",
613
+ style: { width }
614
+ }), /* @__PURE__ */ jsxs("button", {
615
+ type: "button",
616
+ onClick: () => setOpen(!open),
617
+ "aria-expanded": open,
618
+ "data-testid": `metrics-issue-${group.key}`,
619
+ className: "relative flex min-h-10 w-full flex-wrap items-start gap-3 rounded-md p-3 text-left text-base hover:bg-doom-tint-blue focus-visible:outline focus-visible:outline-1 focus-visible:outline-doom-blue",
620
+ children: [
621
+ /* @__PURE__ */ jsx("span", {
622
+ className: "w-8 shrink-0 text-right font-semibold text-doom-yellow",
623
+ children: group.occurrences
624
+ }),
625
+ /* @__PURE__ */ jsx("span", {
626
+ className: "w-24 shrink-0 break-words font-medium text-doom-hi",
627
+ children: titleOf(group)
628
+ }),
629
+ /* @__PURE__ */ jsx("span", {
630
+ className: "min-w-40 flex-1 break-words text-doom-dim",
631
+ children: group.detail
632
+ }),
633
+ calls === void 0 ? null : /* @__PURE__ */ jsxs("span", {
634
+ className: "shrink-0 text-doom-dim",
635
+ children: [calls, " token samples"]
636
+ })
637
+ ]
638
+ })]
639
+ }), open ? /* @__PURE__ */ jsxs("div", {
640
+ className: "flex flex-col gap-1 px-3 py-3 text-base",
641
+ "data-testid": `metrics-issue-body-${group.key}`,
642
+ children: [
643
+ /* @__PURE__ */ jsxs("div", {
644
+ className: "flex flex-wrap gap-x-3 gap-y-1 text-sm text-doom-dim",
645
+ children: [
646
+ /* @__PURE__ */ jsxs("span", { children: ["category ", group.category] }),
647
+ group.tool === null ? null : /* @__PURE__ */ jsxs("span", { children: ["tool ", group.tool] }),
648
+ group.errorType === null ? null : /* @__PURE__ */ jsxs("span", { children: ["error ", group.errorType] }),
649
+ group.statusCode === null ? null : /* @__PURE__ */ jsxs("span", { children: ["status ", group.statusCode] }),
650
+ group.agentName === null ? null : /* @__PURE__ */ jsxs("span", { children: ["agent ", group.agentName] }),
651
+ group.model === null ? null : /* @__PURE__ */ jsxs("span", { children: ["model ", group.model] }),
652
+ /* @__PURE__ */ jsxs("span", { children: ["last seen ", group.lastSeen] })
653
+ ]
654
+ }),
655
+ /* @__PURE__ */ jsx("span", {
656
+ className: "break-words text-doom-dim",
657
+ children: group.detail
658
+ }),
659
+ /* @__PURE__ */ jsx("ul", {
660
+ className: "flex flex-col gap-1 text-sm text-doom-dim",
661
+ children: group.members.map((member, index) => /* @__PURE__ */ jsxs("li", {
662
+ className: "flex flex-wrap gap-x-2",
663
+ children: [
664
+ /* @__PURE__ */ jsx("span", { children: member.timestamp }),
665
+ /* @__PURE__ */ jsx("span", { children: member.level }),
666
+ /* @__PURE__ */ jsxs("span", { children: [member.occurrenceCount, "×"] }),
667
+ /* @__PURE__ */ jsx("span", {
668
+ className: "min-w-0 truncate",
669
+ children: member.message
670
+ })
671
+ ]
672
+ }, `${member.timestamp}-${String(index)}`))
673
+ })
674
+ ]
675
+ }) : null]
676
+ });
677
+ }
678
+ function IssuesDetail({ view, tools, summaryCount }) {
679
+ const groups = groupIssues(view.samples);
680
+ const max = groups[0]?.occurrences ?? 0;
681
+ const callsByTool = new Map(tools.map((tool) => [tool.name, tool.calls]));
682
+ return /* @__PURE__ */ jsxs("div", {
683
+ className: "flex flex-col gap-3",
684
+ children: [
685
+ summaryCount !== void 0 && summaryCount !== view.totalIssues ? /* @__PURE__ */ jsxs("p", {
686
+ className: "rounded-md border border-doom-edge-yellow bg-doom-tint-yellow p-3 text-sm leading-relaxed text-doom-yellow",
687
+ role: "status",
688
+ "data-testid": "metrics-issue-count-mismatch",
689
+ children: [
690
+ "The summary reports ",
691
+ summaryCount,
692
+ " ",
693
+ summaryCount === 1 ? "issue" : "issues",
694
+ "; incident analysis returned",
695
+ " ",
696
+ view.totalIssues,
697
+ ". The counts differ, so these details should not be treated as a complete explanation of the summary."
698
+ ]
699
+ }) : null,
700
+ /* @__PURE__ */ jsxs("span", {
701
+ className: "text-sm text-doom-dim",
702
+ children: [
703
+ /* @__PURE__ */ jsx("span", {
704
+ className: "text-doom-hi",
705
+ children: view.totalIssues
706
+ }),
707
+ " occurrences of",
708
+ " ",
709
+ /* @__PURE__ */ jsx("span", {
710
+ className: "text-doom-hi",
711
+ children: groups.length
712
+ }),
713
+ " distinct problems in the returned samples, most frequent first"
714
+ ]
715
+ }),
716
+ groups.length === 0 ? /* @__PURE__ */ jsx("p", {
717
+ className: "rounded-md border border-dashed border-doom-border p-4 text-sm text-doom-dim",
718
+ children: view.totalIssues === 0 ? "No issues were detected in the returned analysis. This does not establish coverage of unrecorded activity." : "The analysis counted issues but returned no incident samples."
719
+ }) : /* @__PURE__ */ jsx("ul", {
720
+ className: "flex flex-col gap-2",
721
+ "data-testid": "metrics-issue-groups",
722
+ children: groups.map((group) => /* @__PURE__ */ jsx(IssueRow, {
723
+ group,
724
+ max,
725
+ tools
726
+ }, group.key))
727
+ }),
728
+ Object.keys(view.byTool).length === 0 ? null : /* @__PURE__ */ jsxs("div", {
729
+ className: "flex flex-col gap-2 overflow-x-auto",
730
+ children: [/* @__PURE__ */ jsx("h4", {
731
+ className: "text-base font-semibold text-doom-hi",
732
+ children: "Failures by tool"
733
+ }), /* @__PURE__ */ jsxs("table", {
734
+ className: "w-full text-sm",
735
+ "data-testid": "metrics-issues-tools",
736
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
737
+ className: "text-sm text-doom-dim",
738
+ children: [
739
+ /* @__PURE__ */ jsx("th", {
740
+ scope: "col",
741
+ className: "py-2 text-left font-normal",
742
+ children: "Tool"
743
+ }),
744
+ /* @__PURE__ */ jsx("th", {
745
+ scope: "col",
746
+ className: "text-right font-normal",
747
+ children: "Failures"
748
+ }),
749
+ /* @__PURE__ */ jsx("th", {
750
+ scope: "col",
751
+ className: "text-right font-normal",
752
+ children: "Attribution context"
753
+ })
754
+ ]
755
+ }) }), /* @__PURE__ */ jsx("tbody", { children: countRows(view.byTool).map(([name, failures]) => {
756
+ const calls = callsByTool.get(name);
757
+ return /* @__PURE__ */ jsxs("tr", {
758
+ className: "border-b border-doom-border/40",
759
+ children: [
760
+ /* @__PURE__ */ jsx("td", {
761
+ className: "min-w-0 break-all py-2 text-doom-dim",
762
+ children: name
763
+ }),
764
+ /* @__PURE__ */ jsx("td", {
765
+ className: "w-16 py-2 text-right text-doom-red",
766
+ children: failures
767
+ }),
768
+ /* @__PURE__ */ jsx("td", {
769
+ className: "w-28 py-2 text-right text-doom-dim",
770
+ children: calls === void 0 ? "no token samples" : `${String(calls)} token samples`
771
+ })
772
+ ]
773
+ }, name);
774
+ }) })]
775
+ })]
776
+ }),
777
+ /* @__PURE__ */ jsxs("div", {
778
+ className: "flex flex-wrap gap-x-4 gap-y-1 text-sm text-doom-dim",
779
+ children: [/* @__PURE__ */ jsx("span", {
780
+ className: "text-sm font-semibold text-doom-dim",
781
+ children: "by category"
782
+ }), countRows(view.byCategory).map(([name, count]) => /* @__PURE__ */ jsxs("span", { children: [
783
+ name,
784
+ " ",
785
+ /* @__PURE__ */ jsx("span", {
786
+ className: "text-doom-hi",
787
+ children: count
788
+ })
789
+ ] }, name))]
790
+ })
791
+ ]
792
+ });
793
+ }
794
+ //#endregion
795
+ //#region src/extensions/(frontend)/setting/_components/IssuesSection.tsx
796
+ /** Issue analysis is an explicit read, not another request on every metrics refresh. */
797
+ function IssuesSection({ tools, focus, count, startTime, endTime, broaderScope = false }) {
798
+ const [view, setView] = useState();
799
+ const [message, setMessage] = useState("");
800
+ const [loading, setLoading] = useState(false);
801
+ const [open, setOpen] = useState(false);
802
+ const pending = useRef(void 0);
803
+ const detailsId = useId();
804
+ useEffect(() => {
805
+ pending.current?.abort();
806
+ setView(void 0);
807
+ setMessage("");
808
+ setLoading(false);
809
+ setOpen(false);
810
+ return () => pending.current?.abort();
811
+ }, [
812
+ focus,
813
+ startTime,
814
+ endTime
815
+ ]);
816
+ const load = () => {
817
+ pending.current?.abort();
818
+ const controller = new AbortController();
819
+ pending.current = controller;
820
+ setOpen(true);
821
+ setLoading(true);
822
+ setMessage("");
823
+ setView(void 0);
824
+ fetchIssues(focus, controller.signal, {
825
+ startTime,
826
+ endTime
827
+ }).then((result) => {
828
+ if (controller.signal.aborted) return;
829
+ setLoading(false);
830
+ if ("error" in result) setMessage(result.error || "Issue details could not be loaded.");
831
+ else if (isIssuesUnavailable(result.issues)) setMessage(result.issues.detail);
832
+ else setView(result.issues);
833
+ }).catch((error) => {
834
+ if (controller.signal.aborted) return;
835
+ setLoading(false);
836
+ setMessage(error instanceof Error ? error.message : "Issue details could not be loaded.");
837
+ });
838
+ };
839
+ return /* @__PURE__ */ jsxs("section", {
840
+ className: "flex min-w-0 flex-col gap-3 rounded-lg border border-doom-border bg-doom-panel p-4",
841
+ "data-testid": "metrics-issues",
842
+ children: [
843
+ /* @__PURE__ */ jsxs("div", {
844
+ className: "flex flex-wrap items-center justify-between gap-3",
845
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("h3", {
846
+ className: "text-lg font-semibold text-doom-hi",
847
+ children: ["Issues to investigate", count === void 0 ? "" : ` · ${count} ${count === 1 ? "issue" : "issues"}`]
848
+ }), /* @__PURE__ */ jsx("p", {
849
+ className: "mt-1 text-sm text-doom-dim",
850
+ children: "Inspect recurring failures and the evidence behind them."
851
+ })] }), /* @__PURE__ */ jsxs("div", {
852
+ className: "flex items-center gap-2",
853
+ children: [
854
+ loading ? /* @__PURE__ */ jsx(Spinner, { label: "Reading issue details" }) : null,
855
+ /* @__PURE__ */ jsx(Button, {
856
+ variant: "outline",
857
+ size: "md",
858
+ disabled: loading,
859
+ onClick: load,
860
+ "aria-expanded": open,
861
+ "aria-controls": detailsId,
862
+ "data-testid": open ? "metrics-issues-reload" : "metrics-issues-open",
863
+ children: open ? "Reload details" : "Inspect issues"
864
+ }),
865
+ open ? /* @__PURE__ */ jsx(Button, {
866
+ variant: "ghost",
867
+ size: "md",
868
+ onClick: () => {
869
+ pending.current?.abort();
870
+ setLoading(false);
871
+ setOpen(false);
872
+ },
873
+ "aria-controls": detailsId,
874
+ children: "Hide"
875
+ }) : null
876
+ ]
877
+ })]
878
+ }),
879
+ /* @__PURE__ */ jsxs("p", {
880
+ className: "text-sm leading-relaxed text-doom-dim",
881
+ children: [
882
+ "Details are loaded on demand.",
883
+ " ",
884
+ startTime === void 0 ? endTime === void 0 ? "This hub did not report a time window; details cover all available history." : "Details cover available history up to report time; no start bound was supplied." : "Details use the report’s recorded time window.",
885
+ " ",
886
+ broaderScope ? "LogSink cannot filter issue details by model, provider or agent, so this analysis covers all groups in that window." : focus === void 0 ? "Analysis covers all groups." : "Filtered to this session."
887
+ ]
888
+ }),
889
+ /* @__PURE__ */ jsxs("div", {
890
+ id: detailsId,
891
+ hidden: !open,
892
+ "aria-busy": loading,
893
+ children: [
894
+ message === "" ? null : /* @__PURE__ */ jsx("p", {
895
+ role: "alert",
896
+ className: "rounded-md border border-doom-edge-yellow bg-doom-tint-yellow p-3 text-sm text-doom-yellow",
897
+ "data-testid": "metrics-issues-message",
898
+ children: message
899
+ }),
900
+ loading ? /* @__PURE__ */ jsx("p", {
901
+ role: "status",
902
+ className: "py-3 text-sm text-doom-dim",
903
+ children: "Reading recorded incidents..."
904
+ }) : null,
905
+ view === void 0 ? null : /* @__PURE__ */ jsx(IssuesDetail, {
906
+ view,
907
+ tools: broaderScope ? [] : tools,
908
+ summaryCount: broaderScope ? void 0 : count
909
+ })
910
+ ]
911
+ })
912
+ ]
913
+ });
914
+ }
915
+ //#endregion
916
+ //#region src/extensions/(frontend)/setting/_components/MetricsSummary.tsx
917
+ /** Independent counters, never a stacked breakdown of the provider total. */
918
+ function MetricsSummary({ totals }) {
919
+ const cards = [
920
+ {
921
+ label: "Total tokens",
922
+ value: totals.totalTokens,
923
+ detail: "Provider-reported total",
924
+ color: "text-doom-blue"
925
+ },
926
+ {
927
+ label: "Input tokens",
928
+ value: totals.inputTokens,
929
+ detail: "Reported input counter",
930
+ color: "text-doom-hi"
931
+ },
932
+ {
933
+ label: "Output tokens",
934
+ value: totals.outputTokens,
935
+ detail: "Reported output counter",
936
+ color: "text-doom-hi"
937
+ },
938
+ {
939
+ label: "Recorded issues",
940
+ value: totals.issueCount,
941
+ detail: "Detected in this selection",
942
+ color: totals.issueCount > 0 ? "text-doom-yellow" : "text-doom-hi"
943
+ }
944
+ ];
945
+ return /* @__PURE__ */ jsx("dl", {
946
+ className: "grid grid-cols-2 gap-3 @3xl:grid-cols-4",
947
+ "data-testid": "metrics-summary",
948
+ children: cards.map((card) => /* @__PURE__ */ jsxs("div", {
949
+ className: "min-w-0 rounded-lg border border-doom-border bg-doom-panel p-4",
950
+ children: [
951
+ /* @__PURE__ */ jsx("dt", {
952
+ className: "text-sm text-doom-dim",
953
+ children: card.label
954
+ }),
955
+ /* @__PURE__ */ jsx("dd", {
956
+ className: `mt-2 text-lg font-semibold tabular-nums ${card.color}`,
957
+ title: Number.isFinite(card.value) ? card.value.toLocaleString("en-US") : "Not reported",
958
+ children: formatTokens(card.value)
959
+ }),
960
+ /* @__PURE__ */ jsx("dd", {
961
+ className: "mt-1 text-sm text-doom-dim",
962
+ children: card.detail
963
+ })
964
+ ]
965
+ }, card.label))
966
+ });
967
+ }
968
+ //#endregion
969
+ //#region src/extensions/(frontend)/setting/_components/ToolMetrics.tsx
970
+ /** The sink attributes a whole turn to its tools. These are samples, not bills. */
971
+ function ToolMetrics({ tools }) {
972
+ const [sort, setSort] = useState("p90TotalTokens");
973
+ const rows = [...tools].sort((a, b) => b[sort] - a[sort] || a.name.localeCompare(b.name));
974
+ return /* @__PURE__ */ jsxs("section", {
975
+ className: "flex min-w-0 flex-col gap-4 rounded-lg border border-doom-border bg-doom-panel p-4",
976
+ "aria-label": "Tool token samples",
977
+ children: [
978
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
979
+ className: "text-lg font-semibold text-doom-hi",
980
+ children: "Tool token samples"
981
+ }), /* @__PURE__ */ jsx("p", {
982
+ className: "mt-1 text-sm text-doom-dim",
983
+ children: "Which tools participate in token-heavy turns?"
984
+ })] }),
985
+ rows.length === 0 ? /* @__PURE__ */ jsxs("div", {
986
+ className: "flex flex-1 flex-col justify-center gap-2 rounded-md border border-dashed border-doom-border p-5",
987
+ "data-testid": "metrics-tools-empty",
988
+ children: [/* @__PURE__ */ jsx("p", {
989
+ className: "text-base font-semibold text-doom-hi",
990
+ children: "No token-attributed tool calls"
991
+ }), /* @__PURE__ */ jsx("p", {
992
+ className: "text-sm leading-relaxed text-doom-dim",
993
+ children: "Tool execution logs may exist, but this selection has no samples that connect tools to model usage. This does not mean no tools ran."
994
+ })]
995
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
996
+ className: "flex flex-wrap gap-1",
997
+ role: "group",
998
+ "aria-label": "Rank tool samples by",
999
+ children: [/* @__PURE__ */ jsx(Button, {
1000
+ size: "sm",
1001
+ variant: sort === "p90TotalTokens" ? "primary" : "ghost",
1002
+ "aria-pressed": sort === "p90TotalTokens",
1003
+ onClick: () => setSort("p90TotalTokens"),
1004
+ children: "P90 turn tokens"
1005
+ }), /* @__PURE__ */ jsx(Button, {
1006
+ size: "sm",
1007
+ variant: sort === "calls" ? "primary" : "ghost",
1008
+ "aria-pressed": sort === "calls",
1009
+ onClick: () => setSort("calls"),
1010
+ children: "Sampled calls"
1011
+ })]
1012
+ }), /* @__PURE__ */ jsx("div", {
1013
+ className: "overflow-x-auto",
1014
+ children: /* @__PURE__ */ jsxs("table", {
1015
+ className: "w-full text-base tabular-nums",
1016
+ "data-testid": "metrics-tools",
1017
+ children: [
1018
+ /* @__PURE__ */ jsxs("caption", {
1019
+ className: "sr-only",
1020
+ children: ["Tools ranked by ", sort === "calls" ? "sampled calls" : "90th percentile whole-turn tokens"]
1021
+ }),
1022
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
1023
+ className: "border-b border-doom-border text-sm text-doom-dim",
1024
+ children: [
1025
+ /* @__PURE__ */ jsx("th", {
1026
+ scope: "col",
1027
+ className: "pb-2 text-left font-normal",
1028
+ children: "Tool"
1029
+ }),
1030
+ /* @__PURE__ */ jsx("th", {
1031
+ scope: "col",
1032
+ className: "pb-2 pl-3 text-right font-normal",
1033
+ children: "Sampled calls"
1034
+ }),
1035
+ /* @__PURE__ */ jsx("th", {
1036
+ scope: "col",
1037
+ className: "pb-2 pl-3 text-right font-normal",
1038
+ children: "P90 turn tokens"
1039
+ })
1040
+ ]
1041
+ }) }),
1042
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((tool) => /* @__PURE__ */ jsxs("tr", {
1043
+ className: "border-b border-doom-border/40",
1044
+ children: [
1045
+ /* @__PURE__ */ jsx("th", {
1046
+ scope: "row",
1047
+ className: "max-w-48 break-all py-3 text-left font-normal text-doom-hi",
1048
+ children: tool.name
1049
+ }),
1050
+ /* @__PURE__ */ jsx("td", {
1051
+ className: "py-3 pl-3 text-right text-doom-dim",
1052
+ children: formatTokens(tool.calls)
1053
+ }),
1054
+ /* @__PURE__ */ jsx("td", {
1055
+ className: "py-3 pl-3 text-right text-doom-blue",
1056
+ title: String(tool.p90TotalTokens),
1057
+ children: formatTokens(tool.p90TotalTokens)
1058
+ })
1059
+ ]
1060
+ }, tool.name)) })
1061
+ ]
1062
+ })
1063
+ })] }),
1064
+ /* @__PURE__ */ jsxs("details", {
1065
+ className: "text-sm leading-relaxed text-doom-dim",
1066
+ children: [/* @__PURE__ */ jsx("summary", {
1067
+ className: "cursor-pointer rounded-sm focus-visible:outline focus-visible:outline-1 focus-visible:outline-doom-blue",
1068
+ children: "How these samples are counted"
1069
+ }), /* @__PURE__ */ jsx("p", {
1070
+ className: "mt-2",
1071
+ children: "This view includes only calls with recorded token attribution; external MCP calls remain in the logs. P90 is the 90th percentile of the whole turn, not each tool's own consumption. Several tools may share a turn, so do not add these values together."
1072
+ })]
1073
+ })
1074
+ ]
1075
+ });
1076
+ }
1077
+ //#endregion
1078
+ //#region src/extensions/(frontend)/setting/_components/MetricsReportView.tsx
1079
+ const DIMENSION_LABELS = {
1080
+ session: "session",
1081
+ agent: "agent",
1082
+ model: "model",
1083
+ provider: "provider"
1084
+ };
1085
+ const DIMENSION_NOTES = { session: "Session ids are hashed before export. Select a row to filter usage, not to open a session." };
1086
+ /** The same data-only dashboard is rendered by the stories and the settings panel. */
1087
+ function MetricsReportView({ report, onFocus, onPeriodChange }) {
1088
+ const label = DIMENSION_LABELS[report.dimension];
1089
+ const unattributed = report.groups.filter((group) => group.key === "unknown" || group.key === "").reduce((sum, group) => sum + group.totalTokens, 0);
1090
+ const counters = [
1091
+ {
1092
+ name: "Cache reads",
1093
+ value: report.totals.cachedTokens
1094
+ },
1095
+ {
1096
+ name: "Reasoning tokens",
1097
+ value: report.totals.reasoningTokens
1098
+ },
1099
+ {
1100
+ name: "Usage events",
1101
+ value: report.totals.usageEventCount
1102
+ },
1103
+ {
1104
+ name: "Log and span records",
1105
+ value: report.totals.totalRecords
1106
+ }
1107
+ ];
1108
+ const generated = new Date(report.generatedAt);
1109
+ return /* @__PURE__ */ jsxs("div", {
1110
+ className: "@container flex w-full min-w-0 flex-col gap-4",
1111
+ "data-testid": "metrics-report",
1112
+ children: [
1113
+ /* @__PURE__ */ jsx(MetricsSummary, { totals: report.totals }),
1114
+ /* @__PURE__ */ jsxs("div", {
1115
+ className: "grid min-w-0 grid-cols-1 gap-4 @3xl:grid-cols-3",
1116
+ children: [/* @__PURE__ */ jsxs("section", {
1117
+ className: "flex min-w-0 flex-col gap-4 rounded-lg border border-doom-border bg-doom-panel p-4 @3xl:col-span-2",
1118
+ "aria-label": "Usage over time",
1119
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
1120
+ className: "text-lg font-semibold text-doom-hi",
1121
+ children: "Usage over time"
1122
+ }), /* @__PURE__ */ jsx("p", {
1123
+ className: "mt-1 text-sm text-doom-dim",
1124
+ children: "Recorded tokens, not estimated spend."
1125
+ })] }), /* @__PURE__ */ jsx(TimelineChart, {
1126
+ buckets: report.timeline,
1127
+ bucketUnit: report.bucketUnit,
1128
+ onShorterPeriod: report.period === "day" || onPeriodChange === void 0 ? void 0 : () => onPeriodChange("day")
1129
+ })]
1130
+ }), /* @__PURE__ */ jsxs("section", {
1131
+ className: "flex min-w-0 flex-col gap-4 rounded-lg border border-doom-border bg-doom-panel p-4",
1132
+ "aria-label": "Reported counters",
1133
+ children: [
1134
+ /* @__PURE__ */ jsx("h3", {
1135
+ className: "text-lg font-semibold text-doom-hi",
1136
+ children: "Reported counters"
1137
+ }),
1138
+ /* @__PURE__ */ jsx("dl", {
1139
+ className: "flex flex-col gap-3",
1140
+ children: counters.map((counter) => /* @__PURE__ */ jsxs("div", {
1141
+ className: "flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1 border-b border-doom-border/40 pb-3",
1142
+ children: [/* @__PURE__ */ jsx("dt", {
1143
+ className: "text-sm text-doom-dim",
1144
+ children: counter.name
1145
+ }), /* @__PURE__ */ jsx("dd", {
1146
+ className: "text-base font-semibold tabular-nums text-doom-hi",
1147
+ title: counter.value === void 0 ? "Not reported" : String(counter.value),
1148
+ children: formatTokens(counter.value ?? NaN)
1149
+ })]
1150
+ }, counter.name))
1151
+ }),
1152
+ /* @__PURE__ */ jsx("p", {
1153
+ className: "text-sm leading-relaxed text-doom-dim",
1154
+ children: "Monetary cost is not included in this metrics report."
1155
+ }),
1156
+ /* @__PURE__ */ jsxs("details", {
1157
+ className: "text-sm leading-relaxed text-doom-dim",
1158
+ children: [/* @__PURE__ */ jsx("summary", {
1159
+ className: "cursor-pointer rounded-sm focus-visible:outline focus-visible:outline-1 focus-visible:outline-doom-blue",
1160
+ children: "Why the counters may not add up"
1161
+ }), /* @__PURE__ */ jsx("p", {
1162
+ className: "mt-2",
1163
+ children: "The total is reported by providers. Input, output, cache reads and reasoning are independent counters and may overlap or be incomplete. They are not a breakdown and may not sum to the total. A gap is not evidence of cache traffic. Missing fields say Not reported; a recorded zero stays zero."
1164
+ })]
1165
+ })
1166
+ ]
1167
+ })]
1168
+ }),
1169
+ /* @__PURE__ */ jsxs("div", {
1170
+ className: "grid min-w-0 grid-cols-1 items-start gap-4 @3xl:grid-cols-2",
1171
+ children: [/* @__PURE__ */ jsxs("section", {
1172
+ className: "flex min-w-0 flex-col gap-3 rounded-lg border border-doom-border bg-doom-panel p-4",
1173
+ "aria-label": `Tokens by ${label}`,
1174
+ children: [
1175
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("h3", {
1176
+ className: "text-lg font-semibold text-doom-hi",
1177
+ children: ["Tokens by ", label]
1178
+ }), /* @__PURE__ */ jsx("p", {
1179
+ className: "mt-1 text-sm text-doom-dim",
1180
+ children: "Largest recorded consumers. Select a named row to filter."
1181
+ })] }),
1182
+ unattributed > 0 ? /* @__PURE__ */ jsxs("div", {
1183
+ className: "rounded-md border border-doom-edge-yellow bg-doom-tint-yellow p-3",
1184
+ "data-testid": "metrics-attribution-notice",
1185
+ children: [/* @__PURE__ */ jsxs("p", {
1186
+ className: "text-sm font-semibold text-doom-yellow",
1187
+ children: [label.charAt(0).toUpperCase() + label.slice(1), " attribution missing"]
1188
+ }), /* @__PURE__ */ jsxs("p", {
1189
+ className: "mt-1 text-sm leading-relaxed text-doom-dim",
1190
+ children: [
1191
+ formatTokens(unattributed),
1192
+ " tokens have no ",
1193
+ label,
1194
+ " recorded. They are included in the total, but cannot be assigned to a named ",
1195
+ label,
1196
+ "."
1197
+ ]
1198
+ })]
1199
+ }) : null,
1200
+ DIMENSION_NOTES[report.dimension] === void 0 ? null : /* @__PURE__ */ jsx("p", {
1201
+ className: "text-sm leading-relaxed text-doom-dim",
1202
+ children: DIMENSION_NOTES[report.dimension]
1203
+ }),
1204
+ /* @__PURE__ */ jsx(GroupBars, {
1205
+ groups: report.groups,
1206
+ totalTokens: report.totals.totalTokens,
1207
+ focus: report.focus,
1208
+ onFocus
1209
+ }, `${report.dimension}:${report.focus ?? ""}`),
1210
+ /* @__PURE__ */ jsxs("p", {
1211
+ className: "text-sm text-doom-dim",
1212
+ children: [
1213
+ "Showing ",
1214
+ report.groups.length,
1215
+ " of ",
1216
+ formatTokens(report.totals.groupCount),
1217
+ " ",
1218
+ label,
1219
+ " groups. Bars show share of the reported total."
1220
+ ]
1221
+ }),
1222
+ report.totals.failedGroups > 0 ? /* @__PURE__ */ jsxs("p", {
1223
+ className: "text-sm text-doom-yellow",
1224
+ children: [
1225
+ report.totals.failedGroups,
1226
+ " of ",
1227
+ report.totals.groupCount,
1228
+ " ",
1229
+ label,
1230
+ "s failed"
1231
+ ]
1232
+ }) : null
1233
+ ]
1234
+ }), /* @__PURE__ */ jsx(ToolMetrics, { tools: report.tools })]
1235
+ }),
1236
+ /* @__PURE__ */ jsx(IssuesSection, {
1237
+ tools: report.tools,
1238
+ focus: report.dimension === "session" ? report.focus : void 0,
1239
+ count: report.totals.issueCount,
1240
+ startTime: report.startTime,
1241
+ endTime: report.endTime,
1242
+ broaderScope: report.focus !== void 0 && report.dimension !== "session"
1243
+ }, `${report.generatedAt}:${report.dimension}:${report.focus ?? ""}`),
1244
+ /* @__PURE__ */ jsxs("footer", {
1245
+ className: "flex flex-wrap justify-between gap-2 border-t border-doom-border pt-3 text-sm text-doom-dim",
1246
+ "data-testid": "metrics-provenance",
1247
+ children: [/* @__PURE__ */ jsxs("span", { children: [
1248
+ "Source: LogSink ·",
1249
+ " ",
1250
+ report.transport === "http" ? "HTTP" : report.transport === "worker" ? "database worker" : "transport not reported"
1251
+ ] }), /* @__PURE__ */ jsxs("span", {
1252
+ title: report.generatedAt,
1253
+ children: ["Report generated ", Number.isNaN(generated.getTime()) ? report.generatedAt : generated.toLocaleString()]
1254
+ })]
1255
+ })
1256
+ ]
1257
+ });
1258
+ }
1259
+ //#endregion
1260
+ //#region src/extensions/(frontend)/setting/_components/MetricsNotice.tsx
1261
+ /**
1262
+ * Everything the page says instead of, or above, a report.
1263
+ *
1264
+ * Kept pure and apart from the panel because these are the states that decide
1265
+ * whether a reader trusts the numbers, and they are the ones a fetching
1266
+ * component makes awkward to assert. The panel decides which state it is in;
1267
+ * this decides how each one reads.
1268
+ */
1269
+ const EMPTY_TITLES = {
1270
+ "no-sink": "no log sink",
1271
+ "no-data": "nothing recorded yet",
1272
+ "no-api": "metrics not installed",
1273
+ "query-error": "metrics could not be loaded"
1274
+ };
1275
+ function EmptyForReason({ response }) {
1276
+ return /* @__PURE__ */ jsx(EmptyState, {
1277
+ title: EMPTY_TITLES[response.unavailable],
1278
+ description: response.detail,
1279
+ "data-testid": "metrics-empty"
1280
+ });
1281
+ }
1282
+ function FocusNotice({ requested, applied, dimension, onClear }) {
1283
+ if (requested === "") return null;
1284
+ if (applied === void 0) return /* @__PURE__ */ jsxs("span", {
1285
+ className: "rounded-md border border-doom-edge-yellow bg-doom-tint-yellow p-3 text-sm text-doom-yellow",
1286
+ "data-testid": "metrics-focus-refused",
1287
+ children: [
1288
+ "this log sink does not support narrowing by ",
1289
+ DIMENSION_LABELS[dimension],
1290
+ ", so the numbers below are still everything",
1291
+ /* @__PURE__ */ jsx(Button, {
1292
+ variant: "ghost",
1293
+ size: "md",
1294
+ className: "ml-2",
1295
+ onClick: onClear,
1296
+ children: "clear"
1297
+ })
1298
+ ]
1299
+ });
1300
+ return /* @__PURE__ */ jsxs("span", {
1301
+ className: "flex flex-wrap items-center gap-2 rounded-md border border-doom-border bg-doom-panel p-3 text-sm text-doom-dim",
1302
+ "data-testid": "metrics-focus",
1303
+ children: [
1304
+ "narrowed to ",
1305
+ /* @__PURE__ */ jsx("span", {
1306
+ className: "min-w-0 break-all text-doom-hi",
1307
+ children: applied
1308
+ }),
1309
+ /* @__PURE__ */ jsx(Button, {
1310
+ variant: "ghost",
1311
+ size: "md",
1312
+ className: "ml-2",
1313
+ onClick: onClear,
1314
+ children: "clear"
1315
+ })
1316
+ ]
1317
+ });
1318
+ }
1319
+ /** First load and filter changes have no report to display yet. */
1320
+ function MetricsLoading() {
1321
+ return /* @__PURE__ */ jsxs("div", {
1322
+ className: "flex flex-col gap-4",
1323
+ role: "status",
1324
+ "aria-live": "polite",
1325
+ "data-testid": "metrics-loading",
1326
+ children: [
1327
+ /* @__PURE__ */ jsx("span", {
1328
+ className: "text-sm text-doom-dim",
1329
+ children: "Loading recorded metrics..."
1330
+ }),
1331
+ /* @__PURE__ */ jsx("div", {
1332
+ "aria-hidden": "true",
1333
+ className: "grid grid-cols-2 gap-3 @3xl:grid-cols-4",
1334
+ children: [
1335
+ 0,
1336
+ 1,
1337
+ 2,
1338
+ 3
1339
+ ].map((index) => /* @__PURE__ */ jsx("div", { className: "h-24 animate-pulse rounded-lg border border-doom-border bg-doom-panel motion-reduce:animate-none" }, index))
1340
+ }),
1341
+ /* @__PURE__ */ jsx("div", {
1342
+ "aria-hidden": "true",
1343
+ className: "h-48 animate-pulse rounded-lg border border-doom-border bg-doom-panel motion-reduce:animate-none"
1344
+ })
1345
+ ]
1346
+ });
1347
+ }
1348
+ //#endregion
1349
+ //#region src/extensions/(frontend)/setting/_components/MetricsPanel.tsx
1350
+ const PERIOD_LABELS = {
1351
+ day: "Past day",
1352
+ week: "Past week",
1353
+ month: "Past month",
1354
+ all: "All history"
1355
+ };
1356
+ /** Only render a response under the controls that requested it. */
1357
+ function MetricsPanel(_props) {
1358
+ const [dimension, setDimension] = useState("model");
1359
+ const [period, setPeriod] = useState("week");
1360
+ const [focus, setFocus] = useState("");
1361
+ const [refreshKey, setRefreshKey] = useState(0);
1362
+ const [completed, setCompleted] = useState();
1363
+ const requestKey = JSON.stringify([
1364
+ dimension,
1365
+ period,
1366
+ focus,
1367
+ refreshKey
1368
+ ]);
1369
+ useEffect(() => {
1370
+ const controller = new AbortController();
1371
+ fetchMetrics(dimension, period, focus, controller.signal).then((result) => {
1372
+ if (!controller.signal.aborted) setCompleted({
1373
+ key: requestKey,
1374
+ result
1375
+ });
1376
+ }).catch((error) => {
1377
+ if (!controller.signal.aborted) setCompleted({
1378
+ key: requestKey,
1379
+ result: { error: error instanceof Error ? error.message : "Metrics could not be loaded." }
1380
+ });
1381
+ });
1382
+ return () => controller.abort();
1383
+ }, [
1384
+ dimension,
1385
+ period,
1386
+ focus,
1387
+ requestKey
1388
+ ]);
1389
+ const result = completed?.key === requestKey ? completed.result : void 0;
1390
+ const loading = result === void 0;
1391
+ const response = result !== void 0 && "report" in result ? result.report : void 0;
1392
+ const report = response === void 0 || isMetricsUnavailable(response) ? void 0 : response;
1393
+ const error = result !== void 0 && "error" in result ? result.error : "";
1394
+ return /* @__PURE__ */ jsxs("div", {
1395
+ className: "@container flex w-full min-w-0 flex-col gap-4",
1396
+ "data-testid": "metrics-panel",
1397
+ "aria-busy": loading,
1398
+ children: [
1399
+ /* @__PURE__ */ jsxs("div", {
1400
+ className: "flex min-w-0 flex-wrap items-end gap-3 rounded-lg border border-doom-border bg-doom-panel p-3",
1401
+ children: [
1402
+ /* @__PURE__ */ jsxs("div", {
1403
+ className: "flex flex-col gap-2",
1404
+ children: [/* @__PURE__ */ jsx("span", {
1405
+ className: "text-base font-medium text-doom-text",
1406
+ children: "Group by"
1407
+ }), /* @__PURE__ */ jsxs(Select, {
1408
+ value: dimension,
1409
+ onValueChange: (next) => {
1410
+ setFocus("");
1411
+ setDimension(next);
1412
+ },
1413
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1414
+ "aria-label": "Group metrics by",
1415
+ "data-testid": "metrics-dimension",
1416
+ className: "w-40 ",
1417
+ children: /* @__PURE__ */ jsx(SelectValue, {})
1418
+ }), /* @__PURE__ */ jsx(SelectContent, { children: METRICS_DIMENSIONS.map((candidate) => /* @__PURE__ */ jsx(SelectItem, {
1419
+ value: candidate,
1420
+ children: DIMENSION_LABELS[candidate]
1421
+ }, candidate)) })]
1422
+ })]
1423
+ }),
1424
+ /* @__PURE__ */ jsxs("div", {
1425
+ className: "flex flex-col gap-2",
1426
+ children: [/* @__PURE__ */ jsx("span", {
1427
+ className: "text-base font-medium text-doom-text",
1428
+ children: "Period"
1429
+ }), /* @__PURE__ */ jsxs(Select, {
1430
+ value: period,
1431
+ onValueChange: (next) => setPeriod(next),
1432
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1433
+ "aria-label": "Metrics period",
1434
+ "data-testid": "metrics-period",
1435
+ className: "w-40 ",
1436
+ children: /* @__PURE__ */ jsx(SelectValue, {})
1437
+ }), /* @__PURE__ */ jsx(SelectContent, { children: METRICS_PERIODS.map((candidate) => /* @__PURE__ */ jsx(SelectItem, {
1438
+ value: candidate,
1439
+ children: PERIOD_LABELS[candidate]
1440
+ }, candidate)) })]
1441
+ })]
1442
+ }),
1443
+ /* @__PURE__ */ jsx(Button, {
1444
+ variant: "outline",
1445
+ size: "md",
1446
+ className: "ml-auto",
1447
+ onClick: () => setRefreshKey((current) => current + 1),
1448
+ disabled: loading,
1449
+ "data-testid": "metrics-refresh",
1450
+ children: loading ? "Loading..." : "Refresh"
1451
+ })
1452
+ ]
1453
+ }),
1454
+ loading ? /* @__PURE__ */ jsx(MetricsLoading, {}) : null,
1455
+ error === "" ? null : /* @__PURE__ */ jsxs("p", {
1456
+ role: "alert",
1457
+ className: "rounded-lg border border-doom-edge-red bg-doom-tint-red p-4 text-sm text-doom-red",
1458
+ "data-testid": "metrics-error",
1459
+ children: [error, " Use Refresh to try again."]
1460
+ }),
1461
+ response !== void 0 && isMetricsUnavailable(response) ? /* @__PURE__ */ jsx(EmptyForReason, { response }) : null,
1462
+ report === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FocusNotice, {
1463
+ requested: focus,
1464
+ applied: report.focus,
1465
+ dimension: report.dimension,
1466
+ onClear: () => setFocus("")
1467
+ }), /* @__PURE__ */ jsx(MetricsReportView, {
1468
+ report,
1469
+ onFocus: setFocus,
1470
+ onPeriodChange: setPeriod
1471
+ })] })
1472
+ ]
1473
+ });
1474
+ }
1475
+ //#endregion
1476
+ //#region src/extensions/(frontend)/setting/metrics.panel.web.tsx
1477
+ var metrics_panel_web_default = defineSettingsPanel({
1478
+ label: "metrics",
1479
+ detail: "recorded usage, token attribution and issues",
1480
+ component: MetricsPanel
1481
+ });
1482
+ //#endregion
1483
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_components/AgentDiagnosticCard.tsx
1484
+ function AgentDiagnosticCard({ toolName, output, running, isError }) {
1485
+ const lines = output.split("\n").filter(Boolean).map((text) => ({
1486
+ text,
1487
+ tone: "text"
1488
+ }));
1489
+ return /* @__PURE__ */ jsx(MessageItem, {
1490
+ tone: toolTone({
1491
+ running,
1492
+ isError
1493
+ }),
1494
+ expandable: lines.length > 12,
1495
+ children: ({ expanded }) => {
1496
+ const view = collapseLines(lines, 12, expanded);
1497
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
1498
+ title: toolName,
1499
+ children: "Captured agent evidence"
1500
+ }), /* @__PURE__ */ jsxs(MessageItemBody, { children: [
1501
+ /* @__PURE__ */ jsx(MessageLines, { lines: view.shown }),
1502
+ view.hidden > 0 ? /* @__PURE__ */ jsxs(MessageItemStatus, {
1503
+ expands: true,
1504
+ children: [view.hidden, " more lines"]
1505
+ }) : null,
1506
+ running ? /* @__PURE__ */ jsx(MessageItemStatus, {
1507
+ tone: "running",
1508
+ children: "Reading session telemetry"
1509
+ }) : null,
1510
+ isError ? /* @__PURE__ */ jsx(MessageItemStatus, {
1511
+ tone: "error",
1512
+ children: "Diagnostic unavailable"
1513
+ }) : null
1514
+ ] })] });
1515
+ }
1516
+ });
1517
+ }
1518
+ //#endregion
1519
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/diagnose-agent.web.tsx
1520
+ var diagnose_agent_web_default = defineToolRenderer({
1521
+ tools: ["diagnose_agent"],
1522
+ message: AgentDiagnosticCard
1523
+ });
1524
+ //#endregion
1525
+ //#region generated/web.ts
1526
+ const via = (identity, value) => value === void 0 ? void 0 : {
1527
+ ...identity,
1528
+ ...value
1529
+ };
1530
+ const webPlugin = defineWebPlugin({
1531
+ id: "log",
1532
+ global: { get settingsPanels() {
1533
+ return [via({ id: "metrics" }, metrics_panel_web_default)];
1534
+ } },
1535
+ session: { get toolRenderers() {
1536
+ return [via({ tools: ["diagnose_agent"] }, diagnose_agent_web_default)];
1537
+ } }
1538
+ });
1539
+ //#endregion
1540
+ export { webPlugin };
1541
+
1542
+ //# sourceMappingURL=web.mjs.map