@agent-native/core 0.95.1 → 0.96.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.
Files changed (224) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +25 -0
  3. package/corpus/core/package.json +3 -1
  4. package/corpus/core/src/a2a/client.ts +11 -1
  5. package/corpus/core/src/agent/production-agent.ts +87 -27
  6. package/corpus/core/src/agent/thread-data-builder.ts +7 -5
  7. package/corpus/core/src/chat-threads/store.ts +20 -1
  8. package/corpus/core/src/cli/code-agent-executor.ts +1 -0
  9. package/corpus/core/src/cli/code-agent-runs.ts +14 -0
  10. package/corpus/core/src/client/AgentPanel.tsx +32 -25
  11. package/corpus/core/src/client/AssistantChat.tsx +36 -20
  12. package/corpus/core/src/client/MultiTabAssistantChat.tsx +141 -90
  13. package/corpus/core/src/client/chat/ChatHistoryList.tsx +404 -0
  14. package/corpus/core/src/client/chat/index.ts +6 -0
  15. package/corpus/core/src/client/chat/tool-call-display.tsx +37 -3
  16. package/corpus/core/src/client/code-agent-chat-adapter.ts +72 -4
  17. package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +52 -11
  18. package/corpus/core/src/client/composer/TiptapComposer.tsx +157 -20
  19. package/corpus/core/src/client/composer/VoiceButton.tsx +28 -0
  20. package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +5 -0
  21. package/corpus/core/src/client/conversation/code-agent-transcript.ts +4 -5
  22. package/corpus/core/src/client/guided-questions.tsx +2 -1
  23. package/corpus/core/src/client/index.ts +11 -0
  24. package/corpus/core/src/client/use-chat-threads.ts +10 -1
  25. package/corpus/core/src/client/voice-provider-status.ts +44 -0
  26. package/corpus/core/src/code-agents/transcript-normalizer.ts +76 -0
  27. package/corpus/core/src/deploy/build.ts +12 -11
  28. package/corpus/core/src/integrations/a2a-continuation-processor.ts +35 -5
  29. package/corpus/core/src/integrations/adapters/slack.ts +74 -21
  30. package/corpus/core/src/integrations/webhook-handler.ts +14 -0
  31. package/corpus/core/src/localization/default-messages.ts +6 -0
  32. package/corpus/core/src/observability/hosted-model-experiment.ts +118 -0
  33. package/corpus/core/src/observability/routes.ts +17 -0
  34. package/corpus/core/src/observability/store.ts +4 -1
  35. package/corpus/core/src/observability/traces.ts +41 -2
  36. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +19 -1
  37. package/corpus/core/src/scripts/call-agent.ts +53 -3
  38. package/corpus/core/src/scripts/chat/search-chats.ts +11 -8
  39. package/corpus/core/src/secrets/register-framework-secrets.ts +1 -1
  40. package/corpus/core/src/server/agent-chat-plugin.ts +41 -6
  41. package/corpus/core/src/server/core-routes-plugin.ts +15 -0
  42. package/corpus/core/src/server/realtime-voice.ts +71 -24
  43. package/corpus/core/src/server/voice-providers-status.ts +4 -3
  44. package/corpus/core/src/shared/cache-control.ts +2 -8
  45. package/corpus/core/src/styles/agent-native.css +1 -0
  46. package/corpus/core/src/styles/chat-history-list.css +313 -0
  47. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
  48. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
  49. package/corpus/core/src/vite/client.ts +1 -0
  50. package/corpus/templates/analytics/AGENTS.md +7 -0
  51. package/corpus/templates/analytics/actions/bigquery.ts +26 -2
  52. package/corpus/templates/analytics/actions/data-source-status.ts +18 -0
  53. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +5 -4
  54. package/corpus/templates/analytics/app/global.css +10 -0
  55. package/corpus/templates/analytics/app/pages/Ask.tsx +25 -2
  56. package/corpus/templates/analytics/changelog/2026-07-10-analytics-answers-simple-time-bounded-metrics-without-unnece.md +6 -0
  57. package/corpus/templates/analytics/changelog/2026-07-10-analytics-chat-reliably-uses-connected-data-sources-across-s.md +6 -0
  58. package/corpus/templates/analytics/changelog/2026-07-10-chat-threads-now-stay-clear-of-top-actions-and-fade-smoothly.md +6 -0
  59. package/corpus/templates/analytics/changelog/2026-07-10-context-chips-can-now-be-selected-and-removed-one-at-a-time-.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-07-10-the-ask-composer-no-longer-carries-dashboard-or-panel-contex.md +6 -0
  61. package/corpus/templates/analytics/changelog/2026-07-10-the-ask-section-stays-highlighted-while-its-chat-history-is-.md +6 -0
  62. package/corpus/templates/analytics/server/lib/bigquery.ts +90 -14
  63. package/corpus/templates/analytics/server/plugins/agent-chat.ts +82 -7
  64. package/dist/a2a/client.d.ts +7 -0
  65. package/dist/a2a/client.d.ts.map +1 -1
  66. package/dist/a2a/client.js +2 -1
  67. package/dist/a2a/client.js.map +1 -1
  68. package/dist/agent/production-agent.d.ts +6 -0
  69. package/dist/agent/production-agent.d.ts.map +1 -1
  70. package/dist/agent/production-agent.js +63 -26
  71. package/dist/agent/production-agent.js.map +1 -1
  72. package/dist/agent/thread-data-builder.d.ts +1 -0
  73. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  74. package/dist/agent/thread-data-builder.js +6 -5
  75. package/dist/agent/thread-data-builder.js.map +1 -1
  76. package/dist/chat-threads/store.d.ts +10 -0
  77. package/dist/chat-threads/store.d.ts.map +1 -1
  78. package/dist/chat-threads/store.js +6 -0
  79. package/dist/chat-threads/store.js.map +1 -1
  80. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  81. package/dist/cli/code-agent-executor.js +1 -0
  82. package/dist/cli/code-agent-executor.js.map +1 -1
  83. package/dist/cli/code-agent-runs.d.ts +12 -0
  84. package/dist/cli/code-agent-runs.d.ts.map +1 -1
  85. package/dist/cli/code-agent-runs.js +1 -0
  86. package/dist/cli/code-agent-runs.js.map +1 -1
  87. package/dist/client/AgentPanel.d.ts.map +1 -1
  88. package/dist/client/AgentPanel.js +3 -3
  89. package/dist/client/AgentPanel.js.map +1 -1
  90. package/dist/client/AssistantChat.d.ts +11 -0
  91. package/dist/client/AssistantChat.d.ts.map +1 -1
  92. package/dist/client/AssistantChat.js +7 -4
  93. package/dist/client/AssistantChat.js.map +1 -1
  94. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  95. package/dist/client/MultiTabAssistantChat.js +83 -10
  96. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  97. package/dist/client/chat/ChatHistoryList.d.ts +88 -0
  98. package/dist/client/chat/ChatHistoryList.d.ts.map +1 -0
  99. package/dist/client/chat/ChatHistoryList.js +83 -0
  100. package/dist/client/chat/ChatHistoryList.js.map +1 -0
  101. package/dist/client/chat/index.d.ts +1 -0
  102. package/dist/client/chat/index.d.ts.map +1 -1
  103. package/dist/client/chat/index.js +1 -0
  104. package/dist/client/chat/index.js.map +1 -1
  105. package/dist/client/chat/tool-call-display.d.ts +13 -1
  106. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  107. package/dist/client/chat/tool-call-display.js +10 -2
  108. package/dist/client/chat/tool-call-display.js.map +1 -1
  109. package/dist/client/code-agent-chat-adapter.d.ts +17 -1
  110. package/dist/client/code-agent-chat-adapter.d.ts.map +1 -1
  111. package/dist/client/code-agent-chat-adapter.js +47 -3
  112. package/dist/client/code-agent-chat-adapter.js.map +1 -1
  113. package/dist/client/composer/RealtimeVoiceMode.d.ts +11 -1
  114. package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
  115. package/dist/client/composer/RealtimeVoiceMode.js +5 -3
  116. package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
  117. package/dist/client/composer/TiptapComposer.d.ts +25 -2
  118. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  119. package/dist/client/composer/TiptapComposer.js +96 -5
  120. package/dist/client/composer/TiptapComposer.js.map +1 -1
  121. package/dist/client/composer/VoiceButton.d.ts.map +1 -1
  122. package/dist/client/composer/VoiceButton.js +21 -1
  123. package/dist/client/composer/VoiceButton.js.map +1 -1
  124. package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  125. package/dist/client/composer/useRealtimeVoiceMode.js +5 -0
  126. package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
  127. package/dist/client/conversation/code-agent-transcript.d.ts +2 -0
  128. package/dist/client/conversation/code-agent-transcript.d.ts.map +1 -1
  129. package/dist/client/conversation/code-agent-transcript.js +3 -5
  130. package/dist/client/conversation/code-agent-transcript.js.map +1 -1
  131. package/dist/client/guided-questions.d.ts.map +1 -1
  132. package/dist/client/guided-questions.js +2 -1
  133. package/dist/client/guided-questions.js.map +1 -1
  134. package/dist/client/index.d.ts +3 -1
  135. package/dist/client/index.d.ts.map +1 -1
  136. package/dist/client/index.js +3 -1
  137. package/dist/client/index.js.map +1 -1
  138. package/dist/client/use-chat-threads.d.ts.map +1 -1
  139. package/dist/client/use-chat-threads.js +9 -1
  140. package/dist/client/use-chat-threads.js.map +1 -1
  141. package/dist/client/voice-provider-status.d.ts +10 -0
  142. package/dist/client/voice-provider-status.d.ts.map +1 -0
  143. package/dist/client/voice-provider-status.js +32 -0
  144. package/dist/client/voice-provider-status.js.map +1 -0
  145. package/dist/code-agents/transcript-normalizer.d.ts +29 -0
  146. package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
  147. package/dist/code-agents/transcript-normalizer.js +55 -0
  148. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  149. package/dist/collab/routes.d.ts +1 -1
  150. package/dist/collab/struct-routes.d.ts +1 -1
  151. package/dist/deploy/build.d.ts.map +1 -1
  152. package/dist/deploy/build.js +13 -11
  153. package/dist/deploy/build.js.map +1 -1
  154. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  155. package/dist/integrations/a2a-continuation-processor.js +25 -5
  156. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  157. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  158. package/dist/integrations/adapters/slack.js +55 -21
  159. package/dist/integrations/adapters/slack.js.map +1 -1
  160. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  161. package/dist/integrations/webhook-handler.js +11 -0
  162. package/dist/integrations/webhook-handler.js.map +1 -1
  163. package/dist/localization/default-messages.d.ts +5 -0
  164. package/dist/localization/default-messages.d.ts.map +1 -1
  165. package/dist/localization/default-messages.js +5 -0
  166. package/dist/localization/default-messages.js.map +1 -1
  167. package/dist/notifications/routes.d.ts +2 -2
  168. package/dist/observability/hosted-model-experiment.d.ts +39 -0
  169. package/dist/observability/hosted-model-experiment.d.ts.map +1 -0
  170. package/dist/observability/hosted-model-experiment.js +90 -0
  171. package/dist/observability/hosted-model-experiment.js.map +1 -0
  172. package/dist/observability/routes.d.ts +1 -1
  173. package/dist/observability/routes.d.ts.map +1 -1
  174. package/dist/observability/routes.js +16 -0
  175. package/dist/observability/routes.js.map +1 -1
  176. package/dist/observability/store.d.ts.map +1 -1
  177. package/dist/observability/store.js +4 -1
  178. package/dist/observability/store.js.map +1 -1
  179. package/dist/observability/traces.d.ts +6 -0
  180. package/dist/observability/traces.d.ts.map +1 -1
  181. package/dist/observability/traces.js +28 -2
  182. package/dist/observability/traces.js.map +1 -1
  183. package/dist/resources/handlers.d.ts +1 -1
  184. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  185. package/dist/scripts/agent-engines/list-agent-engines.js +19 -1
  186. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  187. package/dist/scripts/call-agent.d.ts.map +1 -1
  188. package/dist/scripts/call-agent.js +43 -3
  189. package/dist/scripts/call-agent.js.map +1 -1
  190. package/dist/scripts/chat/search-chats.d.ts +1 -1
  191. package/dist/scripts/chat/search-chats.d.ts.map +1 -1
  192. package/dist/scripts/chat/search-chats.js +11 -8
  193. package/dist/scripts/chat/search-chats.js.map +1 -1
  194. package/dist/secrets/register-framework-secrets.js +1 -1
  195. package/dist/secrets/register-framework-secrets.js.map +1 -1
  196. package/dist/server/agent-chat-plugin.d.ts +11 -0
  197. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  198. package/dist/server/agent-chat-plugin.js +27 -6
  199. package/dist/server/agent-chat-plugin.js.map +1 -1
  200. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  201. package/dist/server/core-routes-plugin.d.ts +3 -0
  202. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  203. package/dist/server/core-routes-plugin.js +13 -0
  204. package/dist/server/core-routes-plugin.js.map +1 -1
  205. package/dist/server/realtime-voice.d.ts.map +1 -1
  206. package/dist/server/realtime-voice.js +55 -21
  207. package/dist/server/realtime-voice.js.map +1 -1
  208. package/dist/server/voice-providers-status.d.ts.map +1 -1
  209. package/dist/server/voice-providers-status.js +2 -3
  210. package/dist/server/voice-providers-status.js.map +1 -1
  211. package/dist/shared/cache-control.d.ts +4 -6
  212. package/dist/shared/cache-control.d.ts.map +1 -1
  213. package/dist/shared/cache-control.js +2 -7
  214. package/dist/shared/cache-control.js.map +1 -1
  215. package/dist/styles/agent-native.css +1 -0
  216. package/dist/styles/chat-history-list.css +313 -0
  217. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
  218. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
  219. package/dist/vite/client.d.ts.map +1 -1
  220. package/dist/vite/client.js +1 -0
  221. package/dist/vite/client.js.map +1 -1
  222. package/package.json +3 -1
  223. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
  224. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
