@tonyclaw/agent-inspector 2.0.2 → 2.0.4

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 (60) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-Bp7_x-5N.js → CompareDrawer-BCH_fsLm.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-D85_UANk.js +101 -0
  4. package/.output/public/assets/{ReplayDialog-DFHCd0yx.js → ReplayDialog-DTeaHHit.js} +1 -1
  5. package/.output/public/assets/RequestAnatomy-DZ8grAih.js +1 -0
  6. package/.output/public/assets/ResponseView-Cldm6RCi.js +1 -0
  7. package/.output/public/assets/{StreamingChunkSequence-Bjs4Lqwn.js → StreamingChunkSequence-3x4p-yT7.js} +1 -1
  8. package/.output/public/assets/_sessionId-YqWFBu6d.js +1 -0
  9. package/.output/public/assets/index-BIw2H6jO.js +1 -0
  10. package/.output/public/assets/index-CobXD0yH.css +1 -0
  11. package/.output/public/assets/{json-viewer-6uV_YXws.js → json-viewer-BrzjD7qI.js} +1 -1
  12. package/.output/public/assets/{main-FSGUGtEL.js → main-mgxeUdZQ.js} +2 -2
  13. package/.output/server/_libs/lucide-react.mjs +8 -8
  14. package/.output/server/{_sessionId-_bf9vUww.mjs → _sessionId-C4xsxIWm.mjs} +2 -2
  15. package/.output/server/_ssr/{CompareDrawer-DIth2DQM.mjs → CompareDrawer-DuWEpqQ7.mjs} +4 -4
  16. package/.output/server/_ssr/{ProxyViewerContainer-249bTH-T.mjs → ProxyViewerContainer-Cckz5qKu.mjs} +519 -89
  17. package/.output/server/_ssr/{ReplayDialog-C1aGx0y1.mjs → ReplayDialog-BDRcr8E5.mjs} +4 -4
  18. package/.output/server/_ssr/{RequestAnatomy-D2bCiEJn.mjs → RequestAnatomy-BoO2_Ij0.mjs} +5 -5
  19. package/.output/server/_ssr/{ResponseView-DP6k4Xs_.mjs → ResponseView-DZiPBxvO.mjs} +21 -17
  20. package/.output/server/_ssr/{StreamingChunkSequence-HyXZV-b5.mjs → StreamingChunkSequence-D-be7KEL.mjs} +3 -3
  21. package/.output/server/_ssr/{index-Bt47f9pn.mjs → index-5RImHKfu.mjs} +2 -2
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{json-viewer-Co-YRwUP.mjs → json-viewer-aJhb93ZK.mjs} +2 -2
  24. package/.output/server/_ssr/{router-to_OJirX.mjs → router-Dgkv5nKP.mjs} +38 -99
  25. package/.output/server/{_tanstack-start-manifest_v-Bd-2YRWo.mjs → _tanstack-start-manifest_v-B8rrWXjr.mjs} +1 -1
  26. package/.output/server/index.mjs +63 -63
  27. package/README.md +5 -2
  28. package/package.json +1 -1
  29. package/src/components/ProxyViewer.tsx +25 -15
  30. package/src/components/ProxyViewerContainer.tsx +2 -1
  31. package/src/components/providers/SettingsDialog.tsx +45 -1
  32. package/src/components/proxy-viewer/AgentTraceSummary.tsx +276 -0
  33. package/src/components/proxy-viewer/AnswerMarkdown.tsx +16 -0
  34. package/src/components/proxy-viewer/ConversationGroup.tsx +18 -0
  35. package/src/components/proxy-viewer/ConversationHeader.tsx +6 -6
  36. package/src/components/proxy-viewer/LogEntry.tsx +5 -5
  37. package/src/components/proxy-viewer/LogEntryHeader.tsx +9 -14
  38. package/src/components/proxy-viewer/ResponseView.tsx +2 -6
  39. package/src/components/proxy-viewer/ToolTraceEvents.tsx +32 -0
  40. package/src/components/proxy-viewer/TurnGroup.tsx +15 -1
  41. package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +2 -2
  42. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +6 -12
  43. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +10 -14
  44. package/src/components/proxy-viewer/viewerState.ts +177 -0
  45. package/src/lib/runtimeConfig.ts +6 -0
  46. package/src/lib/timeDisplay.ts +22 -0
  47. package/src/lib/useOnboarding.ts +2 -0
  48. package/src/lib/useStripConfig.ts +16 -0
  49. package/src/proxy/chunkStorage.ts +3 -4
  50. package/src/proxy/config.ts +3 -0
  51. package/src/proxy/logger.ts +8 -15
  52. package/src/proxy/store.ts +8 -16
  53. package/src/routes/api/config.ts +5 -1
  54. package/src/routes/api/providers.$providerId.test.log.ts +0 -79
  55. package/.output/public/assets/ProxyViewerContainer-USuxPy-K.js +0 -101
  56. package/.output/public/assets/RequestAnatomy-ehyrskxt.js +0 -1
  57. package/.output/public/assets/ResponseView-BNGyc8e_.js +0 -1
  58. package/.output/public/assets/_sessionId-D_SeK_qp.js +0 -1
  59. package/.output/public/assets/index-BGGOWR7A.js +0 -1
  60. package/.output/public/assets/index-CIL46Z2y.css +0 -1
@@ -1,5 +1,5 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, D as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, a as RuntimeConfigSchema, r as requestFormatForPath, c as createPendingProviderTestResults, P as ProviderTestResultsSchema, d as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, g as getSessionPath, e as ProviderConfigSchema, s as stripClaudeCodeBillingHeader, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, b as AnthropicRequestSchema } from "./router-to_OJirX.mjs";
2
+ import { C as CapturedLogSchema, D as DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS, a as DEFAULT_TIME_DISPLAY_FORMAT, b as RuntimeConfigSchema, r as requestFormatForPath, e as createPendingProviderTestResults, P as ProviderTestResultsSchema, f as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, T as TimeDisplayFormatSchema, g as getSessionPath, h as ProviderConfigSchema, K as KnowledgeCandidateSchema, s as stripClaudeCodeBillingHeader, d as safeGetOwnProperty, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, c as AnthropicRequestSchema } from "./router-Dgkv5nKP.mjs";
3
3
  import { u as useSWR, a as useSWRConfig } from "../_libs/swr.mjs";
4
4
  import { J as JSZip } from "../_libs/jszip.mjs";
5
5
  import { c as clsx } from "../_libs/clsx.mjs";
