@invergent/agent-chat-react 1.5.10 → 1.6.0

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.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { cn } from './chunk-QSC4UIVT.js';
2
2
  import { createContext, memo, useRef, useState, useEffect, useCallback, useMemo, lazy, useContext, Suspense, Children } from 'react';
3
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
3
4
  import { cva } from 'class-variance-authority';
4
5
  import { Collapsible as Collapsible$1, Slot, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Tooltip as Tooltip$1, Tabs as Tabs$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, HoverCard as HoverCard$1, Progress as Progress$1 } from 'radix-ui';
5
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
7
7
  import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
8
8
  import { cjk } from '@streamdown/cjk';
9
9
  import { code } from '@streamdown/code';
@@ -79,6 +79,114 @@ function Button({
79
79
  }
80
80
  );
81
81
  }
82
+ function BrowserControlBar({
83
+ sessionId,
84
+ hasControl,
85
+ adapter
86
+ }) {
87
+ const [pending, setPending] = useState(false);
88
+ const [error, setError] = useState(null);
89
+ async function toggleControl() {
90
+ if (!sessionId || pending) return;
91
+ setPending(true);
92
+ setError(null);
93
+ try {
94
+ if (hasControl) {
95
+ await adapter.releaseBrowserControl(sessionId);
96
+ } else {
97
+ await adapter.acquireBrowserControl(sessionId);
98
+ }
99
+ } catch (nextError) {
100
+ setError(nextError.message);
101
+ } finally {
102
+ setPending(false);
103
+ }
104
+ }
105
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-11 items-center gap-2 border-t border-line bg-card px-3 py-2", children: [
106
+ /* @__PURE__ */ jsxs(
107
+ Button,
108
+ {
109
+ type: "button",
110
+ size: "xs",
111
+ variant: hasControl ? "secondary" : "default",
112
+ disabled: !sessionId || pending,
113
+ onClick: () => void toggleControl(),
114
+ children: [
115
+ hasControl ? /* @__PURE__ */ jsx(RotateCcwIcon, { className: "size-3", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(MousePointer2Icon, { className: "size-3", "aria-hidden": "true" }),
116
+ hasControl ? "Return control" : "Take control"
117
+ ]
118
+ }
119
+ ),
120
+ /* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [
121
+ /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-red-500", "aria-hidden": "true" }),
122
+ "Live"
123
+ ] }),
124
+ error && /* @__PURE__ */ jsx("span", { className: "max-w-40 truncate text-[11px] text-destructive", children: error })
125
+ ] });
126
+ }
127
+ function BrowserLiveView({ src }) {
128
+ return /* @__PURE__ */ jsx(
129
+ "iframe",
130
+ {
131
+ "data-testid": "browser-iframe",
132
+ title: "Browser live view",
133
+ src,
134
+ className: "h-full w-full border-0 bg-black",
135
+ allow: "clipboard-read; clipboard-write"
136
+ }
137
+ );
138
+ }
139
+ function BrowserStatusDot({
140
+ status
141
+ }) {
142
+ return /* @__PURE__ */ jsx(
143
+ "span",
144
+ {
145
+ "aria-hidden": "true",
146
+ className: cn(
147
+ "inline-block size-2 shrink-0 rounded-full",
148
+ status === "live" && "bg-emerald-500",
149
+ status === "user-control" && "bg-amber-500",
150
+ status === "provisioning" && "animate-pulse bg-sky-500",
151
+ status === "closed" && "bg-muted-foreground"
152
+ )
153
+ }
154
+ );
155
+ }
156
+ function BrowserPane({ sessionId, state, adapter }) {
157
+ const liveViewUrl = useMemo(
158
+ () => adapter.browserLiveViewUrl(sessionId),
159
+ [adapter, sessionId]
160
+ );
161
+ const hasLiveView = state.status !== "provisioning" && state.status !== "closed";
162
+ return /* @__PURE__ */ jsxs(
163
+ "div",
164
+ {
165
+ "data-testid": "browser-pane",
166
+ className: "flex h-full min-h-0 flex-col bg-background",
167
+ children: [
168
+ /* @__PURE__ */ jsxs("header", { className: "flex min-h-10 items-center gap-2 border-b border-line bg-card px-3 text-xs text-foreground", children: [
169
+ /* @__PURE__ */ jsx(ZapIcon, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
170
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Browser" }),
171
+ /* @__PURE__ */ jsx(BrowserStatusDot, { status: state.status }),
172
+ state.controlOwner && /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-amber-500", children: [
173
+ state.controlOwner,
174
+ " has control"
175
+ ] })
176
+ ] }),
177
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-black", children: state.status === "provisioning" ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Starting browser..." }) : state.status === "closed" ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center bg-background text-sm text-muted-foreground", children: "Browser closed." }) : /* @__PURE__ */ jsx(BrowserLiveView, { src: liveViewUrl }) }),
178
+ hasLiveView && /* @__PURE__ */ jsx(
179
+ BrowserControlBar,
180
+ {
181
+ sessionId,
182
+ hasControl: state.status === "user-control",
183
+ adapter
184
+ }
185
+ )
186
+ ]
187
+ }
188
+ );
189
+ }
82
190
  var Conversation = ({ className, ...props }) => /* @__PURE__ */ jsx(
83
191
  StickToBottom,
84
192
  {
@@ -565,6 +673,80 @@ function TimelineSeparator({
565
673
  }
566
674
  );
567
675
  }
676
+ function BrowserActivityGroup({ calls }) {
677
+ const [open, setOpen] = useState(false);
678
+ const latest = calls[calls.length - 1];
679
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-none border border-line bg-card text-xs", children: [
680
+ /* @__PURE__ */ jsxs(
681
+ "button",
682
+ {
683
+ type: "button",
684
+ onClick: () => setOpen((current) => !current),
685
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-muted/40",
686
+ children: [
687
+ open ? /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3 shrink-0", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3 shrink-0", "aria-hidden": "true" }),
688
+ /* @__PURE__ */ jsx(ZapIcon, { className: "size-3 shrink-0 text-amber-500", "aria-hidden": "true" }),
689
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "browser" }),
690
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 truncate text-muted-foreground", children: [
691
+ "(",
692
+ calls.length,
693
+ " ",
694
+ calls.length === 1 ? "action" : "actions",
695
+ " - latest:",
696
+ " ",
697
+ latest ? summarise(latest) : "none",
698
+ ")"
699
+ ] })
700
+ ]
701
+ }
702
+ ),
703
+ open && /* @__PURE__ */ jsx("ul", { className: "border-t border-line py-1", children: calls.map((call) => {
704
+ const error = stringValue(parseJson(call.result).error);
705
+ return /* @__PURE__ */ jsxs(
706
+ "li",
707
+ {
708
+ className: "flex items-center gap-2 px-6 py-1 font-mono text-[11px]",
709
+ children: [
710
+ error && /* @__PURE__ */ jsx(
711
+ AlertCircleIcon,
712
+ {
713
+ "data-testid": `activity-error-${call.id}`,
714
+ className: "size-3 shrink-0 text-destructive",
715
+ "aria-hidden": "true"
716
+ }
717
+ ),
718
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: summarise(call) }),
719
+ error && /* @__PURE__ */ jsx("span", { className: "ml-auto max-w-40 truncate text-destructive", children: error })
720
+ ]
721
+ },
722
+ call.id
723
+ );
724
+ }) })
725
+ ] });
726
+ }
727
+ function summarise(call) {
728
+ const verb = call.toolName.replace(/^browser_/, "");
729
+ const args = parseJson(call.args);
730
+ const ref = stringValue(args.ref);
731
+ const url = stringValue(args.url);
732
+ const text = stringValue(args.text);
733
+ if (url) return `${verb} ${url}`;
734
+ if (ref) return `${verb} ${ref}`;
735
+ if (text) return `${verb} "${text.slice(0, 24)}"`;
736
+ return verb;
737
+ }
738
+ function parseJson(raw) {
739
+ if (!raw) return {};
740
+ try {
741
+ const parsed = JSON.parse(raw);
742
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
743
+ } catch {
744
+ return {};
745
+ }
746
+ }
747
+ function stringValue(value) {
748
+ return typeof value === "string" ? value : "";
749
+ }
568
750
 
