@tonyclaw/agent-inspector 2.0.3 → 2.0.5

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 (68) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-D5A4bTfV.js → CompareDrawer-3nRwtk8J.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-CbW5VRER.js +101 -0
  4. package/.output/public/assets/ReplayDialog-Cl62N9PI.js +1 -0
  5. package/.output/public/assets/RequestAnatomy-DgQWGvjs.js +1 -0
  6. package/.output/public/assets/ResponseView-Cvc-ct4E.js +1 -0
  7. package/.output/public/assets/StreamingChunkSequence-BCQaCAIe.js +1 -0
  8. package/.output/public/assets/_sessionId-CcD_aLGq.js +1 -0
  9. package/.output/public/assets/index-B_dffD3u.js +1 -0
  10. package/.output/public/assets/index-CX796gvi.css +1 -0
  11. package/.output/public/assets/{json-viewer-BbU0n8eM.js → json-viewer-IXejqXB0.js} +1 -1
  12. package/.output/public/assets/{main-CZT_F-gu.js → main-2NlGzgOe.js} +2 -2
  13. package/.output/server/_libs/lucide-react.mjs +181 -114
  14. package/.output/server/{_sessionId-B-s9P7fJ.mjs → _sessionId-DWCTasJU.mjs} +3 -3
  15. package/.output/server/_ssr/{CompareDrawer-C08L3UOO.mjs → CompareDrawer-DhrN1uC2.mjs} +6 -6
  16. package/.output/server/_ssr/{ProxyViewerContainer-CMWl3Ijy.mjs → ProxyViewerContainer-DRl51s_n.mjs} +910 -186
  17. package/.output/server/_ssr/{ReplayDialog-CPDo9_G5.mjs → ReplayDialog-BQT_ygxC.mjs} +240 -14
  18. package/.output/server/_ssr/{RequestAnatomy-D9wt_K1E.mjs → RequestAnatomy-DS2tZOgq.mjs} +5 -5
  19. package/.output/server/_ssr/{ResponseView-DXaL7nY3.mjs → ResponseView-e0kL2C3x.mjs} +25 -21
  20. package/.output/server/_ssr/{StreamingChunkSequence-B_hudZyb.mjs → StreamingChunkSequence-BJG-m7xs.mjs} +3 -3
  21. package/.output/server/_ssr/{index-CuE_BN86.mjs → index-Dea3OeRw.mjs} +2 -2
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{json-viewer-Ci6kkjde.mjs → json-viewer-DDU55MLK.mjs} +3 -3
  24. package/.output/server/_ssr/{router-BemxgIg7.mjs → router-Dl7oh0zx.mjs} +164 -82
  25. package/.output/server/_tanstack-start-manifest_v-m-FJNBVf.mjs +4 -0
  26. package/.output/server/index.mjs +70 -70
  27. package/package.json +1 -1
  28. package/src/components/OnboardingBanner.tsx +11 -19
  29. package/src/components/ProxyViewer.tsx +26 -16
  30. package/src/components/ProxyViewerContainer.tsx +2 -1
  31. package/src/components/providers/ProviderCard.tsx +6 -20
  32. package/src/components/providers/SettingsDialog.tsx +140 -3
  33. package/src/components/proxy-viewer/AgentTraceSummary.tsx +731 -72
  34. package/src/components/proxy-viewer/AnswerMarkdown.tsx +16 -0
  35. package/src/components/proxy-viewer/CompareDrawer.tsx +4 -2
  36. package/src/components/proxy-viewer/ConversationGroup.tsx +12 -0
  37. package/src/components/proxy-viewer/ConversationHeader.tsx +6 -6
  38. package/src/components/proxy-viewer/LogEntry.tsx +5 -5
  39. package/src/components/proxy-viewer/LogEntryHeader.tsx +21 -36
  40. package/src/components/proxy-viewer/ReplayDialog.tsx +190 -8
  41. package/src/components/proxy-viewer/ResponseView.tsx +4 -8
  42. package/src/components/proxy-viewer/ToolTraceEvents.tsx +37 -17
  43. package/src/components/proxy-viewer/TurnGroup.tsx +18 -2
  44. package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +2 -2
  45. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +6 -12
  46. package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +2 -2
  47. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +10 -14
  48. package/src/components/proxy-viewer/replayComparison.ts +131 -0
  49. package/src/components/proxy-viewer/useKeyboardNavigation.ts +64 -22
  50. package/src/components/proxy-viewer/viewerState.ts +14 -2
  51. package/src/knowledge/candidateStore.ts +32 -1
  52. package/src/lib/runtimeConfig.ts +6 -0
  53. package/src/lib/timeDisplay.ts +22 -0
  54. package/src/lib/useOnboarding.ts +2 -0
  55. package/src/lib/useStripConfig.ts +16 -0
  56. package/src/proxy/config.ts +3 -0
  57. package/src/routes/api/config.ts +5 -1
  58. package/src/routes/api/knowledge.candidates.$candidateId.ts +50 -0
  59. package/src/routes/api/knowledge.sessions.$sessionId.candidates.ts +12 -2
  60. package/.output/public/assets/ProxyViewerContainer-Da0jpBkp.js +0 -101
  61. package/.output/public/assets/ReplayDialog-CxUk_TF0.js +0 -1
  62. package/.output/public/assets/RequestAnatomy-DIlzjgjJ.js +0 -1
  63. package/.output/public/assets/ResponseView-DQCuKJ1G.js +0 -1
  64. package/.output/public/assets/StreamingChunkSequence-DHk4SGGL.js +0 -1
  65. package/.output/public/assets/_sessionId-dY1TTl7N.js +0 -1
  66. package/.output/public/assets/index-D7wwbwly.css +0 -1
  67. package/.output/public/assets/index-FqQZbfl2.js +0 -1
  68. package/.output/server/_tanstack-start-manifest_v--L1_b4sd.mjs +0 -4
@@ -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, d as createPendingProviderTestResults, P as ProviderTestResultsSchema, e as createFailedProviderTestResults, M as MAX_SLOW_RESPONSE_THRESHOLD_SECONDS, g as getSessionPath, f as ProviderConfigSchema, K as KnowledgeCandidateSchema, s as stripClaudeCodeBillingHeader, c as safeGetOwnProperty, p as parseOpenAIResponse, O as OpenAIRequestSchema, A as AnthropicResponseSchema$1, b as AnthropicRequestSchema } from "./router-BemxgIg7.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-Dl7oh0zx.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, 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";
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, T as Terminal, e as ChevronUp, L as LoaderCircle, f as ChevronRight, g as User, h as Clock, M as MessageSquare, Z as Zap, E as ExternalLink, i as Trash2, W as Wrench, j as TriangleAlert, B as Brain, k as EyeOff, l as Eye, R as RotateCw, m as Pencil, n as Minus, o as CircleCheckBig, p as CircleX, q as ShieldCheck, r as RefreshCw, s as Save, F as FileSearch, t as CircleCheck, u as CloudUpload, O as OctagonAlert, G as Globe, v as FileTerminal, w as Radio, x as ChevronsUp, y as ChevronsDown, z as FileDiff, H as History, I as RotateCcw, J as GitCompareArrows, K as CircleQuestionMark, N as Server, Q as Gauge, V as Lock, Y as Wifi, _ as WifiOff, $ as ArrowUp, a0 as ArrowDown, a1 as Rows3, a2 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";
@@ -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
@@ -167,27 +179,20 @@ function OnboardingBanner() {
167
179
  "aria-label": "Onboarding tip",
168
180
  className: "mx-4 mt-2 mb-1 flex items-start gap-3 rounded-md border border-amber-500/30 bg-amber-500/5 px-4 py-3 text-sm",
169
181
  children: [
170
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
171
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-amber-600 dark:text-amber-400 mb-1", children: "Quick tour of the log tabs" }),
172
- /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "space-y-0.5 text-muted-foreground text-xs leading-relaxed", children: [
182
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0 flex-1", children: [
183
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1 font-medium text-amber-600 dark:text-amber-400", children: "Agent Inspector is ready" }),
184
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("ul", { className: "space-y-0.5 text-xs leading-relaxed text-muted-foreground", children: [
173
185
  /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
174
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Request" }),
175
- " / ",
176
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Response" }),
177
- " — structured views of what the proxy sent and received."
186
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Trace" }),
187
+ ": requests, responses, streaming chunks, tools, and timing."
178
188
  ] }),
179
189
  /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
180
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Headers" }),
181
- " request and response headers after proxy processing."
190
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Replay" }),
191
+ ": resend captured requests and compare provider behavior."
182
192
  ] }),
183
193
  /* @__PURE__ */ jsxRuntimeExports.jsxs("li", { children: [
184
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Raw Headers" }),
185
- " / ",
186
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Raw Request" }),
187
- " /",
188
- " ",
189
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Raw Response" }),
190
- " — exact bytes from the upstream provider (visible in Full mode)."
194
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Memory" }),
195
+ ": create reviewable candidates before promotion to OpenClaw."
191
196
  ] })
192
197
  ] })
193
198
  ] }),
