@invergent/agent-chat-react 1.6.7 → 1.6.8

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
@@ -335,6 +335,16 @@ interface AgentChatAdapter {
335
335
  eventId?: number;
336
336
  status?: string;
337
337
  }>;
338
+ defineOutcome?(input: {
339
+ sessionId: string;
340
+ description: string;
341
+ rubric?: string;
342
+ maxIterations?: number;
343
+ }): Promise<{
344
+ eventId?: number;
345
+ outcomeId?: string;
346
+ processedAt?: string;
347
+ }>;
338
348
  pauseSession(input: {
339
349
  sessionId: string;
340
350
  }): Promise<void>;
package/dist/index.d.ts CHANGED
@@ -335,6 +335,16 @@ interface AgentChatAdapter {
335
335
  eventId?: number;
336
336
  status?: string;
337
337
  }>;
338
+ defineOutcome?(input: {
339
+ sessionId: string;
340
+ description: string;
341
+ rubric?: string;
342
+ maxIterations?: number;
343
+ }): Promise<{
344
+ eventId?: number;
345
+ outcomeId?: string;
346
+ processedAt?: string;
347
+ }>;
338
348
  pauseSession(input: {
339
349
  sessionId: string;
340
350
  }): Promise<void>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { cn } from './chunk-QSC4UIVT.js';
2
2
  import { createContext, memo, useRef, useState, useEffect, useCallback, useMemo, lazy, useContext, Suspense, Children } from 'react';
3
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, EyeIcon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, InboxIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
3
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, EyeIcon, GlobeIcon, CheckIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, InboxIcon, CalendarClockIcon, UsersIcon, MessageSquareIcon, ZapIcon, Maximize2Icon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, SquareIcon, ArrowDownIcon, RotateCcwIcon, MousePointer2Icon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import { Collapsible as Collapsible$1, Slot, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Tooltip as Tooltip$1, Tabs as Tabs$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1, HoverCard as HoverCard$1, Progress as Progress$1 } from 'radix-ui';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -2064,10 +2064,7 @@ function WebToolBlock({ tc }) {
2064
2064
  let displayText = "";
2065
2065
  try {
2066
2066
  const args = JSON.parse(tc.args);
2067
- if (tc.toolName === "web_extract") {
2068
- const urls = args.urls ?? [];
2069
- displayText = urls[0] ?? "";
2070
- } else if (tc.toolName === "web_search") {
2067
+ if (tc.toolName === "web_search") {
2071
2068
  displayText = args.query ?? "";
2072
2069
  } else if (tc.toolName === "web_crawl") {
2073
2070
  displayText = args.url ?? "";
@@ -2075,12 +2072,10 @@ function WebToolBlock({ tc }) {
2075
2072
  } catch {
2076
2073
  }
2077
2074
  const toolLabel2 = {
2078
- web_extract: "Web Fetch",
2079
2075
  web_search: "Web Search",
2080
2076
  web_crawl: "Web Crawl"
2081
2077
  }[tc.toolName] ?? tc.toolName;
2082
2078
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
2083
- /* @__PURE__ */ jsx(GlobeIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
2084
2079
  /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: toolLabel2 }),
2085
2080
  displayText && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: displayText })
2086
2081
  ] });
@@ -2099,6 +2094,88 @@ function VisionAnalyzeBlock({ tc }) {
2099
2094
  image && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: image })
2100
2095
  ] });
2101
2096
  }