@@ -9,7 +9,7 @@ import { R as Root, T as Trigger$2, C as Content, a as Close, b as Title, P as P
9
9
  import { d as diffJson, a as diffLines } from "../_libs/diff.mjs";
10
10
  import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
11
11
  import { R as Root2, T as Trigger, I as Icon, V as Value, P as Portal, C as Content2, a as Viewport, b as Item, c as ItemIndicator, d as ItemText, S as ScrollUpButton, e as ScrollDownButton } from "../_libs/radix-ui__react-select.mjs";
12
- import { C as Check, X, P as Plus, D as Download, S as Settings, A as ArrowLeft, a as Copy, b as ChevronDown, U as Upload, c as Scan, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, E as ExternalLink, T as Trash2, i as TriangleAlert, j as EyeOff, k as Eye, R as RotateCw, l as Pencil, m as Minus, n as CircleCheckBig, O as OctagonAlert, W as Wrench, G as Globe, F as FileTerminal, o as Radio, p as ChevronsUp, q as ChevronsDown, r as FileDiff, H as History, s as RotateCcw, t as GitCompareArrows, u as CircleQuestionMark, v as Server, w as Gauge, x as Lock, y as Wifi, z as WifiOff, B as ArrowUp, I as ArrowDown, J as Rows3, K as Columns2 } from "../_libs/lucide-react.mjs";
12
+ import { C as Check, X, P as Plus, D as Download, S as Settings, A as ArrowLeft, a as Copy, b as ChevronDown, U as Upload, c as Scan, d as CircleAlert, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, E as ExternalLink, T as Trash2, W as Wrench, i as TriangleAlert, B as Brain, j as EyeOff, k as Eye, R as RotateCw, l as Pencil, m as Minus, n as CircleCheckBig, O as OctagonAlert, G as Globe, F as FileTerminal, o as Radio, p as ChevronsUp, q as ChevronsDown, r as FileDiff, H as History, s as RotateCcw, t as GitCompareArrows, u as CircleQuestionMark, v as Server, w as Gauge, x as Lock, y as Wifi, z as WifiOff, I as ArrowUp, J as ArrowDown, K as Rows3, N as Columns2 } from "../_libs/lucide-react.mjs";
13
13
  import { u as union, d as object, a as array, l as literal, b as string, n as number, c as boolean, _ as _enum } from "../_libs/zod.mjs";
14
14
  import { P as Provider, R as Root3, T as Trigger$1, a as Portal$1, C as Content2$1, A as Arrow2 } from "../_libs/radix-ui__react-tooltip.mjs";
15
15
  import { R as Root2$1, L as List, T as Trigger$3, C as Content$1 } from "../_libs/radix-ui__react-tabs.mjs";
@@ -17,7 +17,7 @@ import { S as Slot } from "../_libs/radix-ui__react-slot.mjs";
17
17
  const ApiErrorSchema = object({
18
18
  error: string()
19
19
  });
20
- async function parseJsonResponse(response, schema) {
20
+ async function parseJsonResponse$1(response, schema) {
21
21
  const data = await response.json();
22
22
  return schema.parse(data);
23
23
  }
@@ -36,7 +36,7 @@ async function fetchJson(input, schema, init, errorFallback) {
36
36
  const fallback = errorFallback?.(response) ?? `Request failed with status ${response.status}`;
37
37
  throw new Error(await readApiError(response, fallback));
38
38
  }
39
- return parseJsonResponse(response, schema);
39
+ return parseJsonResponse$1(response, schema);
40
40
  }
41
41
  const STRIP_CONFIG_SWR_KEY = "/api/config";
42
42
  async function fetcher$2(url) {
@@ -71,10 +71,12 @@ function useStripConfig() {
71
71
  const { mutate: globalMutate } = useSWRConfig();
72
72
  const strip = response.data?.stripClaudeCodeBillingHeader ?? false;
73
73
  const slowResponseThresholdSeconds = response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS;
74
+ const timeDisplayFormat = response.data?.timeDisplayFormat ?? DEFAULT_TIME_DISPLAY_FORMAT;
74
75
  const optimisticConfig = (patch) => ({
75
76
  stripClaudeCodeBillingHeader: response.data?.stripClaudeCodeBillingHeader ?? false,
76
77
  hasSeenOnboarding: response.data?.hasSeenOnboarding ?? false,
77
78
  slowResponseThresholdSeconds: response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
79
+ timeDisplayFormat: response.data?.timeDisplayFormat ?? DEFAULT_TIME_DISPLAY_FORMAT,
78
80
  ...patch
79
81
  });
80
82
  const setStrip = async (next) => {
@@ -99,13 +101,22 @@ function useStripConfig() {
99
101
  }
100
102
  );
101
103
  };
104
+ const setTimeDisplayFormat = async (next) => {
105
+ await globalMutate(STRIP_CONFIG_SWR_KEY, setRuntimeConfig({ timeDisplayFormat: next }), {
106
+ optimisticData: optimisticConfig({ timeDisplayFormat: next }),
107
+ rollbackOnError: true,
108
+ revalidate: false
109
+ });
110
+ };
102
111
  return {
103
112
  strip,
104
113
  slowResponseThresholdSeconds,
114
+ timeDisplayFormat,
105
115
  isLoading: response.isLoading,
106
116
  isError: response.error !== void 0,
107
117
  setStrip,
108
- setSlowResponseThresholdSeconds
118
+ setSlowResponseThresholdSeconds,
119
+ setTimeDisplayFormat
109
120
  };
110
121
  }
111
122
  const ONBOARDING_SWR_KEY = "/api/config";
@@ -145,7 +156,8 @@ function useOnboarding() {
145
156
  optimisticData: {
146
157
  stripClaudeCodeBillingHeader: response.data?.stripClaudeCodeBillingHeader ?? false,
147
158
  hasSeenOnboarding: true,
148
- slowResponseThresholdSeconds: response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS
159
+ slowResponseThresholdSeconds: response.data?.slowResponseThresholdSeconds ?? DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
160
+ timeDisplayFormat: response.data?.timeDisplayFormat ?? DEFAULT_TIME_DISPLAY_FORMAT
149
161
  },
150
162
  rollbackOnError: true,
151
163
  revalidate: false
@@ -261,6 +273,21 @@ async function exportLogsAsZip(logs) {
261
273
  document.body.removeChild(anchor);
262
274
  URL.revokeObjectURL(url);
263
275
  }
276
+ function formatTimestamp(iso, format) {
277
+ switch (format) {
278
+ case "full":
279
+ return iso;
280
+ case "time":
281
+ return new Date(iso).toLocaleTimeString([], {
282
+ hour: "2-digit",
283
+ minute: "2-digit",
284
+ second: "2-digit"
285
+ });
286
+ }
287
+ }
288
+ function formatTimestampRange(startedAt, endedAt, format) {
289
+ return `${formatTimestamp(startedAt, format)} - ${formatTimestamp(endedAt, format)}`;
290
+ }
264
291
  function cn(...inputs) {
265
292
  return twMerge(clsx(inputs));
266
293
  }
@@ -275,7 +302,7 @@ function getStatusCategory(status) {
275
302
  if (status >= 500) return "server_error";
276
303
  return "pending";
277
304
  }
278
- const version = "2.0.2";
305
+ const version = "2.0.4";
279
306
  const packageJson = {
280
307
  version
281
308
  };
@@ -469,10 +496,6 @@ const API_FORMAT_LABELS = {
469
496
  openai: "OpenAI",
470
497
  unknown: "Unknown"
471
498
  };
472
- function formatTimestamp(iso) {
473
- const date = new Date(iso);
474
- return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
475
- }
476
499
  function ConversationHeader({
477
500
  conversationId,
478
501
  startTime,
@@ -486,6 +509,7 @@ function ConversationHeader({
486
509
  hideApiFormat = false,
487
510
  isLoading = false,
488
511
  userAgent,
512
+ timeDisplayFormat,
489
513
  onClear
490
514
  }) {
491
515
  const [confirmOpen, setConfirmOpen] = reactExports.useState(false);
@@ -559,11 +583,7 @@ function ConversationHeader({
559
583
  ),
560
584
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
561
585
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
562
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums", children: [
563
- formatTimestamp(startTime),
564
- " - ",
565
- formatTimestamp(endTime)
566
- ] })
586
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatTimestampRange(startTime, endTime, timeDisplayFormat) })
567
587
  ] }),