@@ -198,7 +203,7 @@ function OnboardingBanner() {
198
203
  onClick: () => {
199
204
  void markSeen();
200
205
  },
201
- className: "inline-flex items-center gap-1.5 text-xs h-8 px-3 rounded-md border border-amber-500/40 text-amber-700 dark:text-amber-300 hover:bg-amber-500/10 transition-colors shrink-0",
206
+ className: "inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md border border-amber-500/40 px-3 text-xs text-amber-700 transition-colors hover:bg-amber-500/10 dark:text-amber-300",
202
207
  "aria-label": "Dismiss onboarding tip",
203
208
  children: [
204
209
  /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5" }),
@@ -213,7 +218,7 @@ function OnboardingBanner() {
213
218
  onClick: () => {
214
219
  void markSeen();
215
220
  },
216
- className: "text-muted-foreground hover:text-foreground transition-colors shrink-0 p-1 -m-1",
221
+ className: "-m-1 shrink-0 p-1 text-muted-foreground transition-colors hover:text-foreground",
217
222
  "aria-label": "Dismiss",
218
223
  children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-3.5" })
219
224
  }
@@ -261,6 +266,21 @@ async function exportLogsAsZip(logs) {
261
266
  document.body.removeChild(anchor);
262
267
  URL.revokeObjectURL(url);
263
268
  }
269
+ function formatTimestamp(iso, format) {
270
+ switch (format) {
271
+ case "full":
272
+ return iso;
273
+ case "time":
274
+ return new Date(iso).toLocaleTimeString([], {
275
+ hour: "2-digit",
276
+ minute: "2-digit",
277
+ second: "2-digit"
278
+ });
279
+ }
280
+ }
281
+ function formatTimestampRange(startedAt, endedAt, format) {
282
+ return `${formatTimestamp(startedAt, format)} - ${formatTimestamp(endedAt, format)}`;
283
+ }
264
284
  function cn(...inputs) {
265
285
  return twMerge(clsx(inputs));
266
286
  }
@@ -275,7 +295,7 @@ function getStatusCategory(status) {
275
295
  if (status >= 500) return "server_error";
276
296
  return "pending";
277
297
  }
278
- const version = "2.0.3";
298
+ const version = "2.0.5";
279
299
  const packageJson = {
280
300
  version
281
301
  };
@@ -469,10 +489,6 @@ const API_FORMAT_LABELS = {
469
489
  openai: "OpenAI",
470
490
  unknown: "Unknown"
471
491
  };
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
492
  function ConversationHeader({
477
493
  conversationId,
478
494
  startTime,
@@ -486,6 +502,7 @@ function ConversationHeader({
486
502
  hideApiFormat = false,
487
503
  isLoading = false,
488
504
  userAgent,
505
+ timeDisplayFormat,
489
506
  onClear
490
507
  }) {
491
508
  const [confirmOpen, setConfirmOpen] = reactExports.useState(false);
@@ -559,11 +576,7 @@ function ConversationHeader({
559
576
  ),
560
577
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
561
578
  /* @__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
- ] })
579
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatTimestampRange(startTime, endTime, timeDisplayFormat) })
567
580
  ] }),
568
581
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
569
582
  /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
@@ -1343,27 +1356,27 @@ function useCopyFeedback(text) {
1343
1356
  return { copied, copy };
1344
1357
  }
1345
1358
  const LazyCompareDrawer = reactExports.lazy(
1346
- () => import("./CompareDrawer-C08L3UOO.mjs").then((m) => ({ default: m.CompareDrawer }))
1359
+ () => import("./CompareDrawer-DhrN1uC2.mjs").then((m) => ({ default: m.CompareDrawer }))
1347
1360
  );
1348
1361
  const LazyReplayDialog = reactExports.lazy(
1349
- () => import("./ReplayDialog-CPDo9_G5.mjs").then((m) => ({ default: m.ReplayDialog }))
1362
+ () => import("./ReplayDialog-BQT_ygxC.mjs").then((m) => ({ default: m.ReplayDialog }))
1350
1363
  );
1351
1364
  const LazyRequestAnatomy = reactExports.lazy(
1352
- () => import("./RequestAnatomy-D9wt_K1E.mjs").then((m) => ({ default: m.RequestAnatomy }))
1365
+ () => import("./RequestAnatomy-DS2tZOgq.mjs").then((m) => ({ default: m.RequestAnatomy }))
1353
1366
  );
1354
1367
  const LazyResponseView = reactExports.lazy(
1355
- () => import("./ResponseView-DXaL7nY3.mjs").then((m) => ({ default: m.ResponseView }))
1368
+ () => import("./ResponseView-e0kL2C3x.mjs").then((m) => ({ default: m.ResponseView }))
1356
1369
  );
1357
1370
  const LazyStreamingChunkSequence = reactExports.lazy(
1358
- () => import("./StreamingChunkSequence-B_hudZyb.mjs").then((m) => ({
1371
+ () => import("./StreamingChunkSequence-BJG-m7xs.mjs").then((m) => ({
1359
1372
  default: m.StreamingChunkSequence
1360
1373
  }))
1361
1374
  );
1362
1375
  const LazyJsonViewer = reactExports.lazy(
1363
- () => import("./json-viewer-Ci6kkjde.mjs").then((m) => ({ default: m.JsonViewer }))
1376
+ () => import("./json-viewer-DDU55MLK.mjs").then((m) => ({ default: m.JsonViewer }))
1364
1377
  );
1365
1378
  const LazyJsonViewerFromString = reactExports.lazy(
1366
- () => import("./json-viewer-Ci6kkjde.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1379
+ () => import("./json-viewer-DDU55MLK.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1367
1380
  );
1368
1381
  const HIGHLIGHT_DURATION_MS = 1200;
1369
1382
  const MAX_HIGHLIGHT_ATTEMPTS = 12;
@@ -1780,17 +1793,16 @@ const LogEntryHeader = reactExports.memo(function({
1780
1793
  toolCount = null,
1781
1794
  expanded,
1782
1795
  onToggle,
1783
- responseToolNames = null,
1784
1796
  cacheTrend = null,
1785
1797
  activeTab,
1786
1798
  tabActions,
1787
1799
  onReplay,
1788
- slowResponseThresholdSeconds = 0
1800
+ slowResponseThresholdSeconds = 0,
1801
+ timeDisplayFormat
1789
1802
  }) {
1790
1803
  const statusCategory = getStatusCategory(log.responseStatus);
1791
1804
  const isSlowResponse = log.elapsedMs !== null && slowResponseThresholdSeconds > 0 && log.elapsedMs > slowResponseThresholdSeconds * 1e3;
1792
1805
  const hasTokens = log.inputTokens !== null || log.outputTokens !== null;
1793
- const toolNamesJoined = reactExports.useMemo(() => responseToolNames?.join(", ") ?? null, [responseToolNames]);
1794
1806
  return /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1795
1807
  "div",
1796
1808
  {
@@ -1819,7 +1831,7 @@ const LogEntryHeader = reactExports.memo(function({
1819
1831
  ] }),
1820
1832
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1821
1833
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1822
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: log.timestamp })
1834
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", title: log.timestamp, children: formatTimestamp(log.timestamp, timeDisplayFormat) })
1823
1835
  ] }),
1824
1836
  log.model !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1825
1837
  /* @__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" }) }) }),
@@ -1884,25 +1896,19 @@ const LogEntryHeader = reactExports.memo(function({
1884
1896
  )
1885
1897
  ] })
1886
1898
  ] }),
1887
- log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1888
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1889
- /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.creation ?? null }),
1890
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
1891
- "Cache +",
1892
- formatTokens(log.cacheCreationInputTokens)
1893
- ] })
1894
- ] }) }),
1895
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens cached for reuse, reducing future API cost" })
1899
+ log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1900
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.creation ?? null }),
1901
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
1902
+ "KV Cache +",
1903
+ formatTokens(log.cacheCreationInputTokens)
1904
+ ] })
1896
1905
  ] }),
1897
- log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
1898
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1899
- /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.read ?? null }),
1900
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
1901
- "Cache ~",
1902
- formatTokens(log.cacheReadInputTokens)
1903
- ] })
1904
- ] }) }),
1905
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens served from cache, reducing API cost" })
1906
+ log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0", children: [
1907
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CacheTrendIndicator, { trend: cacheTrend?.read ?? null }),
1908
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
1909
+ "KV Cache ~",
1910
+ formatTokens(log.cacheReadInputTokens)
1911
+ ] })
1906
1912
  ] }),
1907
1913
  messageCount !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs shrink-0", children: [
1908
1914
  /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3" }),
@@ -1912,10 +1918,6 @@ const LogEntryHeader = reactExports.memo(function({
1912
1918
  /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3" }),
1913
1919
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: toolCount })
1914
1920
  ] }),
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
1921
  log.origin !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(
1920
1922
  "span",
1921
1923
  {
@@ -2403,6 +2405,7 @@ const LogEntry = reactExports.memo(function({
2403
2405
  viewMode = "simple",
2404
2406
  strip,
2405
2407
  slowResponseThresholdSeconds,
2408
+ timeDisplayFormat,
2406
2409
  cacheTrend = null,
2407
2410
  onCompareWithPrevious
2408
2411
  }) {
@@ -2419,10 +2422,6 @@ const LogEntry = reactExports.memo(function({
2419
2422
  () => adapter.analyzeRequest(log.rawRequestBody),
2420
2423
  [adapter, log.rawRequestBody]
2421
2424
  );
2422
- const responseAnalysis = reactExports.useMemo(
2423
- () => adapter.analyzeResponse(log.responseText),
2424
- [adapter, log.responseText]
2425
- );
2426
2425
  const strippedRequestBody = reactExports.useMemo(() => {
2427
2426
  if (!strip || resolvedFormat !== "anthropic" || log.rawRequestBody === null) {
2428
2427
  return null;
@@ -2555,9 +2554,9 @@ const LogEntry = reactExports.memo(function({
2555
2554
  toolCount: requestAnalysis.toolCount,
2556
2555
  expanded,
2557
2556
  onToggle: () => setExpanded(!expanded),
2558
- responseToolNames: responseAnalysis.toolNames,
2559
2557
  cacheTrend,
2560
2558
  slowResponseThresholdSeconds,
2559
+ timeDisplayFormat,
2561
2560
  activeTab,
2562
2561
  tabActions,
2563
2562
  onReplay: onCompareWithPrevious === void 0 ? void 0 : () => {
@@ -2764,27 +2763,39 @@ function ThreadConnector({
2764
2763
  ) })
2765
2764
  ] });
2766
2765
  }
2767
- function ToolTraceEvents({ events }) {
2768
- if (events.length === 0) return null;
2769
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-3 mb-2 grid gap-1.5", children: events.map((event) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
2766
+ function ToolTraceEventRow({ event }) {
2767
+ const argumentCopy = useCopyFeedback(event.argumentsText);
2768
+ const canCopyArguments = event.argumentsText !== null;
2769
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2770
2770
  "div",
2771
2771
  {
2772
- className: "flex min-w-0 items-center gap-2 rounded-md border border-amber-500/20 bg-amber-500/5 px-2.5 py-1.5 text-xs",
2772
+ className: "group/tool-trace 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",
2773
2773
  children: [
2774
- /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3.5 shrink-0 text-amber-400" }),
2775
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-semibold text-amber-300", children: event.name }),
2776
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-muted-foreground", children: [
2777
- "#",
2778
- event.logId
2779
- ] }),
2774
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3.5 shrink-0 text-sky-400/70" }),
2775
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-semibold text-foreground/80", children: event.name }),
2780
2776
  event.argumentsPreview !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2781
2777
  /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 shrink-0 text-muted-foreground/60" }),
2782
2778
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate font-mono text-muted-foreground", children: event.argumentsPreview })
2783
- ] })
2779
+ ] }),
2780
+ canCopyArguments && /* @__PURE__ */ jsxRuntimeExports.jsx(
2781
+ "button",
2782
+ {
2783
+ type: "button",
2784
+ className: "ml-auto inline-flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground opacity-0 transition-opacity hover:bg-background/80 hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover/tool-trace:opacity-100 group-focus-within/tool-trace:opacity-100",
2785
+ onClick: argumentCopy.copy,
2786
+ "aria-label": argumentCopy.copied ? "Copied tool arguments" : "Copy tool arguments",
2787
+ title: argumentCopy.copied ? "Copied tool arguments" : "Copy tool arguments",
2788
+ children: argumentCopy.copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3.5" })
2789
+ }
2790
+ )
2784
2791
  ]
2785
2792
  },
2786
2793
  event.id
2787
- )) });
2794
+ );
2795
+ }
2796
+ function ToolTraceEvents({ events }) {
2797
+ if (events.length === 0) return null;
2798
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mx-3 mb-2 grid gap-1.5", children: events.map((event) => /* @__PURE__ */ jsxRuntimeExports.jsx(ToolTraceEventRow, { event }, event.id)) });
2788
2799
  }