2097
+ function parseUrls(rawArgs) {
2098
+ try {
2099
+ const args = JSON.parse(rawArgs);
2100
+ if (Array.isArray(args.urls)) {
2101
+ return args.urls.filter((u) => typeof u === "string");
2102
+ }
2103
+ } catch {
2104
+ }
2105
+ return [];
2106
+ }
2107
+ function parseResult(rawResult) {
2108
+ const byUrl = /* @__PURE__ */ new Map();
2109
+ if (!rawResult) return { byUrl, toolError: null };
2110
+ try {
2111
+ const parsed = JSON.parse(rawResult);
2112
+ const entries = Array.isArray(parsed?.results) ? parsed.results : [];
2113
+ for (const entry of entries) {
2114
+ if (entry && typeof entry.url === "string") {
2115
+ byUrl.set(entry.url, entry);
2116
+ }
2117
+ }
2118
+ if (byUrl.size === 0 && typeof parsed?.error === "string") {
2119
+ return { byUrl, toolError: parsed.error };
2120
+ }
2121
+ } catch {
2122
+ }
2123
+ return { byUrl, toolError: null };
2124
+ }
2125
+ function domain(url) {
2126
+ try {
2127
+ return new URL(url).hostname.replace(/^www\./, "");
2128
+ } catch {
2129
+ return url;
2130
+ }
2131
+ }
2132
+ function WebExtractToolBlock({ tc }) {
2133
+ const urls = parseUrls(tc.args);
2134
+ const { byUrl, toolError } = parseResult(tc.result);
2135
+ const isComplete = tc.status === "complete" || tc.status === "error";
2136
+ if (urls.length === 0) {
2137
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
2138
+ /* @__PURE__ */ jsx(GlobeIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
2139
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Web Fetch" })
2140
+ ] });
2141
+ }
2142
+ return /* @__PURE__ */ jsxs("div", { children: [
2143
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
2144
+ /* @__PURE__ */ jsx(GlobeIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
2145
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Web Fetch" }),
2146
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 text-xs", children: [
2147
+ urls.length,
2148
+ " URL",
2149
+ urls.length !== 1 ? "s" : ""
2150
+ ] })
2151
+ ] }),
2152
+ /* @__PURE__ */ jsx("ul", { className: "mt-1 ml-5 space-y-0.5", children: urls.map((url, i) => {
2153
+ const entry = byUrl.get(url);
2154
+ const perUrlError = entry?.error;
2155
+ const failure = perUrlError ?? (isComplete && !entry ? toolError : null);
2156
+ const fetched = entry && !perUrlError;
2157
+ const pending = !isComplete && !entry;
2158
+ const row = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
2159
+ /* @__PURE__ */ jsx(
2160
+ "span",
2161
+ {
2162
+ className: cn(
2163
+ "truncate",
2164
+ pending ? "text-muted-foreground/50" : "text-muted-foreground"
2165
+ ),
2166
+ children: domain(url)
2167
+ }
2168
+ ),
2169
+ fetched && /* @__PURE__ */ jsx(CheckIcon, { className: "size-3 text-green-500/70 shrink-0" }),
2170
+ failure && /* @__PURE__ */ jsx(XIcon, { className: "size-3 text-red-500/80 shrink-0" })
2171
+ ] });
2172
+ return /* @__PURE__ */ jsx("li", { children: failure ? /* @__PURE__ */ jsxs(Tooltip, { children: [
2173
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: row }),
2174
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: failure })
2175
+ ] }) : row }, `${url}-${i}`);
2176
+ }) })
2177
+ ] });
2178
+ }
2102
2179
  function splitChangeLines(change) {
2103
2180
  const v = change.value;
2104
2181
  if (v === "") return [];
@@ -2778,7 +2855,7 @@ function parseArgs2(args) {
2778
2855
  return { action: "unknown" };
2779
2856
  }
2780
2857
  }