568
588
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
569
589
  /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
@@ -1343,27 +1363,27 @@ function useCopyFeedback(text) {
1343
1363
  return { copied, copy };
1344
1364
  }
1345
1365
  const LazyCompareDrawer = reactExports.lazy(
1346
- () => import("./CompareDrawer-DIth2DQM.mjs").then((m) => ({ default: m.CompareDrawer }))
1366
+ () => import("./CompareDrawer-DuWEpqQ7.mjs").then((m) => ({ default: m.CompareDrawer }))
1347
1367
  );
1348
1368
  const LazyReplayDialog = reactExports.lazy(
1349
- () => import("./ReplayDialog-C1aGx0y1.mjs").then((m) => ({ default: m.ReplayDialog }))
1369
+ () => import("./ReplayDialog-BDRcr8E5.mjs").then((m) => ({ default: m.ReplayDialog }))
1350
1370
  );
1351
1371
  const LazyRequestAnatomy = reactExports.lazy(
1352
- () => import("./RequestAnatomy-D2bCiEJn.mjs").then((m) => ({ default: m.RequestAnatomy }))
1372
+ () => import("./RequestAnatomy-BoO2_Ij0.mjs").then((m) => ({ default: m.RequestAnatomy }))
1353
1373
  );
1354
1374
  const LazyResponseView = reactExports.lazy(
1355
- () => import("./ResponseView-DP6k4Xs_.mjs").then((m) => ({ default: m.ResponseView }))
1375
+ () => import("./ResponseView-DZiPBxvO.mjs").then((m) => ({ default: m.ResponseView }))
1356
1376
  );
1357
1377
  const LazyStreamingChunkSequence = reactExports.lazy(
1358
- () => import("./StreamingChunkSequence-HyXZV-b5.mjs").then((m) => ({
1378
+ () => import("./StreamingChunkSequence-D-be7KEL.mjs").then((m) => ({
1359
1379
  default: m.StreamingChunkSequence
1360
1380
  }))
1361
1381
  );
1362
1382
  const LazyJsonViewer = reactExports.lazy(
1363
- () => import("./json-viewer-Co-YRwUP.mjs").then((m) => ({ default: m.JsonViewer }))
1383
+ () => import("./json-viewer-aJhb93ZK.mjs").then((m) => ({ default: m.JsonViewer }))
1364
1384
  );
1365
1385
  const LazyJsonViewerFromString = reactExports.lazy(
1366
- () => import("./json-viewer-Co-YRwUP.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1386
+ () => import("./json-viewer-aJhb93ZK.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1367
1387
  );
1368
1388
  const HIGHLIGHT_DURATION_MS = 1200;
1369
1389
  const MAX_HIGHLIGHT_ATTEMPTS = 12;
@@ -1757,7 +1777,7 @@ const STATUS_BADGE_CLASSES = {
1757
1777
  server_error: "bg-rose-500/15 text-rose-400 border-rose-500/25",
1758
1778
  pending: "bg-muted text-muted-foreground border-border"
1759
1779
  };
1760
- function formatElapsed$1(ms) {
1780
+ function formatElapsed$2(ms) {
1761
1781
  if (ms < 1e3) return `${ms}ms`;
1762
1782
  return `${(ms / 1e3).toFixed(1)}s`;
1763
1783
  }
@@ -1780,17 +1800,16 @@ const LogEntryHeader = reactExports.memo(function({
1780
1800
  toolCount = null,
1781
1801
  expanded,
1782
1802
  onToggle,
1783
- responseToolNames = null,
1784
1803
  cacheTrend = null,
1785
1804
  activeTab,
1786
1805
  tabActions,
1787
1806
  onReplay,
1788
- slowResponseThresholdSeconds = 0
1807
+ slowResponseThresholdSeconds = 0,
1808
+ timeDisplayFormat
1789
1809
  }) {
1790
1810
  const statusCategory = getStatusCategory(log.responseStatus);
1791
1811
  const isSlowResponse = log.elapsedMs !== null && slowResponseThresholdSeconds > 0 && log.elapsedMs > slowResponseThresholdSeconds * 1e3;
1792
1812
  const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
1793
- const toolNamesJoined = reactExports.useMemo(() => responseToolNames?.join(", ") ?? null, [responseToolNames]);
1794
1813
  return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1795
1814
  "div",
1796
1815
  {
@@ -1819,7 +1838,7 @@ const LogEntryHeader = reactExports.memo(function({
1819
1838
  ] }),
1820
1839
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1821
1840
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1822
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: log.timestamp })
1841
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", title: log.timestamp, children: formatTimestamp(log.timestamp, timeDisplayFormat) })
1823
1842
  ] }),
1824
1843
  log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1825
1844
  /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProviderLogo, { provider: detectProvider(log.model), className: "size-4" }) }) }),
@@ -1849,12 +1868,12 @@ const LogEntryHeader = reactExports.memo(function({
1849
1868
  ),
1850
1869
  children: [
1851
1870
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1852
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$1(log.elapsedMs) }),
1871
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$2(log.elapsedMs) }),
1853
1872
  isSlowResponse && /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3", "aria-label": "Slow response" })
1854
1873
  ]
1855
1874
  }
1856
1875
  ) }),
