@tonyclaw/agent-inspector 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-Bp7_x-5N.js → CompareDrawer-BCH_fsLm.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-D85_UANk.js +101 -0
  4. package/.output/public/assets/{ReplayDialog-DFHCd0yx.js → ReplayDialog-DTeaHHit.js} +1 -1
  5. package/.output/public/assets/RequestAnatomy-DZ8grAih.js +1 -0
  6. package/.output/public/assets/ResponseView-Cldm6RCi.js +1 -0
  7. package/.output/public/assets/{StreamingChunkSequence-Bjs4Lqwn.js → StreamingChunkSequence-3x4p-yT7.js} +1 -1
  8. package/.output/public/assets/_sessionId-YqWFBu6d.js +1 -0
  9. package/.output/public/assets/index-BIw2H6jO.js +1 -0
  10. package/.output/public/assets/index-CobXD0yH.css +1 -0
  11. package/.output/public/assets/{json-viewer-6uV_YXws.js → json-viewer-BrzjD7qI.js} +1 -1
  12. package/.output/public/assets/{main-FSGUGtEL.js → main-mgxeUdZQ.js} +2 -2
  13. package/.output/server/_libs/lucide-react.mjs +8 -8
  14. package/.output/server/{_sessionId-_bf9vUww.mjs → _sessionId-C4xsxIWm.mjs} +2 -2
  15. package/.output/server/_ssr/{CompareDrawer-DIth2DQM.mjs → CompareDrawer-DuWEpqQ7.mjs} +4 -4
  16. package/.output/server/_ssr/{ProxyViewerContainer-249bTH-T.mjs → ProxyViewerContainer-Cckz5qKu.mjs} +519 -89
  17. package/.output/server/_ssr/{ReplayDialog-C1aGx0y1.mjs → ReplayDialog-BDRcr8E5.mjs} +4 -4
  18. package/.output/server/_ssr/{RequestAnatomy-D2bCiEJn.mjs → RequestAnatomy-BoO2_Ij0.mjs} +5 -5
  19. package/.output/server/_ssr/{ResponseView-DP6k4Xs_.mjs → ResponseView-DZiPBxvO.mjs} +21 -17
  20. package/.output/server/_ssr/{StreamingChunkSequence-HyXZV-b5.mjs → StreamingChunkSequence-D-be7KEL.mjs} +3 -3
  21. package/.output/server/_ssr/{index-Bt47f9pn.mjs → index-5RImHKfu.mjs} +2 -2
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{json-viewer-Co-YRwUP.mjs → json-viewer-aJhb93ZK.mjs} +2 -2
  24. package/.output/server/_ssr/{router-to_OJirX.mjs → router-Dgkv5nKP.mjs} +38 -99
  25. package/.output/server/{_tanstack-start-manifest_v-Bd-2YRWo.mjs → _tanstack-start-manifest_v-B8rrWXjr.mjs} +1 -1
  26. package/.output/server/index.mjs +63 -63
  27. package/README.md +5 -2
  28. package/package.json +1 -1
  29. package/src/components/ProxyViewer.tsx +25 -15
  30. package/src/components/ProxyViewerContainer.tsx +2 -1
  31. package/src/components/providers/SettingsDialog.tsx +45 -1
  32. package/src/components/proxy-viewer/AgentTraceSummary.tsx +276 -0
  33. package/src/components/proxy-viewer/AnswerMarkdown.tsx +16 -0
  34. package/src/components/proxy-viewer/ConversationGroup.tsx +18 -0
  35. package/src/components/proxy-viewer/ConversationHeader.tsx +6 -6
  36. package/src/components/proxy-viewer/LogEntry.tsx +5 -5
  37. package/src/components/proxy-viewer/LogEntryHeader.tsx +9 -14
  38. package/src/components/proxy-viewer/ResponseView.tsx +2 -6
  39. package/src/components/proxy-viewer/ToolTraceEvents.tsx +32 -0
  40. package/src/components/proxy-viewer/TurnGroup.tsx +15 -1
  41. package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +2 -2
  42. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +6 -12
  43. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +10 -14
  44. package/src/components/proxy-viewer/viewerState.ts +177 -0
  45. package/src/lib/runtimeConfig.ts +6 -0
  46. package/src/lib/timeDisplay.ts +22 -0
  47. package/src/lib/useOnboarding.ts +2 -0
  48. package/src/lib/useStripConfig.ts +16 -0
  49. package/src/proxy/chunkStorage.ts +3 -4
  50. package/src/proxy/config.ts +3 -0
  51. package/src/proxy/logger.ts +8 -15
  52. package/src/proxy/store.ts +8 -16
  53. package/src/routes/api/config.ts +5 -1
  54. package/src/routes/api/providers.$providerId.test.log.ts +0 -79
  55. package/.output/public/assets/ProxyViewerContainer-USuxPy-K.js +0 -101
  56. package/.output/public/assets/RequestAnatomy-ehyrskxt.js +0 -1
  57. package/.output/public/assets/ResponseView-BNGyc8e_.js +0 -1
  58. package/.output/public/assets/_sessionId-D_SeK_qp.js +0 -1
  59. package/.output/public/assets/index-BGGOWR7A.js +0 -1
  60. package/.output/public/assets/index-CIL46Z2y.css +0 -1