2789
2800
  const PREVIEW_LIMIT = 180;
2790
2801
  function shouldRenderConversationContent(standalone, expanded) {
@@ -2844,6 +2855,12 @@ function previewValue(value) {
2844
2855
  if (normalized.length === 0) return null;
2845
2856
  return normalized.length > PREVIEW_LIMIT ? `${normalized.slice(0, PREVIEW_LIMIT - 1)}...` : normalized;
2846
2857
  }
2858
+ function copyValue(value) {
2859
+ if (value === void 0 || value === null) return null;
2860
+ if (typeof value === "string") return value.length > 0 ? value : null;
2861
+ const raw = JSON.stringify(value, null, 2);
2862
+ return raw === void 0 || raw.length === 0 ? null : raw;
2863
+ }
2847
2864
  function extractAnthropicToolTraceEvents(log) {
2848
2865
  const parsed = parseJsonResponse(log.responseText);
2849
2866
  const content = safeGetOwnProperty(parsed, "content");
@@ -2854,13 +2871,15 @@ function extractAnthropicToolTraceEvents(log) {
2854
2871
  if (type !== "tool_use") continue;
2855
2872
  const name = safeGetOwnProperty(block, "name");
2856
2873
  if (typeof name !== "string" || name.length === 0) continue;
2874
+ const input = safeGetOwnProperty(block, "input");
2857
2875
  events.push({
2858
2876
  id: `${String(log.id)}-anthropic-tool-${String(events.length)}`,
2859
2877
  logId: log.id,
2860
2878
  index: events.length,
2861
2879
  provider: "anthropic",
2862
2880
  name,
2863
- argumentsPreview: previewValue(safeGetOwnProperty(block, "input"))
2881
+ argumentsText: copyValue(input),
2882
+ argumentsPreview: previewValue(input)
2864
2883
  });
2865
2884
  }
2866
2885
  return events;
@@ -2878,13 +2897,15 @@ function extractOpenAIToolTraceEvents(log) {
2878
2897
  const fn = safeGetOwnProperty(call, "function");
2879
2898
  const name = safeGetOwnProperty(fn, "name");
2880
2899
  if (typeof name !== "string" || name.length === 0) continue;
2900
+ const args = safeGetOwnProperty(fn, "arguments");
2881
2901
  events.push({
2882
2902
  id: `${String(log.id)}-openai-tool-${String(events.length)}`,
2883
2903
  logId: log.id,
2884
2904
  index: events.length,
2885
2905
  provider: "openai",
2886
2906
  name,
2887
- argumentsPreview: previewValue(safeGetOwnProperty(fn, "arguments"))
2907
+ argumentsText: copyValue(args),
2908
+ argumentsPreview: previewValue(args)
2888
2909
  });
2889
2910
  }
2890
2911
  }
@@ -2964,7 +2985,8 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
2964
2985
  cacheTrends,
2965
2986
  onCompareWithPrevious,
2966
2987
  comparisonPredecessors,
2967
- turnIndex = 0
2988
+ turnIndex = 0,
2989
+ timeDisplayFormat
2968
2990
  }) {
2969
2991
  const lastIdx = entries.length - 1;
2970
2992
  const lastStop = entries[lastIdx]?.stopReason ?? null;
@@ -3046,17 +3068,27 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3046
3068
  window.cancelAnimationFrame(raf);
3047
3069
  };
3048
3070
  }, []);
3071
+ const firstLogId = entries[0]?.log.id ?? turnIndex;
3072
+ const turnLabel = `Turn ${String(turnIndex + 1)}`;
3049
3073
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
3050
3074
  "div",
