@invergent/agent-chat-react 2.4.13 → 2.5.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.cjs CHANGED
@@ -4473,6 +4473,51 @@ function DefaultToolBlock({ tc }) {
4473
4473
  ] })
4474
4474
  ] });
4475
4475
  }
4476
+ function ResearchOutlineBlock({ tc }) {
4477
+ const args = parseArgs(tc.args) ?? {};
4478
+ const result = tc.result ? parseArgs(tc.result) ?? {} : {};
4479
+ const outline = args.action === "set" ? args.outline ?? "" : result.outline ?? "";
4480
+ const sections = result.sections ?? [];
4481
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-sm border border-border bg-muted/40 p-2 text-xs", children: [
4482
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1 font-semibold uppercase tracking-widest text-muted-foreground", children: [
4483
+ "Research outline",
4484
+ sections.length ? ` \xB7 ${sections.length} sections` : ""
4485
+ ] }),
4486
+ outline ? /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap font-mono text-[11px] leading-snug", children: outline }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: "updated" })
4487
+ ] });
4488
+ }
4489
+ function ResearchMemoryBlock({ tc }) {
4490
+ const args = parseArgs(tc.args) ?? {};
4491
+ const result = tc.result ? parseArgs(tc.result) ?? {} : {};
4492
+ let label;
4493
+ if (args.action === "add") {
4494
+ const host = args.url ? hostname(args.url) : "";
4495
+ label = `Recorded source ${result.source_id ?? ""}${host ? ` \xB7 ${host}` : ""}`;
4496
+ } else if (args.action === "retrieve") {
4497
+ const n = result.sources?.length ?? 0;
4498
+ label = `Retrieved ${n} source${n === 1 ? "" : "s"}${args.query ? ` for "${truncate2(args.query)}"` : ""}`;
4499
+ } else if (args.action === "list") {
4500
+ const n = result.sources?.length ?? 0;
4501
+ label = `Listed ${n} source${n === 1 ? "" : "s"}`;
4502
+ } else {
4503
+ label = "research_memory";
4504
+ }
4505
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-muted-foreground", children: [
4506
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: "research" }),
4507
+ " ",
4508
+ label
4509
+ ] });
4510
+ }
4511
+ function hostname(url) {
4512
+ try {
4513
+ return new URL(url).hostname.replace(/^www\./, "");
4514
+ } catch {
4515
+ return url;
4516
+ }
4517
+ }
4518
+ function truncate2(s, n = 40) {
4519
+ return s.length > n ? `${s.slice(0, n)}\u2026` : s;
4520
+ }
4476
4521
  function ToolCallBlock({
4477
4522
  tc,
4478
4523
  resolvedArtifactName,
@@ -4525,6 +4570,10 @@ function ToolCallBlock({
4525
4570
  return /* @__PURE__ */ jsxRuntime.jsx(DelegateToolBlock, { tc });
4526
4571
  case "memory":
4527
4572
  return /* @__PURE__ */ jsxRuntime.jsx(MemoryToolBlock, { tc });
4573
+ case "research_outline":
4574
+ return /* @__PURE__ */ jsxRuntime.jsx(ResearchOutlineBlock, { tc });
4575
+ case "research_memory":
4576
+ return /* @__PURE__ */ jsxRuntime.jsx(ResearchMemoryBlock, { tc });
4528
4577
  case "spawn_worker":
4529
4578
  case "send_worker_message":
4530
4579
  case "stop_worker":
@@ -6073,6 +6122,7 @@ function ChatComposerInner({
6073
6122
  onToggleWorkspace,
6074
6123
  canShowBrowser = false,
6075
6124
  canShowWorkspace = false,
6125
+ deepResearchEnabled = false,
6076
6126
  viewMode = "simple",
6077
6127
  onViewModeChange
6078
6128
  }) {
@@ -6102,24 +6152,34 @@ function ChatComposerInner({
6102
6152
  }
6103
6153
  }, [adapter, showSlashMenu, menuMode]);
6104
6154
  const builtinCommands = react.useMemo(
6105
- () => [
6106
- { value: "/clear", label: "/clear", description: "Clear conversation" },
6107
- { value: "/compress", label: "/compress", description: "Compress context" },
6108
- { value: "/goal", label: "/goal", description: "Define an outcome goal" },
6109
- { value: "/goal status", label: "/goal status", description: "Show outcome goal status" },
6110
- { value: "/goal pause", label: "/goal pause", description: "Pause automatic goal continuation" },
6111
- { value: "/goal resume", label: "/goal resume", description: "Resume a paused goal" },
6112
- { value: "/goal clear", label: "/goal clear", description: "Clear the current goal" },
6113
- { value: "/mission ", label: "/mission", description: "Start an orchestrated rubric-judged mission" },
6114
- { value: "/mission status", label: "/mission status", description: "Show current mission status" },
6115
- { value: "/mission pause", label: "/mission pause", description: "Pause the mission evaluator" },
6116
- { value: "/mission resume", label: "/mission resume", description: "Resume a paused mission" },
6117
- { value: "/mission cancel", label: "/mission cancel", description: "Cancel the mission" },
6118
- { value: "/loop", label: "/loop", description: "Schedule recurring prompt" },
6119
- { value: "/loop list", label: "/loop list", description: "List active loops" },
6120
- { value: "/loop cancel", label: "/loop cancel", description: "Cancel a loop by ID" }
6121
- ],
6122
- []
6155
+ () => {
6156
+ const base = [
6157
+ { value: "/clear", label: "/clear", description: "Clear conversation" },
6158
+ { value: "/compress", label: "/compress", description: "Compress context" },
6159
+ { value: "/goal", label: "/goal", description: "Define an outcome goal" },
6160
+ { value: "/goal status", label: "/goal status", description: "Show outcome goal status" },
6161
+ { value: "/goal pause", label: "/goal pause", description: "Pause automatic goal continuation" },
6162
+ { value: "/goal resume", label: "/goal resume", description: "Resume a paused goal" },
6163
+ { value: "/goal clear", label: "/goal clear", description: "Clear the current goal" },
6164
+ { value: "/mission ", label: "/mission", description: "Start an orchestrated rubric-judged mission" },
6165
+ { value: "/mission status", label: "/mission status", description: "Show current mission status" },
6166
+ { value: "/mission pause", label: "/mission pause", description: "Pause the mission evaluator" },
6167
+ { value: "/mission resume", label: "/mission resume", description: "Resume a paused mission" },
6168
+ { value: "/mission cancel", label: "/mission cancel", description: "Cancel the mission" },
6169
+ { value: "/loop", label: "/loop", description: "Schedule recurring prompt" },
6170
+ { value: "/loop list", label: "/loop list", description: "List active loops" },
6171
+ { value: "/loop cancel", label: "/loop cancel", description: "Cancel a loop by ID" }
6172
+ ];
6173
+ if (deepResearchEnabled) {
6174
+ base.push({
6175
+ value: "/deep-research ",
6176
+ label: "/deep-research",
6177
+ description: "Delegate a topic to the deep-research workflow"
6178
+ });
6179
+ }
6180
+ return base;
6181
+ },
6182
+ [deepResearchEnabled]
6123
6183
  );
6124
6184
  const scheduledExamples = react.useMemo(
6125
6185
  () => [
@@ -8801,11 +8861,11 @@ function extractToolDetail(tc) {
8801
8861
  }
8802
8862
  case "terminal": {
8803
8863
  const cmd = stringArg("command");
8804
- return cmd ? truncate2(cmd, 40) : null;
8864
+ return cmd ? truncate3(cmd, 40) : null;
8805
8865
  }
8806
8866
  case "execute_code": {
8807
8867
  const code3 = stringArg("code");
8808
- return code3 ? truncate2(code3.split("\n")[0] ?? "", 40) : null;
8868
+ return code3 ? truncate3(code3.split("\n")[0] ?? "", 40) : null;
8809
8869
  }
8810
8870
  case "web_search":
8811
8871
  case "web_crawl":
@@ -8830,7 +8890,7 @@ function lastPathSegment(path) {
8830
8890
  const idx = cleaned.lastIndexOf("/");
8831
8891
  return idx >= 0 ? cleaned.slice(idx + 1) : cleaned;
8832
8892
  }
8833
- function truncate2(s, max) {
8893
+ function truncate3(s, max) {
8834
8894
  return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
8835
8895
  }
8836
8896
  var _SIMPLE_MODE_HIDDEN_TOOLS = /* @__PURE__ */ new Set([
@@ -9331,7 +9391,8 @@ function ChatThread({
9331
9391
  canShowBrowser = false,
9332
9392
  canShowWorkspace = false,
9333
9393
  viewMode = "simple",
9334
- onViewModeChange
9394
+ onViewModeChange,
9395
+ deepResearchEnabled = false
9335
9396
  }) {
9336
9397
  const groups = react.useMemo(() => groupMessages(messages), [messages]);
9337
9398
  const awaitingInput = react.useMemo(() => isAwaitingUserInput(messages), [messages]);
@@ -9464,12 +9525,51 @@ function ChatThread({
9464
9525
  canShowBrowser,
9465
9526
  canShowWorkspace,
9466
9527
  viewMode,
9467
- onViewModeChange
9528
+ onViewModeChange,
9529
+ deepResearchEnabled
9468
9530
  }
9469
9531
  )
9470
9532
  ] })
9471
9533
  ] });
9472
9534
  }
9535
+ function ResearchSourcesPanel({
9536
+ sources
9537
+ }) {
9538
+ if (sources.length === 0) {
9539
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3 text-xs text-muted-foreground", children: "No research sources yet. Sources appear here as the deep-research agent curates evidence." });
9540
+ }
9541
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 p-2", children: [
9542
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-1 pb-1 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: [
9543
+ "Sources \xB7 ",
9544
+ sources.length
9545
+ ] }),
9546
+ sources.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
9547
+ "a",
9548
+ {
9549
+ id: `source-${s.sourceId}`,
9550
+ href: s.url,
9551
+ target: "_blank",
9552
+ rel: "noreferrer",
9553
+ className: "group flex items-baseline gap-2 rounded-sm px-1 py-1 hover:bg-muted",
9554
+ children: [
9555
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-[10px] font-semibold text-primary", children: s.sourceId }),
9556
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex flex-col overflow-hidden", children: [
9557
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-foreground group-hover:underline", children: s.title || s.url }),
9558
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-[10px] text-muted-foreground", children: hostname2(s.url) })
9559
+ ] })
9560
+ ]
9561
+ },
9562
+ s.sourceId
9563
+ ))
9564
+ ] });
9565
+ }
9566
+ function hostname2(url) {
9567
+ try {
9568
+ return new URL(url).hostname.replace(/^www\./, "");
9569
+ } catch {
9570
+ return url;
9571
+ }
9572
+ }
9473
9573
 