@@ -38,107 +38,107 @@ const assets = {
38
38
  "/assets/alibaba-TTwafVwX.svg": {
39
39
  "type": "image/svg+xml",
40
40
  "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
41
- "mtime": "2026-06-20T06:54:35.499Z",
41
+ "mtime": "2026-06-20T09:50:40.188Z",
42
42
  "size": 5915,
43
43
  "path": "../public/assets/alibaba-TTwafVwX.svg"
44
44
  },
45
- "/assets/CompareDrawer-Bp7_x-5N.js": {
45
+ "/assets/index-BIw2H6jO.js": {
46
46
  "type": "text/javascript; charset=utf-8",
47
- "etag": '"4a1f-jydruemAUSMwVzObceAGMUJLrQ8"',
48
- "mtime": "2026-06-20T06:54:35.500Z",
49
- "size": 18975,
50
- "path": "../public/assets/CompareDrawer-Bp7_x-5N.js"
47
+ "etag": '"74-4Z3U3XPh22QGtEz8rgtC6KUxxBk"',
48
+ "mtime": "2026-06-20T09:50:40.189Z",
49
+ "size": 116,
50
+ "path": "../public/assets/index-BIw2H6jO.js"
51
+ },
52
+ "/assets/index-CobXD0yH.css": {
53
+ "type": "text/css; charset=utf-8",
54
+ "etag": '"17587-wM3UwOtYjpQy81oA4sjkIdizScg"',
55
+ "mtime": "2026-06-20T09:50:40.188Z",
56
+ "size": 95623,
57
+ "path": "../public/assets/index-CobXD0yH.css"
51
58
  },
52
59
  "/assets/minimax-BPMzvuL-.jpeg": {
53
60
  "type": "image/jpeg",
54
61
  "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
55
- "mtime": "2026-06-20T06:54:35.499Z",
62
+ "mtime": "2026-06-20T09:50:40.188Z",
56
63
  "size": 6918,
57
64
  "path": "../public/assets/minimax-BPMzvuL-.jpeg"
58
65
  },
59
- "/assets/json-viewer-6uV_YXws.js": {
66
+ "/assets/json-viewer-BrzjD7qI.js": {
60
67
  "type": "text/javascript; charset=utf-8",
61
- "etag": '"1e651-ErvitreVwBoWFEPdSIq0EqZIuw4"',
62
- "mtime": "2026-06-20T06:54:35.501Z",
63
- "size": 124497,
64
- "path": "../public/assets/json-viewer-6uV_YXws.js"
68
+ "etag": '"1e652-kToNy9EOWKlmvVVK2ggoK6Yj4is"',
69
+ "mtime": "2026-06-20T09:50:40.189Z",
70
+ "size": 124498,
71
+ "path": "../public/assets/json-viewer-BrzjD7qI.js"
65
72
  },
66
- "/assets/index-CIL46Z2y.css": {
67
- "type": "text/css; charset=utf-8",
68
- "etag": '"17078-OCA3ki0c0CZu1AhDXwlliM+a+qY"',
69
- "mtime": "2026-06-20T06:54:35.499Z",
70
- "size": 94328,
71
- "path": "../public/assets/index-CIL46Z2y.css"
73
+ "/assets/main-mgxeUdZQ.js": {
74
+ "type": "text/javascript; charset=utf-8",
75
+ "etag": '"5138c-8pFfB8xlDwdi154QNl3icGhbrO0"',
76
+ "mtime": "2026-06-20T09:50:40.189Z",
77
+ "size": 332684,
78
+ "path": "../public/assets/main-mgxeUdZQ.js"
72
79
  },
73
- "/assets/index-BGGOWR7A.js": {
80
+ "/assets/RequestAnatomy-DZ8grAih.js": {
74
81
  "type": "text/javascript; charset=utf-8",
75
- "etag": '"74-JGabHf17Kno+3wF7wJwm2nHkVhg"',
76
- "mtime": "2026-06-20T06:54:35.500Z",
77
- "size": 116,
78
- "path": "../public/assets/index-BGGOWR7A.js"
82
+ "etag": '"1426-KgPrtOak3tXSA1eBiDgPYDF0NzY"',
83
+ "mtime": "2026-06-20T09:50:40.189Z",
84
+ "size": 5158,
85
+ "path": "../public/assets/RequestAnatomy-DZ8grAih.js"
79
86
  },
80
- "/assets/StreamingChunkSequence-Bjs4Lqwn.js": {
87
+ "/assets/ReplayDialog-DTeaHHit.js": {
81
88
  "type": "text/javascript; charset=utf-8",
82
- "etag": '"d81-yAIItbapwm0q/MbLRpCH915YVMc"',
83
- "mtime": "2026-06-20T06:54:35.501Z",
84
- "size": 3457,
85
- "path": "../public/assets/StreamingChunkSequence-Bjs4Lqwn.js"
89
+ "etag": '"11c0-vzfqRFtK58XnhNRimd5URd22Gcs"',
90
+ "mtime": "2026-06-20T09:50:40.189Z",
91
+ "size": 4544,
92
+ "path": "../public/assets/ReplayDialog-DTeaHHit.js"
86
93
  },
87
- "/assets/RequestAnatomy-ehyrskxt.js": {
94
+ "/assets/ResponseView-Cldm6RCi.js": {
88
95
  "type": "text/javascript; charset=utf-8",
89
- "etag": '"142a-KSCjJNt2RlqmIm3y55z67ygsWGU"',
90
- "mtime": "2026-06-20T06:54:35.500Z",
91
- "size": 5162,
92
- "path": "../public/assets/RequestAnatomy-ehyrskxt.js"
96
+ "etag": '"6b01-Qix5qsvqjrCTAlqfCJ+Pp8h1e9I"',
97
+ "mtime": "2026-06-20T09:50:40.189Z",
98
+ "size": 27393,
99
+ "path": "../public/assets/ResponseView-Cldm6RCi.js"
100
+ },
101
+ "/assets/StreamingChunkSequence-3x4p-yT7.js": {
102
+ "type": "text/javascript; charset=utf-8",
103
+ "etag": '"d81-mHjIspQgaAOB4LmN6Cfnz+NcWOM"',
104
+ "mtime": "2026-06-20T09:50:40.189Z",
105
+ "size": 3457,
106
+ "path": "../public/assets/StreamingChunkSequence-3x4p-yT7.js"
93
107
  },
94
108
  "/assets/zhipuai-BPNAnxo-.svg": {
95
109
  "type": "image/svg+xml",
96
110
  "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
97
- "mtime": "2026-06-20T06:54:35.499Z",
111
+ "mtime": "2026-06-20T09:50:40.188Z",
98
112
  "size": 11256,
99
113
  "path": "../public/assets/zhipuai-BPNAnxo-.svg"
100
114
  },
101
- "/assets/main-FSGUGtEL.js": {
102
- "type": "text/javascript; charset=utf-8",
103
- "etag": '"5138c-aZy2XlKYAw/+FTrgYI1Iz4MHtww"',
104
- "mtime": "2026-06-20T06:54:35.500Z",
105
- "size": 332684,
106
- "path": "../public/assets/main-FSGUGtEL.js"
107
- },
108
- "/assets/ProxyViewerContainer-USuxPy-K.js": {
115
+ "/assets/CompareDrawer-BCH_fsLm.js": {
109
116
  "type": "text/javascript; charset=utf-8",
110
- "etag": '"774b9-fxnez8ICkM1Nb7unXdv1GIz+RbA"',
111
- "mtime": "2026-06-20T06:54:35.500Z",
112
- "size": 488633,
113
- "path": "../public/assets/ProxyViewerContainer-USuxPy-K.js"
117
+ "etag": '"4a1f-Y215Tlm1TY2GPfXfX4DX86bgJPU"',
118
+ "mtime": "2026-06-20T09:50:40.189Z",
119
+ "size": 18975,
120
+ "path": "../public/assets/CompareDrawer-BCH_fsLm.js"
114
121
  },
115
- "/assets/_sessionId-D_SeK_qp.js": {
122
+ "/assets/_sessionId-YqWFBu6d.js": {
116
123
  "type": "text/javascript; charset=utf-8",
117
- "etag": '"d2-pqW14/f9UjxfuYAzRgQmGaiZhbs"',
118
- "mtime": "2026-06-20T06:54:35.500Z",
124
+ "etag": '"d2-hOIbwqwgtPs0H/vPSZ1gDPz5pFU"',
125
+ "mtime": "2026-06-20T09:50:40.189Z",
119
126
  "size": 210,
120
- "path": "../public/assets/_sessionId-D_SeK_qp.js"
121
- },
122
- "/assets/ResponseView-BNGyc8e_.js": {
123
- "type": "text/javascript; charset=utf-8",
124
- "etag": '"6e91-De46gQnfLBYdkbMBsXcKinAu7mE"',
125
- "mtime": "2026-06-20T06:54:35.501Z",
126
- "size": 28305,
127
- "path": "../public/assets/ResponseView-BNGyc8e_.js"
127
+ "path": "../public/assets/_sessionId-YqWFBu6d.js"
128
128
  },
129
129
  "/assets/qwen-CONDcHqt.png": {
130
130
  "type": "image/png",
131
131
  "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
132
- "mtime": "2026-06-20T06:54:35.500Z",
132
+ "mtime": "2026-06-20T09:50:40.188Z",
133
133
  "size": 357059,
134
134
  "path": "../public/assets/qwen-CONDcHqt.png"
135
135
  },
136
- "/assets/ReplayDialog-DFHCd0yx.js": {
136
+ "/assets/ProxyViewerContainer-D85_UANk.js": {
137
137
  "type": "text/javascript; charset=utf-8",
138
- "etag": '"11c0-ZB9rU8oZnq77GG7kiCz6W03GjPI"',
139
- "mtime": "2026-06-20T06:54:35.500Z",
140
- "size": 4544,
141
- "path": "../public/assets/ReplayDialog-DFHCd0yx.js"
138
+ "etag": '"79ec4-8t+BfQ9tY8ia35/KlmoU69wDlc8"',
139
+ "mtime": "2026-06-20T09:50:40.189Z",
140
+ "size": 499396,
141
+ "path": "../public/assets/ProxyViewerContainer-D85_UANk.js"
142
142
  }
143
143
  };
144
144
  function readAsset(id) {
package/README.md CHANGED
@@ -28,7 +28,8 @@ therefore appears in Task Manager as **Agent Inspector <version>** with the
28
28
  project icon. The executable also carries standard `FileVersion` and
29
29
  `ProductVersion` metadata for the installed package version. If npm lifecycle
30
30
  scripts are disabled, Agent Inspector falls back to `node.exe` and keeps
31
- working normally.
31
+ working normally. Set `AGENT_INSPECTOR_SKIP_WINDOWS_RUNTIME=1` to skip the
32
+ Windows runtime metadata step explicitly.
32
33
 
33
34
  For Linux containers, the Windows runtime setup is a no-op. To keep images lean,
34
35
  install with optional dependencies omitted:
@@ -51,7 +52,9 @@ The onboarding command installs guided setup skills for local agents:
51
52
 
52
53
  During npm global install, Agent Inspector also makes a best-effort Codex skill install when
53
54
  `~/.codex` already exists. Set `AGENT_INSPECTOR_SKIP_CODEX_SKILL=1` to skip that postinstall step,
54
- or run `agent-inspector onboard --codex-only --force` later to refresh it.
55
+ set `AGENT_INSPECTOR_INSTALL_CODEX_SKILL=1` to attempt it even before `~/.codex` exists, or run
56
+ `agent-inspector onboard --codex-only --force` later to refresh it. For a full refresh of all
57
+ generated onboarding files, run `agent-inspector onboard --force`.
55
58
 
56
59
  For local development from source:
57
60
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/agent-inspector",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "type": "module",
5
5
  "description": "Agent observability and knowledge capture layer for AI coding tools.",
6
6
  "license": "MIT",
@@ -3,6 +3,8 @@ import { ArrowLeft, Check, Copy, Download, Plus } from "lucide-react";
3
3
 
4
4
  import type { CapturedLog } from "../proxy/schemas";
5
5
  import { exportLogsAsZip } from "../lib/export-logs";
6
+ import type { TimeDisplayFormat } from "../lib/runtimeConfig";
7
+ import { formatTimestampRange } from "../lib/timeDisplay";
6
8
  import { formatTokens } from "../lib/utils";
7
9
  import packageJson from "../../package.json";
8
10
  import { ConversationGroup, groupLogsByConversation } from "./proxy-viewer";
@@ -33,17 +35,11 @@ function computeTokenSummary(logs: CapturedLog[]): { totalIn: number; totalOut:
33
35
  return { totalIn, totalOut };
34
36
  }
35
37
 
36
- function formatTimeRange(logs: CapturedLog[]): string | null {
38
+ function formatTimeRange(logs: CapturedLog[], timeDisplayFormat: TimeDisplayFormat): string | null {
37
39
  const first = logs[0];
38
40
  const last = logs[logs.length - 1];
39
41
  if (first === undefined || last === undefined) return null;
40
- const format = (iso: string): string =>
41
- new Date(iso).toLocaleTimeString([], {
42
- hour: "2-digit",
43
- minute: "2-digit",
44
- second: "2-digit",
45
- });
46
- return `${format(first.timestamp)} - ${format(last.timestamp)}`;
42
+ return formatTimestampRange(first.timestamp, last.timestamp, timeDisplayFormat);
47
43
  }
48
44
 
49
45
  function getFirstUserAgent(logs: CapturedLog[]): string | null {
@@ -132,14 +128,19 @@ function SessionContextBar({
132
128
  logs,
133
129
  totalIn,
134
130
  totalOut,
131
+ timeDisplayFormat,
135
132
  }: {
136
133
  sessionId: string;
137
134
  logs: CapturedLog[];
138
135
  totalIn: number;
139
136
  totalOut: number;
137
+ timeDisplayFormat: TimeDisplayFormat;
140
138
  }): JSX.Element {
141
139
  const [copied, setCopied] = useState(false);
142
- const timeRange = useMemo(() => formatTimeRange(logs), [logs]);
140
+ const timeRange = useMemo(
141
+ () => formatTimeRange(logs, timeDisplayFormat),
142
+ [logs, timeDisplayFormat],
143
+ );
143
144
  const userAgent = useMemo(() => getFirstUserAgent(logs), [logs]);
144
145
 
145
146
  const handleCopyLink = useCallback(() => {
@@ -216,6 +217,8 @@ export type ProxyViewerProps = {
216
217
  strip: boolean;
217
218
  /** Slow-response threshold in seconds. `0` disables the warning indicator. */
218
219
  slowResponseThresholdSeconds: number;
220
+ /** Controls whether timestamps render as compact local time or full ISO strings. */
221
+ timeDisplayFormat: TimeDisplayFormat;
219
222
  /** Hide the session filter dropdown. Used on `/session/$id` routes where
220
223
  * the session is already pinned by the URL and the dropdown would just
221
224
  * fight the URL state. */
@@ -238,6 +241,7 @@ export function ProxyViewer({
238
241
  onViewModeChange,
239
242
  strip,
240
243
  slowResponseThresholdSeconds,
244
+ timeDisplayFormat,
241
245
  hideSessionFilter = false,
242
246
  pinnedSessionId,
243
247
  }: ProxyViewerProps): JSX.Element {
@@ -291,6 +295,7 @@ export function ProxyViewer({
291
295
  }, []);
292
296
 
293
297
  const groups = useMemo(() => groupLogsByConversation(logs), [logs]);
298
+ const hasPinnedSessionContext = pinnedSessionId !== undefined;
294
299
  const cacheTrends = useMemo(() => computeCacheTrends(groups), [groups]);
295
300
  const comparisonPredecessors = useMemo(() => buildValidPredecessors(groups), [groups]);
296
301
  const handleCompareWithPrevious = useCallback(
@@ -380,6 +385,7 @@ export function ProxyViewer({
380
385
  logs={logs}
381
386
  totalIn={totalIn}
382
387
  totalOut={totalOut}
388
+ timeDisplayFormat={timeDisplayFormat}
383
389
  />
384
390
  )}
385
391
 
@@ -438,12 +444,14 @@ export function ProxyViewer({
438
444
  </button>
439
445
  </div>
440
446
  <div className="flex-1" />
441
- <span className="text-muted-foreground text-xs font-mono">
442
- {logs.length} request{logs.length !== 1 ? "s" : ""}
443
- {totalIn > 0 || totalOut > 0
444
- ? ` · ${formatTokens(totalIn)} in / ${formatTokens(totalOut)} out`
445
- : ""}
446
- </span>
447
+ {!hasPinnedSessionContext && (
448
+ <span className="text-muted-foreground text-xs font-mono">
449
+ {logs.length} request{logs.length !== 1 ? "s" : ""}
450
+ {totalIn > 0 || totalOut > 0
451
+ ? ` · ${formatTokens(totalIn)} in / ${formatTokens(totalOut)} out`
452
+ : ""}
453
+ </span>
454
+ )}
447
455
  {logs.length > 0 && (
448
456
  <button
449
457
  type="button"
@@ -527,6 +535,8 @@ export function ProxyViewer({
527
535
  comparisonPredecessors={comparisonPredecessors}
528
536
  onClearGroup={onClearGroup}
529
537
  standalone={groups.length === 1}
538
+ hasPinnedSessionContext={hasPinnedSessionContext}
539
+ timeDisplayFormat={timeDisplayFormat}
530
540
  />
531
541
  ))}
532
542
  </div>
@@ -330,7 +330,7 @@ export function ProxyViewerContainer({
330
330
 
331
331
  // Read the strip config once at the container so the virtualized list does
332
332
  // not need N independent SWR subscriptions per row.
333
- const { strip, slowResponseThresholdSeconds } = useStripConfig();
333
+ const { strip, slowResponseThresholdSeconds, timeDisplayFormat } = useStripConfig();
334
334
 
335
335
  return (
336
336
  <>
@@ -354,6 +354,7 @@ export function ProxyViewerContainer({
354
354
  onViewModeChange={setViewMode}
355
355
  strip={strip}
356
356
  slowResponseThresholdSeconds={slowResponseThresholdSeconds}
357
+ timeDisplayFormat={timeDisplayFormat}
357
358
  // Session filter is the URL's job when `initialSessionId` was given.
358
359
  hideSessionFilter={initialSessionId !== undefined}
359
360
  pinnedSessionId={initialSessionId}
@@ -6,7 +6,11 @@ import { Button } from "../ui/button";
6
6
  import { ProvidersPanel } from "./ProvidersPanel";
7
7
  import { useProviders } from "../../lib/useProviders";
8
8
  import { useStripConfig } from "../../lib/useStripConfig";
9
- import { MAX_SLOW_RESPONSE_THRESHOLD_SECONDS } from "../../lib/runtimeConfig";
9
+ import {
10
+ MAX_SLOW_RESPONSE_THRESHOLD_SECONDS,
11
+ TimeDisplayFormatSchema,
12
+ type TimeDisplayFormat,
13
+ } from "../../lib/runtimeConfig";
10
14
 
11
15
  export function SettingsDialog(): JSX.Element {
12
16
  const [open, setOpen] = useState(false);
@@ -100,9 +104,11 @@ function ProxySettingsTab(): JSX.Element {
100
104
  const {
101
105
  strip,
102
106
  slowResponseThresholdSeconds,
107
+ timeDisplayFormat,
103
108
  isLoading,
104
109
  setStrip,
105
110
  setSlowResponseThresholdSeconds,
111
+ setTimeDisplayFormat,
106
112
  } = useStripConfig();
107
113
  const [error, setError] = useState<string | null>(null);
108
114
  const [pending, setPending] = useState(false);
@@ -137,6 +143,21 @@ function ProxySettingsTab(): JSX.Element {
137
143
  [setSlowResponseThresholdSeconds],
138
144
  );
139
145
 
146
+ const handleTimeDisplayFormatChange = useCallback(
147
+ async (next: TimeDisplayFormat) => {
148
+ setError(null);
149
+ setPending(true);
150
+ try {
151
+ await setTimeDisplayFormat(next);
152
+ } catch (err) {
153
+ setError(err instanceof Error ? err.message : String(err));
154
+ } finally {
155
+ setPending(false);
156
+ }
157
+ },
158
+ [setTimeDisplayFormat],
159
+ );
160
+
140
161
  return (
141
162
  <div className="space-y-4">
142
163
  <div className="space-y-1">
@@ -196,6 +217,29 @@ function ProxySettingsTab(): JSX.Element {
196
217
  </div>
197
218
  </div>
198
219
 
220
+ <div className="space-y-1">
221
+ <label htmlFor="time-display-format" className="text-sm font-semibold">
222
+ Time display
223
+ </label>
224
+ <p className="text-xs text-muted-foreground">
225
+ Controls timestamps in session summaries, conversation headers, and log rows.
226
+ </p>
227
+ <select
228
+ id="time-display-format"
229
+ value={timeDisplayFormat}
230
+ disabled={isLoading || pending}
231
+ onChange={(event) => {
232
+ const parsed = TimeDisplayFormatSchema.safeParse(event.currentTarget.value);
233
+ if (!parsed.success) return;
234
+ void handleTimeDisplayFormatChange(parsed.data);
235
+ }}
236
+ className="h-8 rounded-md border border-input bg-background px-2 text-sm disabled:cursor-not-allowed disabled:opacity-50"
237
+ >
238
+ <option value="time">Time only</option>
239
+ <option value="full">Full ISO</option>
240
+ </select>
241
+ </div>
242
+
199
243
  {error !== null && <p className="text-xs text-destructive">Failed to save: {error}</p>}
200
244
  </div>
201
245
  );