@@ -2,7 +2,6 @@ import {
2
2
  IconX,
3
3
  IconPlus,
4
4
  IconHistory,
5
- IconSearch,
6
5
  IconLink,
7
6
  IconLinkOff,
8
7
  IconCheck,
@@ -47,6 +46,11 @@ import {
47
46
  buildChatModelGroups,
48
47
  type EngineModelGroup,
49
48
  } from "./chat-model-groups.js";
49
+ import {
50
+ ChatHistoryList,
51
+ type ChatHistoryItem,
52
+ type ChatHistorySection,
53
+ } from "./chat/ChatHistoryList.js";
50
54
  import {
51
55
  Popover,
52
56
  PopoverAnchor,
@@ -444,6 +448,8 @@ function HistoryPopover({
444
448
  onClose,
445
449
  onLoadMore,
446
450
  onSearch,
451
+ onTogglePin,
452
+ onRename,
447
453
  }: {
448
454
  threads: ChatThreadSummary[];
449
455
  openTabIds: Set<string>;
@@ -456,6 +462,11 @@ function HistoryPopover({
456
462
  onClose: () => void;
457
463
  onLoadMore?: () => void;
458
464
  onSearch?: (query: string) => Promise<ChatThreadSummary[]>;
465
+ /** Presence enables the pin/unpin row action. Receives the thread's
466
+ * current pinned state so the caller can flip it. */
467
+ onTogglePin?: (id: string, pinned: boolean) => void;
468
+ /** Presence enables the inline rename row action. */
469
+ onRename?: (id: string, nextTitle: string) => void;
459
470
  }) {
460
471
  const [search, setSearch] = useState("");
461
472
  const [searchResults, setSearchResults] = useState<
@@ -512,18 +523,26 @@ function HistoryPopover({
512
523
  )
513
524
  : visibleThreads;
514
525
 
515
- // When scope is set we split history into two sections so the user can
516
- // see "this deck's chats" first without losing access to general /
526
+ // Pinned threads always float to the top of `threads` already (see
527
+ // `sortThreadSummaries` in use-chat-threads.ts and the matching server
528
+ // ORDER BY), but pulling them into their own labeled section — instead of
529
+ // just relying on sort order within "All chats" — makes the pin affordance
530
+ // visible at a glance, matching common chat-history UX.
531
+ const pinnedThreads = filtered.filter((t) => t.pinnedAt != null);
532
+ const unpinnedThreads = filtered.filter((t) => t.pinnedAt == null);
533
+
534
+ // When scope is set we split (unpinned) history into two sections so the
535
+ // user can see "this deck's chats" first without losing access to general /
517
536
  // other-deck chats. Section labels intentionally use the current
518
537
  // resource type (deck/design/dashboard) instead of a generic phrase.
519
538
  const sectionedThreads = currentScope
520
539
  ? {
521
- scoped: filtered.filter(
540
+ scoped: unpinnedThreads.filter(
522
541
  (t) =>
523
542
  t.scope?.type === currentScope.type &&
524
543
  t.scope?.id === currentScope.id,
525
544
  ),
526
- other: filtered.filter(
545
+ other: unpinnedThreads.filter(
527
546
  (t) =>
528
547
  !t.scope ||
529
548
  t.scope.type !== currentScope.type ||
@@ -532,6 +551,53 @@ function HistoryPopover({
532
551
  }
533
552
  : null;
534
553
 
554
+ const toHistoryItem = (thread: ChatThreadSummary): ChatHistoryItem => {
555
+ const isActive = thread.id === activeThreadId;
556
+ const title = thread.title || thread.preview || "Chat";
557
+ return {
558
+ id: thread.id,
559
+ title,
560
+ titleText: title,
561
+ subtitle:
562
+ thread.preview && thread.title !== thread.preview
563
+ ? thread.preview
564
+ : undefined,
565
+ detail: thread.scope?.label || undefined,
566
+ timestamp: isActive
567
+ ? "Active"
568
+ : openTabIds.has(thread.id)
569
+ ? "Open"
570
+ : formatThreadTime(thread.updatedAt),
571
+ pinned: thread.pinnedAt != null,
572
+ };
573
+ };
574
+
575
+ const historySections: ChatHistorySection[] = [
576
+ ...(pinnedThreads.length > 0
577
+ ? [
578
+ {
579
+ id: "pinned",
580
+ label: "Pinned",
581
+ items: pinnedThreads.map(toHistoryItem),
582
+ },
583
+ ]
584
+ : []),
585
+ ...(sectionedThreads
586
+ ? [
587
+ {
588
+ id: "scoped",
589
+ label: `This ${currentScope!.type}`,
590
+ items: sectionedThreads.scoped.map(toHistoryItem),
591
+ },
592
+ {
593
+ id: "other",
594
+ label: "All chats",
595
+ items: sectionedThreads.other.map(toHistoryItem),
596
+ },
597
+ ]
598
+ : [{ id: "all", items: unpinnedThreads.map(toHistoryItem) }]),
599
+ ];
600
+
535
601
  return (
536
602
  <Popover open onOpenChange={(open) => !open && onClose()}>
537
603
  <PopoverAnchor asChild>
@@ -547,90 +613,45 @@ function HistoryPopover({
547
613
  }}
548
614
  className="w-72 rounded-lg p-0"
549
615
  >
550
- <div className="flex items-center gap-2 px-3 py-2 border-b border-border">
551
- <IconSearch size={13} />
552
- <input
553
- ref={inputRef}
554
- type="text"
555
- value={search}
556
- onChange={(e) => setSearch(e.target.value)}
557
- placeholder="Search chats..."
558
- className="flex-1 bg-transparent text-xs text-foreground placeholder:text-muted-foreground outline-none"
559
- />
560
- </div>
561
- <div className="max-h-64 overflow-y-auto py-1">
562
- {loadError && !search.trim() ? (
563
- <div className="px-3 py-4 text-xs text-amber-500 text-center">
564
- {loadError}
565
- </div>
566
- ) : isSearching ? (
567
- <div className="px-3 py-4 text-xs text-muted-foreground text-center">
568
- Searching...
569
- </div>
570
- ) : filtered.length === 0 ? (
571
- <div className="px-3 py-4 text-xs text-muted-foreground text-center">
572
- {search ? "No matching chats" : "No chats yet"}
573
- </div>
574
- ) : sectionedThreads ? (
575
- <>
576
- {sectionedThreads.scoped.length > 0 && (
577
- <>
578
- <div className="px-3 pt-1.5 pb-1 text-[10px] uppercase tracking-wider text-muted-foreground/70">
579
- This {currentScope!.type}
580
- </div>
581
- {sectionedThreads.scoped.map((thread) =>
582
- renderThreadRow(
583
- thread,
584
- activeThreadId,
585
- openTabIds,
586
- formatThreadTime,
587
- onSelect,
588
- onClose,
589
- ),
590
- )}
591
- </>
592
- )}
593
- {sectionedThreads.other.length > 0 && (
594
- <>
595
- <div className="px-3 pt-2 pb-1 text-[10px] uppercase tracking-wider text-muted-foreground/70">
596
- All chats
597
- </div>
598
- {sectionedThreads.other.map((thread) =>
599
- renderThreadRow(
600
- thread,
601
- activeThreadId,
602
- openTabIds,
603
- formatThreadTime,
604
- onSelect,
605
- onClose,
606
- ),
607
- )}
608
- </>
609
- )}
610
- </>
611
- ) : (
612
- filtered.map((thread) =>
613
- renderThreadRow(
614
- thread,
615
- activeThreadId,
616
- openTabIds,
617
- formatThreadTime,
618
- onSelect,
619
- onClose,
620
- ),
621
- )
622
- )}
623
- {!search.trim() && hasMoreThreads && (
624
- <button
625
- type="button"
626
- onClick={() => onLoadMore?.()}
627
- disabled={isLoadingMoreThreads}
628
- className="mx-1 mt-1 flex w-[calc(100%-0.5rem)] items-center justify-center rounded-md px-3 py-2 text-xs text-muted-foreground hover:bg-accent hover:text-foreground disabled:cursor-default disabled:opacity-60"
629
- >
630
- {isLoadingMoreThreads ? "Loading..." : "Load older chats"}
631
- </button>
632
- )}
633
- </div>
616
+ <ChatHistoryList
617
+ sections={historySections}
618
+ activeId={activeThreadId}
619
+ onSelect={(id) => {
620
+ onSelect(id);
621
+ onClose();
622
+ }}
623
+ onTogglePin={
624
+ onTogglePin
625
+ ? (id) => {
626
+ const isPinned =
627
+ threads.find((t) => t.id === id)?.pinnedAt != null;
628
+ onTogglePin(id, !isPinned);
629
+ }
630
+ : undefined
631
+ }
632
+ onRename={onRename}
633
+ searchValue={search}
634
+ onSearchChange={setSearch}
635
+ searchPlaceholder="Search chats..."
636
+ searchInputRef={inputRef}
637
+ loading={isSearching}
638
+ loadingLabel="Searching..."
639
+ error={loadError && !search.trim() ? loadError : undefined}
640
+ emptyLabel="No chats yet"
641
+ emptySearchLabel="No matching chats"
642
+ footer={
643
+ !search.trim() && hasMoreThreads ? (
644
+ <button
645
+ type="button"
646
+ onClick={() => onLoadMore?.()}
647
+ disabled={isLoadingMoreThreads}
648
+ className="mx-1 mt-1 flex w-[calc(100%-0.5rem)] items-center justify-center rounded-md px-3 py-2 text-xs text-muted-foreground hover:bg-accent hover:text-foreground disabled:cursor-default disabled:opacity-60"
649
+ >
650
+ {isLoadingMoreThreads ? "Loading..." : "Load older chats"}
651
+ </button>
652
+ ) : undefined
653
+ }
654
+ />
634
655
  </PopoverContent>
635
656
  </Popover>
636
657
  );
@@ -1074,6 +1095,8 @@ export function MultiTabAssistantChat({
1074
1095
  isLoadingMoreThreads,
1075
1096
  threadsLoadError,
1076
1097
  isNewThread,
1098
+ pinThread,
1099
+ renameThread,
1077
1100
  } = useChatThreads(apiUrl, storageKey, scope, {
1078
1101
  restoreActiveThread,
1079
1102
  routeThreadId: threadUrlSyncEnabled ? urlThreadId : undefined,
@@ -1105,8 +1128,28 @@ export function MultiTabAssistantChat({
1105
1128
  );
1106
1129
  const [runningThreads, setRunningThreads] = useState<Set<string>>(new Set());
1107
1130
  const [showHistory, setShowHistory] = useState(false);
1131
+ const [pageOverlayScrolled, setPageOverlayScrolled] = useState(false);
1108
1132
  const newThreadIds = useRef<Set<string>>(new Set());
1109
1133
 
1134
+ useEffect(() => {
1135
+ setPageOverlayScrolled(false);
1136
+ }, [activeThreadId]);
1137
+
1138
+ const handlePageOverlayScroll = useCallback(
1139
+ (event: React.UIEvent<HTMLDivElement>) => {
1140
+ const target = event.target;
1141
+ if (
1142
+ !renderOverlay ||
1143
+ !(target instanceof HTMLElement) ||
1144
+ !target.closest(".agent-chat-scroll")
1145
+ ) {
1146
+ return;
1147
+ }
1148
+ setPageOverlayScrolled(target.scrollTop > 1);
1149
+ },
1150
+ [renderOverlay],
1151
+ );
1152
+
1110
1153
  // ─── Model state ─────────────────────────────────────────────────────────
1111
1154
  const [availableModels, setAvailableModels] = useState<EngineModelGroup[]>(
1112
1155
  [],
@@ -2653,7 +2696,13 @@ export function MultiTabAssistantChat({
2653
2696
  : null}
2654
2697
 
2655
2698
  {/* Chat content with optional overlay */}
2656
- <div className="relative flex-1 flex flex-col min-h-0">
2699
+ <div
2700
+ className="relative flex-1 flex flex-col min-h-0"
2701
+ data-agent-page-chat-scrolled={
2702
+ renderOverlay && pageOverlayScrolled ? "" : undefined
2703
+ }
2704
+ onScrollCapture={renderOverlay ? handlePageOverlayScroll : undefined}
2705
+ >
2657
2706
  {renderOverlay ? renderOverlay(headerProps) : null}
2658
2707
 
2659
2708
  {/* History popover — rendered inside relative container so positioning works */}
@@ -2670,6 +2719,8 @@ export function MultiTabAssistantChat({
2670
2719
  onClose={() => setShowHistory(false)}
2671
2720
  onLoadMore={loadMoreThreads}
2672
2721
  onSearch={searchThreads}
2722
+ onTogglePin={pinThread}
2723
+ onRename={renameThread}
2673
2724
  />
2674
2725
  )}
2675
2726