@invergent/agent-chat-react 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -209,11 +209,23 @@ interface AgentChatMissionTask {
209
209
  createdAt: string | null;
210
210
  completedAt: string | null;
211
211
  }
212
+ type AgentChatMissionWorkerKind = "task" | "worker" | "delegation";
212
213
  interface AgentChatMissionWorker {
213
- taskId: string;
214
+ /**
215
+ * Which delegation primitive produced this child:
216
+ *
217
+ * - "task" — `spawn_task` (durable Task row, retried by dispatcher)
218
+ * - "worker" — `spawn_worker` (async one-shot, durable session)
219
+ * - "delegation" — `delegate_task` (sync fork-join, ephemeral session)
220
+ *
221
+ * `taskId` / `taskStatus` are non-null only for `kind === "task"` —
222
+ * the other two primitives don't create Task rows.
223
+ */
224
+ kind: AgentChatMissionWorkerKind;
225
+ taskId: string | null;
214
226
  workerSessionId: string;
215
227
  agentDefName: string | null;
216
- taskStatus: string;
228
+ taskStatus: string | null;
217
229
  sessionStatus: string;
218
230
  latestEventId: number | null;
219
231
  latestEventKind: string | null;
package/dist/index.d.ts CHANGED
@@ -209,11 +209,23 @@ interface AgentChatMissionTask {
209
209
  createdAt: string | null;
210
210
  completedAt: string | null;
211
211
  }
212
+ type AgentChatMissionWorkerKind = "task" | "worker" | "delegation";
212
213
  interface AgentChatMissionWorker {
213
- taskId: string;
214
+ /**
215
+ * Which delegation primitive produced this child:
216
+ *
217
+ * - "task" — `spawn_task` (durable Task row, retried by dispatcher)
218
+ * - "worker" — `spawn_worker` (async one-shot, durable session)
219
+ * - "delegation" — `delegate_task` (sync fork-join, ephemeral session)
220
+ *
221
+ * `taskId` / `taskStatus` are non-null only for `kind === "task"` —
222
+ * the other two primitives don't create Task rows.
223
+ */
224
+ kind: AgentChatMissionWorkerKind;
225
+ taskId: string | null;
214
226
  workerSessionId: string;
215
227
  agentDefName: string | null;
216
- taskStatus: string;
228
+ taskStatus: string | null;
217
229
  sessionStatus: string;
218
230
  latestEventId: number | null;
219
231
  latestEventKind: string | null;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { cn } from './chunk-QSC4UIVT.js';
2
2
  import { createContext, memo, useRef, useEffect, useCallback, useMemo, useState, lazy, useContext, Suspense, Children } from 'react';
3
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, ChevronRightIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, Loader2Icon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, CheckIcon, CopyIcon, TerminalIcon, SparklesIcon, ClockIcon, PlusIcon, CloudIcon, HardDriveIcon, PaperclipIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, SearchIcon, GitBranchIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, EyeIcon, GlobeIcon, ListTodoIcon, ArrowUpIcon, XCircleIcon, CheckCircle2Icon, Code, CircleCheckIcon, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
3
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, ChevronRightIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, Loader2Icon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, CheckIcon, CopyIcon, TerminalIcon, SparklesIcon, ClockIcon, PlusIcon, CloudIcon, HardDriveIcon, PaperclipIcon, GlobeIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, SearchIcon, GitBranchIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, EyeIcon, ListTodoIcon, ArrowUpIcon, XCircleIcon, CheckCircle2Icon, Code, CircleCheckIcon, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import { Collapsible as Collapsible$1, Slot, Tooltip as Tooltip$1, Separator as Separator$1, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Popover as Popover$1, Progress as Progress$1, Tabs as Tabs$1 } from 'radix-ui';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -5698,7 +5698,13 @@ function ChatComposerInner({
5698
5698
  disabled = false,
5699
5699
  disabledReason,
5700
5700
  tokenUsage,
5701
- onComposerError
5701
+ onComposerError,
5702
+ showBrowser = false,
5703
+ onToggleBrowser,
5704
+ showWorkspace = false,
5705
+ onToggleWorkspace,
5706
+ canShowBrowser = false,
5707
+ canShowWorkspace = false
5702
5708
  }) {
5703
5709
  const { adapter } = useAgentChatAdapterContext();
5704
5710
  const { textInput, attachments } = usePromptInputController();
@@ -6005,6 +6011,28 @@ function ChatComposerInner({
6005
6011
  }
6006
6012
  )
6007
6013
  ] }),
