@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.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
  () => [
@@ -8256,6 +8316,20 @@ function ArtifactRow({
8256
8316
  // src/components/chat/chat-thread.tsx
8257
8317
  init_utils();
8258
8318
  var WORKING_ON_IT_DELAY_MS = 250;
8319
+ function hasUserFacingAskContent(msg) {
8320
+ return !!msg.toolCalls?.some((tc) => tc.toolName === "ask_user_question");
8321
+ }
8322
+ function isAwaitingUserInput(messages) {
8323
+ for (let i = messages.length - 1; i >= 0; i--) {
8324
+ const m = messages[i];
8325
+ if (m.role === "user") return false;
8326
+ if (m.role !== "assistant") continue;
8327
+ return !!m.toolCalls?.some(
8328
+ (tc) => tc.toolName === "ask_user_question" && tc.status === "running"
8329
+ );
8330
+ }
8331
+ return false;
8332
+ }
8259
8333
  function messageToEntries(msg, isLast, artifactFallbacks) {
8260
8334
  if (msg.role === "system") {
8261
8335
  if (msg.systemKind === "skill_invoked") {
@@ -8280,6 +8354,14 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
8280
8354
  version
8281
8355
  }];
8282
8356
  }
8357
+ if (msg.systemKind === "browser_marker" || msg.systemKind === "browser_marker_warning") {
8358
+ return [{
8359
+ kind: "browser_marker",
8360
+ key: msg.id,
8361
+ content: msg.content,
8362
+ warning: msg.systemKind === "browser_marker_warning"
8363
+ }];
8364
+ }
8283
8365
  return [];
8284
8366
  }
8285
8367
  const entries = [];
@@ -8296,12 +8378,22 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
8296
8378
  });
8297
8379
  }
8298
8380
  if (hasContent && hasToolCalls) {
8299
- entries.push({
8300
- kind: "narration",
8301
- key: `${msg.id}-narration`,
8302
- text: msg.content,
8303
- isStreaming: isStreaming && !effectiveHasContent
8304
- });
8381
+ if (hasUserFacingAskContent(msg)) {
8382
+ entries.push({
8383
+ kind: "text",
8384
+ key: `${msg.id}-text`,
8385
+ content: msg.content,
8386
+ isStreaming: false,
8387
+ msg
8388
+ });
8389
+ } else {
8390
+ entries.push({
8391
+ kind: "narration",
8392
+ key: `${msg.id}-narration`,
8393
+ text: msg.content,
8394
+ isStreaming: isStreaming && !effectiveHasContent
8395
+ });
8396
+ }
8305
8397
  }
8306
8398
  if (hasToolCalls) {
8307
8399
  for (const tc of msg.toolCalls) {
@@ -8465,6 +8557,23 @@ function OrphanSystemMarker({
8465
8557
  }
8466
8558
  );
8467
8559
  }
8560
+ if (message.systemKind === "browser_marker" || message.systemKind === "browser_marker_warning") {
8561
+ const warning = message.systemKind === "browser_marker_warning";
8562
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8563
+ "div",
8564
+ {
8565
+ className: cn(
8566
+ "my-2 flex items-center gap-2 px-4 text-xs",
8567
+ warning ? "text-amber-600" : "text-foreground/60"
8568
+ ),
8569
+ role: warning ? "alert" : "status",
8570
+ children: [
8571
+ warning ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "size-3.5 shrink-0", "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-2 rounded-full bg-foreground/30" }),
8572
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: message.content })
8573
+ ]
8574
+ }
8575
+ );
8576
+ }
8468
8577
  if (message.systemKind === "error" && message.errorInfo) {
8469
8578
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto my-2 w-full max-w-4xl", children: /* @__PURE__ */ jsxRuntime.jsx(ErrorMessage, { errorInfo: message.errorInfo, onRetry }) });
8470
8579
  }