1857
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: isSlowResponse ? `Slow response: ${formatElapsed$1(log.elapsedMs)} exceeds ${formatElapsed$1(
1876
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: isSlowResponse ? `Slow response: ${formatElapsed$2(log.elapsedMs)} exceeds ${formatElapsed$2(
1858
1877
  slowResponseThresholdSeconds * 1e3
1859
1878
  )}` : "Elapsed response time" })
1860
1879
  ] }),
@@ -1912,10 +1931,6 @@ const LogEntryHeader = reactExports.memo(function({
1912
1931
  /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3" }),
1913
1932
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: toolCount })
1914
1933
  ] }),
1915
- responseToolNames !== null && responseToolNames.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-amber-400/80 text-xs shrink-0", children: [
1916
- /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3" }),
1917
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums truncate max-w-[160px]", children: toolNamesJoined })
1918
- ] }),
1919
1934
  log.origin !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1920
1935
  "span",
1921
1936
  {
@@ -2403,6 +2418,7 @@ const LogEntry = reactExports.memo(function({
2403
2418
  viewMode = "simple",
2404
2419
  strip,
2405
2420
  slowResponseThresholdSeconds,
2421
+ timeDisplayFormat,
2406
2422
  cacheTrend = null,
2407
2423
  onCompareWithPrevious
2408
2424
  }) {
@@ -2419,10 +2435,6 @@ const LogEntry = reactExports.memo(function({
2419
2435
  () => adapter.analyzeRequest(log.rawRequestBody),
2420
2436
  [adapter, log.rawRequestBody]
2421
2437
  );
2422
- const responseAnalysis = reactExports.useMemo(
2423
- () => adapter.analyzeResponse(log.responseText),
2424
- [adapter, log.responseText]
2425
- );
2426
2438
  const strippedRequestBody = reactExports.useMemo(() => {
2427
2439
  if (!strip || resolvedFormat !== "anthropic" || log.rawRequestBody === null) {
2428
2440
  return null;
@@ -2555,9 +2567,9 @@ const LogEntry = reactExports.memo(function({
2555
2567
  toolCount: requestAnalysis.toolCount,
2556
2568
  expanded,
2557
2569
  onToggle: () => setExpanded(!expanded),
2558
- responseToolNames: responseAnalysis.toolNames,
2559
2570
  cacheTrend,
2560
2571
  slowResponseThresholdSeconds,
2572
+ timeDisplayFormat,
2561
2573
  activeTab,
2562
2574
  tabActions,
2563
2575
  onReplay: onCompareWithPrevious === void 0 ? void 0 : () => {
@@ -2764,6 +2776,25 @@ function ThreadConnector({
2764
2776
  ) })
2765
2777
  ] });
2766
2778
  }
2779
+ function ToolTraceEvents({ events }) {
2780
+ if (events.length === 0) return null;
2781
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-3 mb-2 grid gap-1.5", children: events.map((event) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
2782
+ "div",
2783
+ {
2784
+ className: "flex min-w-0 items-center gap-2 rounded-md border border-border/70 bg-muted/20 px-2.5 py-1.5 text-xs",
2785
+ children: [
2786
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3.5 shrink-0 text-sky-400/70" }),
2787
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-semibold text-foreground/80", children: event.name }),
2788
+ event.argumentsPreview !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2789
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 shrink-0 text-muted-foreground/60" }),
2790
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate font-mono text-muted-foreground", children: event.argumentsPreview })
2791
+ ] })
2792
+ ]
2793
+ },
2794
+ event.id
2795
+ )) });
2796
+ }
2797
+ const PREVIEW_LIMIT = 180;
2767
2798
  function shouldRenderConversationContent(standalone, expanded) {
2768
2799
  return standalone || expanded;
2769
2800
  }
@@ -2801,7 +2832,135 @@ function buildValidPredecessors(groups) {
2801
2832
  }
2802
2833
  return predecessors;
2803
2834
  }
2804
- function formatElapsed(ms) {
2835
+ function parseJsonResponse(responseText) {
2836
+ if (responseText === null) return null;
2837
+ try {
2838
+ const parsed = JSON.parse(responseText);
2839
+ if (typeof parsed === "string") {
2840
+ return JSON.parse(parsed);
2841
+ }
2842
+ return parsed;
2843
+ } catch {
2844
+ return null;
2845
+ }
2846
+ }
2847
+ function previewValue(value) {
2848
+ if (value === void 0 || value === null) return null;
2849
+ const raw = typeof value === "string" ? value : JSON.stringify(value);
2850
+ if (raw === void 0) return null;
2851
+ const normalized = raw.replace(/\s+/g, " ").trim();
2852
+ if (normalized.length === 0) return null;
2853
+ return normalized.length > PREVIEW_LIMIT ? `${normalized.slice(0, PREVIEW_LIMIT - 1)}...` : normalized;
2854
+ }
2855
+ function extractAnthropicToolTraceEvents(log) {
2856
+ const parsed = parseJsonResponse(log.responseText);
2857
+ const content = safeGetOwnProperty(parsed, "content");
2858
+ if (!Array.isArray(content)) return [];
2859
+ const events = [];
2860
+ for (const block of content) {
2861
+ const type = safeGetOwnProperty(block, "type");
2862
+ if (type !== "tool_use") continue;
2863
+ const name = safeGetOwnProperty(block, "name");
2864
+ if (typeof name !== "string" || name.length === 0) continue;
2865
+ events.push({
2866
+ id: `${String(log.id)}-anthropic-tool-${String(events.length)}`,
2867
+ logId: log.id,
2868
+ index: events.length,
2869
+ provider: "anthropic",
2870
+ name,
2871
+ argumentsPreview: previewValue(safeGetOwnProperty(block, "input"))
2872
+ });
2873
+ }
2874
+ return events;
2875
+ }
2876
+ function extractOpenAIToolTraceEvents(log) {
2877
+ const parsed = parseJsonResponse(log.responseText);
2878
+ const choices = safeGetOwnProperty(parsed, "choices");
2879
+ if (!Array.isArray(choices)) return [];
2880
+ const events = [];
2881
+ for (const choice of choices) {
2882
+ const message = safeGetOwnProperty(choice, "message");
2883
+ const toolCalls = safeGetOwnProperty(message, "tool_calls");
2884
+ if (!Array.isArray(toolCalls)) continue;
2885
+ for (const call of toolCalls) {
2886
+ const fn = safeGetOwnProperty(call, "function");
2887
+ const name = safeGetOwnProperty(fn, "name");
2888
+ if (typeof name !== "string" || name.length === 0) continue;
2889
+ events.push({
2890
+ id: `${String(log.id)}-openai-tool-${String(events.length)}`,
2891
+ logId: log.id,
2892
+ index: events.length,
2893
+ provider: "openai",
2894
+ name,
2895
+ argumentsPreview: previewValue(safeGetOwnProperty(fn, "arguments"))
2896
+ });
2897
+ }
2898
+ }
2899
+ return events;
2900
+ }
2901
+ function extractToolTraceEvents(log) {
2902
+ const format = resolveLogFormat(log);
2903
+ switch (format) {
2904
+ case "anthropic":
2905
+ return extractAnthropicToolTraceEvents(log);
2906
+ case "openai":
2907
+ return extractOpenAIToolTraceEvents(log);
2908
+ case "unknown":
2909
+ return [];
2910
+ }
2911
+ }
2912
+ function buildTraceSummary(logs, slowResponseThresholdSeconds, knowledgeCandidateCount = 0) {
2913
+ let failedCallCount = 0;
2914
+ let pendingCallCount = 0;
2915
+ let slowCallCount = 0;
2916
+ let totalInputTokens = 0;
2917
+ let totalOutputTokens = 0;
2918
+ let totalCacheCreationInputTokens = 0;
2919
+ let totalCacheReadInputTokens = 0;
2920
+ let totalElapsedMs = 0;
2921
+ let maxElapsedMs = null;
2922
+ let toolCallCount = 0;
2923
+ for (const log of logs) {
2924
+ if (log.responseStatus === null) {
2925
+ pendingCallCount += 1;
2926
+ } else if (log.responseStatus >= 400) {
2927
+ failedCallCount += 1;
2928
+ }
2929
+ if (log.elapsedMs !== null && slowResponseThresholdSeconds > 0 && log.elapsedMs > slowResponseThresholdSeconds * 1e3) {
2930
+ slowCallCount += 1;
2931
+ }
2932
+ if (log.inputTokens !== null) totalInputTokens += log.inputTokens;
2933
+ if (log.outputTokens !== null) totalOutputTokens += log.outputTokens;
2934
+ if (log.cacheCreationInputTokens !== null) {
2935
+ totalCacheCreationInputTokens += log.cacheCreationInputTokens;
2936
+ }
2937
+ if (log.cacheReadInputTokens !== null) {
2938
+ totalCacheReadInputTokens += log.cacheReadInputTokens;
2939
+ }
2940
+ if (log.elapsedMs !== null) {
2941
+ totalElapsedMs += log.elapsedMs;
2942
+ maxElapsedMs = maxElapsedMs === null ? log.elapsedMs : Math.max(maxElapsedMs, log.elapsedMs);
2943
+ }
2944
+ toolCallCount += extractToolTraceEvents(log).length;
2945
+ }
2946
+ return {
2947
+ llmCallCount: logs.length,
2948
+ toolCallCount,
2949
+ failedCallCount,
2950
+ pendingCallCount,
2951
+ slowCallCount,
2952
+ totalInputTokens,
2953
+ totalOutputTokens,
2954
+ totalCacheCreationInputTokens,
2955
+ totalCacheReadInputTokens,
2956
+ totalElapsedMs,
2957
+ maxElapsedMs,
2958
+ startedAt: logs[0]?.timestamp ?? null,
2959
+ endedAt: logs[logs.length - 1]?.timestamp ?? null,
2960
+ knowledgeCandidateCount
2961
+ };
2962
+ }
2963
+ function formatElapsed$1(ms) {
2805
2964
  if (ms < 1e3) return `${ms}ms`;
2806
2965
  return `${(ms / 1e3).toFixed(1)}s`;
2807
2966
  }
@@ -2813,7 +2972,8 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
2813
2972
  cacheTrends,
2814
2973
  onCompareWithPrevious,
2815
2974
  comparisonPredecessors,
2816
- turnIndex = 0
2975
+ turnIndex = 0,
2976
+ timeDisplayFormat
2817
2977
  }) {
2818
2978
  const lastIdx = entries.length - 1;
2819
2979
  const lastStop = entries[lastIdx]?.stopReason ?? null;
@@ -2871,6 +3031,14 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
2871
3031
  const EndCrab = reactExports.useMemo(() => getCrabVariant(entries[lastIdx]?.log.id ?? 0), [entries, lastIdx]);
2872
3032
  const bgClass = turnIndex % 2 === 0 ? "bg-muted/10" : "bg-muted/25";
2873
3033
  const aggregateIsSlow = aggregate.maxElapsed !== null && slowResponseThresholdSeconds > 0 && aggregate.maxElapsed > slowResponseThresholdSeconds * 1e3;
3034
+ const toolEventsByLogId = reactExports.useMemo(() => {
3035
+ const events = /* @__PURE__ */ new Map();
3036
+ for (const entry of entries) {
3037
+ const extracted = extractToolTraceEvents(entry.log);
3038
+ if (extracted.length > 0) events.set(entry.log.id, extracted);
3039
+ }
3040
+ return events;
3041
+ }, [entries]);
2874
3042
  const [layoutVersion, setLayoutVersion] = reactExports.useState(0);
2875
3043
  const containerRef = reactExports.useRef(null);
2876
3044
  reactExports.useEffect(() => {
@@ -3001,14 +3169,14 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3001
3169
  ),
3002
3170
  children: [
3003
3171
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
3004
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed(aggregate.maxElapsed) }),
3172
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$1(aggregate.maxElapsed) }),
3005
3173
  aggregateIsSlow && /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3", "aria-label": "Slow response" })
3006
3174
  ]
3007
3175
  }
3008
3176
  ) }),
3009
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: aggregateIsSlow ? `Slow response: ${formatElapsed(
3177
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: aggregateIsSlow ? `Slow response: ${formatElapsed$1(
3010
3178
  aggregate.maxElapsed
3011
- )} exceeds ${formatElapsed(slowResponseThresholdSeconds * 1e3)}` : "Slowest request in this turn" })
3179
+ )} exceeds ${formatElapsed$1(slowResponseThresholdSeconds * 1e3)}` : "Slowest request in this turn" })
3012
3180
  ] }) }),
3013
3181
  aggregate.hasTokens && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 shrink-0", children: [
3014
3182
  /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3 text-muted-foreground" }),
@@ -3052,23 +3220,225 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3052
3220
  onToggle: toggleCollapse
3053
3221
  }
3054
3222
  ),
3055
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cn("flex-1 min-w-0 mb-0.5 rounded-lg", bgClass), children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3056
- LogEntry,
3057
- {
3058
- log,
3059
- viewMode,
3060
- strip,
3061
- slowResponseThresholdSeconds,
3062
- cacheTrend: cacheTrends?.get(log.id) ?? null,
3063
- onCompareWithPrevious: comparisonPredecessors.has(log.id) ? onCompareWithPrevious : void 0
3064
- }
3065
- ) })
3223
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("flex-1 min-w-0 mb-0.5 rounded-lg", bgClass), children: [
3224
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3225
+ LogEntry,
3226
+ {
3227
+ log,
3228
+ viewMode,
3229
+ strip,
3230
+ slowResponseThresholdSeconds,
3231
+ timeDisplayFormat,
3232
+ cacheTrend: cacheTrends?.get(log.id) ?? null,
3233
+ onCompareWithPrevious: comparisonPredecessors.has(log.id) ? onCompareWithPrevious : void 0
3234
+ }
3235
+ ),
3236
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ToolTraceEvents, { events: toolEventsByLogId.get(log.id) ?? [] })
3237
+ ] })
3066
3238
  ] }, log.id);
3067
3239
  })
3068
3240
  )
3069
3241
  }
3070
3242
  );
3071
3243
  });
3244
+ const CandidateResponseSchema = object({
3245
+ candidates: array(KnowledgeCandidateSchema)
3246
+ });
3247
+ function formatElapsed(ms) {
3248
+ if (ms === null) return "-";
3249
+ if (ms < 1e3) return `${String(ms)}ms`;
3250
+ return `${(ms / 1e3).toFixed(1)}s`;
3251
+ }
3252
+ function formatTimeRange$1(startedAt, endedAt, timeDisplayFormat) {
3253
+ if (startedAt === null || endedAt === null) return null;
3254
+ return formatTimestampRange(startedAt, endedAt, timeDisplayFormat);
3255
+ }
3256
+ function formatCandidateCount(count) {
3257
+ return `${String(count)} candidate${count === 1 ? "" : "s"}`;
3258
+ }
3259
+ function getLogAnchor(logId) {
3260
+ return `log-${String(logId)}`;
3261
+ }
3262
+ function jumpToLog(logId) {
3263
+ const anchor = getLogAnchor(logId);
3264
+ const target = document.getElementById(anchor);
3265
+ window.history.replaceState(null, "", `#${anchor}`);
3266
+ if (!(target instanceof HTMLElement)) return;
3267
+ target.scrollIntoView({ block: "center", behavior: "smooth" });
3268
+ target.focus({ preventScroll: true });
3269
+ }
3270
+ function CandidateList({ candidates }) {
3271
+ if (candidates.length === 0) return null;
3272
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 grid gap-1.5", children: candidates.map((candidate) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3273
+ "div",
3274
+ {
3275
+ className: "rounded-md border border-border/80 bg-background/60 px-2.5 py-2",
3276
+ children: [
3277
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
3278
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "h-5 px-1.5 text-[10px] font-mono", children: candidate.type }),
3279
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 flex-1 truncate text-xs font-medium", title: candidate.title, children: candidate.title }),
3280
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 font-mono text-[10px] text-muted-foreground", children: candidate.status })
3281
+ ] }),
3282
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 flex flex-wrap items-center gap-1.5", children: candidate.logIds.map((logId) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3283
+ "a",
3284
+ {
3285
+ href: `#${getLogAnchor(logId)}`,
3286
+ onClick: (event) => {
3287
+ event.preventDefault();
3288
+ jumpToLog(logId);
3289
+ },
3290
+ className: "rounded border border-blue-400/25 px-1.5 py-0.5 font-mono text-[10px] text-blue-400 underline-offset-2 transition-colors hover:bg-blue-400/10 hover:text-blue-300 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
3291
+ "aria-label": `Jump to evidence log ${String(logId)}`,
3292
+ children: [
3293
+ "#",
3294
+ logId
3295
+ ]
3296
+ },
3297
+ logId
3298
+ )) })
3299
+ ]
3300
+ },
3301
+ candidate.id
3302
+ )) });
3303
+ }
3304
+ function AgentTraceSummary({
3305
+ logs,
3306
+ scopeId,
3307
+ slowResponseThresholdSeconds,
3308
+ showRollupMetrics,
3309
+ timeDisplayFormat
3310
+ }) {
3311
+ const [candidates, setCandidates] = reactExports.useState([]);
3312
+ const [candidatesExpanded, setCandidatesExpanded] = reactExports.useState(true);
3313
+ const [candidateState, setCandidateState] = reactExports.useState({
3314
+ status: "idle",
3315
+ error: null
3316
+ });
3317
+ const hasCandidates = candidates.length > 0;
3318
+ const summary = reactExports.useMemo(
3319
+ () => buildTraceSummary(logs, slowResponseThresholdSeconds, candidates.length),
3320
+ [candidates.length, logs, slowResponseThresholdSeconds]
3321
+ );
3322
+ const showElapsedSummary = showRollupMetrics || summary.maxElapsedMs !== null;
3323
+ const timeRange = reactExports.useMemo(
3324
+ () => formatTimeRange$1(summary.startedAt, summary.endedAt, timeDisplayFormat),
3325
+ [summary.endedAt, summary.startedAt, timeDisplayFormat]
3326
+ );
3327
+ const createCandidates = reactExports.useCallback(() => {
3328
+ if (logs.length === 0 || candidateState.status === "loading") return;
3329
+ setCandidateState({ status: "loading", error: null });
3330
+ void (async () => {
3331
+ try {
3332
+ const response = await fetch(
3333
+ `/api/knowledge/sessions/${encodeURIComponent(scopeId)}/candidates`,
3334
+ { method: "POST" }
3335
+ );
3336
+ if (!response.ok) {
3337
+ const message = await readApiError(
3338
+ response,
3339
+ `Candidate generation failed with ${String(response.status)}`
3340
+ );
3341
+ setCandidateState({ status: "failed", error: message });
3342
+ return;
3343
+ }
3344
+ const parsed = await parseJsonResponse$1(response, CandidateResponseSchema);
3345
+ setCandidates(parsed.candidates);
3346
+ setCandidatesExpanded(parsed.candidates.length > 0);
3347
+ setCandidateState({ status: "ready", error: null });
3348
+ } catch (error) {
3349
+ setCandidateState({
3350
+ status: "failed",
3351
+ error: error instanceof Error ? error.message : "Candidate response was invalid"
3352
+ });
3353
+ }
3354
+ })();
3355
+ }, [candidateState.status, logs.length, scopeId]);
3356
+ if (logs.length === 0) return null;
3357
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "mb-2 rounded-lg border border-border bg-muted/10 px-3 py-2", children: [
3358
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-xs", children: [
3359
+ showRollupMetrics && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 font-semibold text-foreground", children: [
3360
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3.5 text-blue-400" }),
3361
+ summary.llmCallCount,
3362
+ " LLM"
3363
+ ] }),
3364
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3365
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3.5 text-sky-400/70" }),
3366
+ summary.toolCallCount,
3367
+ " tools"
3368
+ ] }),
3369
+ showRollupMetrics && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3370
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3.5 text-emerald-400" }),
3371
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3372
+ formatTokens(summary.totalInputTokens),
3373
+ " / ",
3374
+ formatTokens(summary.totalOutputTokens)
3375
+ ] })
3376
+ ] }),
3377
+ (summary.totalCacheCreationInputTokens > 0 || summary.totalCacheReadInputTokens > 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3378
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3.5 text-purple-400" }),
3379
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3380
+ "+",
3381
+ formatTokens(summary.totalCacheCreationInputTokens),
3382
+ " / ~",
3383
+ formatTokens(summary.totalCacheReadInputTokens)
3384
+ ] })
3385
+ ] }),
3386
+ showElapsedSummary && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3387
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3.5" }),
3388
+ showRollupMetrics && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: formatElapsed(summary.totalElapsedMs) }),
3389
+ summary.maxElapsedMs !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-muted-foreground/70", children: [
3390
+ "max ",
3391
+ formatElapsed(summary.maxElapsedMs)
3392
+ ] })
3393
+ ] }),
3394
+ showRollupMetrics && timeRange !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-muted-foreground/70", children: timeRange }),
3395
+ (summary.failedCallCount > 0 || summary.pendingCallCount > 0 || summary.slowCallCount > 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-amber-400", children: [
3396
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3.5" }),
3397
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3398
+ summary.failedCallCount,
3399
+ " fail / ",
3400
+ summary.pendingCallCount,
3401
+ " pending /",
3402
+ " ",
3403
+ summary.slowCallCount,
3404
+ " slow"
3405
+ ] })
3406
+ ] }),
3407
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
3408
+ hasCandidates && /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "h-6 px-2 text-[10px] font-mono", children: formatCandidateCount(summary.knowledgeCandidateCount) }),
3409
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
3410
+ Button,
3411
+ {
3412
+ type: "button",
3413
+ variant: "outline",
3414
+ size: "sm",
3415
+ className: "h-7 gap-1.5 px-2 text-xs",
3416
+ onClick: createCandidates,
3417
+ disabled: candidateState.status === "loading",
3418
+ children: [
3419
+ candidateState.status === "loading" ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Brain, { className: "size-3.5" }),
3420
+ "Candidate"
3421
+ ]
3422
+ }
3423
+ ),
3424
+ hasCandidates && /* @__PURE__ */ jsxRuntimeExports.jsx(
3425
+ Button,
3426
+ {
3427
+ type: "button",
3428
+ variant: "ghost",
3429
+ size: "icon",
3430
+ className: "size-7 text-muted-foreground",
3431
+ onClick: () => setCandidatesExpanded((value) => !value),
3432
+ "aria-expanded": candidatesExpanded,
3433
+ "aria-label": candidatesExpanded ? "Collapse memory candidates" : "Expand memory candidates",
3434
+ children: candidatesExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3.5" })
3435
+ }
3436
+ )
3437
+ ] }),
3438
+ candidateState.status === "failed" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-2 text-xs text-destructive", children: candidateState.error }),
3439
+ candidatesExpanded && /* @__PURE__ */ jsxRuntimeExports.jsx(CandidateList, { candidates })
3440
+ ] });
3441
+ }
3072
3442
  function computeStats(logs) {
3073
3443
  let totalInput = 0;
3074
3444
  let totalOutput = 0;
@@ -3087,7 +3457,9 @@ const ConversationGroup = reactExports.memo(function({
3087
3457
  onCompareWithPrevious,
3088
3458
  comparisonPredecessors,
3089
3459
  onClearGroup,
3090
- standalone = false
3460
+ standalone = false,
3461
+ hasPinnedSessionContext = false,
3462
+ timeDisplayFormat
3091
3463
  }) {
3092
3464
  const [expanded, setExpanded] = reactExports.useState(false);
3093
3465
  const stats = reactExports.useMemo(() => computeStats(group.logs), [group.logs]);
@@ -3095,6 +3467,7 @@ const ConversationGroup = reactExports.memo(function({
3095
3467
  const endTime = group.logs[group.logs.length - 1]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
3096
3468
  const mixed = hasMixedApiFormat(group.logs);
3097
3469
  const isLoading = group.logs.some((log) => log.responseStatus === null);
3470
+ const showTraceRollupMetrics = standalone && !hasPinnedSessionContext;
3098
3471
  const turnGroups = reactExports.useMemo(() => buildTurnGroups(group.logs), [group.logs]);
3099
3472
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-2", children: [
3100
3473
  !standalone && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -3112,23 +3485,37 @@ const ConversationGroup = reactExports.memo(function({
3112
3485
  hideApiFormat: mixed,
3113
3486
  isLoading,
3114
3487
  userAgent: group.logs[0]?.userAgent ?? null,
3488
+ timeDisplayFormat,
3115
3489
  onClear: () => onClearGroup(group.logs.map((l) => l.id))
3116
3490
  }
3117
3491
  ),
3118
- shouldRenderConversationContent(standalone, expanded) && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3119
- TurnGroup,
3120
- {
3121
- entries: tg.entries,
3122
- viewMode,
3123
- strip,
3124
- slowResponseThresholdSeconds,
3125
- cacheTrends,
3126
- onCompareWithPrevious,
3127
- comparisonPredecessors,
3128
- turnIndex: tg.turnIndex
3129
- },
3130
- tg.turnIndex
3131
- )) })
3492
+ shouldRenderConversationContent(standalone, expanded) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3493
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3494
+ AgentTraceSummary,
3495
+ {
3496
+ logs: group.logs,
3497
+ scopeId: group.conversationId,
3498
+ slowResponseThresholdSeconds,
3499
+ showRollupMetrics: showTraceRollupMetrics,
3500
+ timeDisplayFormat
3501
+ }
3502
+ ),
3503
+ turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3504
+ TurnGroup,
3505
+ {
3506
+ entries: tg.entries,
3507
+ viewMode,
3508
+ strip,
3509
+ slowResponseThresholdSeconds,
3510
+ timeDisplayFormat,
3511
+ cacheTrends,
3512
+ onCompareWithPrevious,
3513
+ comparisonPredecessors,
3514
+ turnIndex: tg.turnIndex
3515
+ },
3516
+ tg.turnIndex
3517
+ ))
3518
+ ] })
3132
3519
  ] });