6014
+ canShowBrowser && onToggleBrowser && /* @__PURE__ */ jsx(
6015
+ PromptInputButton,
6016
+ {
6017
+ "aria-label": showBrowser ? "Hide browser pane" : "Show browser pane",
6018
+ "aria-pressed": showBrowser,
6019
+ onClick: onToggleBrowser,
6020
+ tooltip: showBrowser ? "Hide browser pane" : "Show browser pane",
6021
+ className: showBrowser ? "bg-accent text-foreground" : void 0,
6022
+ children: /* @__PURE__ */ jsx(GlobeIcon, { className: "size-4" })
6023
+ }
6024
+ ),
6025
+ canShowWorkspace && onToggleWorkspace && /* @__PURE__ */ jsx(
6026
+ PromptInputButton,
6027
+ {
6028
+ "aria-label": showWorkspace ? "Hide workspace pane" : "Show workspace pane",
6029
+ "aria-pressed": showWorkspace,
6030
+ onClick: onToggleWorkspace,
6031
+ tooltip: showWorkspace ? "Hide workspace pane" : "Show workspace pane",
6032
+ className: showWorkspace ? "bg-accent text-foreground" : void 0,
6033
+ children: /* @__PURE__ */ jsx(FolderIcon, { className: "size-4" })
6034
+ }
6035
+ ),
6008
6036
  tokenUsage && tokenUsage.contextWindow > 0 && /* @__PURE__ */ jsxs(
6009
6037
  Context,
6010
6038
  {
@@ -7223,7 +7251,13 @@ function ChatThread({
7223
7251
  tokenUsage,
7224
7252
  retryIndicator,
7225
7253
  onRetry,
7226
- onComposerError
7254
+ onComposerError,
7255
+ showBrowser = false,
7256
+ onToggleBrowser,
7257
+ showWorkspace = false,
7258
+ onToggleWorkspace,
7259
+ canShowBrowser = false,
7260
+ canShowWorkspace = false
7227
7261
  }) {
7228
7262
  const groups = useMemo(() => groupMessages(messages), [messages]);
7229
7263
  const artifactFallbacks = useMemo(() => {
@@ -7340,7 +7374,13 @@ function ChatThread({
7340
7374
  disabled,
7341
7375
  disabledReason,
7342
7376
  tokenUsage,
7343
- onComposerError
7377
+ onComposerError,
7378
+ showBrowser,
7379
+ onToggleBrowser,
7380
+ showWorkspace,
7381
+ onToggleWorkspace,
7382
+ canShowBrowser,
7383
+ canShowWorkspace
7344
7384
  }
7345
7385
  )
7346
7386
  ] })
@@ -9274,6 +9314,16 @@ function useAgentChatRuntime({
9274
9314
  }));
9275
9315
  }
9276
9316
  }).catch(() => void 0);
