@mastra/playground-ui 21.0.1-alpha.0 → 22.0.0-alpha.2

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 (37) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/index.cjs.js +2378 -335
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.css +133 -11
  5. package/dist/index.es.js +2365 -337
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/src/domains/agents/components/agent-layout.d.ts +2 -1
  8. package/dist/src/domains/agents/components/agent-traces-panel.d.ts +9 -2
  9. package/dist/src/domains/agents/components/browser-view/agent-busy-overlay.d.ts +11 -0
  10. package/dist/src/domains/agents/components/browser-view/browser-sidebar-tab.d.ts +5 -0
  11. package/dist/src/domains/agents/components/browser-view/browser-thumbnail.d.ts +12 -0
  12. package/dist/src/domains/agents/components/browser-view/browser-tool-call-history.d.ts +9 -0
  13. package/dist/src/domains/agents/components/browser-view/browser-tool-call-item.d.ts +6 -0
  14. package/dist/src/domains/agents/components/browser-view/browser-view-frame.d.ts +15 -0
  15. package/dist/src/domains/agents/components/browser-view/browser-view-header.d.ts +15 -0
  16. package/dist/src/domains/agents/components/browser-view/browser-view-panel.d.ts +12 -0
  17. package/dist/src/domains/agents/components/browser-view/click-ripple-overlay.d.ts +16 -0
  18. package/dist/src/domains/agents/components/browser-view/index.d.ts +8 -0
  19. package/dist/src/domains/agents/context/browser-session-context.d.ts +62 -0
  20. package/dist/src/domains/agents/context/browser-tool-calls-context.d.ts +33 -0
  21. package/dist/src/domains/agents/context/index.d.ts +2 -0
  22. package/dist/src/domains/agents/hooks/use-browser-stream.d.ts +34 -0
  23. package/dist/src/domains/agents/hooks/use-click-ripple.d.ts +29 -0
  24. package/dist/src/domains/agents/hooks/use-close-browser.d.ts +12 -0
  25. package/dist/src/domains/agents/hooks/use-input-coordination.d.ts +20 -0
  26. package/dist/src/domains/agents/hooks/use-keyboard-interaction.d.ts +19 -0
  27. package/dist/src/domains/agents/hooks/use-mouse-interaction.d.ts +28 -0
  28. package/dist/src/domains/agents/index.d.ts +1 -0
  29. package/dist/src/domains/agents/utils/__tests__/coordinate-mapping.test.d.ts +1 -0
  30. package/dist/src/domains/agents/utils/coordinate-mapping.d.ts +68 -0
  31. package/dist/src/domains/agents/utils/key-mapping.d.ts +8 -0
  32. package/dist/src/domains/datasets/components/bulk-trace-review-dialog.d.ts +18 -0
  33. package/dist/src/domains/datasets/components/dataset-detail/dataset-item-form.d.ts +3 -1
  34. package/dist/src/domains/datasets/components/save-as-dataset-item-dialog.d.ts +5 -1
  35. package/dist/src/domains/metrics/hooks/use-avg-score-kpi-metrics.d.ts +1 -1
  36. package/dist/src/lib/ai-ui/thread.d.ts +2 -1
  37. package/package.json +9 -9
package/dist/index.cjs.js CHANGED
@@ -61,25 +61,25 @@ const reactSyntaxHighlighter = require('@assistant-ui/react-syntax-highlighter')
61
61
  const prism = require('react-syntax-highlighter/dist/cjs/styles/prism');
62
62
  const clientJs = require('@mastra/client-js');
63
63
  const di = require('@mastra/core/di');
64
- const compatibility = require('./compatibility-BSEoV343.cjs');
65
64
  const dnd = require('@hello-pangea/dnd');
66
65
  const SwitchPrimitives = require('@radix-ui/react-switch');
67
66
  const format = require('date-fns/format');
68
67
  const HoverCard = require('@radix-ui/react-hover-card');
69
- const AlertDialogPrimitive = require('@radix-ui/react-alert-dialog');
70
68
  const semver = require('semver');
71
- const DropdownMenuPrimitive = require('@radix-ui/react-dropdown-menu');
72
- const features = require('@mastra/core/features');
73
- const observability = require('@mastra/core/observability');
74
- const storage = require('@mastra/core/storage');
75
69
  const recharts = require('recharts');
70
+ const observability = require('@mastra/core/observability');
71
+ const DropdownMenuPrimitive = require('@radix-ui/react-dropdown-menu');
76
72
  const Papa = require('papaparse');
73
+ const AlertDialogPrimitive = require('@radix-ui/react-alert-dialog');
77
74
  const reactDom = require('react-dom');
78
75
  const isToday = require('date-fns/isToday');
76
+ const features = require('@mastra/core/features');
77
+ const storage = require('@mastra/core/storage');
79
78
  const cmdk = require('cmdk');
80
79
  const merge = require('@codemirror/merge');
81
80
  const state = require('@codemirror/state');
82
81
  const reactSyntaxHighlighter$1 = require('react-syntax-highlighter');
82
+ const compatibility = require('./compatibility-BSEoV343.cjs');
83
83
 
