@tonyclaw/agent-inspector 3.0.24 → 3.0.26

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 (39) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-BC52FYwn.js → CompareDrawer-SZRUDQc9.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-BJvwOrSi.js +4234 -0
  4. package/.output/public/assets/{ReplayDialog-BcKsarKt.js → ReplayDialog-DgfmqiM3.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-DR0uGpCj.js → RequestAnatomy-CS6c7Qfn.js} +1 -1
  6. package/.output/public/assets/{ResponseView-kxAAPfmn.js → ResponseView-C0Ri_5gm.js} +2 -2
  7. package/.output/public/assets/{StreamingChunkSequence-YQR0PjQj.js → StreamingChunkSequence-7dYcHWyZ.js} +1 -1
  8. package/.output/public/assets/{_sessionId-DRl-Y9om.js → _sessionId-D-V84cgw.js} +1 -1
  9. package/.output/public/assets/{index-CQbrc-hi.js → index-CSIL7-MK.js} +3 -3
  10. package/.output/public/assets/index-CmeF5XXS.css +1 -0
  11. package/.output/public/assets/{index-CcSIBI4L.js → index-DgFWCxOs.js} +1 -1
  12. package/.output/public/assets/{index-ZrMgyEZV.js → index-DrVJsks2.js} +1 -1
  13. package/.output/public/assets/{json-viewer-CS5pwnEA.js → json-viewer-C9Ji3gYN.js} +1 -1
  14. package/.output/server/_libs/lucide-react.mjs +245 -217
  15. package/.output/server/_libs/three.mjs +26016 -0
  16. package/.output/server/{_sessionId-BLk2xPkR.mjs → _sessionId-BQ6YQOO1.mjs} +7 -6
  17. package/.output/server/_ssr/{CompareDrawer-Cw7Dseo3.mjs → CompareDrawer-CqNzIeMR.mjs} +8 -7
  18. package/.output/server/_ssr/{ProxyViewerContainer-D8SB0zyQ.mjs → ProxyViewerContainer-CMC7Vsbw.mjs} +1164 -405
  19. package/.output/server/_ssr/{ReplayDialog-B-YMhZkE.mjs → ReplayDialog-D9p_LaTr.mjs} +9 -8
  20. package/.output/server/_ssr/{RequestAnatomy-a9d8UTYZ.mjs → RequestAnatomy-B4ge3qNZ.mjs} +8 -7
  21. package/.output/server/_ssr/{ResponseView-PzaZS4CN.mjs → ResponseView-BCqMKApI.mjs} +8 -7
  22. package/.output/server/_ssr/{StreamingChunkSequence-C21_TCXk.mjs → StreamingChunkSequence-BA4zTLE4.mjs} +8 -7
  23. package/.output/server/_ssr/{index-CrCeNP7H.mjs → index-CzxhDjlq.mjs} +7 -6
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{json-viewer-Di2gxs9H.mjs → json-viewer-B3qV6iEJ.mjs} +8 -7
  26. package/.output/server/_ssr/{router-BJzdUbcH.mjs → router-CZe_R31M.mjs} +4 -4
  27. package/.output/server/{_tanstack-start-manifest_v-CGBTdAYC.mjs → _tanstack-start-manifest_v-B50VTLrH.mjs} +1 -1
  28. package/.output/server/index.mjs +68 -68
  29. package/package.json +3 -1
  30. package/src/components/ProxyViewer.tsx +2 -280
  31. package/src/components/inspector-pet/InspectorPet.tsx +405 -0
  32. package/src/components/inspector-pet/InspectorPetCrab3D.tsx +455 -0
  33. package/src/components/inspector-pet/inspectorPetLogic.ts +258 -0
  34. package/src/components/providers/ProviderForm.tsx +238 -163
  35. package/src/components/providers/ProvidersPanel.tsx +16 -6
  36. package/src/components/providers/providerFormCapabilities.ts +36 -0
  37. package/styles/globals.css +64 -28
  38. package/.output/public/assets/ProxyViewerContainer-WuChOrSS.js +0 -127
  39. package/.output/public/assets/index-DQeXi4Rv.css +0 -1
