@invergent/agent-chat-react 2.16.7 → 2.17.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, __publicField } from './chunk-47MF6JUW.js';
2
2
  import { createContext, memo, useRef, useEffect, useCallback, useMemo, useState, useLayoutEffect, forwardRef, useId, lazy, useContext, useSyncExternalStore, Fragment as Fragment$1, Suspense, Children } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight as ChevronRight$1, RefreshCw, CreditCard, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, PlugZapIcon, ChevronRightIcon, ArrowLeftIcon, Loader2Icon, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, CalendarClockIcon, UsersIcon, Pen, Eraser, Type, MousePointer2, Hand, Maximize2, Undo2, Redo2, CheckIcon, CopyIcon, DownloadIcon, Maximize2Icon, GlobeIcon, FolderIcon, MessageSquareIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, ChevronLeftIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, TrashIcon, ImageIcon, FileTextIcon, Clock, ArrowRight, ListTreeIcon, PenLineIcon, TerminalIcon, SparklesIcon, ClockIcon, PaperclipIcon, CloudIcon, HardDriveIcon, IdCardIcon, PlusIcon, ShrinkIcon, FolderOpenIcon, FileIcon, MinusIcon, SearchIcon, CircleDot, XCircle, CheckCircle2, ZapIcon, ArrowUpIcon, CircleCheckIcon, ThumbsUpIcon, ThumbsDownIcon, BookOpenIcon, GitBranchIcon, ActivityIcon, EyeIcon, LibraryIcon, ListTodoIcon, BotIcon, ListIcon, FileEditIcon, WrenchIcon, FileCodeIcon, CodeIcon, FileArchiveIcon, FileVideoIcon, FileAudioIcon, FileImageIcon, FileChartLineIcon, FileSpreadsheetIcon, XCircleIcon, CheckCircle2Icon, Code, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
4
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight as ChevronRight$1, RefreshCw, CreditCard, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, PlugZapIcon, ChevronRightIcon, ArrowLeftIcon, Loader2Icon, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, CalendarClockIcon, UsersIcon, Pen, Eraser, Type, MousePointer2, Hand, Maximize2, Undo2, Redo2, CheckIcon, CopyIcon, DownloadIcon, Maximize2Icon, LaptopIcon, GlobeIcon, FolderIcon, MessageSquareIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, ChevronLeftIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, TrashIcon, ImageIcon, FileTextIcon, Clock, ArrowRight, ListTreeIcon, PenLineIcon, TerminalIcon, SparklesIcon, ClockIcon, PaperclipIcon, CloudIcon, HardDriveIcon, IdCardIcon, PlusIcon, ShrinkIcon, FolderOpenIcon, FileIcon, MinusIcon, SearchIcon, CircleDot, XCircle, CheckCircle2, ZapIcon, ArrowUpIcon, CircleCheckIcon, ThumbsUpIcon, ThumbsDownIcon, BookOpenIcon, GitBranchIcon, ActivityIcon, EyeIcon, LibraryIcon, ListTodoIcon, BotIcon, ListIcon, FileEditIcon, WrenchIcon, FileCodeIcon, CodeIcon, FileArchiveIcon, FileVideoIcon, FileAudioIcon, FileImageIcon, FileChartLineIcon, FileSpreadsheetIcon, XCircleIcon, CheckCircle2Icon, Code, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
5
5
  import { cva } from 'class-variance-authority';
6
- import { Collapsible as Collapsible$1, Slot, Tooltip as Tooltip$1, Checkbox as Checkbox$1, Label as Label$1, Progress as Progress$1, Tabs as Tabs$1, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Popover as Popover$1 } from 'radix-ui';
6
+ import { Collapsible as Collapsible$1, Slot, Tooltip as Tooltip$1, Checkbox as Checkbox$1, Label as Label$1, Progress as Progress$1, Tabs as Tabs$1, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1 } from 'radix-ui';
7
7
  import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
8
8
  import { cjk } from '@streamdown/cjk';
9
9
  import { code } from '@streamdown/code';
@@ -32,6 +32,70 @@ function useAgentChatAdapterContext() {
32
32
  }
33
33
  return value;
34
34
  }
