@tonyclaw/agent-inspector 2.0.1 → 2.0.3

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 (53) hide show
  1. package/.output/cli.js +344 -53
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-sVLGhCO3.js → CompareDrawer-D5A4bTfV.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-Da0jpBkp.js +101 -0
  5. package/.output/public/assets/{ReplayDialog-DxbFUqNW.js → ReplayDialog-CxUk_TF0.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-CSmGQa_g.js → RequestAnatomy-DIlzjgjJ.js} +1 -1
  7. package/.output/public/assets/ResponseView-DQCuKJ1G.js +1 -0
  8. package/.output/public/assets/{StreamingChunkSequence-BzqpY0TN.js → StreamingChunkSequence-DHk4SGGL.js} +1 -1
  9. package/.output/public/assets/_sessionId-dY1TTl7N.js +1 -0
  10. package/.output/public/assets/index-D7wwbwly.css +1 -0
  11. package/.output/public/assets/index-FqQZbfl2.js +1 -0
  12. package/.output/public/assets/{json-viewer-CKNMihlh.js → json-viewer-BbU0n8eM.js} +1 -1
  13. package/.output/public/assets/{main-yWf8dv9w.js → main-CZT_F-gu.js} +2 -2
  14. package/.output/server/_libs/lucide-react.mjs +8 -8
  15. package/.output/server/{_sessionId-DfHd0gd8.mjs → _sessionId-B-s9P7fJ.mjs} +2 -2
  16. package/.output/server/_ssr/{CompareDrawer-DGYAUWgF.mjs → CompareDrawer-C08L3UOO.mjs} +4 -4
  17. package/.output/server/_ssr/{ProxyViewerContainer-fawglkTo.mjs → ProxyViewerContainer-CMWl3Ijy.mjs} +414 -70
  18. package/.output/server/_ssr/{ReplayDialog-B4vlKa2W.mjs → ReplayDialog-CPDo9_G5.mjs} +4 -4
  19. package/.output/server/_ssr/{RequestAnatomy-BNQvEIZK.mjs → RequestAnatomy-D9wt_K1E.mjs} +3 -3
  20. package/.output/server/_ssr/{ResponseView-X6X6G16_.mjs → ResponseView-DXaL7nY3.mjs} +4 -4
  21. package/.output/server/_ssr/{StreamingChunkSequence-BPVN3MnF.mjs → StreamingChunkSequence-B_hudZyb.mjs} +3 -3
  22. package/.output/server/_ssr/{index-CXmpc2X5.mjs → index-CuE_BN86.mjs} +2 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-3XC3eq4R.mjs → json-viewer-Ci6kkjde.mjs} +2 -2
  25. package/.output/server/_ssr/{router-C0B2qvIM.mjs → router-BemxgIg7.mjs} +402 -131
  26. package/.output/server/{_tanstack-start-manifest_v-7tfsmd2I.mjs → _tanstack-start-manifest_v--L1_b4sd.mjs} +1 -1
  27. package/.output/server/index.mjs +62 -62
  28. package/README.md +50 -7
  29. package/package.json +3 -2
  30. package/scripts/setup-codex-skill.mjs +38 -0
  31. package/scripts/setup-windows-runtime.mjs +4 -3
  32. package/src/cli/onboard.ts +175 -68
  33. package/src/cli/templates/codex-skill-onboard.ts +210 -0
  34. package/src/components/providers/ProviderCard.tsx +2 -27
  35. package/src/components/providers/ProvidersPanel.tsx +16 -0
  36. package/src/components/proxy-viewer/AgentTraceSummary.tsx +218 -0
  37. package/src/components/proxy-viewer/ConversationGroup.tsx +6 -0
  38. package/src/components/proxy-viewer/ToolTraceEvents.tsx +33 -0
  39. package/src/components/proxy-viewer/TurnGroup.tsx +11 -1
  40. package/src/components/proxy-viewer/viewerState.ts +177 -0
  41. package/src/knowledge/openclawClient.ts +34 -5
  42. package/src/knowledge/openclawGatewayClient.ts +237 -0
  43. package/src/knowledge/openclawMarkdown.ts +146 -0
  44. package/src/lib/providerTestPrompt.ts +78 -0
  45. package/src/proxy/chunkStorage.ts +3 -4
  46. package/src/proxy/logger.ts +8 -15
  47. package/src/proxy/store.ts +8 -16
  48. package/src/routes/api/providers.$providerId.test.log.ts +7 -99
  49. package/.output/public/assets/ProxyViewerContainer-p9QvzZ6U.js +0 -101
  50. package/.output/public/assets/ResponseView-B5f89c8Z.js +0 -1
  51. package/.output/public/assets/_sessionId-BF7ftHV3.js +0 -1
  52. package/.output/public/assets/index-BU0PpLby.js +0 -1
  53. package/.output/public/assets/index-CpWG2hFn.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-C0B2qvIM.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";
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) {
@@ -275,7 +275,7 @@ function getStatusCategory(status) {
275
275
  if (status >= 500) return "server_error";
276
276
  return "pending";
277
277
  }
278
- const version = "2.0.1";
278
+ const version = "2.0.3";
279
279
  const packageJson = {
280
280
  version
281
281
  };
@@ -1343,27 +1343,27 @@ function useCopyFeedback(text) {
1343
1343
  return { copied, copy };
1344
1344
  }
1345
1345
  const LazyCompareDrawer = reactExports.lazy(
1346
- () => import("./CompareDrawer-DGYAUWgF.mjs").then((m) => ({ default: m.CompareDrawer }))
1346
+ () => import("./CompareDrawer-C08L3UOO.mjs").then((m) => ({ default: m.CompareDrawer }))
1347
1347
  );
1348
1348
  const LazyReplayDialog = reactExports.lazy(
1349
- () => import("./ReplayDialog-B4vlKa2W.mjs").then((m) => ({ default: m.ReplayDialog }))
1349
+ () => import("./ReplayDialog-CPDo9_G5.mjs").then((m) => ({ default: m.ReplayDialog }))
1350
1350
  );
1351
1351
  const LazyRequestAnatomy = reactExports.lazy(
1352
- () => import("./RequestAnatomy-BNQvEIZK.mjs").then((m) => ({ default: m.RequestAnatomy }))
1352
+ () => import("./RequestAnatomy-D9wt_K1E.mjs").then((m) => ({ default: m.RequestAnatomy }))
1353
1353
  );
1354
1354
  const LazyResponseView = reactExports.lazy(
1355
- () => import("./ResponseView-X6X6G16_.mjs").then((m) => ({ default: m.ResponseView }))
1355
+ () => import("./ResponseView-DXaL7nY3.mjs").then((m) => ({ default: m.ResponseView }))
1356
1356
  );
1357
1357
  const LazyStreamingChunkSequence = reactExports.lazy(
1358
- () => import("./StreamingChunkSequence-BPVN3MnF.mjs").then((m) => ({
1358
+ () => import("./StreamingChunkSequence-B_hudZyb.mjs").then((m) => ({
1359
1359
  default: m.StreamingChunkSequence
1360
1360
  }))
1361
1361
  );
1362
1362
  const LazyJsonViewer = reactExports.lazy(
1363
- () => import("./json-viewer-3XC3eq4R.mjs").then((m) => ({ default: m.JsonViewer }))
1363
+ () => import("./json-viewer-Ci6kkjde.mjs").then((m) => ({ default: m.JsonViewer }))
1364
1364
  );
1365
1365
  const LazyJsonViewerFromString = reactExports.lazy(
1366
- () => import("./json-viewer-3XC3eq4R.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1366
+ () => import("./json-viewer-Ci6kkjde.mjs").then((m) => ({ default: m.JsonViewerFromString }))
1367
1367
  );
1368
1368
  const HIGHLIGHT_DURATION_MS = 1200;
1369
1369
  const MAX_HIGHLIGHT_ATTEMPTS = 12;
@@ -1757,7 +1757,7 @@ const STATUS_BADGE_CLASSES = {
1757
1757
  server_error: "bg-rose-500/15 text-rose-400 border-rose-500/25",
1758
1758
  pending: "bg-muted text-muted-foreground border-border"
1759
1759
  };
1760
- function formatElapsed$1(ms) {
1760
+ function formatElapsed$2(ms) {
1761
1761
  if (ms < 1e3) return `${ms}ms`;
1762
1762
  return `${(ms / 1e3).toFixed(1)}s`;
1763
1763
  }
@@ -1849,12 +1849,12 @@ const LogEntryHeader = reactExports.memo(function({
1849
1849
  ),
1850
1850
  children: [
1851
1851
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
1852
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$1(log.elapsedMs) }),
1852
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$2(log.elapsedMs) }),
1853
1853
  isSlowResponse && /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3", "aria-label": "Slow response" })
1854
1854
  ]
1855
1855
  }
1856
1856
  ) }),