9474
9574
  // src/components/ai-elements/file-tree.tsx
9475
9575
  init_utils();
@@ -10216,7 +10316,8 @@ function createInitialAgentChatState(options = {}) {
10216
10316
  terminal: false,
10217
10317
  workspaceRefreshKey: 0,
10218
10318
  browser: null,
10219
- viewMode: options.viewMode ?? "simple"
10319
+ viewMode: options.viewMode ?? "simple",
10320
+ researchSources: []
10220
10321
  };
10221
10322
  }
10222
10323
  function applyAgentChatEvent(state, event) {
@@ -10302,11 +10403,12 @@ function applyAgentChatEvent(state, event) {
10302
10403
  const toolName = findToolNameById(nextState.messages, toolCallId);
10303
10404
  const messages = applyToolResult(nextState.messages, event.data);
10304
10405
  const mutatesWorkspace = toolName !== null && WORKSPACE_MUTATING_TOOLS.has(toolName);
10305
- return {
10406
+ const withResult = {
10306
10407
  ...nextState,
10307
10408
  messages,
10308
10409
  workspaceRefreshKey: mutatesWorkspace ? nextState.workspaceRefreshKey + 1 : nextState.workspaceRefreshKey
10309
10410
  };
10411
+ return collectResearchSource(withResult, toolName, event.data);
10310
10412
  }
10311
10413
  case "harness.wake":
10312
10414
  case "llm.request":
@@ -11038,6 +11140,32 @@ function findToolNameById(messages, toolCallId) {
11038
11140
  }
11039
11141
  return null;
11040
11142
  }
11143
+ function collectResearchSource(state, toolName, data) {
11144
+ if (toolName !== "research_memory") return state;
11145
+ const rawResult = data.content ?? data.result;
11146
+ const resultText = typeof rawResult === "string" ? rawResult : JSON.stringify(rawResult ?? {});
11147
+ let parsed;
11148
+ try {
11149
+ parsed = JSON.parse(resultText);
11150
+ } catch {
11151
+ return state;
11152
+ }
11153
+ if (!parsed.success || !parsed.source_id || !parsed.url) return state;
11154
+ if (state.researchSources.some((s) => s.sourceId === parsed.source_id)) {
11155
+ return state;
11156
+ }
11157
+ return {
11158
+ ...state,
11159
+ researchSources: [
11160
+ ...state.researchSources,
11161
+ {
11162
+ sourceId: parsed.source_id,
11163
+ url: parsed.url,
11164
+ title: parsed.title ?? ""
11165
+ }
11166
+ ]
11167
+ };
11168
+ }
11041
11169
  function findLatestConsultExpertCall(messages) {
11042
11170
  for (let i = messages.length - 1; i >= 0; i--) {
11043
11171
  const msg = messages[i];
@@ -11487,7 +11615,8 @@ function useAgentChatRuntime({
11487
11615
  markSending,
11488
11616
  markSendError,
11489
11617
  viewMode: state.viewMode,
11490
- setViewMode
11618
+ setViewMode,
11619
+ researchSources: state.researchSources
11491
11620
  };
11492
11621
  }
11493
11622
  function parseEventData(data) {
@@ -11546,7 +11675,8 @@ function AgentChat({
11546
11675
  onFileSelect,
11547
11676
  onMessagesChange,
11548
11677
  disabled,
11549
- onComposerError
11678
+ onComposerError,
11679
+ deepResearchEnabled = false
11550
11680
  }) {
11551
11681
  const [workspacePath, setWorkspacePath] = react.useState(null);
11552
11682
  const [mobileView, setMobileView] = react.useState("chat");
@@ -11569,7 +11699,8 @@ function AgentChat({
11569
11699
  const browserVisible = browserAvailable && showBrowser;
11570
11700
  const workspaceAvailable = !!sessionId;
11571
11701
  const workspaceVisible = workspaceAvailable && showWorkspace;
11572
- const rightStackVisible = browserVisible || workspaceVisible;
11702
+ const sourcesVisible = runtime.researchSources.length > 0;
11703
+ const rightStackVisible = browserVisible || workspaceVisible || sourcesVisible;
11573
11704
  const bothPanesVisible = browserVisible && workspaceVisible;
11574
11705
  react.useEffect(() => {
11575
11706
  onMessagesChange?.(runtime.messages);
@@ -11684,7 +11815,8 @@ function AgentChat({
11684
11815
  canShowBrowser: browserAvailable,
11685
11816
  canShowWorkspace: workspaceAvailable,
11686
11817
  viewMode: runtime.viewMode,
11687
- onViewModeChange: runtime.setViewMode
11818
+ onViewModeChange: runtime.setViewMode,
11819
+ deepResearchEnabled
11688
11820
  }
11689
11821
  )
11690
11822
  }
@@ -11744,6 +11876,20 @@ function AgentChat({
11744
11876
  }
11745
11877
  )
11746
11878
  }
11879
+ ),
11880
+ sourcesVisible && /* @__PURE__ */ jsxRuntime.jsx(
11881
+ "div",
11882
+ {
11883
+ "data-testid": "research-sources-panel-frame",
11884
+ className: cn(
11885
+ "min-h-0 w-full overflow-auto",
11886
+ // When other panes share the stack the sources
11887
+ // panel takes a fixed minimum so the source rows
11888
+ // stay readable; otherwise it fills the column.
11889
+ browserVisible || workspaceVisible ? "max-h-1/3 border-t border-line" : "h-full"
11890
+ ),
11891
+ children: /* @__PURE__ */ jsxRuntime.jsx(ResearchSourcesPanel, { sources: runtime.researchSources })
11892
+ }
11747
11893
  )
11748
11894
  ]
11749
11895
  }
@@ -12493,7 +12639,7 @@ function groupMissionTasksByBucket(tasks) {
12493
12639
  function deriveMissionWorkerActivityLabel(worker) {
12494
12640
  const kind = worker.latestEventKind;
12495
12641
  if (kind === "tool.call" && worker.latestEventSummary) {
12496
- return `tool: ${truncate3(worker.latestEventSummary, 80)}`;
12642
+ return `tool: ${truncate4(worker.latestEventSummary, 80)}`;
12497
12643
  }
12498
12644
  if (kind === "llm.response") {
12499
12645
  return "thinking";
@@ -12502,11 +12648,11 @@ function deriveMissionWorkerActivityLabel(worker) {
12502
12648
  return "received tool result";
12503
12649
  }
12504
12650
  if (kind && worker.latestEventSummary) {
12505
- return `${kind}: ${truncate3(worker.latestEventSummary, 60)}`;
12651
+ return `${kind}: ${truncate4(worker.latestEventSummary, 60)}`;
12506
12652
  }
12507
12653
  return worker.sessionStatus || "active";
12508
12654
  }
12509
- function truncate3(s, max) {
12655
+ function truncate4(s, max) {
12510
12656
  if (s.length <= max) return s;
12511
12657
  return `${s.slice(0, max - 1)}\u2026`;
12512
12658
  }
@@ -13494,6 +13640,50 @@ function MissionsPanel({
13494
13640
  )) })
13495
13641
  ] });
13496
13642
  }
13643
+ var CITATION_RE = /\[(S\d+(?:\s*,\s*S\d+)*)\]/g;
13644
+ function splitCitations(text) {
13645
+ if (!text) return [];
13646
+ const segments = [];
13647
+ let lastIndex = 0;
13648
+ for (const match of text.matchAll(CITATION_RE)) {
13649
+ const start = match.index ?? 0;
13650
+ if (start > lastIndex) {
13651
+ segments.push({ kind: "text", value: text.slice(lastIndex, start) });
13652
+ }
13653
+ for (const id of match[1].split(",")) {
13654
+ segments.push({ kind: "cite", value: id.trim() });
13655
+ }
13656
+ lastIndex = start + match[0].length;
13657
+ }
13658
+ if (lastIndex < text.length) {
13659
+ segments.push({ kind: "text", value: text.slice(lastIndex) });
13660
+ }
13661
+ return segments;
13662
+ }
13663
+ function CitationText({
13664
+ text,
13665
+ sources,
13666
+ onCitationClick
13667
+ }) {
13668
+ const byId = new Map(sources.map((s) => [s.sourceId, s]));
13669
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: splitCitations(text).map((seg, i) => {
13670
+ if (seg.kind === "text") {
13671
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: seg.value }, i);
13672
+ }
13673
+ const src = byId.get(seg.value);
13674
+ return /* @__PURE__ */ jsxRuntime.jsx(
13675
+ "button",
13676
+ {
13677
+ type: "button",
13678
+ title: src ? `${src.title} \u2014 ${src.url}` : seg.value,
13679
+ onClick: () => onCitationClick?.(seg.value),
13680
+ className: "mx-0.5 inline-flex items-center rounded-sm bg-muted px-1 text-[10px] font-semibold text-primary hover:bg-primary/10",
13681
+ children: seg.value
13682
+ },
13683
+ i
13684
+ );
13685
+ }) });
13686
+ }
13497
13687
  init_utils();