569
751
  // src/components/chat/tools/shared.ts
570
752
  function statusColorClass(status) {
@@ -5886,6 +6068,32 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
5886
6068
  }
5887
6069
  return entries;
5888
6070
  }
6071
+ function groupBrowserActivityEntries(entries) {
6072
+ const grouped = [];
6073
+ let buffer = [];
6074
+ const flush = () => {
6075
+ if (buffer.length === 0) return;
6076
+ grouped.push({
6077
+ kind: "browser_activity",
6078
+ key: `browser-activity-${buffer[0]?.id}-${buffer[buffer.length - 1]?.id}`,
6079
+ calls: buffer
6080
+ });
6081
+ buffer = [];
6082
+ };
6083
+ for (const entry of entries) {
6084
+ if (entry.kind === "tool" && isBrowserToolCall(entry.tc)) {
6085
+ buffer.push(entry.tc);
6086
+ continue;
6087
+ }
6088
+ flush();
6089
+ grouped.push(entry);
6090
+ }
6091
+ flush();
6092
+ return grouped;
6093
+ }
6094
+ function isBrowserToolCall(call) {
6095
+ return call.toolName.startsWith("browser_");
6096
+ }
5889
6097
  function groupMessages(messages) {
5890
6098
  const groups = [];
5891
6099
  let pendingSystem = [];
@@ -6042,6 +6250,15 @@ function TimelineEntryItem({
6042
6250
  ] }) })