3051
3075
  {
3052
3076
  ref: containerRef,
3053
- className: cn("border rounded-lg", isPending ? "border-amber-500/10" : "border-transparent"),
3077
+ tabIndex: collapsed ? void 0 : 0,
3078
+ role: collapsed ? void 0 : "group",
3079
+ "aria-label": collapsed ? void 0 : turnLabel,
3080
+ "data-nav-id": collapsed ? void 0 : `turn-${String(firstLogId)}`,
3081
+ className: cn(
3082
+ "border rounded-lg",
3083
+ isPending ? "border-amber-500/10" : "border-transparent",
3084
+ !collapsed && "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none"
3085
+ ),
3054
3086
  children: collapsed ? (
3055
3087
  /* ---- Collapsed: dual-crab (+ summary card for multi-log turns) ---- */
3056
3088
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
3057
3089
  "div",
3058
3090
  {
3059
- "data-nav-id": `turn-collapsed-${entries[0]?.log.id ?? turnIndex}`,
3091
+ "data-nav-id": `turn-collapsed-${String(firstLogId)}`,
3060
3092
  "data-nav-action": "expand",
3061
3093
  role: "button",
3062
3094
  tabIndex: 0,
@@ -3219,6 +3251,7 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3219
3251
  viewMode,
3220
3252
  strip,
3221
3253
  slowResponseThresholdSeconds,
3254
+ timeDisplayFormat,
3222
3255
  cacheTrend: cacheTrends?.get(log.id) ?? null,
3223
3256
  onCompareWithPrevious: comparisonPredecessors.has(log.id) ? onCompareWithPrevious : void 0
3224
3257
  }
@@ -3234,55 +3267,458 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3234
3267
  const CandidateResponseSchema = object({
3235
3268
  candidates: array(KnowledgeCandidateSchema)
3236
3269
  });
3270
+ const CandidatePromotionResponseSchema = object({
3271
+ candidate: KnowledgeCandidateSchema
3272
+ });
3273
+ const CandidatePromotionFailureResponseSchema = object({
3274
+ error: string().optional(),
3275
+ candidate: KnowledgeCandidateSchema.optional()
3276
+ });
3277
+ const CandidateUpdateResponseSchema = object({
3278
+ candidate: KnowledgeCandidateSchema
3279
+ });
3280
+ const CANDIDATE_STATUS_CLASSES = {
3281
+ draft: "border-amber-500/30 bg-amber-500/10 text-amber-500",
3282
+ promoted: "border-emerald-500/30 bg-emerald-500/10 text-emerald-500",
3283
+ failed: "border-destructive/30 bg-destructive/10 text-destructive"
3284
+ };
3237
3285
  function formatElapsed(ms) {
3238
3286
  if (ms === null) return "-";
3239
3287
  if (ms < 1e3) return `${String(ms)}ms`;
3240
3288
  return `${(ms / 1e3).toFixed(1)}s`;
3241
3289
  }
3242
- function formatTimeRange$1(startedAt, endedAt) {
3290
+ function formatTimeRange$1(startedAt, endedAt, timeDisplayFormat) {
3243
3291
  if (startedAt === null || endedAt === null) return null;
3244
- const format = (iso) => new Date(iso).toLocaleTimeString([], {
3245
- hour: "2-digit",
3246
- minute: "2-digit",
3247
- second: "2-digit"
3248
- });
3249
- return `${format(startedAt)} - ${format(endedAt)}`;
3292
+ return formatTimestampRange(startedAt, endedAt, timeDisplayFormat);
3293
+ }
3294
+ function formatCandidateCount(count) {
3295
+ return `${String(count)} candidate${count === 1 ? "" : "s"}`;
3296
+ }
3297
+ function getLogAnchor(logId) {
3298
+ return `log-${String(logId)}`;
3250
3299
  }
3251
- function scrollToLog(logId) {
3252
- const target = document.getElementById(`log-${String(logId)}`);
3300
+ function jumpToLog(logId) {
3301
+ const anchor = getLogAnchor(logId);
3302
+ const target = document.getElementById(anchor);
3303
+ window.history.replaceState(null, "", `#${anchor}`);
3253
3304
  if (!(target instanceof HTMLElement)) return;
3254
3305
  target.scrollIntoView({ block: "center", behavior: "smooth" });
3255
3306
  target.focus({ preventScroll: true });
3256
- if (target.getAttribute("data-nav-action") === "expand") {
3257
- target.click();
3307
+ }
3308
+ function firstFailureLog(logs) {
3309
+ for (const log of logs) {
3310
+ if (log.responseStatus !== null && log.responseStatus >= 400) return log;
3258
3311
  }
3312
+ return null;
3259
3313
  }
3260
- function CandidateList({ candidates }) {
3261
- if (candidates.length === 0) return null;
3262
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 grid gap-1.5", children: candidates.map((candidate) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3263
- "div",
3314
+ function slowestLog(logs) {
3315
+ let current = null;
3316
+ let currentElapsed = -1;
3317
+ for (const log of logs) {
3318
+ if (log.elapsedMs === null) continue;
3319
+ if (log.elapsedMs > currentElapsed) {
3320
+ current = log;
3321
+ currentElapsed = log.elapsedMs;
3322
+ }
3323
+ }
3324
+ return current;
3325
+ }
3326
+ function firstToolLog(logs) {
3327
+ for (const log of logs) {
3328
+ if (extractToolTraceEvents(log).length > 0) return log;
3329
+ }
3330
+ return null;
3331
+ }
3332
+ function buildTraceInsights(input) {
3333
+ const insights = [
3334
+ {
3335
+ kind: "session",
3336
+ title: `${String(input.summary.llmCallCount)} LLM call${input.summary.llmCallCount === 1 ? "" : "s"}`,
3337
+ detail: `Scope ${input.scopeId}`,
3338
+ logId: input.logs[0]?.id ?? null
3339
+ }
3340
+ ];
3341
+ const toolLog = firstToolLog(input.logs);
3342
+ if (input.summary.toolCallCount > 0 && toolLog !== null) {
3343
+ insights.push({
3344
+ kind: "tool",
3345
+ title: `${String(input.summary.toolCallCount)} tool call${input.summary.toolCallCount === 1 ? "" : "s"}`,
3346
+ detail: `First tool evidence at #${String(toolLog.id)}`,
3347
+ logId: toolLog.id
3348
+ });
3349
+ }
3350
+ const failure = firstFailureLog(input.logs);
3351
+ if (failure !== null) {
3352
+ insights.push({
3353
+ kind: "failure",
3354
+ title: `Failure #${String(failure.responseStatus)}`,
3355
+ detail: `First failed request is #${String(failure.id)}`,
3356
+ logId: failure.id
3357
+ });
3358
+ }
3359
+ const slowest = slowestLog(input.logs);
3360
+ if (slowest !== null && input.summary.maxElapsedMs !== null) {
3361
+ insights.push({
3362
+ kind: "slow",
3363
+ title: `Slowest ${formatElapsed(input.summary.maxElapsedMs)}`,
3364
+ detail: `Max latency observed at #${String(slowest.id)}`,
3365
+ logId: slowest.id
3366
+ });
3367
+ }
3368
+ if (input.candidates.length > 0) {
3369
+ const draftCount = input.candidates.filter((candidate) => candidate.status === "draft").length;
3370
+ const promotedCount = input.candidates.filter(
3371
+ (candidate) => candidate.status === "promoted"
3372
+ ).length;
3373
+ insights.push({
3374
+ kind: "candidate",
3375
+ title: `${String(input.candidates.length)} memory candidate${input.candidates.length === 1 ? "" : "s"}`,
3376
+ detail: `${String(draftCount)} draft / ${String(promotedCount)} promoted`,
3377
+ logId: input.candidates[0]?.logIds[0] ?? null
3378
+ });
3379
+ }
3380
+ return insights;
3381
+ }
3382
+ function insightIcon(kind) {
3383
+ switch (kind) {
3384
+ case "session":
3385
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3.5 text-blue-400" });
3386
+ case "tool":
3387
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3.5 text-sky-400/70" });
3388
+ case "slow":
3389
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3.5 text-amber-400" });
3390
+ case "failure":
3391
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(CircleX, { className: "size-3.5 text-destructive" });
3392
+ case "candidate":
3393
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(Brain, { className: "size-3.5 text-emerald-400" });
3394
+ }
3395
+ }
3396
+ function TraceInsights({ insights }) {
3397
+ if (insights.length === 0) return null;
3398
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 grid gap-1.5 border-t border-border/70 pt-2 md:grid-cols-2 xl:grid-cols-3", children: insights.map((insight) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3399
+ "button",
3264
3400
  {
3265
- className: "rounded-md border border-border/80 bg-background/60 px-2.5 py-2",
3401
+ type: "button",
3402
+ className: cn(
3403
+ "flex min-w-0 items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs",
3404
+ "text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground",
3405
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
3406
+ ),
3407
+ onClick: () => {
3408
+ if (insight.logId !== null) jumpToLog(insight.logId);
3409
+ },
3410
+ disabled: insight.logId === null,
3266
3411
  children: [
3267
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
3268
- /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "h-5 px-1.5 text-[10px] font-mono", children: candidate.type }),
3269
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 flex-1 truncate text-xs font-medium", title: candidate.title, children: candidate.title }),
3270
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 font-mono text-[10px] text-muted-foreground", children: candidate.status })
3412
+ insightIcon(insight.kind),
3413
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "min-w-0", children: [
3414
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block truncate font-medium text-foreground/90", children: insight.title }),
3415
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block truncate font-mono text-[10px]", children: insight.detail })
3416
+ ] })
3417
+ ]
3418
+ },
3419
+ `${insight.kind}-${insight.title}`
3420
+ )) });
3421
+ }
3422
+ function candidateStatusLabel(status) {
3423
+ switch (status) {
3424
+ case "draft":
3425
+ return "Draft";
3426
+ case "promoted":
3427
+ return "Promoted";
3428
+ case "failed":
3429
+ return "Failed";
3430
+ }
3431
+ }
3432
+ function candidatePromoteLabel(candidate, promoting) {
3433
+ if (promoting) return "Promoting";
3434
+ switch (candidate.status) {
3435
+ case "draft":
3436
+ return "Promote";
3437
+ case "failed":
3438
+ return "Retry";
3439
+ case "promoted":
3440
+ return "Promoted";
3441
+ }
3442
+ }
3443
+ function redactionLabel(candidate) {
3444
+ if (!candidate.redaction.redacted) return "No sensitive pattern matched";
3445
+ return `Redacted ${candidate.redaction.patterns.join(", ")}`;
3446
+ }
3447
+ function previewText(value, maxLength) {
3448
+ const normalized = value.replace(/\s+/g, " ").trim();
3449
+ if (normalized.length <= maxLength) return normalized;
3450
+ return `${normalized.slice(0, maxLength - 3)}...`;
3451
+ }
3452
+ function logRangeLabel(logIds) {
3453
+ const first = logIds[0];
3454
+ const last = logIds[logIds.length - 1];
3455
+ if (first === void 0) return "No evidence logs";
3456
+ if (last === void 0 || first === last) return `#${String(first)}`;
3457
+ return `#${String(first)}-#${String(last)}`;
3458
+ }
3459
+ function tagsToText(tags) {
3460
+ return tags.join(", ");
3461
+ }
3462
+ function textToTags(value) {
3463
+ return value.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
3464
+ }
3465
+ function CandidateItem({
3466
+ candidate,
3467
+ isPromoting,
3468
+ isUpdating,
3469
+ onPromoteCandidate,
3470
+ onUpdateCandidate
3471
+ }) {
3472
+ const [editing, setEditing] = reactExports.useState(false);
3473
+ const [draftType, setDraftType] = reactExports.useState(candidate.type);
3474
+ const [draftTitle, setDraftTitle] = reactExports.useState(candidate.title);
3475
+ const [draftContent, setDraftContent] = reactExports.useState(candidate.content);
3476
+ const [draftTags, setDraftTags] = reactExports.useState(tagsToText(candidate.tags));
3477
+ const [localError, setLocalError] = reactExports.useState(null);
3478
+ const canPromote = candidate.status !== "promoted";
3479
+ const canEdit = candidate.status !== "promoted";
3480
+ const resetDraft = reactExports.useCallback(() => {
3481
+ setDraftType(candidate.type);
3482
+ setDraftTitle(candidate.title);
3483
+ setDraftContent(candidate.content);
3484
+ setDraftTags(tagsToText(candidate.tags));
3485
+ setLocalError(null);
3486
+ }, [candidate.content, candidate.tags, candidate.title, candidate.type]);
3487
+ const saveDraft = reactExports.useCallback(async () => {
3488
+ const title = draftTitle.trim();
3489
+ const content = draftContent.trim();
3490
+ const tags = textToTags(draftTags);
3491
+ if (title.length === 0) {
3492
+ setLocalError("Title is required.");
3493
+ return;
3494
+ }
3495
+ if (content.length === 0) {
3496
+ setLocalError("Content is required.");
3497
+ return;
3498
+ }
3499
+ if (tags.length === 0) {
3500
+ setLocalError("At least one tag is required.");
3501
+ return;
3502
+ }
3503
+ setLocalError(null);
3504
+ const saved = await onUpdateCandidate(candidate.id, {
3505
+ type: draftType,
3506
+ title,
3507
+ content,
3508
+ tags
3509
+ });
3510
+ if (saved) setEditing(false);
3511
+ }, [candidate.id, draftContent, draftTags, draftTitle, draftType, onUpdateCandidate]);
3512
+ if (editing) {
3513
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-md border border-border/80 bg-background/60 px-2.5 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-2", children: [
3514
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
3515
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
3516
+ "select",
3517
+ {
3518
+ value: draftType,
3519
+ disabled: isUpdating,
3520
+ onChange: (event) => {
3521
+ const parsed = KnowledgeCandidateSchema.shape.type.safeParse(
3522
+ event.currentTarget.value
3523
+ );
3524
+ if (parsed.success) setDraftType(parsed.data);
3525
+ },
3526
+ className: "h-7 rounded-md border border-input bg-background px-2 text-xs",
3527
+ "aria-label": "Candidate type",
3528
+ children: [
3529
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "episode", children: "episode" }),
3530
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "procedure", children: "procedure" }),
3531
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "preference", children: "preference" }),
3532
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "project-fact", children: "project-fact" })
3533
+ ]
3534
+ }
3535
+ ),
3536
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3537
+ "input",
3538
+ {
3539
+ value: draftTitle,
3540
+ disabled: isUpdating,
3541
+ onChange: (event) => setDraftTitle(event.currentTarget.value),
3542
+ className: "h-7 min-w-[220px] flex-1 rounded-md border border-input bg-background px-2 text-xs",
3543
+ "aria-label": "Candidate title"
3544
+ }
3545
+ )
3546
+ ] }),
3547
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3548
+ "textarea",
3549
+ {
3550
+ value: draftContent,
3551
+ disabled: isUpdating,
3552
+ onChange: (event) => setDraftContent(event.currentTarget.value),
3553
+ className: "min-h-28 rounded-md border border-input bg-background px-2 py-1.5 font-mono text-[11px] leading-relaxed",
3554
+ "aria-label": "Candidate content"
3555
+ }
3556
+ ),
3557
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3558
+ "input",
3559
+ {
3560
+ value: draftTags,
3561
+ disabled: isUpdating,
3562
+ onChange: (event) => setDraftTags(event.currentTarget.value),
3563
+ className: "h-7 rounded-md border border-input bg-background px-2 text-xs",
3564
+ "aria-label": "Candidate tags"
3565
+ }
3566
+ ),
3567
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
3568
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-muted-foreground", children: [
3569
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldCheck, { className: "size-3" }),
3570
+ "Saving reruns Inspector redaction before promotion."
3271
3571
  ] }),