84
84
  function _interopNamespaceDefault(e) {
85
85
  const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
@@ -113,8 +113,8 @@ const SliderPrimitive__namespace = /*#__PURE__*/_interopNamespaceDefault(SliderP
113
113
  const RadixTabs__namespace = /*#__PURE__*/_interopNamespaceDefault(RadixTabs);
114
114
  const SwitchPrimitives__namespace = /*#__PURE__*/_interopNamespaceDefault(SwitchPrimitives);
115
115
  const HoverCard__namespace = /*#__PURE__*/_interopNamespaceDefault(HoverCard);
116
- const AlertDialogPrimitive__namespace = /*#__PURE__*/_interopNamespaceDefault(AlertDialogPrimitive);
117
116
  const DropdownMenuPrimitive__namespace = /*#__PURE__*/_interopNamespaceDefault(DropdownMenuPrimitive);
117
+ const AlertDialogPrimitive__namespace = /*#__PURE__*/_interopNamespaceDefault(AlertDialogPrimitive);
118
118
 
119
119
  const defaultSettings = {
120
120
  modelSettings: {
@@ -13995,11 +13995,69 @@ function useActivatedSkills() {
13995
13995
  return React.useContext(ActivatedSkillsContext) ?? FALLBACK_CONTEXT;
13996
13996
  }
13997
13997
 
13998
+ const BROWSER_TOOL_PREFIXES = ["browser_", "stagehand_"];
13999
+ function isBrowserTool(toolName) {
14000
+ return BROWSER_TOOL_PREFIXES.some((prefix) => toolName.startsWith(prefix));
14001
+ }
14002
+ function isBrowserToolError(result) {
14003
+ if (!result || typeof result !== "object") return false;
14004
+ const r = result;
14005
+ return r.success === false && typeof r.code === "string";
14006
+ }
14007
+ const BrowserToolCallsContext = React.createContext(null);
14008
+ function BrowserToolCallsProvider({ children }) {
14009
+ const [toolCallMap, setToolCallMap] = React.useState(/* @__PURE__ */ new Map());
14010
+ const registerToolCall = React.useCallback((entry) => {
14011
+ setToolCallMap((prev) => {
14012
+ const existing = prev.get(entry.toolCallId);
14013
+ if (existing && existing.result === entry.result && existing.status === entry.status) {
14014
+ return prev;
14015
+ }
14016
+ const next = new Map(prev);
14017
+ next.set(entry.toolCallId, existing ? { ...entry, timestamp: existing.timestamp } : entry);
14018
+ return next;
14019
+ });
14020
+ }, []);
14021
+ const toolCalls = React.useMemo(
14022
+ () => Array.from(toolCallMap.values()).sort((a, b) => a.timestamp - b.timestamp),
14023
+ [toolCallMap]
14024
+ );
14025
+ const value = React.useMemo(() => ({ toolCalls, registerToolCall }), [toolCalls, registerToolCall]);
14026
+ return /* @__PURE__ */ jsxRuntime.jsx(BrowserToolCallsContext.Provider, { value, children });
14027
+ }
14028
+ function useBrowserToolCalls() {
14029
+ const ctx = React.useContext(BrowserToolCallsContext);
14030
+ if (!ctx) {
14031
+ throw new Error("useBrowserToolCalls must be used within a BrowserToolCallsProvider");
14032
+ }
14033
+ return ctx;
14034
+ }
14035
+ function useBrowserToolCallsSafe() {
14036
+ return React.useContext(BrowserToolCallsContext);
14037
+ }
14038
+
13998
14039
  const ToolFallback = ({ toolName, result, args, ...props }) => {
13999
14040
  return /* @__PURE__ */ jsxRuntime.jsx(WorkflowRunProvider, { workflowId: "", withoutTimeTravel: true, children: /* @__PURE__ */ jsxRuntime.jsx(ToolFallbackInner, { toolName, result, args, ...props }) });
14000
14041
  };
14001
14042
  const ToolFallbackInner = ({ toolName, result, args, metadata, toolCallId, ...props }) => {
14043
+ const browserCtx = useBrowserToolCallsSafe();
14044
+ const isBrowser = isBrowserTool(toolName);
14002
14045
  const { activateSkill } = useActivatedSkills();
14046
+ React.useEffect(() => {
14047
+ if (!isBrowser || !browserCtx) return;
14048
+ let status = "pending";
14049
+ if (result !== void 0) {
14050
+ status = isBrowserToolError(result) ? "error" : "complete";
14051
+ }
14052
+ browserCtx.registerToolCall({
14053
+ toolCallId,
14054
+ toolName,
14055
+ args: typeof args === "object" ? args : {},
14056
+ result,
14057
+ status,
14058
+ timestamp: Date.now()
14059
+ });
14060
+ }, [isBrowser, toolCallId, toolName, args, result, browserCtx]);
14003
14061
  React.useEffect(() => {
14004
14062
  if (toolName !== "skill") return;
14005
14063
  if (!args?.name) return;
@@ -14010,6 +14068,9 @@ const ToolFallbackInner = ({ toolName, result, args, metadata, toolCallId, ...pr
14010
14068
  if (toolName === "mastra-memory-om-observation") {
14011
14069
  return /* @__PURE__ */ jsxRuntime.jsx(ObservationMarkerBadge, { toolName, args, metadata });
14012
14070
  }
14071
+ if (isBrowser && browserCtx) {
14072
+ return null;
14073
+ }
14013
14074
  const isAgent = metadata?.mode === "network" && metadata.from === "AGENT" || toolName.startsWith("agent-");
14014
14075
  const isWorkflow = metadata?.mode === "network" && metadata.from === "WORKFLOW" || toolName.startsWith("workflow-");
14015
14076
  const isNetwork = metadata?.mode === "network";
@@ -15995,10 +16056,12 @@ const Avatar = ({ src, name, size = "sm", interactive = false }) => {
15995
16056
  );
15996
16057
  };
15997
16058
 
15998
- const Thread = ({ agentName, agentId, hasMemory, hasModelList, hideModelSwitcher }) => {
16059
+ const Thread = ({ agentName, agentId, threadId, hasMemory, hasModelList, hideModelSwitcher }) => {
15999
16060
  const areaRef = React.useRef(null);
16000
16061
  const messagesContainerRef = React.useRef(null);
16001
16062
  useAutoscroll(areaRef, { enabled: true });
16063
+ const { hasSession, viewMode, isInSidebar } = useBrowserSession();
16064
+ const showThumbnailInChat = hasSession && (viewMode === "collapsed" || viewMode === "expanded") && !isInSidebar;
16002
16065
  const WrappedAssistantMessage = (props) => {
16003
16066
  return /* @__PURE__ */ jsxRuntime.jsx(AssistantMessage, { ...props, hasModelList });
16004
16067
  };
@@ -16023,6 +16086,7 @@ const Thread = ({ agentName, agentId, hasMemory, hasModelList, hideModelSwitcher
16023
16086
  /* @__PURE__ */ jsxRuntime.jsx("div", {})
16024
16087
  ] })
16025
16088
  ] }),
16089
+ showThumbnailInChat && agentId && threadId && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-4 mb-2 max-w-3xl w-full mx-auto", children: /* @__PURE__ */ jsxRuntime.jsx(BrowserThumbnail, { agentName }) }),
16026
16090
  /* @__PURE__ */ jsxRuntime.jsx(
16027
16091
  Composer,
16028
16092
  {
@@ -16240,6 +16304,243 @@ function useWorkingMemory() {
16240
16304
  return ctx;
16241
16305
  }
16242
16306
 
16307
+ function useCloseBrowser() {
16308
+ return reactQuery.useMutation({
16309
+ mutationFn: async ({ agentId, threadId }) => {
16310
+ const response = await fetch(`/api/agents/${agentId}/browser/close`, {
16311
+ method: "POST",
16312
+ headers: { "Content-Type": "application/json" },
16313
+ body: JSON.stringify({ threadId })
16314
+ });
16315
+ if (!response.ok) {
16316
+ throw new Error(`Failed to close browser: ${response.status}`);
16317
+ }
16318
+ return { success: true };
16319
+ },
16320
+ onError: (err) => {
16321
+ console.error("[useCloseBrowser] Error closing browser:", err);
16322
+ }
16323
+ });
16324
+ }
16325
+
16326
+ const BrowserSessionContext = React.createContext(null);
16327
+ function BrowserSessionProvider({ children, agentId, threadId }) {
16328
+ const [hasSession, setHasSession] = React.useState(false);
16329
+ const [viewMode, setViewModeState] = React.useState("collapsed");
16330
+ const [status, setStatusState] = React.useState("idle");
16331
+ const [currentUrl, setCurrentUrlState] = React.useState(null);
16332
+ const [latestFrame, setLatestFrameState] = React.useState(null);
16333
+ const [viewport, setViewport] = React.useState(null);
16334
+ const wsRef = React.useRef(null);
16335
+ const reconnectAttemptRef = React.useRef(0);
16336
+ const reconnectTimeoutRef = React.useRef(null);
16337
+ const currentConnectionRef = React.useRef(null);
16338
+ const maxReconnectAttempts = 5;
16339
+ const clearReconnectTimeout = React.useCallback(() => {
16340
+ if (reconnectTimeoutRef.current) {
16341
+ clearTimeout(reconnectTimeoutRef.current);
16342
+ reconnectTimeoutRef.current = null;
16343
+ }
16344
+ }, []);
16345
+ const disconnect = React.useCallback(() => {
16346
+ clearReconnectTimeout();
16347
+ currentConnectionRef.current = null;
16348
+ if (wsRef.current) {
16349
+ wsRef.current.close();
16350
+ wsRef.current = null;
16351
+ }
16352
+ setHasSession(false);
16353
+ setStatusState("idle");
16354
+ setCurrentUrlState(null);
16355
+ setLatestFrameState(null);
16356
+ setViewport(null);
16357
+ }, [clearReconnectTimeout]);
16358
+ const sendMessage = React.useCallback((data) => {
16359
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
16360
+ wsRef.current.send(data);
16361
+ }
16362
+ }, []);
16363
+ const intentionalCloseRef = React.useRef(false);
16364
+ const connect = React.useCallback(() => {
16365
+ if (!agentId || !threadId) return;
16366
+ if (currentConnectionRef.current?.agentId === agentId && currentConnectionRef.current?.threadId === threadId && wsRef.current?.readyState === WebSocket.OPEN) {
16367
+ return;
16368
+ }
16369
+ clearReconnectTimeout();
16370
+ if (wsRef.current) {
16371
+ intentionalCloseRef.current = true;
16372
+ wsRef.current.close();
16373
+ wsRef.current = null;
16374
+ }
16375
+ currentConnectionRef.current = { agentId, threadId };
16376
+ setStatusState("connecting");
16377
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
16378
+ const wsUrl = `${protocol}//${window.location.host}/browser/${agentId}/stream?threadId=${encodeURIComponent(threadId)}`;
16379
+ try {
16380
+ const ws = new WebSocket(wsUrl);
16381
+ intentionalCloseRef.current = false;
16382
+ wsRef.current = ws;
16383
+ ws.onopen = () => {
16384
+ setStatusState("connected");
16385
+ reconnectAttemptRef.current = 0;
16386
+ };
16387
+ ws.onmessage = (event) => {
16388
+ const data = event.data;
16389
+ if (data.startsWith("{")) {
16390
+ try {
16391
+ const parsed = JSON.parse(data);
16392
+ if (parsed.status) {
16393
+ switch (parsed.status) {
16394
+ case "browser_starting":
16395
+ setStatusState("browser_starting");
16396
+ break;
16397
+ case "streaming":
16398
+ setStatusState("streaming");
16399
+ setHasSession(true);
16400
+ break;
16401
+ case "browser_closed":
16402
+ setStatusState("browser_closed");
16403
+ setHasSession(false);
16404
+ setViewModeState("collapsed");
16405
+ break;
16406
+ case "stopped":
16407
+ setStatusState("disconnected");
16408
+ break;
16409
+ case "error":
16410
+ setStatusState("error");
16411
+ setHasSession(false);
16412
+ break;
16413
+ }
16414
+ }
16415
+ if (parsed.url) {
16416
+ setCurrentUrlState(parsed.url);
16417
+ }
16418
+ if (parsed.viewport) {
16419
+ setViewport(parsed.viewport);
16420
+ }
16421
+ } catch {
16422
+ setLatestFrameState(data);
16423
+ }
16424
+ } else {
16425
+ setLatestFrameState(data);
16426
+ setStatusState((prev) => prev !== "streaming" ? "streaming" : prev);
16427
+ setHasSession(true);
16428
+ }
16429
+ };
16430
+ ws.onerror = () => {
16431
+ };
16432
+ ws.onclose = (event) => {
16433
+ if (wsRef.current !== ws) return;
16434
+ wsRef.current = null;
16435
+ if (!intentionalCloseRef.current && !event.wasClean && reconnectAttemptRef.current < maxReconnectAttempts) {
16436
+ reconnectAttemptRef.current += 1;
16437
+ const delay = Math.min(1e3 * Math.pow(2, reconnectAttemptRef.current - 1), 1e4);
16438
+ setStatusState("disconnected");
16439
+ reconnectTimeoutRef.current = setTimeout(() => {
16440
+ connect();
16441
+ }, delay);
16442
+ } else if (reconnectAttemptRef.current >= maxReconnectAttempts) {
16443
+ setStatusState("error");
16444
+ }
16445
+ };
16446
+ } catch {
16447
+ setStatusState("error");
16448
+ }
16449
+ }, [agentId, threadId, clearReconnectTimeout]);
16450
+ React.useEffect(() => {
16451
+ if (agentId && threadId) {
16452
+ connect();
16453
+ }
16454
+ return () => {
16455
+ disconnect();
16456
+ };
16457
+ }, [agentId, threadId, connect, disconnect]);
16458
+ React.useEffect(() => {
16459
+ const handleVisibilityChange = () => {
16460
+ if (document.visibilityState === "visible" && status === "disconnected" && agentId && threadId) {
16461
+ reconnectAttemptRef.current = 0;
16462
+ connect();
16463
+ }
16464
+ };
16465
+ document.addEventListener("visibilitychange", handleVisibilityChange);
16466
+ return () => {
16467
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
16468
+ };
16469
+ }, [status, agentId, threadId, connect]);
16470
+ const setViewMode = React.useCallback((mode) => {
16471
+ setViewModeState(mode);
16472
+ }, []);
16473
+ const show = React.useCallback(() => {
16474
+ setViewModeState("modal");
16475
+ }, []);
16476
+ const hide = React.useCallback(() => {
16477
+ setViewModeState("collapsed");
16478
+ }, []);
16479
+ const endSession = React.useCallback(() => {
16480
+ setHasSession(false);
16481
+ setViewModeState("collapsed");
16482
+ setLatestFrameState(null);
16483
+ }, []);
16484
+ const closeBrowserMutation = useCloseBrowser();
16485
+ const closeBrowser = React.useCallback(async () => {
16486
+ if (closeBrowserMutation.isPending || !agentId) return;
16487
+ try {
16488
+ await closeBrowserMutation.mutateAsync({ agentId, threadId });
16489
+ endSession();
16490
+ } catch {
16491
+ }
16492
+ }, [agentId, threadId, closeBrowserMutation, endSession]);
16493
+ const isClosing = closeBrowserMutation.isPending;
16494
+ const value = React.useMemo(
16495
+ () => ({
16496
+ hasSession,
16497
+ viewMode,
16498
+ isPanelOpen: viewMode === "modal",
16499
+ isInSidebar: viewMode === "sidebar",
16500
+ isActive: hasSession,
16501
+ // backward compat - reflects session activity, not view mode
16502
+ status,
16503
+ currentUrl,
16504
+ latestFrame,
16505
+ viewport,
16506
+ isClosing,
16507
+ setViewMode,
16508
+ show,
16509
+ hide,
16510
+ endSession,
16511
+ closeBrowser,
16512
+ sendMessage,
16513
+ connect,
16514
+ disconnect
16515
+ }),
16516
+ [
16517
+ hasSession,
16518
+ viewMode,
16519
+ status,
16520
+ currentUrl,
16521
+ latestFrame,
16522
+ viewport,
16523
+ isClosing,
16524
+ setViewMode,
16525
+ show,
16526
+ hide,
16527
+ endSession,
16528
+ closeBrowser,
16529
+ sendMessage,
16530
+ connect,
16531
+ disconnect
16532
+ ]
16533
+ );
16534
+ return /* @__PURE__ */ jsxRuntime.jsx(BrowserSessionContext.Provider, { value, children });
16535
+ }
16536
+ function useBrowserSession() {
16537
+ const ctx = React.useContext(BrowserSessionContext);
16538
+ if (!ctx) {
16539
+ throw new Error("useBrowserSession must be used within a BrowserSessionProvider");
16540
+ }
16541
+ return ctx;
16542
+ }
16543
+
16243
16544
  const ObservationalMemoryContext = React.createContext(null);
16244
16545
  function ObservationalMemoryProvider({ children }) {
16245
16546
  const [isObservingFromStream, setIsObservingFromStream] = React.useState(false);
@@ -17844,6 +18145,7 @@ const AgentChat = ({
17844
18145
  agentName: agentName ?? "",
17845
18146
  hasMemory: memory,
17846
18147
  agentId,
18148
+ threadId,
17847
18149
  hasModelList: Boolean(modelList),
17848
18150
  hideModelSwitcher
17849
18151
  }
@@ -19525,6 +19827,8 @@ function getShortId(id) {
19525
19827
  function SaveAsDatasetItemDialog({
19526
19828
  initialInput,
19527
19829
  initialGroundTruth,
19830
+ initialTrajectory,
19831
+ trajectoryLoading,
19528
19832
  breadcrumb,
19529
19833
  isOpen,
19530
19834
  onClose,
@@ -19534,15 +19838,30 @@ function SaveAsDatasetItemDialog({
19534
19838
  const [selectedDatasetId, setSelectedDatasetId] = React.useState("");
19535
19839
  const [input, setInput] = React.useState("");
19536
19840
  const [groundTruth, setGroundTruth] = React.useState("");
19841
+ const [expectedTrajectory, setExpectedTrajectory] = React.useState("");
19537
19842
  const { data, isLoading: isDatasetsLoading } = useDatasets();
19538
19843
  const { addItem } = useDatasetMutations();
19539
19844
  const datasets = data?.datasets ?? [];
19845
+ const prevOpenRef = React.useRef(false);
19846
+ const trajectorySeededRef = React.useRef(false);
19540
19847
  React.useEffect(() => {
19541
- if (isOpen) {
19848
+ if (isOpen && !prevOpenRef.current) {
19542
19849
  setInput(initialInput);
19543
19850
  setGroundTruth(initialGroundTruth);
19851
+ setExpectedTrajectory(initialTrajectory ?? "");
19852
+ trajectorySeededRef.current = !!initialTrajectory;
19544
19853
  }
19545
- }, [isOpen, initialInput, initialGroundTruth]);
19854
+ prevOpenRef.current = isOpen;
19855
+ if (!isOpen) {
19856
+ trajectorySeededRef.current = false;
19857
+ }
19858
+ }, [isOpen, initialInput, initialGroundTruth, initialTrajectory]);
19859
+ React.useEffect(() => {
19860
+ if (isOpen && initialTrajectory && !trajectorySeededRef.current) {
19861
+ setExpectedTrajectory(initialTrajectory);
19862
+ trajectorySeededRef.current = true;
19863
+ }
19864
+ }, [isOpen, initialTrajectory]);
19546
19865
  const handleSubmit = async (e) => {
19547
19866
  e.preventDefault();
19548
19867
  if (!selectedDatasetId) {
@@ -19565,11 +19884,21 @@ function SaveAsDatasetItemDialog({
19565
19884
  return;
19566
19885
  }
19567
19886
  }
19887
+ let parsedTrajectory;
19888
+ if (expectedTrajectory.trim()) {
19889
+ try {
19890
+ parsedTrajectory = JSON.parse(expectedTrajectory);
19891
+ } catch {
19892
+ toast.error("Expected Trajectory must be valid JSON");
19893
+ return;
19894
+ }
19895
+ }
19568
19896
  try {
19569
19897
  await addItem.mutateAsync({
19570
19898
  datasetId: selectedDatasetId,
19571
19899
  input: parsedInput,
19572
19900
  groundTruth: parsedGroundTruth,
19901
+ expectedTrajectory: parsedTrajectory,
19573
19902
  ...source ? { source } : {}
19574
19903
  });
19575
19904
  const targetDataset = datasets.find((d) => d.id === selectedDatasetId);
@@ -19577,6 +19906,7 @@ function SaveAsDatasetItemDialog({
19577
19906
  setSelectedDatasetId("");
19578
19907
  setInput("{}");
19579
19908
  setGroundTruth("");
19909
+ setExpectedTrajectory("");
19580
19910
  onClose();
19581
19911
  } catch (error) {
19582
19912
  toast.error(`Failed to save item: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -19632,6 +19962,18 @@ function SaveAsDatasetItemDialog({
19632
19962
  /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "item-ground-truth", children: "Ground Truth (JSON, optional)" }),
19633
19963
  /* @__PURE__ */ jsxRuntime.jsx(CodeEditor, { value: groundTruth, onChange: setGroundTruth, showCopyButton: false, className: "min-h-[80px]" })
19634
19964
  ] }),
19965
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
19966
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "item-trajectory", children: "Expected Trajectory (JSON, optional)" }),
19967
+ /* @__PURE__ */ jsxRuntime.jsx(
19968
+ CodeEditor,
19969
+ {
19970
+ value: expectedTrajectory,
19971
+ onChange: setExpectedTrajectory,
19972
+ showCopyButton: false,
19973
+ className: "min-h-[80px]"
19974
+ }
19975
+ )
19976
+ ] }),
19635
19977
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 pt-4", children: [
19636
19978
  /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "outline", onClick: handleCancel, children: "Cancel" }),
19637
19979
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -19639,8 +19981,8 @@ function SaveAsDatasetItemDialog({
19639
19981
  {
19640
19982
  type: "submit",
19641
19983
  variant: "light",
19642
- disabled: addItem.isPending || !selectedDatasetId || datasets.length === 0,
19643
- children: addItem.isPending ? "Saving..." : "Save Item"
19984
+ disabled: addItem.isPending || trajectoryLoading || !selectedDatasetId || datasets.length === 0,
19985
+ children: addItem.isPending ? "Saving..." : trajectoryLoading ? "Loading trajectory..." : "Save Item"
19644
19986
  }
19645
19987
  )
19646
19988
  ] })
@@ -19774,8 +20116,9 @@ function ScoreDialog({
19774
20116
  usageContext = "scorerPage"
19775
20117
  }) {
19776
20118
  const [datasetDialogOpen, setDatasetDialogOpen] = React.useState(false);
19777
- const { Link } = useLinkComponent();
20119
+ const { Link, paths } = useLinkComponent();
19778
20120
  const isCodeBased = isCodeBasedScorer$1(score);
20121
+ const scorerDetailHref = score?.scorerId && score?.entityId ? `${paths.scorerLink(score.scorerId)}?entity=${encodeURIComponent(score.entityId)}&scoreId=${encodeURIComponent(score.id)}` : void 0;
19779
20122
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19780
20123
  /* @__PURE__ */ jsxRuntime.jsxs(
19781
20124
  SideDialog,
@@ -19838,7 +20181,7 @@ function ScoreDialog({
19838
20181
  ...usageContext === "SpanDialog" ? [
19839
20182
  {
19840
20183
  label: "Scorer",
19841
- value: score?.scorer?.name || "-",
20184
+ value: scorerDetailHref ? /* @__PURE__ */ jsxRuntime.jsx(Link, { href: scorerDetailHref, children: score?.scorer?.name || "-" }) : score?.scorer?.name || "-",
19842
20185
  key: "scorer-name"
19843
20186
  }
19844
20187
  ] : [],
@@ -22927,6 +23270,779 @@ const AgentToolPanel = ({ toolId, agentId }) => {
22927
23270
  );
22928
23271
  };
22929
23272
 
23273
+ const TOOL_DISPLAY_NAMES$1 = {
23274
+ // AgentBrowser tools
23275
+ browser_goto: "Go to",
23276
+ browser_click: "Click",
23277
+ browser_type: "Type",
23278
+ browser_scroll: "Scroll",
23279
+ browser_snapshot: "Snapshot",
23280
+ browser_close: "Close",
23281
+ browser_select: "Select",
23282
+ browser_press: "Press",
23283
+ browser_hover: "Hover",
23284
+ browser_back: "Back",
23285
+ browser_dialog: "Dialog",
23286
+ browser_wait: "Wait",
23287
+ browser_tabs: "Tabs",
23288
+ browser_drag: "Drag",
23289
+ browser_evaluate: "Evaluate",
23290
+ // StagehandBrowser tools
23291
+ stagehand_navigate: "Navigate",
23292
+ stagehand_act: "Act",
23293
+ stagehand_extract: "Extract",
23294
+ stagehand_observe: "Observe",
23295
+ stagehand_close: "Close",
23296
+ stagehand_tabs: "Tabs"
23297
+ };
23298
+ const KEY_ARG_MAP = {
23299
+ // AgentBrowser tools
23300
+ browser_goto: "url",
23301
+ browser_click: "ref",
23302
+ browser_type: "text",
23303
+ browser_scroll: "direction",
23304
+ browser_close: "reason",
23305
+ browser_select: "value",
23306
+ browser_press: "key",
23307
+ browser_hover: "ref",
23308
+ browser_dialog: "action",
23309
+ browser_wait: "time",
23310
+ browser_tabs: "action",
23311
+ browser_drag: "sourceRef",
23312
+ browser_evaluate: "expression",
23313
+ // StagehandBrowser tools
23314
+ stagehand_navigate: "url",
23315
+ stagehand_act: "action",
23316
+ stagehand_extract: "instruction",
23317
+ stagehand_observe: "instruction",
23318
+ stagehand_tabs: "action"
23319
+ };
23320
+ function getDisplayName(toolName) {
23321
+ if (TOOL_DISPLAY_NAMES$1[toolName]) {
23322
+ return TOOL_DISPLAY_NAMES$1[toolName];
23323
+ }
23324
+ return toolName.replace(/^(browser_|stagehand_)/, "");
23325
+ }
23326
+ function getKeyArgSummary(toolName, args) {
23327
+ const key = KEY_ARG_MAP[toolName];
23328
+ if (!key) return null;
23329
+ const value = args[key];
23330
+ if (value === void 0 || value === null) return null;
23331
+ const str = String(value);
23332
+ return str.length > 50 ? `${str.slice(0, 47)}...` : str;
23333
+ }
23334
+ function BrowserToolCallItem({ entry }) {
23335
+ const [isExpanded, setIsExpanded] = React.useState(false);
23336
+ const displayName = getDisplayName(entry.toolName);
23337
+ const keyArg = getKeyArgSummary(entry.toolName, entry.args);
23338
+ const { __mastraMetadata: _, ...displayArgs } = entry.args;
23339
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-border1 last:border-b-0", children: [
23340
+ /* @__PURE__ */ jsxRuntime.jsxs(
23341
+ "button",
23342
+ {
23343
+ type: "button",
23344
+ onClick: () => setIsExpanded((prev) => !prev),
23345
+ "aria-expanded": isExpanded,
23346
+ className: "flex items-center gap-2 w-full px-3 py-0.5 text-left hover:bg-surface3 transition-colors",
23347
+ children: [
23348
+ /* @__PURE__ */ jsxRuntime.jsx(
23349
+ lucideReact.ChevronRight,
23350
+ {
23351
+ className: cn("h-3 w-3 text-neutral3 transition-transform shrink-0", isExpanded && "rotate-90")
23352
+ }
23353
+ ),
23354
+ /* @__PURE__ */ jsxRuntime.jsx(StatusDot, { status: entry.status }),
23355
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-neutral6 shrink-0", children: displayName }),
23356
+ keyArg && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-neutral3 truncate", children: keyArg })
23357
+ ]
23358
+ }
23359
+ ),
23360
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 pb-2 space-y-2", children: [
23361
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
23362
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-neutral4 pb-1", children: "Arguments" }),
23363
+ /* @__PURE__ */ jsxRuntime.jsx(CodeEditor, { data: displayArgs, "data-testid": "browser-tool-args" })
23364
+ ] }),
23365
+ entry.result !== void 0 && entry.result !== null && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
23366
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-neutral4 pb-1", children: "Result" }),
23367
+ typeof entry.result === "string" ? /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "whitespace-pre text-xs bg-surface4 p-2 rounded-md overflow-x-auto max-h-40 overflow-y-auto", children: entry.result }) : /* @__PURE__ */ jsxRuntime.jsx(
23368
+ CodeEditor,
23369
+ {
23370
+ data: entry.result,
23371
+ "data-testid": "browser-tool-result"
23372
+ }
23373
+ )
23374
+ ] })
23375
+ ] })
23376
+ ] });
23377
+ }
23378
+ function StatusDot({ status }) {
23379
+ switch (status) {
23380
+ case "pending":
23381
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3 w-3 text-neutral4 animate-spin shrink-0" });
23382
+ case "complete":
23383
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3 w-3 text-green-500 shrink-0" });
23384
+ case "error":
23385
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3 text-red-500 shrink-0" });
23386
+ }
23387
+ }
23388
+
23389
+ function BrowserToolCallHistory({ className }) {
23390
+ const { toolCalls } = useBrowserToolCalls();
23391
+ const [isExpanded, setIsExpanded] = React.useState(true);
23392
+ const listRef = React.useRef(null);
23393
+ React.useEffect(() => {
23394
+ if (isExpanded && listRef.current) {
23395
+ listRef.current.scrollTop = listRef.current.scrollHeight;
23396
+ }
23397
+ }, [toolCalls.length, isExpanded]);
23398
+ if (toolCalls.length === 0) return null;
23399
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col overflow-hidden", className), children: [
23400
+ /* @__PURE__ */ jsxRuntime.jsxs(
23401
+ "button",
23402
+ {
23403
+ type: "button",
23404
+ onClick: () => setIsExpanded((prev) => !prev),
23405
+ "aria-expanded": isExpanded,
23406
+ className: "flex items-center gap-2 w-full px-3 py-1 text-left hover:bg-surface3 transition-colors shrink-0",
23407
+ children: [
23408
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: cn("h-3.5 w-3.5 text-neutral3 transition-transform", isExpanded ? "rotate-180" : "") }),
23409
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium text-neutral4", children: [
23410
+ "Browser Actions (",
23411
+ toolCalls.length,
23412
+ ")"
23413
+ ] })
23414
+ ]
23415
+ }
23416
+ ),
23417
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { ref: listRef, className: "flex-1 overflow-y-auto bg-surface1", children: toolCalls.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(BrowserToolCallItem, { entry }, entry.toolCallId)) })
23418
+ ] });
23419
+ }
23420
+
23421
+ const MAX_RIPPLES = 10;
23422
+ function useClickRipple(options) {
23423
+ const [ripples, setRipples] = React.useState([]);
23424
+ const idRef = React.useRef(0);
23425
+ const viewportRef = React.useRef(options.viewport);
23426
+ React.useEffect(() => {
23427
+ viewportRef.current = options.viewport;
23428
+ }, [options.viewport]);
23429
+ const removeRipple = React.useCallback((id) => {
23430
+ setRipples((prev) => prev.filter((r) => r.id !== id));
23431
+ }, []);
23432
+ React.useEffect(() => {
23433
+ if (!options.enabled || !options.imgRef.current) {
23434
+ return;
23435
+ }
23436
+ const imgElement = options.imgRef.current;
23437
+ function handleMouseDown(e) {
23438
+ if (e.button !== 0) return;
23439
+ const viewport = viewportRef.current;
23440
+ if (!viewport) return;
23441
+ const rect = imgElement.getBoundingClientRect();
23442
+ const relX = e.clientX - rect.left;
23443
+ const relY = e.clientY - rect.top;
23444
+ const scale = Math.min(rect.width / viewport.width, rect.height / viewport.height);
23445
+ const renderedWidth = viewport.width * scale;
23446
+ const renderedHeight = viewport.height * scale;
23447
+ const offsetX = (rect.width - renderedWidth) / 2;
23448
+ const offsetY = (rect.height - renderedHeight) / 2;
23449
+ const imageX = relX - offsetX;
23450
+ const imageY = relY - offsetY;
23451
+ if (imageX < 0 || imageY < 0 || imageX > renderedWidth || imageY > renderedHeight) {
23452
+ return;
23453
+ }
23454
+ setRipples((prev) => {
23455
+ if (prev.length >= MAX_RIPPLES) return prev;
23456
+ const id = ++idRef.current;
23457
+ return [...prev, { id, x: relX, y: relY }];
23458
+ });
23459
+ }
23460
+ imgElement.addEventListener("mousedown", handleMouseDown);
23461
+ return () => {
23462
+ imgElement.removeEventListener("mousedown", handleMouseDown);
23463
+ };
23464
+ }, [options.enabled, options.imgRef]);
23465
+ return { ripples, removeRipple };
23466
+ }
23467
+
23468
+ function useInputCoordination() {
23469
+ const { toolCalls } = useBrowserToolCalls();
23470
+ return React.useMemo(() => {
23471
+ const pendingCalls = toolCalls.filter((tc) => tc.status === "pending");
23472
+ const isAgentBusy = pendingCalls.length > 0;
23473
+ const activeToolName = isAgentBusy ? pendingCalls[0].toolName : null;
23474
+ return { isAgentBusy, activeToolName, pendingCount: pendingCalls.length };
23475
+ }, [toolCalls]);
23476
+ }
23477
+
23478
+ const LINE_HEIGHT_PX = 16;
23479
+ const MAX_DELTA = 500;
23480
+ function mapClientToViewport(clientX, clientY, elemRect, viewport) {
23481
+ const relX = clientX - elemRect.left;
23482
+ const relY = clientY - elemRect.top;
23483
+ const scale = Math.min(elemRect.width / viewport.width, elemRect.height / viewport.height);
23484
+ const renderedWidth = viewport.width * scale;
23485
+ const renderedHeight = viewport.height * scale;
23486
+ const offsetX = (elemRect.width - renderedWidth) / 2;
23487
+ const offsetY = (elemRect.height - renderedHeight) / 2;
23488
+ const imageX = relX - offsetX;
23489
+ const imageY = relY - offsetY;
23490
+ if (imageX < 0 || imageY < 0 || imageX > renderedWidth || imageY > renderedHeight) {
23491
+ return null;
23492
+ }
23493
+ return {
23494
+ x: imageX / scale,
23495
+ y: imageY / scale
23496
+ };
23497
+ }
23498
+ function normalizeWheelDelta(delta, deltaMode, viewportHeight) {
23499
+ let pixels;
23500
+ switch (deltaMode) {
23501
+ case 0:
23502
+ pixels = delta;
23503
+ break;
23504
+ case 1:
23505
+ pixels = delta * LINE_HEIGHT_PX;
23506
+ break;
23507
+ case 2:
23508
+ pixels = delta * (viewportHeight ?? 800);
23509
+ break;
23510
+ default:
23511
+ pixels = delta;
23512
+ }
23513
+ return Math.max(-MAX_DELTA, Math.min(MAX_DELTA, pixels));
23514
+ }
23515
+ function getModifiers(event) {
23516
+ let modifiers = 0;
23517
+ if (event.altKey) modifiers |= 1;
23518
+ if (event.ctrlKey) modifiers |= 2;
23519
+ if (event.metaKey) modifiers |= 4;
23520
+ if (event.shiftKey) modifiers |= 8;
23521
+ return modifiers;
23522
+ }
23523
+
23524
+ function isPrintableKey(key) {
23525
+ return key.length === 1;
23526
+ }
23527
+
23528
+ function useKeyboardInteraction(options) {
23529
+ const sendRef = React.useRef(options.sendMessage);
23530
+ const onEscapeRef = React.useRef(options.onEscape);
23531
+ React.useEffect(() => {
23532
+ sendRef.current = options.sendMessage;
23533
+ }, [options.sendMessage]);
23534
+ React.useEffect(() => {
23535
+ onEscapeRef.current = options.onEscape;
23536
+ }, [options.onEscape]);
23537
+ React.useEffect(() => {
23538
+ if (!options.enabled) {
23539
+ return;
23540
+ }
23541
+ function sendKeyboardMsg(eventType, key, code, text, modifiers) {
23542
+ const msg = { type: "keyboard", eventType };
23543
+ if (key !== void 0) msg.key = key;
23544
+ if (code !== void 0) msg.code = code;
23545
+ if (text !== void 0) msg.text = text;
23546
+ if (modifiers) msg.modifiers = modifiers;
23547
+ sendRef.current(JSON.stringify(msg));
23548
+ }
23549
+ function handleKeyDown(e) {
23550
+ if (e.isComposing || e.keyCode === 229) return;
23551
+ if (e.key === "Escape") {
23552
+ e.preventDefault();
23553
+ e.stopPropagation();
23554
+ onEscapeRef.current();
23555
+ return;
23556
+ }
23557
+ e.preventDefault();
23558
+ e.stopPropagation();
23559
+ const modifiers = getModifiers(e);
23560
+ const isPrintable = isPrintableKey(e.key);
23561
+ sendKeyboardMsg("keyDown", e.key, e.code, void 0, modifiers);
23562
+ if (isPrintable) {
23563
+ sendKeyboardMsg("char", e.key, void 0, e.key, modifiers);
23564
+ }
23565
+ }
23566
+ function handleKeyUp(e) {
23567
+ if (e.isComposing || e.keyCode === 229) return;
23568
+ if (e.key === "Escape") return;
23569
+ e.preventDefault();
23570
+ e.stopPropagation();
23571
+ sendKeyboardMsg("keyUp", e.key, e.code, void 0, getModifiers(e));
23572
+ }
23573
+ function handleCompositionEnd(e) {
23574
+ const text = e.data;
23575
+ if (!text) return;
23576
+ for (const char of text) {
23577
+ sendKeyboardMsg("keyDown", char, void 0, void 0, 0);
23578
+ sendKeyboardMsg("char", char, void 0, char, 0);
23579
+ sendKeyboardMsg("keyUp", char, void 0, void 0, 0);
23580
+ }
23581
+ }
23582
+ document.addEventListener("keydown", handleKeyDown, { capture: true });
23583
+ document.addEventListener("keyup", handleKeyUp, { capture: true });
23584
+ document.addEventListener("compositionend", handleCompositionEnd);
23585
+ return () => {
23586
+ document.removeEventListener("keydown", handleKeyDown, { capture: true });
23587
+ document.removeEventListener("keyup", handleKeyUp, { capture: true });
23588
+ document.removeEventListener("compositionend", handleCompositionEnd);
23589
+ };
23590
+ }, [options.enabled]);
23591
+ }
23592
+
23593
+ function useMouseInteraction(options) {
23594
+ const viewportRef = React.useRef(options.viewport);
23595
+ const sendRef = React.useRef(options.sendMessage);
23596
+ React.useEffect(() => {
23597
+ viewportRef.current = options.viewport;
23598
+ }, [options.viewport]);
23599
+ React.useEffect(() => {
23600
+ sendRef.current = options.sendMessage;
23601
+ }, [options.sendMessage]);
23602
+ React.useEffect(() => {
23603
+ if (!options.enabled || !options.imgRef.current) {
23604
+ return;
23605
+ }
23606
+ const imgElement = options.imgRef.current;
23607
+ function sendMouseEvent(eventType, x, y, button, clickCount, modifiers, deltaX, deltaY) {
23608
+ const msg = { type: "mouse", eventType, x, y };
23609
+ if (button !== void 0) msg.button = button;
23610
+ if (clickCount !== void 0) msg.clickCount = clickCount;
23611
+ if (modifiers !== void 0) msg.modifiers = modifiers;
23612
+ if (deltaX !== void 0) msg.deltaX = deltaX;
23613
+ if (deltaY !== void 0) msg.deltaY = deltaY;
23614
+ sendRef.current(JSON.stringify(msg));
23615
+ }
23616
+ function mapButton(domButton) {
23617
+ if (domButton === 2) return "right";
23618
+ if (domButton === 1) return "middle";
23619
+ return "left";
23620
+ }
23621
+ function handleMouseDown(e) {
23622
+ const viewport = viewportRef.current;
23623
+ if (!viewport) return;
23624
+ const rect = imgElement.getBoundingClientRect();
23625
+ const mapped = mapClientToViewport(e.clientX, e.clientY, rect, viewport);
23626
+ if (!mapped) return;
23627
+ const button = mapButton(e.button);
23628
+ const modifiers = getModifiers(e);
23629
+ sendMouseEvent("mouseMoved", mapped.x, mapped.y, void 0, void 0, modifiers);
23630
+ sendMouseEvent("mousePressed", mapped.x, mapped.y, button, 1, modifiers);
23631
+ }
23632
+ function handleMouseUp(e) {
23633
+ const viewport = viewportRef.current;
23634
+ if (!viewport) return;
23635
+ const rect = imgElement.getBoundingClientRect();
23636
+ const mapped = mapClientToViewport(e.clientX, e.clientY, rect, viewport);
23637
+ if (!mapped) return;
23638
+ const button = mapButton(e.button);
23639
+ sendMouseEvent("mouseReleased", mapped.x, mapped.y, button, 1, getModifiers(e));
23640
+ }
23641
+ function handleContextMenu(e) {
23642
+ e.preventDefault();
23643
+ }
23644
+ function handleWheel(e) {
23645
+ e.preventDefault();
23646
+ const viewport = viewportRef.current;
23647
+ if (!viewport) return;
23648
+ const rect = imgElement.getBoundingClientRect();
23649
+ const mapped = mapClientToViewport(e.clientX, e.clientY, rect, viewport);
23650
+ if (!mapped) return;
23651
+ const normDeltaX = normalizeWheelDelta(e.deltaX, e.deltaMode, viewport.height);
23652
+ const normDeltaY = normalizeWheelDelta(e.deltaY, e.deltaMode, viewport.height);
23653
+ sendMouseEvent("mouseWheel", mapped.x, mapped.y, void 0, void 0, getModifiers(e), normDeltaX, normDeltaY);
23654
+ }
23655
+ let rafId = null;
23656
+ let lastMoveTime = 0;
23657
+ let pendingMoveEvent = null;
23658
+ const FRAME_INTERVAL = 1e3 / 30;
23659
+ function handleMouseMove(e) {
23660
+ pendingMoveEvent = e;
23661
+ if (rafId !== null) return;
23662
+ rafId = requestAnimationFrame((now) => {
23663
+ rafId = null;
23664
+ if (!pendingMoveEvent) return;
23665
+ const delta = now - lastMoveTime;
23666
+ if (delta < FRAME_INTERVAL) return;
23667
+ lastMoveTime = now;
23668
+ const viewport = viewportRef.current;
23669
+ if (!viewport) {
23670
+ pendingMoveEvent = null;
23671
+ return;
23672
+ }
23673
+ const eventToProcess = pendingMoveEvent;
23674
+ pendingMoveEvent = null;
23675
+ const rect = imgElement.getBoundingClientRect();
23676
+ const mapped = mapClientToViewport(eventToProcess.clientX, eventToProcess.clientY, rect, viewport);
23677
+ if (!mapped) return;
23678
+ sendMouseEvent("mouseMoved", mapped.x, mapped.y, void 0, void 0, getModifiers(eventToProcess));
23679
+ });
23680
+ }
23681
+ imgElement.addEventListener("mousedown", handleMouseDown);
23682
+ imgElement.addEventListener("mouseup", handleMouseUp);
23683
+ imgElement.addEventListener("contextmenu", handleContextMenu);
23684
+ imgElement.addEventListener("wheel", handleWheel, { passive: false });
23685
+ imgElement.addEventListener("mousemove", handleMouseMove);
23686
+ return () => {
23687
+ imgElement.removeEventListener("mousedown", handleMouseDown);
23688
+ imgElement.removeEventListener("mouseup", handleMouseUp);
23689
+ imgElement.removeEventListener("contextmenu", handleContextMenu);
23690
+ imgElement.removeEventListener("wheel", handleWheel);
23691
+ imgElement.removeEventListener("mousemove", handleMouseMove);
23692
+ if (rafId !== null) {
23693
+ cancelAnimationFrame(rafId);
23694
+ }
23695
+ };
23696
+ }, [options.enabled, options.imgRef]);
23697
+ }
23698
+
23699
+ const TOOL_DISPLAY_NAMES = {
23700
+ // AgentBrowser tools
23701
+ browser_navigate: "Navigating",
23702
+ browser_goto: "Navigating",
23703
+ browser_click: "Clicking",
23704
+ browser_type: "Typing",
23705
+ browser_scroll: "Scrolling",
23706
+ browser_screenshot: "Capturing",
23707
+ browser_snapshot: "Reading page",
23708
+ browser_close: "Closing",
23709
+ browser_select: "Selecting",
23710
+ // StagehandBrowser tools
23711
+ stagehand_navigate: "Navigating",
23712
+ stagehand_act: "Acting",
23713
+ stagehand_extract: "Extracting",
23714
+ stagehand_observe: "Observing",
23715
+ stagehand_screenshot: "Capturing",
23716
+ stagehand_close: "Closing"
23717
+ };
23718
+ function AgentBusyOverlay({ toolName }) {
23719
+ const displayName = toolName ? TOOL_DISPLAY_NAMES[toolName] ?? toolName.replace(/^(browser_|stagehand_)/, "") : "Working";
23720
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-surface1/40 flex items-center justify-center z-10 cursor-not-allowed", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 bg-surface2 px-3 py-1.5 rounded-md border border-border1 shadow-sm", children: [
23721
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3.5 w-3.5 text-accent1 animate-spin" }),
23722
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-medium text-neutral4", children: [
23723
+ "Agent: ",
23724
+ displayName
23725
+ ] })
23726
+ ] }) });
23727
+ }
23728
+
23729
+ const RIPPLE_SIZE = 32;
23730
+ const RIPPLE_RADIUS = RIPPLE_SIZE / 2;
23731
+ function ClickRippleOverlay({ ripples, onAnimationEnd }) {
23732
+ if (ripples.length === 0) return null;
23733
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: ripples.map((ripple) => /* @__PURE__ */ jsxRuntime.jsx(
23734
+ "span",
23735
+ {
23736
+ className: "absolute rounded-full pointer-events-none animate-click-ripple bg-accent1/40",
23737
+ style: {
23738
+ left: ripple.x - RIPPLE_RADIUS,
23739
+ top: ripple.y - RIPPLE_RADIUS,
23740
+ width: RIPPLE_SIZE,
23741
+ height: RIPPLE_SIZE
23742
+ },
23743
+ onAnimationEnd: () => onAnimationEnd(ripple.id)
23744
+ },
23745
+ ripple.id
23746
+ )) });
23747
+ }
23748
+
23749
+ function BrowserViewFrame({ className, onStatusChange, onUrlChange, onFirstFrame }) {
23750
+ const imgRef = React.useRef(null);
23751
+ const containerRef = React.useRef(null);
23752
+ const hasFrameRef = React.useRef(false);
23753
+ const [hasFrame, setHasFrame] = React.useState(false);
23754
+ const [isInteractive, setIsInteractive] = React.useState(false);
23755
+ const { status, currentUrl, latestFrame, viewport, sendMessage } = useBrowserSession();
23756
+ React.useEffect(() => {
23757
+ if (latestFrame && imgRef.current) {
23758
+ imgRef.current.src = `data:image/jpeg;base64,${latestFrame}`;
23759
+ if (!hasFrameRef.current) {
23760
+ hasFrameRef.current = true;
23761
+ setHasFrame(true);
23762
+ }
23763
+ }
23764
+ }, [latestFrame]);
23765
+ const exitInteractive = React.useCallback(() => {
23766
+ setIsInteractive(false);
23767
+ }, []);
23768
+ const handleFrameClick = React.useCallback(() => {
23769
+ if (status === "streaming") {
23770
+ setIsInteractive(true);
23771
+ }
23772
+ }, [status]);
23773
+ const { isAgentBusy, activeToolName } = useInputCoordination();
23774
+ useMouseInteraction({
23775
+ imgRef,
23776
+ viewport,
23777
+ sendMessage,
23778
+ enabled: status === "streaming" && !isAgentBusy
23779
+ });
23780
+ useKeyboardInteraction({
23781
+ sendMessage,
23782
+ enabled: isInteractive && status === "streaming" && hasFrame && !isAgentBusy,
23783
+ onEscape: exitInteractive
23784
+ });
23785
+ const { ripples, removeRipple } = useClickRipple({
23786
+ imgRef,
23787
+ viewport,
23788
+ enabled: status === "streaming" && hasFrame && !isAgentBusy
23789
+ });
23790
+ React.useEffect(() => {
23791
+ onStatusChange?.(status);
23792
+ }, [status, onStatusChange]);
23793
+ React.useEffect(() => {
23794
+ onUrlChange?.(currentUrl);
23795
+ }, [currentUrl, onUrlChange]);
23796
+ React.useEffect(() => {
23797
+ if (hasFrame) {
23798
+ onFirstFrame?.();
23799
+ }
23800
+ }, [hasFrame, onFirstFrame]);
23801
+ React.useEffect(() => {
23802
+ if (!isInteractive) return;
23803
+ function handleDocumentMouseDown(e) {
23804
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
23805
+ setIsInteractive(false);
23806
+ }
23807
+ }
23808
+ function handleWindowBlur() {
23809
+ setIsInteractive(false);
23810
+ }
23811
+ document.addEventListener("mousedown", handleDocumentMouseDown);
23812
+ window.addEventListener("blur", handleWindowBlur);
23813
+ return () => {
23814
+ document.removeEventListener("mousedown", handleDocumentMouseDown);
23815
+ window.removeEventListener("blur", handleWindowBlur);
23816
+ };
23817
+ }, [isInteractive]);
23818
+ React.useEffect(() => {
23819
+ if (status !== "streaming") {
23820
+ setIsInteractive(false);
23821
+ }
23822
+ }, [status]);
23823
+ const isLoading = (status === "connecting" || status === "browser_starting" || status === "streaming") && !hasFrame;
23824
+ const isReconnecting = status === "disconnected" && hasFrame;
23825
+ const hasError = status === "error";
23826
+ return /* @__PURE__ */ jsxRuntime.jsxs(
23827
+ "div",
23828
+ {
23829
+ ref: containerRef,
23830
+ className: cn(
23831
+ "relative w-full aspect-video bg-surface2 rounded-md overflow-hidden",
23832
+ isInteractive && !isAgentBusy && "ring-2 ring-accent1",
23833
+ isInteractive && isAgentBusy && "ring-2 ring-amber-400",
23834
+ className
23835
+ ),
23836
+ children: [
23837
+ /* @__PURE__ */ jsxRuntime.jsx(
23838
+ "img",
23839
+ {
23840
+ ref: imgRef,
23841
+ alt: "Browser screencast",
23842
+ onClick: handleFrameClick,
23843
+ onKeyDown: (e) => {
23844
+ if (e.key === "Enter" || e.key === " ") {
23845
+ e.preventDefault();
23846
+ handleFrameClick();
23847
+ }
23848
+ },
23849
+ tabIndex: status === "streaming" ? 0 : -1,
23850
+ role: "button",
23851
+ className: cn(
23852
+ "absolute inset-0 w-full h-full object-contain",
23853
+ hasFrame ? "opacity-100" : "opacity-0",
23854
+ status === "streaming" && (isInteractive ? "cursor-text" : "cursor-pointer")
23855
+ )
23856
+ }
23857
+ ),
23858
+ /* @__PURE__ */ jsxRuntime.jsx(ClickRippleOverlay, { ripples, onAnimationEnd: removeRipple }),
23859
+ isAgentBusy && /* @__PURE__ */ jsxRuntime.jsx(AgentBusyOverlay, { toolName: activeToolName }),
23860
+ isLoading && /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "absolute inset-0" }),
23861
+ isReconnecting && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-surface1/80 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2", children: [
23862
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-4 border-2 border-neutral4 border-t-transparent rounded-full animate-spin" }),
23863
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-neutral4", children: "Reconnecting..." })
23864
+ ] }) }),
23865
+ hasError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-50 bg-black/70 backdrop-blur-sm flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3 px-6 py-4 text-center", children: [
23866
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-14 h-14 rounded-full bg-red-500/20 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
23867
+ "svg",
23868
+ {
23869
+ className: "w-7 h-7 text-red-400",
23870
+ fill: "none",
23871
+ viewBox: "0 0 24 24",
23872
+ stroke: "currentColor",
23873
+ strokeWidth: 1.5,
23874
+ children: /* @__PURE__ */ jsxRuntime.jsx(
23875
+ "path",
23876
+ {
23877
+ strokeLinecap: "round",
23878
+ strokeLinejoin: "round",
23879
+ d: "M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
23880
+ }
23881
+ )
23882
+ }
23883
+ ) }),
23884
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
23885
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-lg font-medium text-white", children: "Connection Error" }),
23886
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-white/70", children: "Failed to connect to browser" })
23887
+ ] })
23888
+ ] }) })
23889
+ ]
23890
+ }
23891
+ );
23892
+ }
23893
+
23894
+ const statusBadgeVariants = cva(
23895
+ // Base styles
23896
+ "inline-flex items-center gap-1.5 rounded-full text-ui-xs font-medium transition-colors duration-normal",
23897
+ {
23898
+ variants: {
23899
+ variant: {
23900
+ success: "bg-accent1Dark text-accent1",
23901
+ warning: "bg-accent6Dark text-accent6",
23902
+ error: "bg-accent2Dark text-accent2",
23903
+ info: "bg-accent5Dark text-accent5",
23904
+ neutral: "bg-surface4 text-neutral4"
23905
+ },
23906
+ size: {
23907
+ sm: "px-1.5 py-0.5 text-ui-xs",
23908
+ md: "px-2 py-1 text-ui-xs",
23909
+ lg: "px-2.5 py-1 text-ui-sm"
23910
+ },
23911
+ withDot: {
23912
+ true: "",
23913
+ false: ""
23914
+ },
23915
+ pulse: {
23916
+ true: "",
23917
+ false: ""
23918
+ }
23919
+ },
23920
+ defaultVariants: {
23921
+ variant: "neutral",
23922
+ size: "md",
23923
+ withDot: false,
23924
+ pulse: false
23925
+ }
23926
+ }
23927
+ );
23928
+ const dotVariants = cva("rounded-full", {
23929
+ variants: {
23930
+ variant: {
23931
+ success: "bg-accent1",
23932
+ warning: "bg-accent6",
23933
+ error: "bg-accent2",
23934
+ info: "bg-accent5",
23935
+ neutral: "bg-neutral3"
23936
+ },
23937
+ size: {
23938
+ sm: "w-1 h-1",
23939
+ md: "w-1.5 h-1.5",
23940
+ lg: "w-2 h-2"
23941
+ },
23942
+ pulse: {
23943
+ true: "animate-pulse",
23944
+ false: ""
23945
+ }
23946
+ },
23947
+ defaultVariants: {
23948
+ variant: "neutral",
23949
+ size: "md",
23950
+ pulse: false
23951
+ }
23952
+ });
23953
+ function StatusBadge({ className, variant, size, withDot, pulse, children, ...props }) {
23954
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn(statusBadgeVariants({ variant, size, withDot, pulse }), className), ...props, children: [
23955
+ withDot && /* @__PURE__ */ jsxRuntime.jsx("span", { className: dotVariants({ variant, size, pulse }) }),
23956
+ children
23957
+ ] });
23958
+ }
23959
+
23960
+ function getStatusBadgeConfig$2(status) {
23961
+ switch (status) {
23962
+ case "idle":
23963
+ return { variant: "neutral", pulse: false, label: "Idle" };
23964
+ case "connecting":
23965
+ return { variant: "warning", pulse: true, label: "Connecting" };
23966
+ case "connected":
23967
+ return { variant: "warning", pulse: true, label: "Connected" };
23968
+ case "browser_starting":
23969
+ return { variant: "warning", pulse: true, label: "Starting" };
23970
+ case "streaming":
23971
+ return { variant: "success", pulse: false, label: "Live" };
23972
+ case "browser_closed":
23973
+ return { variant: "neutral", pulse: false, label: "Closed" };
23974
+ case "disconnected":
23975
+ return { variant: "error", pulse: true, label: "Disconnected" };
23976
+ case "error":
23977
+ return { variant: "error", pulse: false, label: "Error" };
23978
+ default:
23979
+ return { variant: "neutral", pulse: false, label: "Unknown" };
23980
+ }
23981
+ }
23982
+ function BrowserSidebarTab() {
23983
+ const { status, currentUrl, closeBrowser, setViewMode } = useBrowserSession();
23984
+ const handleClose = React.useCallback(async () => {
23985
+ await closeBrowser();
23986
+ }, [closeBrowser]);
23987
+ const handleCenterView = React.useCallback(() => {
23988
+ setViewMode("modal");
23989
+ }, [setViewMode]);
23990
+ const handleMinimize = React.useCallback(() => {
23991
+ setViewMode("collapsed");
23992
+ }, [setViewMode]);
23993
+ const handleFirstFrame = React.useCallback(() => {
23994
+ }, []);
23995
+ const statusConfig = getStatusBadgeConfig$2(status);
23996
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full overflow-hidden", children: [
23997
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border1 shrink-0", children: [
23998
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Globe, { className: "h-4 w-4 text-neutral4 shrink-0" }),
23999
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0 px-2 py-1 bg-surface2 rounded border border-border1", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-xs truncate block", currentUrl ? "text-neutral5" : "text-neutral3 italic"), children: currentUrl || "No URL" }) }),
24000
+ /* @__PURE__ */ jsxRuntime.jsx(StatusBadge, { variant: statusConfig.variant, size: "sm", withDot: true, pulse: statusConfig.pulse, children: statusConfig.label })
24001
+ ] }),
24002
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
24003
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
24004
+ /* @__PURE__ */ jsxRuntime.jsx(BrowserViewFrame, { className: "w-full", onFirstFrame: handleFirstFrame }),
24005
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 right-2 flex gap-1", children: [
24006
+ /* @__PURE__ */ jsxRuntime.jsx(
24007
+ IconButton,
24008
+ {
24009
+ variant: "light",
24010
+ size: "sm",
24011
+ tooltip: "Center view",
24012
+ onClick: handleCenterView,
24013
+ className: "bg-surface1/80 backdrop-blur-sm",
24014
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-3.5 w-3.5" })
24015
+ }
24016
+ ),
24017
+ /* @__PURE__ */ jsxRuntime.jsx(
24018
+ IconButton,
24019
+ {
24020
+ variant: "light",
24021
+ size: "sm",
24022
+ tooltip: "Minimize to chat",
24023
+ onClick: handleMinimize,
24024
+ className: "bg-surface1/80 backdrop-blur-sm",
24025
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize2, { className: "h-3.5 w-3.5" })
24026
+ }
24027
+ ),
24028
+ /* @__PURE__ */ jsxRuntime.jsx(
24029
+ IconButton,
24030
+ {
24031
+ variant: "light",
24032
+ size: "sm",
24033
+ tooltip: "Close browser",
24034
+ onClick: handleClose,
24035
+ className: "bg-surface1/80 backdrop-blur-sm",
24036
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5" })
24037
+ }
24038
+ )
24039
+ ] })
24040
+ ] }) }),
24041
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-3", children: /* @__PURE__ */ jsxRuntime.jsx(BrowserToolCallHistory, {}) })
24042
+ ] })
24043
+ ] });
24044
+ }
24045
+
22930
24046
  const AgentMemoryConfig = ({ agentId }) => {
22931
24047
  const { data, isLoading } = useMemoryConfig(agentId);
22932
24048
  const [expandedSections, setExpandedSections] = React.useState(/* @__PURE__ */ new Set(["General", "Semantic Recall"]));
@@ -24034,6 +25150,7 @@ function AgentMemory({ agentId, threadId, memoryType }) {
24034
25150
  function AgentInformation({ agentId, threadId }) {
24035
25151
  const { data: agent } = useAgent(agentId);
24036
25152
  const { data: memory, isLoading: isMemoryLoading } = useMemory(agentId);
25153
+ const { hasSession, isInSidebar } = useBrowserSession();
24037
25154
  const hasMemory = !isMemoryLoading && Boolean(memory?.result);
24038
25155
  const { selectedTab, handleTabChange } = useAgentInformationTab({
24039
25156
  isMemoryLoading,
@@ -24041,39 +25158,46 @@ function AgentInformation({ agentId, threadId }) {
24041
25158
  });
24042
25159
  return /* @__PURE__ */ jsxRuntime.jsxs(AgentInformationLayout, { children: [
24043
25160
  /* @__PURE__ */ jsxRuntime.jsx(AgentEntityHeader, { agentId }),
24044
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-hidden border-t border-border1 flex flex-col", children: /* @__PURE__ */ jsxRuntime.jsxs(Tabs, { defaultTab: "overview", value: selectedTab, onValueChange: handleTabChange, children: [
24045
- /* @__PURE__ */ jsxRuntime.jsxs(TabList, { children: [
24046
- /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "overview", children: "Overview" }),
24047
- /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "model-settings", children: "Model Settings" }),
24048
- hasMemory && /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "memory", children: "Memory" }),
24049
- agent?.requestContextSchema && /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "request-context", children: "Request Context" }),
24050
- /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "tracing-options", children: "Tracing Options" })
24051
- ] }),
24052
- /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "overview", children: /* @__PURE__ */ jsxRuntime.jsx(AgentMetadata, { agentId }) }),
24053
- /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "model-settings", children: /* @__PURE__ */ jsxRuntime.jsx(AgentSettings, { agentId }) }),
24054
- agent?.requestContextSchema && /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "request-context", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-5", children: /* @__PURE__ */ jsxRuntime.jsx(RequestContextSchemaForm, { requestContextSchema: agent.requestContextSchema }) }) }),
24055
- hasMemory && /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "memory", children: /* @__PURE__ */ jsxRuntime.jsx(AgentMemory, { agentId, threadId, memoryType: memory?.memoryType }) }),
24056
- /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "tracing-options", children: /* @__PURE__ */ jsxRuntime.jsx(TracingRunOptions, {}) })
24057
- ] }) })
25161
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-hidden border-t border-border1 flex flex-col relative", children: [
25162
+ hasSession && isInSidebar && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-10 bg-surface1", children: /* @__PURE__ */ jsxRuntime.jsx(BrowserSidebarTab, {}) }),
25163
+ /* @__PURE__ */ jsxRuntime.jsxs(Tabs, { defaultTab: "overview", value: selectedTab, onValueChange: handleTabChange, children: [
25164
+ /* @__PURE__ */ jsxRuntime.jsxs(TabList, { children: [
25165
+ /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "overview", children: "Overview" }),
25166
+ /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "model-settings", children: "Model Settings" }),
25167
+ hasMemory && /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "memory", children: "Memory" }),
25168
+ agent?.requestContextSchema && /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "request-context", children: "Request Context" }),
25169
+ /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "tracing-options", children: "Tracing Options" })
25170
+ ] }),
25171
+ /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "overview", children: /* @__PURE__ */ jsxRuntime.jsx(AgentMetadata, { agentId }) }),
25172
+ /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "model-settings", children: /* @__PURE__ */ jsxRuntime.jsx(AgentSettings, { agentId }) }),
25173
+ agent?.requestContextSchema && /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "request-context", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-5", children: /* @__PURE__ */ jsxRuntime.jsx(RequestContextSchemaForm, { requestContextSchema: agent.requestContextSchema }) }) }),
25174
+ hasMemory && /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "memory", children: /* @__PURE__ */ jsxRuntime.jsx(AgentMemory, { agentId, threadId, memoryType: memory?.memoryType }) }),
25175
+ /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "tracing-options", children: /* @__PURE__ */ jsxRuntime.jsx(TracingRunOptions, {}) })
25176
+ ] })
25177
+ ] })
24058
25178
  ] });
24059
25179
  }
24060
25180
  const STORAGE_KEY = "agent-info-selected-tab";
25181
+ const VALID_TABS = /* @__PURE__ */ new Set(["overview", "model-settings", "memory", "request-context", "tracing-options"]);
24061
25182
  const useAgentInformationTab = ({ isMemoryLoading, hasMemory }) => {
24062
25183
  const [selectedTab, setSelectedTab] = React.useState(() => {
24063
- return sessionStorage.getItem(STORAGE_KEY) || "overview";
25184
+ const stored = sessionStorage.getItem(STORAGE_KEY) || "overview";
25185
+ if (!VALID_TABS.has(stored)) return "overview";
25186
+ return stored;
24064
25187
  });
24065
- const handleTabChange = (value) => {
25188
+ const effectiveTab = (() => {
25189
+ if (!VALID_TABS.has(selectedTab)) return "overview";
25190
+ if (selectedTab === "memory" && !isMemoryLoading && !hasMemory) {
25191
+ return "overview";
25192
+ }
25193
+ return selectedTab;
25194
+ })();
25195
+ const handleTabChange = React.useCallback((value) => {
24066
25196
  setSelectedTab(value);
24067
25197
  sessionStorage.setItem(STORAGE_KEY, value);
24068
- };
24069
- React.useEffect(() => {
24070
- if (!isMemoryLoading && !hasMemory && selectedTab === "memory") {
24071
- setSelectedTab("overview");
24072
- sessionStorage.setItem(STORAGE_KEY, "overview");
24073
- }
24074
- }, [isMemoryLoading, hasMemory, selectedTab]);
25198
+ }, []);
24075
25199
  return {
24076
- selectedTab,
25200
+ selectedTab: effectiveTab,
24077
25201
  handleTabChange
24078
25202
  };
24079
25203
  };
@@ -24090,9 +25214,9 @@ const AgentInformationTabLayout = ({ children, agentId }) => {
24090
25214
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-hidden border-t border-border1 flex flex-col min-w-0 w-full", children: /* @__PURE__ */ jsxRuntime.jsx(Tabs, { defaultTab: "overview", value: selectedTab, onValueChange: handleTabChange, children }) });
24091
25215
  };
24092
25216
 
24093
- const AgentLayout = ({ agentId, children, leftSlot, rightSlot }) => {
25217
+ const AgentLayout = ({ agentId, children, leftSlot, rightSlot, browserOverlay }) => {
24094
25218
  const { defaultLayout, onLayoutChange } = reactResizablePanels.useDefaultLayout({
24095
- id: `agent-layout-${agentId}`,
25219
+ id: `agent-layout-v2-${agentId}`,
24096
25220
  storage: localStorage
24097
25221
  });
24098
25222
  const computedClassName = getMainContentContentClassName({
@@ -24100,42 +25224,545 @@ const AgentLayout = ({ agentId, children, leftSlot, rightSlot }) => {
24100
25224
  isDivided: true,
24101
25225
  hasLeftServiceColumn: Boolean(leftSlot)
24102
25226
  });
24103
- return /* @__PURE__ */ jsxRuntime.jsxs(reactResizablePanels.Group, { className: computedClassName, defaultLayout, onLayoutChange, children: [
24104
- leftSlot && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
24105
- /* @__PURE__ */ jsxRuntime.jsx(
24106
- CollapsiblePanel,
24107
- {
24108
- direction: "left",
24109
- id: "left-slot",
24110
- minSize: 200,
24111
- maxSize: "30%",
24112
- defaultSize: 200,
24113
- collapsedSize: 60,
24114
- collapsible: true,
24115
- children: leftSlot
24116
- }
24117
- ),
24118
- /* @__PURE__ */ jsxRuntime.jsx(PanelSeparator, {})
25227
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full w-full overflow-hidden", children: [
25228
+ /* @__PURE__ */ jsxRuntime.jsxs(reactResizablePanels.Group, { className: computedClassName, defaultLayout, onLayoutChange, children: [
25229
+ leftSlot && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
25230
+ /* @__PURE__ */ jsxRuntime.jsx(
25231
+ CollapsiblePanel,
25232
+ {
25233
+ direction: "left",
25234
+ id: "left-slot",
25235
+ minSize: 200,
25236
+ maxSize: "30%",
25237
+ defaultSize: 200,
25238
+ collapsedSize: 60,
25239
+ collapsible: true,
25240
+ children: leftSlot
25241
+ }
25242
+ ),
25243
+ /* @__PURE__ */ jsxRuntime.jsx(PanelSeparator, {})
25244
+ ] }),
25245
+ /* @__PURE__ */ jsxRuntime.jsx(reactResizablePanels.Panel, { id: "main-slot", className: "grid overflow-y-auto relative bg-surface1 py-4", children }),
25246
+ rightSlot && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
25247
+ /* @__PURE__ */ jsxRuntime.jsx(PanelSeparator, {}),
25248
+ /* @__PURE__ */ jsxRuntime.jsx(
25249
+ CollapsiblePanel,
25250
+ {
25251
+ direction: "right",
25252
+ id: "right-slot",
25253
+ minSize: 300,
25254
+ maxSize: "50%",
25255
+ defaultSize: "30%",
25256
+ collapsedSize: 60,
25257
+ collapsible: true,
25258
+ children: rightSlot
25259
+ }
25260
+ )
25261
+ ] })
24119
25262
  ] }),
24120
- /* @__PURE__ */ jsxRuntime.jsx(reactResizablePanels.Panel, { id: "main-slot", className: "grid overflow-y-auto relative bg-surface1 py-4", children }),
24121
- rightSlot && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
24122
- /* @__PURE__ */ jsxRuntime.jsx(PanelSeparator, {}),
24123
- /* @__PURE__ */ jsxRuntime.jsx(
24124
- CollapsiblePanel,
25263
+ browserOverlay
25264
+ ] });
25265
+ };
25266
+
25267
+ function BrowserThumbnail({ agentName = "Agent" }) {
25268
+ const { hasSession, viewMode, status, currentUrl, latestFrame, setViewMode, closeBrowser } = useBrowserSession();
25269
+ const { toolCalls } = useBrowserToolCalls();
25270
+ const imgRef = React.useRef(null);
25271
+ const [hasFrame, setHasFrame] = React.useState(false);
25272
+ const actionsRef = React.useRef(null);
25273
+ const isExpanded = viewMode === "expanded";
25274
+ React.useEffect(() => {
25275
+ if (latestFrame) {
25276
+ const frameData = `data:image/jpeg;base64,${latestFrame}`;
25277
+ if (imgRef.current) {
25278
+ imgRef.current.src = frameData;
25279
+ }
25280
+ if (!hasFrame) {
25281
+ setHasFrame(true);
25282
+ }
25283
+ }
25284
+ }, [latestFrame, hasFrame]);
25285
+ React.useEffect(() => {
25286
+ if (!hasSession) {
25287
+ setHasFrame(false);
25288
+ if (imgRef.current) {
25289
+ imgRef.current.src = "";
25290
+ }
25291
+ }
25292
+ }, [hasSession]);
25293
+ React.useEffect(() => {
25294
+ if (isExpanded && actionsRef.current) {
25295
+ actionsRef.current.scrollTop = actionsRef.current.scrollHeight;
25296
+ }
25297
+ }, [toolCalls.length, isExpanded]);
25298
+ const handleToggleExpand = React.useCallback(() => {
25299
+ setViewMode(isExpanded ? "collapsed" : "expanded");
25300
+ }, [isExpanded, setViewMode]);
25301
+ const handleOpenModal = React.useCallback(() => {
25302
+ setViewMode("modal");
25303
+ }, [setViewMode]);
25304
+ const handleOpenSidebar = React.useCallback(() => {
25305
+ setViewMode("sidebar");
25306
+ }, [setViewMode]);
25307
+ const handleClose = React.useCallback(async () => {
25308
+ await closeBrowser();
25309
+ }, [closeBrowser]);
25310
+ const displayUrl = React.useMemo(() => {
25311
+ if (!currentUrl) return "Browser";
25312
+ try {
25313
+ return new URL(currentUrl).hostname;
25314
+ } catch {
25315
+ return currentUrl;
25316
+ }
25317
+ }, [currentUrl]);
25318
+ if (!hasSession || viewMode === "modal" || viewMode === "sidebar") {
25319
+ return null;
25320
+ }
25321
+ const isLive = status === "streaming";
25322
+ return /* @__PURE__ */ jsxRuntime.jsxs(
25323
+ "div",
25324
+ {
25325
+ className: cn(
25326
+ "bg-surface2 border border-border1 rounded-lg overflow-hidden transition-all duration-200",
25327
+ "hover:border-border2"
25328
+ ),
25329
+ children: [
25330
+ /* @__PURE__ */ jsxRuntime.jsxs(
25331
+ "button",
25332
+ {
25333
+ type: "button",
25334
+ onClick: handleToggleExpand,
25335
+ className: cn(
25336
+ "group flex items-center gap-3 w-full px-4 py-3",
25337
+ "hover:bg-surface3 transition-colors",
25338
+ "focus:outline-none focus:ring-2 focus:ring-accent1 focus:ring-inset"
25339
+ ),
25340
+ children: [
25341
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative shrink-0 w-24 h-14 bg-surface3 rounded-md overflow-hidden border border-border1", children: [
25342
+ hasFrame ? /* @__PURE__ */ jsxRuntime.jsx("img", { ref: imgRef, alt: "Browser preview", className: "w-full h-full object-cover" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center w-full h-full", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Monitor, { className: "h-5 w-5 text-neutral3" }) }),
25343
+ isLive && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-1 right-1 w-2 h-2 bg-success rounded-full animate-pulse" })
25344
+ ] }),
25345
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 text-left", children: [
25346
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
25347
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-medium text-neutral6 truncate", children: [
25348
+ agentName,
25349
+ "'s browser"
25350
+ ] }),
25351
+ /* @__PURE__ */ jsxRuntime.jsx(StatusBadge, { variant: isLive ? "success" : "neutral", size: "sm", withDot: true, pulse: isLive, children: isLive ? "Live" : "Idle" })
25352
+ ] }),
25353
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-neutral4 truncate mt-0.5", children: displayUrl })
25354
+ ] }),
25355
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 text-neutral4 group-hover:text-neutral5 transition-colors", children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-5 w-5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-5 w-5" }) })
25356
+ ]
25357
+ }
25358
+ ),
25359
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border1", children: [
25360
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
25361
+ /* @__PURE__ */ jsxRuntime.jsx(BrowserViewFrame, { className: "w-full" }),
25362
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 right-2 flex gap-1", children: [
25363
+ /* @__PURE__ */ jsxRuntime.jsx(
25364
+ IconButton,
25365
+ {
25366
+ variant: "light",
25367
+ size: "sm",
25368
+ tooltip: "Center view",
25369
+ onClick: handleOpenModal,
25370
+ className: "bg-surface1/80 backdrop-blur-sm",
25371
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "h-3.5 w-3.5" })
25372
+ }
25373
+ ),
25374
+ /* @__PURE__ */ jsxRuntime.jsx(
25375
+ IconButton,
25376
+ {
25377
+ variant: "light",
25378
+ size: "sm",
25379
+ tooltip: "Open in sidebar",
25380
+ onClick: handleOpenSidebar,
25381
+ className: "bg-surface1/80 backdrop-blur-sm",
25382
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelRight, { className: "h-3.5 w-3.5" })
25383
+ }
25384
+ ),
25385
+ /* @__PURE__ */ jsxRuntime.jsx(
25386
+ IconButton,
25387
+ {
25388
+ variant: "light",
25389
+ size: "sm",
25390
+ tooltip: "Close browser",
25391
+ onClick: handleClose,
25392
+ className: "bg-surface1/80 backdrop-blur-sm",
25393
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5" })
25394
+ }
25395
+ )
25396
+ ] })
25397
+ ] }) }),
25398
+ toolCalls.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { ref: actionsRef, className: "border-t border-border1 max-h-40 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 py-2", children: [
25399
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-xs font-medium text-neutral4 mb-2", children: "Browser Actions" }),
25400
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1", children: toolCalls.slice(-5).map((entry) => /* @__PURE__ */ jsxRuntime.jsx(BrowserToolCallItem, { entry }, entry.toolCallId)) })
25401
+ ] }) })
25402
+ ] })
25403
+ ]
25404
+ }
25405
+ );
25406
+ }
25407
+
25408
+ function getStatusBadgeConfig$1(status) {
25409
+ switch (status) {
25410
+ case "idle":
25411
+ return { variant: "neutral", pulse: false, label: "Idle" };
25412
+ case "connecting":
25413
+ return { variant: "warning", pulse: true, label: "Connecting" };
25414
+ case "connected":
25415
+ return { variant: "warning", pulse: true, label: "Connected" };
25416
+ case "browser_starting":
25417
+ return { variant: "warning", pulse: true, label: "Starting" };
25418
+ case "streaming":
25419
+ return { variant: "success", pulse: false, label: "Live" };
25420
+ case "browser_closed":
25421
+ return { variant: "neutral", pulse: false, label: "Closed" };
25422
+ case "disconnected":
25423
+ return { variant: "error", pulse: true, label: "Disconnected" };
25424
+ case "error":
25425
+ return { variant: "error", pulse: false, label: "Error" };
25426
+ default:
25427
+ return { variant: "neutral", pulse: false, label: "Unknown" };
25428
+ }
25429
+ }
25430
+ function BrowserViewHeader({
25431
+ url,
25432
+ status,
25433
+ isCollapsed,
25434
+ className,
25435
+ onClose,
25436
+ onToggleCollapse,
25437
+ onTuck
25438
+ }) {
25439
+ const { variant, pulse, label } = getStatusBadgeConfig$1(status);
25440
+ return /* @__PURE__ */ jsxRuntime.jsxs(
25441
+ "div",
25442
+ {
25443
+ className: cn(
25444
+ "flex items-center justify-between px-3 py-2 border-b border-border1 bg-surface1",
25445
+ isCollapsed ? "rounded-md" : "rounded-t-md",
25446
+ className
25447
+ ),
25448
+ children: [
25449
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0 mr-3", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-sm text-neutral4 truncate block", !url && "text-neutral3 italic"), children: url || "No URL" }) }),
25450
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
25451
+ /* @__PURE__ */ jsxRuntime.jsx(StatusBadge, { variant, size: "sm", withDot: true, pulse, children: label }),
25452
+ onTuck && /* @__PURE__ */ jsxRuntime.jsx(
25453
+ "button",
25454
+ {
25455
+ onClick: onTuck,
25456
+ className: "p-1 rounded hover:bg-surface3 text-neutral3 hover:text-neutral6 transition-colors",
25457
+ title: "Minimize to pill",
25458
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minus, { className: "h-4 w-4" })
25459
+ }
25460
+ ),
25461
+ onToggleCollapse && /* @__PURE__ */ jsxRuntime.jsx(
25462
+ "button",
25463
+ {
25464
+ onClick: onToggleCollapse,
25465
+ className: "p-1 rounded hover:bg-surface3 text-neutral3 hover:text-neutral6 transition-colors",
25466
+ title: isCollapsed ? "Expand browser view" : "Minimize browser view",
25467
+ children: isCollapsed ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-4 w-4" })
25468
+ }
25469
+ ),
25470
+ onClose && /* @__PURE__ */ jsxRuntime.jsx(
25471
+ "button",
25472
+ {
25473
+ onClick: onClose,
25474
+ className: "p-1 rounded hover:bg-surface3 text-neutral3 hover:text-neutral6 transition-colors",
25475
+ title: "Close browser session",
25476
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
25477
+ }
25478
+ )
25479
+ ] })
25480
+ ]
25481
+ }
25482
+ );
25483
+ }
25484
+
25485
+ function getStatusBadgeConfig(status) {
25486
+ switch (status) {
25487
+ case "idle":
25488
+ return { variant: "neutral", pulse: false, label: "Idle" };
25489
+ case "connecting":
25490
+ return { variant: "warning", pulse: true, label: "Connecting" };
25491
+ case "connected":
25492
+ return { variant: "warning", pulse: true, label: "Connected" };
25493
+ case "browser_starting":
25494
+ return { variant: "warning", pulse: true, label: "Starting" };
25495
+ case "streaming":
25496
+ return { variant: "success", pulse: false, label: "Live" };
25497
+ case "browser_closed":
25498
+ return { variant: "neutral", pulse: false, label: "Closed" };
25499
+ case "disconnected":
25500
+ return { variant: "error", pulse: true, label: "Disconnected" };
25501
+ case "error":
25502
+ return { variant: "error", pulse: false, label: "Error" };
25503
+ default:
25504
+ return { variant: "neutral", pulse: false, label: "Unknown" };
25505
+ }
25506
+ }
25507
+ function BrowserViewPanel() {
25508
+ const { viewMode, status, currentUrl, hide, closeBrowser, setViewMode } = useBrowserSession();
25509
+ const [isVisible, setIsVisible] = React.useState(false);
25510
+ const dialogRef = React.useRef(null);
25511
+ const previousFocusRef = React.useRef(null);
25512
+ const isPanelOpen = viewMode === "modal";
25513
+ React.useEffect(() => {
25514
+ if (isPanelOpen) {
25515
+ requestAnimationFrame(() => setIsVisible(true));
25516
+ } else {
25517
+ setIsVisible(false);
25518
+ }
25519
+ }, [isPanelOpen]);
25520
+ React.useEffect(() => {
25521
+ if (isPanelOpen) {
25522
+ previousFocusRef.current = document.activeElement;
25523
+ dialogRef.current?.focus();
25524
+ } else if (previousFocusRef.current) {
25525
+ previousFocusRef.current.focus();
25526
+ previousFocusRef.current = null;
25527
+ }
25528
+ }, [isPanelOpen]);
25529
+ const handleClose = React.useCallback(async () => {
25530
+ await closeBrowser();
25531
+ }, [closeBrowser]);
25532
+ const handleMinimize = React.useCallback(() => {
25533
+ hide();
25534
+ }, [hide]);
25535
+ const handleOpenSidebar = React.useCallback(() => {
25536
+ setViewMode("sidebar");
25537
+ }, [setViewMode]);
25538
+ const handleOpenExternal = React.useCallback(() => {
25539
+ if (!currentUrl) return;
25540
+ try {
25541
+ const url = new URL(currentUrl);
25542
+ if (url.protocol === "http:" || url.protocol === "https:") {
25543
+ window.open(url.href, "_blank", "noopener,noreferrer");
25544
+ }
25545
+ } catch {
25546
+ }
25547
+ }, [currentUrl]);
25548
+ React.useEffect(() => {
25549
+ if (!isPanelOpen) return;
25550
+ const handleKeyDown = (e) => {
25551
+ if (e.key === "Escape") {
25552
+ e.preventDefault();
25553
+ hide();
25554
+ }
25555
+ };
25556
+ document.addEventListener("keydown", handleKeyDown);
25557
+ return () => document.removeEventListener("keydown", handleKeyDown);
25558
+ }, [isPanelOpen, hide]);
25559
+ const handleBackdropClick = React.useCallback(
25560
+ (e) => {
25561
+ if (e.target === e.currentTarget) {
25562
+ hide();
25563
+ }
25564
+ },
25565
+ [hide]
25566
+ );
25567
+ const statusConfig = getStatusBadgeConfig(status);
25568
+ return /* @__PURE__ */ jsxRuntime.jsx(
25569
+ "div",
25570
+ {
25571
+ className: cn(
25572
+ "fixed inset-0 z-50 flex items-center justify-center p-8",
25573
+ "bg-black/60 backdrop-blur-sm transition-opacity duration-200",
25574
+ isPanelOpen && isVisible ? "opacity-100" : "opacity-0 pointer-events-none"
25575
+ ),
25576
+ onClick: handleBackdropClick,
25577
+ "aria-hidden": !isPanelOpen,
25578
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
25579
+ "div",
24125
25580
  {
24126
- direction: "right",
24127
- id: "right-slot",
24128
- minSize: 300,
24129
- maxSize: "50%",
24130
- defaultSize: "30%",
24131
- collapsedSize: 60,
24132
- collapsible: true,
24133
- children: rightSlot
25581
+ ref: dialogRef,
25582
+ role: "dialog",
25583
+ "aria-modal": "true",
25584
+ "aria-label": "Browser view",
25585
+ tabIndex: -1,
25586
+ className: cn(
25587
+ "flex flex-col w-full max-w-5xl max-h-full",
25588
+ "bg-surface2 rounded-xl border border-border1 shadow-2xl overflow-hidden",
25589
+ "transition-transform duration-200 outline-none",
25590
+ isPanelOpen && isVisible ? "scale-100" : "scale-95"
25591
+ ),
25592
+ onClick: (e) => e.stopPropagation(),
25593
+ children: [
25594
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-4 py-3 border-b border-border1 shrink-0", children: [
25595
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Globe, { className: "h-4 w-4 text-neutral4 shrink-0" }),
25596
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0 px-3 py-1.5 bg-surface3 rounded-md border border-border1", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-sm truncate block", currentUrl ? "text-neutral5" : "text-neutral3 italic"), children: currentUrl || "No URL" }) }),
25597
+ /* @__PURE__ */ jsxRuntime.jsx(StatusBadge, { variant: statusConfig.variant, size: "sm", withDot: true, pulse: statusConfig.pulse, children: statusConfig.label }),
25598
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 ml-2", children: [
25599
+ /* @__PURE__ */ jsxRuntime.jsx(IconButton, { variant: "ghost", size: "sm", tooltip: "Open in sidebar", onClick: handleOpenSidebar, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PanelRight, { className: "h-4 w-4" }) }),
25600
+ /* @__PURE__ */ jsxRuntime.jsx(IconButton, { variant: "ghost", size: "sm", tooltip: "Minimize to chat", onClick: handleMinimize, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize2, { className: "h-4 w-4" }) }),
25601
+ currentUrl && /* @__PURE__ */ jsxRuntime.jsx(IconButton, { variant: "ghost", size: "sm", tooltip: "Open in new tab", onClick: handleOpenExternal, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-4 w-4" }) }),
25602
+ /* @__PURE__ */ jsxRuntime.jsx(IconButton, { variant: "ghost", size: "sm", tooltip: "Close browser", onClick: handleClose, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }) })
25603
+ ] })
25604
+ ] }),
25605
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
25606
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsx(BrowserViewFrame, { className: "w-full max-h-[60vh]" }) }),
25607
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pb-4", children: /* @__PURE__ */ jsxRuntime.jsx(BrowserToolCallHistory, {}) })
25608
+ ] })
25609
+ ]
24134
25610
  }
24135
25611
  )
24136
- ] })
24137
- ] });
24138
- };
25612
+ }
25613
+ );
25614
+ }
25615
+
25616
+ function useBrowserStream(options) {
25617
+ const { agentId, threadId, enabled = true, onFrame, maxReconnectAttempts = 5 } = options;
25618
+ const [status, setStatus] = React.useState("idle");
25619
+ const [error, setError] = React.useState(null);
25620
+ const [currentUrl, setCurrentUrl] = React.useState(null);
25621
+ const [viewport, setViewport] = React.useState(null);
25622
+ const wsRef = React.useRef(null);
25623
+ const reconnectAttemptRef = React.useRef(0);
25624
+ const reconnectTimeoutRef = React.useRef(null);
25625
+ const onFrameRef = React.useRef(onFrame);
25626
+ React.useEffect(() => {
25627
+ onFrameRef.current = onFrame;
25628
+ }, [onFrame]);
25629
+ const clearReconnectTimeout = React.useCallback(() => {
25630
+ if (reconnectTimeoutRef.current) {
25631
+ clearTimeout(reconnectTimeoutRef.current);
25632
+ reconnectTimeoutRef.current = null;
25633
+ }
25634
+ }, []);
25635
+ const disconnect = React.useCallback(() => {
25636
+ clearReconnectTimeout();
25637
+ if (wsRef.current) {
25638
+ wsRef.current.close();
25639
+ wsRef.current = null;
25640
+ }
25641
+ setStatus("idle");
25642
+ setError(null);
25643
+ setViewport(null);
25644
+ }, [clearReconnectTimeout]);
25645
+ const sendMessage = React.useCallback((data) => {
25646
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
25647
+ wsRef.current.send(data);
25648
+ }
25649
+ }, []);
25650
+ const connect = React.useCallback(() => {
25651
+ clearReconnectTimeout();
25652
+ if (wsRef.current) {
25653
+ wsRef.current.close();
25654
+ wsRef.current = null;
25655
+ }
25656
+ setStatus("connecting");
25657
+ setError(null);
25658
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
25659
+ const wsUrl = `${protocol}//${window.location.host}/browser/${agentId}/stream?threadId=${encodeURIComponent(threadId)}`;
25660
+ try {
25661
+ const ws = new WebSocket(wsUrl);
25662
+ wsRef.current = ws;
25663
+ ws.onopen = () => {
25664
+ setStatus("connected");
25665
+ setError(null);
25666
+ reconnectAttemptRef.current = 0;
25667
+ };
25668
+ ws.onmessage = (event) => {
25669
+ const data = event.data;
25670
+ if (data.startsWith("{")) {
25671
+ try {
25672
+ const parsed = JSON.parse(data);
25673
+ if (parsed.status) {
25674
+ switch (parsed.status) {
25675
+ case "browser_starting":
25676
+ setStatus("browser_starting");
25677
+ break;
25678
+ case "streaming":
25679
+ setStatus("streaming");
25680
+ break;
25681
+ case "browser_closed":
25682
+ setStatus("browser_closed");
25683
+ break;
25684
+ case "stopped":
25685
+ setStatus("disconnected");
25686
+ break;
25687
+ default:
25688
+ break;
25689
+ }
25690
+ }
25691
+ if (parsed.error) {
25692
+ setError(parsed.error);
25693
+ setStatus("error");
25694
+ }
25695
+ if (parsed.url) {
25696
+ setCurrentUrl(parsed.url);
25697
+ }
25698
+ if (parsed.viewport) {
25699
+ setViewport(parsed.viewport);
25700
+ }
25701
+ } catch {
25702
+ onFrameRef.current?.(data);
25703
+ }
25704
+ } else {
25705
+ onFrameRef.current?.(data);
25706
+ setStatus((prev) => prev !== "streaming" ? "streaming" : prev);
25707
+ }
25708
+ };
25709
+ ws.onerror = () => {
25710
+ setError("WebSocket error occurred");
25711
+ };
25712
+ ws.onclose = (event) => {
25713
+ wsRef.current = null;
25714
+ if (!event.wasClean && enabled && reconnectAttemptRef.current < maxReconnectAttempts) {
25715
+ setStatus("disconnected");
25716
+ const delay = Math.min(1e3 * Math.pow(2, reconnectAttemptRef.current), 3e4);
25717
+ reconnectAttemptRef.current += 1;
25718
+ reconnectTimeoutRef.current = setTimeout(() => {
25719
+ connect();
25720
+ }, delay);
25721
+ } else if (reconnectAttemptRef.current >= maxReconnectAttempts) {
25722
+ setStatus("error");
25723
+ setError("Maximum reconnection attempts reached");
25724
+ } else {
25725
+ setStatus("idle");
25726
+ }
25727
+ };
25728
+ } catch (err) {
25729
+ setStatus("error");
25730
+ setError(err instanceof Error ? err.message : "Failed to create WebSocket");
25731
+ }
25732
+ }, [agentId, threadId, enabled, maxReconnectAttempts, clearReconnectTimeout]);
25733
+ React.useEffect(() => {
25734
+ const handleVisibilityChange = () => {
25735
+ if (document.visibilityState === "visible" && enabled && status === "disconnected") {
25736
+ reconnectAttemptRef.current = 0;
25737
+ connect();
25738
+ }
25739
+ };
25740
+ document.addEventListener("visibilitychange", handleVisibilityChange);
25741
+ return () => {
25742
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
25743
+ };
25744
+ }, [enabled, status, connect]);
25745
+ React.useEffect(() => {
25746
+ return () => {
25747
+ clearReconnectTimeout();
25748
+ if (wsRef.current) {
25749
+ wsRef.current.close();
25750
+ wsRef.current = null;
25751
+ }
25752
+ };
25753
+ }, [clearReconnectTimeout]);
25754
+ const isActive = status === "connecting" || status === "connected" || status === "browser_starting" || status === "streaming";
25755
+ return {
25756
+ status,
25757
+ error,
25758
+ currentUrl,
25759
+ viewport,
25760
+ sendMessage,
25761
+ connect,
25762
+ disconnect,
25763
+ isActive
25764
+ };
25765
+ }
24139
25766
 
24140
25767
  const ruleSchema = zod.z.object({
24141
25768
  field: zod.z.string(),
@@ -33780,11 +35407,36 @@ function getInitialInput(traceDetails) {
33780
35407
  return JSON.stringify(rawInput, null, 2);
33781
35408
  }
33782
35409
  function TraceAsItemDialog({ traceDetails, traceId, isOpen, onClose, level = 2 }) {
35410
+ const client = react.useMastraClient();
35411
+ const { data: trajectory, isLoading: isTrajectoryLoading } = reactQuery.useQuery({
35412
+ queryKey: ["trace-trajectory", traceId],
35413
+ queryFn: () => client.getTraceTrajectory(traceId),
35414
+ enabled: isOpen && !!traceId
35415
+ });
35416
+ const initialTrajectory = trajectory?.steps && trajectory.steps.length > 0 ? JSON.stringify(
35417
+ {
35418
+ steps: trajectory.steps.map((step) => {
35419
+ const { name, stepType, children, ...rest } = step;
35420
+ const expected = { name, stepType };
35421
+ for (const [k, v] of Object.entries(rest)) {
35422
+ if (v != null && k !== "durationMs" && k !== "metadata") {
35423
+ expected[k] = v;
35424
+ }
35425
+ }
35426
+ return expected;
35427
+ }),
35428
+ ordering: "relaxed"
35429
+ },
35430
+ null,
35431
+ 2
35432
+ ) : void 0;
33783
35433
  return /* @__PURE__ */ jsxRuntime.jsx(
33784
35434
  SaveAsDatasetItemDialog,
33785
35435
  {
33786
35436
  initialInput: getInitialInput(traceDetails),
33787
35437
  initialGroundTruth: traceDetails?.output != null ? JSON.stringify(traceDetails.output, null, 2) : "",
35438
+ initialTrajectory,
35439
+ trajectoryLoading: isTrajectoryLoading,
33788
35440
  breadcrumb: /* @__PURE__ */ jsxRuntime.jsxs(TextAndIcon, { children: [
33789
35441
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeIcon, {}),
33790
35442
  " ",
@@ -34486,7 +36138,10 @@ function TraceDialog({
34486
36138
  if (initialSpanId) {
34487
36139
  setSelectedSpanId(initialSpanId);
34488
36140
  setDialogIsOpen(true);
36141
+ return;
34489
36142
  }
36143
+ setSelectedSpanId(void 0);
36144
+ setDialogIsOpen(false);
34490
36145
  }, [initialSpanId]);
34491
36146
  React.useEffect(() => {
34492
36147
  if (spanScoresPage > 0) {
@@ -34517,11 +36172,17 @@ function TraceDialog({
34517
36172
  });
34518
36173
  const handleSpanClick = (id) => {
34519
36174
  if (selectedSpanId === id) {
36175
+ if (traceId) {
36176
+ navigate(computeTraceLink(traceId));
36177
+ }
34520
36178
  setSelectedSpanId(void 0);
34521
- } else {
34522
- setSelectedSpanId(id);
34523
- setSpanDialogDefaultTab("details");
34524
- setDialogIsOpen(true);
36179
+ return;
36180
+ }
36181
+ setSelectedSpanId(id);
36182
+ setSpanDialogDefaultTab("details");
36183
+ setDialogIsOpen(true);
36184
+ if (traceId) {
36185
+ navigate(computeTraceLink(traceId, id));
34525
36186
  }
34526
36187
  };
34527
36188
  const handleToScoring = () => {
@@ -34771,6 +36432,70 @@ function TraceDialog({
34771
36432
  ] });
34772
36433
  }
34773
36434
 
36435
+ const sizeClasses = {
36436
+ small: "text-[11px] pt-[5px] pb-[4px]",
36437
+ default: "text-[12px] pt-[5px] pb-[4px] ",
36438
+ large: "text-[13px] pt-[5px] pb-[4px] "
36439
+ };
36440
+ const bgColorClasses = {
36441
+ gray: { bright: "bg-neutral-700", muted: "bg-neutral-700/80" },
36442
+ red: { bright: "bg-red-900", muted: "bg-red-900/80" },
36443
+ orange: { bright: "bg-yellow-900", muted: "bg-yellow-900/80" },
36444
+ blue: { bright: "bg-blue-800", muted: "bg-blue-800/80" },
36445
+ green: { bright: "bg-green-900", muted: "bg-green-900/80" },
36446
+ purple: { bright: "bg-purple-900", muted: "bg-purple-900/80" },
36447
+ yellow: { bright: "bg-yellow-700", muted: "bg-yellow-700/80" },
36448
+ cyan: { bright: "bg-cyan-900", muted: "bg-cyan-900/80" },
36449
+ pink: { bright: "bg-pink-900", muted: "bg-pink-900/80" }
36450
+ };
36451
+ const txtIntensityClasses = {
36452
+ bright: "text-neutral4",
36453
+ muted: "text-neutral3"
36454
+ };
36455
+ const Chip = ({
36456
+ color = "gray",
36457
+ size = "default",
36458
+ intensity = "bright",
36459
+ className,
36460
+ children,
36461
+ ...props
36462
+ }) => {
36463
+ return /* @__PURE__ */ jsxRuntime.jsx(
36464
+ "span",
36465
+ {
36466
+ className: cn(
36467
+ "inline-flex items-center rounded-md uppercase px-[0.5em] gap-[0.4em] tracking-wide font-normal",
36468
+ // general styles for svg icons within the chip
36469
+ "[&>svg]:w-[1em] [&>svg]:h-[1em] [&>svg]:translate-y-[-0.02em] [&>svg]:mx-[-0.2em]",
36470
+ // if the chip has only one child and it's an svg, make it fully opaque
36471
+ "[&>svg]:opacity-50 [&>svg:first-child:last-child]:opacity-100",
36472
+ sizeClasses[size],
36473
+ bgColorClasses[color][intensity],
36474
+ txtIntensityClasses[intensity],
36475
+ className
36476
+ ),
36477
+ style: { lineHeight: 1 },
36478
+ ...props,
36479
+ children
36480
+ }
36481
+ );
36482
+ };
36483
+
36484
+ const ChipsGroup = React.forwardRef(function ChipsGroup2({ children, className, ...props }, ref) {
36485
+ return /* @__PURE__ */ jsxRuntime.jsx(
36486
+ "div",
36487
+ {
36488
+ ref,
36489
+ className: cn(
36490
+ "flex gap-[1px] items-center [&>*:not(:last-child)]:rounded-r-none [&>*:not(:first-child)]:rounded-l-none",
36491
+ className
36492
+ ),
36493
+ ...props,
36494
+ children
36495
+ }
36496
+ );
36497
+ });
36498
+
34774
36499
  function formatTimestamp$2(dateStr) {
34775
36500
  const date = new Date(dateStr);
34776
36501
  return date.toLocaleDateString(void 0, {
@@ -34836,6 +36561,43 @@ function parseOutput(output) {
34836
36561
  error: typeof obj.error === "string" ? obj.error : obj.error ? String(obj.error) : void 0
34837
36562
  };
34838
36563
  }
36564
+ function TrajectoryStepsSection({ traceId }) {
36565
+ const client = react.useMastraClient();
36566
+ const [isOpen, setIsOpen] = React.useState(false);
36567
+ const {
36568
+ data: trajectory,
36569
+ isLoading,
36570
+ isError
36571
+ } = reactQuery.useQuery({
36572
+ queryKey: ["trajectory", traceId],
36573
+ queryFn: () => client.getTraceTrajectory(traceId),
36574
+ enabled: isOpen
36575
+ });
36576
+ return /* @__PURE__ */ jsxRuntime.jsxs(Collapsible, { open: isOpen, onOpenChange: setIsOpen, children: [
36577
+ /* @__PURE__ */ jsxRuntime.jsxs(CollapsibleTrigger, { className: "flex items-center gap-1.5 text-xs text-purple-400 font-medium hover:text-purple-300", children: [
36578
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-3 w-3 shrink-0" }),
36579
+ "Trajectory Steps"
36580
+ ] }),
36581
+ /* @__PURE__ */ jsxRuntime.jsx(CollapsibleContent, { children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-1 px-3 py-2", children: [
36582
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: "h-3 w-3" }),
36583
+ /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-neutral3", children: "Loading trajectory..." })
36584
+ ] }) : isError ? /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-red-400 mt-1 px-3 py-2", children: "Failed to load trajectory steps" }) : trajectory?.steps && trajectory.steps.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 space-y-1", children: [
36585
+ trajectory.steps.map((step, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-surface1 rounded text-xs", children: [
36586
+ /* @__PURE__ */ jsxRuntime.jsx(Chip, { size: "small", color: "purple", children: String(step.stepType || "step") }),
36587
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-neutral5 font-mono font-medium", children: String(step.name || `Step ${i + 1}`) }),
36588
+ typeof step.durationMs === "number" && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-neutral2 ml-auto", children: [
36589
+ step.durationMs,
36590
+ "ms"
36591
+ ] })
36592
+ ] }, i)),
36593
+ typeof trajectory.totalDurationMs === "number" && /* @__PURE__ */ jsxRuntime.jsxs(Txt, { variant: "ui-xs", className: "text-neutral3 px-3 py-1", children: [
36594
+ "Total: ",
36595
+ trajectory.totalDurationMs,
36596
+ "ms"
36597
+ ] })
36598
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-neutral2 mt-1 px-3 py-2", children: "No trajectory steps found" }) })
36599
+ ] });
36600
+ }
34839
36601
  function ResultOutputSection({
34840
36602
  output,
34841
36603
  traceId,
@@ -35072,12 +36834,31 @@ function ExperimentResultsPanel({
35072
36834
  /* @__PURE__ */ jsxRuntime.jsx(Checkbox, { checked: isChecked, onCheckedChange: () => toggleItem(result.id) }),
35073
36835
  /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: hasError ? "error" : "success", children: hasError ? "Error" : "Success" }),
35074
36836
  /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-neutral2 font-mono", children: result.itemId.slice(0, 8) }),
35075
- itemScores.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 ml-auto", children: itemScores.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: "default", children: [
35076
- s.scorerId,
35077
- ": ",
35078
- s.score.toFixed(3)
35079
- ] }, s.scorerId)) })
36837
+ itemScores.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 ml-auto flex-wrap", children: [
36838
+ itemScores.filter((s) => s.entityType !== "TRAJECTORY").map((s) => /* @__PURE__ */ jsxRuntime.jsxs(Badge, { variant: "default", children: [
36839
+ s.scorerId,
36840
+ ": ",
36841
+ s.score.toFixed(3)
36842
+ ] }, s.scorerId)),
36843
+ itemScores.filter((s) => s.entityType === "TRAJECTORY").map((s) => /* @__PURE__ */ jsxRuntime.jsxs(Chip, { size: "small", color: "purple", children: [
36844
+ s.scorerId,
36845
+ ": ",
36846
+ s.score.toFixed(3)
36847
+ ] }, s.scorerId))
36848
+ ] })
35080
36849
  ] }),
36850
+ itemScores.some((s) => s.entityType === "TRAJECTORY") && /* @__PURE__ */ jsxRuntime.jsxs(Collapsible, { children: [
36851
+ /* @__PURE__ */ jsxRuntime.jsxs(CollapsibleTrigger, { className: "flex items-center gap-1.5 text-xs text-purple-400 font-medium hover:text-purple-300", children: [
36852
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-3 w-3 shrink-0" }),
36853
+ "Trajectory Score Details"
36854
+ ] }),
36855
+ /* @__PURE__ */ jsxRuntime.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 space-y-2", children: itemScores.filter((s) => s.entityType === "TRAJECTORY").map((s) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-surface1 rounded px-3 py-2 space-y-1", children: [
36856
+ /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-purple-400 font-medium", children: s.scorerId }),
36857
+ s.reason && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-neutral4", children: s.reason }),
36858
+ s.preprocessStepResult && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-xs text-neutral3 overflow-x-auto whitespace-pre-wrap break-words max-h-48 overflow-y-auto", children: JSON.stringify(s.preprocessStepResult, null, 2) })
36859
+ ] }, s.scorerId)) }) })
36860
+ ] }),
36861
+ result.traceId && itemScores.some((s) => s.entityType === "TRAJECTORY") && /* @__PURE__ */ jsxRuntime.jsx(TrajectoryStepsSection, { traceId: result.traceId }),
35081
36862
  /* @__PURE__ */ jsxRuntime.jsxs(Collapsible, { children: [
35082
36863
  /* @__PURE__ */ jsxRuntime.jsxs(CollapsibleTrigger, { className: "flex items-center gap-1.5 text-xs text-neutral3 font-medium hover:text-neutral5", children: [
35083
36864
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-3 w-3 shrink-0" }),
@@ -35341,70 +37122,6 @@ const useDatasetVersions = (datasetId) => {
35341
37122
  });
35342
37123
  };
35343
37124
 
35344
- const sizeClasses = {
35345
- small: "text-[11px] pt-[5px] pb-[4px]",
35346
- default: "text-[12px] pt-[5px] pb-[4px] ",
35347
- large: "text-[13px] pt-[5px] pb-[4px] "
35348
- };
35349
- const bgColorClasses = {
35350
- gray: { bright: "bg-neutral-700", muted: "bg-neutral-700/80" },
35351
- red: { bright: "bg-red-900", muted: "bg-red-900/80" },
35352
- orange: { bright: "bg-yellow-900", muted: "bg-yellow-900/80" },
35353
- blue: { bright: "bg-blue-800", muted: "bg-blue-800/80" },
35354
- green: { bright: "bg-green-900", muted: "bg-green-900/80" },
35355
- purple: { bright: "bg-purple-900", muted: "bg-purple-900/80" },
35356
- yellow: { bright: "bg-yellow-700", muted: "bg-yellow-700/80" },
35357
- cyan: { bright: "bg-cyan-900", muted: "bg-cyan-900/80" },
35358
- pink: { bright: "bg-pink-900", muted: "bg-pink-900/80" }
35359
- };
35360
- const txtIntensityClasses = {
35361
- bright: "text-neutral4",
35362
- muted: "text-neutral3"
35363
- };
35364
- const Chip = ({
35365
- color = "gray",
35366
- size = "default",
35367
- intensity = "bright",
35368
- className,
35369
- children,
35370
- ...props
35371
- }) => {
35372
- return /* @__PURE__ */ jsxRuntime.jsx(
35373
- "span",
35374
- {
35375
- className: cn(
35376
- "inline-flex items-center rounded-md uppercase px-[0.5em] gap-[0.4em] tracking-wide font-normal",
35377
- // general styles for svg icons within the chip
35378
- "[&>svg]:w-[1em] [&>svg]:h-[1em] [&>svg]:translate-y-[-0.02em] [&>svg]:mx-[-0.2em]",
35379
- // if the chip has only one child and it's an svg, make it fully opaque
35380
- "[&>svg]:opacity-50 [&>svg:first-child:last-child]:opacity-100",
35381
- sizeClasses[size],
35382
- bgColorClasses[color][intensity],
35383
- txtIntensityClasses[intensity],
35384
- className
35385
- ),
35386
- style: { lineHeight: 1 },
35387
- ...props,
35388
- children
35389
- }
35390
- );
35391
- };
35392
-
35393
- const ChipsGroup = React.forwardRef(function ChipsGroup2({ children, className, ...props }, ref) {
35394
- return /* @__PURE__ */ jsxRuntime.jsx(
35395
- "div",
35396
- {
35397
- ref,
35398
- className: cn(
35399
- "flex gap-[1px] items-center [&>*:not(:last-child)]:rounded-r-none [&>*:not(:first-child)]:rounded-l-none",
35400
- className
35401
- ),
35402
- ...props,
35403
- children
35404
- }
35405
- );
35406
- });
35407
-
35408
37125
  function formatTimestamp$1(date) {
35409
37126
  const d = new Date(date);
35410
37127
  return d.toLocaleDateString("en-US", { month: "short", day: "numeric" }) + ", " + d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
@@ -35415,6 +37132,11 @@ function truncateValue$2(value, maxLength = 120) {
35415
37132
  if (!str || str.length <= maxLength) return str || "-";
35416
37133
  return str.slice(0, maxLength) + "…";
35417
37134
  }
37135
+ function getExpectedTrajectoryLabel(expectedTrajectory) {
37136
+ const traj = expectedTrajectory;
37137
+ const steps = Array.isArray(traj?.steps) ? traj.steps.length : 0;
37138
+ return steps > 0 ? `${steps} expected steps` : "trajectory";
37139
+ }
35418
37140
  const TAG_COLORS = ["blue", "green", "purple", "orange", "cyan", "pink", "red", "yellow"];
35419
37141
  function getTagColor(tag) {
35420
37142
  let hash = 0;
@@ -35698,7 +37420,10 @@ function DatasetDetailView({
35698
37420
  className: "w-full text-left px-4 py-2 hover:bg-surface3 transition-colors flex items-start gap-2",
35699
37421
  children: [
35700
37422
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: "sm", className: "text-neutral3 mt-0.5 shrink-0", children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, {}) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, {}) }),
35701
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-neutral5 block truncate", children: truncateValue$2(item.input) }) })
37423
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 flex items-center gap-2", children: [
37424
+ /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-neutral5 block truncate flex-1", children: truncateValue$2(item.input) }),
37425
+ item.expectedTrajectory != null && /* @__PURE__ */ jsxRuntime.jsx(Chip, { size: "small", color: "purple", children: getExpectedTrajectoryLabel(item.expectedTrajectory) })
37426
+ ] })
35702
37427
  ]
35703
37428
  }
35704
37429
  ),
@@ -35820,12 +37545,14 @@ function ExpandedItemEditor({
35820
37545
  const [isConfirmingDelete, setIsConfirmingDelete] = React.useState(false);
35821
37546
  const [inputValue, setInputValue] = React.useState("");
35822
37547
  const [groundTruthValue, setGroundTruthValue] = React.useState("");
37548
+ const [trajectoryValue, setTrajectoryValue] = React.useState("");
35823
37549
  const { updateItem, deleteItem } = useDatasetMutations();
35824
37550
  const startEditing = React.useCallback(() => {
35825
37551
  setInputValue(formatValue$4(item.input));
35826
37552
  setGroundTruthValue(formatValue$4(item.groundTruth));
37553
+ setTrajectoryValue(formatValue$4(item.expectedTrajectory));
35827
37554
  setIsEditing(true);
35828
- }, [item.input, item.groundTruth]);
37555
+ }, [item.input, item.groundTruth, item.expectedTrajectory]);
35829
37556
  const cancelEditing = React.useCallback(() => {
35830
37557
  setIsEditing(false);
35831
37558
  }, []);
@@ -35852,19 +37579,28 @@ function ExpandedItemEditor({
35852
37579
  parsedGroundTruth = groundTruthValue;
35853
37580
  }
35854
37581
  }
37582
+ let parsedTrajectory;
37583
+ if (trajectoryValue.trim()) {
37584
+ try {
37585
+ parsedTrajectory = JSON.parse(trajectoryValue);
37586
+ } catch {
37587
+ parsedTrajectory = trajectoryValue;
37588
+ }
37589
+ }
35855
37590
  try {
35856
37591
  await updateItem.mutateAsync({
35857
37592
  datasetId,
35858
37593
  itemId: item.id,
35859
37594
  input: parsedInput,
35860
- groundTruth: parsedGroundTruth
37595
+ groundTruth: parsedGroundTruth,
37596
+ expectedTrajectory: parsedTrajectory
35861
37597
  });
35862
37598
  toast.success("Item updated");
35863
37599
  setIsEditing(false);
35864
37600
  } catch (error) {
35865
37601
  toast.error(`Failed to update: ${error instanceof Error ? error.message : "Unknown error"}`);
35866
37602
  }
35867
- }, [inputValue, groundTruthValue, datasetId, item.id, updateItem]);
37603
+ }, [inputValue, groundTruthValue, trajectoryValue, datasetId, item.id, updateItem]);
35868
37604
  if (isEditing) {
35869
37605
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 pb-3 pl-10 space-y-2", children: [
35870
37606
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -35892,6 +37628,19 @@ function ExpandedItemEditor({
35892
37628
  }
35893
37629
  )
35894
37630
  ] }),