3133
3520
  });
3134
3521
  function CrabLogo({ className }) {
@@ -4480,7 +4867,7 @@ function ProvidersPanel({
4480
4867
  try {
4481
4868
  const res = await fetch("/api/config/paths");
4482
4869
  if (res.ok) {
4483
- const data = await parseJsonResponse(res, ConfigPathsResponseSchema);
4870
+ const data = await parseJsonResponse$1(res, ConfigPathsResponseSchema);
4484
4871
  setConfigPath(data.providerConfig);
4485
4872
  }
4486
4873
  } catch {
@@ -4559,7 +4946,7 @@ function ProvidersPanel({
4559
4946
  signal: controller.signal
4560
4947
  });
4561
4948
  if (res.ok) {
4562
- const results = await parseJsonResponse(res, ProviderTestResultsSchema);
4949
+ const results = await parseJsonResponse$1(res, ProviderTestResultsSchema);
4563
4950
  updateTestResults(providerId, results);
4564
4951
  await persistProviderTestLog(providerId, results);
4565
4952
  } else {
@@ -4599,7 +4986,7 @@ function ProvidersPanel({
4599
4986
  setError(await readApiError(res, "Failed to add provider"));
4600
4987
  return;
4601
4988
  }
4602
- const newProvider = await parseJsonResponse(res, ProviderConfigSchema);
4989
+ const newProvider = await parseJsonResponse$1(res, ProviderConfigSchema);
4603
4990
  setShowForm(false);
4604
4991
  triggerHighlight(newProvider.id);
4605
4992
  refreshProviders();
@@ -4622,7 +5009,7 @@ function ProvidersPanel({
4622
5009
  setError(await readApiError(res, "Failed to update provider"));
4623
5010
  return;
4624
5011
  }
4625
- const updated = await parseJsonResponse(res, ProviderConfigSchema);
5012
+ const updated = await parseJsonResponse$1(res, ProviderConfigSchema);
4626
5013
  setEditingProvider(void 0);
4627
5014
  triggerHighlight(updated.id);
4628
5015
  refreshProviders();
@@ -4697,7 +5084,7 @@ function ProvidersPanel({
4697
5084
  headers: { "Content-Type": "application/json" },
4698
5085
  body: JSON.stringify(text)
4699
5086
  });
4700
- const data = await parseJsonResponse(res, ImportResponseSchema);
5087
+ const data = await parseJsonResponse$1(res, ImportResponseSchema);
4701
5088
  if (res.ok && data.imported !== void 0 && data.imported > 0) {
4702
5089
  refreshProviders();
4703
5090
  setError(null);
@@ -4986,9 +5373,11 @@ function ProxySettingsTab() {
4986
5373
  const {
4987
5374
  strip,
4988
5375
  slowResponseThresholdSeconds,
5376
+ timeDisplayFormat,
4989
5377
  isLoading,
4990
5378
  setStrip,
4991
- setSlowResponseThresholdSeconds
5379
+ setSlowResponseThresholdSeconds,
5380
+ setTimeDisplayFormat
4992
5381
  } = useStripConfig();
4993
5382
  const [error, setError] = reactExports.useState(null);
4994
5383
  const [pending, setPending] = reactExports.useState(false);
@@ -5020,6 +5409,20 @@ function ProxySettingsTab() {
5020
5409
  },
5021
5410
  [setSlowResponseThresholdSeconds]
5022
5411
  );
5412
+ const handleTimeDisplayFormatChange = reactExports.useCallback(
5413
+ async (next) => {
5414
+ setError(null);
5415
+ setPending(true);
5416
+ try {
5417
+ await setTimeDisplayFormat(next);
5418
+ } catch (err) {
5419
+ setError(err instanceof Error ? err.message : String(err));
5420
+ } finally {
5421
+ setPending(false);
5422
+ }
5423
+ },
5424
+ [setTimeDisplayFormat]
5425
+ );
5023
5426
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
5024
5427
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
5025
5428
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-semibold", children: "Claude Code billing header" }),
@@ -5080,6 +5483,28 @@ function ProxySettingsTab() {
5080
5483
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground", children: "seconds" })
5081
5484
  ] })
5082
5485
  ] }),
5486
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
5487
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "time-display-format", className: "text-sm font-semibold", children: "Time display" }),
5488
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Controls timestamps in session summaries, conversation headers, and log rows." }),
5489
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
5490
+ "select",
5491
+ {
5492
+ id: "time-display-format",
5493
+ value: timeDisplayFormat,
5494
+ disabled: isLoading || pending,
5495
+ onChange: (event) => {
5496
+ const parsed = TimeDisplayFormatSchema.safeParse(event.currentTarget.value);
5497
+ if (!parsed.success) return;
5498
+ void handleTimeDisplayFormatChange(parsed.data);
5499
+ },
5500
+ className: "h-8 rounded-md border border-input bg-background px-2 text-sm disabled:cursor-not-allowed disabled:opacity-50",
5501
+ children: [
5502
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "time", children: "Time only" }),
5503
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "full", children: "Full ISO" })
5504
+ ]
5505
+ }
5506
+ )
5507
+ ] }),
5083
5508
  error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-destructive", children: [