9317
+ void adapter.getBrowserState(sessionId).then((browserState) => {
9318
+ if (sessionIdRef.current !== sessionId) return;
9319
+ setState((prev) => ({
9320
+ ...prev,
9321
+ browser: browserState ? {
9322
+ status: browserState.status,
9323
+ controlOwner: browserState.controlOwner ?? null
9324
+ } : null
9325
+ }));
9326
+ }).catch(() => void 0);
9277
9327
  return () => {
9278
9328
  clearReconnectTimer();
9279
9329
  closeStream();
@@ -9518,6 +9568,8 @@ function AgentChat({
9518
9568
  }) {
9519
9569
  const [workspacePath, setWorkspacePath] = useState(null);
9520
9570
  const [mobileView, setMobileView] = useState("chat");
9571
+ const [showBrowser, setShowBrowser] = useState(true);
9572
+ const [showWorkspace, setShowWorkspace] = useState(true);
9521
9573
  const runtime = useAgentChatRuntime({
9522
9574
  adapter,
9523
9575
  agentId,
@@ -9528,7 +9580,12 @@ function AgentChat({
9528
9580
  const effectiveDisabled = disabled || isReadOnlySession;
9529
9581
  const disabledReason = isReadOnlySession ? "Scheduled run is read-only" : void 0;
9530
9582
  const browserState = runtime.state.browser;
9531
- const hasBrowserPanel = browserState !== null && sessionId;
9583
+ const browserAvailable = browserState !== null && browserState.status !== "closed" && !!sessionId;
9584
+ const browserVisible = browserAvailable && showBrowser;
9585
+ const workspaceAvailable = !!sessionId;
9586
+ const workspaceVisible = workspaceAvailable && showWorkspace;
9587
+ const rightStackVisible = browserVisible || workspaceVisible;
9588
+ const bothPanesVisible = browserVisible && workspaceVisible;
9532
9589
  useEffect(() => {
9533
9590
  onMessagesChange?.(runtime.messages);
9534
9591
  }, [onMessagesChange, runtime.messages]);
@@ -9536,11 +9593,18 @@ function AgentChat({
9536
9593
  (path) => {
9537
9594
  setWorkspacePath(path);
9538
9595
  setMobileView("workspace");
9596
+ setShowWorkspace(true);
9539
9597
  onFileSelect?.(path);
9540
9598
  },
9541
9599
  [onFileSelect]
9542
9600
  );
9543
- const showMobileToggle = Boolean(sessionId);
9601
+ const handleToggleBrowser = useCallback(() => {
9602
+ setShowBrowser((prev) => !prev);
9603
+ }, []);
9604
+ const handleToggleWorkspace = useCallback(() => {
9605
+ setShowWorkspace((prev) => !prev);
9606
+ }, []);
9607
+ const showMobileToggle = rightStackVisible;
9544
9608
  return /* @__PURE__ */ jsx(
9545
9609
  AgentChatAdapterProvider,
9546
9610
  {
@@ -9558,8 +9622,11 @@ function AgentChat({
9558
9622
  // Phone: flex column, tab toggle on top, then either chat or
9559
9623
  // right stack visible based on `data-mobile-view`.
9560
9624
  "flex min-h-0 flex-1 flex-col overflow-hidden bg-background text-sm text-foreground",
9561
- // md+: restore desktop two-pane layout.
9562
- hasBrowserPanel ? "md:relative md:flex-row" : "md:flex-row"
9625
+ // md+: restore desktop two-pane layout when the right stack
9626
+ // is visible. With both panes, absolute positioning lets the
9627
+ // browser/workspace split occupy a fixed width. Without the
9628
+ // right stack, the chat takes the full width.
9629
+ browserVisible ? "md:relative md:flex-row" : "md:flex-row"
9563
9630
  ),
9564
9631
  style: { direction: "ltr", ...RIGHT_STACK_STYLE },
9565
9632
  children: [
@@ -9600,8 +9667,13 @@ function AgentChat({
9600
9667
  // Phone: full width column, hidden when workspace tab active.
9601
9668
  "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
9602
9669
  showMobileToggle && "data-[mobile-view=workspace]:hidden md:flex!",
9603
- // md+: restore desktop positioning.
9604
- hasBrowserPanel ? "md:absolute md:inset-y-0 md:left-0 md:right-(--right-stack-w,440px) md:flex" : "md:relative md:flex-1"
9670
+ // md+: positioning depends on whether the browser/workspace
9671
+ // right stack is laid out. When the browser pane is shown
9672
+ // we pin a fixed-width column on the right and absolutely
9673
+ // position the chat panel beside it. Otherwise the chat
9674
+ // panel just flexes alongside the workspace (or fills the
9675
+ // space when nothing else is visible).
9676
+ browserVisible ? "md:absolute md:inset-y-0 md:left-0 md:right-(--right-stack-w,440px) md:flex" : "md:relative md:flex-1"
9605
9677
  ),
9606
9678
  children: /* @__PURE__ */ jsx(
9607
9679
  ChatThread,
@@ -9618,12 +9690,18 @@ function AgentChat({
9618
9690
  disabledReason,
9619
9691
  tokenUsage: runtime.tokenUsage,
9620
9692
  retryIndicator: runtime.retryIndicator,
9621
- onComposerError
9693
+ onComposerError,
9694
+ showBrowser,
9695
+ onToggleBrowser: handleToggleBrowser,
9696
+ showWorkspace,
9697
+ onToggleWorkspace: handleToggleWorkspace,
9698
+ canShowBrowser: browserAvailable,
9699
+ canShowWorkspace: workspaceAvailable
9622
9700
  }
9623
9701
  )
9624
9702
  }
9625
9703
  ),
9626
- /* @__PURE__ */ jsxs(
9704
+ rightStackVisible && /* @__PURE__ */ jsxs(
9627
9705
  "div",
9628
9706
  {
9629
9707
  "data-testid": "right-stack",
@@ -9632,17 +9710,23 @@ function AgentChat({
9632
9710
  // Phone: full width column, hidden when chat tab active.
9633
9711
  "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden",
9634
9712
  showMobileToggle && "data-[mobile-view=chat]:hidden md:flex!",
9635
- // md+: restore desktop positioning. With browser panel the
9636
- // right stack is absolutely positioned at the configured
9637
- // width; without it the stack flows in the row.
9638
- hasBrowserPanel ? "md:absolute md:inset-y-0 md:right-0 md:w-(--right-stack-w,440px) md:flex-none" : "md:relative md:shrink-0 md:flex-none md:w-(--right-stack-w,440px)"
9713
+ // md+: positioning differs depending on what is inside.
9714
+ // - browser visible (with or without workspace): absolute
9715
+ // right column at the configured width.
9716
+ // - workspace only: relative shrink-0 column letting
9717
+ // WorkspacePanel manage its own width via the resize
9718
+ // handle.
9719
+ browserVisible ? "md:absolute md:inset-y-0 md:right-0 md:w-(--right-stack-w,440px) md:flex-none" : "md:relative md:shrink-0 md:flex-none md:w-auto"
9639
9720
  ),
9640
9721
  children: [
9641
- hasBrowserPanel && /* @__PURE__ */ jsx(
9722
+ browserVisible && /* @__PURE__ */ jsx(
9642
9723
  "div",
9643
9724
  {
9644
9725
  "data-testid": "browser-panel",
9645
- className: "h-1/2 min-h-0 w-full overflow-hidden border-b border-line",
9726
+ className: cn(
9727
+ "min-h-0 w-full overflow-hidden",
9728
+ bothPanesVisible ? "h-1/2 border-b border-line" : "h-full"
9729
+ ),
9646
9730
  children: /* @__PURE__ */ jsx(
9647
9731
  BrowserPane,
9648
9732
  {
@@ -9653,11 +9737,11 @@ function AgentChat({
9653
9737
  )
9654
9738
  }
9655
9739
  ),
9656
- /* @__PURE__ */ jsx(
9740
+ workspaceVisible && /* @__PURE__ */ jsx(
9657
9741
  "div",
9658
9742
  {
9659
9743
  "data-testid": "workspace-panel-frame",
9660
- className: hasBrowserPanel ? "h-1/2 min-h-0 w-full overflow-hidden" : "min-h-0 h-full",
9744
+ className: bothPanesVisible ? "h-1/2 min-h-0 w-full overflow-hidden" : "min-h-0 h-full",
9661
9745
  children: /* @__PURE__ */ jsx(
9662
9746
  WorkspacePanel,
9663
9747
  {
@@ -9667,7 +9751,7 @@ function AgentChat({
9667
9751
  onSelectedPathChange: setWorkspacePath,
9668
9752
  refreshSignal: runtime.workspaceRefreshKey,
9669
9753
  disabled: effectiveDisabled,
9670
- fillParent: Boolean(hasBrowserPanel)
9754
+ fillParent: bothPanesVisible
9671
9755
  }
9672
9756
  )
9673
9757
  }
@@ -10470,6 +10554,16 @@ function truncate2(s, max) {
10470
10554
  return `${s.slice(0, max - 1)}\u2026`;
10471
10555
  }
10472
10556
  var DEFAULT_POLL_INTERVAL_MS = 5e3;
10557
+ var MISSION_WORKER_KIND_BADGE_CLASS = {
10558
+ task: "border-primary/40 bg-primary/10 text-primary uppercase tracking-wide text-[10px]",
10559
+ worker: "border-foreground/30 bg-foreground/5 text-foreground/80 uppercase tracking-wide text-[10px]",
10560
+ delegation: "border-foreground/20 bg-foreground/[0.03] text-foreground/60 uppercase tracking-wide text-[10px]"
10561
+ };
10562
+ var MISSION_WORKER_KIND_TOOLTIP = {
10563
+ task: "Durable Task row created by spawn_task. Retried by the dispatcher; survives across coordinator wakes.",
10564
+ worker: "Async one-shot session spawned by spawn_worker. Durable session, no retry/DAG.",
10565
+ delegation: "Sync fork-join child spawned by delegate_task. Coordinator's wake blocked until it finished."
10566
+ };
10473
10567
  var INITIAL_STATE = {
10474
10568
  mission: null,
10475
10569
  tasks: [],
@@ -10556,7 +10650,11 @@ function MissionDashboard({
10556
10650
  [state.tasks]
10557
10651
  );
10558
10652
  const runningWorkerCount = useMemo(
10559
- () => state.workers.filter((w) => w.taskStatus === "running").length,
10653
+ // "running" means the child is still consuming compute right now.
10654
+ // For task-backed children that's ``taskStatus === "running"``; for
10655
+ // spawn_worker / delegate_task direct children the Task row doesn't
10656
+ // exist, so fall back to the session-level signal.
10657
+ () => state.workers.filter((w) => w.kind === "task" ? w.taskStatus === "running" : w.sessionStatus === "active").length,
10560
10658
  [state.workers]
10561
10659
  );
10562
10660
  if (state.loading && !state.mission) {
@@ -10619,7 +10717,7 @@ function MissionDashboard({
10619
10717
  }
10620
10718
  };
10621
10719
  const statusVariant = mission.status === "satisfied" ? "default" : mission.status === "failed" || mission.status === "cancelled" ? "destructive" : "secondary";
10622
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-4 sm:gap-6 sm:p-6 max-w-6xl mx-auto", children: [
10720
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-4 sm:gap-6 sm:p-6", children: [
10623
10721
  /* @__PURE__ */ jsxs(Card, { children: [
10624
10722
  /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-stretch gap-4 sm:flex-row sm:items-start sm:justify-between", children: [
10625
10723
  /* @__PURE__ */ jsxs("div", { className: "space-y-1 min-w-0 flex-1", children: [
@@ -10767,7 +10865,16 @@ function MissionDashboard({
10767
10865
  children: [
10768
10866
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
10769
10867
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
10770
- /* @__PURE__ */ jsx(Badge, { className: "text-foreground/70", children: w.taskStatus }),
10868
+ /* @__PURE__ */ jsx(
10869
+ Badge,
10870
+ {
10871
+ variant: "outline",
10872
+ className: MISSION_WORKER_KIND_BADGE_CLASS[w.kind],
10873
+ title: MISSION_WORKER_KIND_TOOLTIP[w.kind],
10874
+ children: w.kind
10875
+ }
10876
+ ),
10877
+ /* @__PURE__ */ jsx(Badge, { className: "text-foreground/70", children: w.kind === "task" ? w.taskStatus ?? "\u2014" : w.sessionStatus }),
10771
10878
  w.agentDefName ? /* @__PURE__ */ jsx("span", { className: "text-xs text-foreground/70", children: w.agentDefName }) : null
10772
10879
  ] }),
10773
10880
  /* @__PURE__ */ jsx("div", { className: "text-sm wrap-break-word", children: deriveMissionWorkerActivityLabel(w) }),