35
+ function useProjectThreads(adapter, projectId) {
36
+ const [rows, setRows] = useState({});
37
+ useEffect(() => {
38
+ setRows({});
39
+ if (!projectId || !adapter.listProjectThreads || !adapter.openProjectStream) return;
40
+ let closed = false;
41
+ let reading = false;
42
+ let wanted = /* @__PURE__ */ new Set();
43
+ const failed = /* @__PURE__ */ new Set();
44
+ const readOne = async (threadId) => {
45
+ if (threadId === null) {
46
+ const all = await adapter.listProjectThreads({ projectId });
47
+ if (!closed) setRows(Object.fromEntries(all.map((row2) => [row2.id, row2])));
48
+ return;
49
+ }
50
+ const [row] = await adapter.listProjectThreads({ projectId, threadId });
51
+ if (!closed) setRows(({ [threadId]: _gone, ...rest }) => row ? { ...rest, [threadId]: row } : rest);
52
+ };
53
+ const read = async () => {
54
+ if (reading) return;
55
+ reading = true;
56
+ while (wanted.size > 0 && !closed) {
57
+ const asked = wanted;
58
+ wanted = /* @__PURE__ */ new Set();
59
+ for (const threadId of asked.has(null) ? [null] : asked) {
60
+ if (closed) break;
61
+ try {
62
+ await readOne(threadId);
63
+ } catch {
64
+ failed.add(threadId);
65
+ }
66
+ }
67
+ }
68
+ reading = false;
69
+ };
70
+ const want = (threadId) => {
71
+ wanted.add(threadId);
72
+ for (const again of failed) wanted.add(again);
73
+ failed.clear();
74
+ void read();
75
+ };
76
+ const stream = adapter.openProjectStream({ projectId });
77
+ stream.onerror = () => {
78
+ if (closed) return;
79
+ closed = true;
80
+ setRows({});
81
+ };
82
+ stream.addEventListener("ready", () => want(null));
83
+ stream.addEventListener("change", (event) => {
84
+ let threadId = null;
85
+ try {
86
+ const named2 = JSON.parse(event.data).thread_id;
87
+ threadId = typeof named2 === "string" ? named2 : null;
88
+ } catch {
89
+ }
90
+ want(threadId);
91
+ });
92
+ return () => {
93
+ closed = true;
94
+ stream.close();
95
+ };
96
+ }, [adapter, projectId]);
97
+ return rows;
98
+ }
35
99
  var NAMED_KEYS = /* @__PURE__ */ new Set([
36
100
  "Enter",
37
101
  "Tab",
@@ -240,7 +304,7 @@ function BrowserShell({
240
304
  children: /* @__PURE__ */ jsx(Reload, {})
241
305
  }
242
306
  ),
243
- /* @__PURE__ */ jsx(
307
+ (onToggleControl || hasControl) && /* @__PURE__ */ jsx(
244
308
  "button",
245
309
  {
246
310
  type: "button",
@@ -758,11 +822,26 @@ function ConfirmDialog({
758
822
  }
759
823
  );
760
824
  }
825
+ function ComputerBrowserPane({
826
+ available,
827
+ readOnly,
828
+ draw
829
+ }) {
830
+ return /* @__PURE__ */ jsx(
831
+ "div",
832
+ {
833
+ "data-testid": "browser-pane",
834
+ className: "flex h-full min-h-0 flex-col items-center justify-center gap-3 bg-background p-6 text-center text-sm text-muted-foreground",
835
+ children: draw ? draw({ available, readOnly }) : available ? "The browser is open on the chat's computer." : "The chat's computer has no supported browser."
836
+ }
837
+ );
838
+ }
761
839
  function BrowserPane({
762
840
  sessionId,
763
841
  state,
764
842
  adapter,
765
- onClose
843
+ onClose,
844
+ readOnly = false
766
845
  }) {
767
846
  const [fullscreenOpen, setFullscreenOpen] = useState(false);
768
847
  const [openFullscreenOnControl, setOpenFullscreenOnControl] = useState(false);
@@ -819,8 +898,8 @@ function BrowserPane({
819
898
  src: shellUrl,
820
899
  testId,
821
900
  hasControl: localControlActive,
822
- onToggleControl: hasControlAdapter ? () => void control.toggleControl() : void 0,
823
- onClose: onClose ? () => control.setConfirmOpen(true) : void 0,
901
+ onToggleControl: hasControlAdapter && !readOnly ? () => void control.toggleControl() : void 0,
902
+ onClose: onClose && !readOnly ? () => control.setConfirmOpen(true) : void 0,
824
903
  onMaximize: fullscreenOpen ? void 0 : () => setFullscreenOpen(true),
825
904
  onDisconnect: () => setLocalControlActive(false)
826
905
  }
@@ -3931,13 +4010,13 @@ function Textarea({ className, ...props }) {
3931
4010
  var MAX_REASON_LENGTH = 500;
3932
4011
  function ExpertToolBlock({ tc }) {
3933
4012
  const [expanded, setExpanded] = useState(false);
3934
- const { adapter, sessionId } = useAgentChatAdapterContext();
4013
+ const { adapter, sessionId, readOnly } = useAgentChatAdapterContext();
3935
4014
  const [pending, setPending] = useState(null);
3936
4015
  const [error, setError] = useState(null);
3937
4016
  const [reasonDraft, setReasonDraft] = useState(null);
3938
4017
  const rating = tc.expertFeedback?.rating ?? pending;
3939
4018
  const alreadyRated = tc.expertFeedback !== void 0;
3940
- const canRate = tc.expertResultEventId !== void 0 && sessionId !== null && adapter.submitExpertFeedback !== void 0 && tc.status !== "running";
4019
+ const canRate = !readOnly && tc.expertResultEventId !== void 0 && sessionId !== null && adapter.submitExpertFeedback !== void 0 && tc.status !== "running";
3941
4020
  const submit = async (next, reason) => {
3942
4021
  if (sessionId === null || tc.expertResultEventId === void 0 || adapter.submitExpertFeedback === void 0) return;
3943
4022
  setPending(next);
@@ -4293,11 +4372,12 @@ function ConversationalAsk({
4293
4372
  question,
4294
4373
  assistantContent
4295
4374
  }) {
4296
- const { adapter, sessionId } = useAgentChatAdapterContext();
4375
+ const { adapter, sessionId, readOnly } = useAgentChatAdapterContext();
4297
4376
  const [submitting, setSubmitting] = useState(false);
4298
4377
  const [error, setError] = useState(null);
4299
4378
  const answered = tc.askUserQuestionAnswers !== void 0;
4300
- const pending = !answered && tc.status === "running";
4379
+ const open = !answered && tc.status === "running";
4380
+ const pending = open && !readOnly;
4301
4381
  const choices = question.choices ?? [];
4302
4382
  const showPrompt = useMemo(
4303
4383
  () => !promptEchoedInContent(assistantContent, question.prompt),
@@ -4359,7 +4439,7 @@ function ConversationalAsk({
4359
4439
  }
4360
4440
  ),
4361
4441
  error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }),
4362
- !answered && !pending && /* @__PURE__ */ jsx("p", { className: "text-xs italic text-muted-foreground/70", children: "No answer recorded." })
4442
+ !answered && !open && /* @__PURE__ */ jsx("p", { className: "text-xs italic text-muted-foreground/70", children: "No answer recorded." })
4363
4443
  ] });
4364
4444
  }
4365
4445
  function QuickReply({
@@ -4393,14 +4473,14 @@ function BatchAsk({
4393
4473
  questions,
4394
4474
  viewMode
4395
4475
  }) {
4396
- const { adapter, sessionId } = useAgentChatAdapterContext();
4476
+ const { adapter, sessionId, readOnly } = useAgentChatAdapterContext();
4397
4477
  const [active, setActive] = useState(0);
4398
4478
  const [selections, setSelections] = useState(
4399
4479
  () => questions.map(emptySelection)
4400
4480
  );
4401
4481
  const [submitting, setSubmitting] = useState(false);
4402
4482
  const [error, setError] = useState(null);
4403
- const locked = tc.askUserQuestionAnswers !== void 0 || tc.status !== "running";
4483
+ const locked = readOnly || tc.askUserQuestionAnswers !== void 0 || tc.status !== "running";
4404
4484
  useEffect(() => {
4405
4485
  if (active >= questions.length && questions.length > 0) setActive(0);
4406
4486
  }, [active, questions.length]);
@@ -4700,7 +4780,7 @@ function BatchAskLocked({
4700
4780
  );
4701
4781
  const cancelled = !answers && tc.status !== "running";
4702
4782
  return /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border bg-muted/20 px-3 py-2 text-sm max-w-2xl", children: [
4703
- /* @__PURE__ */ jsx("div", { className: "mb-1 text-xs text-muted-foreground", children: cancelled ? "Clarification cancelled" : "Clarification answered" }),
4783
+ /* @__PURE__ */ jsx("div", { className: "mb-1 text-xs text-muted-foreground", children: cancelled ? "Clarification cancelled" : answers ? "Clarification answered" : "Waiting for an answer" }),
4704
4784
  /* @__PURE__ */ jsx("ul", { className: "space-y-1.5", children: questions.map((q, i) => {
4705
4785
  const a = byPrompt.get(q.prompt);
4706
4786
  return /* @__PURE__ */ jsxs("li", { className: "text-sm", children: [
@@ -5939,6 +6019,59 @@ function CommandItem({
5939
6019
  }
5940
6020
  );
5941
6021
  }
6022
+ function DropdownMenu({
6023
+ ...props
6024
+ }) {
6025
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Root, { "data-slot": "dropdown-menu", ...props });
6026
+ }
6027
+ function DropdownMenuTrigger({
6028
+ ...props
6029
+ }) {
6030
+ return /* @__PURE__ */ jsx(
6031
+ DropdownMenu$1.Trigger,
6032
+ {
6033
+ "data-slot": "dropdown-menu-trigger",
6034
+ ...props
6035
+ }
6036
+ );
6037
+ }
6038
+ function DropdownMenuContent({
6039
+ className,
6040
+ align = "start",
6041
+ sideOffset = 4,
6042
+ ...props
6043
+ }) {
6044
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, { children: /* @__PURE__ */ jsx(
6045
+ DropdownMenu$1.Content,
6046
+ {
6047
+ "data-slot": "dropdown-menu-content",
6048
+ sideOffset,
6049
+ align,
6050
+ className: cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-48 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-none bg-popover p-1.5 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
6051
+ ...props
6052
+ }
6053
+ ) });
6054
+ }
6055
+ function DropdownMenuItem({
6056
+ className,
6057
+ inset,
6058
+ variant = "default",
6059
+ ...props
6060
+ }) {
6061
+ return /* @__PURE__ */ jsx(
6062
+ DropdownMenu$1.Item,
6063
+ {
6064
+ "data-slot": "dropdown-menu-item",
6065
+ "data-inset": inset,
6066
+ "data-variant": variant,
6067
+ className: cn(
6068
+ "group/dropdown-menu-item relative flex cursor-default items-center gap-2.5 rounded-none px-3 py-2 text-xs font-medium tracking-wider uppercase outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-9.5 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 data-[variant=destructive]:*:[svg]:text-destructive",
6069
+ className
6070
+ ),
6071
+ ...props
6072
+ }
6073
+ );
6074
+ }
5942
6075
  function Spinner({ className, ...props }) {
5943
6076
  return /* @__PURE__ */ jsx(Loader2Icon, { role: "status", "aria-label": "Loading", className: cn("size-4 animate-spin", className), ...props });
5944
6077
  }
@@ -8215,12 +8348,12 @@ function BrandBeam({
8215
8348
  borderRadius,
8216
8349
  className
8217
8350
  }) {
8218
- const reducedMotion = useSyncExternalStore(
8351
+ const reducedMotion2 = useSyncExternalStore(
8219
8352
  subscribeReducedMotion,
8220
8353
  readReducedMotion,
8221
8354
  readReducedMotionServer
8222
8355
  );
8223
- const effectiveActive = active && !(reducedMotion && ROTATE_SIZES.has(size2));
8356
+ const effectiveActive = active && !(reducedMotion2 && ROTATE_SIZES.has(size2));
8224
8357
  return /* @__PURE__ */ jsx(
8225
8358
  BorderBeam,
8226
8359
  {
@@ -10617,7 +10750,9 @@ var MAX_CPS = 240;
10617
10750
  var DRAIN_SECONDS = 0.25;
10618
10751
  var SNAP_LAG = 800;
10619
10752
  var MAX_FRAME_DT = 0.1;
10753
+ var reducedMotion = () => typeof document !== "undefined" && document.documentElement.dataset.motion === "reduced";
10620
10754
  function useSmoothStream(target, isStreaming) {
10755
+ const instant = reducedMotion();
10621
10756
  const [displayedLen, setDisplayedLen] = useState(
10622
10757
  () => isStreaming ? 0 : target.length
10623
10758
  );
@@ -10626,7 +10761,7 @@ function useSmoothStream(target, isStreaming) {
10626
10761
  useEffect(() => {
10627
10762
  setDisplayedLen((cur) => cur > target.length ? target.length : cur);
10628
10763
  }, [target]);
10629
- const isActive = isStreaming || displayedLen < target.length;
10764
+ const isActive = !instant && (isStreaming || displayedLen < target.length);
10630
10765
  useEffect(() => {
10631
10766
  if (!isActive) return;
10632
10767
  let raf = 0;
@@ -10658,7 +10793,7 @@ function useSmoothStream(target, isStreaming) {
10658
10793
  raf = requestAnimationFrame(tick);
10659
10794
  return () => cancelAnimationFrame(raf);
10660
10795
  }, [isActive]);
10661
- return displayedLen >= target.length ? target : target.slice(0, displayedLen);
10796
+ return instant || displayedLen >= target.length ? target : target.slice(0, displayedLen);
10662
10797
  }
10663
10798
 
10664
10799
  // src/runtime/reasoning-tokens.ts
@@ -11729,6 +11864,304 @@ function ArtifactRow({
11729
11864
  }
11730
11865
  return /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: artifact.label });
11731
11866
  }
11867
+ var GROUP_LABEL = {
11868
+ waiting: "Waiting on you",
11869
+ working: "Working",
11870
+ idle: "Idle",
11871
+ resolved: "Resolved"
11872
+ };
11873
+ var STATE_LABEL = {
11874
+ working: "Working",
11875
+ reported: "Reported",
11876
+ failed: "Failed"
11877
+ };
11878
+ var FILES_SHOWN = 3;
11879
+ var MARKS = {
11880
+ landed: null,
11881
+ redoing: "being redone",
11882
+ not_merged: "not merged",
11883
+ undone: "undone"
11884
+ };
11885
+ var changedBy = (by) => by.kind === "you" ? "you" : by.kind === "thread" ? `the thread ${by.title}` : `the routine ${by.name}`;
11886
+ function named(paths) {
11887
+ const names = paths.slice(0, FILES_SHOWN);
11888
+ if (paths.length > FILES_SHOWN) names.push(`${paths.length - FILES_SHOWN} more files`);
11889
+ return names.length === 1 ? names[0] : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
11890
+ }
11891
+ function undoLines(result) {
11892
+ const lines = result.applied.length > 0 ? [`Put back ${named(result.applied)}.`] : [];
11893
+ const picked = result.pickedUp ?? [];
11894
+ if (picked.length > 0) lines.push(`Your changes to ${named(picked)} were recorded first: each is a version in its History.`);
11895
+ for (const { path, by, pruned } of result.skipped.slice(0, FILES_SHOWN)) {
11896
+ lines.push(pruned ? `${path} could not be put back: its version from before is no longer kept.` : by === null ? `${path} was left as it is.` : `${path} was changed after this, by ${changedBy(by)}. It was left as it is. Restore an earlier version from its History.`);
11897
+ }
11898
+ const more = result.skipped.length - FILES_SHOWN;
11899
+ if (more === 1) lines.push("1 more file was left as it is: its History says who changed it.");
11900
+ if (more > 1) lines.push(`${more} more files were left as they are: each one's History says who changed it.`);
11901
+ return lines.length > 0 ? lines : ["Nothing to put back: the files are as they were."];
11902
+ }
11903
+ var ASIDE_WHAT = {
11904
+ copy: "its copy",
11905
+ repository: "its repository",
11906
+ place: "the earlier history at its folder's path, with its threads' copies"
11907
+ };
11908
+ function setAsideWhen(at) {
11909
+ const parts = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).formatToParts(new Date(at));
11910
+ const part = (type) => parts.find((found) => found.type === type)?.value ?? "";
11911
+ return `${part("day")} ${part("month")}, ${part("hour")}:${part("minute")}`;
11912
+ }
11913
+ function setAsideLines(aside) {
11914
+ const each = (entries) => (Array.isArray(entries) ? entries : []).flatMap((entry) => {
11915
+ const { kind, at } = typeof entry === "object" && entry !== null ? entry : {};
11916
+ const moment = typeof at === "string" ? Date.parse(at) : NaN;
11917
+ const what = typeof kind === "string" && Object.hasOwn(ASIDE_WHAT, kind) ? ASIDE_WHAT[kind] : void 0;
11918
+ return what !== void 0 && !Number.isNaN(moment) ? [`${what} from ${setAsideWhen(moment)}`] : [];
11919
+ });
11920
+ if (!aside) return [];
11921
+ const count = Number.isSafeInteger(aside.snapshots) && aside.snapshots > 0 ? aside.snapshots : 0;
11922
+ const kept = [...each(aside.kept), ...count > 0 ? [`${count} snapshot${count > 1 ? "s" : ""}`] : []];
11923
+ const gone = each(aside.gone);
11924
+ return [
11925
+ ...kept.length > 0 ? [`Kept on its computer: ${kept.join(" \xB7 ")}`] : [],
11926
+ ...gone.length > 0 ? [`No longer kept: ${gone.join(" \xB7 ")}`] : []
11927
+ ];
11928
+ }
11929
+ function firstLine3(text) {
11930
+ return text?.split("\n").find((line) => line.trim())?.trim() ?? null;
11931
+ }
11932
+ function statusLineOf(report) {
11933
+ for (const line of report?.split("\n") ?? []) {
11934
+ if (/^\s*#{1,6}(\s|$)/.test(line)) continue;
11935
+ const plain = line.replace(/\*\*|__|`/g, "").trim();
11936
+ if (plain) return plain;
11937
+ }
11938
+ return null;
11939
+ }
11940
+ var cardStarts = /* @__PURE__ */ new WeakMap();
11941
+ var startListeners = /* @__PURE__ */ new Set();
11942
+ var startsChanged = 0;
11943
+ var drawnCards = /* @__PURE__ */ new Map();
11944
+ function setCardStart(adapter, card, start) {
11945
+ const starts = cardStarts.get(adapter) ?? /* @__PURE__ */ new Map();
11946
+ cardStarts.set(adapter, starts.set(card, start));
11947
+ startsChanged++;
11948
+ for (const listener of startListeners) listener();
11949
+ }
11950
+ function useCardStarts(adapter) {
11951
+ useSyncExternalStore(
11952
+ (listener) => {
11953
+ startListeners.add(listener);
11954
+ return () => {
11955
+ startListeners.delete(listener);
11956
+ };
11957
+ },
11958
+ () => startsChanged
11959
+ );
11960
+ return cardStarts.get(adapter) ?? /* @__PURE__ */ new Map();
11961
+ }
11962
+ function ThreadCards({ message }) {
11963
+ if (message.worker) return /* @__PURE__ */ jsx(WorkerCard, { worker: message.worker });
11964
+ if (message.proposal) return /* @__PURE__ */ jsx(ProposalCard, { proposal: message.proposal });
11965
+ return null;
11966
+ }
11967
+ function WorkerCard({ worker }) {
11968
+ const { adapter, projectId, onFileSelect, onOpenSession, threadRows } = useAgentChatAdapterContext();
11969
+ const undoKey = `undo:${worker.id}`;
11970
+ const undone = useCardStarts(adapter).get(undoKey);
11971
+ const live = threadRows?.[worker.id];
11972
+ const group = live?.group && Object.hasOwn(GROUP_LABEL, live.group) ? live.group : void 0;
11973
+ const status = live ? [group && GROUP_LABEL[group], live.progress && `${live.progress.done}/${live.progress.total}`].filter(Boolean).join(" \xB7 ") : STATE_LABEL[worker.state];
11974
+ const statusLine = live ? live.statusLine : statusLineOf(worker.report);
11975
+ const files = live ? Array.isArray(live.files) ? live.files : [] : worker.files;
11976
+ const unlisted = live && Number.isSafeInteger(live.moreFiles) && live.moreFiles > 0 ? live.moreFiles : 0;
11977
+ const more = Math.max(files.length - FILES_SHOWN, 0) + unlisted;
11978
+ const setAside = setAsideLines(live?.setAside);
11979
+ const name = worker.title ?? firstLine3(worker.goal);
11980
+ const view = worker.title !== null ? "View thread" : "View worker";
11981
+ const canUndo = !!live && !!projectId && !!adapter.undoProjectChanges;
11982
+ const undoingNow = undone?.state === "undoing";
11983
+ const undo = async (target) => {
11984
+ if (undoingNow) return;
11985
+ setCardStart(adapter, undoKey, { state: "undoing" });
11986
+ try {
11987
+ setCardStart(adapter, undoKey, { state: "undone", result: await adapter.undoProjectChanges({ projectId, target }) });
11988
+ } catch (error) {
11989
+ setCardStart(adapter, undoKey, { state: "not_undone", error: error instanceof Error ? error.message : "The change could not be undone." });
11990
+ }
11991
+ };
11992
+ return /* @__PURE__ */ jsxs("div", { "data-testid": "worker-card", "data-group": group, className: "my-2 rounded-lg border border-border px-3 py-2 text-sm", children: [
11993
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
11994
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", title: name ?? void 0, children: /* @__PURE__ */ jsx("bdi", { children: name }) }),
11995
+ /* @__PURE__ */ jsx("span", { "data-testid": "worker-card-status", className: "shrink-0 text-xs text-muted-foreground", children: status })
11996
+ ] }),
11997
+ statusLine && /* @__PURE__ */ jsx("p", { className: "truncate text-foreground/70", title: statusLine, children: /* @__PURE__ */ jsx("bdi", { children: statusLine }) }),
11998
+ files.length + unlisted > 0 && /* @__PURE__ */ jsxs("ul", { className: "mt-1 flex flex-wrap gap-x-3 text-xs", children: [
11999
+ files.slice(0, FILES_SHOWN).map((file) => {
12000
+ const landing = "landing" in file && file.landing && Object.hasOwn(MARKS, file.landing) ? file.landing : null;
12001
+ const mark = landing ? MARKS[landing] : null;
12002
+ return (
12003
+ // A long name is cut, its mark never.
12004
+ /* @__PURE__ */ jsxs("li", { className: "flex min-w-0 max-w-full", title: file.label, children: [
12005
+ file.kind === "file" && onFileSelect ? /* @__PURE__ */ jsx(
12006
+ "button",
12007
+ {
12008
+ type: "button",
12009
+ className: "min-w-0 truncate underline",
12010
+ onClick: () => onFileSelect(file.ref),
12011
+ children: /* @__PURE__ */ jsx("bdi", { children: file.label })
12012
+ }
12013
+ ) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: /* @__PURE__ */ jsx("bdi", { children: file.label }) }),
12014
+ mark && /* @__PURE__ */ jsx("span", { "data-mark": landing, className: "shrink-0 whitespace-pre text-muted-foreground", children: ` \xB7 ${mark}` })
12015
+ ] }, `${file.kind}:${file.ref}`)
12016
+ );
12017
+ }),
12018
+ more > 0 && /* @__PURE__ */ jsxs("li", { children: [
12019
+ "+",
12020
+ more,
12021
+ " more"
12022
+ ] })
12023
+ ] }),
12024
+ setAside.length > 0 && /* @__PURE__ */ jsx("div", { "data-testid": "worker-card-set-aside", className: "mt-1 text-xs text-muted-foreground", children: setAside.map((line, at) => /* @__PURE__ */ jsx("p", { children: line }, at)) }),
12025
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-1", children: [
12026
+ onOpenSession && /* @__PURE__ */ jsx(Button, { size: "xs", variant: "ghost", "aria-label": name ? `${view} ${name}` : void 0, onClick: () => onOpenSession(worker.id), children: view }),
12027
+ canUndo && live.landingId && /* @__PURE__ */ jsx(
12028
+ Button,
12029
+ {
12030
+ size: "xs",
12031
+ variant: "ghost",
12032
+ disabled: undoingNow,
12033
+ "aria-label": name ? `Undo ${name}'s latest changes` : void 0,
12034
+ onClick: () => void undo({ landingId: live.landingId }),
12035
+ children: "Undo"
12036
+ }
12037
+ ),
12038
+ canUndo && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
12039
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { size: "xs", variant: "ghost", "aria-label": name ? `More for ${name}` : "More", children: "\u2026" }) }),
12040
+ /* @__PURE__ */ jsx(DropdownMenuContent, { children: /* @__PURE__ */ jsx(DropdownMenuItem, { disabled: undoingNow, onSelect: () => void undo({ threadId: worker.id }), children: "Undo this thread's changes" }) })
12041
+ ] })
12042
+ ] }),
12043
+ canUndo && /* @__PURE__ */ jsx("div", { role: "status", className: "text-xs text-foreground/70", children: undone?.state === "undone" && undoLines(undone.result).map((line, at) => /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("bdi", { children: line }) }, at)) }),
12044
+ canUndo && undone?.state === "not_undone" && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-xs text-destructive", children: /* @__PURE__ */ jsx("bdi", { children: undone.error }) })
12045
+ ] });
12046
+ }
12047
+ function ProposalCard({ proposal }) {
12048
+ const { adapter, projectId, onOpenSession } = useAgentChatAdapterContext();
12049
+ const starts = useCardStarts(adapter);
12050
+ const startOf = (key) => starts.get(`${proposal.proposalId}:${key}`);
12051
+ const told = (key, start2) => setCardStart(adapter, `${proposal.proposalId}:${key}`, start2);
12052
+ const started = {};
12053
+ for (const { key } of proposal.threads) {
12054
+ const start2 = startOf(key);
12055
+ if (start2?.state === "started") started[key] = start2.threadId;
12056
+ }
12057
+ Object.assign(started, proposal.started);
12058
+ const startingAt = (key) => {
12059
+ const start2 = startOf(key);
12060
+ return start2?.state === "starting" ? start2.where : void 0;
12061
+ };
12062
+ const starting = (key) => startingAt(key) !== void 0;
12063
+ const canStart = !!projectId && !!adapter.startProposedThread;
12064
+ const canStartHere = !!projectId && !!adapter.startLocalThread;
12065
+ const viewButtons = useRef({});
12066
+ useEffect(() => {
12067
+ drawnCards.set(proposal.proposalId, (drawnCards.get(proposal.proposalId) ?? 0) + 1);
12068
+ return () => {
12069
+ const left = (drawnCards.get(proposal.proposalId) ?? 1) - 1;
12070
+ if (left > 0) drawnCards.set(proposal.proposalId, left);
12071
+ else drawnCards.delete(proposal.proposalId);
12072
+ };
12073
+ }, [proposal.proposalId]);
12074
+ useEffect(() => {
12075
+ for (const { key } of proposal.threads) {
12076
+ const start2 = startOf(key);
12077
+ if (start2?.state !== "started" || !start2.focus) continue;
12078
+ if (!document.activeElement || document.activeElement === document.body) viewButtons.current[key]?.focus();
12079
+ told(key, { ...start2, focus: false });
12080
+ }
12081
+ });
12082
+ const start = async (key, title) => {
12083
+ told(key, { state: "starting", where: title === void 0 ? "cloud" : "device" });
12084
+ try {
12085
+ const card = { projectId, proposalId: proposal.proposalId, key };
12086
+ const row = title === void 0 ? await adapter.startProposedThread(card) : await adapter.startLocalThread({ ...card, title });
12087
+ told(key, { state: "started", threadId: row.id, focus: drawnCards.has(proposal.proposalId) });
12088
+ } catch (error) {
12089
+ told(key, { state: "failed", error: error instanceof Error ? error.message : "The thread could not be started." });
12090
+ }
12091
+ };
12092
+ const startable = proposal.threads.filter(
12093
+ (thread) => thread.where === "cloud" && !started[thread.key] && !starting(thread.key)
12094
+ );
12095
+ const startAll = async (keys) => {
12096
+ for (const key of keys) told(key, { state: "starting", where: "cloud" });
12097
+ for (const key of keys) await start(key);
12098
+ };
12099
+ return /* @__PURE__ */ jsxs("div", { "data-testid": "thread-proposal-card", className: "my-2 rounded-lg border border-border px-3 py-2 text-sm", children: [
12100
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
12101
+ /* @__PURE__ */ jsx("span", { className: "flex-1 font-medium text-foreground", children: "Proposed threads" }),
12102
+ canStart && startable.length > 1 && /* @__PURE__ */ jsx(Button, { size: "xs", onClick: () => void startAll(startable.map((thread) => thread.key)), children: "Start all" })
12103
+ ] }),
12104
+ /* @__PURE__ */ jsx("ul", { className: "mt-1 space-y-2", children: proposal.threads.map((thread) => {
12105
+ const threadId = started[thread.key];
12106
+ const tried = startOf(thread.key);
12107
+ return /* @__PURE__ */ jsxs("li", { "data-testid": "proposed-thread", children: [
12108
+ /* @__PURE__ */ jsx("div", { className: "break-words font-medium text-foreground", children: /* @__PURE__ */ jsx("bdi", { children: thread.title }) }),
12109
+ /* @__PURE__ */ jsx("p", { className: "line-clamp-2 break-words text-foreground/70", children: /* @__PURE__ */ jsx("bdi", { children: thread.goal }) }),
12110
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
12111
+ /* @__PURE__ */ jsx("span", { role: "status", children: threadId ? "Started" : "" }),
12112
+ threadId ? onOpenSession && /* @__PURE__ */ jsx(
12113
+ Button,
12114
+ {
12115
+ ref: (node) => {
12116
+ viewButtons.current[thread.key] = node;
12117
+ },
12118
+ size: "xs",
12119
+ variant: "ghost",
12120
+ "aria-label": `View thread ${thread.title}`,
12121
+ onClick: () => onOpenSession(threadId),
12122
+ children: "View thread"
12123
+ }
12124
+ ) : thread.where === "device" ? /* @__PURE__ */ jsxs(Fragment, { children: [
12125
+ /* @__PURE__ */ jsx("span", { children: canStartHere ? "Allow Surogate to work in a folder on your device" : "This works in a folder on your device, which needs Surogate Desktop" }),
12126
+ canStartHere && /* @__PURE__ */ jsx(
12127
+ Button,
12128
+ {
12129
+ size: "xs",
12130
+ variant: "outline",
12131
+ disabled: starting(thread.key),
12132
+ "aria-label": `${startingAt(thread.key) === "device" ? "Starting" : "Allow"} ${thread.title}`,
12133
+ onClick: () => void start(thread.key, thread.title),
12134
+ children: startingAt(thread.key) === "device" ? "Starting\u2026" : "Allow"
12135
+ }
12136
+ ),
12137
+ canStart && /* @__PURE__ */ jsx(
12138
+ Button,
12139
+ {
12140
+ size: "xs",
12141
+ variant: "ghost",
12142
+ disabled: starting(thread.key),
12143
+ "aria-label": startingAt(thread.key) === "cloud" ? `Starting ${thread.title}` : `Run ${thread.title} in the cloud instead`,
12144
+ onClick: () => void start(thread.key),
12145
+ children: startingAt(thread.key) === "cloud" ? "Starting\u2026" : "Run in the cloud instead"
12146
+ }
12147
+ )
12148
+ ] }) : canStart ? /* @__PURE__ */ jsx(
12149
+ Button,
12150
+ {
12151
+ size: "xs",
12152
+ variant: "outline",
12153
+ disabled: starting(thread.key),
12154
+ "aria-label": `${starting(thread.key) ? "Starting" : "Start"} ${thread.title}`,
12155
+ onClick: () => void start(thread.key),
12156
+ children: starting(thread.key) ? "Starting\u2026" : "Start"
12157
+ }
12158
+ ) : null
12159
+ ] }),
12160
+ !threadId && tried?.state === "failed" && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-xs text-destructive", children: tried.error })
12161
+ ] }, thread.key);
12162
+ }) })
12163
+ ] });
12164
+ }
11732
12165
  var WORKING_ON_IT_DELAY_MS = 250;