3272
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 flex flex-wrap items-center gap-1.5", children: candidate.logIds.map((logId) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3273
- "button",
3572
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
3573
+ localError !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-destructive", children: localError }),
3574
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
3575
+ Button,
3274
3576
  {
3275
3577
  type: "button",
3276
- onClick: () => scrollToLog(logId),
3277
- className: "rounded border border-border px-1.5 py-0.5 font-mono text-[10px] text-blue-400 transition-colors hover:bg-muted hover:text-blue-300",
3578
+ variant: "ghost",
3579
+ size: "sm",
3580
+ className: "h-7 gap-1.5 px-2 text-xs",
3581
+ disabled: isUpdating,
3582
+ onClick: () => {
3583
+ resetDraft();
3584
+ setEditing(false);
3585
+ },
3586
+ children: [
3587
+ /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "size-3.5" }),
3588
+ "Cancel"
3589
+ ]
3590
+ }
3591
+ ),
3592
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
3593
+ Button,
3594
+ {
3595
+ type: "button",
3596
+ variant: "outline",
3597
+ size: "sm",
3598
+ className: "h-7 gap-1.5 px-2 text-xs",
3599
+ disabled: isUpdating,
3600
+ onClick: () => {
3601
+ void saveDraft();
3602
+ },
3278
3603
  children: [
3279
- "#",
3280
- logId
3604
+ isUpdating ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "size-3.5" }),
3605
+ "Save"
3281
3606
  ]
3607
+ }
3608
+ )
3609
+ ] })
3610
+ ] }) });
3611
+ }
3612
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-md border border-border/80 bg-background/60 px-2.5 py-2", children: [
3613
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
3614
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "h-5 px-1.5 text-[10px] font-mono", children: candidate.type }),
3615
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 flex-1 truncate text-xs font-medium", title: candidate.title, children: candidate.title }),
3616
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3617
+ Badge,
3618
+ {
3619
+ variant: "outline",
3620
+ className: cn(
3621
+ "h-5 shrink-0 px-1.5 text-[10px] font-mono",
3622
+ CANDIDATE_STATUS_CLASSES[candidate.status]
3623
+ ),
3624
+ children: candidateStatusLabel(candidate.status)
3625
+ }
3626
+ )
3627
+ ] }),
3628
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3629
+ "p",
3630
+ {
3631
+ className: "mt-1 text-[11px] leading-relaxed text-muted-foreground",
3632
+ title: candidate.content,
3633
+ children: previewText(candidate.content, 360)
3634
+ }
3635
+ ),
3636
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[10px] text-muted-foreground", children: [
3637
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1", children: [
3638
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FileSearch, { className: "size-3" }),
3639
+ logRangeLabel(candidate.logIds)
3640
+ ] }),
3641
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1", children: [
3642
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldCheck, { className: "size-3" }),
3643
+ redactionLabel(candidate)
3644
+ ] }),
3645
+ candidate.status === "promoted" && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 text-emerald-500", children: [
3646
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { className: "size-3" }),
3647
+ candidate.openClawMemoryId ?? "OpenClaw"
3648
+ ] }),
3649
+ candidate.status === "failed" && candidate.error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 text-destructive", children: [
3650
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CircleX, { className: "size-3" }),
3651
+ candidate.error
3652
+ ] })
3653
+ ] }),
3654
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-1.5", children: [
3655
+ candidate.logIds.map((logId) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
3656
+ "a",
3657
+ {
3658
+ href: `#${getLogAnchor(logId)}`,
3659
+ onClick: (event) => {
3660
+ event.preventDefault();
3661
+ jumpToLog(logId);
3282
3662
  },
3283
- logId
3284
- )) })
3285
- ]
3663
+ 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",
3664
+ "aria-label": `Jump to evidence log ${String(logId)}`,
3665
+ children: [
3666
+ "#",
3667
+ logId
3668
+ ]
3669
+ },
3670
+ logId
3671
+ )),
3672
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
3673
+ canEdit && /* @__PURE__ */ jsxRuntimeExports.jsxs(
3674
+ Button,
3675
+ {
3676
+ type: "button",
3677
+ variant: "ghost",
3678
+ size: "sm",
3679
+ className: "h-6 gap-1.5 px-2 text-[10px]",
3680
+ disabled: isUpdating || isPromoting,
3681
+ onClick: () => setEditing(true),
3682
+ children: [
3683
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Pencil, { className: "size-3" }),
3684
+ "Review"
3685
+ ]
3686
+ }
3687
+ ),
3688
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
3689
+ Button,
3690
+ {
3691
+ type: "button",
3692
+ variant: canPromote ? "outline" : "ghost",
3693
+ size: "sm",
3694
+ className: "h-6 gap-1.5 px-2 text-[10px]",
3695
+ onClick: () => onPromoteCandidate(candidate.id),
3696
+ disabled: !canPromote || isPromoting || isUpdating,
3697
+ children: [
3698
+ isPromoting ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "size-3 animate-spin" }) : canPromote ? /* @__PURE__ */ jsxRuntimeExports.jsx(CloudUpload, { className: "size-3" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheck, { className: "size-3" }),
3699
+ candidatePromoteLabel(candidate, isPromoting)
3700
+ ]
3701
+ }
3702
+ )
3703
+ ] })
3704
+ ] });
3705
+ }
3706
+ function CandidateList({
3707
+ candidates,
3708
+ promotingCandidateIds,
3709
+ updatingCandidateIds,
3710
+ onPromoteCandidate,
3711
+ onUpdateCandidate
3712
+ }) {
3713
+ if (candidates.length === 0) return null;
3714
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 grid gap-1.5", children: candidates.map((candidate) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3715
+ CandidateItem,
3716
+ {
3717
+ candidate,
3718
+ isPromoting: promotingCandidateIds.has(candidate.id),
3719
+ isUpdating: updatingCandidateIds.has(candidate.id),
3720
+ onPromoteCandidate,
3721
+ onUpdateCandidate
3286
3722
  },
3287
3723
  candidate.id
3288
3724
  )) });
@@ -3290,20 +3726,35 @@ function CandidateList({ candidates }) {
3290
3726
  function AgentTraceSummary({
3291
3727
  logs,
3292
3728
  scopeId,
3293
- slowResponseThresholdSeconds
3729
+ slowResponseThresholdSeconds,
3730
+ showRollupMetrics,
3731
+ timeDisplayFormat
3294
3732
  }) {
3295
3733
  const [candidates, setCandidates] = reactExports.useState([]);
3734
+ const [candidatesExpanded, setCandidatesExpanded] = reactExports.useState(true);
3296
3735
  const [candidateState, setCandidateState] = reactExports.useState({
3297
3736
  status: "idle",
3298
3737
  error: null
3299
3738
  });
3739
+ const [promotingCandidateIds, setPromotingCandidateIds] = reactExports.useState(
3740
+ () => /* @__PURE__ */ new Set()
3741
+ );
3742
+ const [updatingCandidateIds, setUpdatingCandidateIds] = reactExports.useState(
3743
+ () => /* @__PURE__ */ new Set()
3744
+ );
3745
+ const hasCandidates = candidates.length > 0;
3300
3746
  const summary = reactExports.useMemo(
3301
3747
  () => buildTraceSummary(logs, slowResponseThresholdSeconds, candidates.length),
3302
3748
  [candidates.length, logs, slowResponseThresholdSeconds]
3303
3749
  );
3750
+ const traceInsights = reactExports.useMemo(
3751
+ () => buildTraceInsights({ logs, scopeId, summary, candidates }),
3752
+ [candidates, logs, scopeId, summary]
3753
+ );
3754
+ const showElapsedSummary = showRollupMetrics || summary.maxElapsedMs !== null;
3304
3755
  const timeRange = reactExports.useMemo(
3305
- () => formatTimeRange$1(summary.startedAt, summary.endedAt),
3306
- [summary.endedAt, summary.startedAt]
3756
+ () => formatTimeRange$1(summary.startedAt, summary.endedAt, timeDisplayFormat),
3757
+ [summary.endedAt, summary.startedAt, timeDisplayFormat]
3307
3758
  );
3308
3759
  const createCandidates = reactExports.useCallback(() => {
3309
3760
  if (logs.length === 0 || candidateState.status === "loading") return;
@@ -3324,7 +3775,10 @@ function AgentTraceSummary({
3324
3775
  }
3325
3776
  const parsed = await parseJsonResponse$1(response, CandidateResponseSchema);
3326
3777
  setCandidates(parsed.candidates);
3327
- setCandidateState({ status: "ready", error: null });
3778
+ setCandidatesExpanded(parsed.candidates.length > 0);
3779
+ setCandidateState(
3780
+ parsed.candidates.length > 0 ? { status: "ready", error: null } : { status: "failed", error: "No candidate was generated for this trace scope." }
3781
+ );
3328
3782
  } catch (error) {
3329
3783
  setCandidateState({
3330
3784
  status: "failed",
@@ -3333,20 +3787,119 @@ function AgentTraceSummary({
3333
3787
  }
3334
3788
  })();
3335
3789
  }, [candidateState.status, logs.length, scopeId]);
3790
+ const promoteCandidate = reactExports.useCallback((candidateId) => {
3791
+ setPromotingCandidateIds((current) => {
3792
+ if (current.has(candidateId)) return current;
3793
+ const next = new Set(current);
3794
+ next.add(candidateId);
3795
+ return next;
3796
+ });
3797
+ void (async () => {
3798
+ try {
3799
+ const response = await fetch(
3800
+ `/api/knowledge/candidates/${encodeURIComponent(candidateId)}/promote`,
3801
+ { method: "POST" }
3802
+ );
3803
+ const raw = await response.json().catch(() => null);
3804
+ const parsed = CandidatePromotionResponseSchema.safeParse(raw);
3805
+ if (response.ok && parsed.success) {
3806
+ setCandidates(
3807
+ (current) => current.map(
3808
+ (candidate) => candidate.id === parsed.data.candidate.id ? parsed.data.candidate : candidate
3809
+ )
3810
+ );
3811
+ setCandidateState({ status: "ready", error: null });
3812
+ return;
3813
+ }
3814
+ const failure = CandidatePromotionFailureResponseSchema.safeParse(raw);
3815
+ if (failure.success && failure.data.candidate !== void 0) {
3816
+ setCandidates(
3817
+ (current) => current.map(
3818
+ (candidate) => candidate.id === failure.data.candidate?.id ? failure.data.candidate : candidate
3819
+ )
3820
+ );
3821
+ }
3822
+ setCandidateState({
3823
+ status: "failed",
3824
+ error: failure.success ? failure.data.error ?? "Candidate promotion failed" : `Candidate promotion failed with ${String(response.status)}`
3825
+ });
3826
+ } catch (error) {
3827
+ setCandidateState({
3828
+ status: "failed",
3829
+ error: error instanceof Error ? error.message : "Candidate promotion failed"
3830
+ });
3831
+ } finally {
3832
+ setPromotingCandidateIds((current) => {
3833
+ const next = new Set(current);
3834
+ next.delete(candidateId);
3835
+ return next;
3836
+ });
3837
+ }
3838
+ })();
3839
+ }, []);
3840
+ const updateCandidate = reactExports.useCallback(
3841
+ async (candidateId, update) => {
3842
+ setUpdatingCandidateIds((current) => {
3843
+ if (current.has(candidateId)) return current;
3844
+ const next = new Set(current);
3845
+ next.add(candidateId);
3846
+ return next;
3847
+ });
3848
+ try {
3849
+ const response = await fetch(
3850
+ `/api/knowledge/candidates/${encodeURIComponent(candidateId)}`,
3851
+ {
3852
+ method: "PATCH",
3853
+ headers: { "Content-Type": "application/json" },
3854
+ body: JSON.stringify(update)
3855
+ }
3856
+ );
3857
+ if (!response.ok) {
3858
+ const message = await readApiError(
3859
+ response,
3860
+ `Candidate update failed with ${String(response.status)}`
3861
+ );
3862
+ setCandidateState({ status: "failed", error: message });
3863
+ return false;
3864
+ }
3865
+ const parsed = await parseJsonResponse$1(response, CandidateUpdateResponseSchema);
3866
+ setCandidates(
3867
+ (current) => current.map(
3868
+ (candidate) => candidate.id === parsed.candidate.id ? parsed.candidate : candidate
3869
+ )
3870
+ );
3871
+ setCandidateState({ status: "ready", error: null });
3872
+ return true;
3873
+ } catch (error) {
3874
+ setCandidateState({
3875
+ status: "failed",
3876
+ error: error instanceof Error ? error.message : "Candidate update failed"
3877
+ });
3878
+ return false;
3879
+ } finally {
3880
+ setUpdatingCandidateIds((current) => {
3881
+ const next = new Set(current);
3882
+ next.delete(candidateId);
3883
+ return next;
3884
+ });
3885
+ }
3886
+ },
3887
+ []
3888
+ );
3336
3889
  if (logs.length === 0) return null;
3337
3890
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "mb-2 rounded-lg border border-border bg-muted/10 px-3 py-2", children: [
3338
3891
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2 text-xs", children: [
3339
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 font-semibold text-foreground", children: [
3892
+ showRollupMetrics && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 font-semibold text-foreground", children: [
3340
3893
  /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3.5 text-blue-400" }),
3341
3894
  summary.llmCallCount,
3342
3895
  " LLM"
3343
3896
  ] }),
3344
3897
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3345
- /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3.5 text-amber-400" }),
3898
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Wrench, { className: "size-3.5 text-sky-400/70" }),
3346
3899
  summary.toolCallCount,
3347
3900
  " tools"
3348
3901
  ] }),
