@invergent/agent-chat-react 2.4.12 → 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.js CHANGED
@@ -4216,6 +4216,51 @@ function DefaultToolBlock({ tc }) {
4216
4216
  ] })
4217
4217
  ] });
4218
4218
  }
4219
+ function ResearchOutlineBlock({ tc }) {
4220
+ const args = parseArgs(tc.args) ?? {};
4221
+ const result = tc.result ? parseArgs(tc.result) ?? {} : {};
4222
+ const outline = args.action === "set" ? args.outline ?? "" : result.outline ?? "";
4223
+ const sections = result.sections ?? [];
4224
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-sm border border-border bg-muted/40 p-2 text-xs", children: [
4225
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 font-semibold uppercase tracking-widest text-muted-foreground", children: [
4226
+ "Research outline",
4227
+ sections.length ? ` \xB7 ${sections.length} sections` : ""
4228
+ ] }),
4229
+ outline ? /* @__PURE__ */ jsx("pre", { className: "max-h-48 overflow-auto whitespace-pre-wrap font-mono text-[11px] leading-snug", children: outline }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "updated" })
4230
+ ] });
4231
+ }
4232
+ function ResearchMemoryBlock({ tc }) {
4233
+ const args = parseArgs(tc.args) ?? {};
4234
+ const result = tc.result ? parseArgs(tc.result) ?? {} : {};
4235
+ let label;
4236
+ if (args.action === "add") {
4237
+ const host = args.url ? hostname(args.url) : "";
4238
+ label = `Recorded source ${result.source_id ?? ""}${host ? ` \xB7 ${host}` : ""}`;
4239
+ } else if (args.action === "retrieve") {
4240
+ const n = result.sources?.length ?? 0;
4241
+ label = `Retrieved ${n} source${n === 1 ? "" : "s"}${args.query ? ` for "${truncate2(args.query)}"` : ""}`;
4242
+ } else if (args.action === "list") {
4243
+ const n = result.sources?.length ?? 0;
4244
+ label = `Listed ${n} source${n === 1 ? "" : "s"}`;
4245
+ } else {
4246
+ label = "research_memory";
4247
+ }
4248
+ return /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
4249
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: "research" }),
4250
+ " ",
4251
+ label
4252
+ ] });
4253
+ }
4254
+ function hostname(url) {
4255
+ try {
4256
+ return new URL(url).hostname.replace(/^www\./, "");
4257
+ } catch {
4258
+ return url;
4259
+ }
4260
+ }
4261
+ function truncate2(s, n = 40) {
4262
+ return s.length > n ? `${s.slice(0, n)}\u2026` : s;
4263
+ }
4219
4264
  function ToolCallBlock({
4220
4265
  tc,
4221
4266
  resolvedArtifactName,
@@ -4268,6 +4313,10 @@ function ToolCallBlock({
4268
4313
  return /* @__PURE__ */ jsx(DelegateToolBlock, { tc });
4269
4314
  case "memory":
4270
4315
  return /* @__PURE__ */ jsx(MemoryToolBlock, { tc });
4316
+ case "research_outline":
4317
+ return /* @__PURE__ */ jsx(ResearchOutlineBlock, { tc });
4318
+ case "research_memory":
4319
+ return /* @__PURE__ */ jsx(ResearchMemoryBlock, { tc });
4271
4320
  case "spawn_worker":
4272
4321
  case "send_worker_message":
4273
4322
  case "stop_worker":
@@ -5792,6 +5841,7 @@ function ChatComposerInner({
5792
5841
  onToggleWorkspace,
5793
5842
  canShowBrowser = false,
5794
5843
  canShowWorkspace = false,
5844
+ deepResearchEnabled = false,
5795
5845
  viewMode = "simple",
5796
5846
  onViewModeChange
5797
5847
  }) {
@@ -5821,24 +5871,34 @@ function ChatComposerInner({
5821
5871
  }
5822
5872
  }, [adapter, showSlashMenu, menuMode]);
5823
5873
  const builtinCommands = useMemo(
5824
- () => [
5825
- { value: "/clear", label: "/clear", description: "Clear conversation" },
5826
- { value: "/compress", label: "/compress", description: "Compress context" },
5827
- { value: "/goal", label: "/goal", description: "Define an outcome goal" },
5828
- { value: "/goal status", label: "/goal status", description: "Show outcome goal status" },
5829
- { value: "/goal pause", label: "/goal pause", description: "Pause automatic goal continuation" },
5830
- { value: "/goal resume", label: "/goal resume", description: "Resume a paused goal" },
5831
- { value: "/goal clear", label: "/goal clear", description: "Clear the current goal" },
5832
- { value: "/mission ", label: "/mission", description: "Start an orchestrated rubric-judged mission" },
5833
- { value: "/mission status", label: "/mission status", description: "Show current mission status" },
5834
- { value: "/mission pause", label: "/mission pause", description: "Pause the mission evaluator" },
5835
- { value: "/mission resume", label: "/mission resume", description: "Resume a paused mission" },
5836
- { value: "/mission cancel", label: "/mission cancel", description: "Cancel the mission" },
5837
- { value: "/loop", label: "/loop", description: "Schedule recurring prompt" },
5838
- { value: "/loop list", label: "/loop list", description: "List active loops" },
5839
- { value: "/loop cancel", label: "/loop cancel", description: "Cancel a loop by ID" }
5840
- ],
5841
- []
5874
+ () => {
5875
+ const base = [
5876
+ { value: "/clear", label: "/clear", description: "Clear conversation" },
5877
+ { value: "/compress", label: "/compress", description: "Compress context" },
5878
+ { value: "/goal", label: "/goal", description: "Define an outcome goal" },
5879
+ { value: "/goal status", label: "/goal status", description: "Show outcome goal status" },
5880
+ { value: "/goal pause", label: "/goal pause", description: "Pause automatic goal continuation" },
5881
+ { value: "/goal resume", label: "/goal resume", description: "Resume a paused goal" },
5882
+ { value: "/goal clear", label: "/goal clear", description: "Clear the current goal" },
5883
+ { value: "/mission ", label: "/mission", description: "Start an orchestrated rubric-judged mission" },
5884
+ { value: "/mission status", label: "/mission status", description: "Show current mission status" },
5885
+ { value: "/mission pause", label: "/mission pause", description: "Pause the mission evaluator" },
5886
+ { value: "/mission resume", label: "/mission resume", description: "Resume a paused mission" },
5887
+ { value: "/mission cancel", label: "/mission cancel", description: "Cancel the mission" },
5888
+ { value: "/loop", label: "/loop", description: "Schedule recurring prompt" },
5889
+ { value: "/loop list", label: "/loop list", description: "List active loops" },
5890
+ { value: "/loop cancel", label: "/loop cancel", description: "Cancel a loop by ID" }
5891
+ ];
5892
+ if (deepResearchEnabled) {
5893
+ base.push({
5894
+ value: "/deep-research ",
5895
+ label: "/deep-research",
5896
+ description: "Delegate a topic to the deep-research workflow"
5897
+ });
5898
+ }
5899
+ return base;
5900
+ },
5901
+ [deepResearchEnabled]
5842
5902
  );
5843
5903
  const scheduledExamples = useMemo(
5844
5904
  () => [
@@ -7958,6 +8018,20 @@ function ArtifactRow({
7958
8018
  ) });
7959
8019
  }
7960
8020
  var WORKING_ON_IT_DELAY_MS = 250;
8021
+ function hasUserFacingAskContent(msg) {
8022
+ return !!msg.toolCalls?.some((tc) => tc.toolName === "ask_user_question");
8023
+ }
8024
+ function isAwaitingUserInput(messages) {
8025
+ for (let i = messages.length - 1; i >= 0; i--) {
8026
+ const m = messages[i];
8027
+ if (m.role === "user") return false;
8028
+ if (m.role !== "assistant") continue;
8029
+ return !!m.toolCalls?.some(
8030
+ (tc) => tc.toolName === "ask_user_question" && tc.status === "running"
8031
+ );
8032
+ }
8033
+ return false;
8034
+ }
7961
8035
  function messageToEntries(msg, isLast, artifactFallbacks) {
7962
8036
  if (msg.role === "system") {
7963
8037
  if (msg.systemKind === "skill_invoked") {
@@ -7982,6 +8056,14 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
7982
8056
  version
7983
8057
  }];
7984
8058
  }
8059
+ if (msg.systemKind === "browser_marker" || msg.systemKind === "browser_marker_warning") {
8060
+ return [{
8061
+ kind: "browser_marker",
8062
+ key: msg.id,
8063
+ content: msg.content,
8064
+ warning: msg.systemKind === "browser_marker_warning"
8065
+ }];
8066
+ }
7985
8067
  return [];
7986
8068
  }
7987
8069
  const entries = [];
@@ -7998,12 +8080,22 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
7998
8080
  });
7999
8081
  }