2781
- function parseResult(result) {
2858
+ function parseResult2(result) {
2782
2859
  if (!result) return null;
2783
2860
  try {
2784
2861
  return JSON.parse(result);
@@ -2996,7 +3073,7 @@ function OutputPreview({ output }) {
2996
3073
  function ProcessToolBlock({ tc }) {
2997
3074
  const isRunning = tc.status === "running";
2998
3075
  const args = parseArgs2(tc.args);
2999
- const result = parseResult(tc.result);
3076
+ const result = parseResult2(tc.result);
3000
3077
  const action = args.action || "unknown";
3001
3078
  const actionLabel = {
3002
3079
  list: "Process List",
@@ -4033,6 +4110,7 @@ function ToolCallBlock({
4033
4110
  case "session_search":
4034
4111
  return /* @__PURE__ */ jsx(SessionSearchBlock, { tc });
4035
4112
  case "web_extract":
4113
+ return /* @__PURE__ */ jsx(WebExtractToolBlock, { tc });
4036
4114
  case "web_search":
4037
4115
  case "web_crawl":
4038
4116
  return /* @__PURE__ */ jsx(WebToolBlock, { tc });
@@ -4074,6 +4152,51 @@ function ToolCallBlock({
4074
4152
  return /* @__PURE__ */ jsx(DefaultToolBlock, { tc });
4075
4153
  }
4076
4154
  }
4155
+ function parseQuery(rawArgs) {
4156
+ try {
4157
+ const args = JSON.parse(rawArgs);
4158
+ return typeof args.query === "string" ? args.query : "";
4159
+ } catch {
4160
+ return "";
4161
+ }
4162
+ }
4163
+ function parseResultCount2(rawResult) {
4164
+ if (!rawResult) return null;
4165
+ try {
4166
+ const parsed = JSON.parse(rawResult);
4167
+ if (Array.isArray(parsed?.data?.web)) return parsed.data.web.length;
4168
+ if (Array.isArray(parsed?.results)) return parsed.results.length;
4169
+ } catch {
4170
+ }
4171
+ return null;
4172
+ }
4173
+ function WebSearchRow({ tc }) {
4174
+ const query = parseQuery(tc.args);
4175
+ const count = parseResultCount2(tc.result);
4176
+ const showCount = count !== null && tc.status === "complete";
4177
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs min-w-0", children: [
4178
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate", children: query || "\u2014" }),
4179
+ showCount && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/50 shrink-0", children: [
4180
+ count,
4181
+ " result",
4182
+ count !== 1 ? "s" : ""
4183
+ ] })
4184
+ ] });
4185
+ }
4186
+ function WebSearchGroupBlock({ tcs }) {
4187
+ return /* @__PURE__ */ jsxs("div", { children: [
4188
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
4189
+ /* @__PURE__ */ jsx(SearchIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
4190
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Web Search" }),
4191
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 text-xs", children: [
4192
+ tcs.length,
4193
+ " ",
4194
+ tcs.length === 1 ? "query" : "queries"
4195
+ ] })
4196
+ ] }),
4197
+ /* @__PURE__ */ jsx("ul", { className: "mt-1 ml-5 space-y-0.5", children: tcs.map((tc) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(WebSearchRow, { tc }) }, tc.id)) })
4198
+ ] });
4199
+ }
4077
4200
  var ChatMessage = memo(function ChatMessage2({
4078
4201
  message,
4079
4202
  isLast,
@@ -5473,6 +5596,11 @@ function ChatComposerInner({
5473
5596
  const builtin = [
5474
5597
  { value: "/clear", label: "/clear", description: "Clear conversation" },
5475
5598
  { value: "/compress", label: "/compress", description: "Compress context" },
5599
+ { value: "/goal", label: "/goal", description: "Define an outcome goal" },
5600
+ { value: "/goal status", label: "/goal status", description: "Show outcome goal status" },
5601
+ { value: "/goal pause", label: "/goal pause", description: "Pause automatic goal continuation" },
5602
+ { value: "/goal resume", label: "/goal resume", description: "Resume a paused goal" },
5603
+ { value: "/goal clear", label: "/goal clear", description: "Clear the current goal" },
5476
5604
  { value: "/loop", label: "/loop", description: "Schedule recurring prompt" },
5477
5605
  { value: "/loop list", label: "/loop list", description: "List active loops" },
5478
5606
  { value: "/loop cancel", label: "/loop cancel", description: "Cancel a loop by ID" }
@@ -6296,6 +6424,33 @@ function groupBrowserActivityEntries(entries) {
6296
6424
  function isBrowserToolCall(call) {
6297
6425
  return call.toolName.startsWith("browser_");
6298
6426
  }
6427
+ function groupWebSearchEntries(entries) {
6428
+ const grouped = [];
6429
+ let buffer = [];
6430
+ const flush = () => {
6431
+ if (buffer.length === 0) return;
6432
+ if (buffer.length === 1) {
6433
+ grouped.push({ kind: "tool", key: buffer[0].id, tc: buffer[0] });
6434
+ } else {
6435
+ grouped.push({
6436
+ kind: "web_search_group",
6437
+ key: `web-search-${buffer[0].id}-${buffer[buffer.length - 1].id}`,
6438
+ calls: buffer
6439
+ });
6440
+ }
6441
+ buffer = [];
6442
+ };
6443
+ for (const entry of entries) {
6444
+ if (entry.kind === "tool" && entry.tc.toolName === "web_search") {
6445
+ buffer.push(entry.tc);
6446
+ continue;
6447
+ }
6448
+ flush();
6449
+ grouped.push(entry);
6450
+ }
6451
+ flush();
6452
+ return grouped;
6453
+ }
6299
6454
  function groupMessages(messages) {
6300
6455
  const groups = [];
6301
6456
  let pendingSystem = [];
@@ -6461,6 +6616,18 @@ function TimelineEntryItem({
6461
6616
  /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(BrowserActivityGroup, { calls: entry.calls }) })
6462
6617
  ] });
6463
6618
  }
6619
+ if (entry.kind === "web_search_group") {
6620
+ const allComplete = entry.calls.every((tc) => tc.status === "complete");
6621
+ const anyRunning = entry.calls.some((tc) => tc.status === "running");
6622
+ const indicatorClass = anyRunning ? "bg-emerald-500 animate-pulse" : allComplete ? "bg-emerald-500" : "bg-red-500";
6623
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
6624
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
6625
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
6626
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: cn("size-2 border-none", indicatorClass) })
6627
+ ] }),
6628
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(WebSearchGroupBlock, { tcs: entry.calls }) })
6629
+ ] });
6630
+ }
6464
6631
  if (entry.kind === "text") {
6465
6632
  return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
6466
6633
  /* @__PURE__ */ jsxs(TimelineHeader, { children: [
@@ -6528,6 +6695,7 @@ function AssistantGroup({
6528
6695
  entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
6529
6696
  }
6530
6697
  entries = groupBrowserActivityEntries(entries);
6698
+ entries = groupWebSearchEntries(entries);
6531
6699
  const isTailGroup = lastGlobalIndex === totalMessages - 1;
6532
6700
  const lastEntry = entries[entries.length - 1];
6533
6701
  const hasRunningTool = entries.some(
@@ -8683,11 +8851,12 @@ function useAgentChatRuntime({
8683
8851
  const send = useCallback(
8684
8852
  async (content, images) => {
8685
8853
  markSending(content, images);
8854
+ const goal = images?.length ? null : parseGoalDefinition(content);
8686
8855
  if (!sessionId) {
8687
8856
  try {
8688
8857
  const session2 = await adapter.createSession({ agentId });
8689
8858
  onSessionChange?.(session2.id);
8690
- await adapter.sendMessage({ sessionId: session2.id, content, images });
8859
+ await sendTurn(adapter, session2.id, content, images, goal);
8691
8860
  } catch (error) {
8692
8861
  markSendError(error instanceof Error ? error.message : "send failed");
8693
8862
  throw error;
@@ -8696,7 +8865,7 @@ function useAgentChatRuntime({
8696
8865
  }
8697
8866
  try {
8698
8867
  ensureStreamForNewTurn(sessionId);
8699
- await adapter.sendMessage({ sessionId, content, images });
8868
+ await sendTurn(adapter, sessionId, content, images, goal);
8700
8869
  } catch (error) {
8701
8870
  markSendError(error instanceof Error ? error.message : "send failed");
8702
8871
  throw error;
@@ -8769,6 +8938,30 @@ function findLastAssistantIndex2(messages) {
8769
8938
  }
8770
8939
  return -1;
8771
8940
  }
8941
+ var GOAL_RUBRIC_RE = /\n\s*(?:rubric|criteria)\s*:\s*\n/i;
8942
+ var GOAL_CONTROL_COMMANDS = /* @__PURE__ */ new Set(["", "status", "pause", "resume", "clear"]);
8943
+ async function sendTurn(adapter, sessionId, content, images, goal) {
8944
+ if (goal && adapter.defineOutcome) {
8945
+ await adapter.defineOutcome({
8946
+ sessionId,
8947
+ description: goal.description,
8948
+ rubric: goal.rubric
8949
+ });
8950
+ return;
8951
+ }
8952
+ await adapter.sendMessage({ sessionId, content, images });
8953
+ }
8954
+ function parseGoalDefinition(content) {
8955
+ const text = content.trim();
8956
+ if (text !== "/goal" && !text.startsWith("/goal ")) return null;
8957
+ const args = text.slice("/goal".length).trim();
8958
+ if (GOAL_CONTROL_COMMANDS.has(args.toLowerCase())) return null;
8959
+ const match = GOAL_RUBRIC_RE.exec(args);
8960
+ const description = (match ? args.slice(0, match.index) : args).trim();
8961
+ if (!description) return null;
8962
+ const rubric = match ? args.slice(match.index + match[0].length).trim() : "";
8963
+ return rubric ? { description, rubric } : { description };
8964
+ }
8772
8965
  function AgentChat({
8773
8966
  adapter,
8774
8967
  agentId,