3349
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3902
+ showRollupMetrics && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3350
3903
  /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3.5 text-emerald-400" }),
3351
3904
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3352
3905
  formatTokens(summary.totalInputTokens),
@@ -3357,21 +3910,21 @@ function AgentTraceSummary({
3357
3910
  (summary.totalCacheCreationInputTokens > 0 || summary.totalCacheReadInputTokens > 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3358
3911
  /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3.5 text-purple-400" }),
3359
3912
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3360
- "+",
3913
+ "KV Cache +",
3361
3914
  formatTokens(summary.totalCacheCreationInputTokens),
3362
3915
  " / ~",
3363
3916
  formatTokens(summary.totalCacheReadInputTokens)
3364
3917
  ] })
3365
3918
  ] }),
3366
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3919
+ showElapsedSummary && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3367
3920
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3.5" }),
3368
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: formatElapsed(summary.totalElapsedMs) }),
3921
+ showRollupMetrics && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: formatElapsed(summary.totalElapsedMs) }),
3369
3922
  summary.maxElapsedMs !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-muted-foreground/70", children: [
3370
3923
  "max ",
3371
3924
  formatElapsed(summary.maxElapsedMs)
3372
3925
  ] })
3373
3926
  ] }),
3374
- timeRange !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-muted-foreground/70", children: timeRange }),
3927
+ showRollupMetrics && timeRange !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-muted-foreground/70", children: timeRange }),
3375
3928
  (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: [
3376
3929
  /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3.5" }),
3377
3930
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
@@ -3385,10 +3938,7 @@ function AgentTraceSummary({
3385
3938
  ] })
3386
3939
  ] }),
3387
3940
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1" }),
3388
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, { variant: "outline", className: "h-6 px-2 text-[10px] font-mono", children: [
3389
- summary.knowledgeCandidateCount,
3390
- " memory"
3391
- ] }),
3941
+ hasCandidates && /* @__PURE__ */ jsxRuntimeExports.jsx(Badge, { variant: "outline", className: "h-6 px-2 text-[10px] font-mono", children: formatCandidateCount(summary.knowledgeCandidateCount) }),
3392
3942
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
3393
3943
  Button,
3394
3944
  {
@@ -3403,10 +3953,33 @@ function AgentTraceSummary({
3403
3953
  "Candidate"
3404
3954
  ]
3405
3955
  }
3956
+ ),
3957
+ hasCandidates && /* @__PURE__ */ jsxRuntimeExports.jsx(
3958
+ Button,
3959
+ {
3960
+ type: "button",
3961
+ variant: "ghost",
3962
+ size: "icon",
3963
+ className: "size-7 text-muted-foreground",
3964
+ onClick: () => setCandidatesExpanded((value) => !value),
3965
+ "aria-expanded": candidatesExpanded,
3966
+ "aria-label": candidatesExpanded ? "Collapse memory candidates" : "Expand memory candidates",
3967
+ children: candidatesExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "size-3.5" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3.5" })
3968
+ }
3406
3969
  )
3407
3970
  ] }),
3408
3971
  candidateState.status === "failed" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-2 text-xs text-destructive", children: candidateState.error }),
3409
- /* @__PURE__ */ jsxRuntimeExports.jsx(CandidateList, { candidates })
3972
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TraceInsights, { insights: traceInsights }),
3973
+ candidatesExpanded && /* @__PURE__ */ jsxRuntimeExports.jsx(
3974
+ CandidateList,
3975
+ {
3976
+ candidates,
3977
+ promotingCandidateIds,
3978
+ updatingCandidateIds,
3979
+ onPromoteCandidate: promoteCandidate,
3980
+ onUpdateCandidate: updateCandidate
3981
+ }
3982
+ )
3410
3983
  ] });
3411
3984
  }
3412
3985
  function computeStats(logs) {
@@ -3427,7 +4000,9 @@ const ConversationGroup = reactExports.memo(function({
3427
4000
  onCompareWithPrevious,
3428
4001
  comparisonPredecessors,
3429
4002
  onClearGroup,
3430
- standalone = false
4003
+ standalone = false,
4004
+ hasPinnedSessionContext = false,
4005
+ timeDisplayFormat
3431
4006
  }) {
3432
4007
  const [expanded, setExpanded] = reactExports.useState(false);
3433
4008
  const stats = reactExports.useMemo(() => computeStats(group.logs), [group.logs]);
@@ -3435,6 +4010,7 @@ const ConversationGroup = reactExports.memo(function({
3435
4010
  const endTime = group.logs[group.logs.length - 1]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
3436
4011
  const mixed = hasMixedApiFormat(group.logs);
3437
4012
  const isLoading = group.logs.some((log) => log.responseStatus === null);
4013
+ const showTraceRollupMetrics = standalone && !hasPinnedSessionContext;
3438
4014
  const turnGroups = reactExports.useMemo(() => buildTurnGroups(group.logs), [group.logs]);
3439
4015
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-2", children: [
3440
4016
  !standalone && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -3452,6 +4028,7 @@ const ConversationGroup = reactExports.memo(function({
3452
4028
  hideApiFormat: mixed,
3453
4029
  isLoading,
3454
4030
  userAgent: group.logs[0]?.userAgent ?? null,
4031
+ timeDisplayFormat,
3455
4032
  onClear: () => onClearGroup(group.logs.map((l) => l.id))
3456
4033
  }
3457
4034
  ),
@@ -3461,7 +4038,9 @@ const ConversationGroup = reactExports.memo(function({
3461
4038
  {
3462
4039
  logs: group.logs,
3463
4040
  scopeId: group.conversationId,
3464
- slowResponseThresholdSeconds
4041
+ slowResponseThresholdSeconds,
4042
+ showRollupMetrics: showTraceRollupMetrics,
4043
+ timeDisplayFormat
3465
4044
  }
3466
4045
  ),
3467
4046
  turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -3471,6 +4050,7 @@ const ConversationGroup = reactExports.memo(function({
3471
4050
  viewMode,
3472
4051
  strip,
3473
4052
  slowResponseThresholdSeconds,
4053
+ timeDisplayFormat,
3474
4054
  cacheTrends,
3475
4055
  onCompareWithPrevious,
3476
4056
  comparisonPredecessors,
@@ -4051,26 +4631,18 @@ function TestStatus({ result }) {
4051
4631
  }
4052
4632
  if (result.cacheCreationInputTokens !== void 0 && result.cacheCreationInputTokens > 0) {
4053
4633
  tokenParts.push(
4054
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4055
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
4056
- "+",
4057
- result.cacheCreationInputTokens,
4058
- " cache"
4059
- ] }) }),
4060
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens cached for reuse, reducing future API cost" })
4061
- ] }) }, "cache-create")
4634
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-emerald-400", children: [
4635
+ "KV Cache +",
4636
+ result.cacheCreationInputTokens
4637
+ ] }, "cache-create")
4062
4638
  );