8000
8082
  if (hasContent && hasToolCalls) {
8001
- entries.push({
8002
- kind: "narration",
8003
- key: `${msg.id}-narration`,
8004
- text: msg.content,
8005
- isStreaming: isStreaming && !effectiveHasContent
8006
- });
8083
+ if (hasUserFacingAskContent(msg)) {
8084
+ entries.push({
8085
+ kind: "text",
8086
+ key: `${msg.id}-text`,
8087
+ content: msg.content,
8088
+ isStreaming: false,
8089
+ msg
8090
+ });
8091
+ } else {
8092
+ entries.push({
8093
+ kind: "narration",
8094
+ key: `${msg.id}-narration`,
8095
+ text: msg.content,
8096
+ isStreaming: isStreaming && !effectiveHasContent
8097
+ });
8098
+ }
8007
8099
  }
8008
8100
  if (hasToolCalls) {
8009
8101
  for (const tc of msg.toolCalls) {
@@ -8167,6 +8259,23 @@ function OrphanSystemMarker({
8167
8259
  }
8168
8260
  );
8169
8261
  }
8262
+ if (message.systemKind === "browser_marker" || message.systemKind === "browser_marker_warning") {
8263
+ const warning = message.systemKind === "browser_marker_warning";
8264
+ return /* @__PURE__ */ jsxs(
8265
+ "div",
8266
+ {
8267
+ className: cn(
8268
+ "my-2 flex items-center gap-2 px-4 text-xs",
8269
+ warning ? "text-amber-600" : "text-foreground/60"
8270
+ ),
8271
+ role: warning ? "alert" : "status",
8272
+ children: [
8273
+ warning ? /* @__PURE__ */ jsx(AlertTriangle, { className: "size-3.5 shrink-0", "aria-hidden": true }) : /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-foreground/30" }),
8274
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: message.content })
8275
+ ]
8276
+ }
8277
+ );
8278
+ }
8170
8279
  if (message.systemKind === "error" && message.errorInfo) {
8171
8280
  return /* @__PURE__ */ jsx("div", { className: "mx-auto my-2 w-full max-w-4xl", children: /* @__PURE__ */ jsx(ErrorMessage, { errorInfo: message.errorInfo, onRetry }) });
8172
8281
  }
