@agent-native/toolkit 0.204.0 → 0.204.1-nightly-20261008034100

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 (144) hide show
  1. package/dist/app/agent-page/AgentJobsTab.d.ts.map +1 -1
  2. package/dist/app/agent-page/AgentJobsTab.js +4 -2
  3. package/dist/app/agent-page/AgentJobsTab.js.map +1 -1
  4. package/dist/app/agent-page/AutomationDetailsDialog.d.ts +2 -1
  5. package/dist/app/agent-page/AutomationDetailsDialog.d.ts.map +1 -1
  6. package/dist/app/agent-page/AutomationDetailsDialog.js +8 -4
  7. package/dist/app/agent-page/AutomationDetailsDialog.js.map +1 -1
  8. package/dist/app/agent-page/ScheduledTriggerNotice.d.ts.map +1 -1
  9. package/dist/app/agent-page/ScheduledTriggerNotice.js +41 -19
  10. package/dist/app/agent-page/ScheduledTriggerNotice.js.map +1 -1
  11. package/dist/app/agent-page/use-jobs.d.ts +1 -1
  12. package/dist/app/agent-page/use-jobs.d.ts.map +1 -1
  13. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  14. package/dist/app/agentkit/react/components.js +124 -45
  15. package/dist/app/agentkit/react/components.js.map +1 -1
  16. package/dist/app/agentkit/react/context.d.ts +22 -1
  17. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  18. package/dist/app/agentkit/react/context.js +14 -1
  19. package/dist/app/agentkit/react/context.js.map +1 -1
  20. package/dist/app/agentkit/react/root.d.ts +3 -2
  21. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  22. package/dist/app/agentkit/react/root.js +2 -2
  23. package/dist/app/agentkit/react/root.js.map +1 -1
  24. package/dist/app/agentkit/react/styles.css +32 -2
  25. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  26. package/dist/app/chat/AgentKitAssistantChat.js +28 -3
  27. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  28. package/dist/app/chat/AgentPanel.js +1 -1
  29. package/dist/app/chat/AgentPanel.js.map +1 -1
  30. package/dist/app/chat/RunStuckBanner.d.ts +8 -1
  31. package/dist/app/chat/RunStuckBanner.d.ts.map +1 -1
  32. package/dist/app/chat/RunStuckBanner.js +68 -6
  33. package/dist/app/chat/RunStuckBanner.js.map +1 -1
  34. package/dist/app/chat/chat/run-recovery.d.ts +4 -2
  35. package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
  36. package/dist/app/chat/chat/run-recovery.js +3 -3
  37. package/dist/app/chat/chat/run-recovery.js.map +1 -1
  38. package/dist/app/chat/composer/use-mention-search.d.ts +1 -0
  39. package/dist/app/chat/composer/use-mention-search.d.ts.map +1 -1
  40. package/dist/app/chat/feedback-report.d.ts +12 -0
  41. package/dist/app/chat/feedback-report.d.ts.map +1 -0
  42. package/dist/app/chat/feedback-report.js +21 -0
  43. package/dist/app/chat/feedback-report.js.map +1 -0
  44. package/dist/app/chat/reconcile-settled-run.d.ts +15 -0
  45. package/dist/app/chat/reconcile-settled-run.d.ts.map +1 -0
  46. package/dist/app/chat/reconcile-settled-run.js +41 -0
  47. package/dist/app/chat/reconcile-settled-run.js.map +1 -0
  48. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  49. package/dist/app/i18n/catalogs/ar-SA.js +9 -0
  50. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  51. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  52. package/dist/app/i18n/catalogs/de-DE.js +9 -0
  53. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  54. package/dist/app/i18n/catalogs/en-US.d.ts +9 -0
  55. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  56. package/dist/app/i18n/catalogs/en-US.js +9 -0
  57. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  58. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  59. package/dist/app/i18n/catalogs/es-ES.js +9 -0
  60. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  61. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  62. package/dist/app/i18n/catalogs/fr-FR.js +9 -0
  63. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  64. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  65. package/dist/app/i18n/catalogs/hi-IN.js +9 -0
  66. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  67. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  68. package/dist/app/i18n/catalogs/ja-JP.js +9 -0
  69. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  70. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  71. package/dist/app/i18n/catalogs/ko-KR.js +9 -0
  72. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  73. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  74. package/dist/app/i18n/catalogs/pt-BR.js +9 -0
  75. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  76. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  77. package/dist/app/i18n/catalogs/zh-CN.js +9 -0
  78. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  79. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  80. package/dist/app/i18n/catalogs/zh-TW.js +9 -0
  81. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  82. package/dist/app/settings/shell/pages/audit.d.ts.map +1 -1
  83. package/dist/app/settings/shell/pages/audit.js +11 -2
  84. package/dist/app/settings/shell/pages/audit.js.map +1 -1
  85. package/dist/composer/ComposerContextMenu.d.ts +14 -1
  86. package/dist/composer/ComposerContextMenu.d.ts.map +1 -1
  87. package/dist/composer/ComposerContextMenu.js +65 -19
  88. package/dist/composer/ComposerContextMenu.js.map +1 -1
  89. package/dist/composer/MentionPopover.d.ts.map +1 -1
  90. package/dist/composer/MentionPopover.js +3 -3
  91. package/dist/composer/MentionPopover.js.map +1 -1
  92. package/dist/composer/PromptComposer.d.ts +1 -1
  93. package/dist/composer/PromptComposer.d.ts.map +1 -1
  94. package/dist/composer/TiptapComposer.d.ts +2 -2
  95. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  96. package/dist/composer/TiptapComposer.js +92 -36
  97. package/dist/composer/TiptapComposer.js.map +1 -1
  98. package/dist/composer/use-mention-search.d.ts +1 -0
  99. package/dist/composer/use-mention-search.d.ts.map +1 -1
  100. package/dist/composer/use-mention-search.js +8 -1
  101. package/dist/composer/use-mention-search.js.map +1 -1
  102. package/package.json +4 -4
  103. package/src/app/agent-page/AgentJobsTab.blocked.spec.tsx +43 -0
  104. package/src/app/agent-page/AgentJobsTab.tsx +14 -5
  105. package/src/app/agent-page/AutomationDetailsDialog.tsx +22 -8
  106. package/src/app/agent-page/ScheduledTriggerNotice.spec.tsx +45 -3
  107. package/src/app/agent-page/ScheduledTriggerNotice.tsx +52 -31
  108. package/src/app/agent-page/use-jobs.ts +1 -1
  109. package/src/app/agentkit/react/components.interaction.spec.tsx +311 -1
  110. package/src/app/agentkit/react/components.spec.tsx +106 -2
  111. package/src/app/agentkit/react/components.tsx +217 -80
  112. package/src/app/agentkit/react/context.tsx +36 -0
  113. package/src/app/agentkit/react/root.tsx +4 -0
  114. package/src/app/agentkit/react/styles.css +32 -2
  115. package/src/app/chat/AgentKitAssistantChat.tsx +35 -2
  116. package/src/app/chat/AgentPanel.header.spec.ts +43 -7
  117. package/src/app/chat/AgentPanel.tsx +1 -1
  118. package/src/app/chat/RunStuckBanner.spec.tsx +258 -17
  119. package/src/app/chat/RunStuckBanner.tsx +107 -5
  120. package/src/app/chat/chat/run-recovery.tsx +10 -2
  121. package/src/app/chat/feedback-report.spec.ts +45 -0
  122. package/src/app/chat/feedback-report.ts +27 -0
  123. package/src/app/chat/reconcile-settled-run.spec.ts +112 -0
  124. package/src/app/chat/reconcile-settled-run.ts +57 -0
  125. package/src/app/i18n/catalogs/ar-SA.ts +12 -0
  126. package/src/app/i18n/catalogs/de-DE.ts +12 -0
  127. package/src/app/i18n/catalogs/en-US.ts +12 -0
  128. package/src/app/i18n/catalogs/es-ES.ts +12 -0
  129. package/src/app/i18n/catalogs/fr-FR.ts +12 -0
  130. package/src/app/i18n/catalogs/hi-IN.ts +12 -0
  131. package/src/app/i18n/catalogs/ja-JP.ts +12 -0
  132. package/src/app/i18n/catalogs/ko-KR.ts +12 -0
  133. package/src/app/i18n/catalogs/pt-BR.ts +12 -0
  134. package/src/app/i18n/catalogs/zh-CN.ts +10 -0
  135. package/src/app/i18n/catalogs/zh-TW.ts +10 -0
  136. package/src/app/settings/shell/pages/audit.spec.tsx +30 -0
  137. package/src/app/settings/shell/pages/audit.tsx +12 -1
  138. package/src/composer/ComposerContextMenu.tsx +94 -22
  139. package/src/composer/ComposerPlusMenu.parity.spec.tsx +218 -233
  140. package/src/composer/MentionPopover.tsx +12 -3
  141. package/src/composer/PromptComposer.context.spec.tsx +526 -195
  142. package/src/composer/PromptComposer.tsx +1 -1
  143. package/src/composer/TiptapComposer.tsx +105 -37
  144. package/src/composer/use-mention-search.ts +8 -1