13498
13688
  var DEFAULT_LIMIT2 = 50;
13499
13689
  var DEFAULT_POLL_INTERVAL_MS3 = 3e4;
@@ -14172,16 +14362,19 @@ function SessionTreePanel({
14172
14362
  exports.ACTIVE_MISSION_STATUSES = ACTIVE_MISSION_STATUSES;
14173
14363
  exports.AgentChat = AgentChat;
14174
14364
  exports.AgentChatAdapterProvider = AgentChatAdapterProvider;
14365
+ exports.CitationText = CitationText;
14175
14366
  exports.InboxPanel = InboxPanel;
14176
14367
  exports.MessageResponse = MessageResponse;
14177
14368
  exports.MissionDashboard = MissionDashboard;
14178
14369
  exports.MissionsPanel = MissionsPanel;
14370
+ exports.ResearchSourcesPanel = ResearchSourcesPanel;
14179
14371
  exports.ScheduledWorkPanel = ScheduledWorkPanel;
14180
14372
  exports.SessionTreePanel = SessionTreePanel;
14181
14373
  exports.deriveMissionWorkerActivityLabel = deriveMissionWorkerActivityLabel;
14182
14374
  exports.groupMissionTasksByBucket = groupMissionTasksByBucket;
14183
14375
  exports.isTerminalMissionStatus = isTerminalMissionStatus;
14184
14376
  exports.missionTaskBucket = missionTaskBucket;
14377
+ exports.splitCitations = splitCitations;
14185
14378
  exports.useAgentChatAdapterContext = useAgentChatAdapterContext;
14186
14379
  exports.useAgentChatRuntime = useAgentChatRuntime;
14187
14380
  exports.useChatViewMode = useChatViewMode;