1857
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: isSlowResponse ? `Slow response: ${formatElapsed$1(log.elapsedMs)} exceeds ${formatElapsed$1(
1857
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: isSlowResponse ? `Slow response: ${formatElapsed$2(log.elapsedMs)} exceeds ${formatElapsed$2(
1858
1858
  slowResponseThresholdSeconds * 1e3
1859
1859
  )}` : "Elapsed response time" })
1860
1860
  ] }),
@@ -2764,6 +2764,29 @@ function ThreadConnector({
2764
2764
  ) })
2765
2765
  ] });
2766
2766
  }
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(
2770
+ "div",
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",
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
+ ] }),
2780
+ event.argumentsPreview !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
2781
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronRight, { className: "size-3 shrink-0 text-muted-foreground/60" }),
2782
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "min-w-0 truncate font-mono text-muted-foreground", children: event.argumentsPreview })
2783
+ ] })
2784
+ ]
2785
+ },
2786
+ event.id
2787
+ )) });
2788
+ }
2789
+ const PREVIEW_LIMIT = 180;
2767
2790
  function shouldRenderConversationContent(standalone, expanded) {
2768
2791
  return standalone || expanded;
2769
2792
  }
@@ -2801,7 +2824,135 @@ function buildValidPredecessors(groups) {
2801
2824
  }
2802
2825
  return predecessors;
2803
2826
  }
2804
- function formatElapsed(ms) {
2827
+ function parseJsonResponse(responseText) {
2828
+ if (responseText === null) return null;
2829
+ try {
2830
+ const parsed = JSON.parse(responseText);
2831
+ if (typeof parsed === "string") {
2832
+ return JSON.parse(parsed);
2833
+ }
2834
+ return parsed;
2835
+ } catch {
2836
+ return null;
2837
+ }
2838
+ }
2839
+ function previewValue(value) {
2840
+ if (value === void 0 || value === null) return null;
2841
+ const raw = typeof value === "string" ? value : JSON.stringify(value);
2842
+ if (raw === void 0) return null;
2843
+ const normalized = raw.replace(/\s+/g, " ").trim();
2844
+ if (normalized.length === 0) return null;
2845
+ return normalized.length > PREVIEW_LIMIT ? `${normalized.slice(0, PREVIEW_LIMIT - 1)}...` : normalized;
2846
+ }
2847
+ function extractAnthropicToolTraceEvents(log) {
2848
+ const parsed = parseJsonResponse(log.responseText);
2849
+ const content = safeGetOwnProperty(parsed, "content");
2850
+ if (!Array.isArray(content)) return [];
2851
+ const events = [];
2852
+ for (const block of content) {
2853
+ const type = safeGetOwnProperty(block, "type");
2854
+ if (type !== "tool_use") continue;
2855
+ const name = safeGetOwnProperty(block, "name");
2856
+ if (typeof name !== "string" || name.length === 0) continue;
2857
+ events.push({
2858
+ id: `${String(log.id)}-anthropic-tool-${String(events.length)}`,
2859
+ logId: log.id,
2860
+ index: events.length,
2861
+ provider: "anthropic",
2862
+ name,
2863
+ argumentsPreview: previewValue(safeGetOwnProperty(block, "input"))
2864
+ });
2865
+ }
2866
+ return events;
2867
+ }
2868
+ function extractOpenAIToolTraceEvents(log) {
2869
+ const parsed = parseJsonResponse(log.responseText);
2870
+ const choices = safeGetOwnProperty(parsed, "choices");
2871
+ if (!Array.isArray(choices)) return [];
2872
+ const events = [];
2873
+ for (const choice of choices) {
2874
+ const message = safeGetOwnProperty(choice, "message");
2875
+ const toolCalls = safeGetOwnProperty(message, "tool_calls");
2876
+ if (!Array.isArray(toolCalls)) continue;
2877
+ for (const call of toolCalls) {
2878
+ const fn = safeGetOwnProperty(call, "function");
2879
+ const name = safeGetOwnProperty(fn, "name");
2880
+ if (typeof name !== "string" || name.length === 0) continue;
2881
+ events.push({
2882
+ id: `${String(log.id)}-openai-tool-${String(events.length)}`,
2883
+ logId: log.id,
2884
+ index: events.length,
2885
+ provider: "openai",
2886
+ name,
2887
+ argumentsPreview: previewValue(safeGetOwnProperty(fn, "arguments"))
2888
+ });
2889
+ }
2890
+ }
2891
+ return events;
2892
+ }
2893
+ function extractToolTraceEvents(log) {
2894
+ const format = resolveLogFormat(log);
2895
+ switch (format) {
2896
+ case "anthropic":
2897
+ return extractAnthropicToolTraceEvents(log);
2898
+ case "openai":
2899
+ return extractOpenAIToolTraceEvents(log);
2900
+ case "unknown":
2901
+ return [];
2902
+ }
2903
+ }
2904
+ function buildTraceSummary(logs, slowResponseThresholdSeconds, knowledgeCandidateCount = 0) {
2905
+ let failedCallCount = 0;
2906
+ let pendingCallCount = 0;
2907
+ let slowCallCount = 0;
2908
+ let totalInputTokens = 0;
2909
+ let totalOutputTokens = 0;
2910
+ let totalCacheCreationInputTokens = 0;
2911
+ let totalCacheReadInputTokens = 0;
2912
+ let totalElapsedMs = 0;
2913
+ let maxElapsedMs = null;
2914
+ let toolCallCount = 0;
2915
+ for (const log of logs) {
2916
+ if (log.responseStatus === null) {
2917
+ pendingCallCount += 1;
2918
+ } else if (log.responseStatus >= 400) {
2919
+ failedCallCount += 1;
2920
+ }
2921
+ if (log.elapsedMs !== null && slowResponseThresholdSeconds > 0 && log.elapsedMs > slowResponseThresholdSeconds * 1e3) {
2922
+ slowCallCount += 1;
2923
+ }
2924
+ if (log.inputTokens !== null) totalInputTokens += log.inputTokens;
2925
+ if (log.outputTokens !== null) totalOutputTokens += log.outputTokens;
2926
+ if (log.cacheCreationInputTokens !== null) {
2927
+ totalCacheCreationInputTokens += log.cacheCreationInputTokens;
2928
+ }
2929
+ if (log.cacheReadInputTokens !== null) {
2930
+ totalCacheReadInputTokens += log.cacheReadInputTokens;
2931
+ }
2932
+ if (log.elapsedMs !== null) {
2933
+ totalElapsedMs += log.elapsedMs;
2934
+ maxElapsedMs = maxElapsedMs === null ? log.elapsedMs : Math.max(maxElapsedMs, log.elapsedMs);
2935
+ }
2936
+ toolCallCount += extractToolTraceEvents(log).length;
2937
+ }
2938
+ return {
2939
+ llmCallCount: logs.length,
2940
+ toolCallCount,
2941
+ failedCallCount,
2942
+ pendingCallCount,
2943
+ slowCallCount,
2944
+ totalInputTokens,
2945
+ totalOutputTokens,
2946
+ totalCacheCreationInputTokens,
2947
+ totalCacheReadInputTokens,
2948
+ totalElapsedMs,
2949
+ maxElapsedMs,
2950
+ startedAt: logs[0]?.timestamp ?? null,
2951
+ endedAt: logs[logs.length - 1]?.timestamp ?? null,
2952
+ knowledgeCandidateCount
2953
+ };
2954
+ }
2955
+ function formatElapsed$1(ms) {
2805
2956
  if (ms < 1e3) return `${ms}ms`;
2806
2957
  return `${(ms / 1e3).toFixed(1)}s`;
2807
2958
  }
@@ -2871,6 +3022,14 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
2871
3022
  const EndCrab = reactExports.useMemo(() => getCrabVariant(entries[lastIdx]?.log.id ?? 0), [entries, lastIdx]);
2872
3023
  const bgClass = turnIndex % 2 === 0 ? "bg-muted/10" : "bg-muted/25";
2873
3024
  const aggregateIsSlow = aggregate.maxElapsed !== null && slowResponseThresholdSeconds > 0 && aggregate.maxElapsed > slowResponseThresholdSeconds * 1e3;
3025
+ const toolEventsByLogId = reactExports.useMemo(() => {
3026
+ const events = /* @__PURE__ */ new Map();
3027
+ for (const entry of entries) {
3028
+ const extracted = extractToolTraceEvents(entry.log);
3029
+ if (extracted.length > 0) events.set(entry.log.id, extracted);
3030
+ }
3031
+ return events;
3032
+ }, [entries]);
2874
3033
  const [layoutVersion, setLayoutVersion] = reactExports.useState(0);
2875
3034
  const containerRef = reactExports.useRef(null);
2876
3035
  reactExports.useEffect(() => {
@@ -3001,14 +3160,14 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3001
3160
  ),
3002
3161
  children: [
3003
3162
  /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3" }),
3004
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed(aggregate.maxElapsed) }),
3163
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono tabular-nums", children: formatElapsed$1(aggregate.maxElapsed) }),
3005
3164
  aggregateIsSlow && /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3", "aria-label": "Slow response" })
3006
3165
  ]
3007
3166
  }
3008
3167
  ) }),
3009
- /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: aggregateIsSlow ? `Slow response: ${formatElapsed(
3168
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TooltipContent, { children: aggregateIsSlow ? `Slow response: ${formatElapsed$1(
3010
3169
  aggregate.maxElapsed
3011
- )} exceeds ${formatElapsed(slowResponseThresholdSeconds * 1e3)}` : "Slowest request in this turn" })
3170
+ )} exceeds ${formatElapsed$1(slowResponseThresholdSeconds * 1e3)}` : "Slowest request in this turn" })
3012
3171
  ] }) }),
3013
3172
  aggregate.hasTokens && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 shrink-0", children: [
3014
3173
  /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3 text-muted-foreground" }),
@@ -3052,23 +3211,204 @@ const TurnGroup = reactExports.memo(function TurnGroup2({
3052
3211
  onToggle: toggleCollapse
3053
3212
  }
3054
3213
  ),
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
- ) })
3214
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cn("flex-1 min-w-0 mb-0.5 rounded-lg", bgClass), children: [
3215
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3216
+ LogEntry,
3217
+ {
3218
+ log,
3219
+ viewMode,
3220
+ strip,
3221
+ slowResponseThresholdSeconds,
3222
+ cacheTrend: cacheTrends?.get(log.id) ?? null,
3223
+ onCompareWithPrevious: comparisonPredecessors.has(log.id) ? onCompareWithPrevious : void 0
3224
+ }
3225
+ ),
3226
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ToolTraceEvents, { events: toolEventsByLogId.get(log.id) ?? [] })
3227
+ ] })
3066
3228
  ] }, log.id);
3067
3229
  })
3068
3230
  )
3069
3231
  }
3070
3232
  );
3071
3233
  });
3234
+ const CandidateResponseSchema = object({
3235
+ candidates: array(KnowledgeCandidateSchema)
3236
+ });
3237
+ function formatElapsed(ms) {
3238
+ if (ms === null) return "-";
3239
+ if (ms < 1e3) return `${String(ms)}ms`;
3240
+ return `${(ms / 1e3).toFixed(1)}s`;
3241
+ }
3242
+ function formatTimeRange$1(startedAt, endedAt) {
3243
+ 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)}`;
3250
+ }
3251
+ function scrollToLog(logId) {
3252
+ const target = document.getElementById(`log-${String(logId)}`);
3253
+ if (!(target instanceof HTMLElement)) return;
3254
+ target.scrollIntoView({ block: "center", behavior: "smooth" });
3255
+ target.focus({ preventScroll: true });
3256
+ if (target.getAttribute("data-nav-action") === "expand") {
3257
+ target.click();
3258
+ }
3259
+ }
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",
3264
+ {
3265
+ className: "rounded-md border border-border/80 bg-background/60 px-2.5 py-2",
3266
+ 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 })
3271
+ ] }),
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",
3274
+ {
3275
+ 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",
3278
+ children: [
3279
+ "#",
3280
+ logId
3281
+ ]
3282
+ },
3283
+ logId
3284
+ )) })
3285
+ ]
3286
+ },
3287
+ candidate.id
3288
+ )) });
3289
+ }
3290
+ function AgentTraceSummary({
3291
+ logs,
3292
+ scopeId,
3293
+ slowResponseThresholdSeconds
3294
+ }) {
3295
+ const [candidates, setCandidates] = reactExports.useState([]);
3296
+ const [candidateState, setCandidateState] = reactExports.useState({
3297
+ status: "idle",
3298
+ error: null
3299
+ });
3300
+ const summary = reactExports.useMemo(
3301
+ () => buildTraceSummary(logs, slowResponseThresholdSeconds, candidates.length),
3302
+ [candidates.length, logs, slowResponseThresholdSeconds]
3303
+ );
3304
+ const timeRange = reactExports.useMemo(
3305
+ () => formatTimeRange$1(summary.startedAt, summary.endedAt),
3306
+ [summary.endedAt, summary.startedAt]
3307
+ );
3308
+ const createCandidates = reactExports.useCallback(() => {
3309
+ if (logs.length === 0 || candidateState.status === "loading") return;
3310
+ setCandidateState({ status: "loading", error: null });
3311
+ void (async () => {
3312
+ try {
3313
+ const response = await fetch(
3314
+ `/api/knowledge/sessions/${encodeURIComponent(scopeId)}/candidates`,
3315
+ { method: "POST" }
3316
+ );
3317
+ if (!response.ok) {
3318
+ const message = await readApiError(
3319
+ response,
3320
+ `Candidate generation failed with ${String(response.status)}`
3321
+ );
3322
+ setCandidateState({ status: "failed", error: message });
3323
+ return;
3324
+ }
3325
+ const parsed = await parseJsonResponse$1(response, CandidateResponseSchema);
3326
+ setCandidates(parsed.candidates);
3327
+ setCandidateState({ status: "ready", error: null });
3328
+ } catch (error) {
3329
+ setCandidateState({
3330
+ status: "failed",
3331
+ error: error instanceof Error ? error.message : "Candidate response was invalid"
3332
+ });
3333
+ }
3334
+ })();
3335
+ }, [candidateState.status, logs.length, scopeId]);
3336
+ if (logs.length === 0) return null;
3337
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { className: "mb-2 rounded-lg border border-border bg-muted/10 px-3 py-2", children: [
3338
+ /* @__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: [
3340
+ /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "size-3.5 text-blue-400" }),
3341
+ summary.llmCallCount,
3342
+ " LLM"
3343
+ ] }),
3344
+ /* @__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" }),
3346
+ summary.toolCallCount,
3347
+ " tools"
3348
+ ] }),
3349
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3350
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3.5 text-emerald-400" }),
3351
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3352
+ formatTokens(summary.totalInputTokens),
3353
+ " / ",
3354
+ formatTokens(summary.totalOutputTokens)
3355
+ ] })
3356
+ ] }),
3357
+ (summary.totalCacheCreationInputTokens > 0 || summary.totalCacheReadInputTokens > 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3358
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Zap, { className: "size-3.5 text-purple-400" }),
3359
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3360
+ "+",
3361
+ formatTokens(summary.totalCacheCreationInputTokens),
3362
+ " / ~",
3363
+ formatTokens(summary.totalCacheReadInputTokens)
3364
+ ] })
3365
+ ] }),
3366
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1.5 text-muted-foreground", children: [
3367
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { className: "size-3.5" }),
3368
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: formatElapsed(summary.totalElapsedMs) }),
3369
+ summary.maxElapsedMs !== null && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-muted-foreground/70", children: [
3370
+ "max ",
3371
+ formatElapsed(summary.maxElapsedMs)
3372
+ ] })
3373
+ ] }),
3374
+ timeRange !== null && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-muted-foreground/70", children: timeRange }),
3375
+ (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
+ /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "size-3.5" }),
3377
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [
3378
+ summary.failedCallCount,
3379
+ " fail / ",
3380
+ summary.pendingCallCount,
3381
+ " pending /",
3382
+ " ",
3383
+ summary.slowCallCount,
3384
+ " slow"
3385
+ ] })
3386
+ ] }),
3387
+ /* @__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
+ ] }),
3392
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
3393
+ Button,
3394
+ {
3395
+ type: "button",
3396
+ variant: "outline",
3397
+ size: "sm",
3398
+ className: "h-7 gap-1.5 px-2 text-xs",
3399
+ onClick: createCandidates,
3400
+ disabled: candidateState.status === "loading",
3401
+ children: [
3402
+ candidateState.status === "loading" ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Brain, { className: "size-3.5" }),
3403
+ "Candidate"
3404
+ ]
3405
+ }
3406
+ )
3407
+ ] }),
3408
+ candidateState.status === "failed" && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "mt-2 text-xs text-destructive", children: candidateState.error }),
3409
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CandidateList, { candidates })
3410
+ ] });
3411
+ }
3072
3412
  function computeStats(logs) {
3073
3413
  let totalInput = 0;
3074
3414
  let totalOutput = 0;
@@ -3115,20 +3455,30 @@ const ConversationGroup = reactExports.memo(function({
3115
3455
  onClear: () => onClearGroup(group.logs.map((l) => l.id))
3116
3456
  }
3117
3457
  ),
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
- )) })
3458
+ shouldRenderConversationContent(standalone, expanded) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
3459
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
3460
+ AgentTraceSummary,
3461
+ {
3462
+ logs: group.logs,
3463
+ scopeId: group.conversationId,
3464
+ slowResponseThresholdSeconds
3465
+ }
3466
+ ),
3467
+ turnGroups.map((tg) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3468
+ TurnGroup,
3469
+ {
3470
+ entries: tg.entries,
3471
+ viewMode,
3472
+ strip,
3473
+ slowResponseThresholdSeconds,
3474
+ cacheTrends,
3475
+ onCompareWithPrevious,
3476
+ comparisonPredecessors,
3477
+ turnIndex: tg.turnIndex
3478
+ },
3479
+ tg.turnIndex
3480
+ ))
3481
+ ] })
3132
3482
  ] });
3133
3483
  });
3134
3484
  function CrabLogo({ className }) {
@@ -3798,26 +4148,6 @@ function ProviderCard({
3798
4148
  const [showApiKey, setShowApiKey] = reactExports.useState(false);
3799
4149
  const [copied, setCopied] = reactExports.useState(false);
3800
4150
  const [showModelResults, setShowModelResults] = reactExports.useState(false);
3801
- const hasLoggedRef = reactExports.useRef(null);
3802
- const lastTestedAtRef = reactExports.useRef(void 0);
3803
- if (testResults?.testedAt !== void 0 && testResults.testedAt !== lastTestedAtRef.current) {
3804
- lastTestedAtRef.current = testResults.testedAt;
3805
- hasLoggedRef.current = null;
3806
- }
3807
- const handleToggleModelResults = reactExports.useCallback(() => {
3808
- setShowModelResults((v) => {
3809
- const next = !v;
3810
- if (next && hasLoggedRef.current === null && testResults?.models !== void 0) {
3811
- hasLoggedRef.current = testResults.testedAt ?? "";
3812
- void fetch(`/api/providers/${provider.id}/test/log`, {
3813
- method: "POST",
3814
- headers: { "Content-Type": "application/json" },
3815
- body: JSON.stringify(testResults)
3816
- });
3817
- }
3818
- return next;
3819
- });
3820
- }, [provider.id, testResults]);
3821
4151
  function handleCopy() {
3822
4152
  navigator.clipboard.writeText(provider.apiKey).catch(() => {
3823
4153
  });
@@ -3911,7 +4241,7 @@ function ProviderCard({
3911
4241
  "button",
3912
4242
  {
3913
4243
  type: "button",
3914
- onClick: handleToggleModelResults,
4244
+ onClick: () => setShowModelResults((value) => !value),
3915
4245
  className: "text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1",
3916
4246
  children: [
3917
4247
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: showModelResults ? "▾" : "▸" }),
@@ -4429,6 +4759,19 @@ function createProviderPayload(data) {
4429
4759
  source: data.source
4430
4760
  };
4431
4761
  }
4762
+ async function persistProviderTestLog(providerId, results) {
4763
+ if (results.models === void 0) {
4764
+ return;
4765
+ }
4766
+ try {
4767
+ await fetch(`/api/providers/${providerId}/test/log`, {
4768
+ method: "POST",
4769
+ headers: { "Content-Type": "application/json" },
4770
+ body: JSON.stringify(results)
4771
+ });
4772
+ } catch {
4773
+ }
4774
+ }
4432
4775
  function ProvidersPanel({
4433
4776
  externalProviders,
4434
4777
  isLoading = false,
@@ -4487,7 +4830,7 @@ function ProvidersPanel({
4487
4830
  try {
4488
4831
  const res = await fetch("/api/config/paths");
4489
4832
  if (res.ok) {
4490
- const data = await parseJsonResponse(res, ConfigPathsResponseSchema);
4833
+ const data = await parseJsonResponse$1(res, ConfigPathsResponseSchema);
4491
4834
  setConfigPath(data.providerConfig);
4492
4835
  }
4493
4836
  } catch {
@@ -4566,8 +4909,9 @@ function ProvidersPanel({
4566
4909
  signal: controller.signal
4567
4910
  });
4568
4911
  if (res.ok) {
4569
- const results = await parseJsonResponse(res, ProviderTestResultsSchema);
4912
+ const results = await parseJsonResponse$1(res, ProviderTestResultsSchema);
4570
4913
  updateTestResults(providerId, results);
4914
+ await persistProviderTestLog(providerId, results);
4571
4915
  } else {
4572
4916
  updateTestResults(
4573
4917
  providerId,
@@ -4605,7 +4949,7 @@ function ProvidersPanel({
4605
4949
  setError(await readApiError(res, "Failed to add provider"));
4606
4950
  return;
4607
4951
  }
4608
- const newProvider = await parseJsonResponse(res, ProviderConfigSchema);
4952
+ const newProvider = await parseJsonResponse$1(res, ProviderConfigSchema);
4609
4953
  setShowForm(false);
4610
4954
  triggerHighlight(newProvider.id);
4611
4955
  refreshProviders();
@@ -4628,7 +4972,7 @@ function ProvidersPanel({
4628
4972
  setError(await readApiError(res, "Failed to update provider"));
4629
4973
  return;
4630
4974
  }
4631
- const updated = await parseJsonResponse(res, ProviderConfigSchema);
4975
+ const updated = await parseJsonResponse$1(res, ProviderConfigSchema);
4632
4976
  setEditingProvider(void 0);
4633
4977
  triggerHighlight(updated.id);
4634
4978
  refreshProviders();
@@ -4703,7 +5047,7 @@ function ProvidersPanel({
4703
5047
  headers: { "Content-Type": "application/json" },
4704
5048
  body: JSON.stringify(text)
4705
5049
  });
4706
- const data = await parseJsonResponse(res, ImportResponseSchema);
5050
+ const data = await parseJsonResponse$1(res, ImportResponseSchema);
4707
5051
  if (res.ok && data.imported !== void 0 && data.imported > 0) {
4708
5052
  refreshProviders();
4709
5053
  setError(null);