@invergent/agent-chat-react 1.6.7 → 1.6.9

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
- 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';
2
+ import { createContext, memo, useRef, useEffect, useCallback, useMemo, useState, lazy, useContext, Suspense, Children } from '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';
@@ -703,8 +703,7 @@ var useReasoning = () => {
703
703
  }
704
704
  return context;
705
705
  };
706
- var AUTO_CLOSE_DELAY = 1e3;
707
- var AUTO_OPEN_DELAY = 100;
706
+ var MIN_DURATION_MS = 100;
708
707
  var MS_IN_S = 1e3;
709
708
  var Reasoning = memo(
710
709
  ({
@@ -718,7 +717,6 @@ var Reasoning = memo(
718
717
  ...props
719
718
  }) => {
720
719
  const resolvedDefaultOpen = defaultOpen ?? false;
721
- const isExplicitlyClosed = defaultOpen === false;
722
720
  const [isOpen, setIsOpen] = useControllableState({
723
721
  defaultProp: resolvedDefaultOpen,
724
722
  onChange: onOpenChange,
@@ -728,8 +726,6 @@ var Reasoning = memo(
728
726
  defaultProp: void 0,
729
727
  prop: durationProp
730
728
  });
731
- const hasEverStreamedRef = useRef(false);
732
- const [hasAutoClosed, setHasAutoClosed] = useState(false);
733
729
  const startTimeRef = useRef(null);
734
730
  useEffect(() => {
735
731
  if (isStreaming) {
@@ -739,29 +735,11 @@ var Reasoning = memo(
739
735
  } else if (startTimeRef.current !== null) {
740
736
  const elapsed = Date.now() - startTimeRef.current;
741
737
  startTimeRef.current = null;
742
- if (elapsed >= AUTO_OPEN_DELAY) {
743
- hasEverStreamedRef.current = true;
738
+ if (elapsed >= MIN_DURATION_MS) {
744
739
  setDuration(Math.ceil(elapsed / MS_IN_S));
745
740
  }
746
741
  }
747
742
  }, [isStreaming, setDuration]);
748
- useEffect(() => {
749
- if (!isStreaming || isOpen || isExplicitlyClosed) return;
750
- const timer = setTimeout(() => {
751
- hasEverStreamedRef.current = true;
752
- setIsOpen(true);
753
- }, AUTO_OPEN_DELAY);
754
- return () => clearTimeout(timer);
755
- }, [isStreaming, isOpen, setIsOpen, isExplicitlyClosed]);
756
- useEffect(() => {
757
- if (hasEverStreamedRef.current && !isStreaming && isOpen && !hasAutoClosed) {
758
- const timer = setTimeout(() => {
759
- setIsOpen(false);
760
- setHasAutoClosed(true);
761
- }, AUTO_CLOSE_DELAY);
762
- return () => clearTimeout(timer);
763
- }
764
- }, [isStreaming, isOpen, setIsOpen, hasAutoClosed]);
765
743
  const handleOpenChange = useCallback(
766
744
  (newOpen) => {
767
745
  setIsOpen(newOpen);
@@ -1183,7 +1161,7 @@ function TerminalCollapsible({ command, output, isRunning, hasError }) {
1183
1161
  className: "not-prose w-full",
1184
1162
  children: [
1185
1163
  /* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "group/trigger flex w-fit items-center gap-2 text-sm transition-colors", children: [
1186
- /* @__PURE__ */ jsx("span", { className: "text-left", children: isRunning ? /* @__PURE__ */ jsx(Shimmer, { as: "span", duration: 1, children: "Running command..." }) : hasError ? /* @__PURE__ */ jsx("span", { className: "font-semibold text-destructive", children: "Command failed" }) : /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Command result" }) }),
1164
+ /* @__PURE__ */ jsx("span", { className: "text-left", children: isRunning ? /* @__PURE__ */ jsx(Shimmer, { as: "span", duration: 1, children: "Running command..." }) : hasError ? /* @__PURE__ */ jsx("span", { className: "font-semibold text-destructive", children: "Command failed" }) : /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "Command" }) }),
1187
1165
  /* @__PURE__ */ jsx(
1188
1166
  ChevronDownIcon,
1189
1167
  {
@@ -1234,14 +1212,16 @@ function TerminalToolBlock({ tc }) {
1234
1212
  const isRunning = tc.status === "running";
1235
1213
  const result = parseTerminalResult(tc.result, tc.args);
1236
1214
  if (result) {
1237
- const output = result.output || result.error || "";
1215
+ const hasError = result.exit_code !== 0 || Boolean(result.error);
1216
+ if (hasError) return null;
1217
+ const output = result.output || "";
1238
1218
  return /* @__PURE__ */ jsx(
1239
1219
  TerminalCollapsible,
1240
1220
  {
1241
1221
  command: result.command,
1242
1222
  output,
1243
1223
  isRunning,
1244
- hasError: result.exit_code !== 0 || Boolean(result.error)
1224
+ hasError: false
1245
1225
  }
1246
1226
  );
1247
1227
  }
@@ -2064,10 +2044,7 @@ function WebToolBlock({ tc }) {
2064
2044
  let displayText = "";
2065
2045
  try {
2066
2046
  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") {
2047
+ if (tc.toolName === "web_search") {
2071
2048
  displayText = args.query ?? "";
2072
2049
  } else if (tc.toolName === "web_crawl") {
2073
2050
  displayText = args.url ?? "";
@@ -2075,12 +2052,10 @@ function WebToolBlock({ tc }) {
2075
2052
  } catch {
2076
2053
  }
2077
2054
  const toolLabel2 = {
2078
- web_extract: "Web Fetch",
2079
2055
  web_search: "Web Search",
2080
2056
  web_crawl: "Web Crawl"
2081
2057
  }[tc.toolName] ?? tc.toolName;
2082
2058
  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
2059
  /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: toolLabel2 }),
2085
2060
  displayText && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: displayText })
2086
2061
  ] });
@@ -2099,6 +2074,88 @@ function VisionAnalyzeBlock({ tc }) {
2099
2074
  image && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/70 truncate text-xs", children: image })
2100
2075
  ] });
2101
2076
  }
2077
+ function parseUrls(rawArgs) {
2078
+ try {
2079
+ const args = JSON.parse(rawArgs);
2080
+ if (Array.isArray(args.urls)) {
2081
+ return args.urls.filter((u) => typeof u === "string");
2082
+ }
2083
+ } catch {
2084
+ }
2085
+ return [];
2086
+ }
2087
+ function parseResult(rawResult) {
2088
+ const byUrl = /* @__PURE__ */ new Map();
2089
+ if (!rawResult) return { byUrl, toolError: null };
2090
+ try {
2091
+ const parsed = JSON.parse(rawResult);
2092
+ const entries = Array.isArray(parsed?.results) ? parsed.results : [];
2093
+ for (const entry of entries) {
2094
+ if (entry && typeof entry.url === "string") {
2095
+ byUrl.set(entry.url, entry);
2096
+ }
2097
+ }
2098
+ if (byUrl.size === 0 && typeof parsed?.error === "string") {
2099
+ return { byUrl, toolError: parsed.error };
2100
+ }
2101
+ } catch {
2102
+ }
2103
+ return { byUrl, toolError: null };
2104
+ }
2105
+ function domain(url) {
2106
+ try {
2107
+ return new URL(url).hostname.replace(/^www\./, "");
2108
+ } catch {
2109
+ return url;
2110
+ }
2111
+ }
2112
+ function WebExtractToolBlock({ tc }) {
2113
+ const urls = parseUrls(tc.args);
2114
+ const { byUrl, toolError } = parseResult(tc.result);
2115
+ const isComplete = tc.status === "complete" || tc.status === "error";
2116
+ if (urls.length === 0) {
2117
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm py-0.5", children: [
2118
+ /* @__PURE__ */ jsx(GlobeIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
2119
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Web Fetch" })
2120
+ ] });
2121
+ }
2122
+ return /* @__PURE__ */ jsxs("div", { children: [
2123
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
2124
+ /* @__PURE__ */ jsx(GlobeIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
2125
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Web Fetch" }),
2126
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 text-xs", children: [
2127
+ urls.length,
2128
+ " URL",
2129
+ urls.length !== 1 ? "s" : ""
2130
+ ] })
2131
+ ] }),
2132
+ /* @__PURE__ */ jsx("ul", { className: "mt-1 ml-5 space-y-0.5", children: urls.map((url, i) => {
2133
+ const entry = byUrl.get(url);
2134
+ const perUrlError = entry?.error;
2135
+ const failure = perUrlError ?? (isComplete && !entry ? toolError : null);
2136
+ const fetched = entry && !perUrlError;
2137
+ const pending = !isComplete && !entry;
2138
+ const row = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
2139
+ /* @__PURE__ */ jsx(
2140
+ "span",
2141
+ {
2142
+ className: cn(
2143
+ "truncate",
2144
+ pending ? "text-muted-foreground/50" : "text-muted-foreground"
2145
+ ),
2146
+ children: domain(url)
2147
+ }
2148
+ ),
2149
+ fetched && /* @__PURE__ */ jsx(CheckIcon, { className: "size-3 text-green-500/70 shrink-0" }),
2150
+ failure && /* @__PURE__ */ jsx(XIcon, { className: "size-3 text-red-500/80 shrink-0" })
2151
+ ] });
2152
+ return /* @__PURE__ */ jsx("li", { children: failure ? /* @__PURE__ */ jsxs(Tooltip, { children: [
2153
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: row }),
2154
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: failure })
2155
+ ] }) : row }, `${url}-${i}`);
2156
+ }) })
2157
+ ] });
2158
+ }
2102
2159
  function splitChangeLines(change) {
2103
2160
  const v = change.value;
2104
2161
  if (v === "") return [];
@@ -2778,7 +2835,7 @@ function parseArgs2(args) {
2778
2835
  return { action: "unknown" };
2779
2836
  }
2780
2837
  }