@@ -8267,6 +8376,36 @@ function TimelineEntryItem({
8267
8376
  if (entry.kind === "narration") {
8268
8377
  return /* @__PURE__ */ jsx(NarrationEntry, { entry, step });
8269
8378
  }
8379
+ if (entry.kind === "browser_marker") {
8380
+ return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
8381
+ /* @__PURE__ */ jsxs(TimelineHeader, { children: [
8382
+ /* @__PURE__ */ jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8383
+ /* @__PURE__ */ jsx(
8384
+ TimelineIndicator,
8385
+ {
8386
+ className: cn(
8387
+ "size-2 border-none",
8388
+ entry.warning ? "bg-amber-500" : "bg-foreground/30"
8389
+ )
8390
+ }
8391
+ )
8392
+ ] }),
8393
+ /* @__PURE__ */ jsx(TimelineContent, { children: /* @__PURE__ */ jsxs(
8394
+ "div",
8395
+ {
8396
+ className: cn(
8397
+ "flex items-center gap-1.5 py-1 text-sm",
8398
+ entry.warning ? "text-amber-600" : "text-foreground/60"
8399
+ ),
8400
+ role: entry.warning ? "alert" : "status",
8401
+ children: [
8402
+ entry.warning && /* @__PURE__ */ jsx(AlertTriangle, { className: "size-3.5 shrink-0", "aria-hidden": true }),
8403
+ /* @__PURE__ */ jsx("span", { children: entry.content })
8404
+ ]
8405
+ }
8406
+ ) })
8407
+ ] });
8408
+ }
8270
8409
  if (entry.kind === "skill_invoked") {
8271
8410
  return /* @__PURE__ */ jsxs(TimelineItem, { step, children: [
8272
8411
  /* @__PURE__ */ jsxs(TimelineHeader, { children: [
@@ -8424,11 +8563,11 @@ function extractToolDetail(tc) {
8424
8563
  }
8425
8564
  case "terminal": {
8426
8565
  const cmd = stringArg("command");
8427
- return cmd ? truncate2(cmd, 40) : null;
8566
+ return cmd ? truncate3(cmd, 40) : null;
8428
8567
  }
8429
8568
  case "execute_code": {
8430
8569
  const code3 = stringArg("code");
8431
- return code3 ? truncate2(code3.split("\n")[0] ?? "", 40) : null;
8570
+ return code3 ? truncate3(code3.split("\n")[0] ?? "", 40) : null;
8432
8571
  }
8433
8572
  case "web_search":
8434
8573
  case "web_crawl":
@@ -8453,7 +8592,7 @@ function lastPathSegment(path) {
8453
8592
  const idx = cleaned.lastIndexOf("/");
8454
8593
  return idx >= 0 ? cleaned.slice(idx + 1) : cleaned;
8455
8594
  }
8456
- function truncate2(s, max) {
8595
+ function truncate3(s, max) {
8457
8596
  return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
8458
8597
  }
8459
8598
  var _SIMPLE_MODE_HIDDEN_TOOLS = /* @__PURE__ */ new Set([
@@ -8646,13 +8785,13 @@ function IterationGroup({
8646
8785
  }) {
8647
8786
  const summary = message.iterationSummary?.summary;
8648
8787
  const [open, setOpen] = useState(false);
8788
+ const askCall = (message.toolCalls ?? []).find(
8789
+ (tc) => tc.toolName === "ask_user_question"
8790
+ );
8791
+ if (askCall) {
8792
+ return /* @__PURE__ */ jsx("div", { className: "px-1 py-0.5", children: /* @__PURE__ */ jsx(AskUserQuestionToolBlock, { tc: askCall }) });
8793
+ }
8649
8794
  if (isIterationLive(message)) {
8650
- const askCall = (message.toolCalls ?? []).find(
8651
- (tc) => tc.toolName === "ask_user_question" && tc.status === "running"
8652
- );
8653
- if (askCall) {
8654
- return /* @__PURE__ */ jsx("div", { className: "px-1 py-0.5", children: /* @__PURE__ */ jsx(AskUserQuestionToolBlock, { tc: askCall }) });
8655
- }
8656
8795
  const label2 = liveIterationLabel(message);
8657
8796
  return /* @__PURE__ */ jsx("div", { className: "py-0.5 text-sm", children: /* @__PURE__ */ jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: label2 }) });
8658
8797
  }
@@ -8711,6 +8850,14 @@ function deriveFileArtifactsFromMessages(messages) {
8711
8850
  const seen = /* @__PURE__ */ new Set();
8712
8851
  const out = [];
8713
8852
  for (const msg of messages) {
8853
+ if (msg.role === "system") {
8854
+ if (msg.systemKind !== "artifact") continue;
8855
+ const { artifactId, name } = unpackArtifactMeta(msg.systemMeta, msg.content);
8856
+ if (!artifactId || seen.has(`artifact:${artifactId}`)) continue;
8857
+ seen.add(`artifact:${artifactId}`);
8858
+ out.push({ kind: "artifact", label: name, ref: artifactId });
8859
+ continue;
8860
+ }
8714
8861
  if (msg.role !== "assistant" || !msg.toolCalls) continue;
8715
8862
  for (const tc of msg.toolCalls) {
8716
8863
  const status = effectiveStatus(tc);
@@ -8730,16 +8877,6 @@ function deriveFileArtifactsFromMessages(messages) {
8730
8877
  });
8731
8878
  continue;
8732
8879
  }
8733
- if (tc.toolName === "create_artifact") {
8734
- const name = typeof args.name === "string" ? args.name : "";
8735
- if (!name || seen.has(`artifact:${name}`)) continue;
8736
- seen.add(`artifact:${name}`);
8737
- out.push({
8738
- kind: "artifact",
8739
- label: name,
8740
- ref: name
8741
- });
8742
- }
8743
8880
  }
8744
8881
  }
8745
8882
  return out;
@@ -8765,15 +8902,18 @@ function SimpleAssistantGroup({
8765
8902
  if (fromHarness && fromHarness.artifacts.length > 0) {
8766
8903
  return fromHarness;
8767
8904
  }
8905
+ if (turnComplete) {
8906
+ const derived = deriveFileArtifactsFromMessages(messages);
8907
+ if (derived.length > 0) {
8908
+ return {
8909
+ turnId: fromHarness?.turnId ?? "",
8910
+ recap: summaryPref === "show" ? fromHarness?.recap ?? "" : "",
8911
+ artifacts: derived
8912
+ };
8913
+ }
8914
+ }
8768
8915
  if (summaryPref === "hide") return null;
8769
- if (!turnComplete) return fromHarness;
8770
- const derived = deriveFileArtifactsFromMessages(messages);
8771
- if (derived.length === 0) return fromHarness;
8772
- return {
8773
- turnId: fromHarness?.turnId ?? "",
8774
- recap: fromHarness?.recap ?? "",
8775
- artifacts: derived
8776
- };
8916
+ return fromHarness;
8777
8917
  })();
8778
8918
  return /* @__PURE__ */ jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxs(MessageContent, { children: [
8779
8919
  /* @__PURE__ */ jsx("div", { className: "space-y-2", children: messages.map((message) => {
@@ -8791,10 +8931,11 @@ function SimpleAssistantGroup({
8791
8931
  }
8792
8932
  if (message === tail && tailIsTextOnly && finalText) return null;
8793
8933
  const rawContent = (message.content ?? "").trim();
8794
- const { action, inferredNarration } = stripAndParseNextAction(rawContent);
8934
+ const { action, inferredNarration, cleaned } = stripAndParseNextAction(rawContent);
8795
8935
  const narrationLine = action ? action.body.trim().toLowerCase() === "done" ? null : action.body : inferredNarration;
8936
+ const askBody = hasUserFacingAskContent(message) ? cleaned : null;
8796
8937
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
8797
- narrationLine && /* @__PURE__ */ jsx("p", { className: "text-sm text-foreground", children: narrationLine }),
8938
+ askBody ? /* @__PURE__ */ jsx(MessageResponse, { children: askBody }) : narrationLine ? /* @__PURE__ */ jsx("p", { className: "text-sm text-foreground", children: narrationLine }) : null,
8798
8939
  /* @__PURE__ */ jsx(
8799
8940
  IterationGroup,
8800
8941
  {
@@ -8859,7 +9000,7 @@ function AssistantGroup({
8859
9000
  entries = groupWebSearchEntries(entries);
8860
9001
  for (let i = entries.length - 1; i >= 0; i--) {
8861
9002
  const entry = entries[i];
8862
- if (entry?.kind === "text") {
9003
+ if (entry?.kind === "text" && !hasUserFacingAskContent(entry.msg)) {
8863
9004
  entries[i] = { ...entry, isFinalTurnText: true };
8864
9005
  break;
8865
9006
  }
@@ -8952,10 +9093,14 @@ function ChatThread({
8952
9093
  canShowBrowser = false,
8953
9094
  canShowWorkspace = false,
8954
9095
  viewMode = "simple",
8955
- onViewModeChange
9096
+ onViewModeChange,
9097
+ deepResearchEnabled = false
8956
9098
  }) {
8957
9099
  const groups = useMemo(() => groupMessages(messages), [messages]);
8958
- const showWorkingOnIt = useDelayedRunningIndicator(isRunning);
9100
+ const awaitingInput = useMemo(() => isAwaitingUserInput(messages), [messages]);
9101
+ const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput;
9102
+ const composerDisabled = disabled || awaitingInput;
9103
+ const composerDisabledReason = awaitingInput ? "Answer the question above to continue." : disabledReason;
8959
9104
  const artifactFallbacks = useMemo(() => {
8960
9105
  const fallbacks = {};
8961
9106
  const createArtifactToolCallIds = [];
@@ -9058,12 +9203,12 @@ function ChatThread({
9058
9203
  ] }),
9059
9204
  /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
9060
9205
  retryIndicator && /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsx(RetryBanner, { indicator: retryIndicator }) }),
9061
- disabled && disabledReason ? /* @__PURE__ */ jsx(
9206
+ composerDisabled && composerDisabledReason ? /* @__PURE__ */ jsx(
9062
9207
  "div",
9063
9208
  {
9064
9209
  className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-foreground/60",
9065
9210
  role: "status",
9066
- children: disabledReason
9211
+ children: composerDisabledReason
9067
9212
  }
9068
9213
  ) : /* @__PURE__ */ jsx(
9069
9214
  ChatComposer,
@@ -9071,8 +9216,8 @@ function ChatThread({
9071
9216
  onSend,
9072
9217
  onStop,
9073
9218
  isRunning,
9074
- disabled,
9075
- disabledReason,
9219
+ disabled: composerDisabled,
9220
+ disabledReason: composerDisabledReason,
9076
9221
  tokenUsage,
9077
9222
  onComposerError,
9078
9223
  showBrowser,
@@ -9082,12 +9227,51 @@ function ChatThread({
9082
9227
  canShowBrowser,
9083
9228
  canShowWorkspace,
9084
9229
  viewMode,
9085
- onViewModeChange
9230
+ onViewModeChange,
9231
+ deepResearchEnabled
9086
9232
  }
9087
9233
  )
9088
9234
  ] })
9089
9235
  ] });
9090
9236
  }
9237
+ function ResearchSourcesPanel({
9238
+ sources
9239
+ }) {
9240
+ if (sources.length === 0) {
9241
+ return /* @__PURE__ */ 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." });
9242
+ }
9243
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 p-2", children: [
9244
+ /* @__PURE__ */ jsxs("div", { className: "px-1 pb-1 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: [
9245
+ "Sources \xB7 ",
9246
+ sources.length
9247
+ ] }),
9248
+ sources.map((s) => /* @__PURE__ */ jsxs(
9249
+ "a",
9250
+ {
9251
+ id: `source-${s.sourceId}`,
9252
+ href: s.url,
9253
+ target: "_blank",
9254
+ rel: "noreferrer",
9255
+ className: "group flex items-baseline gap-2 rounded-sm px-1 py-1 hover:bg-muted",
9256
+ children: [
9257
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] font-semibold text-primary", children: s.sourceId }),
9258
+ /* @__PURE__ */ jsxs("span", { className: "flex flex-col overflow-hidden", children: [
9259
+ /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-foreground group-hover:underline", children: s.title || s.url }),
9260
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[10px] text-muted-foreground", children: hostname2(s.url) })
9261
+ ] })
9262
+ ]
9263
+ },
9264
+ s.sourceId
9265
+ ))
9266
+ ] });
9267
+ }
9268
+ function hostname2(url) {
9269
+ try {
9270
+ return new URL(url).hostname.replace(/^www\./, "");
9271
+ } catch {
9272
+ return url;
9273
+ }
9274
+ }
9091
9275
  var noop = () => {
9092
9276
  };
9093
9277
  var FileTreeContext = createContext({
@@ -9825,7 +10009,8 @@ function createInitialAgentChatState(options = {}) {
9825
10009
  terminal: false,
9826
10010
  workspaceRefreshKey: 0,
9827
10011
  browser: null,
9828
- viewMode: options.viewMode ?? "simple"
10012
+ viewMode: options.viewMode ?? "simple",
10013
+ researchSources: []
9829
10014
  };
9830
10015
  }
9831
10016
  function applyAgentChatEvent(state, event) {
@@ -9911,11 +10096,12 @@ function applyAgentChatEvent(state, event) {
9911
10096
  const toolName = findToolNameById(nextState.messages, toolCallId);
9912
10097
  const messages = applyToolResult(nextState.messages, event.data);
9913
10098
  const mutatesWorkspace = toolName !== null && WORKSPACE_MUTATING_TOOLS.has(toolName);
9914
- return {
10099
+ const withResult = {
9915
10100
  ...nextState,
9916
10101
  messages,
9917
10102
  workspaceRefreshKey: mutatesWorkspace ? nextState.workspaceRefreshKey + 1 : nextState.workspaceRefreshKey
9918
10103
  };
10104
+ return collectResearchSource(withResult, toolName, event.data);
9919
10105
  }
9920
10106
  case "harness.wake":
9921
10107
  case "llm.request":
@@ -10647,6 +10833,32 @@ function findToolNameById(messages, toolCallId) {
10647
10833
  }
10648
10834
  return null;
10649
10835
  }
10836
+ function collectResearchSource(state, toolName, data) {
10837
+ if (toolName !== "research_memory") return state;
10838
+ const rawResult = data.content ?? data.result;
10839
+ const resultText = typeof rawResult === "string" ? rawResult : JSON.stringify(rawResult ?? {});
10840
+ let parsed;
10841
+ try {
10842
+ parsed = JSON.parse(resultText);
10843
+ } catch {
10844
+ return state;
10845
+ }
10846
+ if (!parsed.success || !parsed.source_id || !parsed.url) return state;
10847
+ if (state.researchSources.some((s) => s.sourceId === parsed.source_id)) {
10848
+ return state;
10849
+ }
10850
+ return {
10851
+ ...state,
10852
+ researchSources: [
10853
+ ...state.researchSources,
10854
+ {
10855
+ sourceId: parsed.source_id,
10856
+ url: parsed.url,
10857
+ title: parsed.title ?? ""
10858
+ }
10859
+ ]
10860
+ };
10861
+ }
10650
10862
  function findLatestConsultExpertCall(messages) {
10651
10863
  for (let i = messages.length - 1; i >= 0; i--) {
10652
10864
  const msg = messages[i];
@@ -11096,7 +11308,8 @@ function useAgentChatRuntime({
11096
11308
  markSending,
11097
11309
  markSendError,
11098
11310
  viewMode: state.viewMode,
11099
- setViewMode
11311
+ setViewMode,
11312
+ researchSources: state.researchSources
11100
11313
  };
11101
11314
  }
11102
11315
  function parseEventData(data) {
@@ -11155,7 +11368,8 @@ function AgentChat({
11155
11368
  onFileSelect,
11156
11369
  onMessagesChange,
11157
11370
  disabled,
11158
- onComposerError
11371
+ onComposerError,
11372
+ deepResearchEnabled = false
11159
11373
  }) {
11160
11374
  const [workspacePath, setWorkspacePath] = useState(null);
11161
11375
  const [mobileView, setMobileView] = useState("chat");
@@ -11178,7 +11392,8 @@ function AgentChat({
11178
11392
  const browserVisible = browserAvailable && showBrowser;
11179
11393
  const workspaceAvailable = !!sessionId;
11180
11394
  const workspaceVisible = workspaceAvailable && showWorkspace;
11181
- const rightStackVisible = browserVisible || workspaceVisible;
11395
+ const sourcesVisible = runtime.researchSources.length > 0;
11396
+ const rightStackVisible = browserVisible || workspaceVisible || sourcesVisible;
11182
11397
  const bothPanesVisible = browserVisible && workspaceVisible;
11183
11398
  useEffect(() => {
11184
11399
  onMessagesChange?.(runtime.messages);
@@ -11293,7 +11508,8 @@ function AgentChat({
11293
11508
  canShowBrowser: browserAvailable,
11294
11509
  canShowWorkspace: workspaceAvailable,
11295
11510
  viewMode: runtime.viewMode,
11296
- onViewModeChange: runtime.setViewMode
11511
+ onViewModeChange: runtime.setViewMode,
11512
+ deepResearchEnabled
11297
11513
  }
11298
11514
  )
11299
11515
  }
@@ -11353,6 +11569,20 @@ function AgentChat({
11353
11569
  }
11354
11570
  )
11355
11571
  }
11572
+ ),
11573
+ sourcesVisible && /* @__PURE__ */ jsx(
11574
+ "div",
11575
+ {
11576
+ "data-testid": "research-sources-panel-frame",
11577
+ className: cn(
11578
+ "min-h-0 w-full overflow-auto",
11579
+ // When other panes share the stack the sources
11580
+ // panel takes a fixed minimum so the source rows
11581
+ // stay readable; otherwise it fills the column.
11582
+ browserVisible || workspaceVisible ? "max-h-1/3 border-t border-line" : "h-full"
11583
+ ),
11584
+ children: /* @__PURE__ */ jsx(ResearchSourcesPanel, { sources: runtime.researchSources })
11585
+ }
11356
11586
  )
11357
11587
  ]
11358
11588
  }
@@ -12098,7 +12328,7 @@ function groupMissionTasksByBucket(tasks) {
12098
12328
  function deriveMissionWorkerActivityLabel(worker) {
12099
12329
  const kind = worker.latestEventKind;
12100
12330
  if (kind === "tool.call" && worker.latestEventSummary) {
12101
- return `tool: ${truncate3(worker.latestEventSummary, 80)}`;
12331
+ return `tool: ${truncate4(worker.latestEventSummary, 80)}`;
12102
12332
  }
12103
12333
  if (kind === "llm.response") {
12104
12334
  return "thinking";
@@ -12107,11 +12337,11 @@ function deriveMissionWorkerActivityLabel(worker) {
12107
12337
  return "received tool result";
12108
12338
  }
12109
12339
  if (kind && worker.latestEventSummary) {
12110
- return `${kind}: ${truncate3(worker.latestEventSummary, 60)}`;
12340
+ return `${kind}: ${truncate4(worker.latestEventSummary, 60)}`;
12111
12341
  }
12112
12342
  return worker.sessionStatus || "active";
12113
12343
  }
12114
- function truncate3(s, max) {
12344
+ function truncate4(s, max) {
12115
12345
  if (s.length <= max) return s;
12116
12346
  return `${s.slice(0, max - 1)}\u2026`;
12117
12347
  }
@@ -13098,6 +13328,50 @@ function MissionsPanel({
13098
13328
  )) })
13099
13329
  ] });
13100
13330
  }
13331
+ var CITATION_RE = /\[(S\d+(?:\s*,\s*S\d+)*)\]/g;
13332
+ function splitCitations(text) {
13333
+ if (!text) return [];
13334
+ const segments = [];
13335
+ let lastIndex = 0;
13336
+ for (const match of text.matchAll(CITATION_RE)) {
13337
+ const start = match.index ?? 0;
13338
+ if (start > lastIndex) {
13339
+ segments.push({ kind: "text", value: text.slice(lastIndex, start) });
13340
+ }
13341
+ for (const id of match[1].split(",")) {
13342
+ segments.push({ kind: "cite", value: id.trim() });
13343
+ }
13344
+ lastIndex = start + match[0].length;
13345
+ }
13346
+ if (lastIndex < text.length) {
13347
+ segments.push({ kind: "text", value: text.slice(lastIndex) });
13348
+ }
13349
+ return segments;
13350
+ }
13351
+ function CitationText({
13352
+ text,
13353
+ sources,
13354
+ onCitationClick
13355
+ }) {
13356
+ const byId = new Map(sources.map((s) => [s.sourceId, s]));
13357
+ return /* @__PURE__ */ jsx(Fragment, { children: splitCitations(text).map((seg, i) => {
13358
+ if (seg.kind === "text") {
13359
+ return /* @__PURE__ */ jsx("span", { children: seg.value }, i);
13360
+ }
13361
+ const src = byId.get(seg.value);
13362
+ return /* @__PURE__ */ jsx(
13363
+ "button",
13364
+ {
13365
+ type: "button",
13366
+ title: src ? `${src.title} \u2014 ${src.url}` : seg.value,
13367
+ onClick: () => onCitationClick?.(seg.value),
13368
+ className: "mx-0.5 inline-flex items-center rounded-sm bg-muted px-1 text-[10px] font-semibold text-primary hover:bg-primary/10",
13369
+ children: seg.value
13370
+ },
13371
+ i
13372
+ );
13373
+ }) });
13374
+ }
13101
13375
  var DEFAULT_LIMIT2 = 50;
13102
13376
  var DEFAULT_POLL_INTERVAL_MS3 = 3e4;
13103
13377
  function fingerprint2(items) {
@@ -13771,6 +14045,6 @@ function SessionTreePanel({
13771
14045
  ] });
13772
14046
  }
13773
14047
 
13774
- export { ACTIVE_MISSION_STATUSES, AgentChat, AgentChatAdapterProvider, InboxPanel, MessageResponse, MissionDashboard, MissionsPanel, ScheduledWorkPanel, SessionTreePanel, deriveMissionWorkerActivityLabel, groupMissionTasksByBucket, isTerminalMissionStatus, missionTaskBucket, useAgentChatAdapterContext, useAgentChatRuntime, useChatViewMode, useInboxUnreadCount };
14048
+ export { ACTIVE_MISSION_STATUSES, AgentChat, AgentChatAdapterProvider, CitationText, InboxPanel, MessageResponse, MissionDashboard, MissionsPanel, ResearchSourcesPanel, ScheduledWorkPanel, SessionTreePanel, deriveMissionWorkerActivityLabel, groupMissionTasksByBucket, isTerminalMissionStatus, missionTaskBucket, splitCitations, useAgentChatAdapterContext, useAgentChatRuntime, useChatViewMode, useInboxUnreadCount };
13775
14049
  //# sourceMappingURL=index.js.map
13776
14050
  //# sourceMappingURL=index.js.map