@invergent/agent-chat-react 2.7.0 → 2.7.1

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
@@ -4922,6 +4922,74 @@ function AttachmentChip({
4922
4922
  }
4923
4923
  );
4924
4924
  }
4925
+ function tokenizeInline(text) {
4926
+ const tokens = [];
4927
+ let buf = "";
4928
+ let i = 0;
4929
+ const flush = () => {
4930
+ if (buf) {
4931
+ tokens.push({ kind: "text", value: buf });
4932
+ buf = "";
4933
+ }
4934
+ };
4935
+ while (i < text.length) {
4936
+ const ch = text[i];
4937
+ if (ch === "`") {
4938
+ const end = text.indexOf("`", i + 1);
4939
+ if (end > i) {
4940
+ flush();
4941
+ tokens.push({ kind: "code", value: text.slice(i + 1, end) });
4942
+ i = end + 1;
4943
+ continue;
4944
+ }
4945
+ }
4946
+ if (ch === "*" && text[i + 1] === "*") {
4947
+ const end = text.indexOf("**", i + 2);
4948
+ if (end > i + 1) {
4949
+ flush();
4950
+ tokens.push({ kind: "strong", value: text.slice(i + 2, end) });
4951
+ i = end + 2;
4952
+ continue;
4953
+ }
4954
+ }
4955
+ if (ch === "*") {
4956
+ const end = text.indexOf("*", i + 1);
4957
+ if (end > i + 1) {
4958
+ flush();
4959
+ tokens.push({ kind: "em", value: text.slice(i + 1, end) });
4960
+ i = end + 1;
4961
+ continue;
4962
+ }
4963
+ }
4964
+ buf += ch;
4965
+ i += 1;
4966
+ }
4967
+ flush();
4968
+ return tokens;
4969
+ }
4970
+ function renderInlineMarkdown(text) {
4971
+ if (!text || !/[*`]/.test(text)) return text ?? null;
4972
+ const tokens = tokenizeInline(text);
4973
+ return tokens.map((tok, idx) => {
4974
+ switch (tok.kind) {
4975
+ case "strong":
4976
+ return /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "font-semibold", children: tok.value }, idx);
4977
+ case "em":
4978
+ return /* @__PURE__ */ jsxRuntime.jsx("em", { children: tok.value }, idx);
4979
+ case "code":
4980
+ return /* @__PURE__ */ jsxRuntime.jsx(
4981
+ "code",
4982
+ {
4983
+ className: "rounded bg-muted px-1 py-0.5 font-mono text-[0.9em]",
4984
+ children: tok.value
4985
+ },
4986
+ idx
4987
+ );
4988
+ default:
4989
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: tok.value }, idx);
4990
+ }
4991
+ });
4992
+ }
4925
4993
 
4926
4994
  // src/components/ui/command.tsx
4927
4995
  init_utils();
@@ -6352,6 +6420,7 @@ function ChatComposerInner({
6352
6420
  canShowBrowser = false,
6353
6421
  canShowWorkspace = false,
6354
6422
  deepResearchEnabled = false,
6423
+ researchEnabled = false,
6355
6424
  codeAgentsEnabled = false,
6356
6425
  viewMode = "simple",
6357
6426
  onViewModeChange
@@ -6422,6 +6491,13 @@ function ChatComposerInner({
6422
6491
  description: "Delegate a topic to the deep-research workflow"
6423
6492
  });
6424
6493
  }
6494
+ if (researchEnabled) {
6495
+ base.push({
6496
+ value: "/auto-research ",
6497
+ label: "/auto-research",
6498
+ description: "Launch an autonomous research mission (Arbor)"
6499
+ });
6500
+ }
6425
6501
  if (codeAgentsEnabled) {
6426
6502
  base.push(
6427
6503
  { value: "/code claude ", label: "/code claude", description: "Run Claude Code on the workspace (your plan)" },
@@ -6433,7 +6509,7 @@ function ChatComposerInner({
6433
6509
  }
6434
6510
  return base;
6435
6511
  },
6436
- [deepResearchEnabled, codeAgentsEnabled]
6512
+ [deepResearchEnabled, researchEnabled, codeAgentsEnabled]
6437
6513
  );
6438
6514
  const scheduledExamples = react.useMemo(
6439
6515
  () => [
@@ -7971,6 +8047,16 @@ var ErrorMessage = react.memo(function ErrorMessage2({
7971
8047
  const [detailOpen, setDetailOpen] = react.useState(false);
7972
8048
  const [retryPending, setRetryPending] = react.useState(false);
7973
8049
  const [retryError, setRetryError] = react.useState(null);
8050
+ if (errorInfo.insufficientCredits) {
8051
+ return /* @__PURE__ */ jsxRuntime.jsx(
8052
+ InsufficientCreditsCard,
8053
+ {
8054
+ detail: errorInfo.detail,
8055
+ onDismiss,
8056
+ className
8057
+ }
8058
+ );
8059
+ }
7974
8060
  const showRetry = errorInfo.retryable && !!onRetry;
7975
8061
  const handleRetry = async () => {
7976
8062
  if (!onRetry) return;
@@ -8042,6 +8128,46 @@ var ErrorMessage = react.memo(function ErrorMessage2({
8042
8128
  }
8043
8129
  );
8044
8130
  });
8131
+ function parseRequestedTokens(detail) {
8132
+ const m = detail.match(/["']?requested["']?\s*[:=]\s*(\d+)/);
8133
+ return m ? Number(m[1]) : null;
8134
+ }
8135
+ var InsufficientCreditsCard = react.memo(function InsufficientCreditsCard2({
8136
+ detail,
8137
+ onDismiss,
8138
+ className
8139
+ }) {
8140
+ const { onOpenBilling } = useAgentChatAdapterContext();
8141
+ const requested = parseRequestedTokens(detail);
8142
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8143
+ "div",
8144
+ {
8145
+ role: "alert",
8146
+ className: cn(
8147
+ "flex w-full gap-3 rounded-md border border-amber-500/30 bg-amber-500/5 p-4",
8148
+ className
8149
+ ),
8150
+ children: [
8151
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "mt-0.5 size-5 shrink-0 text-amber-600 dark:text-amber-400" }),
8152
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-2", children: [
8153
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-semibold text-foreground", children: "You're out of credits" }),
8154
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground", children: [
8155
+ "This run was blocked by the token-credit limit",
8156
+ requested ? ` (it needed ~${requested.toLocaleString()} tokens)` : "",
8157
+ ". Buy additional credits or upgrade your plan to keep your agents running."
8158
+ ] }),
8159
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 pt-1", children: [
8160
+ onOpenBilling && /* @__PURE__ */ jsxRuntime.jsxs(Button, { size: "sm", onClick: onOpenBilling, children: [
8161
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "size-4" }),
8162
+ "Go to Billing"
8163
+ ] }),
8164
+ onDismiss && /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", variant: "ghost", onClick: onDismiss, children: "Dismiss" })
8165
+ ] })
8166
+ ] })
8167
+ ]
8168
+ }
8169
+ );
8170
+ });
8045
8171
  init_utils();
8046
8172
  var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
8047
8173
  var PDF_WORKER_SRC = new URL(
@@ -9063,7 +9189,11 @@ function cancelledToolLabel(toolName) {
9063
9189
  research_memory: "Research",
9064
9190
  research_outline: "Research",
9065
9191
  generate_image: "image generation",
9066
- generate_video: "video generation"
9192
+ generate_video: "video generation",
9193
+ // Arbor research-mission tools (the /auto-research coordinator loop).
9194
+ idea_tree: "Idea tree",
9195
+ dispatch_experiments: "Dispatch experiments",
9196
+ merge_experiment: "Merge experiment"
9067
9197
  };
9068
9198
  if (map[toolName]) return map[toolName];
9069
9199
  if (toolName.startsWith("mcp__")) return formatMcpToolLabel(toolName);
@@ -9240,7 +9370,7 @@ function NarrationEntry({
9240
9370
  /* @__PURE__ */ jsxRuntime.jsx(TimelineSeparator, { style: { backgroundColor: "var(--color-border)" } }),
9241
9371
  /* @__PURE__ */ jsxRuntime.jsx(TimelineIndicator, { className: "size-2 border-none bg-foreground/30" })
9242
9372
  ] }),
9243
- /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: line }) })
9373
+ /* @__PURE__ */ jsxRuntime.jsx(TimelineContent, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: renderInlineMarkdown(line) }) })
9244
9374
  ] });
9245
9375
  }
9246
9376
  function TextEntry({
@@ -9356,6 +9486,17 @@ function extractToolDetail(tc) {
9356
9486
  const prompt = stringArg("prompt");
9357
9487
  return prompt ? truncate3(prompt, 60) : null;
9358
9488
  }
9489
+ case "idea_tree":
9490
+ return stringArg("action");
9491
+ case "dispatch_experiments": {
9492
+ const keys = args.node_keys;
9493
+ if (Array.isArray(keys) && keys.length > 0) {
9494
+ return truncate3(keys.map((k) => String(k)).join(", "), 40);
9495
+ }
9496
+ return stringArg("action");
9497
+ }
9498
+ case "merge_experiment":
9499
+ return stringArg("node_key") ?? stringArg("action");
9359
9500
  case "memory":
9360
9501
  return stringArg("action") ?? stringArg("key");
9361
9502
  case "research_memory": {
@@ -9657,7 +9798,7 @@ function IterationGroup({
9657
9798
  "aria-expanded": open,
9658
9799
  className: "flex w-full items-center gap-2 rounded py-0.5 text-left text-sm hover:bg-muted/40",
9659
9800
  children: [
9660
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-1 truncate", labelTone), children: label }),
9801
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("flex-1 truncate", labelTone), children: renderInlineMarkdown(label) }),
9661
9802
  /* @__PURE__ */ jsxRuntime.jsx(
9662
9803
  lucideReact.ChevronDown,
9663
9804
  {
@@ -9784,7 +9925,7 @@ function SimpleAssistantGroup({
9784
9925
  const narrationLine = action ? action.body.trim().toLowerCase() === "done" ? null : action.body : inferredNarration;
9785
9926
  const askBody = hasUserFacingAskContent(message) ? cleaned : null;
9786
9927
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
9787
- askBody ? /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: askBody }) : narrationLine ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground", children: narrationLine }) : null,
9928
+ askBody ? /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { children: askBody }) : narrationLine ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-foreground", children: renderInlineMarkdown(narrationLine) }) : null,
9788
9929
  /* @__PURE__ */ jsxRuntime.jsx(
9789
9930
  IterationGroup,
9790
9931
  {
@@ -9946,6 +10087,7 @@ function ChatThread({
9946
10087
  viewMode = "simple",
9947
10088
  onViewModeChange,
9948
10089
  deepResearchEnabled = false,
10090
+ researchEnabled = false,
9949
10091
  codeAgentsEnabled = false,
9950
10092
  researchSources = [],
9951
10093
  hideTurnSummary = false,
@@ -10012,6 +10154,7 @@ function ChatThread({
10012
10154
  viewMode,
10013
10155
  onViewModeChange,
10014
10156
  deepResearchEnabled,
10157
+ researchEnabled,
10015
10158
  codeAgentsEnabled
10016
10159
  }
10017
10160
  ),
@@ -10855,6 +10998,9 @@ var AGENT_CHAT_LISTENED_EVENTS = [
10855
10998
  "iteration.summary",
10856
10999
  "turn.summary"
10857
11000
  ];
11001
+ var AGENT_CHAT_LISTENED_EVENT_SET = new Set(
11002
+ AGENT_CHAT_LISTENED_EVENTS
11003
+ );
10858
11004
 
10859
11005
  // src/runtime/reducer.ts
10860
11006
  var EMPTY_TOKEN_USAGE = {
@@ -10877,6 +11023,7 @@ function createInitialAgentChatState(options = {}) {
10877
11023
  sessionDone: false,
10878
11024
  hadDeltas: false,
10879
11025
  terminal: false,
11026
+ stopped: false,
10880
11027
  workspaceRefreshKey: 0,
10881
11028
  browser: null,
10882
11029
  viewMode: options.viewMode ?? "simple",
@@ -10891,13 +11038,13 @@ function applyAgentChatEvent(state, event) {
10891
11038
  sessionDone: state.sessionDone || event.type === "session.done"
10892
11039
  };
10893
11040
  nextState = applyRetryIndicator(nextState, event);
10894
- if (state.terminal && isPostTerminalLiveEvent(event.type)) {
11041
+ if (state.stopped && isPostTerminalLiveEvent(event.type)) {
10895
11042
  return nextState;
10896
11043
  }
10897
11044
  switch (event.type) {
10898
11045
  case "user.message":
10899
11046
  return withMessages(
10900
- { ...nextState, terminal: false, isRunning: true },
11047
+ { ...nextState, terminal: false, stopped: false, isRunning: true },
10901
11048
  applyUserMessage(nextState.messages, event.eventId, event.data)
10902
11049
  );
10903
11050
  case "skill.invoked":
@@ -11520,13 +11667,21 @@ function buildErrorInfo(event) {
11520
11667
  if (event.type !== "session.fail" || typeof event.data.error_category !== "string") {
11521
11668
  return void 0;
11522
11669
  }
11670
+ const detail = stringValue2(event.data.error_detail) || stringValue2(event.data.error);
11671
+ const insufficientCredits = isInsufficientCreditsError(detail);
11523
11672
  return {
11524
11673
  category: event.data.error_category,
11525
- title: stringValue2(event.data.error_title) || "The session failed due to an internal error.",
11526
- detail: stringValue2(event.data.error_detail) || stringValue2(event.data.error),
11527
- retryable: Boolean(event.data.retryable)
11674
+ title: insufficientCredits ? "You're out of credits" : stringValue2(event.data.error_title) || "The session failed due to an internal error.",
11675
+ detail,
11676
+ // A credit shortfall is not a transient failure — retrying just hits
11677
+ // the same 402. The billing CTA replaces Retry.
11678
+ retryable: insufficientCredits ? false : Boolean(event.data.retryable),
11679
+ insufficientCredits
11528
11680
  };
11529
11681
  }
11682
+ function isInsufficientCreditsError(text) {
11683
+ return typeof text === "string" && text.includes("insufficient_credits");
11684
+ }
11530
11685
  function applyExpertResult(messages, eventId, data) {
11531
11686
  const match = findLatestConsultExpertCall(messages);
11532
11687
  if (!match) return messages;
@@ -11926,6 +12081,15 @@ function readTurnMeta(data) {
11926
12081
  }
11927
12082
 
11928
12083
  // src/runtime/use-agent-chat-runtime.ts
12084
+ var RECONCILE_FIRST_DELAY = 4e3;
12085
+ var RECONCILE_FAST = 1500;
12086
+ var RECONCILE_ACTIVE = 4e3;
12087
+ var RECONCILE_MISSION_IDLE = 5e3;
12088
+ var RECONCILE_IDLE = 15e3;
12089
+ function applyRuntimeEvent(prev, event) {
12090
+ if (event.eventId > 0 && event.eventId <= prev.lastEventId) return prev;
12091
+ return applyAgentChatEvent(prev, event);
12092
+ }
11929
12093
  var VIEW_MODE_KEY = "@invergent/agent-chat-react:viewMode";
11930
12094
  var VIEW_MODE_EVENT = "@invergent/agent-chat-react:viewMode:change";
11931
12095
  function readPersistedViewMode() {
@@ -11993,6 +12157,7 @@ function useAgentChatRuntime({
11993
12157
  const stateRef = react.useRef(state);
11994
12158
  const streamRef = react.useRef(null);
11995
12159
  const reconnectTimerRef = react.useRef(null);
12160
+ const reconcileTimerRef = react.useRef(null);
11996
12161
  const previousSessionIdRef = react.useRef(sessionId);
11997
12162
  const sessionIdRef = react.useRef(sessionId);
11998
12163
  react.useEffect(() => {
@@ -12025,7 +12190,7 @@ function useAgentChatRuntime({
12025
12190
  const data = parseEventData(messageEvent.data);
12026
12191
  const eventId = messageEvent.lastEventId ? Number(messageEvent.lastEventId) : 0;
12027
12192
  setState(
12028
- (prev) => applyAgentChatEvent(prev, {
12193
+ (prev) => applyRuntimeEvent(prev, {
12029
12194
  type: eventType,
12030
12195
  eventId,
12031
12196
  data
@@ -12057,12 +12222,110 @@ function useAgentChatRuntime({
12057
12222
  stateRef.current = {
12058
12223
  ...stateRef.current,
12059
12224
  sessionDone: false,
12060
- terminal: false
12225
+ terminal: false,
12226
+ stopped: false
12061
12227
  };
12062
12228
  openStream(targetSessionId, after);
12063
12229
  },
12064
12230
  [clearReconnectTimer, closeStream, openStream]
12065
12231
  );
12232
+ const reviveStreamIfNeeded = react.useCallback(
12233
+ (targetSessionId) => {
12234
+ if (streamRef.current) return;
12235
+ if (reconnectTimerRef.current) return;
12236
+ stateRef.current = { ...stateRef.current, sessionDone: false };
12237
+ setState(
12238
+ (prev) => prev.sessionDone ? { ...prev, sessionDone: false } : prev
12239
+ );
12240
+ openStream(targetSessionId, stateRef.current.lastEventId);
12241
+ },
12242
+ [openStream]
12243
+ );
12244
+ const applyPolledEvents = react.useCallback((events) => {
12245
+ if (events.length === 0) return;
12246
+ setState((prev) => {
12247
+ let next = prev;
12248
+ for (const event of events) {
12249
+ if (!AGENT_CHAT_LISTENED_EVENT_SET.has(event.type)) continue;
12250
+ next = applyRuntimeEvent(next, {
12251
+ type: event.type,
12252
+ eventId: event.id,
12253
+ data: event.data ?? {}
12254
+ });
12255
+ }
12256
+ return next;
12257
+ });
12258
+ }, []);
12259
+ const reconcileTick = react.useCallback(async () => {
12260
+ const targetSessionId = sessionIdRef.current;
12261
+ if (!targetSessionId) return RECONCILE_IDLE;
12262
+ let appliedNew = false;
12263
+ if (adapter.pollEvents) {
12264
+ let cursor = stateRef.current.lastEventId;
12265
+ try {
12266
+ for (let page = 0; page < 5; page++) {
12267
+ const res = await adapter.pollEvents({
12268
+ sessionId: targetSessionId,
12269
+ after: cursor,
12270
+ limit: 200
12271
+ });
12272
+ if (sessionIdRef.current !== targetSessionId) return RECONCILE_IDLE;
12273
+ const events = res.events ?? [];
12274
+ if (events.length === 0) break;
12275
+ const fresh = events.filter((event) => event.id > cursor);
12276
+ const known = fresh.filter(
12277
+ (event) => AGENT_CHAT_LISTENED_EVENT_SET.has(event.type)
12278
+ );
12279
+ if (known.length > 0) {
12280
+ applyPolledEvents(known);
12281
+ appliedNew = true;
12282
+ }
12283
+ for (const event of events) cursor = Math.max(cursor, event.id);
12284
+ if (!res.hasMore) break;
12285
+ }
12286
+ } catch {
12287
+ }
12288
+ }
12289
+ let live = false;
12290
+ let isMission = false;
12291
+ let statusKnown = false;
12292
+ try {
12293
+ const session2 = await adapter.getSession({ sessionId: targetSessionId });
12294
+ if (sessionIdRef.current !== targetSessionId) return RECONCILE_IDLE;
12295
+ statusKnown = true;
12296
+ live = session2.status === "active";
12297
+ isMission = Boolean(
12298
+ session2.config?.active_mission_id
12299
+ );
12300
+ } catch {
12301
+ }
12302
+ if (live) reviveStreamIfNeeded(targetSessionId);
12303
+ if (appliedNew) return RECONCILE_FAST;
12304
+ if (live || !statusKnown) return RECONCILE_ACTIVE;
12305
+ return isMission || stateRef.current.isRunning ? RECONCILE_MISSION_IDLE : RECONCILE_IDLE;
12306
+ }, [adapter, applyPolledEvents, reviveStreamIfNeeded]);
12307
+ react.useEffect(() => {
12308
+ if (!sessionId) return;
12309
+ let cancelled = false;
12310
+ const schedule = (delay) => {
12311
+ if (cancelled) return;
12312
+ reconcileTimerRef.current = setTimeout(() => {
12313
+ if (cancelled) return;
12314
+ void reconcileTick().catch(() => RECONCILE_ACTIVE).then((next) => {
12315
+ if (cancelled) return;
12316
+ schedule(next);
12317
+ });
12318
+ }, delay);
12319
+ };
12320
+ schedule(RECONCILE_FIRST_DELAY);
12321
+ return () => {
12322
+ cancelled = true;
12323
+ if (reconcileTimerRef.current) {
12324
+ clearTimeout(reconcileTimerRef.current);
12325
+ reconcileTimerRef.current = null;
12326
+ }
12327
+ };
12328
+ }, [sessionId, reconcileTick]);
12066
12329
  react.useEffect(() => {
12067
12330
  const previousSessionId = previousSessionIdRef.current;
12068
12331
  previousSessionIdRef.current = sessionId;
@@ -12187,6 +12450,10 @@ function useAgentChatRuntime({
12187
12450
  return {
12188
12451
  ...prev,
12189
12452
  terminal: true,
12453
+ // User-initiated stop: gate the aborted turn's late in-flight
12454
+ // events (a natural session.complete leaves this false so an
12455
+ // autonomous mission resume still streams).
12456
+ stopped: true,
12190
12457
  isRunning: false,
12191
12458
  messages
12192
12459
  };
@@ -12269,6 +12536,7 @@ function useAgentChatRuntime({
12269
12536
  setState((prev) => ({
12270
12537
  ...prev,
12271
12538
  terminal: false,
12539
+ stopped: false,
12272
12540
  retryIndicator: null,
12273
12541
  isRunning: true
12274
12542
  }));
@@ -12396,8 +12664,10 @@ function AgentChat({
12396
12664
  disabled,
12397
12665
  onComposerError,
12398
12666
  deepResearchEnabled = false,
12667
+ researchEnabled = false,
12399
12668
  codeAgentsEnabled = false,
12400
- onOpenIntegrations
12669
+ onOpenIntegrations,
12670
+ onOpenBilling
12401
12671
  }) {
12402
12672
  const [workspacePath, setWorkspacePath] = react.useState(null);
12403
12673
  const [mobileView, setMobileView] = react.useState("chat");
@@ -12456,7 +12726,8 @@ function AgentChat({
12456
12726
  value: {
12457
12727
  adapter,
12458
12728
  sessionId,
12459
- onFileSelect: handleFileSelect
12729
+ onFileSelect: handleFileSelect,
12730
+ onOpenBilling
12460
12731
  },
12461
12732
  children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(
12462
12733
  "section",
@@ -12546,6 +12817,7 @@ function AgentChat({
12546
12817
  viewMode: runtime.viewMode,
12547
12818
  onViewModeChange: runtime.setViewMode,
12548
12819
  deepResearchEnabled,
12820
+ researchEnabled,
12549
12821
  codeAgentsEnabled,
12550
12822
  researchSources: runtime.researchSources,
12551
12823
  hideTurnSummary,
@@ -14222,7 +14494,7 @@ function ActivityRow({
14222
14494
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 text-xs text-foreground font-mono", children: missionEventActorLabel(event, feed.sessions) }),
14223
14495
  taskId ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 rounded bg-muted px-1.5 py-0.5 font-mono text-[9px] text-muted-foreground/70", children: taskId.slice(0, 8) }) : null
14224
14496
  ] }),
14225
- /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "min-w-0 flex-1 truncate text-foreground/80", children: missionEventSummary(event) }) })
14497
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "min-w-0 flex-1 truncate text-foreground/80", children: renderInlineMarkdown(missionEventSummary(event)) }) })
14226
14498
  ] })
14227
14499
  ] });
14228
14500
  }
@@ -14305,6 +14577,182 @@ function MissionMetadataTab({ mission }) {
14305
14577
  ] })
14306
14578
  ] });
14307
14579
  }
14580
+ function compareNodes(a, b) {
14581
+ if (a.nodeKey === b.nodeKey) return 0;
14582
+ if (a.nodeKey === "ROOT") return -1;
14583
+ if (b.nodeKey === "ROOT") return 1;
14584
+ const pa = a.nodeKey.split(".").map((p) => Number.parseInt(p, 10));
14585
+ const pb = b.nodeKey.split(".").map((p) => Number.parseInt(p, 10));
14586
+ const n = Math.min(pa.length, pb.length);
14587
+ for (let i = 0; i < n; i += 1) {
14588
+ if (pa[i] !== pb[i]) return pa[i] - pb[i];
14589
+ }
14590
+ return pa.length - pb.length;
14591
+ }
14592
+ function statusToneClass(status) {
14593
+ switch (status) {
14594
+ case "merged":
14595
+ return "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400";
14596
+ case "done":
14597
+ return "border-sky-500/30 bg-sky-500/10 text-sky-700 dark:text-sky-400";
14598
+ case "running":
14599
+ return "border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-400";
14600
+ case "failed":
14601
+ return "border-destructive/30 bg-destructive/10 text-destructive";
14602
+ case "pruned":
14603
+ return "border-border bg-muted/40 text-muted-foreground/70 line-through";
14604
+ default:
14605
+ return "border-border bg-muted/30 text-muted-foreground/80";
14606
+ }
14607
+ }
14608
+ function fmtScore(n) {
14609
+ if (n === null || n === void 0) return "\u2014";
14610
+ return n.toFixed(3).replace(/\.?0+$/, "") || "0";
14611
+ }
14612
+ function deltaLabel(score, ref, direction) {
14613
+ if (score === null || score === void 0 || ref === null || ref === void 0) {
14614
+ return null;
14615
+ }
14616
+ const raw = score - ref;
14617
+ const good = direction === "minimize" ? raw < 0 : raw > 0;
14618
+ const sign = raw > 0 ? "+" : raw < 0 ? "\u2212" : "\xB1";
14619
+ return { text: `${sign}${fmtScore(Math.abs(raw))}`, good };
14620
+ }
14621
+ function ScorePair({
14622
+ label,
14623
+ from,
14624
+ to,
14625
+ direction
14626
+ }) {
14627
+ const delta = deltaLabel(to, from, direction);
14628
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
14629
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-mono text-[10px] uppercase tracking-widest text-muted-foreground/60", children: label }),
14630
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-1.5 tabular-nums", children: [
14631
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/70", children: fmtScore(from) }),
14632
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/40", children: "\u2192" }),
14633
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-lg font-semibold text-foreground", children: fmtScore(to) }),
14634
+ delta ? /* @__PURE__ */ jsxRuntime.jsx(
14635
+ "span",
14636
+ {
14637
+ className: delta.good ? "text-xs font-medium text-emerald-600 dark:text-emerald-400" : "text-xs font-medium text-muted-foreground/60",
14638
+ children: delta.text
14639
+ }
14640
+ ) : null
14641
+ ] })
14642
+ ] });
14643
+ }
14644
+ function RunHeader({ run }) {
14645
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "space-y-4 rounded-md border bg-card/40 p-4", children: [
14646
+ run.objective ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground/90", children: run.objective }) : null,
14647
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
14648
+ /* @__PURE__ */ jsxRuntime.jsx(
14649
+ ScorePair,
14650
+ {
14651
+ label: "Dev (working)",
14652
+ from: run.baselineScore,
14653
+ to: run.trunkScore ?? run.baselineScore,
14654
+ direction: run.metricDirection
14655
+ }
14656
+ ),
14657
+ /* @__PURE__ */ jsxRuntime.jsx(
14658
+ ScorePair,
14659
+ {
14660
+ label: "Held-out test (authoritative)",
14661
+ from: run.testBaselineScore,
14662
+ to: run.testTrunkScore ?? run.testBaselineScore,
14663
+ direction: run.metricDirection
14664
+ }
14665
+ )
14666
+ ] }),
14667
+ /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "grid grid-cols-[max-content_1fr] gap-x-4 gap-y-1 text-[11px]", children: [
14668
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground/60", children: "Status" }),
14669
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "text-foreground/80", children: run.status }),
14670
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground/60", children: "Repo" }),
14671
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "text-[11px]", children: run.repoPath }) }),
14672
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground/60", children: "Trunk" }),
14673
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "text-[11px]", children: run.trunkBranch }) }),
14674
+ run.evalCmd ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14675
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground/60", children: "Dev eval" }),
14676
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "text-[11px]", children: run.evalCmd }) })
14677
+ ] }) : null,
14678
+ run.evalCmdTest ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14679
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground/60", children: "Held-out eval" }),
14680
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "text-[11px]", children: run.evalCmdTest }) })
14681
+ ] }) : null
14682
+ ] })
14683
+ ] });
14684
+ }
14685
+ function IdeaRow({
14686
+ node,
14687
+ baseline,
14688
+ direction
14689
+ }) {
14690
+ const indent = node.nodeKey === "ROOT" ? 0 : node.depth;
14691
+ const delta = deltaLabel(node.score, baseline, direction);
14692
+ const headline = (node.hypothesis || "").split("\n")[0];
14693
+ const insight = node.insight ? node.insight.split("\n").slice(-1)[0] : null;
14694
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14695
+ "li",
14696
+ {
14697
+ className: "flex flex-col gap-0.5 border-b border-border/40 py-2 last:border-b-0",
14698
+ style: { paddingLeft: `${indent * 16}px` },
14699
+ children: [
14700
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
14701
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "shrink-0 text-[11px] text-muted-foreground/60", children: node.nodeKey }),
14702
+ /* @__PURE__ */ jsxRuntime.jsx(
14703
+ "span",
14704
+ {
14705
+ className: `shrink-0 rounded border px-1.5 py-0.5 font-mono text-[9px] uppercase tracking-wider ${statusToneClass(node.status)}`,
14706
+ children: node.status
14707
+ }
14708
+ ),
14709
+ node.score !== null && node.score !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0 tabular-nums text-xs font-medium text-foreground/90", children: [
14710
+ fmtScore(node.score),
14711
+ delta ? /* @__PURE__ */ jsxRuntime.jsxs(
14712
+ "span",
14713
+ {
14714
+ className: delta.good ? " text-emerald-600 dark:text-emerald-400" : " text-muted-foreground/50",
14715
+ children: [
14716
+ " ",
14717
+ delta.text
14718
+ ]
14719
+ }
14720
+ ) : null
14721
+ ] }) : null,
14722
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-foreground/80", title: headline, children: headline })
14723
+ ] }),
14724
+ insight ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate pl-[calc(1.5rem)] text-[11px] text-muted-foreground/60", title: node.insight ?? void 0, children: insight }) : null
14725
+ ]
14726
+ }
14727
+ );
14728
+ }
14729
+ function MissionResearchTab({ research }) {
14730
+ const sorted = react.useMemo(
14731
+ () => [...research.nodes].sort(compareNodes),
14732
+ [research.nodes]
14733
+ );
14734
+ const { run } = research;
14735
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
14736
+ /* @__PURE__ */ jsxRuntime.jsx(RunHeader, { run }),
14737
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
14738
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 font-mono text-[10px] uppercase tracking-widest text-muted-foreground/70", children: [
14739
+ "Idea Tree \xB7 ",
14740
+ sorted.length,
14741
+ " node",
14742
+ sorted.length === 1 ? "" : "s"
14743
+ ] }),
14744
+ sorted.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground/60", children: "No hypotheses yet." }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "rounded-md border", children: sorted.map((n) => /* @__PURE__ */ jsxRuntime.jsx(
14745
+ IdeaRow,
14746
+ {
14747
+ node: n,
14748
+ baseline: run.baselineScore,
14749
+ direction: run.metricDirection
14750
+ },
14751
+ n.nodeKey
14752
+ )) })
14753
+ ] })
14754
+ ] });
14755
+ }
14308
14756
  var TASK_STATUS_BADGE_CLASS = {
14309
14757
  running: "border-primary/30 bg-primary/10 text-primary",
14310
14758
  blocked: "border-amber-500/30 bg-amber-500/10 text-amber-600",
@@ -14510,7 +14958,7 @@ function TaskDetail({
14510
14958
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-16 shrink-0 font-mono text-[10px] text-muted-foreground/60", children: formatMissionTimestamp(e.createdAt) }),
14511
14959
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "", children: [
14512
14960
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 font-mono uppercase text-muted-foreground", children: missionEventCategory(e.type) }),
14513
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "min-w-0 flex-1 text-foreground/80", children: missionEventSummary(e) })
14961
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "min-w-0 flex-1 text-foreground/80", children: renderInlineMarkdown(missionEventSummary(e)) })
14514
14962
  ] })
14515
14963
  ] }, e.id)) })
14516
14964
  ] }) : null,
@@ -14623,7 +15071,7 @@ function OutcomeCard({ outcome }) {
14623
15071
  ),
14624
15072
  outcome.timestamp ? /* @__PURE__ */ jsxRuntime.jsx(CardAction, { className: "font-mono text-[10px] text-muted-foreground/60", children: formatMissionTimestamp(outcome.timestamp) }) : null
14625
15073
  ] }),
14626
- /* @__PURE__ */ jsxRuntime.jsx(CardContent, { children: outcome.markdown ? /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { className: "font-mono text-xs leading-relaxed text-foreground/90", children: outcome.markdown }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap font-mono text-xs leading-relaxed text-foreground/90", children: outcome.text }) })
15074
+ /* @__PURE__ */ jsxRuntime.jsx(CardContent, { children: outcome.markdown ? /* @__PURE__ */ jsxRuntime.jsx(MessageResponse, { className: "font-mono text-xs leading-relaxed text-foreground/90", children: outcome.markdown }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "whitespace-pre-wrap font-mono text-xs leading-relaxed text-foreground/90", children: renderInlineMarkdown(outcome.text) }) })
14627
15075
  ]
14628
15076
  }
14629
15077
  );
@@ -14793,6 +15241,7 @@ var INITIAL_STATE = {
14793
15241
  mission: null,
14794
15242
  tasks: [],
14795
15243
  workers: [],
15244
+ research: null,
14796
15245
  loading: true,
14797
15246
  error: null
14798
15247
  };
@@ -14835,16 +15284,19 @@ function MissionDashboard({
14835
15284
  }, []);
14836
15285
  const refresh = react.useCallback(async () => {
14837
15286
  try {
14838
- const [mission2, tasksResp, workersResp] = await Promise.all([
15287
+ const researchFn = adapter.getMissionResearch;
15288
+ const [mission2, tasksResp, workersResp, research] = await Promise.all([
14839
15289
  api.getMission({ missionId }),
14840
15290
  api.getMissionTasks({ missionId }),
14841
- api.getMissionWorkers({ missionId })
15291
+ api.getMissionWorkers({ missionId }),
15292
+ researchFn ? researchFn({ missionId }).catch(() => null) : Promise.resolve(null)
14842
15293
  ]);
14843
15294
  if (!isMounted.current) return;
14844
15295
  setState({
14845
15296
  mission: mission2,
14846
15297
  tasks: tasksResp.tasks,
14847
15298
  workers: workersResp.workers,
15299
+ research,
14848
15300
  loading: false,
14849
15301
  error: null
14850
15302
  });
@@ -14856,7 +15308,7 @@ function MissionDashboard({
14856
15308
  error: err instanceof Error ? err.message : String(err)
14857
15309
  }));
14858
15310
  }
14859
- }, [api, missionId]);
15311
+ }, [adapter, api, missionId]);
14860
15312
  react.useEffect(() => {
14861
15313
  void refresh();
14862
15314
  }, [refresh]);
@@ -15055,6 +15507,10 @@ function MissionDashboard({
15055
15507
  className: "mt-5 flex flex-1 flex-col overflow-hidden sm:mt-6",
15056
15508
  children: [
15057
15509
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 border-b border-border bg-background px-5 sm:px-6", children: /* @__PURE__ */ jsxRuntime.jsxs(TabsList, { variant: "line", children: [
15510
+ state.research ? /* @__PURE__ */ jsxRuntime.jsxs(TabsTrigger, { value: "research", children: [
15511
+ "Research",
15512
+ /* @__PURE__ */ jsxRuntime.jsx(CountBadge, { n: state.research.nodes.length })
15513
+ ] }) : null,
15058
15514
  /* @__PURE__ */ jsxRuntime.jsxs(TabsTrigger, { value: "tasks", children: [
15059
15515
  "Tasks",
15060
15516
  /* @__PURE__ */ jsxRuntime.jsx(CountBadge, { n: state.tasks.length })
@@ -15070,6 +15526,7 @@ function MissionDashboard({
15070
15526
  /* @__PURE__ */ jsxRuntime.jsx(TabsTrigger, { value: "metadata", children: "Metadata" })
15071
15527
  ] }) }),
15072
15528
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto bg-background px-5 py-4 sm:px-6", children: [
15529
+ state.research ? /* @__PURE__ */ jsxRuntime.jsx(TabsContent, { value: "research", className: "mt-0", children: /* @__PURE__ */ jsxRuntime.jsx(MissionResearchTab, { research: state.research }) }) : null,
15073
15530
  /* @__PURE__ */ jsxRuntime.jsx(TabsContent, { value: "tasks", className: "mt-0 h-full", children: /* @__PURE__ */ jsxRuntime.jsx(
15074
15531
  MissionTasksTab,
15075
15532
  {