37631
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
37632
+ /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-neutral3 font-medium", children: "Expected Trajectory (JSON)" }),
37633
+ /* @__PURE__ */ jsxRuntime.jsx(
37634
+ Textarea,
37635
+ {
37636
+ value: trajectoryValue,
37637
+ onChange: (e) => setTrajectoryValue(e.target.value),
37638
+ className: "mt-1 font-mono text-xs",
37639
+ rows: 3,
37640
+ placeholder: "Optional — JSON trajectory expectation"
37641
+ }
37642
+ )
37643
+ ] }),
35895
37644
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 pt-1", children: [
35896
37645
  /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "cta", size: "sm", onClick: handleSave, disabled: updateItem.isPending, children: [
35897
37646
  updateItem.isPending ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: "h-3 w-3" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, {}) }),
@@ -35913,6 +37662,10 @@ function ExpandedItemEditor({
35913
37662
  /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-neutral3 font-medium", children: "Ground Truth" }),
35914
37663
  /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-xs text-neutral5 bg-surface1 rounded px-2 py-1.5 overflow-x-auto whitespace-pre-wrap wrap-break-word max-h-48 overflow-y-auto mt-1", children: formatValue$4(item.groundTruth) })
35915
37664
  ] }),
37665
+ item.expectedTrajectory != null && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
37666
+ /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-neutral3 font-medium", children: "Expected Trajectory" }),
37667
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "text-xs text-neutral5 bg-surface1 rounded px-2 py-1.5 overflow-x-auto whitespace-pre-wrap break-words max-h-48 overflow-y-auto mt-1", children: formatValue$4(item.expectedTrajectory) })
37668
+ ] }),
35916
37669
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 pt-1", children: [
35917
37670
  /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "ghost", size: "sm", onClick: startEditing, children: [
35918
37671
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: "sm", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, {}) }),
@@ -35973,12 +37726,14 @@ function ScorerDetailView({
35973
37726
  const name = scorerData.scorer?.name || scorerId;
35974
37727
  const description = scorerData.scorer?.description;
35975
37728
  const isCode = scorerData.source === "code";
37729
+ const isTrajectory = scorerData.scorer?.config?.type === "trajectory";
35976
37730
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col h-full overflow-hidden", children: [
35977
37731
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 border-b border-border1 space-y-3", children: [
35978
37732
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
35979
37733
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
35980
37734
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
35981
37735
  /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-sm", className: "text-neutral5 font-medium truncate", children: name }),
37736
+ isTrajectory && /* @__PURE__ */ jsxRuntime.jsx(Chip, { size: "small", color: "purple", children: "trajectory" }),
35982
37737
  isCode && /* @__PURE__ */ jsxRuntime.jsx("span", { title: "Defined in code — cannot be edited in the UI", children: /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", children: "Code" }) })
35983
37738
  ] }),
35984
37739
  description && /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-neutral3 block mt-1", children: description })
@@ -36001,6 +37756,7 @@ function ScorerDetailView({
36001
37756
  /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-xs", className: "text-neutral3 font-medium uppercase tracking-wider block mb-2", children: "Details" }),
36002
37757
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
36003
37758
  /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "ID", value: scorerId }),
37759
+ /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Type", value: isTrajectory ? "Trajectory" : "Agent" }),
36004
37760
  /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Source", value: isCode ? "Code" : "Stored" }),
36005
37761
  scorerData.agentIds && scorerData.agentIds.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
36006
37762
  DetailRow,
@@ -39466,6 +41222,197 @@ function extractErrorText(error) {
39466
41222
  return "Error";
39467
41223
  }
39468
41224
 
41225
+ function BulkTraceReviewDialog({
41226
+ isOpen,
41227
+ onClose,
41228
+ datasetId,
41229
+ datasetName,
41230
+ initialItems
41231
+ }) {
41232
+ const [items, setItems] = React.useState(initialItems);
41233
+ const [currentIndex, setCurrentIndex] = React.useState(0);
41234
+ const { batchInsertItems } = useDatasetMutations();
41235
+ React.useEffect(() => {
41236
+ if (isOpen) {
41237
+ setItems(initialItems);
41238
+ setCurrentIndex(0);
41239
+ }
41240
+ }, [isOpen]);
41241
+ const currentItem = items[currentIndex];
41242
+ const total = items.length;
41243
+ const updateCurrentItem = React.useCallback(
41244
+ (field, value) => {
41245
+ setItems((prev) => prev.map((item, i) => i === currentIndex ? { ...item, [field]: value } : item));
41246
+ },
41247
+ [currentIndex]
41248
+ );
41249
+ const removeCurrentItem = React.useCallback(() => {
41250
+ setItems((prev) => {
41251
+ const next = prev.filter((_, i) => i !== currentIndex);
41252
+ if (next.length === 0) {
41253
+ onClose();
41254
+ return prev;
41255
+ }
41256
+ setCurrentIndex((idx) => Math.min(idx, next.length - 1));
41257
+ return next;
41258
+ });
41259
+ }, [currentIndex, onClose]);
41260
+ const handleSubmit = async () => {
41261
+ const parsed = [];
41262
+ for (let i = 0; i < items.length; i++) {
41263
+ const item = items[i];
41264
+ let parsedInput;
41265
+ try {
41266
+ parsedInput = JSON.parse(item.input);
41267
+ } catch {
41268
+ toast.error(`Item ${i + 1}: Input must be valid JSON`);
41269
+ setCurrentIndex(i);
41270
+ return;
41271
+ }
41272
+ let parsedGroundTruth;
41273
+ if (item.groundTruth.trim()) {
41274
+ try {
41275
+ parsedGroundTruth = JSON.parse(item.groundTruth);
41276
+ } catch {
41277
+ toast.error(`Item ${i + 1}: Ground Truth must be valid JSON`);
41278
+ setCurrentIndex(i);
41279
+ return;
41280
+ }
41281
+ }
41282
+ let parsedTrajectory;
41283
+ if (item.expectedTrajectory.trim()) {
41284
+ try {
41285
+ parsedTrajectory = JSON.parse(item.expectedTrajectory);
41286
+ } catch {
41287
+ toast.error(`Item ${i + 1}: Expected Trajectory must be valid JSON`);
41288
+ setCurrentIndex(i);
41289
+ return;
41290
+ }
41291
+ }
41292
+ parsed.push({
41293
+ input: parsedInput,
41294
+ groundTruth: parsedGroundTruth,
41295
+ expectedTrajectory: parsedTrajectory,
41296
+ ...item.source ? { source: item.source } : {}
41297
+ });
41298
+ }
41299
+ try {
41300
+ await batchInsertItems.mutateAsync({ datasetId, items: parsed });
41301
+ toast.success(`Added ${parsed.length} item${parsed.length !== 1 ? "s" : ""} to "${datasetName}"`);
41302
+ onClose();
41303
+ } catch {
41304
+ toast.error("Failed to add items to dataset");
41305
+ }
41306
+ };
41307
+ if (!currentItem) return null;
41308
+ return /* @__PURE__ */ jsxRuntime.jsxs(
41309
+ SideDialog,
41310
+ {
41311
+ dialogTitle: "Review items before adding to dataset",
41312
+ dialogDescription: `Reviewing ${total} item${total !== 1 ? "s" : ""} for dataset "${datasetName}"`,
41313
+ isOpen,
41314
+ onClose,
41315
+ level: 1,
41316
+ children: [
41317
+ /* @__PURE__ */ jsxRuntime.jsxs(SideDialog.Top, { children: [
41318
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DatabaseIcon, { className: "size-4" }),
41319
+ " Review ",
41320
+ total,
41321
+ " item",
41322
+ total !== 1 ? "s" : "",
41323
+ " → ",
41324
+ datasetName
41325
+ ] }),
41326
+ /* @__PURE__ */ jsxRuntime.jsxs(SideDialog.Content, { children: [
41327
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-4", children: [
41328
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
41329
+ /* @__PURE__ */ jsxRuntime.jsx(
41330
+ IconButton,
41331
+ {
41332
+ tooltip: "Previous item",
41333
+ variant: "outline",
41334
+ size: "sm",
41335
+ disabled: currentIndex === 0,
41336
+ onClick: () => setCurrentIndex((prev) => prev - 1),
41337
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeftIcon, {})
41338
+ }
41339
+ ),
41340
+ /* @__PURE__ */ jsxRuntime.jsxs(Txt, { variant: "ui-sm", className: "text-icon3 tabular-nums", children: [
41341
+ currentIndex + 1,
41342
+ " / ",
41343
+ total
41344
+ ] }),
41345
+ /* @__PURE__ */ jsxRuntime.jsx(
41346
+ IconButton,
41347
+ {
41348
+ tooltip: "Next item",
41349
+ variant: "outline",
41350
+ size: "sm",
41351
+ disabled: currentIndex === total - 1,
41352
+ onClick: () => setCurrentIndex((prev) => prev + 1),
41353
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, {})
41354
+ }
41355
+ )
41356
+ ] }),
41357
+ /* @__PURE__ */ jsxRuntime.jsx(IconButton, { tooltip: "Remove this item", variant: "ghost", size: "sm", onClick: removeCurrentItem, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrashIcon, {}) })
41358
+ ] }),
41359
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4", children: [
41360
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
41361
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Input (JSON) *" }),
41362
+ /* @__PURE__ */ jsxRuntime.jsx(
41363
+ CodeEditor,
41364
+ {
41365
+ value: currentItem.input,
41366
+ onChange: (v) => updateCurrentItem("input", v),
41367
+ showCopyButton: false,
41368
+ className: "min-h-[120px]"
41369
+ }
41370
+ )
41371
+ ] }),
41372
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
41373
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Ground Truth (JSON, optional)" }),
41374
+ /* @__PURE__ */ jsxRuntime.jsx(
41375
+ CodeEditor,
41376
+ {
41377
+ value: currentItem.groundTruth,
41378
+ onChange: (v) => updateCurrentItem("groundTruth", v),
41379
+ showCopyButton: false,
41380
+ className: "min-h-[80px]"
41381
+ }
41382
+ )
41383
+ ] }),
41384
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
41385
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Expected Trajectory (JSON, optional)" }),
41386
+ /* @__PURE__ */ jsxRuntime.jsx(
41387
+ CodeEditor,
41388
+ {
41389
+ value: currentItem.expectedTrajectory,
41390
+ onChange: (v) => updateCurrentItem("expectedTrajectory", v),
41391
+ showCopyButton: false,
41392
+ className: "min-h-[80px]"
41393
+ }
41394
+ )
41395
+ ] }),
41396
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 pt-4", children: [
41397
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "outline", onClick: onClose, children: "Cancel" }),
41398
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "light", disabled: batchInsertItems.isPending, onClick: handleSubmit, children: batchInsertItems.isPending ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
41399
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "size-4 animate-spin" }),
41400
+ "Adding..."
41401
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
41402
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DatabaseIcon, { className: "size-4" }),
41403
+ "Add all ",
41404
+ total,
41405
+ " item",
41406
+ total !== 1 ? "s" : ""
41407
+ ] }) })
41408
+ ] })
41409
+ ] })
41410
+ ] })
41411
+ ]
41412
+ }
41413
+ );
41414
+ }
41415
+
39469
41416
  function EntityListPageLayoutRoot({ children, className }) {
39470
41417
  return /* @__PURE__ */ jsxRuntime.jsx(
39471
41418
  "div",
@@ -39559,16 +41506,6 @@ function extractInputPreview(input) {
39559
41506
  if (typeof obj.input === "string") return obj.input;
39560
41507
  return JSON.stringify(input).slice(0, 200);
39561
41508
  }
39562
- function extractRawInput(input) {
39563
- if (!input || typeof input !== "object") return input;
39564
- const obj = input;
39565
- if (obj.messages && Array.isArray(obj.messages)) {
39566
- const userMsgs = obj.messages.filter((m) => m.role === "user");
39567
- const last = userMsgs[userMsgs.length - 1];
39568
- if (last?.content) return last.content;
39569
- }
39570
- return input;
39571
- }
39572
41509
  function extractOutputPreview(output) {
39573
41510
  if (!output) return "";
39574
41511
  if (typeof output === "string") return output;
@@ -39754,11 +41691,31 @@ function BulkAddToDatasetBar({
39754
41691
  ] })
39755
41692
  ] });