@@ -0,0 +1,405 @@
1
+ import {
2
+ type CSSProperties,
3
+ type JSX,
4
+ type PointerEvent as ReactPointerEvent,
5
+ useCallback,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from "react";
11
+ import { createPortal } from "react-dom";
12
+ import { Heart, Pause, Play } from "lucide-react";
13
+ import type { CapturedLog } from "../../contracts";
14
+ import { cn } from "../../lib/utils";
15
+ import {
16
+ INSPECTOR_PET_MARGIN,
17
+ type InspectorPetMood,
18
+ type InspectorPetPosition,
19
+ type InspectorPetViewport,
20
+ clampInspectorPetPosition,
21
+ inspectorPetPanelPosition,
22
+ nextInspectorPetWalkingPosition,
23
+ randomInspectorPetEdgePosition,
24
+ resolveInspectorPetStatus,
25
+ snapInspectorPetToEdge,
26
+ } from "./inspectorPetLogic";
27
+ import { InspectorPetCrab3D } from "./InspectorPetCrab3D";
28
+
29
+ type InspectorPetProps = {
30
+ logs: readonly CapturedLog[];
31
+ };
32
+
33
+ type InspectorPetDragState = {
34
+ pointerId: number;
35
+ offsetX: number;
36
+ offsetY: number;
37
+ startX: number;
38
+ startY: number;
39
+ moved: boolean;
40
+ };
41
+
42
+ const INSPECTOR_PET_ROAMING_STORAGE_KEY = "agent-inspector.pet.roaming";
43
+ const INSPECTOR_PET_MIN_PAUSE_MS = 11_000;
44
+ const INSPECTOR_PET_PAUSE_WINDOW_MS = 9000;
45
+ const INSPECTOR_PET_SCUTTLE_MS = 7200;
46
+ const INSPECTOR_PET_PANEL_DISMISS_MS = 6000;
47
+ const INSPECTOR_PET_REACTION_MS = 760;
48
+ const INSPECTOR_PET_DRAG_THRESHOLD = 4;
49
+ const INSPECTOR_PET_TOP_LAYER_Z_INDEX = 2_147_483_647;
50
+
51
+ function readViewport(): InspectorPetViewport {
52
+ if (typeof window === "undefined") return { width: 1280, height: 720, scale: 1 };
53
+ const rawScale = window
54
+ .getComputedStyle(document.documentElement)
55
+ .getPropertyValue("--inspector-ui-scale");
56
+ const parsedScale = Number.parseFloat(rawScale);
57
+ const scale = Number.isFinite(parsedScale) && parsedScale > 0 ? parsedScale : 1;
58
+ return {
59
+ width: Math.max(window.innerWidth, INSPECTOR_PET_MARGIN * 2),
60
+ height: Math.max(window.innerHeight, INSPECTOR_PET_MARGIN * 2),
61
+ scale,
62
+ };
63
+ }
64
+
65
+ function readStoredRoaming(): boolean {
66
+ if (typeof window === "undefined") return true;
67
+ try {
68
+ return window.localStorage.getItem(INSPECTOR_PET_ROAMING_STORAGE_KEY) !== "0";
69
+ } catch {
70
+ return true;
71
+ }
72
+ }
73
+
74
+ function moodDotClass(mood: InspectorPetMood): string {
75
+ switch (mood) {
76
+ case "active":
77
+ return "bg-cyan-400 shadow-[0_0_9px_rgba(34,211,238,0.65)]";
78
+ case "attention":
79
+ return "bg-red-500 shadow-[0_0_9px_rgba(239,68,68,0.55)]";
80
+ case "idle":
81
+ return "bg-amber-400 shadow-[0_0_8px_rgba(251,191,36,0.45)]";
82
+ case "sleeping":
83
+ return "bg-muted-foreground/45";
84
+ }
85
+ }
86
+
87
+ function moodMotionClass(mood: InspectorPetMood): string {
88
+ switch (mood) {
89
+ case "active":
90
+ return "inspector-pet-active";
91
+ case "attention":
92
+ return "inspector-pet-attention";
93
+ case "idle":
94
+ return "";
95
+ case "sleeping":
96
+ return "inspector-pet-sleeping";
97
+ }
98
+ }
99
+
100
+ export function InspectorPet({ logs }: InspectorPetProps): JSX.Element {
101
+ const [portalHost, setPortalHost] = useState<HTMLElement | null>(null);
102
+ const [viewport, setViewport] = useState<InspectorPetViewport>(readViewport);
103
+ const [position, setPosition] = useState<InspectorPetPosition>({
104
+ x: INSPECTOR_PET_MARGIN,
105
+ y: INSPECTOR_PET_MARGIN,
106
+ });
107
+ const [clockMs, setClockMs] = useState(() => Date.now());
108
+ const [scuttling, setScuttling] = useState(false);
109
+ const [panelTick, setPanelTick] = useState(0);
110
+ const [panelVisible, setPanelVisible] = useState(false);
111
+ const [reactionTick, setReactionTick] = useState(0);
112
+ const [reactionVisible, setReactionVisible] = useState(false);
113
+ const [dragging, setDragging] = useState(false);
114
+ const [roaming, setRoaming] = useState(readStoredRoaming);
115
+ const [reduceMotion, setReduceMotion] = useState(false);
116
+ const dragStateRef = useRef<InspectorPetDragState | null>(null);
117
+ const dragOccurredRef = useRef(false);
118
+
119
+ const status = useMemo(() => resolveInspectorPetStatus(logs, clockMs), [clockMs, logs]);
120
+ const panelPosition = useMemo(
121
+ () => inspectorPetPanelPosition(position, viewport),
122
+ [position, viewport],
123
+ );
124
+ useEffect(() => setPortalHost(document.body), []);
125
+
126
+ useEffect(() => {
127
+ const nextViewport = readViewport();
128
+ setViewport(nextViewport);
129
+ setPosition(randomInspectorPetEdgePosition(nextViewport, Math.random()));
130
+ }, []);
131
+
132
+ useEffect(() => {
133
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
134
+ const handleChange = (event: MediaQueryListEvent): void => setReduceMotion(event.matches);
135
+ setReduceMotion(mediaQuery.matches);
136
+ mediaQuery.addEventListener("change", handleChange);
137
+ return () => mediaQuery.removeEventListener("change", handleChange);
138
+ }, []);
139
+
140
+ useEffect(() => {
141
+ const intervalId = window.setInterval(() => setClockMs(Date.now()), 15_000);
142
+ return () => window.clearInterval(intervalId);
143
+ }, []);
144
+
145
+ useEffect(() => {
146
+ try {
147
+ window.localStorage.setItem(INSPECTOR_PET_ROAMING_STORAGE_KEY, roaming ? "1" : "0");
148
+ } catch {
149
+ // Local storage can be unavailable in hardened browser profiles.
150
+ }
151
+ }, [roaming]);
152
+
153
+ useEffect(() => {
154
+ if (!roaming || dragging || reduceMotion) return undefined;
155
+ let pauseTimeoutId: number | null = null;
156
+ let scuttleTimeoutId: number | null = null;
157
+
158
+ const scheduleScuttle = (): void => {
159
+ const delay = INSPECTOR_PET_MIN_PAUSE_MS + Math.random() * INSPECTOR_PET_PAUSE_WINDOW_MS;
160
+ pauseTimeoutId = window.setTimeout(() => {
161
+ setScuttling(true);
162
+ setPosition((current) =>
163
+ nextInspectorPetWalkingPosition(current, viewport, Math.random(), Math.random()),
164
+ );
165
+ scuttleTimeoutId = window.setTimeout(() => {
166
+ setScuttling(false);
167
+ scheduleScuttle();
168
+ }, INSPECTOR_PET_SCUTTLE_MS);
169
+ }, delay);
170
+ };
171
+
172
+ scheduleScuttle();
173
+ return () => {
174
+ if (pauseTimeoutId !== null) window.clearTimeout(pauseTimeoutId);
175
+ if (scuttleTimeoutId !== null) window.clearTimeout(scuttleTimeoutId);
176
+ };
177
+ }, [dragging, reduceMotion, roaming, viewport]);
178
+
179
+ useEffect(() => {
180
+ const handleResize = (): void => {
181
+ const nextViewport = readViewport();
182
+ setScuttling(false);
183
+ setViewport(nextViewport);
184
+ setPosition((current) => snapInspectorPetToEdge(current, nextViewport));
185
+ };
186
+ window.addEventListener("resize", handleResize);
187
+ return () => window.removeEventListener("resize", handleResize);
188
+ }, []);
189
+
190
+ useEffect(() => {
191
+ if (!panelVisible) return undefined;
192
+ const timeoutId = window.setTimeout(
193
+ () => setPanelVisible(false),
194
+ INSPECTOR_PET_PANEL_DISMISS_MS,
195
+ );
196
+ return () => window.clearTimeout(timeoutId);
197
+ }, [panelTick, panelVisible]);
198
+
199
+ useEffect(() => {
200
+ if (!reactionVisible) return undefined;
201
+ const timeoutId = window.setTimeout(() => setReactionVisible(false), INSPECTOR_PET_REACTION_MS);
202
+ return () => window.clearTimeout(timeoutId);
203
+ }, [reactionTick, reactionVisible]);
204
+
205
+ const handlePet = useCallback(() => {
206
+ if (dragOccurredRef.current) {
207
+ dragOccurredRef.current = false;
208
+ return;
209
+ }
210
+ setClockMs(Date.now());
211
+ setPanelTick((current) => current + 1);
212
+ setPanelVisible(true);
213
+ setReactionTick((current) => current + 1);
214
+ setReactionVisible(true);
215
+ }, []);
216
+
217
+ const handlePointerDown = useCallback((event: ReactPointerEvent<HTMLButtonElement>) => {
218
+ const rect = event.currentTarget.getBoundingClientRect();
219
+ dragOccurredRef.current = false;
220
+ dragStateRef.current = {
221
+ pointerId: event.pointerId,
222
+ offsetX: event.clientX - rect.left,
223
+ offsetY: event.clientY - rect.top,
224
+ startX: event.clientX,
225
+ startY: event.clientY,
226
+ moved: false,
227
+ };
228
+ event.currentTarget.setPointerCapture(event.pointerId);
229
+ setScuttling(false);
230
+ setPanelVisible(false);
231
+ setDragging(true);
232
+ }, []);
233
+
234
+ const handlePointerMove = useCallback(
235
+ (event: ReactPointerEvent<HTMLButtonElement>) => {
236
+ const dragState = dragStateRef.current;
237
+ if (dragState === null || dragState.pointerId !== event.pointerId) return;
238
+ const moved =
239
+ dragState.moved ||
240
+ Math.abs(event.clientX - dragState.startX) >= INSPECTOR_PET_DRAG_THRESHOLD ||
241
+ Math.abs(event.clientY - dragState.startY) >= INSPECTOR_PET_DRAG_THRESHOLD;
242
+ dragState.moved = moved;
243
+ dragOccurredRef.current = moved;
244
+ setPosition(
245
+ clampInspectorPetPosition(
246
+ {
247
+ x: event.clientX - dragState.offsetX,
248
+ y: event.clientY - dragState.offsetY,
249
+ },
250
+ viewport,
251
+ ),
252
+ );
253
+ },
254
+ [viewport],
255
+ );
256
+
257
+ const handlePointerUp = useCallback(
258
+ (event: ReactPointerEvent<HTMLButtonElement>) => {
259
+ const dragState = dragStateRef.current;
260
+ if (dragState === null || dragState.pointerId !== event.pointerId) return;
261
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
262
+ event.currentTarget.releasePointerCapture(event.pointerId);
263
+ }
264
+ dragOccurredRef.current = dragState.moved;
265
+ dragStateRef.current = null;
266
+ setDragging(false);
267
+ setPosition((current) => snapInspectorPetToEdge(current, viewport));
268
+ },
269
+ [viewport],
270
+ );
271
+
272
+ const handlePointerCancel = useCallback((event: ReactPointerEvent<HTMLButtonElement>) => {
273
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
274
+ event.currentTarget.releasePointerCapture(event.pointerId);
275
+ }
276
+ dragStateRef.current = null;
277
+ dragOccurredRef.current = false;
278
+ setDragging(false);
279
+ }, []);
280
+
281
+ const handleToggleRoaming = useCallback(() => {
282
+ setScuttling(false);
283
+ setRoaming((current) => !current);
284
+ }, []);
285
+
286
+ const petStyle = useMemo<CSSProperties>(
287
+ () => ({
288
+ transform: `translate3d(${String(position.x)}px, ${String(position.y)}px, 0) scale(${dragging ? "1.04" : "1"})`,
289
+ transition: dragging
290
+ ? "none"
291
+ : scuttling
292
+ ? `transform ${String(INSPECTOR_PET_SCUTTLE_MS)}ms cubic-bezier(0.22, 0.72, 0.32, 1)`
293
+ : "transform 900ms cubic-bezier(0.22, 0.72, 0.32, 1)",
294
+ touchAction: "none",
295
+ }),
296
+ [dragging, position.x, position.y, scuttling],
297
+ );
298
+
299
+ const panelStyle = useMemo<CSSProperties>(
300
+ () => ({ left: panelPosition.x, top: panelPosition.y }),
301
+ [panelPosition.x, panelPosition.y],
302
+ );
303
+
304
+ const roamingLabel = roaming ? "Pause pet roaming" : "Resume pet roaming";
305
+
306
+ if (portalHost === null) return <></>;
307
+
308
+ return createPortal(
309
+ <div
310
+ className="pointer-events-none fixed inset-0 isolate"
311
+ style={{ zIndex: INSPECTOR_PET_TOP_LAYER_Z_INDEX }}
312
+ data-inspector-pet-layer="true"
313
+ >
314
+ <button
315
+ type="button"
316
+ className={cn(
317
+ "pointer-events-auto fixed left-0 top-0 z-10 flex size-28 items-center justify-center bg-transparent text-foreground outline-none transition-[filter,opacity] duration-200 hover:opacity-95 hover:drop-shadow-[0_0_28px_rgba(245,158,11,0.32)] focus-visible:drop-shadow-[0_0_14px_rgba(251,191,36,0.72)]",
318
+ dragging ? "cursor-grabbing" : "cursor-grab",
319
+ )}
320
+ style={petStyle}
321
+ data-pet-mood={status.mood}
322
+ data-pet-moving={scuttling ? "true" : "false"}
323
+ onClick={handlePet}
324
+ onPointerDown={handlePointerDown}
325
+ onPointerMove={handlePointerMove}
326
+ onPointerUp={handlePointerUp}
327
+ onPointerCancel={handlePointerCancel}
328
+ aria-label={`Inspector pet: ${status.label}`}
329
+ title={status.label}
330
+ >
331
+ <span
332
+ className={cn(
333
+ "relative z-10 flex size-24 items-center justify-center",
334
+ reactionVisible && "inspector-pet-patted",
335
+ )}
336
+ aria-hidden="true"
337
+ >
338
+ <span
339
+ className={cn(
340
+ "relative flex size-24 items-center justify-center",
341
+ moodMotionClass(status.mood),
342
+ )}
343
+ >
344
+ <span className="relative flex size-24 items-center justify-center">
345
+ <InspectorPetCrab3D
346
+ mood={status.mood}
347
+ moving={scuttling && !dragging}
348
+ reduceMotion={reduceMotion}
349
+ />
350
+ </span>
351
+ </span>
352
+ <span
353
+ className={cn(
354
+ "absolute right-0 top-2 size-3 rounded-full border border-background/90",
355
+ moodDotClass(status.mood),
356
+ status.mood === "active" && !reduceMotion && "animate-pulse",
357
+ )}
358
+ />
359
+ {reactionVisible && (
360
+ <Heart
361
+ key={reactionTick}
362
+ className="inspector-pet-heart absolute -right-2 -top-3 size-5 fill-amber-400 text-amber-400"
363
+ />
364
+ )}
365
+ </span>
366
+ </button>
367
+
368
+ {panelVisible && (
369
+ <div
370
+ className="pointer-events-auto fixed z-20 w-48 rounded-md border border-border/80 bg-popover/95 px-3 py-2 text-popover-foreground shadow-[0_14px_34px_rgba(0,0,0,0.28)] backdrop-blur"
371
+ style={panelStyle}
372
+ role="status"
373
+ aria-live="polite"
374
+ >
375
+ <div className="flex min-w-0 items-center gap-2">
376
+ <span className={cn("size-2 shrink-0 rounded-full", moodDotClass(status.mood))} />
377
+ <span className="min-w-0 flex-1 truncate text-xs font-medium">{status.label}</span>
378
+ <button
379
+ type="button"
380
+ className="inline-flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none"
381
+ onClick={handleToggleRoaming}
382
+ aria-label={roamingLabel}
383
+ title={roamingLabel}
384
+ >
385
+ {roaming ? <Pause className="size-3.5" /> : <Play className="size-3.5" />}
386
+ </button>
387
+ </div>
388
+ <div className="mt-1 flex items-center justify-between gap-3 font-mono text-[10px] text-muted-foreground">
389
+ <span>{String(status.capturedCount)} captured</span>
390
+ <span>
391
+ {status.pendingCount > 0
392
+ ? `${String(status.pendingCount)} active`
393
+ : scuttling
394
+ ? "Scuttling"
395
+ : roaming
396
+ ? "Resting"
397
+ : "Paused"}
398
+ </span>
399
+ </div>
400
+ </div>
401
+ )}
402
+ </div>,
403
+ portalHost,
404
+ );
405
+ }