4063
4639
  }
4064
4640
  if (result.cacheReadInputTokens !== void 0 && result.cacheReadInputTokens > 0) {
4065
4641
  tokenParts.push(
4066
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tooltip, { children: [
4067
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
4068
- "~",
4069
- result.cacheReadInputTokens,
4070
- " cached"
4071
- ] }) }),
4072
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: "Tokens served from cache, reducing API cost" })
4073
- ] }) }, "cache-read")
4642
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono tabular-nums text-purple-400", children: [
4643
+ "KV Cache ~",
4644
+ result.cacheReadInputTokens
4645
+ ] }, "cache-read")
4074
4646
  );
4075
4647
  }
4076
4648
  const displayTokens = [];
@@ -5307,7 +5879,8 @@ function SettingsDialog() {
5307
5879
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, className: "flex-1 overflow-hidden", children: [
5308
5880
  /* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, { children: [
5309
5881
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "providers", children: "Providers" }),
5310
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "proxy", children: "Proxy" })
5882
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "proxy", children: "Proxy" }),
5883
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsTrigger, { value: "onboarding", children: "Onboarding" })
5311
5884
  ] }),
5312
5885
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-4 overflow-y-auto flex-1 pr-3", children: [
5313
5886
  /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "providers", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -5326,19 +5899,98 @@ function SettingsDialog() {
5326
5899
  onTestingTimeLeftChange: handleTestingTimeLeftChange
5327
5900
  }
5328
5901
  ) }),
5329
- /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "proxy", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProxySettingsTab, {}) })
5902
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "proxy", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ProxySettingsTab, {}) }),
5903
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "onboarding", children: /* @__PURE__ */ jsxRuntimeExports.jsx(OnboardingSettingsTab, {}) })
5330
5904
  ] })
5331
5905
  ] })
5332
5906
  ] })
5333
5907
  ] });
5334
5908
  }
5909
+ function CopyableSetupValue({
5910
+ id,
5911
+ label,
5912
+ value,
5913
+ copiedId,
5914
+ onCopy
5915
+ }) {
5916
+ const copied = copiedId === id;
5917
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-md border border-border bg-muted/20 px-3 py-2", children: [
5918
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-1 text-xs font-medium text-muted-foreground", children: label }),
5919
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
5920
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "min-w-0 flex-1 truncate font-mono text-xs text-foreground", children: value }),
5921
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
5922
+ Button,
5923
+ {
5924
+ type: "button",
5925
+ variant: "ghost",
5926
+ size: "icon",
5927
+ className: "size-7 shrink-0",
5928
+ onClick: () => onCopy(id, value),
5929
+ "aria-label": copied ? `Copied ${label}` : `Copy ${label}`,
5930
+ children: copied ? /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 text-emerald-500" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Copy, { className: "size-3.5" })
5931
+ }
5932
+ )
5933
+ ] })
5934
+ ] });
5935
+ }
5936
+ function OnboardingSettingsTab() {
5937
+ const [copiedId, setCopiedId] = reactExports.useState(null);
5938
+ const origin = reactExports.useMemo(() => {
5939
+ if (typeof window === "undefined") return "http://localhost:25947";
5940
+ return window.location.origin;
5941
+ }, []);
5942
+ const values = reactExports.useMemo(
5943
+ () => [
5944
+ { id: "skill", label: "Codex skill", value: "agent-inspector onboard --force" },
5945
+ { id: "mcp", label: "MCP URL", value: `${origin}/api/mcp` },
5946
+ { id: "proxy", label: "Proxy URL", value: `${origin}/proxy` },
5947
+ { id: "anthropic", label: "Anthropic base", value: `ANTHROPIC_BASE_URL=${origin}/proxy` }
5948
+ ],
5949
+ [origin]
5950
+ );
5951
+ const handleCopy = reactExports.useCallback((id, value) => {
5952
+ void window.navigator.clipboard.writeText(value).then(() => {
5953
+ setCopiedId(id);
5954
+ setTimeout(() => setCopiedId(null), 1600);
5955
+ });
5956
+ }, []);
5957
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
5958
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
5959
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Terminal, { className: "size-4 text-muted-foreground" }),
5960
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-semibold", children: "Agent onboarding" })
5961
+ ] }),
5962
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid gap-2", children: values.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx(
5963
+ CopyableSetupValue,
5964
+ {
5965
+ id: item.id,
5966
+ label: item.label,
5967
+ value: item.value,
5968
+ copiedId,
5969
+ onCopy: handleCopy
5970
+ },
5971
+ item.id
5972
+ )) }),
5973
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid gap-2 rounded-md border border-border bg-background px-3 py-2 text-xs text-muted-foreground", children: [
5974
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
5975
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 text-emerald-500" }),
5976
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Provider test creates a traceable memory probe session." })
5977
+ ] }),
5978
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
5979
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "size-3.5 text-emerald-500" }),
5980
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Captured sessions can produce reviewable memory candidates." })
5981
+ ] })
5982
+ ] })
5983
+ ] });
5984
+ }
5335
5985
  function ProxySettingsTab() {
5336
5986
  const {
5337
5987
  strip,
5338
5988
  slowResponseThresholdSeconds,
5989
+ timeDisplayFormat,
5339
5990
  isLoading,
5340
5991
  setStrip,
5341
- setSlowResponseThresholdSeconds
5992
+ setSlowResponseThresholdSeconds,
5993
+ setTimeDisplayFormat
5342
5994
  } = useStripConfig();
5343
5995
  const [error, setError] = reactExports.useState(null);
5344
5996
  const [pending, setPending] = reactExports.useState(false);
@@ -5370,6 +6022,20 @@ function ProxySettingsTab() {
5370
6022
  },
5371
6023
  [setSlowResponseThresholdSeconds]
5372
6024
  );
6025
+ const handleTimeDisplayFormatChange = reactExports.useCallback(
6026
+ async (next) => {
6027
+ setError(null);
6028
+ setPending(true);
6029
+ try {
6030
+ await setTimeDisplayFormat(next);
6031
+ } catch (err) {
6032
+ setError(err instanceof Error ? err.message : String(err));
6033
+ } finally {
6034
+ setPending(false);
6035
+ }
6036
+ },
6037
+ [setTimeDisplayFormat]
6038
+ );
5373
6039
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4", children: [
5374
6040
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
5375
6041
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-semibold", children: "Claude Code billing header" }),
@@ -5430,6 +6096,28 @@ function ProxySettingsTab() {
5430
6096
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground", children: "seconds" })
5431
6097
  ] })
5432
6098
  ] }),
6099
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-1", children: [
6100
+ /* @__PURE__ */ jsxRuntimeExports.jsx("label", { htmlFor: "time-display-format", className: "text-sm font-semibold", children: "Time display" }),
6101
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: "Controls timestamps in session summaries, conversation headers, and log rows." }),
6102
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
6103
+ "select",
6104
+ {
6105
+ id: "time-display-format",
6106
+ value: timeDisplayFormat,
6107
+ disabled: isLoading || pending,
6108
+ onChange: (event) => {
6109
+ const parsed = TimeDisplayFormatSchema.safeParse(event.currentTarget.value);
6110
+ if (!parsed.success) return;
6111
+ void handleTimeDisplayFormatChange(parsed.data);
6112
+ },
6113
+ className: "h-8 rounded-md border border-input bg-background px-2 text-sm disabled:cursor-not-allowed disabled:opacity-50",
6114
+ children: [
6115
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "time", children: "Time only" }),
6116
+ /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "full", children: "Full ISO" })
6117
+ ]
6118
+ }
6119
+ )
6120
+ ] }),
5433
6121
  error !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-destructive", children: [
5434
6122
  "Failed to save: ",
5435
6123
  error
@@ -5491,20 +6179,40 @@ function safeItemAt(items, index) {
5491
6179
  }
5492
6180
  function isEditableTarget(target) {
5493
6181
  const tag = target.tagName;
5494
- if (tag === "INPUT" || tag === "TEXTAREA") return true;
6182
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
5495
6183
  if (target.isContentEditable) return true;
5496
6184
  return false;
5497
6185
  }