39756
41693
  }
39757
- function AgentTracesPanel({ agentId }) {
41694
+ function AgentTracesPanel({
41695
+ agentId,
41696
+ basePath,
41697
+ initialTraceId,
41698
+ initialSpanId,
41699
+ initialSpanTab,
41700
+ initialScoreId
41701
+ }) {
39758
41702
  const client = react.useMastraClient();
39759
41703
  const filters = useAgentTracesFilters(agentId);
39760
- const [selectedTraceId, setSelectedTraceId] = React.useState();
39761
- const [dialogIsOpen, setDialogIsOpen] = React.useState(false);
41704
+ const { navigate } = useLinkComponent();
41705
+ const buildTraceUrl = React.useCallback(
41706
+ (traceId, spanId, scoreId, tab) => {
41707
+ const params = new URLSearchParams();
41708
+ if (traceId) params.set("traceId", traceId);
41709
+ if (spanId) params.set("spanId", spanId);
41710
+ if (tab) params.set("tab", tab);
41711
+ if (scoreId) params.set("scoreId", scoreId);
41712
+ const query = params.toString();
41713
+ return query ? `${basePath ?? `/agents/${agentId}/traces`}?${query}` : basePath ?? `/agents/${agentId}/traces`;
41714
+ },
41715
+ [agentId, basePath]
41716
+ );
41717
+ const [selectedTraceId, setSelectedTraceId] = React.useState(initialTraceId);
41718
+ const [dialogIsOpen, setDialogIsOpen] = React.useState(Boolean(initialTraceId));
39762
41719
  const [checkedTraceIds, setCheckedTraceIds] = React.useState(/* @__PURE__ */ new Set());
39763
41720
  const [sort, setSort] = React.useState(null);
39764
41721
  const {
@@ -39871,20 +41828,32 @@ function AgentTracesPanel({ agentId }) {
39871
41828
  return next.size === prev.size ? prev : next;
39872
41829
  });
39873
41830
  }, [displayTraces, checkedTraceIds.size]);