2781
- function parseResult(result) {
2838
+ function parseResult2(result) {
2782
2839
  if (!result) return null;
2783
2840
  try {
2784
2841
  return JSON.parse(result);
@@ -2996,7 +3053,7 @@ function OutputPreview({ output }) {
2996
3053
  function ProcessToolBlock({ tc }) {
2997
3054
  const isRunning = tc.status === "running";
2998
3055
  const args = parseArgs2(tc.args);
2999
- const result = parseResult(tc.result);
3056
+ const result = parseResult2(tc.result);
3000
3057
  const action = args.action || "unknown";
3001
3058
  const actionLabel = {
3002
3059
  list: "Process List",
@@ -4033,6 +4090,7 @@ function ToolCallBlock({
4033
4090
  case "session_search":
4034
4091
  return /* @__PURE__ */ jsx(SessionSearchBlock, { tc });
4035
4092
  case "web_extract":
4093
+ return /* @__PURE__ */ jsx(WebExtractToolBlock, { tc });
4036
4094
  case "web_search":
4037
4095
  case "web_crawl":
4038
4096
  return /* @__PURE__ */ jsx(WebToolBlock, { tc });
@@ -4074,6 +4132,51 @@ function ToolCallBlock({
4074
4132
  return /* @__PURE__ */ jsx(DefaultToolBlock, { tc });
4075
4133
  }
4076
4134
  }
4135
+ function parseQuery(rawArgs) {
4136
+ try {
4137
+ const args = JSON.parse(rawArgs);
4138
+ return typeof args.query === "string" ? args.query : "";
4139
+ } catch {
4140
+ return "";
4141
+ }
4142
+ }
4143
+ function parseResultCount2(rawResult) {
4144
+ if (!rawResult) return null;
4145
+ try {
4146
+ const parsed = JSON.parse(rawResult);
4147
+ if (Array.isArray(parsed?.data?.web)) return parsed.data.web.length;
4148
+ if (Array.isArray(parsed?.results)) return parsed.results.length;
4149
+ } catch {
4150
+ }
4151
+ return null;
4152
+ }
4153
+ function WebSearchRow({ tc }) {
4154
+ const query = parseQuery(tc.args);
4155
+ const count = parseResultCount2(tc.result);
4156
+ const showCount = count !== null && tc.status === "complete";
4157
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs min-w-0", children: [
4158
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate", children: query || "\u2014" }),
4159
+ showCount && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/50 shrink-0", children: [
4160
+ count,
4161
+ " result",
4162
+ count !== 1 ? "s" : ""
4163
+ ] })
4164
+ ] });
4165
+ }
4166
+ function WebSearchGroupBlock({ tcs }) {
4167
+ return /* @__PURE__ */ jsxs("div", { children: [
4168
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
4169
+ /* @__PURE__ */ jsx(SearchIcon, { className: "size-3.5 text-muted-foreground/60 shrink-0" }),
4170
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: "Web Search" }),
4171
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70 text-xs", children: [
4172
+ tcs.length,
4173
+ " ",
4174
+ tcs.length === 1 ? "query" : "queries"
4175
+ ] })
4176
+ ] }),
4177
+ /* @__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)) })
4178
+ ] });
4179
+ }
4077
4180
  var ChatMessage = memo(function ChatMessage2({
4078
4181
  message,
4079
4182
  isLast,
@@ -5473,6 +5576,11 @@ function ChatComposerInner({
5473
5576
  const builtin = [
5474
5577
  { value: "/clear", label: "/clear", description: "Clear conversation" },
5475
5578
  { value: "/compress", label: "/compress", description: "Compress context" },
5579
+ { value: "/goal", label: "/goal", description: "Define an outcome goal" },
5580
+ { value: "/goal status", label: "/goal status", description: "Show outcome goal status" },
5581
+ { value: "/goal pause", label: "/goal pause", description: "Pause automatic goal continuation" },
5582
+ { value: "/goal resume", label: "/goal resume", description: "Resume a paused goal" },
5583
+ { value: "/goal clear", label: "/goal clear", description: "Clear the current goal" },
5476
5584
  { value: "/loop", label: "/loop", description: "Schedule recurring prompt" },
5477
5585
  { value: "/loop list", label: "/loop list", description: "List active loops" },
5478
5586
  { value: "/loop cancel", label: "/loop cancel", description: "Cancel a loop by ID" }
@@ -6256,6 +6364,10 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
6256
6364
  });
6257
6365
  continue;
6258
6366
  }
6367
+ if (tc.toolName === "terminal" && tc.status !== "running") {
6368
+ const parsed = parseTerminalResult(tc.result, tc.args);
6369
+ if (parsed && (parsed.exit_code !== 0 || parsed.error)) continue;
6370
+ }
6259
6371
  entries.push({ kind: "tool", key: tc.id, tc });
6260
6372
  }
6261
6373
  }
@@ -6296,6 +6408,33 @@ function groupBrowserActivityEntries(entries) {
6296
6408
  function isBrowserToolCall(call) {
6297
6409
  return call.toolName.startsWith("browser_");
6298
6410
  }
6411
+ function groupWebSearchEntries(entries) {
6412
+ const grouped = [];
6413
+ let buffer = [];
6414
+ const flush = () => {
6415
+ if (buffer.length === 0) return;
6416
+ if (buffer.length === 1) {
6417
+ grouped.push({ kind: "tool", key: buffer[0].id, tc: buffer[0] });
6418
+ } else {
6419
+ grouped.push({
6420
+ kind: "web_search_group",
6421
+ key: `web-search-${buffer[0].id}-${buffer[buffer.length - 1].id}`,
6422
+ calls: buffer
6423
+ });
6424
+ }
6425
+ buffer = [];
6426
+ };
6427
+ for (const entry of entries) {
6428
+ if (entry.kind === "tool" && entry.tc.toolName === "web_search") {
6429
+ buffer.push(entry.tc);
6430
+ continue;
6431
+ }
6432
+ flush();
6433
+ grouped.push(entry);
6434
+ }
6435
+ flush();
6436
+ return grouped;
6437
+ }
6299
6438
  function groupMessages(messages) {
6300
6439
  const groups = [];
6301
6440
  let pendingSystem = [];
@@ -6461,6 +6600,18 @@ function TimelineEntryItem({
6461
6600
  /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(BrowserActivityGroup, { calls: entry.calls }) })
6462
6601
  ] });
6463
6602
  }
6603
+ if (entry.kind === "web_search_group") {
6604
+ const allComplete = entry.calls.every((tc) => tc.status === "complete");
6605
+ const anyRunning = entry.calls.some((tc) => tc.status === "running");
6606
+ const indicatorClass = anyRunning ? "bg-emerald-500 animate-pulse" : allComplete ? "bg-emerald-500" : "bg-red-500";
6607
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
6608
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
6609
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
6610
+ /* @__PURE__ */ jsx(TimelineIndicator, { className: cn("size-2 border-none", indicatorClass) })
6611
+ ] }),
6612
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsx(WebSearchGroupBlock, { tcs: entry.calls }) })
6613
+ ] });
6614
+ }
6464
6615
  if (entry.kind === "text") {
6465
6616
  return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
6466
6617
  /* @__PURE__ */ jsxs(TimelineHeader, { children: [
@@ -6528,6 +6679,7 @@ function AssistantGroup({
6528
6679
  entries.push(...messageToEntries(messages[i], isLast, artifactFallbacks));
6529
6680
  }
6530
6681
  entries = groupBrowserActivityEntries(entries);
6682
+ entries = groupWebSearchEntries(entries);
6531
6683
  const isTailGroup = lastGlobalIndex === totalMessages - 1;
6532
6684
  const lastEntry = entries[entries.length - 1];
6533
6685
  const hasRunningTool = entries.some(
@@ -8683,11 +8835,12 @@ function useAgentChatRuntime({
8683
8835
  const send = useCallback(
8684
8836
  async (content, images) => {
8685
8837
  markSending(content, images);
8838
+ const goal = images?.length ? null : parseGoalDefinition(content);
8686
8839
  if (!sessionId) {
8687
8840
  try {
8688
8841
  const session2 = await adapter.createSession({ agentId });
8689
8842
  onSessionChange?.(session2.id);
8690
- await adapter.sendMessage({ sessionId: session2.id, content, images });
8843
+ await sendTurn(adapter, session2.id, content, images, goal);
8691
8844
  } catch (error) {
8692
8845
  markSendError(error instanceof Error ? error.message : "send failed");
8693
8846
  throw error;
@@ -8696,7 +8849,7 @@ function useAgentChatRuntime({
8696
8849
  }
8697
8850
  try {
8698
8851
  ensureStreamForNewTurn(sessionId);
8699
- await adapter.sendMessage({ sessionId, content, images });
8852
+ await sendTurn(adapter, sessionId, content, images, goal);
8700
8853
  } catch (error) {
8701
8854
  markSendError(error instanceof Error ? error.message : "send failed");
8702
8855
  throw error;
@@ -8769,6 +8922,30 @@ function findLastAssistantIndex2(messages) {
8769
8922
  }
8770
8923
  return -1;
8771
8924
  }
8925
+ var GOAL_RUBRIC_RE = /\n\s*(?:rubric|criteria)\s*:\s*\n/i;
8926
+ var GOAL_CONTROL_COMMANDS = /* @__PURE__ */ new Set(["", "status", "pause", "resume", "clear"]);
8927
+ async function sendTurn(adapter, sessionId, content, images, goal) {
8928
+ if (goal && adapter.defineOutcome) {
8929
+ await adapter.defineOutcome({
8930
+ sessionId,
8931
+ description: goal.description,
8932
+ rubric: goal.rubric
8933
+ });
8934
+ return;
8935
+ }
8936
+ await adapter.sendMessage({ sessionId, content, images });
8937
+ }
8938
+ function parseGoalDefinition(content) {
8939
+ const text = content.trim();
8940
+ if (text !== "/goal" && !text.startsWith("/goal ")) return null;
8941
+ const args = text.slice("/goal".length).trim();
8942
+ if (GOAL_CONTROL_COMMANDS.has(args.toLowerCase())) return null;
8943
+ const match = GOAL_RUBRIC_RE.exec(args);
8944
+ const description = (match ? args.slice(0, match.index) : args).trim();
8945
+ if (!description) return null;
8946
+ const rubric = match ? args.slice(match.index + match[0].length).trim() : "";
8947
+ return rubric ? { description, rubric } : { description };
8948
+ }
8772
8949
  function AgentChat({
8773
8950
  adapter,
8774
8951
  agentId,
@@ -8866,7 +9043,7 @@ function AgentChat({
8866
9043
  "div",
8867
9044
  {
8868
9045
  "data-testid": "workspace-panel-frame",
8869
- className: hasBrowserPanel ? "h-1/2 min-h-0 w-full overflow-hidden" : "min-h-0",
9046
+ className: hasBrowserPanel ? "h-1/2 min-h-0 w-full overflow-hidden" : "min-h-0 h-full",
8870
9047
  children: /* @__PURE__ */ jsx(
8871
9048
  WorkspacePanel,
8872
9049
  {
@@ -9583,10 +9760,11 @@ function formatKind(value) {
9583
9760
  if (!value) return "Scheduled";
9584
9761
  return value.split(/[_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
9585
9762
  }
9586
- function formatRelative2(value, label) {
9763
+ function formatRelative2(value, label, future = false) {
9587
9764
  if (!value) return null;
9588
9765
  const date = new Date(value);
9589
9766
  if (Number.isNaN(date.getTime())) return null;
9767
+ if (future && date.getTime() <= Date.now()) return `${label} due now`;
9590
9768
  return `${label} ${formatDistanceToNow(date, { addSuffix: true })}`;
9591
9769
  }
9592
9770
  function formatRuns(count) {
@@ -9633,10 +9811,10 @@ function ScheduledWorkRow({
9633
9811
  const lastSessionId = item.lastSessionId ?? null;
9634
9812
  const meta = [
9635
9813
  scheduleDisplay(item),
9636
- formatRelative2(item.nextRunAt, "Next"),
9814
+ formatRelative2(item.nextRunAt, "Next", true),
9637
9815
  formatRelative2(item.lastRunAt, "Last"),
9638
9816
  formatRuns(item.runCount),
9639
- item.expiresAt ? formatRelative2(item.expiresAt, "Expires") : null
9817
+ item.expiresAt ? formatRelative2(item.expiresAt, "Expires", true) : null
9640
9818
  ].filter(Boolean);
9641
9819
  const openLastRun = () => {
9642
9820
  if (lastSessionId) onOpenLastRun(lastSessionId);
@@ -9987,8 +10165,7 @@ function TreeNodeRow({
9987
10165
  const title = entry.title ?? fallbackSessionTitle(entry);
9988
10166
  const subtitle = [
9989
10167
  formatRunKind(entry.runKind),
9990
- entry.agentType,
9991
- entry.model ?? "default",
10168
+ entry.model ?? entry.agentType,
9992
10169
  formatSessionTime(entry.updatedAt)
9993
10170
  ].filter(Boolean).join(" \xB7 ");
9994
10171
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -10043,7 +10220,10 @@ function TreeNodeRow({
10043
10220
  "button",
10044
10221
  {
10045
10222
  type: "button",
10046
- className: "p-1 rounded opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-destructive/10 hover:text-destructive disabled:pointer-events-none disabled:opacity-50 transition-all",
10223
+ className: cn(
10224
+ "p-1 rounded hover:bg-destructive/10 hover:text-destructive disabled:pointer-events-none transition-all",
10225
+ deletingSessionId === entry.id ? "opacity-100" : "opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
10226
+ ),
10047
10227
  onClick: (e) => {
10048
10228
  e.stopPropagation();
10049
10229
  onDelete(entry.id);
@@ -10051,7 +10231,7 @@ function TreeNodeRow({
10051
10231
  "aria-label": "Delete session",
10052
10232
  title: "Delete session",
10053
10233
  disabled: deletingSessionId === entry.id,
10054
- children: /* @__PURE__ */ jsx(Trash2Icon, { className: "w-3 h-3" })
10234
+ children: deletingSessionId === entry.id ? /* @__PURE__ */ jsx(Loader2Icon, { className: "w-3 h-3 animate-spin" }) : /* @__PURE__ */ jsx(Trash2Icon, { className: "w-3 h-3" })
10055
10235
  }
10056
10236
  )
10057
10237
  ]