5498
- function useKeyboardNavigation(containerRef, wrapperRef) {
6186
+ function isInteractiveTarget(target) {
6187
+ const tag = target.tagName;
6188
+ if (tag === "BUTTON" || tag === "A") return true;
6189
+ if (isEditableTarget(target)) return true;
6190
+ return target.closest("[role='button'],[role='menuitem'],[role='option']") !== null;
6191
+ }
6192
+ function isInOpenOverlay(target) {
6193
+ if (target.closest("[role='dialog'],[role='menu'],[role='listbox']") !== null) return true;
6194
+ return document.querySelector("[role='dialog'],[role='menu'],[role='listbox']") !== null;
6195
+ }
6196
+ function hasActiveTextSelection() {
6197
+ const selection = window.getSelection();
6198
+ if (selection === null) return false;
6199
+ return selection.type === "Range" && selection.toString().length > 0;
6200
+ }
6201
+ function isLetterNavigationKey(event, key) {
6202
+ return event.key === key && event.shiftKey;
6203
+ }
6204
+ function useKeyboardNavigation(containerRef, wrapperRef, options = {}) {
5499
6205
  const rootRef = wrapperRef ?? containerRef;
6206
+ const pageWide = options.pageWide === true;
5500
6207
  const handleFocusContainer = reactExports.useCallback(
5501
6208
  (e) => {
5502
6209
  const container = containerRef.current;
6210
+ const root = rootRef.current;
5503
6211
  if (!container) return;
6212
+ if (!root) return;
5504
6213
  const target = e.target;
5505
6214
  if (!isFocusTarget(target)) return;
5506
- if (target !== rootRef.current) return;
5507
- if (!container.contains(target)) return;
6215
+ if (target !== root) return;
5508
6216
  const items = findNavItems(container);
5509
6217
  const first = safeItemAt(items, 0);
5510
6218
  if (first !== null) focusAndScroll(first);
@@ -5512,14 +6220,22 @@ function useKeyboardNavigation(containerRef, wrapperRef) {
5512
6220
  [containerRef, rootRef]
5513
6221
  );
5514
6222
  reactExports.useEffect(() => {
5515
- const root = rootRef.current;
5516
- const container = containerRef.current;
5517
- if (!root || !container) return;
5518
6223
  const handleKeyDown = (e) => {
6224
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
6225
+ const container = containerRef.current;
6226
+ if (!container) return;
5519
6227
  const target = e.target;
5520
6228
  if (!isFocusTarget(target)) return;
5521
- if (!container.contains(target)) return;
5522
6229
  if (isEditableTarget(target)) return;
6230
+ if (hasActiveTextSelection()) return;
6231
+ const isInsideContainer = container.contains(target);
6232
+ if (isInsideContainer && isInteractiveTarget(target) && !target.hasAttribute(NAV_ATTR)) {
6233
+ return;
6234
+ }
6235
+ if (!isInsideContainer) {
6236
+ if (!pageWide) return;
6237
+ if (isInteractiveTarget(target) || isInOpenOverlay(target)) return;
6238
+ }
5523
6239
  const items = findNavItems(container);
5524
6240
  if (items.length === 0) return;
5525
6241
  const current = findClosestNavItem(target, container);
@@ -5528,7 +6244,8 @@ function useKeyboardNavigation(containerRef, wrapperRef) {
5528
6244
  switch (e.key) {
5529
6245
  case "ArrowUp":
5530
6246
  case "W": {
5531
- if (e.key === "W" && !e.shiftKey) break;
6247
+ if (e.shiftKey && e.key === "ArrowUp") break;
6248
+ if (e.key === "W" && !isLetterNavigationKey(e, "W")) break;
5532
6249
  e.preventDefault();
5533
6250
  const prevIdx = currentIdx > 0 ? currentIdx - 1 : currentIdx === -1 ? items.length - 1 : -1;
5534
6251
  if (prevIdx !== -1) {
@@ -5540,7 +6257,8 @@ function useKeyboardNavigation(containerRef, wrapperRef) {
5540
6257
  }
5541
6258
  case "ArrowDown":
5542
6259
  case "S": {
5543
- if (e.key === "S" && !e.shiftKey) break;
6260
+ if (e.shiftKey && e.key === "ArrowDown") break;
6261
+ if (e.key === "S" && !isLetterNavigationKey(e, "S")) break;
5544
6262
  e.preventDefault();
5545
6263
  const nextIdx = currentIdx < items.length - 1 ? currentIdx + 1 : currentIdx === -1 ? 0 : -1;
5546
6264
  if (nextIdx !== -1) {
@@ -5552,7 +6270,8 @@ function useKeyboardNavigation(containerRef, wrapperRef) {
5552
6270
  }
5553
6271
  case "ArrowLeft":
5554
6272
  case "A": {
5555
- if (e.key === "A" && !e.shiftKey) break;
6273
+ if (e.shiftKey && e.key === "ArrowLeft") break;
6274
+ if (e.key === "A" && !isLetterNavigationKey(e, "A")) break;
5556
6275
  if (current === null) break;
5557
6276
  e.preventDefault();
5558
6277
  const action = getAction(current);
@@ -5567,7 +6286,8 @@ function useKeyboardNavigation(containerRef, wrapperRef) {
5567
6286
  }
5568
6287
  case "ArrowRight":
5569
6288
  case "D": {
5570
- if (e.key === "D" && !e.shiftKey) break;
6289
+ if (e.shiftKey && e.key === "ArrowRight") break;
6290
+ if (e.key === "D" && !isLetterNavigationKey(e, "D")) break;
5571
6291
  if (current === null) break;
5572
6292
  e.preventDefault();
5573
6293
  const action = getAction(current);
@@ -5595,13 +6315,11 @@ function useKeyboardNavigation(containerRef, wrapperRef) {
5595
6315
  };
5596
6316
  document.addEventListener("keydown", handleKeyDown, { capture: true });
5597
6317
  return () => document.removeEventListener("keydown", handleKeyDown, { capture: true });
5598
- }, [containerRef, rootRef]);
6318
+ }, [containerRef, pageWide, rootRef]);
5599
6319
  reactExports.useEffect(() => {
5600
- const root = rootRef.current;
5601
- if (!root) return;
5602
- root.addEventListener("focus", handleFocusContainer);
5603
- return () => root.removeEventListener("focus", handleFocusContainer);
5604
- }, [handleFocusContainer, rootRef]);
6320
+ document.addEventListener("focusin", handleFocusContainer);
6321
+ return () => document.removeEventListener("focusin", handleFocusContainer);
6322
+ }, [handleFocusContainer]);
5605
6323
  }
5606
6324
  function truncateSessionId(id) {
5607
6325
  if (id.length <= 30) return id;
@@ -5616,16 +6334,11 @@ function computeTokenSummary(logs) {
5616
6334
  }
5617
6335
  return { totalIn, totalOut };
5618
6336
  }
5619
- function formatTimeRange(logs) {
6337
+ function formatTimeRange(logs, timeDisplayFormat) {
5620
6338
  const first = logs[0];
5621
6339
  const last = logs[logs.length - 1];
5622
6340
  if (first === void 0 || last === void 0) return null;
5623
- const format = (iso) => new Date(iso).toLocaleTimeString([], {
5624
- hour: "2-digit",
5625
- minute: "2-digit",
5626
- second: "2-digit"
5627
- });
5628
- return `${format(first.timestamp)} - ${format(last.timestamp)}`;
6341
+ return formatTimestampRange(first.timestamp, last.timestamp, timeDisplayFormat);
5629
6342
  }
5630
6343
  function getFirstUserAgent(logs) {
5631
6344
  for (const log of logs) {
@@ -5700,10 +6413,14 @@ function SessionContextBar({
5700
6413
  sessionId,
5701
6414
  logs,
5702
6415
  totalIn,
5703
- totalOut
6416
+ totalOut,
6417
+ timeDisplayFormat
5704
6418
  }) {
5705
6419
  const [copied, setCopied] = reactExports.useState(false);
5706
- const timeRange = reactExports.useMemo(() => formatTimeRange(logs), [logs]);
6420
+ const timeRange = reactExports.useMemo(
6421
+ () => formatTimeRange(logs, timeDisplayFormat),
6422
+ [logs, timeDisplayFormat]
6423
+ );
5707
6424
  const userAgent = reactExports.useMemo(() => getFirstUserAgent(logs), [logs]);
5708
6425
  const handleCopyLink = reactExports.useCallback(() => {
5709
6426
  void window.navigator.clipboard.writeText(window.location.href).then(() => {
@@ -5776,6 +6493,7 @@ function ProxyViewer({
5776
6493
  onViewModeChange,
5777
6494
  strip,
5778
6495
  slowResponseThresholdSeconds,
6496
+ timeDisplayFormat,
5779
6497
  hideSessionFilter = false,
5780
6498
  pinnedSessionId
5781
6499
  }) {
@@ -5787,7 +6505,7 @@ function ProxyViewer({
5787
6505
  );
5788
6506
  const logListRef = reactExports.useRef(null);
5789
6507
  const logListWrapperRef = reactExports.useRef(null);
5790
- useKeyboardNavigation(logListRef, logListWrapperRef);
6508
+ useKeyboardNavigation(logListRef, logListWrapperRef, { pageWide: true });
5791
6509
  reactExports.useEffect(() => {
5792
6510
  const perCrabDuration = 400;
5793
6511
  const startDelay = 50;
@@ -5822,6 +6540,7 @@ function ProxyViewer({
5822
6540
  setComparePair(null);
5823
6541
  }, []);
5824
6542
  const groups = reactExports.useMemo(() => groupLogsByConversation(logs), [logs]);
6543
+ const hasPinnedSessionContext = pinnedSessionId !== void 0;
5825
6544
  const cacheTrends = reactExports.useMemo(() => computeCacheTrends(groups), [groups]);
5826
6545
  const comparisonPredecessors = reactExports.useMemo(() => buildValidPredecessors(groups), [groups]);
5827
6546
  const handleCompareWithPrevious = reactExports.useCallback(
@@ -5895,7 +6614,8 @@ function ProxyViewer({
5895
6614
  sessionId: pinnedSessionId,
5896
6615
  logs,
5897
6616
  totalIn,
5898
- totalOut
6617
+ totalOut,
6618
+ timeDisplayFormat
5899
6619
  }
5900
6620
  ),
5901
6621
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
@@ -5934,7 +6654,7 @@ function ProxyViewer({
5934
6654
  )
5935
6655
  ] }),
5936
6656
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
5937
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
6657
+ !hasPinnedSessionContext && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground text-xs font-mono", children: [
5938
6658
  logs.length,
5939
6659
  " request",
5940
6660
  logs.length !== 1 ? "s" : "",
@@ -6005,7 +6725,9 @@ function ProxyViewer({
6005
6725
  onCompareWithPrevious: handleCompareWithPrevious,
6006
6726
  comparisonPredecessors,
6007
6727
  onClearGroup,
6008
- standalone: groups.length === 1
6728
+ standalone: groups.length === 1,
6729
+ hasPinnedSessionContext,
6730
+ timeDisplayFormat
6009
6731
  },
6010
6732
  group.id
6011
6733
  )) })
@@ -6263,7 +6985,7 @@ function ProxyViewerContainer({
6263
6985
  }
6264
6986
  })();
6265
6987
  }, []);
6266
- const { strip, slowResponseThresholdSeconds } = useStripConfig();
6988
+ const { strip, slowResponseThresholdSeconds, timeDisplayFormat } = useStripConfig();
6267
6989
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
6268
6990
  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 }),
6269
6991
  /* @__PURE__ */ jsxRuntimeExports.jsx(OnboardingBanner, {}),
@@ -6283,6 +7005,7 @@ function ProxyViewerContainer({
6283
7005
  onViewModeChange: setViewMode,
6284
7006
  strip,
6285
7007
  slowResponseThresholdSeconds,
7008
+ timeDisplayFormat,
6286
7009
  hideSessionFilter: initialSessionId !== void 0,
6287
7010
  pinnedSessionId: initialSessionId
6288
7011
  }
@@ -6312,5 +7035,6 @@ export {
6312
7035
  getStatusCategory as p,
6313
7036
  parseJsonText as q,
6314
7037
  resolveLogFormat as r,
6315
- safeJsonValue as s
7038
+ safeJsonValue as s,
7039
+ useProviders as u
6316
7040
  };