39874
- const { batchInsertItems } = useDatasetMutations();
41831
+ const [bulkReview, setBulkReview] = React.useState({ isOpen: false, datasetId: "", datasetName: "", items: [] });
41832
+ const [isPreparing, setIsPreparing] = React.useState(false);
41833
+ React.useEffect(() => {
41834
+ if (initialTraceId) {
41835
+ setSelectedTraceId(initialTraceId);
41836
+ setDialogIsOpen(true);
41837
+ return;
41838
+ }
41839
+ setSelectedTraceId(void 0);
41840
+ setDialogIsOpen(false);
41841
+ }, [initialTraceId]);
39875
41842
  const allSelected = displayTraces.length > 0 && displayTraces.every((t) => checkedTraceIds.has(t.traceId));
39876
41843
  const someSelected = checkedTraceIds.size > 0;
39877
41844
  const handleTraceClick = React.useCallback(
39878
41845
  (traceId) => {
39879
41846
  if (selectedTraceId === traceId) {
41847
+ navigate(buildTraceUrl());
39880
41848
  setSelectedTraceId(void 0);
39881
41849
  setDialogIsOpen(false);
39882
41850
  } else {
41851
+ navigate(buildTraceUrl(traceId));
39883
41852
  setSelectedTraceId(traceId);
39884
41853
  setDialogIsOpen(true);
39885
41854
  }
39886
41855
  },
39887
- [selectedTraceId]
41856
+ [buildTraceUrl, navigate, selectedTraceId]
39888
41857
  );