11733
12166
  function hasUserFacingAskContent(msg) {
11734
12167
  return !!msg.toolCalls?.some((tc) => tc.toolName === "ask_user_question");
@@ -11784,6 +12217,9 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
11784
12217
  originatingSessionId
11785
12218
  }];
11786
12219
  }
12220
+ if (msg.systemKind === "worker" || msg.systemKind === "thread_proposal") {
12221
+ return [{ kind: "thread_card", key: msg.id, message: msg }];
12222
+ }
11787
12223
  if (msg.systemKind === "browser_marker" || msg.systemKind === "browser_marker_warning") {
11788
12224
  return [{
11789
12225
  kind: "browser_marker",
@@ -12015,6 +12451,9 @@ function OrphanSystemMarker({
12015
12451
  if (message.systemKind === "error" && message.errorInfo) {
12016
12452
  return /* @__PURE__ */ jsx("div", { className: "mx-auto my-2 w-full max-w-4xl", children: /* @__PURE__ */ jsx(ErrorMessage, { errorInfo: message.errorInfo, onRetry }) });
12017
12453
  }
12454
+ if (message.systemKind === "worker" || message.systemKind === "thread_proposal") {
12455
+ return /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-4xl px-4", children: /* @__PURE__ */ jsx(ThreadCards, { message }) });
12456
+ }
12018
12457
  return null;
12019
12458
  }
12020
12459
  function CancelledToolRow({ tc }) {
@@ -12119,6 +12558,15 @@ function TimelineEntryItem({
12119
12558
  ) })
12120
12559
  ] });
12121
12560
  }
12561
+ if (entry.kind === "thread_card") {
12562
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
12563
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
12564
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
12565
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/30" })
12566
+ ] }),
12567
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(ThreadCards, { message: entry.message }) })
12568
+ ] });
12569
+ }
12122
12570
  if (entry.kind === "skill_invoked") {
12123
12571
  return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
12124
12572
  /* @__PURE__ */ jsxs(TimelineHeader, { children: [
@@ -12731,7 +13179,9 @@ function ChatThread({
12731
13179
  researchSources = [],
12732
13180
  hideTurnSummary = false,
12733
13181
  agentId,
12734
- onOpenIntegrations
13182
+ onOpenIntegrations,
13183
+ deviceWait = null,
13184
+ composerFooter
12735
13185
  }) {
12736
13186
  const groups = useMemo(() => groupMessages(messages), [messages]);
12737
13187
  const activeAssistantId = useMemo(() => {
@@ -12807,6 +13257,18 @@ function ChatThread({
12807
13257
  return null;
12808
13258
  }, [messages]);
12809
13259
  const composerArea = /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
13260
+ /* @__PURE__ */ jsx(
13261
+ "div",
13262
+ {
13263
+ role: "status",
13264
+ "data-testid": "device-wait",
13265
+ className: deviceWait ? "mb-2 flex items-center gap-2 rounded-lg border border-line bg-card px-3 py-2 text-sm text-muted-foreground" : void 0,
13266
+ children: deviceWait && /* @__PURE__ */ jsxs(Fragment, { children: [
13267
+ /* @__PURE__ */ jsx(LaptopIcon, { className: "size-4 shrink-0", "aria-hidden": "true" }),
13268
+ deviceWait.reason === "copying" ? `Making this thread's copy of ${deviceWait.folder || "its folder"}\u2026` : `Waiting for ${deviceWait.deviceName || "your computer"}. The chat goes on once it is back online.`
13269
+ ] })
13270
+ }
13271
+ ),
12810
13272
  retryIndicator && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(RetryBanner, { indicator: retryIndicator }) }),
12811
13273
  researchSources.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(ResearchSourcesPanel, { sources: researchSources }) }),
12812
13274
  (paneCards?.browser || paneCards?.files) && // Layers sliding out from behind the prompt box: each card is
@@ -12891,6 +13353,7 @@ function ChatThread({
12891
13353
  compressEnabled
12892
13354
  }
12893
13355
  ),
13356
+ composerFooter,
12894
13357
  onOpenIntegrations && /* @__PURE__ */ jsx(
12895
13358
  IntegrationsBandSlot,
12896
13359
  {
@@ -12905,76 +13368,80 @@ function ChatThread({
12905
13368
  // input floating mid-screen with dead space under it, and the keyboard
12906
13369
  // then opens across that gap. There the composer keeps its usual place
12907
13370
  // at the bottom edge and the greeting takes the space above it.
12908
- /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-end overflow-hidden bg-background px-3 text-base md:justify-center", children: [
13371
+ // data-transcript: the host's transcript settings shape this, the composer with it.
13372
+ /* @__PURE__ */ jsxs("div", { "data-transcript": "", className: "flex flex-1 flex-col items-center justify-end overflow-hidden bg-background px-3 text-base md:justify-center", children: [
12909
13373
  /* @__PURE__ */ jsx("div", { className: "flex w-full flex-1 flex-col justify-center md:flex-none", children: /* @__PURE__ */ jsx("h2", { className: "mb-6 text-center text-2xl font-bold text-foreground md:mb-8", children: "What should we do ?" }) }),
12910
13374
  /* @__PURE__ */ jsx("div", { className: "w-full", children: composerArea })
12911
13375
  ] })
12912
13376
  );
12913
13377
  }
12914
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col overflow-hidden bg-background text-base", children: [
12915
- /* @__PURE__ */ jsxs(Conversation, { className: "relative flex-1 min-h-0", children: [
12916
- /* @__PURE__ */ jsx(ConversationContent, { className: "mx-auto w-full max-w-4xl", children: messages.length === 0 && isLoadingHistory ? /* @__PURE__ */ jsx(
12917
- ConversationEmptyState,
12918
- {
12919
- icon: /* @__PURE__ */ jsx(MessageSquareIcon, { className: "size-8 opacity-40" }),
12920
- title: "Loading conversation",
12921
- description: "Fetching the session history."
12922
- }
12923
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
12924
- groups.map((group) => {
12925
- if (group.role === "user") {
12926
- const msg = group.messages[0];
12927
- return /* @__PURE__ */ jsx(
12928
- ChatMessage,
12929
- {
12930
- message: msg,
12931
- onFileSelect
12932
- },
12933
- msg.id
12934
- );
13378
+ return (
13379
+ // data-transcript: the host's transcript settings shape this, the composer with it.
13380
+ /* @__PURE__ */ jsxs("div", { "data-transcript": "", className: "flex flex-1 flex-col overflow-hidden bg-background text-base", children: [
13381
+ /* @__PURE__ */ jsxs(Conversation, { className: "relative flex-1 min-h-0", children: [
13382
+ /* @__PURE__ */ jsx(ConversationContent, { className: "mx-auto w-full max-w-4xl", children: messages.length === 0 && isLoadingHistory ? /* @__PURE__ */ jsx(
13383
+ ConversationEmptyState,
13384
+ {
13385
+ icon: /* @__PURE__ */ jsx(MessageSquareIcon, { className: "size-8 opacity-40" }),
13386
+ title: "Loading conversation",
13387
+ description: "Fetching the session history."
12935
13388
  }
12936
- if (group.role === "system") {
12937
- const msg = group.messages[0];
12938
- if (viewMode === "simple" && msg.systemKind === "skill_invoked") {
12939
- return null;
13389
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
13390
+ groups.map((group) => {
13391
+ if (group.role === "user") {
13392
+ const msg = group.messages[0];
13393
+ return /* @__PURE__ */ jsx(
13394
+ ChatMessage,
13395
+ {
13396
+ message: msg,
13397
+ onFileSelect
13398
+ },
13399
+ msg.id
13400
+ );
13401
+ }
13402
+ if (group.role === "system") {
13403
+ const msg = group.messages[0];
13404
+ if (viewMode === "simple" && msg.systemKind === "skill_invoked") {
13405
+ return null;
13406
+ }
13407
+ const groupRetry2 = msg.id === activeFailureId ? onRetry : void 0;
13408
+ return /* @__PURE__ */ jsx(
13409
+ OrphanSystemMarker,
13410
+ {
13411
+ message: msg,
13412
+ sessionId,
13413
+ onRetry: groupRetry2
13414
+ },
13415
+ msg.id
13416
+ );
12940
13417
  }
12941
- const groupRetry2 = msg.id === activeFailureId ? onRetry : void 0;
13418
+ const groupTail = group.messages[group.messages.length - 1];
13419
+ const groupRetry = groupTail.id === activeFailureId ? onRetry : void 0;
12942
13420
  return /* @__PURE__ */ jsx(
12943
- OrphanSystemMarker,
13421
+ AssistantGroup,
12944
13422
  {
12945
- message: msg,
13423
+ messages: group.messages,
13424
+ activeAssistantId,
13425
+ lastGlobalIndex: group.lastGlobalIndex,
13426
+ totalMessages: messages.length,
13427
+ isRunning,
12946
13428
  sessionId,
12947
- onRetry: groupRetry2
13429
+ artifactFallbacks,
13430
+ onFileSelect,
13431
+ onRetry: groupRetry,
13432
+ viewMode,
13433
+ hideTurnSummary
12948
13434
  },
12949
- msg.id
13435
+ group.messages[0].id
12950
13436
  );
12951
- }
12952
- const groupTail = group.messages[group.messages.length - 1];
12953
- const groupRetry = groupTail.id === activeFailureId ? onRetry : void 0;
12954
- return /* @__PURE__ */ jsx(
12955
- AssistantGroup,
12956
- {
12957
- messages: group.messages,
12958
- activeAssistantId,
12959
- lastGlobalIndex: group.lastGlobalIndex,
12960
- totalMessages: messages.length,
12961
- isRunning,
12962
- sessionId,
12963
- artifactFallbacks,
12964
- onFileSelect,
12965
- onRetry: groupRetry,
12966
- viewMode,
12967
- hideTurnSummary
12968
- },
12969
- group.messages[0].id
12970
- );
12971
- }),
12972
- showWorkingOnIt && /* @__PURE__ */ jsx(WorkingOnItIndicator, { activity: orbActivity })
12973
- ] }) }),
12974
- /* @__PURE__ */ jsx(ConversationScrollButton, {})
12975
- ] }),
12976
- composerArea
12977
- ] });
13437
+ }),
13438
+ showWorkingOnIt && /* @__PURE__ */ jsx(WorkingOnItIndicator, { activity: orbActivity })
13439
+ ] }) }),
13440
+ /* @__PURE__ */ jsx(ConversationScrollButton, {})
13441
+ ] }),
13442
+ composerArea
13443
+ ] })
13444
+ );
12978
13445
  }