@@ -98,7 +98,7 @@ export interface PromptComposerProps {
98
98
  onRemoveContextItem?: (key: string) => void;
99
99
  onInspectContextItem?: (key: string) => void;
100
100
  onRetryContextItem?: (key: string) => void;
101
- /** When provided, both + and @ open this shared Add menu. */
101
+ /** When provided, + opens this shared Add menu; a typed @ also suggests its context sources. */
102
102
  contextMenuItems?: readonly ComposerContextMenuItem[];
103
103
  /** Called when the user submits the composer. */
104
104
  onSubmit: (
@@ -42,7 +42,10 @@ import {
42
42
  import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
43
43
  import { formatAttachmentError } from "./attachment-accept.js";
44
44
  import { BuilderBMark } from "./BuilderBMark.js";
45
- import type { ComposerContextMenuItem } from "./ComposerContextMenu.js";
45
+ import {
46
+ searchComposerContextActions,
47
+ type ComposerContextMenuItem,
48
+ } from "./ComposerContextMenu.js";
46
49
  import {
47
50
  ComposerPlusMenu,
48
51
  type ComposerTerminalModeControl,
@@ -377,6 +380,10 @@ function isSameComposerAttachmentSnapshot(
377
380
  return current.id === snapshot.id && current.file === snapshot.file;
378
381
  }
379
382
 
383
+ // Host Add-menu actions listed among "@" suggestions. Picking one runs the
384
+ // action or opens its picker as a dialog instead of inserting a mention.
385
+ const COMPOSER_CONTEXT_ENTRY_SOURCE = "composer-context";
386
+
380
387
  function composerReferenceFromMentionItem(
381
388
  item: MentionItem,
382
389
  ): AgentComposerReference {
@@ -1193,7 +1200,7 @@ export interface TiptapComposerProps {
1193
1200
  onRemoveContextItem?: (key: string) => void;
1194
1201
  onInspectContextItem?: (key: string) => void;
1195
1202
  onRetryContextItem?: (key: string) => void;
1196
- /** Shared +/@ entries; matching IDs replace built-in full-mode actions. */
1203
+ /** Shared + menu entries; matching IDs replace built-in full-mode actions. */
1197
1204
  contextMenuItems?: readonly ComposerContextMenuItem[];
1198
1205
  /**
1199
1206
  * Controls the "+" menu next to the composer. `"full"` (default) shows the
@@ -2776,6 +2783,9 @@ export function TiptapComposer({
2776
2783
  });
2777
2784
  const [popover, setPopover] = useState<PopoverState>(null);
2778
2785
  const [contextMenuOpen, setContextMenuOpen] = useState(false);
2786
+ const [contextEntryRequest, setContextEntryRequest] = useState<{
2787
+ id: string;
2788
+ } | null>(null);
2779
2789
  const popoverRef = useRef<MentionPopoverRef>(null);
2780
2790
  const composerRuntime = useComposerRuntime();
2781
2791
  const lastComposerRuntimeSyncRef = useRef<{
@@ -2890,6 +2900,7 @@ export function TiptapComposer({
2890
2900
  isLoading: mentionsLoading,
2891
2901
  error: mentionsError,
2892
2902
  retry: retryMentions,
2903
+ settledQuery: settledMentionQuery,
2893
2904
  } = useMentionSearch(
2894
2905
  popover?.type === "@" ? popover.query : "",
2895
2906
  includeDefaultMentionSearch && (contextMenuOpen || popover?.type === "@"),
@@ -2912,6 +2923,45 @@ export function TiptapComposer({
2912
2923
  ),
2913
2924
  [hostMentionItems, mentionItems, mentionQuery, slotReferences],
2914
2925
  );
2926
+ const inlineMentionItems = useMemo(() => {
2927
+ if (
2928
+ popover?.type !== "@" ||
2929
+ !hasContextMenu ||
2930
+ disabled ||
2931
+ contextControlsDisabled ||
2932
+ !contextMenuItems?.length
2933
+ )
2934
+ return filteredMentionItems;
2935
+ const addContextLabel = t("agentChat.composer.addContext", {
2936
+ defaultValue: "Add context",
2937
+ });
2938
+ return [
2939
+ ...searchComposerContextActions(contextMenuItems, mentionQuery)
2940
+ // Custom pages only render inside the + menu.
2941
+ .filter(({ action }) => !action.disabled && !action.render)
2942
+ .map(
2943
+ ({ action, categories }): MentionItem => ({
2944
+ id: `${COMPOSER_CONTEXT_ENTRY_SOURCE}:${action.id}`,
2945
+ label: action.label,
2946
+ description: action.description,
2947
+ source: COMPOSER_CONTEXT_ENTRY_SOURCE,
2948
+ refType: COMPOSER_CONTEXT_ENTRY_SOURCE,
2949
+ refId: action.id,
2950
+ section: categories[0] ?? addContextLabel,
2951
+ }),
2952
+ ),
2953
+ ...filteredMentionItems,
2954
+ ];
2955
+ }, [
2956
+ popover?.type,
2957
+ hasContextMenu,
2958
+ disabled,
2959
+ contextControlsDisabled,
2960
+ contextMenuItems,
2961
+ mentionQuery,
2962
+ filteredMentionItems,
2963
+ t,
2964
+ ]);
2915
2965
 
2916
2966
  const {
2917
2967
  skills,
@@ -2960,14 +3010,18 @@ export function TiptapComposer({
2960
3010
  }, [allSlashSkills, popover]);
2961
3011
 
2962
3012
  // Keep refs in sync with state
2963
- const mentionItemsRef = useRef(filteredMentionItems);
2964
- mentionItemsRef.current = filteredMentionItems;
3013
+ // Results for an earlier query are stale until the search for this one
3014
+ // settles, so auto-close may not treat them as final. Host mention items
3015
+ // carry no readiness signal (they may still be loading), so only a settled
3016
+ // default search can end a query as "nothing matches".
3017
+ const mentionSearchSettled =
3018
+ includeDefaultMentionSearch &&
3019
+ !mentionsLoading &&
3020
+ settledMentionQuery === mentionQuery;
2965
3021
  const filteredCommandsRef = useRef(filteredCommands);
2966
3022
  filteredCommandsRef.current = filteredCommands;
2967
3023
  const filteredSkillsRef = useRef(filteredSkills);
2968
3024
  filteredSkillsRef.current = filteredSkills;
2969
- const hasContextMenuRef = useRef(hasContextMenu);
2970
- hasContextMenuRef.current = hasContextMenu;
2971
3025
  const launchersDisabledRef = useRef(disabled || contextControlsDisabled);
2972
3026
  launchersDisabledRef.current = disabled || contextControlsDisabled;
2973
3027
  const onSlashCommandRef = useRef(onSlashCommand);
@@ -3018,6 +3072,24 @@ export function TiptapComposer({
3018
3072
  popoverStateRef.current = null;
3019
3073
  }, []);
3020
3074
 
3075
+ // A query nothing matches is plain text ("@builder.io", "@3pm"), so end the
3076
+ // mention there instead of holding later keys.
3077
+ useEffect(() => {
3078
+ if (
3079
+ mentionQuery &&
3080
+ mentionSearchSettled &&
3081
+ inlineMentionItems.length === 0
3082
+ ) {
3083
+ closePopover();
3084
+ }
3085
+ }, [mentionQuery, mentionSearchSettled, inlineMentionItems, closePopover]);
3086
+
3087
+ // The + menu runs a request from its own effect, which fires before this one;
3088
+ // clear it so a remounted menu cannot run the same action again.
3089
+ useEffect(() => {
3090
+ if (contextEntryRequest) setContextEntryRequest(null);
3091
+ }, [contextEntryRequest]);
3092
+
3021
3093
  // Persist draft to localStorage so refreshes don't lose the prompt.
3022
3094
  const hasDraftScope = Boolean(draftScope?.trim());
3023
3095
  const draftKey =
@@ -3479,17 +3551,6 @@ export function TiptapComposer({
3479
3551
 
3480
3552
  // Handle popover keyboard nav
3481
3553
  if (pop) {
3482
- if (event.key === " " && pop.type === "@" && pop.query) {
3483
- const exact = findExactMentionItem(
3484
- mentionItemsRef.current,
3485
- pop.query,
3486
- );
3487
- if (exact) {
3488
- event.preventDefault();
3489
- selectMention(view, pop, exact);
3490
- return true;
3491
- }
3492
- }
3493
3554
  if (event.key === "ArrowUp") {
3494
3555
  event.preventDefault();
3495
3556
  popoverRef.current?.moveUp();
@@ -3500,7 +3561,18 @@ export function TiptapComposer({
3500
3561
  popoverRef.current?.moveDown();
3501
3562
  return true;
3502
3563
  }
3503
- if (event.key === "Enter") {
3564
+ // A mention comes only from an explicit pick of a highlighted row. With
3565
+ // nothing highlighted, even while a search is pending, the "@" is
3566
+ // plain text: Enter submits it and a late result changes nothing.
3567
+ const highlighted =
3568
+ pop.type === "@" ? popoverRef.current?.getSelectedMention() : null;
3569
+ if (event.key === "Enter" && pop.type === "@" && !highlighted) {
3570
+ closePopover();
3571
+ } else if (event.key === "Tab" && !event.shiftKey && highlighted) {
3572
+ event.preventDefault();
3573
+ selectMention(view, pop, highlighted);
3574
+ return true;
3575
+ } else if (event.key === "Enter") {
3504
3576
  event.preventDefault();
3505
3577
  const idx = popoverRef.current?.getSelectedIndex() ?? 0;
3506
3578
  const currentCommands = filteredCommandsRef.current;
@@ -3616,22 +3688,17 @@ export function TiptapComposer({
3616
3688
  }
3617
3689
 
3618
3690
  // Detect @ trigger — only when preceded by start-of-text, space, or newline
3619
- // (not after alphanumeric chars, which would indicate an email address)
3691
+ // (not after alphanumeric chars, which would indicate an email address).
3692
+ // Keep the typed "@" in the draft and focus in the editor: a focus-taking
3693
+ // menu here would swallow the rest of a literal like "@builder.io".
3620
3694
  if (event.key === "@") {
3695
+ if (launchersDisabledRef.current) return false;
3621
3696
  const { from } = view.state.selection;
3622
3697
  const textBefore = view.state.doc.textBetween(
3623
3698
  Math.max(0, from - 1),
3624
3699
  from,
3625
3700
  );
3626
3701
  if (from === 1 || textBefore === "" || /\s/.test(textBefore)) {
3627
- if (hasContextMenuRef.current) {
3628
- if (launchersDisabledRef.current) return false;
3629
- event.preventDefault();
3630
- popoverStateRef.current = null;
3631
- setPopover(null);
3632
- setContextMenuOpen(true);
3633
- return true;
3634
- }
3635
3702
  const position = getComposerPopoverAnchorPosition(view, from);
3636
3703
  if (!position) return false;
3637
3704
  setTimeout(() => {
@@ -3843,15 +3910,7 @@ export function TiptapComposer({
3843
3910
  insertTextAtCursor(text: string) {
3844
3911
  if (!isComposerEditorUsable(editor)) return;
3845
3912
  editor.commands.focus();
3846
- // An inserted "@" opens the shared Add menu when the host provides one.
3847
- const mention = text === "@";
3848
- if (mention && hasContextMenuRef.current) {
3849
- if (launchersDisabledRef.current) return;
3850
- popoverStateRef.current = null;
3851
- setPopover(null);
3852
- setContextMenuOpen(true);
3853
- return;
3854
- }
3913
+ const mention = text === "@" && !launchersDisabledRef.current;
3855
3914
  let inserted = text;
3856
3915
  if (mention) {
3857
3916
  const { from } = editor.state.selection;
@@ -5324,6 +5383,14 @@ export function TiptapComposer({
5324
5383
  const currentPos = ed.state.selection.from;
5325
5384
  // startPos is after the trigger char, so -1 to include the @ or /
5326
5385
  const deleteFrom = Math.max(0, pop.startPos - 1);
5386
+ if (item.source === COMPOSER_CONTEXT_ENTRY_SOURCE && item.refId) {
5387
+ ed.chain()
5388
+ .focus()
5389
+ .deleteRange({ from: deleteFrom, to: currentPos })
5390
+ .run();
5391
+ setContextEntryRequest({ id: item.refId });
5392
+ return;
5393
+ }
5327
5394
  const normalized = adapters.agentChat!.normalizeReference!(
5328
5395
  composerReferenceFromMentionItem(item),
5329
5396
  ) as AgentComposerReference | null;
@@ -5907,6 +5974,7 @@ export function TiptapComposer({
5907
5974
  ) : hasContextMenu ? (
5908
5975
  <ComposerPlusMenu
5909
5976
  contextMenuItems={launchersDisabled ? [] : contextLauncherItems}
5977
+ openEntry={contextEntryRequest}
5910
5978
  mode={
5911
5979
  launchersDisabled || plusMenuMode === "hidden"
5912
5980
  ? "upload-only"
@@ -6040,7 +6108,7 @@ export function TiptapComposer({
6040
6108
  density={mentionPopoverDensity}
6041
6109
  type={popover?.type ?? "@"}
6042
6110
  position={popover?.position ?? null}
6043
- mentionItems={filteredMentionItems}
6111
+ mentionItems={inlineMentionItems}
6044
6112
  skills={filteredSkills}
6045
6113
  commands={filteredCommands}
6046
6114
  hint={hint}
@@ -12,6 +12,7 @@ export function useMentionSearch(
12
12
  const resolveRequestPath = resolvePathOverride ?? resolvePath;
13
13
  const [items, setItems] = useState<MentionItem[]>([]);
14
14
  const [isLoading, setIsLoading] = useState(false);
15
+ const [settledQuery, setSettledQuery] = useState<string | null>(null);
15
16
  const [error, setError] = useState<Error | null>(null);
16
17
  const [revision, setRevision] = useState(0);
17
18
  const retry = useCallback(() => setRevision((value) => value + 1), []);
@@ -24,6 +25,7 @@ export function useMentionSearch(
24
25
  if (!enabled) {
25
26
  setItems([]);
26
27
  setIsLoading(false);
28
+ setSettledQuery(null);
27
29
  return;
28
30
  }
29
31
 
@@ -33,6 +35,7 @@ export function useMentionSearch(
33
35
 
34
36
  setItems([]);
35
37
  setIsLoading(true);
38
+ setSettledQuery(null);
36
39
 
37
40
  const debounceMs = query.length === 0 ? 0 : 150;
38
41
 
@@ -101,6 +104,10 @@ export function useMentionSearch(
101
104
  receive(line);
102
105
  }
103
106
  }
107
+ // Only a completed search settles its query: a failed one is unknown,
108
+ // not empty, and must not read as "nothing matches".
109
+ if (id === requestIdRef.current && !abort.signal.aborted)
110
+ setSettledQuery(query);
104
111
  } catch (err: unknown) {
105
112
  if (abort.signal.aborted) return;
106
113
  if (id === requestIdRef.current)
@@ -119,5 +126,5 @@ export function useMentionSearch(
119
126
  };
120
127
  }, [query, enabled, resolveRequestPath, revision]);
121
128
 
122
- return { items, isLoading, error, retry };
129
+ return { items, isLoading, error, retry, settledQuery };
123
130
  }