39889
41858
  const handleCheckToggle = React.useCallback((traceId, checked) => {
39890
41859
  setCheckedTraceIds((prev) => {
@@ -39904,33 +41873,67 @@ function AgentTracesPanel({ agentId }) {
39904
41873
  setCheckedTraceIds(new Set(displayTraces.map((t) => t.traceId)));
39905
41874
  }
39906
41875
  }, [allSelected, displayTraces]);
41876
+ const { data: allDatasets } = useDatasets();
39907
41877
  const handleBulkAdd = React.useCallback(
39908
41878
  async (datasetId) => {
39909
41879
  const selected = displayTraces.filter((t) => checkedTraceIds.has(t.traceId));
39910
41880
  if (!selected.length) return;
39911
- const items = selected.map((t) => ({
39912
- input: extractRawInput(t.input),
39913
- groundTruth: t.output ?? void 0,
39914
- source: { type: "trace", referenceId: t.traceId }
39915
- }));
41881
+ setIsPreparing(true);
39916
41882
  try {
39917
- await batchInsertItems.mutateAsync({ datasetId, items });
39918
- toast.success(`Added ${items.length} item${items.length !== 1 ? "s" : ""} to dataset`);
39919
- setCheckedTraceIds(/* @__PURE__ */ new Set());
39920
- } catch {
39921
- toast.error("Failed to add items to dataset");
41883
+ const BATCH_SIZE = 5;
41884
+ const trajectories = [];
41885
+ for (let i = 0; i < selected.length; i += BATCH_SIZE) {
41886
+ const batch = selected.slice(i, i + BATCH_SIZE);
41887
+ const results = await Promise.all(
41888
+ batch.map((t) => client.getTraceTrajectory(t.traceId).catch(() => void 0))
41889
+ );
41890
+ trajectories.push(...results);
41891
+ }
41892
+ const items = selected.map((t, i) => {
41893
+ const trajectory = trajectories[i];
41894
+ let trajectoryExpectation;
41895
+ if (trajectory?.steps && trajectory.steps.length > 0) {
41896
+ trajectoryExpectation = {
41897
+ steps: trajectory.steps.map((step) => {
41898
+ const { name, stepType, ...rest } = step;
41899
+ const expected = { name, stepType };
41900
+ for (const [k, v] of Object.entries(rest)) {
41901
+ if (v != null && k !== "durationMs" && k !== "metadata" && k !== "children") {
41902
+ expected[k] = v;
41903
+ }
41904
+ }
41905
+ return expected;
41906
+ }),
41907
+ ordering: "relaxed"
41908
+ };
41909
+ }
41910
+ const formatJson = (val) => val != null ? JSON.stringify(val, null, 2) : "";
41911
+ const rawInput = t.spanType === "agent_run" && t.input && typeof t.input === "object" && !Array.isArray(t.input) && Array.isArray(t.input.messages) ? t.input.messages : t.input;
41912
+ return {
41913
+ input: formatJson(rawInput),
41914
+ groundTruth: formatJson(t.output),
41915
+ expectedTrajectory: formatJson(trajectoryExpectation),
41916
+ source: { type: "trace", referenceId: t.traceId }
41917
+ };
41918
+ });
41919
+ const datasetName = allDatasets?.datasets?.find((d) => d.id === datasetId)?.name ?? "dataset";
41920
+ setBulkReview({ isOpen: true, datasetId, datasetName, items });
41921
+ } finally {
41922
+ setIsPreparing(false);
39922
41923
  }
39923
41924
  },
39924
- [displayTraces, checkedTraceIds, batchInsertItems]
41925
+ [displayTraces, checkedTraceIds, client, allDatasets]
41926
+ );
41927
+ const computeTraceLink = React.useCallback(
41928
+ (traceId, spanId, tab) => buildTraceUrl(traceId, spanId, void 0, tab),
41929
+ [buildTraceUrl]
39925
41930
  );
39926
- const computeTraceLink = React.useCallback((traceId, spanId) => {
39927
- return `/observability?traceId=${traceId}${spanId ? `&spanId=${spanId}` : ""}`;
39928
- }, []);
39929
41931
  const toNextTrace = React.useMemo(
39930
41932
  () => getToNextEntryFn({
39931
41933
  entries: displayTraces.map((t) => ({ id: t.traceId })),
39932
41934
  id: selectedTraceId,
39933
41935
  update: (id) => {
41936
+ navigate(buildTraceUrl(id));
39934
41937
  setSelectedTraceId(id);
39935
41938
  setDialogIsOpen(true);
39936
41939
  }
@@ -39942,6 +41945,7 @@ function AgentTracesPanel({ agentId }) {
39942
41945
  entries: displayTraces.map((t) => ({ id: t.traceId })),
39943
41946
  id: selectedTraceId,
39944
41947
  update: (id) => {
41948
+ navigate(buildTraceUrl(id));
39945
41949
  setSelectedTraceId(id);
39946
41950
  setDialogIsOpen(true);
39947
41951
  }
@@ -39958,14 +41962,7 @@ function AgentTracesPanel({ agentId }) {
39958
41962
  }
39959
41963
  return /* @__PURE__ */ jsxRuntime.jsxs(EntityListPageLayout, { children: [
39960
41964
  /* @__PURE__ */ jsxRuntime.jsx(EntityListPageLayout.Top, { children: /* @__PURE__ */ jsxRuntime.jsx(AgentTracesToolbar, { filters, scorerOptions }) }),
39961
- someSelected && /* @__PURE__ */ jsxRuntime.jsx(
39962
- BulkAddToDatasetBar,
39963
- {
39964
- selectedCount: checkedTraceIds.size,
39965
- onAdd: handleBulkAdd,
39966
- isPending: batchInsertItems.isPending
39967
- }
39968
- ),
41965
+ someSelected && /* @__PURE__ */ jsxRuntime.jsx(BulkAddToDatasetBar, { selectedCount: checkedTraceIds.size, onAdd: handleBulkAdd, isPending: isPreparing }),
39969
41966
  isTracesLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-20", children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}) }) : displayTraces.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
39970
41967
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-20", children: /* @__PURE__ */ jsxRuntime.jsx(Txt, { variant: "ui-md", className: "text-icon3", children: filters.filtersApplied ? "No traces match the current filters." : "No traces yet." }) }),
39971
41968
  /* @__PURE__ */ jsxRuntime.jsx("div", { ref: setEndOfListElement, className: "h-1", children: isFetchingNextPage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center py-4", children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}) }) })
@@ -40043,6 +42040,7 @@ function AgentTracesPanel({ agentId }) {
40043
42040
  traceDetails: selectedTrace?.spans?.find((s) => s.traceId === selectedTraceId && !s.parentSpanId),
40044
42041
  isOpen: dialogIsOpen,
40045
42042
  onClose: () => {
42043
+ navigate(buildTraceUrl());
40046
42044
  setDialogIsOpen(false);
40047
42045
  setSelectedTraceId(void 0);
40048
42046
  },
@@ -40050,9 +42048,25 @@ function AgentTracesPanel({ agentId }) {
40050
42048
  onPrevious: toPreviousTrace,
40051
42049
  isLoadingSpans: isSelectedTraceLoading,
40052
42050
  computeTraceLink,
42051
+ initialSpanId,
42052
+ initialSpanTab,
42053
+ initialScoreId,
40053
42054
  scorers: scorersMap,
40054
42055
  isLoadingScorers
40055
42056
  }
42057
+ ),
42058
+ /* @__PURE__ */ jsxRuntime.jsx(
42059
+ BulkTraceReviewDialog,
42060
+ {
42061
+ isOpen: bulkReview.isOpen,
42062
+ onClose: () => {
42063
+ setBulkReview((prev) => ({ ...prev, isOpen: false }));
42064
+ setCheckedTraceIds(/* @__PURE__ */ new Set());
42065
+ },
42066
+ datasetId: bulkReview.datasetId,
42067
+ datasetName: bulkReview.datasetName,
42068
+ initialItems: bulkReview.items
42069
+ }
40056
42070
  )
40057
42071
  ] });
40058
42072
  }
@@ -41906,7 +43920,7 @@ function MetricsCardRoot({ children, className }) {
41906
43920
  DashboardCard,
41907
43921
  {
41908
43922
  className: cn(
41909
- "flex-1 grid grid-rows-[4rem_20rem] gap-2 min-w-80 md:min-w-[22rem] lg:min-w-[24rem] xl:min-w-[26rem] 2xl:min-w-[30rem] 3xl:min-w-[32rem]",
43923
+ "flex-1 grid grid-rows-[4rem_1fr] min-h-72 gap-2 min-w-80 md:min-w-[22rem] lg:min-w-[24rem] xl:min-w-[26rem] 2xl:min-w-[30rem]",
41910
43924
  className
41911
43925
  ),
41912
43926
  children
@@ -42057,12 +44071,12 @@ function MetricsLineChart({
42057
44071
  yDomain
42058
44072
  }) {
42059
44073
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
42060
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap w-full items-end gap-4 gap-y-1 mb-4", children: series.map((s) => {
44074
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap w-full items-end gap-4 gap-y-1 mb-4 ", children: series.map((s) => {
42061
44075
  const aggregated = s.aggregate?.(data);
42062
44076
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-baseline gap-2", children: [
42063
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-2 shrink-0 rounded-full translate-y-[-1px]", style: { backgroundColor: s.color } }),
44077
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-2 shrink-0 rounded-full -translate-y-px", style: { backgroundColor: s.color } }),
42064
44078
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui-sm text-neutral3 truncate max-w-24", children: s.label }),
42065
- aggregated && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-ui-lg text-neutral4", children: [
44079
+ aggregated && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-ui-sm text-neutral4", children: [
42066
44080
  aggregated.value,
42067
44081
  aggregated.suffix && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-ui-sm text-neutral2", children: [
42068
44082
  " ",
@@ -42072,7 +44086,15 @@ function MetricsLineChart({
42072
44086
  ] }, s.dataKey);
42073
44087
  }) }),
42074
44088
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height }, children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.LineChart, { data, children: [
42075
- /* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { stroke: "rgba(255,255,255,0.08)", vertical: false }),
44089
+ /* @__PURE__ */ jsxRuntime.jsx(
44090
+ recharts.CartesianGrid,
44091
+ {
44092
+ stroke: "currentColor",
44093
+ strokeOpacity: 0.08,
44094
+ vertical: false,
44095
+ className: "text-black dark:text-white"
44096
+ }
44097
+ ),
42076
44098
  /* @__PURE__ */ jsxRuntime.jsx(
42077
44099
  recharts.XAxis,
42078
44100
  {
@@ -42183,24 +44205,25 @@ function useAvgScoreKpiMetrics() {
42183
44205
  if (scorerIds.length === 0) {
42184
44206
  return { value: null, previousValue: null, changePercent: null };
42185
44207
  }
42186
- const allResults = await Promise.all(
42187
- scorerIds.map((scorerId) => client.listScoresByScorerId({ scorerId, perPage: 100 }))
44208
+ const filters = {
44209
+ timestamp: { start: timestamp.start, end: timestamp.end }
44210
+ };
44211
+ const results = await Promise.all(
44212
+ scorerIds.map(async (scorerId) => {
44213
+ const [avg2, count] = await Promise.all([
44214
+ client.getScoreAggregate({ scorerId, aggregation: "avg", filters }),
44215
+ client.getScoreAggregate({ scorerId, aggregation: "count", filters })
44216
+ ]);
44217
+ return { avg: avg2.value ?? 0, count: count.value ?? 0 };
44218
+ })
42188
44219
  );
42189
- const startMs = timestamp.start.getTime();
42190
- const endMs = timestamp.end.getTime();
42191
- const allScoreValues = [];
42192
- for (const result of allResults) {
42193
- for (const s of result?.scores ?? []) {
42194
- const ts = new Date(s.createdAt).getTime();
42195
- if (ts >= startMs && ts <= endMs) {
42196
- allScoreValues.push(s.score);
42197
- }
42198
- }
42199
- }
42200
- if (allScoreValues.length === 0) {
44220
+ const withData = results.filter((r) => r.count > 0);
44221
+ if (withData.length === 0) {
42201
44222
  return { value: null, previousValue: null, changePercent: null };
42202
44223
  }
42203
- const avg = allScoreValues.reduce((sum, v) => sum + v, 0) / allScoreValues.length;
44224
+ const totalCount = withData.reduce((sum, r) => sum + r.count, 0);
44225
+ const weightedSum = withData.reduce((sum, r) => sum + r.avg * r.count, 0);
44226
+ const avg = weightedSum / totalCount;
42204
44227
  return { value: Math.round(avg * 100) / 100, previousValue: null, changePercent: null };
42205
44228
  }
42206
44229
  });
@@ -42479,78 +44502,84 @@ function useScoresMetrics() {
42479
44502
  return reactQuery.useQuery({
42480
44503
  queryKey: ["metrics", "scores-card", datePreset, customRange],
42481
44504
  queryFn: async () => {
44505
+ const filters = {
44506
+ timestamp: { start: timestamp.start, end: timestamp.end }
44507
+ };
42482
44508
  const scorersMap = await client.listScorers();
42483
44509
  const scorerIds = Object.keys(scorersMap ?? {});
42484
44510
  if (scorerIds.length === 0) {
42485
44511
  return { summaryData: [], overTimeData: [], scorerNames: [], avgScore: null };
42486
44512
  }
42487
- const allResults = await Promise.all(
42488
- // Limited to 100 most recent scores per scorer; pagination not yet implemented
42489
- scorerIds.map((scorerId) => client.listScoresByScorerId({ scorerId, perPage: 100 }))
44513
+ const summaryResults = await Promise.all(
44514
+ scorerIds.map(async (scorerId) => {
44515
+ const [avg, min, max, count] = await Promise.all([
44516
+ client.getScoreAggregate({ scorerId, aggregation: "avg", filters }),
44517
+ client.getScoreAggregate({ scorerId, aggregation: "min", filters }),
44518
+ client.getScoreAggregate({ scorerId, aggregation: "max", filters }),
44519
+ client.getScoreAggregate({ scorerId, aggregation: "count", filters })
44520
+ ]);
44521
+ return {
44522
+ scorer: scorerId,
44523
+ avg: avg.value ?? 0,
44524
+ min: min.value ?? 0,
44525
+ max: max.value ?? 0,
44526
+ count: count.value ?? 0
44527
+ };
44528
+ })
42490
44529
  );
42491
- const startMs = timestamp.start.getTime();
42492
- const endMs = timestamp.end.getTime();
42493
- const allScores = [];
42494
- for (let i = 0; i < scorerIds.length; i++) {
42495
- const scores = allResults[i]?.scores ?? [];
42496
- for (const s of scores) {
42497
- const ts = new Date(s.createdAt).getTime();
42498
- if (ts >= startMs && ts <= endMs) {
42499
- allScores.push({
42500
- scorerId: scorerIds[i],
42501
- score: s.score,
42502
- createdAt: s.createdAt
42503
- });
42504
- }
42505
- }
42506
- }
42507
- if (allScores.length === 0) {
42508
- return { summaryData: [], overTimeData: [], scorerNames: [], avgScore: null };
42509
- }
42510
- const byScorer = /* @__PURE__ */ new Map();
42511
- for (const s of allScores) {
42512
- if (!byScorer.has(s.scorerId)) byScorer.set(s.scorerId, []);
42513
- byScorer.get(s.scorerId).push(s.score);
42514
- }
42515
- const summaryData = Array.from(byScorer.entries()).map(([scorer, vals]) => ({
42516
- scorer,
42517
- avg: vals.reduce((a, b) => a + b, 0) / vals.length,
42518
- min: Math.min(...vals),
42519
- max: Math.max(...vals),
42520
- count: vals.length
42521
- }));
44530
+ const summaryData = summaryResults.filter((s) => s.count > 0);
42522
44531
  const scorerNames = summaryData.map((s) => s.scorer);
42523
- const avgScore = summaryData.reduce((s, d) => s + d.avg, 0) / summaryData.length;
42524
- const bucketMap = /* @__PURE__ */ new Map();
42525
- for (const s of allScores) {
42526
- const ts = new Date(s.createdAt);
42527
- const bucket = Math.floor(ts.getTime() / 36e5) * 36e5;
42528
- if (!bucketMap.has(bucket)) bucketMap.set(bucket, /* @__PURE__ */ new Map());
42529
- const scorerMap = bucketMap.get(bucket);
42530
- if (!scorerMap.has(s.scorerId)) scorerMap.set(s.scorerId, []);
42531
- scorerMap.get(s.scorerId).push(s.score);
44532
+ if (summaryData.length === 0) {
44533
+ return { summaryData: [], overTimeData: [], scorerNames: [], avgScore: null };
42532
44534
  }
42533
- const overTimeData = Array.from(bucketMap.entries()).sort(([a], [b]) => a - b).map(([bucket, scorerMap]) => {
42534
- const point = {
42535
- time: new Date(bucket).toLocaleTimeString("en-US", {
42536
- hour: "2-digit",
42537
- minute: "2-digit",
42538
- hour12: false
44535
+ const totalWeighted = summaryData.reduce((s, d) => s + d.avg * d.count, 0);
44536
+ const totalCount = summaryData.reduce((s, d) => s + d.count, 0);
44537
+ const avgScore = totalCount ? Math.round(totalWeighted / totalCount * 100) / 100 : 0;
44538
+ const interval = "1h";
44539
+ const timeSeriesResults = await Promise.all(
44540
+ scorerNames.map(
44541
+ (scorerId) => client.getScoreTimeSeries({
44542
+ scorerId,
44543
+ interval,
44544
+ aggregation: "avg",
44545
+ filters
42539
44546
  })
42540
- };
42541
- for (const name of scorerNames) {
42542
- const vals = scorerMap.get(name);
42543
- if (vals && vals.length > 0) {
42544
- point[name] = +(vals.reduce((a, b) => a + b, 0) / vals.length).toFixed(2);
44547
+ )
44548
+ );
44549
+ const bucketMap = /* @__PURE__ */ new Map();
44550
+ const rangeSpansDays = timestamp.end.toDateString() !== timestamp.start.toDateString();
44551
+ for (let i = 0; i < scorerNames.length; i++) {
44552
+ const scorerId = scorerNames[i];
44553
+ const series = timeSeriesResults[i]?.series ?? [];
44554
+ for (const s of series) {
44555
+ for (const point of s.points) {
44556
+ const ts = new Date(point.timestamp);
44557
+ const key = ts.toISOString();
44558
+ if (!bucketMap.has(key)) {
44559
+ bucketMap.set(key, {
44560
+ time: rangeSpansDays ? ts.toLocaleString("en-US", {
44561
+ month: "short",
44562
+ day: "numeric",
44563
+ hour: "2-digit",
44564
+ minute: "2-digit",
44565
+ hour12: false
44566
+ }) : ts.toLocaleTimeString("en-US", {
44567
+ hour: "2-digit",
44568
+ minute: "2-digit",
44569
+ hour12: false
44570
+ })
44571
+ });
44572
+ }
44573
+ bucketMap.get(key)[scorerId] = +point.value.toFixed(2);
42545
44574
  }
42546
44575
  }
42547
- return point;
42548
- });
44576
+ }
44577
+ const overTimeData = Array.from(bucketMap.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([, point]) => point);
42549
44578
  return {
42550
44579
  summaryData,
42551
44580
  overTimeData,
42552
44581
  scorerNames,
42553
- avgScore: Math.round(avgScore * 100) / 100
44582
+ avgScore
42554
44583
  };
42555
44584
  }
42556
44585
  });
@@ -42595,7 +44624,7 @@ function ScoresCard() {
42595
44624
  /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "over-time", children: "Over Time" }),
42596
44625
  /* @__PURE__ */ jsxRuntime.jsx(Tab, { value: "summary", children: "Summary" })
42597
44626
  ] }),
42598
- /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "over-time", children: data.overTimeData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(MetricsLineChart, { data: data.overTimeData, series, yDomain: [0, 1] }) : /* @__PURE__ */ jsxRuntime.jsx(MetricsCard.NoData, { message: "No time series data yet" }) }),
44627
+ /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "over-time", className: "pb-0", children: data.overTimeData.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(MetricsLineChart, { data: data.overTimeData, series, yDomain: [0, 1] }) : /* @__PURE__ */ jsxRuntime.jsx(MetricsCard.NoData, { message: "No time series data yet" }) }),
42599
44628
  /* @__PURE__ */ jsxRuntime.jsx(TabContent, { value: "summary", children: /* @__PURE__ */ jsxRuntime.jsx(
42600
44629
  MetricsDataTable,
42601
44630
  {
@@ -42733,7 +44762,7 @@ function HorizontalBars({
42733
44762
  "div",
42734
44763
  {
42735
44764
  className: cn(
42736
- "absolute inset-y-0",
44765
+ "absolute inset-y-0 opacity-40 dark:opacity-100",
42737
44766
  si === 0 && "rounded-l",
42738
44767
  isLastWithValue && "rounded-r"
42739
44768
  ),
@@ -42749,7 +44778,7 @@ function HorizontalBars({
42749
44778
  return /* @__PURE__ */ jsxRuntime.jsx(
42750
44779
  "div",
42751
44780
  {
42752
- className: "absolute inset-y-0 left-0 rounded",
44781
+ className: "absolute inset-y-0 left-0 rounded opacity-40 dark:opacity-100",
42753
44782
  style: { width: `${pct}%`, backgroundColor: seg.color }
42754
44783
  },
42755
44784
  seg.label
@@ -43845,6 +45874,9 @@ function SpanScoreList({
43845
45874
  const score = scoresData?.scores?.find((s) => s?.id === scoreId);
43846
45875
  setSelectedScore(score);
43847
45876
  setDialogIsOpen(true);
45877
+ if (traceId) {
45878
+ navigate(`${computeTraceLink(traceId, spanId)}&tab=scores&scoreId=${encodeURIComponent(scoreId)}`);
45879
+ }
43848
45880
  };
43849
45881
  if (isLoadingScoresData) {
43850
45882
  return /* @__PURE__ */ jsxRuntime.jsx(EntryListSkeleton, { columns: traceScoresListColumns });
@@ -46697,9 +48729,11 @@ function DatasetItemContent({ item }) {
46697
48729
  const inputDisplay = item?.input ? JSON.stringify(item.input, null, 2) : "null";
46698
48730
  const groundTruthDisplay = item?.groundTruth ? JSON.stringify(item.groundTruth, null, 2) : "null";
46699
48731
  const metadataDisplay = item?.metadata ? JSON.stringify(item.metadata, null, 2) : "null";
48732
+ const trajectoryDisplay = item?.expectedTrajectory ? JSON.stringify(item.expectedTrajectory, null, 2) : null;
46700
48733
  return /* @__PURE__ */ jsxRuntime.jsxs(Sections, { children: [
46701
48734
  /* @__PURE__ */ jsxRuntime.jsx(SideDialog.CodeSection, { title: "Input", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileInputIcon, {}), codeStr: inputDisplay }),
46702
48735
  /* @__PURE__ */ jsxRuntime.jsx(SideDialog.CodeSection, { title: "Ground Truth", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileOutputIcon, {}), codeStr: groundTruthDisplay }),
48736
+ trajectoryDisplay && /* @__PURE__ */ jsxRuntime.jsx(SideDialog.CodeSection, { title: "Expected Trajectory", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RouteIcon, {}), codeStr: trajectoryDisplay }),
46703
48737
  /* @__PURE__ */ jsxRuntime.jsx(SideDialog.CodeSection, { title: "Metadata", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TagIcon, {}), codeStr: metadataDisplay })
46704
48738
  ] });
46705
48739
  }
@@ -46722,6 +48756,8 @@ function EditModeContent({
46722
48756
  setGroundTruthValue,
46723
48757
  metadataValue,
46724
48758
  setMetadataValue,
48759
+ trajectoryValue,
48760
+ setTrajectoryValue,
46725
48761
  validationErrors,
46726
48762
  onSave,
46727
48763
  onCancel,
@@ -46751,6 +48787,18 @@ function EditModeContent({
46751
48787
  ),
46752
48788
  validationErrors?.field === "groundTruth" && /* @__PURE__ */ jsxRuntime.jsx(ValidationErrors$1, { field: "groundTruth", errors: validationErrors.errors })
46753
48789
  ] }),
48790
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
48791
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Expected Trajectory (JSON, optional)" }),
48792
+ /* @__PURE__ */ jsxRuntime.jsx(
48793
+ CodeEditor,
48794
+ {
48795
+ value: trajectoryValue,
48796
+ onChange: setTrajectoryValue,
48797
+ showCopyButton: false,
48798
+ className: "min-h-[80px]"
48799
+ }
48800
+ )
48801
+ ] }),
46754
48802
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
46755
48803
  /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Metadata (JSON, optional)" }),
46756
48804
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -46968,6 +49016,7 @@ function DatasetItemPanel({ datasetId, item, items, onItemChange, onClose }) {
46968
49016
  const [inputValue, setInputValue] = React.useState("");
46969
49017
  const [groundTruthValue, setGroundTruthValue] = React.useState("");
46970
49018
  const [metadataValue, setMetadataValue] = React.useState("");
49019
+ const [trajectoryValue, setTrajectoryValue] = React.useState("");
46971
49020
  const [validationErrors, setValidationErrors] = React.useState(null);
46972
49021
  const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
46973
49022
  React.useEffect(() => {
@@ -46975,6 +49024,7 @@ function DatasetItemPanel({ datasetId, item, items, onItemChange, onClose }) {
46975
49024
  setInputValue(JSON.stringify(item.input, null, 2));
46976
49025
  setGroundTruthValue(item.groundTruth ? JSON.stringify(item.groundTruth, null, 2) : "");
46977
49026
  setMetadataValue(item.metadata ? JSON.stringify(item.metadata, null, 2) : "");
49027
+ setTrajectoryValue(item.expectedTrajectory ? JSON.stringify(item.expectedTrajectory, null, 2) : "");
46978
49028
  setIsEditing(false);
46979
49029
  setShowDeleteConfirm(false);
46980
49030
  setValidationErrors(null);
@@ -47020,13 +49070,23 @@ function DatasetItemPanel({ datasetId, item, items, onItemChange, onClose }) {
47020
49070
  return;
47021
49071
  }
47022
49072
  }
49073
+ let parsedTrajectory = null;
49074
+ if (trajectoryValue.trim()) {
49075
+ try {
49076
+ parsedTrajectory = JSON.parse(trajectoryValue);
49077
+ } catch {
49078
+ toast.error("Expected Trajectory must be valid JSON");
49079
+ return;
49080
+ }
49081
+ }
47023
49082
  try {
47024
49083
  await updateItem.mutateAsync({
47025
49084
  datasetId,
47026
49085
  itemId: item.id,
47027
49086
  input: parsedInput,
47028
49087
  groundTruth: parsedGroundTruth,
47029
- metadata: parsedMetadata
49088
+ metadata: parsedMetadata,
49089
+ expectedTrajectory: parsedTrajectory
47030
49090
  });
47031
49091
  toast.success("Item updated successfully");
47032
49092
  setIsEditing(false);
@@ -47044,6 +49104,7 @@ function DatasetItemPanel({ datasetId, item, items, onItemChange, onClose }) {
47044
49104
  setInputValue(JSON.stringify(item.input, null, 2));
47045
49105
  setGroundTruthValue(item.groundTruth ? JSON.stringify(item.groundTruth, null, 2) : "");
47046
49106
  setMetadataValue(item.metadata ? JSON.stringify(item.metadata, null, 2) : "");
49107
+ setTrajectoryValue(item.expectedTrajectory ? JSON.stringify(item.expectedTrajectory, null, 2) : "");
47047
49108
  setIsEditing(false);
47048
49109
  setValidationErrors(null);
47049
49110
  };
@@ -47099,6 +49160,8 @@ function DatasetItemPanel({ datasetId, item, items, onItemChange, onClose }) {
47099
49160
  setGroundTruthValue: handleGroundTruthValueChange,
47100
49161
  metadataValue,
47101
49162
  setMetadataValue,
49163
+ trajectoryValue,
49164
+ setTrajectoryValue,
47102
49165
  validationErrors,
47103
49166
  onSave: handleSave,
47104
49167
  onCancel: handleCancel,
@@ -47226,6 +49289,7 @@ function DatasetItemsList({
47226
49289
  /* @__PURE__ */ jsxRuntime.jsx(ItemList.IdCell, { id: listItem.id }),
47227
49290
  /* @__PURE__ */ jsxRuntime.jsx(ItemList.TextCell, { className: "font-mono", children: listItem.input }),
47228
49291
  columns.some((col) => col.name === "groundTruth") && /* @__PURE__ */ jsxRuntime.jsx(ItemList.TextCell, { className: "font-mono", children: listItem.groundTruth }),
49292
+ columns.some((col) => col.name === "trajectory") && /* @__PURE__ */ jsxRuntime.jsx(ItemList.TextCell, { children: item.expectedTrajectory ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs", children: Array.isArray(item.expectedTrajectory?.steps) ? `${item.expectedTrajectory.steps.length} steps` : "Yes" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-neutral4", children: "—" }) }),
47229
49293
  /* @__PURE__ */ jsxRuntime.jsx(ItemList.DateCell, { date: listItem.date, withTime: true })
47230
49294
  ]
47231
49295
  }
@@ -47689,6 +49753,7 @@ function DatasetItems({
47689
49753
  { name: "id", label: "ID", size: "5rem" },
47690
49754
  { name: "input", label: "Input", size: "1fr" },
47691
49755
  ...!featuredItem ? [{ name: "groundTruth", label: "Ground Truth", size: "1fr" }] : [],
49756
+ ...!featuredItem ? [{ name: "trajectory", label: "Trajectory", size: "6rem" }] : [],
47692
49757
  { name: "date", label: "Created", size: "10rem" }
47693
49758
  ];
47694
49759
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -49155,6 +51220,7 @@ function ValidationErrors({ field, errors }) {
49155
51220
  function AddItemDialog({ datasetId, open, onOpenChange, onSuccess }) {
49156
51221
  const [input, setInput] = React.useState("{}");
49157
51222
  const [groundTruth, setGroundTruth] = React.useState("");
51223
+ const [expectedTrajectory, setExpectedTrajectory] = React.useState("");
49158
51224
  const [validationErrors, setValidationErrors] = React.useState(null);
49159
51225
  const { addItem } = useDatasetMutations();
49160
51226
  const handleSubmit = async (e) => {
@@ -49175,16 +51241,27 @@ function AddItemDialog({ datasetId, open, onOpenChange, onSuccess }) {
49175
51241
  return;
49176
51242
  }
49177
51243
  }
51244
+ let parsedTrajectory;
51245
+ if (expectedTrajectory.trim()) {
51246
+ try {
51247
+ parsedTrajectory = JSON.parse(expectedTrajectory);
51248
+ } catch {
51249
+ toast.error("Expected Trajectory must be valid JSON");
51250
+ return;
51251
+ }
51252
+ }
49178
51253
  try {
49179
51254
  await addItem.mutateAsync({
49180
51255
  datasetId,
49181
51256
  input: parsedInput,
49182
- groundTruth: parsedGroundTruth
51257
+ groundTruth: parsedGroundTruth,
51258
+ expectedTrajectory: parsedTrajectory
49183
51259
  });
49184
51260
  toast.success("Item added successfully");
49185
51261
  setValidationErrors(null);
49186
51262
  setInput("{}");
49187
51263
  setGroundTruth("");
51264
+ setExpectedTrajectory("");
49188
51265
  onOpenChange(false);
49189
51266
  onSuccess?.();
49190
51267
  } catch (error) {
@@ -49211,6 +51288,7 @@ function AddItemDialog({ datasetId, open, onOpenChange, onSuccess }) {
49211
51288
  const handleCancel = () => {
49212
51289
  setInput("{}");
49213
51290
  setGroundTruth("");
51291
+ setExpectedTrajectory("");
49214
51292
  setValidationErrors(null);
49215
51293
  onOpenChange(false);
49216
51294
  };
@@ -49235,6 +51313,18 @@ function AddItemDialog({ datasetId, open, onOpenChange, onSuccess }) {
49235
51313
  ),
49236
51314
  validationErrors?.field === "groundTruth" && /* @__PURE__ */ jsxRuntime.jsx(ValidationErrors, { field: "groundTruth", errors: validationErrors.errors })
49237
51315
  ] }),
51316
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
51317
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "item-trajectory", children: "Expected Trajectory (JSON, optional)" }),
51318
+ /* @__PURE__ */ jsxRuntime.jsx(
51319
+ CodeEditor,
51320
+ {
51321
+ value: expectedTrajectory,
51322
+ onChange: setExpectedTrajectory,
51323
+ showCopyButton: false,
51324
+ className: "min-h-[80px]"
51325
+ }
51326
+ )
51327
+ ] }),
49238
51328
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 pt-4", children: [
49239
51329
  /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", onClick: handleCancel, children: "Cancel" }),
49240
51330
  /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "submit", variant: "primary", disabled: addItem.isPending, children: addItem.isPending ? "Adding..." : "Add Item" })
@@ -53723,72 +55813,6 @@ function EvaluationDatasetsList({
53723
55813
  ] });
53724
55814
  }
53725
55815
 
53726
- const statusBadgeVariants = cva(
53727
- // Base styles
53728
- "inline-flex items-center gap-1.5 rounded-full text-ui-xs font-medium transition-colors duration-normal",
53729
- {
53730
- variants: {
53731
- variant: {
53732
- success: "bg-accent1Dark text-accent1",
53733
- warning: "bg-accent6Dark text-accent6",
53734
- error: "bg-accent2Dark text-accent2",
53735
- info: "bg-accent5Dark text-accent5",
53736
- neutral: "bg-surface4 text-neutral4"
53737
- },
53738
- size: {
53739
- sm: "px-1.5 py-0.5 text-ui-xs",
53740
- md: "px-2 py-1 text-ui-xs",
53741
- lg: "px-2.5 py-1 text-ui-sm"
53742
- },
53743
- withDot: {
53744
- true: "",
53745
- false: ""
53746
- },
53747
- pulse: {
53748
- true: "",
53749
- false: ""
53750
- }
53751
- },
53752
- defaultVariants: {
53753
- variant: "neutral",
53754
- size: "md",
53755
- withDot: false,
53756
- pulse: false
53757
- }
53758
- }
53759
- );
53760
- const dotVariants = cva("rounded-full", {
53761
- variants: {
53762
- variant: {
53763
- success: "bg-accent1",
53764
- warning: "bg-accent6",
53765
- error: "bg-accent2",
53766
- info: "bg-accent5",
53767
- neutral: "bg-neutral3"
53768
- },
53769
- size: {
53770
- sm: "w-1 h-1",
53771
- md: "w-1.5 h-1.5",
53772
- lg: "w-2 h-2"
53773
- },
53774
- pulse: {
53775
- true: "animate-pulse",
53776
- false: ""
53777
- }
53778
- },
53779
- defaultVariants: {
53780
- variant: "neutral",
53781
- size: "md",
53782
- pulse: false
53783
- }
53784
- });
53785
- function StatusBadge({ className, variant, size, withDot, pulse, children, ...props }) {
53786
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn(statusBadgeVariants({ variant, size, withDot, pulse }), className), ...props, children: [
53787
- withDot && /* @__PURE__ */ jsxRuntime.jsx("span", { className: dotVariants({ variant, size, pulse }) }),
53788
- children
53789
- ] });
53790
- }
53791
-
53792
55816
  const COLUMNS$1 = "auto 1fr auto auto auto auto auto auto auto";
53793
55817
  function formatDate(dateStr) {
53794
55818
  if (!dateStr) return "—";
@@ -54175,8 +56199,12 @@ function EvaluationScorersList({ scorers, isLoading, error }) {
54175
56199
  const description = scorer.scorer.config?.description || "";
54176
56200
  const agentCount = scorer.agentIds?.length ?? 0;
54177
56201
  const workflowCount = scorer.workflowIds?.length ?? 0;
56202
+ const isTrajectory = scorer.scorer.config?.type === "trajectory";
54178
56203
  return /* @__PURE__ */ jsxRuntime.jsxs(EntityList.RowLink, { to: paths.scorerLink(scorer.id), children: [
54179
- /* @__PURE__ */ jsxRuntime.jsx(EntityList.NameCell, { children: name }),
56204
+ /* @__PURE__ */ jsxRuntime.jsx(EntityList.NameCell, { children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1.5", children: [
56205
+ name,
56206
+ isTrajectory && /* @__PURE__ */ jsxRuntime.jsx(Chip, { size: "small", color: "purple", children: "trajectory" })
56207
+ ] }) }),
54180
56208
  /* @__PURE__ */ jsxRuntime.jsx(EntityList.DescriptionCell, { children: description }),
54181
56209
  /* @__PURE__ */ jsxRuntime.jsx(EntityList.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: scorer.source === "code" ? "info" : "default", children: scorer.source }) }),
54182
56210
  /* @__PURE__ */ jsxRuntime.jsx(EntityList.TextCell, { className: "text-center", children: agentCount || "" }),
@@ -57757,6 +59785,15 @@ exports.Avatar = Avatar;
57757
59785
  exports.Badge = Badge;
57758
59786
  exports.BranchIcon = BranchIcon;
57759
59787
  exports.Breadcrumb = Breadcrumb$1;
59788
+ exports.BrowserSessionProvider = BrowserSessionProvider;
59789
+ exports.BrowserSidebarTab = BrowserSidebarTab;
59790
+ exports.BrowserThumbnail = BrowserThumbnail;
59791
+ exports.BrowserToolCallHistory = BrowserToolCallHistory;
59792
+ exports.BrowserToolCallItem = BrowserToolCallItem;
59793
+ exports.BrowserToolCallsProvider = BrowserToolCallsProvider;
59794
+ exports.BrowserViewFrame = BrowserViewFrame;
59795
+ exports.BrowserViewHeader = BrowserViewHeader;
59796
+ exports.BrowserViewPanel = BrowserViewPanel;
57760
59797
  exports.Button = Button;
57761
59798
  exports.ButtonWithTooltip = ButtonWithTooltip;
57762
59799
  exports.ButtonsGroup = ButtonsGroup;
@@ -58209,6 +60246,8 @@ exports.highlight = highlight;
58209
60246
  exports.inputVariants = inputVariants;
58210
60247
  exports.isActive = isActive;
58211
60248
  exports.isAuthenticated = isAuthenticated;
60249
+ exports.isBrowserTool = isBrowserTool;
60250
+ exports.isBrowserToolError = isBrowserToolError;
58212
60251
  exports.isRule = isRule;
58213
60252
  exports.isRuleGroup = isRuleGroup;
58214
60253
  exports.isValidLogsDatePreset = isValidLogsDatePreset;
@@ -58267,6 +60306,10 @@ exports.useAllIntegrationTools = useAllIntegrationTools;
58267
60306
  exports.useAllModels = useAllModels;
58268
60307
  exports.useAuthCapabilities = useAuthCapabilities;
58269
60308
  exports.useAvgScoreKpiMetrics = useAvgScoreKpiMetrics;
60309
+ exports.useBrowserSession = useBrowserSession;
60310
+ exports.useBrowserStream = useBrowserStream;
60311
+ exports.useBrowserToolCalls = useBrowserToolCalls;
60312
+ exports.useBrowserToolCallsSafe = useBrowserToolCallsSafe;
58270
60313
  exports.useCSVParser = useCSVParser;
58271
60314
  exports.useCanCreateAgent = useCanCreateAgent;
58272
60315
  exports.useCloneThread = useCloneThread;