12979
13446
  function IntegrationsBandSlot({
12980
13447
  agentId,
@@ -13001,6 +13468,7 @@ var WORKSPACE_MUTATING_TOOLS = /* @__PURE__ */ new Set([
13001
13468
  ]);
13002
13469
  var AGENT_CHAT_LISTENED_EVENTS = [
13003
13470
  "user.message",
13471
+ "coordinator.message",
13004
13472
  "llm.request",
13005
13473
  "llm.response",
13006
13474
  "llm.thinking",
@@ -13032,63 +13500,286 @@ var AGENT_CHAT_LISTENED_EVENTS = [
13032
13500
  "artifact.updated",
13033
13501
  "browser.provisioned",
13034
13502
  "browser.destroyed",
13503
+ "browser.unavailable",
13035
13504
  "browser.control_granted",
13036
13505
  "browser.control_returned",
13037
13506
  "ask_user_question.response",
13038
13507
  "iteration.summary",
13039
13508
  "turn.summary",
13040
- "loop.result"
13509
+ "loop.result",
13510
+ "device.waiting",
13511
+ "device.resumed",
13512
+ "worker.spawned",
13513
+ "worker.complete",
13514
+ "worker.failed",
13515
+ "thread.proposed"
13041
13516
  ];
13042
13517
  var AGENT_CHAT_LISTENED_EVENT_SET = new Set(
13043
13518
  AGENT_CHAT_LISTENED_EVENTS
13044
13519
  );
13045
13520
 
13046
- // src/runtime/reducer.ts
13047
- var EMPTY_TOKEN_USAGE = {
13048
- inputTokens: 0,
13049
- outputTokens: 0,
13050
- reasoningTokens: 0,
13051
- cachedInputTokens: 0,
13052
- totalTokens: 0,
13053
- contextWindow: 0,
13054
- model: ""
13055
- };
13056
- function createInitialAgentChatState(options = {}) {
13057
- return {
13058
- messages: [],
13059
- isRunning: false,
13060
- isLoadingHistory: options.isLoadingHistory ?? false,
13061
- tokenUsage: EMPTY_TOKEN_USAGE,
13062
- retryIndicator: null,
13063
- lastEventId: 0,
13064
- sessionDone: false,
13065
- hadDeltas: false,
13066
- terminal: false,
13067
- stopped: false,
13068
- workspaceRefreshKey: 0,
13069
- browser: null,
13070
- viewMode: options.viewMode ?? "simple",
13071
- researchSources: []
13072
- };
13521
+ // src/components/missions/mission-derive.ts
13522
+ var ACTIVE_MISSION_STATUSES = /* @__PURE__ */ new Set(["active", "paused"]);
13523
+ function isTerminalMissionStatus(status) {
13524
+ return !ACTIVE_MISSION_STATUSES.has(status);
13073
13525
  }
13074
- function applyAgentChatEvent(state, event) {
13075
- let nextState = {
13076
- ...state,
13077
- isLoadingHistory: false,
13078
- lastEventId: Math.max(state.lastEventId, event.eventId),
13079
- sessionDone: state.sessionDone || event.type === "session.done"
13526
+ function missionTaskBucket(status) {
13527
+ if (status === "done") return "done";
13528
+ if (status === "blocked") return "blocked";
13529
+ if (status === "failed" || status === "cancelled") {
13530
+ return "failed_or_cancelled";
13531
+ }
13532
+ return "in_flight";
13533
+ }
13534
+ function groupMissionTasksByBucket(tasks) {
13535
+ const buckets = {
13536
+ in_flight: [],
13537
+ done: [],
13538
+ blocked: [],
13539
+ failed_or_cancelled: []
13080
13540
  };
13081
- nextState = applyRetryIndicator(nextState, event);
13082
- if (state.stopped && isPostTerminalLiveEvent(event.type)) {
13083
- return nextState;
13541
+ for (const t of tasks) {
13542
+ buckets[missionTaskBucket(t.status)].push(t);
13084
13543
  }
13085
- switch (event.type) {
13086
- case "user.message":
13087
- return withMessages(
13088
- { ...nextState, terminal: false, stopped: false, isRunning: true },
13089
- applyUserMessage(nextState.messages, event.eventId, event.data)
13090
- );
13091
- case "skill.invoked":
13544
+ return buckets;
13545
+ }
13546
+ function deriveMissionWorkerActivityLabel(worker) {
13547
+ const kind = worker.latestEventKind;
13548
+ if (kind === "tool.call" && worker.latestEventSummary) {
13549
+ return `tool: ${truncate4(worker.latestEventSummary, 80)}`;
13550
+ }
13551
+ if (kind === "llm.response") {
13552
+ return "thinking";
13553
+ }
13554
+ if (kind === "tool.result") {
13555
+ return "received tool result";
13556
+ }
13557
+ if (kind && worker.latestEventSummary) {
13558
+ return `${kind}: ${truncate4(worker.latestEventSummary, 60)}`;
13559
+ }
13560
+ return worker.sessionStatus || "active";
13561
+ }
13562
+ function truncate4(s, max) {
13563
+ if (s.length <= max) return s;
13564
+ return `${s.slice(0, max - 1)}\u2026`;
13565
+ }
13566
+ var MISSION_EVENT_CATEGORIES = [
13567
+ "spawn",
13568
+ "output",
13569
+ "done",
13570
+ "verdict",
13571
+ "system"
13572
+ ];
13573
+ function missionEventCategory(type) {
13574
+ switch (type) {
13575
+ case "worker.spawned":
13576
+ return "spawn";
13577
+ case "iteration.summary":
13578
+ return "output";
13579
+ case "worker.complete":
13580
+ return "done";
13581
+ case "mission.evaluation.end":
13582
+ return "verdict";
13583
+ default:
13584
+ return "system";
13585
+ }
13586
+ }
13587
+ function dataString(data, key) {
13588
+ const v = data?.[key];
13589
+ return typeof v === "string" && v.trim().length > 0 ? v.trim() : null;
13590
+ }
13591
+ var CONTROL_MARKUP_RE = /<next_action\b[^>]*(?:\/>|>[\s\S]*?<\/next_action>)/gi;
13592
+ function stripMissionControlMarkup(text) {
13593
+ return text.replace(CONTROL_MARKUP_RE, "").trim();
13594
+ }
13595
+ function missionEventSummary(event) {
13596
+ return stripMissionControlMarkup(rawMissionEventSummary(event));
13597
+ }
13598
+ function rawMissionEventSummary(event) {
13599
+ const d = event.data ?? null;
13600
+ switch (event.type) {
13601
+ case "worker.spawned":
13602
+ return dataString(d, "goal") ?? "Task spawned";
13603
+ case "worker.complete":
13604
+ return dataString(d, "result") ?? "Worker complete";
13605
+ case "iteration.summary":
13606
+ return dataString(d, "summary") ?? "";
13607
+ case "mission.evaluation.end": {
13608
+ const result = dataString(d, "result");
13609
+ const feedback = dataString(d, "feedback") ?? dataString(d, "explanation");
13610
+ if (result && feedback) return `${result} \u2014 ${feedback}`;
13611
+ return result ?? feedback ?? "Evaluation finished";
13612
+ }
13613
+ case "task.blocked":
13614
+ return dataString(d, "reason") ?? "Task blocked";
13615
+ case "task.failed":
13616
+ return "Task failed";
13617
+ default: {
13618
+ const text = dataString(d, "summary") ?? dataString(d, "text") ?? dataString(d, "message") ?? dataString(d, "reason");
13619
+ if (text) return text;
13620
+ if (!d) return "";
13621
+ try {
13622
+ return truncate4(JSON.stringify(d), 160);
13623
+ } catch {
13624
+ return "";
13625
+ }
13626
+ }
13627
+ }
13628
+ }
13629
+ function missionEventTaskId(event, sessions) {
13630
+ return dataString(event.data ?? null, "task_id") ?? sessions[event.sessionId]?.taskId ?? null;
13631
+ }
13632
+ function missionEventActorLabel(event, sessions) {
13633
+ const meta = sessions[event.sessionId];
13634
+ if (!meta) return event.sessionId.slice(0, 8);
13635
+ if (meta.kind === "coordinator") return "orchestrator";
13636
+ return meta.agentDefName ?? "worker";
13637
+ }
13638
+ function missionTaskBlocks(tasks) {
13639
+ const blocks = {};
13640
+ for (const t of tasks) {
13641
+ for (const parent of t.parentIds) {
13642
+ (blocks[parent] ?? (blocks[parent] = [])).push(t.id);
13643
+ }
13644
+ }
13645
+ return blocks;
13646
+ }
13647
+ function missionTaskTitle(goal, max = 80) {
13648
+ const line = goal.split("\n").find((l) => l.trim().length > 0)?.trim() ?? goal.trim();
13649
+ return truncate4(line.replace(/\s+/g, " "), max);
13650
+ }
13651
+ var RAIL_GROUP_DEFS = [
13652
+ { key: "running", label: "Running", match: (s) => s === "running" },
13653
+ { key: "blocked", label: "Blocked", match: (s) => s === "blocked" },
13654
+ {
13655
+ key: "queued",
13656
+ label: "Queued",
13657
+ match: (s) => s === "ready" || s === "todo"
13658
+ },
13659
+ { key: "done", label: "Done", match: (s) => s === "done" },
13660
+ {
13661
+ key: "failed",
13662
+ label: "Failed / cancelled",
13663
+ match: (s) => s === "failed" || s === "cancelled"
13664
+ }
13665
+ ];
13666
+ function missionTaskRailGroups(tasks) {
13667
+ const grouped = /* @__PURE__ */ new Set();
13668
+ const groups = RAIL_GROUP_DEFS.map((def) => ({
13669
+ key: def.key,
13670
+ label: def.label,
13671
+ tasks: tasks.filter((t) => {
13672
+ const hit = def.match(t.status);
13673
+ if (hit) grouped.add(t.id);
13674
+ return hit;
13675
+ })
13676
+ }));
13677
+ const stray = tasks.filter((t) => !grouped.has(t.id));
13678
+ if (stray.length > 0) {
13679
+ const queued = groups.find((g) => g.key === "queued");
13680
+ if (queued) queued.tasks.push(...stray);
13681
+ }
13682
+ return groups.filter((g) => g.tasks.length > 0);
13683
+ }
13684
+ var TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
13685
+ function defaultSelectedMissionTaskId(tasks) {
13686
+ if (tasks.length === 0) return null;
13687
+ const ordered = missionTaskRailGroups(tasks).flatMap((g) => g.tasks);
13688
+ const firstLive = ordered.find(
13689
+ (t) => !TERMINAL_TASK_STATUSES.has(t.status)
13690
+ );
13691
+ return (firstLive ?? ordered[0] ?? tasks[0]).id;
13692
+ }
13693
+ function mergeMissionEvents(existing, incoming) {
13694
+ if (incoming.length === 0) return existing;
13695
+ const byId = /* @__PURE__ */ new Map();
13696
+ for (const e of existing) byId.set(e.id, e);
13697
+ for (const e of incoming) byId.set(e.id, e);
13698
+ return [...byId.values()].sort((a, b) => a.id - b.id);
13699
+ }
13700
+ function missionTaskStatusDotClass(status) {
13701
+ switch (status) {
13702
+ case "running":
13703
+ return "bg-primary";
13704
+ case "blocked":
13705
+ return "bg-amber-500";
13706
+ case "done":
13707
+ return "bg-emerald-500";
13708
+ case "failed":
13709
+ return "bg-destructive";
13710
+ case "cancelled":
13711
+ return "bg-muted-foreground";
13712
+ default:
13713
+ return "bg-foreground/30";
13714
+ }
13715
+ }
13716
+ function missionWorkerTaskCounts(worker, tasks) {
13717
+ if (worker.kind !== "task") return null;
13718
+ const mine = tasks.filter((t) => t.agentDefName === worker.agentDefName);
13719
+ return {
13720
+ completed: mine.filter((t) => t.status === "done").length,
13721
+ inFlight: mine.filter(
13722
+ (t) => t.status === "running" || t.status === "blocked"
13723
+ ).length
13724
+ };
13725
+ }
13726
+ function formatMissionTimestamp(iso) {
13727
+ if (!iso) return "";
13728
+ const parsed = new Date(iso);
13729
+ if (Number.isNaN(parsed.getTime())) return iso;
13730
+ return parsed.toLocaleTimeString();
13731
+ }
13732
+
13733
+ // src/runtime/reducer.ts
13734
+ var EMPTY_TOKEN_USAGE = {
13735
+ inputTokens: 0,
13736
+ outputTokens: 0,
13737
+ reasoningTokens: 0,
13738
+ cachedInputTokens: 0,
13739
+ totalTokens: 0,
13740
+ contextWindow: 0,
13741
+ model: ""
13742
+ };
13743
+ function createInitialAgentChatState(options = {}) {
13744
+ return {
13745
+ messages: [],
13746
+ isRunning: false,
13747
+ isLoadingHistory: options.isLoadingHistory ?? false,
13748
+ tokenUsage: EMPTY_TOKEN_USAGE,
13749
+ retryIndicator: null,
13750
+ lastEventId: 0,
13751
+ sessionDone: false,
13752
+ hadDeltas: false,
13753
+ terminal: false,
13754
+ stopped: false,
13755
+ workspaceRefreshKey: 0,
13756
+ browser: null,
13757
+ browserTabs: [],
13758
+ viewMode: options.viewMode ?? "simple",
13759
+ researchSources: [],
13760
+ deviceWait: null
13761
+ };
13762
+ }
13763
+ function applyAgentChatEvent(state, event) {
13764
+ let nextState = {
13765
+ ...state,
13766
+ isLoadingHistory: false,
13767
+ lastEventId: Math.max(state.lastEventId, event.eventId),
13768
+ sessionDone: state.sessionDone || event.type === "session.done"
13769
+ };
13770
+ nextState = applyRetryIndicator(nextState, event);
13771
+ if (state.stopped && isPostTerminalLiveEvent(event.type)) {
13772
+ return nextState;
13773
+ }
13774
+ switch (event.type) {
13775
+ case "user.message":
13776
+ // A project coordinator's follow-up to its thread: shown as the thread reads it, its header saying whose it is.
13777
+ case "coordinator.message":
13778
+ return withMessages(
13779
+ { ...nextState, terminal: false, stopped: false, isRunning: true },
13780
+ applyUserMessage(nextState.messages, event.eventId, event.data)
13781
+ );
13782
+ case "skill.invoked":
13092
13783
  return withMessages(nextState, [
13093
13784
  ...nextState.messages,
13094
13785
  {
@@ -13160,23 +13851,79 @@ function applyAgentChatEvent(state, event) {
13160
13851
  }
13161
13852
  return withMessages(nextState, [...nextState.messages, artifactMessage]);
13162
13853
  }
13163
- case "browser.provisioned":
13164
- return applyBrowserEvent(nextState, event, {
13854
+ case "browser.provisioned": {
13855
+ const tab = computerTab(event);
13856
+ if (tab === null) {
13857
+ return applyBrowserEvent(nextState, event, {
13858
+ status: "live",
13859
+ controlOwner: null
13860
+ });
13861
+ }
13862
+ const browserTabs = [...nextState.browserTabs.filter((open) => open !== tab), tab];
13863
+ if (nextState.browserTabs.length > 0) {
13864
+ const there = nextState.browser?.computer === true && nextState.browser.status !== "unavailable";
13865
+ return {
13866
+ ...nextState,
13867
+ browserTabs,
13868
+ browser: there ? nextState.browser : { status: "live", controlOwner: null, computer: true }
13869
+ };
13870
+ }
13871
+ return applyBrowserEvent({ ...nextState, browserTabs }, event, {
13165
13872
  status: "live",
13166
- controlOwner: null
13873
+ controlOwner: null,
13874
+ computer: true
13167
13875
  });
13876
+ }
13877
+ case "browser.unavailable": {
13878
+ const tab = computerTab(event);
13879
+ const browserTabs = tab === null ? [] : nextState.browserTabs.filter((open) => open !== tab);
13880
+ if (browserTabs.length > 0) return { ...nextState, browserTabs };
13881
+ const none = {
13882
+ ...nextState,
13883
+ browserTabs,
13884
+ browser: { status: "unavailable", controlOwner: null, computer: true }
13885
+ };
13886
+ if (saidLastOfBrowser(nextState.messages) === NO_BROWSER) return none;
13887
+ return withMessages(none, [...none.messages, browserMarker(event)]);
13888
+ }
13889
+ // A take-over on the user's computer is told as the cloud's is: the browser stays the computer's.
13168
13890
  case "browser.control_granted":
13169
13891
  return applyBrowserEvent(nextState, event, {
13170
13892
  status: "user-control",
13171
- controlOwner: stringValue2(event.data.owner_user_id) || null
13893
+ controlOwner: stringValue2(event.data.owner_user_id) || null,
13894
+ ...onComputer(event)
13172
13895
  });
13173
13896
  case "browser.control_returned":
13174
13897
  return applyBrowserEvent(nextState, event, {
13175
13898
  status: "live",
13176
- controlOwner: null
13899
+ controlOwner: null,
13900
+ ...onComputer(event)
13177
13901
  });
13178
- case "browser.destroyed":
13179
- return applyBrowserEvent(nextState, event, null);
13902
+ case "browser.destroyed": {
13903
+ const tab = computerTab(event);
13904
+ const browserTabs = tab === null ? [] : nextState.browserTabs.filter((open) => open !== tab);
13905
+ if (browserTabs.length > 0) return { ...nextState, browserTabs };
13906
+ return applyBrowserEvent({ ...nextState, browserTabs }, event, null);
13907
+ }
13908
+ case "device.waiting":
13909
+ return {
13910
+ ...nextState,
13911
+ deviceWait: {
13912
+ deviceId: stringValue2(event.data.device_id),
13913
+ deviceName: stringValue2(event.data.device_name),
13914
+ reason: stringValue2(event.data.reason) || "offline",
13915
+ folder: stringValue2(event.data.folder)
13916
+ }
13917
+ };
13918
+ case "device.resumed":
13919
+ return { ...nextState, deviceWait: null };
13920
+ case "worker.spawned":
13921
+ return withMessages(nextState, applyWorkerSpawned(nextState.messages, event));
13922
+ case "worker.complete":
13923
+ case "worker.failed":
13924
+ return withMessages(nextState, applyWorkerReport(nextState.messages, event));
13925
+ case "thread.proposed":
13926
+ return withMessages(nextState, [...nextState.messages, proposalMessage(event)]);
13180
13927
  case "llm.delta":
13181
13928
  return applyLlmDelta(nextState, event);
13182
13929
  case "llm.response":
@@ -13198,7 +13945,7 @@ function applyAgentChatEvent(state, event) {
13198
13945
  return collectResearchSource(withResult, toolName, event.data);
13199
13946
  }
13200
13947
  case "harness.wake":
13201
- return { ...nextState, terminal: false, isRunning: true };
13948
+ return { ...nextState, terminal: false, isRunning: true, deviceWait: null };
13202
13949
  case "llm.request":
13203
13950
  return {
13204
13951
  ...nextState,
@@ -13227,7 +13974,7 @@ function applyAgentChatEvent(state, event) {
13227
13974
  if (event.data.strategy === "clear") {
13228
13975
  return {
13229
13976
  ...nextState,
13230
- messages: [],
13977
+ messages: typedBehindTheClear(nextState.messages, event.data.answers),
13231
13978
  tokenUsage: EMPTY_TOKEN_USAGE
13232
13979
  };
13233
13980
  }
@@ -13319,6 +14066,17 @@ function applyRetryIndicator(state, event) {
13319
14066
  return state;
13320
14067
  }
13321
14068
  }
14069
+ function typedBehindTheClear(messages, answers) {
14070
+ if (typeof answers !== "number") return [];
14071
+ let first = messages.length;
14072
+ while (first > 0) {
14073
+ const message = messages[first - 1];
14074
+ const typedAt = /^evt-(\d+)$/.exec(message.id);
14075
+ if (message.role !== "user" || !typedAt || Number(typedAt[1]) <= answers) break;
14076
+ first -= 1;
14077
+ }
14078
+ return messages.slice(first);
14079
+ }
13322
14080
  function applyUserMessage(messages, eventId, data) {
13323
14081
  const content = stringValue2(data.content);
13324
14082
  const images = parseUserMessageImages(data.images);
@@ -13384,6 +14142,22 @@ function parseUserMessageAttachments(raw) {
13384
14142
  }
13385
14143
  return out.length > 0 ? out : void 0;
13386
14144
  }
14145
+ function onComputer(event) {
14146
+ return event.data.computer === true ? { computer: true } : {};
14147
+ }
14148
+ function computerTab(event) {
14149
+ return event.data.computer === true ? stringValue2(event.data.session_id) : null;
14150
+ }
14151
+ var NO_BROWSER = "No supported browser on the chat's computer.";
14152
+ function saidLastOfBrowser(messages) {
14153
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
14154
+ const message = messages[index];
14155
+ if (message.systemKind === "browser_marker" || message.systemKind === "browser_marker_warning") {
14156
+ return message.content;
14157
+ }
14158
+ }
14159
+ return void 0;
14160
+ }
13387
14161
  function applyBrowserEvent(state, event, browser) {
13388
14162
  return withMessages(
13389
14163
  { ...state, browser },
@@ -13407,6 +14181,10 @@ function browserMarker(event) {
13407
14181
  "browser.destroyed": {
13408
14182
  content: "Browser closed.",
13409
14183
  warning: false
14184
+ },
14185
+ "browser.unavailable": {
14186
+ content: NO_BROWSER,
14187
+ warning: true
13410
14188
  }
13411
14189
  };
13412
14190
  const label = labels[event.type] ?? {
@@ -13654,6 +14432,67 @@ function applyTurnSummary(state, event) {
13654
14432
  };
13655
14433
  return { ...state, messages };
13656
14434
  }
14435
+ function workerMessage(worker) {
14436
+ return {
14437
+ id: `worker-${worker.id}`,
14438
+ role: "system",
14439
+ content: worker.title ?? worker.goal,
14440
+ createdAt: /* @__PURE__ */ new Date(),
14441
+ status: "complete",
14442
+ systemKind: "worker",
14443
+ worker
14444
+ };
14445
+ }
14446
+ function applyWorkerSpawned(messages, event) {
14447
+ const id = stringValue2(event.data.worker_id);
14448
+ const proposalId = stringValue2(event.data.proposal_id);
14449
+ const key = stringValue2(event.data.key);
14450
+ const marked = proposalId && key ? messages.map((message) => message.proposal?.proposalId === proposalId ? { ...message, proposal: { ...message.proposal, started: { ...message.proposal.started, [key]: id } } } : message) : messages;
14451
+ if (marked.some((message) => message.worker?.id === id)) return marked;
14452
+ return [...marked, workerMessage({
14453
+ id,
14454
+ title: stringValue2(event.data.title) || null,
14455
+ goal: stringValue2(event.data.goal),
14456
+ state: "working",
14457
+ report: null,
14458
+ files: []
14459
+ })];
14460
+ }
14461
+ function applyWorkerReport(messages, event) {
14462
+ const id = stringValue2(event.data.worker_id);
14463
+ const failed = event.type === "worker.failed";
14464
+ const index = messages.findIndex((message) => message.worker?.id === id);
14465
+ if (index === -1) return messages;
14466
+ const worker = messages[index].worker;
14467
+ const reported = {
14468
+ ...worker,
14469
+ state: failed ? "failed" : "reported",
14470
+ // A mission worker's result can carry the loop's control markup, which never reaches the UI.
14471
+ report: stripMissionControlMarkup(stringValue2(failed ? event.data.error : event.data.result)) || null,
14472
+ // A card lists files, as a thread's row does: not the URLs and commands a turn names.
14473
+ files: failed ? worker.files : parseTurnArtifacts(event.data.files).filter((file) => file.kind === "file" || file.kind === "artifact")
14474
+ };
14475
+ const next = [...messages];
14476
+ next[index] = { ...next[index], worker: reported };
14477
+ return next;
14478
+ }
14479
+ function proposalMessage(event) {
14480
+ const threads = (Array.isArray(event.data.threads) ? event.data.threads : []).filter((thread) => !!thread && typeof thread === "object").map((thread) => ({
14481
+ key: stringValue2(thread.key),
14482
+ title: stringValue2(thread.title),
14483
+ goal: stringValue2(thread.goal),
14484
+ where: thread.where === "device" ? "device" : "cloud"
14485
+ }));
14486
+ return {
14487
+ id: `proposal-${stringValue2(event.data.proposal_id)}`,
14488
+ role: "system",
14489
+ content: threads.map((thread) => thread.title).join(", "),
14490
+ createdAt: /* @__PURE__ */ new Date(),
14491
+ status: "complete",
14492
+ systemKind: "thread_proposal",
14493
+ proposal: { proposalId: stringValue2(event.data.proposal_id), threads, started: {} }
14494
+ };
14495
+ }
13657
14496
  function parseTurnArtifacts(raw) {
13658
14497
  if (!Array.isArray(raw)) return [];
13659
14498
  const out = [];
@@ -13756,7 +14595,7 @@ function applyTerminalEvent(state, event) {
13756
14595
  status: event.type === "session.fail" ? "error" : "complete",
13757
14596
  errorInfo: errorInfo ?? doneMsg.errorInfo
13758
14597
  };
13759
- } else if (event.type === "session.fail" && errorInfo) {
14598
+ } else if (errorInfo) {
13760
14599
  messages.push({
13761
14600
  id: `error-${event.eventId}`,
13762
14601
  role: "system",
@@ -13775,7 +14614,7 @@ function applyTerminalEvent(state, event) {
13775
14614
  };
13776
14615
  }
13777
14616
  function buildErrorInfo(event) {
13778
- if (event.type !== "session.fail" || typeof event.data.error_category !== "string") {
14617
+ if (event.type !== "session.fail" && event.type !== "session.pause" || typeof event.data.error_category !== "string") {
13779
14618
  return void 0;
13780
14619
  }
13781
14620
  const detail = stringValue2(event.data.error_detail) || stringValue2(event.data.error);
@@ -14523,7 +15362,8 @@ function useAgentChatRuntime({
14523
15362
  ...prev,
14524
15363
  browser: browserState ? {
14525
15364
  status: browserState.status,
14526
- controlOwner: browserState.controlOwner ?? null
15365
+ controlOwner: browserState.controlOwner ?? null,
15366
+ ...browserState.computer ? { computer: true } : {}
14527
15367
  } : null
14528
15368
  }));
14529
15369
  }).catch(() => void 0);
@@ -17018,17 +17858,21 @@ function saveDoc(adapter, sessionId, doc) {
17018
17858
  file,
17019
17859
  directory: CANVAS_DIR
17020
17860
  });
17861
+ return true;
17021
17862
  } catch {
17863
+ return false;
17022
17864
  }
17023
17865
  })();
17024
17866
  inFlightSaves.set(sessionId, done);
17025
- return done.then(() => {
17867
+ return done.then((saved) => {
17026
17868
  if (inFlightSaves.get(sessionId) === done) {
17027
17869
  inFlightSaves.delete(sessionId);
17028
17870
  }
17871
+ return saved;
17029
17872
  });
17030
17873
  }
17031
17874
  function useDebouncedSave(adapter, sessionId, doc, delayMs = SAVE_DEBOUNCE_MS) {
17875
+ const [unsaved, setUnsaved] = useState(false);
17032
17876
  const latest = useRef(doc);
17033
17877
  latest.current = doc;
17034
17878
  const dirty = useRef(false);
@@ -17040,7 +17884,7 @@ function useDebouncedSave(adapter, sessionId, doc, delayMs = SAVE_DEBOUNCE_MS) {
17040
17884
  }
17041
17885
  dirty.current = true;
17042
17886
  const timer = setTimeout(() => {
17043
- void saveDoc(adapter, sessionId, latest.current);
17887
+ void saveDoc(adapter, sessionId, latest.current).then((saved) => setUnsaved(!saved));
17044
17888
  }, delayMs);
17045
17889
  return () => clearTimeout(timer);
17046
17890
  }, [adapter, sessionId, doc, delayMs]);
@@ -17050,6 +17894,7 @@ function useDebouncedSave(adapter, sessionId, doc, delayMs = SAVE_DEBOUNCE_MS) {
17050
17894
  void saveDoc(adapter, sessionId, latest.current);
17051
17895
  };
17052
17896
  }, [adapter, sessionId]);
17897
+ return unsaved;
17053
17898
  }
17054
17899
  function shouldReloadCanvas(previous, next, opts = {}) {
17055
17900
  if (!next) return false;
@@ -17358,7 +18203,7 @@ function WhiteboardSurface({
17358
18203
  return next;
17359
18204
  });
17360
18205
  }, [runtime.messages, canvasReady, resolveDraw]);
17361
- useDebouncedSave(adapter, canvasReady ? sessionId : null, doc);
18206
+ const unsaved = useDebouncedSave(adapter, canvasReady ? sessionId : null, doc);
17362
18207
  const hasAnswer = useMemo(
17363
18208
  () => runtime.messages.some((m) => m.role === "assistant"),
17364
18209
  [runtime.messages]
@@ -17971,6 +18816,7 @@ function WhiteboardSurface({
17971
18816
  /* @__PURE__ */ jsx(Spinner, { className: "size-4 shrink-0" }),
17972
18817
  /* @__PURE__ */ jsx("span", { className: "truncate", children: progress ?? (askMode === "deep" ? "Working it out\u2026" : "Thinking\u2026") })
17973
18818
  ] }) }) }) : null,
18819
+ unsaved ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute right-2 top-2 z-10", children: /* @__PURE__ */ jsx("span", { role: "status", className: "rounded-full border bg-background px-3 py-1 text-xs text-muted-foreground shadow-sm", children: "Your last change wasn't saved." }) }) : null,
17974
18820
  restoringVisible ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center bg-black/10", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-3 rounded-full border bg-background px-6 py-3 text-sm font-medium text-muted-foreground shadow-md", children: [
17975
18821
  /* @__PURE__ */ jsx(Spinner, { className: "size-5" }),
17976
18822
  "Restoring board\u2026"
@@ -18709,7 +19555,38 @@ function FileTreeFile({
18709
19555
  }
18710
19556
  );
18711
19557
  }
19558
+ var NONE = {};
19559
+ var chats = /* @__PURE__ */ new Map();
19560
+ var listeners = /* @__PURE__ */ new Set();
19561
+ function changed() {
19562
+ for (const listener of listeners) listener();
19563
+ }
19564
+ function changeChatFiles(chat, changes) {
19565
+ chats.set(chat, { ...chats.get(chat), ...changes });
19566
+ changed();
19567
+ }
19568
+ function leaveChatFiles(chat) {
19569
+ const was = chats.get(chat);
19570
+ if (!was) return;
19571
+ const { entries: _tree, truncated: _cut, ...kept } = was;
19572
+ chats.set(chat, was.deleting ? kept : { ...kept, deleteTarget: null });
19573
+ changed();
19574
+ }
19575
+ function forgetChatFiles() {
19576
+ chats.clear();
19577
+ changed();
19578
+ }
19579
+ function subscribe2(listener) {
19580
+ listeners.add(listener);
19581
+ return () => {
19582
+ listeners.delete(listener);
19583
+ };
19584
+ }
19585
+ function useChatFiles(chat) {
19586
+ return useSyncExternalStore(subscribe2, () => chat === null ? NONE : chats.get(chat) ?? NONE);
19587
+ }
18712
19588
  var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
19589
+ var NO_ENTRIES = [];
18713
19590
  function collectExpandedPaths(entries, depth = 0) {
18714
19591
  const paths = [];
18715
19592
  for (const entry of entries) {
@@ -18823,43 +19700,69 @@ function WorkspacePanel({
18823
19700
  disabled = false
18824
19701
  }) {
18825
19702
  const fileInputRef = useRef(null);
18826
- const [entries, setEntries] = useState([]);
18827
- const [treeLoading, setTreeLoading] = useState(false);
18828
- const [treeError, setTreeError] = useState(null);
18829
- const [notice, setNotice] = useState(null);
18830
- const [uploading, setUploading] = useState(false);
18831
- const [deleteTarget, setDeleteTarget] = useState(null);
19703
+ const {
19704
+ entries = NO_ENTRIES,
19705
+ truncated = false,
19706
+ loading = false,
19707
+ error = null,
19708
+ waiting = null,
19709
+ notice = null,
19710
+ uploading = false,
19711
+ deleteTarget = null,
19712
+ deleting = false
19713
+ } = useChatFiles(sessionId);
18832
19714
  const [expandedPaths, setExpandedPaths] = useState(/* @__PURE__ */ new Set());
18833
19715
  const sessionIdRef = useRef(sessionId);
19716
+ const mountedRef = useRef(true);
19717
+ const readingRef = useRef(null);
18834
19718
  sessionIdRef.current = sessionId;
19719
+ const stopReading = useCallback(() => {
19720
+ const was = readingRef.current;
19721
+ readingRef.current = null;
19722
+ if (!was) return;
19723
+ was.controller.abort();
19724
+ changeChatFiles(was.chat, { loading: false, waiting: null });
19725
+ }, []);
19726
+ useEffect(() => {
19727
+ mountedRef.current = true;
19728
+ return () => {
19729
+ mountedRef.current = false;
19730
+ stopReading();
19731
+ };
19732
+ }, [stopReading]);
19733
+ useEffect(() => () => {
19734
+ if (sessionId) leaveChatFiles(sessionId);
19735
+ }, [sessionId]);
18835
19736
  const fetchTree = useCallback(async () => {
18836
- if (!sessionId) {
18837
- setEntries([]);
18838
- setExpandedPaths(/* @__PURE__ */ new Set());
18839
- setTreeLoading(false);
18840
- setTreeError(null);
18841
- return;
18842
- }
18843
- const requestedSessionId = sessionId;
18844
- setTreeLoading(true);
18845
- setTreeError(null);
19737
+ if (!mountedRef.current || sessionIdRef.current !== sessionId) return;
19738
+ stopReading();
19739
+ if (!sessionId) return;
19740
+ const chat = sessionId;
19741
+ const reading = { chat, controller: new AbortController() };
19742
+ readingRef.current = reading;
19743
+ const current = () => readingRef.current === reading;
19744
+ changeChatFiles(chat, { loading: true, waiting: null, error: null });
18846
19745
  try {
18847
19746
  const tree = await adapter.getWorkspaceTree({
18848
- sessionId: requestedSessionId
19747
+ sessionId: chat,
19748
+ signal: reading.controller.signal,
19749
+ onWaiting: (said) => {
19750
+ if (current()) changeChatFiles(chat, { waiting: said });
19751
+ }
18849
19752
  });
18850
- if (sessionIdRef.current !== requestedSessionId) return;
18851
- setEntries(tree.entries);
19753
+ if (!current()) return;
19754
+ changeChatFiles(chat, { entries: tree.entries, truncated: tree.truncated });
18852
19755
  setExpandedPaths(new Set(collectExpandedPaths(tree.entries)));
18853
- } catch (error) {
18854
- if (sessionIdRef.current !== requestedSessionId) return;
18855
- setEntries([]);
18856
- setTreeError(error.message);
19756
+ } catch (error2) {
19757
+ if (!current()) return;
19758
+ changeChatFiles(chat, { entries: [], error: error2.message });
18857
19759
  } finally {
18858
- if (sessionIdRef.current === requestedSessionId) {
18859
- setTreeLoading(false);
19760
+ if (current()) {
19761
+ readingRef.current = null;
19762
+ changeChatFiles(chat, { loading: false, waiting: null });
18860
19763
  }
18861
19764
  }
18862
- }, [adapter, sessionId]);
19765
+ }, [adapter, sessionId, stopReading]);
18863
19766
  useEffect(() => {
18864
19767
  void fetchTree();
18865
19768
  }, [fetchTree]);
@@ -18902,23 +19805,24 @@ function WorkspacePanel({
18902
19805
  const handleUpload = useCallback(
18903
19806
  async (files) => {
18904
19807
  if (disabled || !sessionId || files.length === 0) return;
18905
- setUploading(true);
18906
- setNotice(null);
19808
+ const chat = sessionId;
19809
+ changeChatFiles(chat, { uploading: true, notice: null });
18907
19810
  try {
18908
19811
  for (const uploadedFile of Array.from(files)) {
18909
19812
  await adapter.uploadWorkspaceFile({
18910
- sessionId,
18911
- file: uploadedFile
19813
+ sessionId: chat,
19814
+ file: uploadedFile,
19815
+ onWaiting: (said) => changeChatFiles(chat, { notice: said })
18912
19816
  });
18913
19817
  }
18914
- setNotice(
18915
- files.length === 1 ? `Uploaded ${files[0]?.name ?? "file"}` : `Uploaded ${files.length} files`
18916
- );
19818
+ changeChatFiles(chat, {
19819
+ notice: files.length === 1 ? `Uploaded ${files[0]?.name ?? "file"}` : `Uploaded ${files.length} files`
19820
+ });
18917
19821
  await fetchTree();
18918
- } catch (error) {
18919
- setNotice(error.message);
19822
+ } catch (error2) {
19823
+ changeChatFiles(chat, { notice: error2.message });
18920
19824
  } finally {
18921
- setUploading(false);
19825
+ changeChatFiles(chat, { uploading: false });
18922
19826
  if (fileInputRef.current) fileInputRef.current.value = "";
18923
19827
  }
18924
19828
  },
@@ -18927,17 +19831,23 @@ function WorkspacePanel({
18927
19831
  const handleDelete = useCallback(
18928
19832
  async (path) => {
18929
19833
  if (disabled || !sessionId) return;
19834
+ const chat = sessionId;
19835
+ changeChatFiles(chat, { deleting: true });
18930
19836
  try {
18931
- await adapter.deleteWorkspaceFile({ sessionId, path });
18932
- if (selectedPath === path) {
19837
+ await adapter.deleteWorkspaceFile({
19838
+ sessionId: chat,
19839
+ path,
19840
+ onWaiting: (said) => changeChatFiles(chat, { notice: said })
19841
+ });
19842
+ if (mountedRef.current && sessionIdRef.current === chat && selectedPath === path) {
18933
19843
  onSelectedPathChange(null);
18934
19844
  }
18935
- setNotice(`Deleted ${path.split("/").pop() ?? path}`);
19845
+ changeChatFiles(chat, { notice: `Deleted ${path.split("/").pop() ?? path}` });
18936
19846
  await fetchTree();
18937
- } catch (error) {
18938
- setNotice(error.message);
19847
+ } catch (error2) {
19848
+ changeChatFiles(chat, { notice: error2.message });
18939
19849
  } finally {
18940
- setDeleteTarget(null);
19850
+ changeChatFiles(chat, { deleteTarget: null, deleting: false });
18941
19851
  }
18942
19852
  },
18943
19853
  [
@@ -18995,12 +19905,12 @@ function WorkspacePanel({
18995
19905
  variant: "ghost",
18996
19906
  size: "icon-sm",
18997
19907
  onClick: () => void fetchTree(),
18998
- disabled: !sessionId || treeLoading,
19908
+ disabled: !sessionId || loading,
18999
19909
  "aria-label": "Refresh workspace",
19000
19910
  children: /* @__PURE__ */ jsx(
19001
19911
  RefreshCwIcon,
19002
19912
  {
19003
- className: cn("size-4", treeLoading && "animate-spin")
19913
+ className: cn("size-4", loading && "animate-spin")
19004
19914
  }
19005
19915
  )
19006
19916
  }
@@ -19008,9 +19918,26 @@ function WorkspacePanel({
19008
19918
  /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: "Refresh" })
19009
19919
  ] })
19010
19920
  ] }),
19011
- notice && /* @__PURE__ */ jsx("div", { className: "border-b border-line px-3 py-2 text-xs text-muted-foreground", children: notice }),
19921
+ notice && /* @__PURE__ */ jsx(
19922
+ "div",
19923
+ {
19924
+ "data-testid": "workspace-notice",
19925
+ className: "border-b border-line px-3 py-2 text-xs text-muted-foreground",
19926
+ children: notice
19927
+ }
19928
+ ),
19929
+ truncated && entries.length > 0 && /* @__PURE__ */ jsx("div", { className: "border-b border-line px-3 py-2 text-xs text-muted-foreground", children: "Some files are not shown." }),
19012
19930
  /* @__PURE__ */ jsx(ScrollArea, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxs("div", { className: "px-1 py-1", children: [
19013
- treeLoading && entries.length === 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1 p-2", children: Array.from({ length: 8 }).map((_, index) => /* @__PURE__ */ jsx(
19931
+ waiting && /* @__PURE__ */ jsx(
19932
+ "div",
19933
+ {
19934
+ role: "status",
19935
+ "data-testid": "tree-waiting",
19936
+ className: "px-3 py-2 text-sm text-muted-foreground",
19937
+ children: waiting
19938
+ }
19939
+ ),
19940
+ loading && !waiting && entries.length === 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1 p-2", children: Array.from({ length: 8 }).map((_, index) => /* @__PURE__ */ jsx(
19014
19941
  Skeleton,
19015
19942
  {
19016
19943
  className: "h-5 rounded",
@@ -19020,11 +19947,11 @@ function WorkspacePanel({
19020
19947
  },
19021
19948
  index
19022
19949
  )) }),
19023
- treeError && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-sm text-destructive", children: [
19950
+ error && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-sm text-destructive", children: [
19024
19951
  /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-4 shrink-0" }),
19025
- /* @__PURE__ */ jsx("span", { children: treeError })
19952
+ /* @__PURE__ */ jsx("span", { children: error })
19026
19953
  ] }),
19027
- !treeLoading && !treeError && entries.length === 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-8 text-center text-sm text-faint", children: [
19954
+ !loading && !error && entries.length === 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-8 text-center text-sm text-faint", children: [
19028
19955
  /* @__PURE__ */ jsx("p", { children: "No workspace files" }),
19029
19956
  /* @__PURE__ */ jsxs(
19030
19957
  Button,
@@ -19053,7 +19980,9 @@ function WorkspacePanel({
19053
19980
  RenderEntries,
19054
19981
  {
19055
19982
  entries,
19056
- onDelete: setDeleteTarget,
19983
+ onDelete: (path) => {
19984
+ if (sessionId) changeChatFiles(sessionId, { deleteTarget: path });
19985
+ },
19057
19986
  downloadUrlFor: (path) => sessionId ? adapter.getWorkspaceDownloadUrl({ sessionId, path }) : "#"
19058
19987
  }
19059
19988
  )
@@ -19067,8 +19996,13 @@ function WorkspacePanel({
19067
19996
  title: "Delete file?",
19068
19997
  description: `This will permanently delete ${deleteTarget?.split("/").pop() ?? "this file"} from the workspace.`,
19069
19998
  confirmLabel: "Delete",
19070
- onConfirm: () => deleteTarget ? handleDelete(deleteTarget) : Promise.resolve(),
19071
- onCancel: () => setDeleteTarget(null)
19999
+ loading: deleting,
20000
+ onConfirm: () => {
20001
+ if (deleteTarget) void handleDelete(deleteTarget);
20002
+ },
20003
+ onCancel: () => {
20004
+ if (sessionId) changeChatFiles(sessionId, { deleteTarget: null });
20005
+ }
19072
20006
  }
19073
20007
  )
19074
20008
  ]
@@ -19081,18 +20015,10 @@ function ConfirmDialog2({
19081
20015
  title,
19082
20016
  description,
19083
20017
  confirmLabel,
20018
+ loading,
19084
20019
  onConfirm,
19085
20020
  onCancel
19086
20021
  }) {
19087
- const [loading, setLoading] = useState(false);
19088
- const handleConfirm = async () => {
19089
- setLoading(true);
19090
- try {
19091
- await onConfirm();
19092
- } finally {
19093
- setLoading(false);
19094
- }
19095
- };
19096
20022
  return /* @__PURE__ */ jsx(
19097
20023
  Dialog,
19098
20024
  {
@@ -19112,7 +20038,7 @@ function ConfirmDialog2({
19112
20038
  {
19113
20039
  variant: "destructive",
19114
20040
  disabled: loading,
19115
- onClick: handleConfirm,
20041
+ onClick: onConfirm,
19116
20042
  children: [
19117
20043
  loading && /* @__PURE__ */ jsx(Loader2Icon, { className: "mr-1.5 size-3.5 animate-spin" }),
19118
20044
  confirmLabel
@@ -19126,6 +20052,10 @@ function ConfirmDialog2({
19126
20052
  }
19127
20053
 
19128
20054
  // src/lib/sessions.ts
20055
+ function sessionComputer(config) {
20056
+ const execution = config?.execution;
20057
+ return execution?.kind === "device" && typeof execution.device_name === "string" ? execution.device_name : null;
20058
+ }
19129
20059
  function isScheduledRunSession(session) {
19130
20060
  return Boolean(
19131
20061
  session?.channel === "scheduled" || session?.config?.scheduled_session_id
@@ -19186,7 +20116,11 @@ function AgentChat({
19186
20116
  goalsEnabled = true,
19187
20117
  compressEnabled = true,
19188
20118
  onOpenIntegrations,
19189
- onOpenBilling
20119
+ onOpenBilling,
20120
+ composerFooter,
20121
+ firstMessage,
20122
+ onFirstMessageSent,
20123
+ computerBrowser
19190
20124
  }) {
19191
20125
  const [workspacePath, setWorkspacePath] = useState(null);
19192
20126
  const [previewPath, setPreviewPath] = useState(null);
@@ -19199,6 +20133,9 @@ function AgentChat({
19199
20133
  sessionId,
19200
20134
  onSessionChange
19201
20135
  });
20136
+ const sessionConfig = runtime.session?.config;
20137
+ const projectId = sessionConfig?.workstream_role === "coordinator" && typeof sessionConfig.workstream_id === "string" ? sessionConfig.workstream_id : null;
20138
+ const threadRows = useProjectThreads(adapter, projectId);
19202
20139
  useEffect(() => {
19203
20140
  setOpenPane(null);
19204
20141
  setFilesOpen(false);
@@ -19226,13 +20163,14 @@ function AgentChat({
19226
20163
  const browserState = runtime.state.browser;
19227
20164
  const browserAvailable = browserState !== null && browserState.status !== "closed" && !!sessionId;
19228
20165
  const browserVisible = browserAvailable && openPane === "browser";
20166
+ const browserOnComputer = browserState?.computer === true;
19229
20167
  const workspaceAvailable = !!sessionId;
19230
20168
  const filePreviewVisible = workspaceAvailable && previewPath !== null && openPane === "file";
19231
20169
  const rightStackVisible = browserVisible || filePreviewVisible;
19232
20170
  const browserPreview = useBrowserPreview({
19233
20171
  adapter,
19234
20172
  sessionId,
19235
- enabled: browserAvailable
20173
+ enabled: browserAvailable && !browserOnComputer
19236
20174
  });
19237
20175
  const browserRunning = browserAvailable;
19238
20176
  useEffect(() => {
@@ -19243,6 +20181,46 @@ function AgentChat({
19243
20181
  useEffect(() => {
19244
20182
  onMessagesChange?.(runtime.messages);
19245
20183
  }, [onMessagesChange, runtime.messages]);
20184
+ const firstSent = useRef(null);
20185
+ const first = useRef({ message: firstMessage ?? null, told: onFirstMessageSent });
20186
+ useEffect(() => {
20187
+ first.current = { message: firstMessage ?? null, told: onFirstMessageSent };
20188
+ });
20189
+ const send = runtime.send;
20190
+ useEffect(() => {
20191
+ if (!firstMessage || firstSent.current === firstMessage.id) return;
20192
+ if (sessionId !== null) {
20193
+ firstSent.current = firstMessage.id;
20194
+ first.current.told?.(firstMessage.id, "Another chat opened before this one was made, so nothing was sent.");
20195
+ return;
20196
+ }
20197
+ if (effectiveDisabled) return;
20198
+ let live = true;
20199
+ queueMicrotask(() => {
20200
+ if (!live || firstSent.current === firstMessage.id) return;
20201
+ firstSent.current = firstMessage.id;
20202
+ send(firstMessage.text).then(
20203
+ () => first.current.told?.(firstMessage.id, null),
20204
+ (error) => first.current.told?.(firstMessage.id, error instanceof Error ? error.message : String(error))
20205
+ );
20206
+ });
20207
+ return () => {
20208
+ live = false;
20209
+ };
20210
+ }, [firstMessage, sessionId, effectiveDisabled, send]);
20211
+ const mounted = useRef(false);
20212
+ useEffect(() => {
20213
+ mounted.current = true;
20214
+ return () => {
20215
+ mounted.current = false;
20216
+ queueMicrotask(() => {
20217
+ const { message, told } = first.current;
20218
+ if (mounted.current || !message || firstSent.current === message.id) return;
20219
+ firstSent.current = message.id;
20220
+ told?.(message.id, "The page left the new chat before it was made, so nothing was sent.");
20221
+ });
20222
+ };
20223
+ }, []);
19246
20224
  const handleOpenFilePreview = useCallback((path) => {
19247
20225
  setWorkspacePath(path);
19248
20226
  setPreviewPath(path);
@@ -19282,7 +20260,11 @@ function AgentChat({
19282
20260
  adapter,
19283
20261
  sessionId,
19284
20262
  onFileSelect: handleFileSelect,
19285
- onOpenBilling
20263
+ onOpenBilling,
20264
+ onOpenSession: onSessionChange,
20265
+ projectId,
20266
+ threadRows,
20267
+ readOnly: disabled === true
19286
20268
  },
19287
20269
  children: /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(
19288
20270
  "section",
@@ -19364,7 +20346,7 @@ function AgentChat({
19364
20346
  isLoadingHistory: runtime.isLoadingHistory,
19365
20347
  onSend: (content, images, attachments) => runtime.send(content, images, attachments),
19366
20348
  onStop: () => runtime.stop(),
19367
- onRetry: runtime.retry,
20349
+ onRetry: disabled ? void 0 : runtime.retry,
19368
20350
  onFileSelect: handleFileSelect,
19369
20351
  disabled: effectiveDisabled,
19370
20352
  disabledReason,
@@ -19382,8 +20364,9 @@ function AgentChat({
19382
20364
  canShowBrowser: browserAvailable,
19383
20365
  canShowWorkspace: workspaceAvailable,
19384
20366
  paneCards: {
19385
- browser: browserRunning ? {
19386
- subtitle: browserState?.controlOwner ? `${browserState.controlOwner} has control` : void 0,
20367
+ // A chat the host only reads offers its browser no card: the card is the way to take it over.
20368
+ browser: browserRunning && !disabled ? {
20369
+ subtitle: browserOnComputer ? browserState?.status === "unavailable" ? "No supported browser on its computer" : "On the chat's computer" : browserState?.controlOwner ? `${browserState.controlOwner} has control` : void 0,
19387
20370
  thumbnail: browserPreview,
19388
20371
  // Show and hide on the same card, like the Files
19389
20372
  // accordion header and the composer's Tools item.
@@ -19419,7 +20402,9 @@ function AgentChat({
19419
20402
  researchSources: runtime.researchSources,
19420
20403
  hideTurnSummary,
19421
20404
  agentId,
19422
- onOpenIntegrations
20405
+ onOpenIntegrations,
20406
+ deviceWait: runtime.state.deviceWait,
20407
+ composerFooter
19423
20408
  }
19424
20409
  )
19425
20410
  }
@@ -19445,13 +20430,21 @@ function AgentChat({
19445
20430
  "data-testid": "browser-panel",
19446
20431
  "data-mobile-view": activeMobilePane,
19447
20432
  className: "min-h-0 h-full w-full overflow-hidden",
19448
- children: /* @__PURE__ */ jsx(
20433
+ children: browserOnComputer ? /* @__PURE__ */ jsx(
20434
+ ComputerBrowserPane,
20435
+ {
20436
+ available: browserState.status !== "unavailable",
20437
+ readOnly: disabled === true,
20438
+ draw: computerBrowser
20439
+ }
20440
+ ) : /* @__PURE__ */ jsx(
19449
20441
  BrowserPane,
19450
20442
  {
19451
20443
  sessionId,
19452
20444
  state: browserState,
19453
20445
  adapter,
19454
- onClose: () => setOpenPane(null)
20446
+ onClose: () => setOpenPane(null),
20447
+ readOnly: disabled === true
19455
20448
  }
19456
20449
  )
19457
20450
  }
@@ -19556,9 +20549,9 @@ var FetchSseEventStream = class {
19556
20549
  queueMicrotask(() => void this.start());
19557
20550
  }
19558
20551
  addEventListener(type, listener) {
19559
- const listeners = this.listeners.get(type) ?? /* @__PURE__ */ new Set();
19560
- listeners.add(listener);
19561
- this.listeners.set(type, listeners);
20552
+ const listeners2 = this.listeners.get(type) ?? /* @__PURE__ */ new Set();
20553
+ listeners2.add(listener);
20554
+ this.listeners.set(type, listeners2);
19562
20555
  }
19563
20556
  close() {
19564
20557
  this.watchdog.stop();
@@ -19635,13 +20628,13 @@ var FetchSseEventStream = class {
19635
20628
  this.dataLines = [];
19636
20629
  return;
19637
20630
  }
19638
- const listeners = this.listeners.get(this.eventType);
20631
+ const listeners2 = this.listeners.get(this.eventType);
19639
20632
  const event = {
19640
20633
  data: this.dataLines.join("\n"),
19641
20634
  lastEventId: this.eventId
19642
20635
  };
19643
- if (listeners) {
19644
- for (const listener of listeners) {
20636
+ if (listeners2) {
20637
+ for (const listener of listeners2) {
19645
20638
  listener(event);
19646
20639
  }
19647
20640
  }
@@ -21038,218 +22031,6 @@ function CardContent({ className, ...props }) {
21038
22031
  }
21039
22032
  );
21040
22033
  }
21041
-
21042
- // src/components/missions/mission-derive.ts
21043
- var ACTIVE_MISSION_STATUSES = /* @__PURE__ */ new Set(["active", "paused"]);
21044
- function isTerminalMissionStatus(status) {
21045
- return !ACTIVE_MISSION_STATUSES.has(status);
21046
- }
21047
- function missionTaskBucket(status) {
21048
- if (status === "done") return "done";
21049
- if (status === "blocked") return "blocked";
21050
- if (status === "failed" || status === "cancelled") {
21051
- return "failed_or_cancelled";
21052
- }
21053
- return "in_flight";
21054
- }
21055
- function groupMissionTasksByBucket(tasks) {
21056
- const buckets = {
21057
- in_flight: [],
21058
- done: [],
21059
- blocked: [],
21060
- failed_or_cancelled: []
21061
- };
21062
- for (const t of tasks) {
21063
- buckets[missionTaskBucket(t.status)].push(t);
21064
- }
21065
- return buckets;
21066
- }
21067
- function deriveMissionWorkerActivityLabel(worker) {
21068
- const kind = worker.latestEventKind;
21069
- if (kind === "tool.call" && worker.latestEventSummary) {
21070
- return `tool: ${truncate4(worker.latestEventSummary, 80)}`;
21071
- }
21072
- if (kind === "llm.response") {
21073
- return "thinking";
21074
- }
21075
- if (kind === "tool.result") {
21076
- return "received tool result";
21077
- }
21078
- if (kind && worker.latestEventSummary) {
21079
- return `${kind}: ${truncate4(worker.latestEventSummary, 60)}`;
21080
- }
21081
- return worker.sessionStatus || "active";
21082
- }
21083
- function truncate4(s, max) {
21084
- if (s.length <= max) return s;
21085
- return `${s.slice(0, max - 1)}\u2026`;
21086
- }
21087
- var MISSION_EVENT_CATEGORIES = [
21088
- "spawn",
21089
- "output",
21090
- "done",
21091
- "verdict",
21092
- "system"
21093
- ];
21094
- function missionEventCategory(type) {
21095
- switch (type) {
21096
- case "worker.spawned":
21097
- return "spawn";
21098
- case "iteration.summary":
21099
- return "output";
21100
- case "worker.complete":
21101
- return "done";
21102
- case "mission.evaluation.end":
21103
- return "verdict";
21104
- default:
21105
- return "system";
21106
- }
21107
- }
21108
- function dataString(data, key) {
21109
- const v = data?.[key];
21110
- return typeof v === "string" && v.trim().length > 0 ? v.trim() : null;
21111
- }
21112
- var CONTROL_MARKUP_RE = /<next_action\b[^>]*(?:\/>|>[\s\S]*?<\/next_action>)/gi;
21113
- function stripMissionControlMarkup(text) {
21114
- return text.replace(CONTROL_MARKUP_RE, "").trim();
21115
- }
21116
- function missionEventSummary(event) {
21117
- return stripMissionControlMarkup(rawMissionEventSummary(event));
21118
- }
21119
- function rawMissionEventSummary(event) {
21120
- const d = event.data ?? null;
21121
- switch (event.type) {
21122
- case "worker.spawned":
21123
- return dataString(d, "goal") ?? "Task spawned";
21124
- case "worker.complete":
21125
- return dataString(d, "result") ?? "Worker complete";
21126
- case "iteration.summary":
21127
- return dataString(d, "summary") ?? "";
21128
- case "mission.evaluation.end": {
21129
- const result = dataString(d, "result");
21130
- const feedback = dataString(d, "feedback") ?? dataString(d, "explanation");
21131
- if (result && feedback) return `${result} \u2014 ${feedback}`;
21132
- return result ?? feedback ?? "Evaluation finished";
21133
- }
21134
- case "task.blocked":
21135
- return dataString(d, "reason") ?? "Task blocked";
21136
- case "task.failed":
21137
- return "Task failed";
21138
- default: {
21139
- const text = dataString(d, "summary") ?? dataString(d, "text") ?? dataString(d, "message") ?? dataString(d, "reason");
21140
- if (text) return text;
21141
- if (!d) return "";
21142
- try {
21143
- return truncate4(JSON.stringify(d), 160);
21144
- } catch {
21145
- return "";
21146
- }
21147
- }
21148
- }
21149
- }
21150
- function missionEventTaskId(event, sessions) {
21151
- return dataString(event.data ?? null, "task_id") ?? sessions[event.sessionId]?.taskId ?? null;
21152
- }
21153
- function missionEventActorLabel(event, sessions) {
21154
- const meta = sessions[event.sessionId];
21155
- if (!meta) return event.sessionId.slice(0, 8);
21156
- if (meta.kind === "coordinator") return "orchestrator";
21157
- return meta.agentDefName ?? "worker";
21158
- }
21159
- function missionTaskBlocks(tasks) {
21160
- const blocks = {};
21161
- for (const t of tasks) {
21162
- for (const parent of t.parentIds) {
21163
- (blocks[parent] ?? (blocks[parent] = [])).push(t.id);
21164
- }
21165
- }
21166
- return blocks;
21167
- }
21168
- function missionTaskTitle(goal, max = 80) {
21169
- const line = goal.split("\n").find((l) => l.trim().length > 0)?.trim() ?? goal.trim();
21170
- return truncate4(line.replace(/\s+/g, " "), max);
21171
- }
21172
- var RAIL_GROUP_DEFS = [
21173
- { key: "running", label: "Running", match: (s) => s === "running" },
21174
- { key: "blocked", label: "Blocked", match: (s) => s === "blocked" },
21175
- {
21176
- key: "queued",
21177
- label: "Queued",
21178
- match: (s) => s === "ready" || s === "todo"
21179
- },
21180
- { key: "done", label: "Done", match: (s) => s === "done" },
21181
- {
21182
- key: "failed",
21183
- label: "Failed / cancelled",
21184
- match: (s) => s === "failed" || s === "cancelled"
21185
- }
21186
- ];
21187
- function missionTaskRailGroups(tasks) {
21188
- const grouped = /* @__PURE__ */ new Set();
21189
- const groups = RAIL_GROUP_DEFS.map((def) => ({
21190
- key: def.key,
21191
- label: def.label,
21192
- tasks: tasks.filter((t) => {
21193
- const hit = def.match(t.status);
21194
- if (hit) grouped.add(t.id);
21195
- return hit;
21196
- })
21197
- }));
21198
- const stray = tasks.filter((t) => !grouped.has(t.id));
21199
- if (stray.length > 0) {
21200
- const queued = groups.find((g) => g.key === "queued");
21201
- if (queued) queued.tasks.push(...stray);
21202
- }
21203
- return groups.filter((g) => g.tasks.length > 0);
21204
- }
21205
- var TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
21206
- function defaultSelectedMissionTaskId(tasks) {
21207
- if (tasks.length === 0) return null;
21208
- const ordered = missionTaskRailGroups(tasks).flatMap((g) => g.tasks);
21209
- const firstLive = ordered.find(
21210
- (t) => !TERMINAL_TASK_STATUSES.has(t.status)
21211
- );
21212
- return (firstLive ?? ordered[0] ?? tasks[0]).id;
21213
- }
21214
- function mergeMissionEvents(existing, incoming) {
21215
- if (incoming.length === 0) return existing;
21216
- const byId = /* @__PURE__ */ new Map();
21217
- for (const e of existing) byId.set(e.id, e);
21218
- for (const e of incoming) byId.set(e.id, e);
21219
- return [...byId.values()].sort((a, b) => a.id - b.id);
21220
- }
21221
- function missionTaskStatusDotClass(status) {
21222
- switch (status) {
21223
- case "running":
21224
- return "bg-primary";
21225
- case "blocked":
21226
- return "bg-amber-500";
21227
- case "done":
21228
- return "bg-emerald-500";
21229
- case "failed":
21230
- return "bg-destructive";
21231
- case "cancelled":
21232
- return "bg-muted-foreground";
21233
- default:
21234
- return "bg-foreground/30";
21235
- }
21236
- }
21237
- function missionWorkerTaskCounts(worker, tasks) {
21238
- if (worker.kind !== "task") return null;
21239
- const mine = tasks.filter((t) => t.agentDefName === worker.agentDefName);
21240
- return {
21241
- completed: mine.filter((t) => t.status === "done").length,
21242
- inFlight: mine.filter(
21243
- (t) => t.status === "running" || t.status === "blocked"
21244
- ).length
21245
- };
21246
- }
21247
- function formatMissionTimestamp(iso) {
21248
- if (!iso) return "";
21249
- const parsed = new Date(iso);
21250
- if (Number.isNaN(parsed.getTime())) return iso;
21251
- return parsed.toLocaleTimeString();
21252
- }
21253
22034
  var CATEGORY_BADGE_CLASS = {
21254
22035
  spawn: "border-primary/30 bg-primary/10 text-primary",
21255
22036
  output: "border-foreground/20 bg-foreground/5 text-foreground/80",
@@ -21902,7 +22683,7 @@ function MissionWorkersTab({
21902
22683
  return /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-muted-foreground/60", children: "No workers attached to this mission right now." });
21903
22684
  }
21904
22685
  return /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2", children: workers.map((w) => /* @__PURE__ */ jsx(
21905
- WorkerCard,
22686
+ WorkerCard2,
21906
22687
  {
21907
22688
  worker: w,
21908
22689
  tasks,
@@ -21911,7 +22692,7 @@ function MissionWorkersTab({
21911
22692
  w.workerSessionId
21912
22693
  )) });
21913
22694
  }
21914
- function WorkerCard({
22695
+ function WorkerCard2({
21915
22696
  worker,
21916
22697
  tasks,
21917
22698
  onOpenTranscript
@@ -23096,6 +23877,7 @@ function sessionToTreeNode(session) {
23096
23877
  model: session.model,
23097
23878
  messageCount: session.messageCount,
23098
23879
  toolCallCount: session.toolCallCount,
23880
+ computer: sessionComputer(session.config),
23099
23881
  createdAt: session.createdAt ?? timestamp,
23100
23882
  updatedAt: session.updatedAt ?? timestamp
23101
23883
  };
@@ -23122,13 +23904,13 @@ function mergeTreeNodes(groups) {
23122
23904
  }
23123
23905
  function collectDeletedSessionIds(nodes, deletedSessionId) {
23124
23906
  const deletedIds = /* @__PURE__ */ new Set([deletedSessionId]);
23125
- let changed = true;
23126
- while (changed) {
23127
- changed = false;
23907
+ let changed2 = true;
23908
+ while (changed2) {
23909
+ changed2 = false;
23128
23910
  for (const node of nodes) {
23129
23911
  if (node.parentId && deletedIds.has(node.parentId) && !deletedIds.has(node.id)) {
23130
23912
  deletedIds.add(node.id);
23131
- changed = true;
23913
+ changed2 = true;
23132
23914
  }
23133
23915
  }
23134
23916
  }
@@ -23224,7 +24006,15 @@ function TreeNodeRow({
23224
24006
  ) : /* @__PURE__ */ jsx("span", { className: "w-4 h-4 shrink-0" }),
23225
24007
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
23226
24008
  /* @__PURE__ */ jsx("div", { className: "text-sm truncate", children: title }),
23227
- /* @__PURE__ */ jsx("div", { className: "text-xs text-faint truncate", children: subtitle })
24009
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-faint min-w-0", children: [
24010
+ entry.computer && // A chat on a folder of the user's computer, in every list of chats, the browser's too.
24011
+ /* @__PURE__ */ jsxs("span", { "data-testid": "session-computer", title: `On ${entry.computer}`, className: "inline-flex min-w-0 max-w-[50%] items-center gap-1", children: [
24012
+ /* @__PURE__ */ jsx(LaptopIcon, { className: "w-3 h-3 shrink-0", "aria-hidden": "true" }),
24013
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: entry.computer })
24014
+ ] }),
24015
+ entry.computer && subtitle && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xB7" }),
24016
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: subtitle })
24017
+ ] })
23228
24018
  ] }),
23229
24019
  isRunning && canStop && isChildSession && /* @__PURE__ */ jsx(
23230
24020
  "button",
@@ -23321,9 +24111,10 @@ function SessionTreePanel({
23321
24111
  }
23322
24112
  const sessionList = listResult.status === "fulfilled" ? listResult.value : null;
23323
24113
  const sessionTree = treeResult.status === "fulfilled" ? treeResult.value : null;
24114
+ const project = sessionTree?.nodes.some((node) => node.parentId === null && node.runKind === "project");
23324
24115
  let nextNodes = mergeTreeNodes([
23325
24116
  sessionList?.sessions.map(sessionToTreeNode) ?? [],
23326
- sessionTree?.nodes ?? []
24117
+ project ? [] : sessionTree?.nodes ?? []
23327
24118
  ]);
23328
24119
  if (pendingDeleteIds.current.size > 0) {
23329
24120
  const stillReturned = /* @__PURE__ */ new Set();
@@ -23475,6 +24266,6 @@ function SessionTreePanel({
23475
24266
  ] });
23476
24267
  }
23477
24268
 
23478
- export { ACTIVE_MISSION_STATUSES, AgentChat, AgentChatAdapterProvider, AgentWhiteboard, BrandBeam, BrowserShell, CitationText, CodingAgentsPanel, ComposioConnectCard, FetchSseEventStream, InboxPanel, IntegrationsBand, IntegrationsPage, MISSION_EVENT_CATEGORIES, MessageResponse, MissionDashboard, MissionsPanel, ORB_STATE_LABELS, OrbShimmerLabel, ResearchSourcesPanel, SIMPLE_MODE_HIDDEN_TOOLS, SIMPLE_MODE_ORB_OPTIONS, ScheduledWorkPanel, SessionTreePanel, WhiteboardSurface, defaultSelectedMissionTaskId, deriveMissionWorkerActivityLabel, deriveOrbActivity, formatMissionTimestamp, groupMissionTasksByBucket, isHiddenSimpleTool, isTerminalMissionStatus, mergeMissionEvents, messageOrbState, missionEventActorLabel, missionEventCategory, missionEventSummary, missionEventTaskId, missionTaskBlocks, missionTaskBucket, missionTaskRailGroups, missionTaskStatusDotClass, missionTaskTitle, missionWorkerTaskCounts, persistChatViewMode, splitCitations, stripMissionControlMarkup, toolOrbState, useAgentChatAdapterContext, useAgentChatRuntime, useBrowserControl, useChatViewMode, useInboxUnreadCount, useMissionEvents };
24269
+ export { ACTIVE_MISSION_STATUSES, AgentChat, AgentChatAdapterProvider, AgentWhiteboard, BrandBeam, BrowserShell, CitationText, CodingAgentsPanel, ComposioConnectCard, FetchSseEventStream, InboxPanel, IntegrationsBand, IntegrationsPage, MISSION_EVENT_CATEGORIES, MessageResponse, MissionDashboard, MissionsPanel, ORB_STATE_LABELS, OrbShimmerLabel, ResearchSourcesPanel, SIMPLE_MODE_HIDDEN_TOOLS, SIMPLE_MODE_ORB_OPTIONS, ScheduledWorkPanel, SessionTreePanel, WhiteboardSurface, defaultSelectedMissionTaskId, deriveMissionWorkerActivityLabel, deriveOrbActivity, forgetChatFiles, formatMissionTimestamp, groupMissionTasksByBucket, isHiddenSimpleTool, isTerminalMissionStatus, mergeMissionEvents, messageOrbState, missionEventActorLabel, missionEventCategory, missionEventSummary, missionEventTaskId, missionTaskBlocks, missionTaskBucket, missionTaskRailGroups, missionTaskStatusDotClass, missionTaskTitle, missionWorkerTaskCounts, persistChatViewMode, sessionComputer, splitCitations, stripMissionControlMarkup, toolOrbState, useAgentChatAdapterContext, useAgentChatRuntime, useBrowserControl, useChatViewMode, useInboxUnreadCount, useMissionEvents };
23479
24270
  //# sourceMappingURL=index.js.map
23480
24271
  //# sourceMappingURL=index.js.map