@@ -8565,6 +8674,36 @@ function TimelineEntryItem({
8565
8674
  if (entry.kind === "narration") {
8566
8675
  return /* @__PURE__ */ jsxRuntime.jsx(NarrationEntry, { entry, step });
8567
8676
  }
8677
+ if (entry.kind === "browser_marker") {
8678
+ return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
8679
+ /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
8680
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
8681
+ /* @__PURE__ */ jsxRuntime.jsx(
8682
+ TimelineIndicator,
8683
+ {
8684
+ className: cn(
8685
+ "size-2 border-none",
8686
+ entry.warning ? "bg-amber-500" : "bg-foreground/30"
8687
+ )
8688
+ }
8689
+ )
8690
+ ] }),
8691
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(
8692
+ "div",
8693
+ {
8694
+ className: cn(
8695
+ "flex items-center gap-1.5 py-1 text-sm",
8696
+ entry.warning ? "text-amber-600" : "text-foreground/60"
8697
+ ),
8698
+ role: entry.warning ? "alert" : "status",
8699
+ children: [
8700
+ entry.warning && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "size-3.5 shrink-0", "aria-hidden": true }),
8701
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: entry.content })
8702
+ ]
8703
+ }
8704
+ ) })
8705
+ ] });
8706
+ }
8568
8707
  if (entry.kind === "skill_invoked") {
8569
8708
  return /* @__PURE__ */ jsxRuntime.jsxs(TimelineItem, { step, children: [
8570
8709
  /* @__PURE__ */ jsxRuntime.jsxs(TimelineHeader, { children: [
@@ -8722,11 +8861,11 @@ function extractToolDetail(tc) {
8722
8861
  }
8723
8862
  case "terminal": {
8724
8863
  const cmd = stringArg("command");
8725
- return cmd ? truncate2(cmd, 40) : null;
8864
+ return cmd ? truncate3(cmd, 40) : null;
8726
8865
  }
8727
8866
  case "execute_code": {
8728
8867
  const code3 = stringArg("code");
8729
- return code3 ? truncate2(code3.split("\n")[0] ?? "", 40) : null;
8868
+ return code3 ? truncate3(code3.split("\n")[0] ?? "", 40) : null;
8730
8869
  }
8731
8870
  case "web_search":
8732
8871
  case "web_crawl":
@@ -8751,7 +8890,7 @@ function lastPathSegment(path) {
8751
8890
  const idx = cleaned.lastIndexOf("/");
8752
8891
  return idx >= 0 ? cleaned.slice(idx + 1) : cleaned;
8753
8892
  }
8754
- function truncate2(s, max) {
8893
+ function truncate3(s, max) {
8755
8894
  return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
8756
8895
  }
8757
8896
  var _SIMPLE_MODE_HIDDEN_TOOLS = /* @__PURE__ */ new Set([
@@ -8944,13 +9083,13 @@ function IterationGroup({
8944
9083
  }) {
8945
9084
  const summary = message.iterationSummary?.summary;
8946
9085
  const [open, setOpen] = react.useState(false);
9086
+ const askCall = (message.toolCalls ?? []).find(
9087
+ (tc) => tc.toolName === "ask_user_question"
9088
+ );
9089
+ if (askCall) {
9090
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 py-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(AskUserQuestionToolBlock, { tc: askCall }) });
9091
+ }
8947
9092
  if (isIterationLive(message)) {
8948
- const askCall = (message.toolCalls ?? []).find(
8949
- (tc) => tc.toolName === "ask_user_question" && tc.status === "running"
8950
- );
8951
- if (askCall) {
8952
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-1 py-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(AskUserQuestionToolBlock, { tc: askCall }) });
8953
- }
8954
9093
  const label2 = liveIterationLabel(message);
8955
9094
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-0.5 text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(Shimmer, { duration: 3, spread: 3, className: "text-sm", children: label2 }) });
8956
9095
  }
@@ -9009,6 +9148,14 @@ function deriveFileArtifactsFromMessages(messages) {
9009
9148
  const seen = /* @__PURE__ */ new Set();
9010
9149
  const out = [];
9011
9150
  for (const msg of messages) {
9151
+ if (msg.role === "system") {
9152
+ if (msg.systemKind !== "artifact") continue;
9153
+ const { artifactId, name } = unpackArtifactMeta(msg.systemMeta, msg.content);
9154
+ if (!artifactId || seen.has(`artifact:${artifactId}`)) continue;
9155
+ seen.add(`artifact:${artifactId}`);
9156
+ out.push({ kind: "artifact", label: name, ref: artifactId });
9157
+ continue;
9158
+ }
9012
9159
  if (msg.role !== "assistant" || !msg.toolCalls) continue;
9013
9160
  for (const tc of msg.toolCalls) {
9014
9161
  const status = effectiveStatus(tc);
@@ -9028,16 +9175,6 @@ function deriveFileArtifactsFromMessages(messages) {
9028
9175
  });
9029
9176
  continue;
9030
9177
  }
9031
- if (tc.toolName === "create_artifact") {
9032
- const name = typeof args.name === "string" ? args.name : "";
9033
- if (!name || seen.has(`artifact:${name}`)) continue;
9034
- seen.add(`artifact:${name}`);
9035
- out.push({
9036
- kind: "artifact",
9037
- label: name,
9038
- ref: name
9039
- });
9040
- }
9041
9178
  }
9042
9179
  }
9043
9180
  return out;
@@ -9063,15 +9200,18 @@ function SimpleAssistantGroup({
9063
9200
  if (fromHarness && fromHarness.artifacts.length > 0) {
9064
9201
  return fromHarness;
9065
9202
  }
9203
+ if (turnComplete) {
9204
+ const derived = deriveFileArtifactsFromMessages(messages);
9205
+ if (derived.length > 0) {
9206
+ return {
9207
+ turnId: fromHarness?.turnId ?? "",
9208
+ recap: summaryPref === "show" ? fromHarness?.recap ?? "" : "",
9209
+ artifacts: derived
9210
+ };
9211
+ }
9212
+ }
9066
9213
  if (summaryPref === "hide") return null;
9067
- if (!turnComplete) return fromHarness;
9068
- const derived = deriveFileArtifactsFromMessages(messages);
9069
- if (derived.length === 0) return fromHarness;
9070
- return {
9071
- turnId: fromHarness?.turnId ?? "",
9072
- recap: fromHarness?.recap ?? "",
9073
- artifacts: derived
9074
- };
9214
+ return fromHarness;
9075
9215
  })();
9076
9216
  return /* @__PURE__ */ jsxRuntime.jsx(Message, { from: "assistant", children: /* @__PURE__ */ jsxRuntime.jsxs(MessageContent, { children: [
9077
9217
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: messages.map((message) => {
@@ -9089,10 +9229,11 @@ function SimpleAssistantGroup({
9089
9229
  }
9090
9230
  if (message === tail && tailIsTextOnly && finalText) return null;
9091
9231
  const rawContent = (message.content ?? "").trim();
9092
- const { action, inferredNarration } = stripAndParseNextAction(rawContent);
9232
+ const { action, inferredNarration, cleaned } = stripAndParseNextAction(rawContent);
9093
9233
  const narrationLine = action ? action.body.trim().toLowerCase() === "done" ? null : action.body : inferredNarration;
9234
+ const askBody = hasUserFacingAskContent(message) ? cleaned : null;
9094
9235
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
9095
- narrationLine && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground", children: narrationLine }),
9236
+ askBody ? /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: askBody }) : narrationLine ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground", children: narrationLine }) : null,
9096
9237
  /* @__PURE__ */ jsxRuntime.jsx(
9097
9238
  IterationGroup,
9098
9239
  {
@@ -9157,7 +9298,7 @@ function AssistantGroup({
9157
9298
  entries = groupWebSearchEntries(entries);
9158
9299
  for (let i = entries.length - 1; i >= 0; i--) {
9159
9300
  const entry = entries[i];
9160
- if (entry?.kind === "text") {
9301
+ if (entry?.kind === "text" && !hasUserFacingAskContent(entry.msg)) {
9161
9302
  entries[i] = { ...entry, isFinalTurnText: true };
9162
9303
  break;
9163
9304
  }
@@ -9250,10 +9391,14 @@ function ChatThread({
9250
9391
  canShowBrowser = false,
9251
9392
  canShowWorkspace = false,
9252
9393
  viewMode = "simple",
9253
- onViewModeChange
9394
+ onViewModeChange,
9395
+ deepResearchEnabled = false
9254
9396
  }) {
9255
9397
  const groups = react.useMemo(() => groupMessages(messages), [messages]);
9256
- const showWorkingOnIt = useDelayedRunningIndicator(isRunning);
9398
+ const awaitingInput = react.useMemo(() => isAwaitingUserInput(messages), [messages]);
9399
+ const showWorkingOnIt = useDelayedRunningIndicator(isRunning) && !awaitingInput;
9400
+ const composerDisabled = disabled || awaitingInput;
9401
+ const composerDisabledReason = awaitingInput ? "Answer the question above to continue." : disabledReason;
9257
9402
  const artifactFallbacks = react.useMemo(() => {
9258
9403
  const fallbacks = {};
9259
9404
  const createArtifactToolCallIds = [];
@@ -9356,12 +9501,12 @@ function ChatThread({
9356
9501
  ] }),
9357
9502
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto w-full max-w-4xl px-3 sm:px-6 pb-3 sm:pb-5 pt-3", children: [
9358
9503
  retryIndicator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(RetryBanner, { indicator: retryIndicator }) }),
9359
- disabled && disabledReason ? /* @__PURE__ */ jsxRuntime.jsx(
9504
+ composerDisabled && composerDisabledReason ? /* @__PURE__ */ jsxRuntime.jsx(
9360
9505
  "div",
9361
9506
  {
9362
9507
  className: "rounded border border-line bg-muted/40 px-3 py-2 text-sm text-foreground/60",
9363
9508
  role: "status",
9364
- children: disabledReason
9509
+ children: composerDisabledReason
9365
9510
  }
9366
9511
  ) : /* @__PURE__ */ jsxRuntime.jsx(
9367
9512
  ChatComposer,
@@ -9369,8 +9514,8 @@ function ChatThread({
9369
9514
  onSend,
9370
9515
  onStop,
9371
9516
  isRunning,
9372
- disabled,
9373
- disabledReason,
9517
+ disabled: composerDisabled,
9518
+ disabledReason: composerDisabledReason,
9374
9519
  tokenUsage,
9375
9520
  onComposerError,
9376
9521
  showBrowser,
@@ -9380,12 +9525,51 @@ function ChatThread({
9380
9525
  canShowBrowser,
9381
9526
  canShowWorkspace,
9382
9527
  viewMode,
9383
- onViewModeChange
9528
+ onViewModeChange,
9529
+ deepResearchEnabled
9384
9530
  }
9385
9531
  )
9386
9532
  ] })
9387
9533
  ] });
9388
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
+ }
9389
9573
 
9390
9574
  // src/components/ai-elements/file-tree.tsx
9391
9575
  init_utils();
@@ -10132,7 +10316,8 @@ function createInitialAgentChatState(options = {}) {
10132
10316
  terminal: false,
10133
10317
  workspaceRefreshKey: 0,
10134
10318
  browser: null,
10135
- viewMode: options.viewMode ?? "simple"
10319
+ viewMode: options.viewMode ?? "simple",
10320
+ researchSources: []
10136
10321
  };
10137
10322
  }
10138
10323
  function applyAgentChatEvent(state, event) {
@@ -10218,11 +10403,12 @@ function applyAgentChatEvent(state, event) {
10218
10403
  const toolName = findToolNameById(nextState.messages, toolCallId);
10219
10404
  const messages = applyToolResult(nextState.messages, event.data);
10220
10405
  const mutatesWorkspace = toolName !== null && WORKSPACE_MUTATING_TOOLS.has(toolName);
10221
- return {
10406
+ const withResult = {
10222
10407
  ...nextState,
10223
10408
  messages,
10224
10409
  workspaceRefreshKey: mutatesWorkspace ? nextState.workspaceRefreshKey + 1 : nextState.workspaceRefreshKey
10225
10410
  };
10411
+ return collectResearchSource(withResult, toolName, event.data);
10226
10412
  }
10227
10413
  case "harness.wake":
10228
10414
  case "llm.request":
@@ -10954,6 +11140,32 @@ function findToolNameById(messages, toolCallId) {
10954
11140
  }
10955
11141
  return null;
10956
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
+ }
10957
11169
  function findLatestConsultExpertCall(messages) {
10958
11170
  for (let i = messages.length - 1; i >= 0; i--) {
10959
11171
  const msg = messages[i];
@@ -11403,7 +11615,8 @@ function useAgentChatRuntime({
11403
11615
  markSending,
11404
11616
  markSendError,
11405
11617
  viewMode: state.viewMode,
11406
- setViewMode
11618
+ setViewMode,
11619
+ researchSources: state.researchSources
11407
11620
  };
11408
11621
  }
11409
11622
  function parseEventData(data) {
@@ -11462,7 +11675,8 @@ function AgentChat({
11462
11675
  onFileSelect,
11463
11676
  onMessagesChange,
11464
11677
  disabled,
11465
- onComposerError
11678
+ onComposerError,
11679
+ deepResearchEnabled = false
11466
11680
  }) {
11467
11681
  const [workspacePath, setWorkspacePath] = react.useState(null);
11468
11682
  const [mobileView, setMobileView] = react.useState("chat");
@@ -11485,7 +11699,8 @@ function AgentChat({
11485
11699
  const browserVisible = browserAvailable && showBrowser;
11486
11700
  const workspaceAvailable = !!sessionId;
11487
11701
  const workspaceVisible = workspaceAvailable && showWorkspace;
11488
- const rightStackVisible = browserVisible || workspaceVisible;
11702
+ const sourcesVisible = runtime.researchSources.length > 0;
11703
+ const rightStackVisible = browserVisible || workspaceVisible || sourcesVisible;
11489
11704
  const bothPanesVisible = browserVisible && workspaceVisible;
11490
11705
  react.useEffect(() => {
11491
11706
  onMessagesChange?.(runtime.messages);
@@ -11600,7 +11815,8 @@ function AgentChat({
11600
11815
  canShowBrowser: browserAvailable,
11601
11816
  canShowWorkspace: workspaceAvailable,
11602
11817
  viewMode: runtime.viewMode,
11603
- onViewModeChange: runtime.setViewMode
11818
+ onViewModeChange: runtime.setViewMode,
11819
+ deepResearchEnabled
11604
11820
  }
11605
11821
  )
11606
11822
  }
@@ -11660,6 +11876,20 @@ function AgentChat({
11660
11876
  }
11661
11877
  )
11662
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
+ }
11663
11893
  )
11664
11894
  ]
11665
11895
  }
@@ -12409,7 +12639,7 @@ function groupMissionTasksByBucket(tasks) {
12409
12639
  function deriveMissionWorkerActivityLabel(worker) {
12410
12640
  const kind = worker.latestEventKind;
12411
12641
  if (kind === "tool.call" && worker.latestEventSummary) {
12412
- return `tool: ${truncate3(worker.latestEventSummary, 80)}`;
12642
+ return `tool: ${truncate4(worker.latestEventSummary, 80)}`;
12413
12643
  }
12414
12644
  if (kind === "llm.response") {
12415
12645
  return "thinking";
@@ -12418,11 +12648,11 @@ function deriveMissionWorkerActivityLabel(worker) {
12418
12648
  return "received tool result";
12419
12649
  }
12420
12650
  if (kind && worker.latestEventSummary) {
12421
- return `${kind}: ${truncate3(worker.latestEventSummary, 60)}`;
12651
+ return `${kind}: ${truncate4(worker.latestEventSummary, 60)}`;
12422
12652
  }
12423
12653
  return worker.sessionStatus || "active";
12424
12654
  }
12425
- function truncate3(s, max) {
12655
+ function truncate4(s, max) {
12426
12656
  if (s.length <= max) return s;
12427
12657
  return `${s.slice(0, max - 1)}\u2026`;
12428
12658
  }
@@ -13410,6 +13640,50 @@ function MissionsPanel({
13410
13640
  )) })
13411
13641
  ] });
13412
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
+ }
13413
13687
  init_utils();
13414
13688
  var DEFAULT_LIMIT2 = 50;
13415
13689
  var DEFAULT_POLL_INTERVAL_MS3 = 3e4;
@@ -14088,16 +14362,19 @@ function SessionTreePanel({
14088
14362
  exports.ACTIVE_MISSION_STATUSES = ACTIVE_MISSION_STATUSES;
14089
14363
  exports.AgentChat = AgentChat;
14090
14364
  exports.AgentChatAdapterProvider = AgentChatAdapterProvider;
14365
+ exports.CitationText = CitationText;
14091
14366
  exports.InboxPanel = InboxPanel;
14092
14367
  exports.MessageResponse = MessageResponse;
14093
14368
  exports.MissionDashboard = MissionDashboard;
14094
14369
  exports.MissionsPanel = MissionsPanel;
14370
+ exports.ResearchSourcesPanel = ResearchSourcesPanel;
14095
14371
  exports.ScheduledWorkPanel = ScheduledWorkPanel;
14096
14372
  exports.SessionTreePanel = SessionTreePanel;
14097
14373
  exports.deriveMissionWorkerActivityLabel = deriveMissionWorkerActivityLabel;
14098
14374
  exports.groupMissionTasksByBucket = groupMissionTasksByBucket;
14099
14375
  exports.isTerminalMissionStatus = isTerminalMissionStatus;
14100
14376
  exports.missionTaskBucket = missionTaskBucket;
14377
+ exports.splitCitations = splitCitations;
14101
14378
  exports.useAgentChatAdapterContext = useAgentChatAdapterContext;
14102
14379
  exports.useAgentChatRuntime = useAgentChatRuntime;
14103
14380
  exports.useChatViewMode = useChatViewMode;