6043
6251
  ] });
6044
6252
  }
6253
+ if (entry.kind === "browser_activity") {
6254
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
6255
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
6256
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
6257
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-amber-500" })
6258
+ ] }),
6259
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(BrowserActivityGroup, { calls: entry.calls }) })
6260
+ ] });
6261
+ }
6045
6262
  if (entry.kind === "text") {
6046
6263
  return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
6047
6264
  /* @__PURE__ */ jsxs(TimelineHeader, { children: [
@@ -6103,11 +6320,12 @@ function AssistantGroup({
6103
6320
  onFileSelect,
6104
6321
  onRetry
6105
6322
  }) {
6106
- const entries = [];
6323
+ let entries = [];
6107
6324
  for (let i = 0; i < messages.length; i++) {
6108
6325
  const isLast = i === messages.length - 1 && lastGlobalIndex === totalMessages - 1;
6109
6326
  entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
6110
6327
  }
6328
+ entries = groupBrowserActivityEntries(entries);
6111
6329
  const isTailGroup = lastGlobalIndex === totalMessages - 1;
6112
6330
  const lastEntry = entries[entries.length - 1];
6113
6331
  const hasRunningTool = entries.some(
@@ -7175,6 +7393,7 @@ function WorkspacePanel({
7175
7393
  return /* @__PURE__ */ jsx(
7176
7394
  "aside",
7177
7395
  {
7396
+ "data-testid": "workspace-panel",
7178
7397
  role: "button",
7179
7398
  tabIndex: 0,
7180
7399
  className: "relative z-10 flex min-h-0 w-10 shrink-0 cursor-pointer items-center justify-center border-l border-muted-foreground/20 bg-card text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30",
@@ -7193,6 +7412,7 @@ function WorkspacePanel({
7193
7412
  return /* @__PURE__ */ jsxs(
7194
7413
  "aside",
7195
7414
  {
7415
+ "data-testid": "workspace-panel",
7196
7416
  className: "relative z-10 flex min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
7197
7417
  style: { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
7198
7418
  children: [
@@ -7434,6 +7654,10 @@ var AGENT_CHAT_LISTENED_EVENTS = [
7434
7654
  "expert.override",
7435
7655
  "artifact.created",
7436
7656
  "artifact.updated",
7657
+ "browser.provisioned",
7658
+ "browser.destroyed",
7659
+ "browser.control_granted",
7660
+ "browser.control_returned",
7437
7661
  "clarify.response"
7438
7662
  ];
7439
7663
 
@@ -7458,7 +7682,8 @@ function createInitialAgentChatState(options = {}) {
7458
7682
  sessionDone: false,
7459
7683
  hadDeltas: false,
7460
7684
  terminal: false,
7461
- workspaceRefreshKey: 0
7685
+ workspaceRefreshKey: 0,
7686
+ browser: null
7462
7687
  };
7463
7688
  }
7464
7689
  function applyAgentChatEvent(state, event) {
@@ -7482,7 +7707,7 @@ function applyAgentChatEvent(state, event) {
7482
7707
  {
7483
7708
  id: `evt-${event.eventId}`,
7484
7709
  role: "system",
7485
- content: stringValue(event.data.skill),
7710
+ content: stringValue2(event.data.skill),
7486
7711
  createdAt: /* @__PURE__ */ new Date(),
7487
7712
  status: "complete",
7488
7713
  systemKind: "skill_invoked",
@@ -7499,7 +7724,7 @@ function applyAgentChatEvent(state, event) {
7499
7724
  {
7500
7725
  id: `evt-${event.eventId}`,
7501
7726
  role: "system",
7502
- content: stringValue(event.data.name),
7727
+ content: stringValue2(event.data.name),
7503
7728
  createdAt: /* @__PURE__ */ new Date(),
7504
7729
  status: "complete",
7505
7730
  systemKind: "artifact",
@@ -7512,6 +7737,23 @@ function applyAgentChatEvent(state, event) {
7512
7737
  }
7513
7738
  }
7514
7739
  ]);
7740
+ case "browser.provisioned":
7741
+ return applyBrowserEvent(nextState, event, {
7742
+ status: "live",
7743
+ controlOwner: null
7744
+ });
7745
+ case "browser.control_granted":
7746
+ return applyBrowserEvent(nextState, event, {
7747
+ status: "user-control",
7748
+ controlOwner: stringValue2(event.data.owner_user_id) || null
7749
+ });
7750
+ case "browser.control_returned":
7751
+ return applyBrowserEvent(nextState, event, {
7752
+ status: "live",
7753
+ controlOwner: null
7754
+ });
7755
+ case "browser.destroyed":
7756
+ return applyBrowserEvent(nextState, event, null);
7515
7757
  case "llm.delta":
7516
7758
  return applyLlmDelta(nextState, event);
7517
7759
  case "llm.response":
@@ -7521,7 +7763,7 @@ function applyAgentChatEvent(state, event) {
7521
7763
  case "tool.call":
7522
7764
  return applyToolCall(nextState, event);
7523
7765
  case "tool.result": {
7524
- const toolCallId = stringValue(event.data.tool_call_id);
7766
+ const toolCallId = stringValue2(event.data.tool_call_id);
7525
7767
  const toolName = findToolNameById(nextState.messages, toolCallId);
7526
7768
  const messages = applyToolResult(nextState.messages, event.data);
7527
7769
  const mutatesWorkspace = toolName !== null && WORKSPACE_MUTATING_TOOLS.has(toolName);
@@ -7591,8 +7833,8 @@ function applyRetryIndicator(state, event) {
7591
7833
  case "session.done":
7592
7834
  return { ...state, retryIndicator: null };
7593
7835
  case "harness.crash": {
7594
- const title = stringValue(event.data.error_title) || "A transient error occurred, retrying...";
7595
- const detail = stringValue(event.data.error_detail) || stringValue(event.data.error);
7836
+ const title = stringValue2(event.data.error_title) || "A transient error occurred, retrying...";
7837
+ const detail = stringValue2(event.data.error_detail) || stringValue2(event.data.error);
7596
7838
  return {
7597
7839
  ...state,
7598
7840
  retryIndicator: {
@@ -7607,7 +7849,7 @@ function applyRetryIndicator(state, event) {
7607
7849
  }
7608
7850
  }
7609
7851
  function applyUserMessage(messages, eventId, data) {
7610
- const content = stringValue(data.content);
7852
+ const content = stringValue2(data.content);
7611
7853
  const next = [...messages];
7612
7854
  const localIdx = next.findIndex(
7613
7855
  (m) => m.role === "user" && m.id.startsWith("local-") && m.content === content
@@ -7625,9 +7867,47 @@ function applyUserMessage(messages, eventId, data) {
7625
7867
  });
7626
7868
  return next;
7627
7869
  }
7870
+ function applyBrowserEvent(state, event, browser) {
7871
+ return withMessages(
7872
+ { ...state, browser },
7873
+ [...state.messages, browserMarker(event)]
7874
+ );
7875
+ }
7876
+ function browserMarker(event) {
7877
+ const labels = {
7878
+ "browser.provisioned": {
7879
+ content: "Browser ready.",
7880
+ warning: false
7881
+ },
7882
+ "browser.control_granted": {
7883
+ content: "A user took control of the browser.",
7884
+ warning: true
7885
+ },
7886
+ "browser.control_returned": {
7887
+ content: "Browser control returned to the agent.",
7888
+ warning: false
7889
+ },
7890
+ "browser.destroyed": {
7891
+ content: "Browser closed.",
7892
+ warning: false
7893
+ }
7894
+ };
7895
+ const label = labels[event.type] ?? {
7896
+ content: "Browser updated.",
7897
+ warning: false
7898
+ };
7899
+ return {
7900
+ id: `browser-marker-${event.eventId}`,
7901
+ role: "system",
7902
+ content: label.content,
7903
+ createdAt: /* @__PURE__ */ new Date(),
7904
+ status: "complete",
7905
+ systemKind: label.warning ? "browser_marker_warning" : "browser_marker"
7906
+ };
7907
+ }
7628
7908
  function applyLlmDelta(state, event) {
7629
- const deltaContent = stringValue(event.data.content);
7630
- const deltaReasoning = stringValue(event.data.reasoning);
7909
+ const deltaContent = stringValue2(event.data.content);
7910
+ const deltaReasoning = stringValue2(event.data.reasoning);
7631
7911
  const messages = [...state.messages];
7632
7912
  const lastIdx = findLastAssistantIndex(messages);
7633
7913
  const lastMsg = lastIdx >= 0 ? messages[lastIdx] : null;
@@ -7664,7 +7944,7 @@ function applyLlmDelta(state, event) {
7664
7944
  function applyLlmResponse(state, event) {
7665
7945
  const messages = [...state.messages];
7666
7946
  const message = objectValue(event.data.message);
7667
- const responseContent = stringValue(message?.content) || stringValue(event.data.content);
7947
+ const responseContent = stringValue2(message?.content) || stringValue2(event.data.content);
7668
7948
  const toolCalls = Array.isArray(message?.tool_calls) ? message?.tool_calls : [];
7669
7949
  const hasToolCalls = toolCalls.length > 0;
7670
7950
  const idx = findLastAssistantIndex(messages);
@@ -7722,12 +8002,12 @@ function applyLlmResponse(state, event) {
7722
8002
  cachedInputTokens: numberValue(event.data.cache_read_tokens),
7723
8003
  totalTokens: inputTokens + outputTokens,
7724
8004
  contextWindow: numberValue(event.data.context_window),
7725
- model: stringValue(event.data.model)
8005
+ model: stringValue2(event.data.model)
7726
8006
  }
7727
8007
  };
7728
8008
  }
7729
8009
  function applyLlmThinking(state, event) {
7730
- const reasoningText = stringValue(event.data.reasoning) || stringValue(event.data.content);
8010
+ const reasoningText = stringValue2(event.data.reasoning) || stringValue2(event.data.content);
7731
8011
  const messages = [...state.messages];
7732
8012
  const idx = findLastAssistantIndex(messages);
7733
8013
  const prev = idx >= 0 ? messages[idx] : null;
@@ -7775,11 +8055,11 @@ function applyToolCall(state, event) {
7775
8055
  messages[assistantIdx] = assistant;
7776
8056
  }
7777
8057
  const existingCalls = assistant.toolCalls ?? [];
7778
- const toolCallId = stringValue(event.data.tool_call_id) || `tc-${event.eventId}`;
8058
+ const toolCallId = stringValue2(event.data.tool_call_id) || `tc-${event.eventId}`;
7779
8059
  if (!existingCalls.some((tc) => tc.id === toolCallId)) {
7780
8060
  const entry = {
7781
8061
  id: toolCallId,
7782
- toolName: stringValue(event.data.name) || stringValue(event.data.tool_name) || "unknown",
8062
+ toolName: stringValue2(event.data.name) || stringValue2(event.data.tool_name) || "unknown",
7783
8063
  args: typeof event.data.arguments === "string" ? event.data.arguments : JSON.stringify(event.data.arguments ?? {}),
7784
8064
  status: "running"
7785
8065
  };
@@ -7795,7 +8075,7 @@ function applyToolCall(state, event) {
7795
8075
  };
7796
8076
  }
7797
8077
  function applyToolResult(messages, data) {
7798
- const targetId = stringValue(data.tool_call_id);
8078
+ const targetId = stringValue2(data.tool_call_id);
7799
8079
  let matchIdx = -1;
7800
8080
  for (let i = messages.length - 1; i >= 0; i--) {
7801
8081
  if (messages[i]?.role === "assistant" && messages[i]?.toolCalls?.some((tc) => tc.id === targetId)) {
@@ -7864,8 +8144,8 @@ function buildErrorInfo(event) {
7864
8144
  }
7865
8145
  return {
7866
8146
  category: event.data.error_category,
7867
- title: stringValue(event.data.error_title) || "The session failed due to an internal error.",
7868
- detail: stringValue(event.data.error_detail) || stringValue(event.data.error),
8147
+ title: stringValue2(event.data.error_title) || "The session failed due to an internal error.",
8148
+ detail: stringValue2(event.data.error_detail) || stringValue2(event.data.error),
7869
8149
  retryable: Boolean(event.data.retryable)
7870
8150
  };
7871
8151
  }
@@ -7909,7 +8189,7 @@ function applyExpertFeedback(messages, type, data) {
7909
8189
  return messages;
7910
8190
  }
7911
8191
  function applyClarifyResponse(messages, data) {
7912
- const targetToolId = stringValue(data.tool_call_id);
8192
+ const targetToolId = stringValue2(data.tool_call_id);
7913
8193
  const responses = Array.isArray(data.responses) ? data.responses : void 0;
7914
8194
  if (!targetToolId || !responses) return messages;
7915
8195
  const next = [...messages];
@@ -7924,8 +8204,8 @@ function applyClarifyResponse(messages, data) {
7924
8204
  clarifyAnswers: responses.map((response) => {
7925
8205
  const row = objectValue(response) ?? {};
7926
8206
  return {
7927
- question: stringValue(row.question),
7928
- answer: stringValue(row.answer),
8207
+ question: stringValue2(row.question),
8208
+ answer: stringValue2(row.answer),
7929
8209
  is_other: Boolean(row.is_other)
7930
8210
  };
7931
8211
  })
@@ -7945,7 +8225,7 @@ function applyPolicyDenied(state, data) {
7945
8225
  ...policyMsg,
7946
8226
  content: `${policyMsg.content}
7947
8227
 
7948
- **Policy denied**: ${stringValue(data.reason) || "Action blocked by governance policy."}`,
8228
+ **Policy denied**: ${stringValue2(data.reason) || "Action blocked by governance policy."}`,
7949
8229
  status: "error"
7950
8230
  };
7951
8231
  }
@@ -7997,7 +8277,7 @@ function objectValue(value) {
7997
8277
  }
7998
8278
  return void 0;
7999
8279
  }
8000
- function stringValue(value) {
8280
+ function stringValue2(value) {
8001
8281
  return typeof value === "string" ? value : "";
8002
8282
  }
8003
8283
  function numberValue(value) {
@@ -8231,6 +8511,7 @@ function useAgentChatRuntime({
8231
8511
  }
8232
8512
  }, [adapter, sessionId]);
8233
8513
  return {
8514
+ state,
8234
8515
  session,
8235
8516
  messages: state.messages,
8236
8517
  isRunning: state.isRunning,
@@ -8282,6 +8563,7 @@ function AgentChat({
8282
8563
  const isReadOnlySession = isScheduledRunSession(runtime.session);
8283
8564
  const effectiveDisabled = disabled || isReadOnlySession;
8284
8565
  const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
8566
+ const browserState = runtime.state.browser;
8285
8567
  useEffect(() => {
8286
8568
  onMessagesChange?.(runtime.messages);
8287
8569
  }, [onMessagesChange, runtime.messages]);
@@ -8318,19 +8600,29 @@ function AgentChat({
8318
8600
  retryIndicator: runtime.retryIndicator
8319
8601
  }
8320
8602
  ) }),
8321
- /* @__PURE__ */ jsx(
8322
- WorkspacePanel,
8323
- {
8324
- adapter,
8325
- sessionId,
8326
- selectedPath: workspacePath,
8327
- onSelectedPathChange: setWorkspacePath,
8328
- collapsed: workspaceCollapsed,
8329
- onCollapsedChange: setWorkspaceCollapsed,
8330
- refreshSignal: runtime.workspaceRefreshKey,
8331
- disabled: effectiveDisabled
8332
- }
8333
- )
8603
+ /* @__PURE__ */ jsxs("div", { "data-testid": "right-stack", className: "flex min-h-0 shrink-0 flex-col", children: [
8604
+ browserState !== null && sessionId && /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 border-b border-line", children: /* @__PURE__ */ jsx(
8605
+ BrowserPane,
8606
+ {
8607
+ sessionId,
8608
+ state: browserState,
8609
+ adapter
8610
+ }
8611
+ ) }),
8612
+ /* @__PURE__ */ jsx(
8613
+ WorkspacePanel,
8614
+ {
8615
+ adapter,
8616
+ sessionId,
8617
+ selectedPath: workspacePath,
8618
+ onSelectedPathChange: setWorkspacePath,
8619
+ collapsed: workspaceCollapsed,
8620
+ onCollapsedChange: setWorkspaceCollapsed,
8621
+ refreshSignal: runtime.workspaceRefreshKey,
8622
+ disabled: effectiveDisabled
8623
+ }
8624
+ )
8625
+ ] })
8334
8626
  ] }) })
8335
8627
  }
8336
8628
  );