5084
5509
  "Failed to save: ",
5085
5510
  error
@@ -5266,16 +5691,11 @@ function computeTokenSummary(logs) {
5266
5691
  }
5267
5692
  return { totalIn, totalOut };
5268
5693
  }
5269
- function formatTimeRange(logs) {
5694
+ function formatTimeRange(logs, timeDisplayFormat) {
5270
5695
  const first = logs[0];
5271
5696
  const last = logs[logs.length - 1];
5272
5697
  if (first === void 0 || last === void 0) return null;
5273
- const format = (iso) => new Date(iso).toLocaleTimeString([], {
5274
- hour: "2-digit",
5275
- minute: "2-digit",
5276
- second: "2-digit"
5277
- });
5278
- return `${format(first.timestamp)} - ${format(last.timestamp)}`;
5698
+ return formatTimestampRange(first.timestamp, last.timestamp, timeDisplayFormat);
5279
5699
  }
5280
5700
  function getFirstUserAgent(logs) {
5281
5701
  for (const log of logs) {
@@ -5350,10 +5770,14 @@ function SessionContextBar({
5350
5770
  sessionId,
5351
5771
  logs,
5352
5772
  totalIn,
5353
- totalOut
5773
+ totalOut,
5774
+ timeDisplayFormat
5354
5775
  }) {
5355
5776
  const [copied, setCopied] = reactExports.useState(false);
5356
- const timeRange = reactExports.useMemo(() => formatTimeRange(logs), [logs]);
5777
+ const timeRange = reactExports.useMemo(
5778
+ () => formatTimeRange(logs, timeDisplayFormat),
5779
+ [logs, timeDisplayFormat]
5780
+ );
5357
5781
  const userAgent = reactExports.useMemo(() => getFirstUserAgent(logs), [logs]);
5358
5782
  const handleCopyLink = reactExports.useCallback(() => {
5359
5783
  void window.navigator.clipboard.writeText(window.location.href).then(() => {
@@ -5426,6 +5850,7 @@ function ProxyViewer({
5426
5850
  onViewModeChange,
5427
5851
  strip,
5428
5852
  slowResponseThresholdSeconds,
5853
+ timeDisplayFormat,
5429
5854
  hideSessionFilter = false,
5430
5855
  pinnedSessionId
5431
5856
  }) {
@@ -5472,6 +5897,7 @@ function ProxyViewer({
5472
5897
  setComparePair(null);
5473
5898
  }, []);
5474
5899
  const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
5900
+ const hasPinnedSessionContext = pinnedSessionId !== void 0;
5475
5901
  const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
5476
5902
  const comparisonPredecessors = reactExports.useMemo(() => buildValidPredecessors(groups), [groups]);
5477
5903
  const handleCompareWithPrevious = reactExports.useCallback(
@@ -5545,7 +5971,8 @@ function ProxyViewer({
5545
5971
  sessionId: pinnedSessionId,
5546
5972
  logs,
5547
5973
  totalIn,
5548
- totalOut
5974
+ totalOut,
5975
+ timeDisplayFormat
5549
5976
  }
5550
5977
  ),
5551
5978
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
@@ -5584,7 +6011,7 @@ function ProxyViewer({
5584
6011
  )
5585
6012
  ] }),
5586
6013
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
5587
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
6014
+ !hasPinnedSessionContext && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
5588
6015
  logs.length,
5589
6016
  " request",
5590
6017
  logs.length !== 1 ? "s" : "",
@@ -5655,7 +6082,9 @@ function ProxyViewer({
5655
6082
  onCompareWithPrevious: handleCompareWithPrevious,
5656
6083
  comparisonPredecessors,
5657
6084
  onClearGroup,
5658
- standalone: groups.length === 1
6085
+ standalone: groups.length === 1,
6086
+ hasPinnedSessionContext,
6087
+ timeDisplayFormat
5659
6088
  },
5660
6089
  group.id
5661
6090
  )) })
@@ -5913,7 +6342,7 @@ function ProxyViewerContainer({
5913
6342
  }
5914
6343
  })();
5915
6344
  }, []);
5916
- const { strip, slowResponseThresholdSeconds } = useStripConfig();
6345
+ const { strip, slowResponseThresholdSeconds, timeDisplayFormat } = useStripConfig();
5917
6346
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
5918
6347
  error !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed top-4 right-4 bg-destructive text-destructive-foreground px-4 py-2 rounded-md text-sm z-50", children: error }),
5919
6348
  /* @__PURE__ */ jsxRuntimeExports.jsx(OnboardingBanner, {}),
@@ -5933,6 +6362,7 @@ function ProxyViewerContainer({
5933
6362
  onViewModeChange: setViewMode,
5934
6363
  strip,
5935
6364
  slowResponseThresholdSeconds,
6365
+ timeDisplayFormat,
5936
6366
  hideSessionFilter: initialSessionId !== void 0,
5937
6367
  pinnedSessionId: initialSessionId
5938
6368
  }