@agent-native/toolkit 0.21.3 → 0.22.0-nightly-20260926111339

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 (166) hide show
  1. package/agent-native.eject.json +11 -20
  2. package/dist/agentkit.d.ts +5 -1
  3. package/dist/agentkit.d.ts.map +1 -1
  4. package/dist/agentkit.js +2 -0
  5. package/dist/agentkit.js.map +1 -1
  6. package/dist/app-shell/PromptHome.d.ts +26 -0
  7. package/dist/app-shell/PromptHome.d.ts.map +1 -0
  8. package/dist/app-shell/PromptHome.js +19 -0
  9. package/dist/app-shell/PromptHome.js.map +1 -0
  10. package/dist/app-shell/PromptHome.spec.d.ts +2 -0
  11. package/dist/app-shell/PromptHome.spec.d.ts.map +1 -0
  12. package/dist/app-shell/PromptHome.spec.js +206 -0
  13. package/dist/app-shell/PromptHome.spec.js.map +1 -0
  14. package/dist/app-shell/TemplateLibraryGrid.d.ts +48 -0
  15. package/dist/app-shell/TemplateLibraryGrid.d.ts.map +1 -0
  16. package/dist/app-shell/TemplateLibraryGrid.js +20 -0
  17. package/dist/app-shell/TemplateLibraryGrid.js.map +1 -0
  18. package/dist/app-shell/TemplatePreviewDialog.d.ts +41 -0
  19. package/dist/app-shell/TemplatePreviewDialog.d.ts.map +1 -0
  20. package/dist/app-shell/TemplatePreviewDialog.js +50 -0
  21. package/dist/app-shell/TemplatePreviewDialog.js.map +1 -0
  22. package/dist/app-shell/TemplatePreviewDialog.spec.d.ts +2 -0
  23. package/dist/app-shell/TemplatePreviewDialog.spec.d.ts.map +1 -0
  24. package/dist/app-shell/TemplatePreviewDialog.spec.js +258 -0
  25. package/dist/app-shell/TemplatePreviewDialog.spec.js.map +1 -0
  26. package/dist/app-shell/index.d.ts +3 -0
  27. package/dist/app-shell/index.d.ts.map +1 -1
  28. package/dist/app-shell/index.js +3 -0
  29. package/dist/app-shell/index.js.map +1 -1
  30. package/dist/app-shell/prompt-home.css +159 -0
  31. package/dist/composer/ComposerContextMenu.d.ts +48 -0
  32. package/dist/composer/ComposerContextMenu.d.ts.map +1 -0
  33. package/dist/composer/ComposerContextMenu.js +295 -0
  34. package/dist/composer/ComposerContextMenu.js.map +1 -0
  35. package/dist/composer/ComposerContextMenu.spec.d.ts +2 -0
  36. package/dist/composer/ComposerContextMenu.spec.d.ts.map +1 -0
  37. package/dist/composer/ComposerContextMenu.spec.js +270 -0
  38. package/dist/composer/ComposerContextMenu.spec.js.map +1 -0
  39. package/dist/composer/ComposerContextMenuSearch.d.ts +11 -0
  40. package/dist/composer/ComposerContextMenuSearch.d.ts.map +1 -0
  41. package/dist/composer/ComposerContextMenuSearch.js +42 -0
  42. package/dist/composer/ComposerContextMenuSearch.js.map +1 -0
  43. package/dist/composer/ComposerContextPicker.d.ts +7 -0
  44. package/dist/composer/ComposerContextPicker.d.ts.map +1 -0
  45. package/dist/composer/ComposerContextPicker.js +71 -0
  46. package/dist/composer/ComposerContextPicker.js.map +1 -0
  47. package/dist/composer/ComposerContextPicker.spec.d.ts +2 -0
  48. package/dist/composer/ComposerContextPicker.spec.d.ts.map +1 -0
  49. package/dist/composer/ComposerContextPicker.spec.js +454 -0
  50. package/dist/composer/ComposerContextPicker.spec.js.map +1 -0
  51. package/dist/composer/ComposerContextPickerDialog.d.ts +8 -0
  52. package/dist/composer/ComposerContextPickerDialog.d.ts.map +1 -0
  53. package/dist/composer/ComposerContextPickerDialog.js +101 -0
  54. package/dist/composer/ComposerContextPickerDialog.js.map +1 -0
  55. package/dist/composer/ComposerContextPickerDialog.spec.d.ts +2 -0
  56. package/dist/composer/ComposerContextPickerDialog.spec.d.ts.map +1 -0
  57. package/dist/composer/ComposerContextPickerDialog.spec.js +329 -0
  58. package/dist/composer/ComposerContextPickerDialog.spec.js.map +1 -0
  59. package/dist/composer/ComposerContextSearchInput.d.ts +7 -0
  60. package/dist/composer/ComposerContextSearchInput.d.ts.map +1 -0
  61. package/dist/composer/ComposerContextSearchInput.js +18 -0
  62. package/dist/composer/ComposerContextSearchInput.js.map +1 -0
  63. package/dist/composer/PromptComposer.context.spec.d.ts +2 -0
  64. package/dist/composer/PromptComposer.context.spec.d.ts.map +1 -0
  65. package/dist/composer/PromptComposer.context.spec.js +383 -0
  66. package/dist/composer/PromptComposer.context.spec.js.map +1 -0
  67. package/dist/composer/PromptComposer.d.ts +19 -5
  68. package/dist/composer/PromptComposer.d.ts.map +1 -1
  69. package/dist/composer/PromptComposer.js +30 -25
  70. package/dist/composer/PromptComposer.js.map +1 -1
  71. package/dist/composer/PromptComposer.spec.js +24 -1
  72. package/dist/composer/PromptComposer.spec.js.map +1 -1
  73. package/dist/composer/TiptapComposer.d.ts +13 -2
  74. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  75. package/dist/composer/TiptapComposer.js +89 -32
  76. package/dist/composer/TiptapComposer.js.map +1 -1
  77. package/dist/composer/context-items.d.ts +12 -0
  78. package/dist/composer/context-items.d.ts.map +1 -0
  79. package/dist/composer/context-items.js +46 -0
  80. package/dist/composer/context-items.js.map +1 -0
  81. package/dist/composer/context-items.spec.d.ts +2 -0
  82. package/dist/composer/context-items.spec.d.ts.map +1 -0
  83. package/dist/composer/context-items.spec.js +43 -0
  84. package/dist/composer/context-items.spec.js.map +1 -0
  85. package/dist/composer/index.d.ts +4 -1
  86. package/dist/composer/index.d.ts.map +1 -1
  87. package/dist/composer/index.js +3 -1
  88. package/dist/composer/index.js.map +1 -1
  89. package/dist/composer/runtime-adapters.d.ts +6 -1
  90. package/dist/composer/runtime-adapters.d.ts.map +1 -1
  91. package/dist/composer/runtime-adapters.js.map +1 -1
  92. package/dist/composer/useComposerContextPicker.d.ts +111 -0
  93. package/dist/composer/useComposerContextPicker.d.ts.map +1 -0
  94. package/dist/composer/useComposerContextPicker.js +299 -0
  95. package/dist/composer/useComposerContextPicker.js.map +1 -0
  96. package/dist/design-system/default-adapter.d.ts.map +1 -1
  97. package/dist/design-system/default-adapter.js +13 -7
  98. package/dist/design-system/default-adapter.js.map +1 -1
  99. package/dist/design-system/default-adapter.spec.js +13 -0
  100. package/dist/design-system/default-adapter.spec.js.map +1 -1
  101. package/dist/design-system/types.d.ts +4 -1
  102. package/dist/design-system/types.d.ts.map +1 -1
  103. package/dist/index.d.ts +0 -1
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +0 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/styles.css +1 -0
  108. package/dist/ui/command.d.ts +8 -2
  109. package/dist/ui/command.d.ts.map +1 -1
  110. package/dist/ui/command.js +9 -2
  111. package/dist/ui/command.js.map +1 -1
  112. package/dist/ui/command.spec.js +20 -1
  113. package/dist/ui/command.spec.js.map +1 -1
  114. package/package.json +2 -15
  115. package/src/agentkit.ts +26 -0
  116. package/src/app-shell/PromptHome.spec.tsx +404 -0
  117. package/src/app-shell/PromptHome.tsx +111 -0
  118. package/src/app-shell/TemplateLibraryGrid.tsx +212 -0
  119. package/src/app-shell/TemplatePreviewDialog.spec.tsx +473 -0
  120. package/src/app-shell/TemplatePreviewDialog.tsx +224 -0
  121. package/src/app-shell/index.ts +3 -0
  122. package/src/app-shell/prompt-home.css +159 -0
  123. package/src/composer/ComposerContextMenu.spec.tsx +343 -0
  124. package/src/composer/ComposerContextMenu.tsx +610 -0
  125. package/src/composer/ComposerContextMenuSearch.tsx +69 -0
  126. package/src/composer/ComposerContextPicker.spec.tsx +520 -0
  127. package/src/composer/ComposerContextPicker.tsx +280 -0
  128. package/src/composer/ComposerContextPickerDialog.spec.tsx +405 -0
  129. package/src/composer/ComposerContextPickerDialog.tsx +274 -0
  130. package/src/composer/ComposerContextSearchInput.tsx +48 -0
  131. package/src/composer/PromptComposer.context.spec.tsx +577 -0
  132. package/src/composer/PromptComposer.spec.ts +24 -1
  133. package/src/composer/PromptComposer.tsx +60 -26
  134. package/src/composer/TiptapComposer.tsx +198 -48
  135. package/src/composer/context-items.spec.ts +68 -0
  136. package/src/composer/context-items.ts +72 -0
  137. package/src/composer/index.ts +27 -0
  138. package/src/composer/runtime-adapters.tsx +6 -1
  139. package/src/composer/useComposerContextPicker.ts +441 -0
  140. package/src/design-system/default-adapter.spec.tsx +23 -0
  141. package/src/design-system/default-adapter.tsx +52 -23
  142. package/src/design-system/types.ts +4 -1
  143. package/src/index.ts +0 -1
  144. package/src/styles.css +1 -0
  145. package/src/ui/command.spec.tsx +33 -1
  146. package/src/ui/command.tsx +52 -9
  147. package/dist/marketing/MarketingHome.d.ts +0 -36
  148. package/dist/marketing/MarketingHome.d.ts.map +0 -1
  149. package/dist/marketing/MarketingHome.js +0 -28
  150. package/dist/marketing/MarketingHome.js.map +0 -1
  151. package/dist/marketing/MarketingHome.spec.d.ts +0 -2
  152. package/dist/marketing/MarketingHome.spec.d.ts.map +0 -1
  153. package/dist/marketing/MarketingHome.spec.js +0 -44
  154. package/dist/marketing/MarketingHome.spec.js.map +0 -1
  155. package/dist/marketing/Starfield.d.ts +0 -8
  156. package/dist/marketing/Starfield.d.ts.map +0 -1
  157. package/dist/marketing/Starfield.js +0 -349
  158. package/dist/marketing/Starfield.js.map +0 -1
  159. package/dist/marketing/index.d.ts +0 -3
  160. package/dist/marketing/index.d.ts.map +0 -1
  161. package/dist/marketing/index.js +0 -3
  162. package/dist/marketing/index.js.map +0 -1
  163. package/src/marketing/MarketingHome.spec.tsx +0 -75
  164. package/src/marketing/MarketingHome.tsx +0 -183
  165. package/src/marketing/Starfield.tsx +0 -403
  166. package/src/marketing/index.ts +0 -6
@@ -15,6 +15,9 @@ import {
15
15
  IconPencil,
16
16
  IconPlugConnected,
17
17
  IconHelpCircle,
18
+ IconRefresh,
19
+ IconAlertCircle,
20
+ IconLoader2,
18
21
  } from "@tabler/icons-react";
19
22
  import Placeholder from "@tiptap/extension-placeholder";
20
23
  import type { EditorView } from "@tiptap/pm/view";
@@ -39,10 +42,20 @@ import {
39
42
  } from "../ui/popover.js";
40
43
  import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
41
44
  import { formatAttachmentError } from "./attachment-accept.js";
45
+ import {
46
+ ComposerContextMenu,
47
+ type ComposerContextMenuItem,
48
+ } from "./ComposerContextMenu.js";
42
49
  import {
43
50
  ComposerPlusMenu,
44
51
  type ComposerTerminalModeControl,
45
52
  } from "./ComposerPlusMenu.js";
53
+ import {
54
+ areComposerContextItemsReady,
55
+ ComposerContextError,
56
+ snapshotComposerContextItems,
57
+ type ComposerContextSnapshot,
58
+ } from "./context-items.js";
46
59
  import { getComposerDraftKey } from "./draft-key.js";
47
60
  import { FileReference } from "./extensions/FileReference.js";
48
61
  import { MentionReference } from "./extensions/MentionReference.js";
@@ -93,6 +106,8 @@ export interface TiptapComposerHandle {
93
106
  /** Insert text through the editor's normal input path. */
94
107
  insertText(text: string): void;
95
108
  setText(text: string): void;
109
+ /** Submit replacement text with the current attachments and context, without editing the draft on failure. */
110
+ submitWithText(text: string): Promise<boolean>;
96
111
  insertReference(ref: AgentComposerReference): void;
97
112
  }
98
113
 
@@ -102,6 +117,7 @@ export const DEFAULT_VOICE_DICTATION_ENABLED = false;
102
117
 
103
118
  export interface TiptapComposerSubmitOptions {
104
119
  intent?: ComposerSubmitIntent;
120
+ contextItems?: ComposerContextSnapshot;
105
121
  }
106
122
 
107
123
  export function canSubmitComposerContent(options: {
@@ -762,6 +778,8 @@ export interface TiptapComposerProps {
762
778
  ariaLabel?: string;
763
779
  disabled?: boolean;
764
780
  /** Prevent submission without making the editable surface lose focus. */
781
+ submissionDisabled?: boolean;
782
+ /** Prevent submission while a host request is in flight. */
765
783
  submitting?: boolean;
766
784
  /** Override the generic document attachment cap for a multipart host. */
767
785
  maxDocumentAttachmentBytes?: number;
@@ -889,9 +907,13 @@ export interface TiptapComposerProps {
889
907
  /** Stable scope for persisted drafts, usually the active thread or tab id. */
890
908
  draftScope?: string;
891
909
  /** Keyed context nuggets staged for the next submitted prompt. */
892
- contextItems?: AgentChatContextItem[];
910
+ contextItems?: readonly AgentChatContextItem[];
893
911
  /** Remove a staged context nugget by key. */
894
912
  onRemoveContextItem?: (key: string) => void;
913
+ onInspectContextItem?: (key: string) => void;
914
+ onRetryContextItem?: (key: string) => void;
915
+ contextMenuItems?: readonly ComposerContextMenuItem[];
916
+ attachmentsEnabled?: boolean;
895
917
  /**
896
918
  * Controls the "+" menu next to the composer. `"full"` (default) shows the
897
919
  * normal Upload / Skill / Job / Automation / MCP picker, plus Extension when
@@ -2434,6 +2456,7 @@ export function TiptapComposer({
2434
2456
  placeholder,
2435
2457
  ariaLabel,
2436
2458
  disabled = false,
2459
+ submissionDisabled = false,
2437
2460
  submitting = false,
2438
2461
  maxDocumentAttachmentBytes = MAX_DOCUMENT_ATTACHMENT_BYTES,
2439
2462
  documentAttachmentLimitLabel = "PDFs",
@@ -2482,14 +2505,19 @@ export function TiptapComposer({
2482
2505
  onConnectLocalRuntime,
2483
2506
  imageModelMenu,
2484
2507
  draftScope,
2485
- contextItems = [],
2508
+ contextItems: providedContextItems,
2486
2509
  onRemoveContextItem,
2510
+ onInspectContextItem,
2511
+ onRetryContextItem,
2512
+ contextMenuItems,
2513
+ attachmentsEnabled = true,
2487
2514
  plusMenuMode = "full",
2488
2515
  terminalModeControl,
2489
2516
  extensionTools = false,
2490
2517
  interceptBuildRequestsForBuilder = false,
2491
2518
  onAttachmentError,
2492
2519
  }: TiptapComposerProps) {
2520
+ const contextItems = providedContextItems ?? [];
2493
2521
  const adapters = useComposerRuntimeAdapters();
2494
2522
  const t = adapters.translate!;
2495
2523
  const sendButtonTooltip = t(getComposerSendTooltipKey(willQueue), {
@@ -2512,10 +2540,18 @@ export function TiptapComposer({
2512
2540
  >(null);
2513
2541
  const composerText = useComposer((state) => state.text);
2514
2542
  const composerAttachments = useComposer((state) => state.attachments);
2543
+ const [contextSubmissionError, setContextSubmissionError] = useState<
2544
+ string | null
2545
+ >(null);
2546
+ useEffect(() => setContextSubmissionError(null), [providedContextItems]);
2515
2547
  const canSend = canSubmitComposerContent({
2516
2548
  hasEditorContent: editorHasText || slotReferences.length > 0,
2517
2549
  attachmentCount: composerAttachments.length,
2518
- disabled: disabled || submitting,
2550
+ disabled:
2551
+ disabled ||
2552
+ submissionDisabled ||
2553
+ submitting ||
2554
+ !areComposerContextItemsReady(contextItems),
2519
2555
  });
2520
2556
  const primaryAction = resolveComposerPrimaryAction({
2521
2557
  canSubmit: canSend,
@@ -2616,6 +2652,12 @@ export function TiptapComposer({
2616
2652
  onTextChangeRef.current = onTextChange;
2617
2653
  const contextItemsRef = useRef(contextItems);
2618
2654
  contextItemsRef.current = contextItems;
2655
+ const contextItemsProvidedRef = useRef(providedContextItems !== undefined);
2656
+ contextItemsProvidedRef.current = providedContextItems !== undefined;
2657
+ const submissionDisabledRef = useRef(
2658
+ disabled || submissionDisabled || submitting,
2659
+ );
2660
+ submissionDisabledRef.current = disabled || submissionDisabled || submitting;
2619
2661
  const onRemoveContextItemRef = useRef(onRemoveContextItem);
2620
2662
  onRemoveContextItemRef.current = onRemoveContextItem;
2621
2663
  const selectedContextItemKeyRef = useRef<string | null>(null);
@@ -2871,7 +2913,9 @@ export function TiptapComposer({
2871
2913
  const cursorAtStart = from === to && from <= 1;
2872
2914
  if (event.key === "Backspace" && onRemoveContextItemRef.current) {
2873
2915
  const chipAction = resolveContextChipBackspaceAction({
2874
- contextItemKeys: contextItemsRef.current.map((item) => item.key),
2916
+ contextItemKeys: contextItemsRef.current
2917
+ .filter((item) => item.removable !== false)
2918
+ .map((item) => item.key),
2875
2919
  selectedKey: selectedContextItemKeyRef.current,
2876
2920
  cursorAtStart,
2877
2921
  });
@@ -2883,9 +2927,6 @@ export function TiptapComposer({
2883
2927
  } else {
2884
2928
  selectedContextItemKeyRef.current = null;
2885
2929
  setSelectedContextItemKey(null);
2886
- contextItemsRef.current = contextItemsRef.current.filter(
2887
- (item) => item.key !== chipAction.key,
2888
- );
2889
2930
  onRemoveContextItemRef.current?.(chipAction.key);
2890
2931
  }
2891
2932
  return true;
@@ -3162,6 +3203,7 @@ export function TiptapComposer({
3162
3203
  onTextChangeRef.current?.(trimmed);
3163
3204
  flushComposerDraft();
3164
3205
  },
3206
+ submitWithText: (text: string) => submitComposer("immediate", text),
3165
3207
  insertReference,
3166
3208
  }));
3167
3209
 
@@ -3558,10 +3600,34 @@ export function TiptapComposer({
3558
3600
  );
3559
3601
 
3560
3602
  const submitComposer = useCallback(
3561
- async (intent: ComposerSubmitIntent = "immediate") => {
3603
+ async (
3604
+ intent: ComposerSubmitIntent = "immediate",
3605
+ textOverride?: string,
3606
+ ): Promise<boolean> => {
3562
3607
  const ed = editor;
3563
- if (!isComposerEditorUsable(ed)) return;
3564
- if (submitInFlightRef.current) return;
3608
+ if (!isComposerEditorUsable(ed)) return false;
3609
+ if (submitInFlightRef.current) return false;
3610
+ if (
3611
+ submissionDisabledRef.current ||
3612
+ !areComposerContextItemsReady(contextItemsRef.current)
3613
+ )
3614
+ return false;
3615
+ let contextSnapshot: ComposerContextSnapshot | undefined;
3616
+ setContextSubmissionError(null);
3617
+ try {
3618
+ contextSnapshot = snapshotComposerContextItems(
3619
+ contextItemsProvidedRef.current ? contextItemsRef.current : undefined,
3620
+ );
3621
+ } catch (error) {
3622
+ if (!(error instanceof ComposerContextError)) throw error;
3623
+ setContextSubmissionError(
3624
+ t("agentChat.composer.contextLimitExceeded", {
3625
+ defaultValue:
3626
+ "Context is too large. Remove an item or attach a smaller selection.",
3627
+ }),
3628
+ );
3629
+ return false;
3630
+ }
3565
3631
 
3566
3632
  draftEditorRef.current = ed;
3567
3633
  flushComposerDraft();
@@ -3583,10 +3649,11 @@ export function TiptapComposer({
3583
3649
  const isCurrentDraftScope = () =>
3584
3650
  draftKeyRef.current === submittingDraftKey &&
3585
3651
  draftScopeGenerationRef.current === submittingDraftGeneration;
3586
- const { text, references } = syncComposerState();
3652
+ const { text: draftText, references } = syncComposerState();
3653
+ const text = textOverride ?? draftText;
3587
3654
  const attachments = composerRuntime.getState().attachments;
3588
3655
  if (!text.trim() && references.length === 0 && attachments.length === 0)
3589
- return;
3656
+ return false;
3590
3657
  const oversizedDocumentError = getOversizedDocumentAttachmentError(
3591
3658
  attachments,
3592
3659
  {
@@ -3597,7 +3664,7 @@ export function TiptapComposer({
3597
3664
  );
3598
3665
  if (oversizedDocumentError) {
3599
3666
  onAttachmentErrorRef.current?.(oversizedDocumentError);
3600
- return;
3667
+ return false;
3601
3668
  }
3602
3669
  const cancelActiveVoice = () => {
3603
3670
  if (
@@ -3617,7 +3684,7 @@ export function TiptapComposer({
3617
3684
  if (matched) {
3618
3685
  clearEditorAfterSubmit();
3619
3686
  announceSlashCommand(matched);
3620
- return;
3687
+ return true;
3621
3688
  }
3622
3689
  }
3623
3690
 
@@ -3634,20 +3701,20 @@ export function TiptapComposer({
3634
3701
  ) {
3635
3702
  cancelActiveVoice();
3636
3703
  clearEditorAfterSubmit();
3637
- return;
3704
+ return true;
3638
3705
  }
3639
3706
 
3640
3707
  if (onBeforeSubmit) {
3641
3708
  submitInFlightRef.current = true;
3642
3709
  try {
3643
3710
  const shouldSubmit = await onBeforeSubmit();
3644
- if (!shouldSubmit) return;
3711
+ if (!shouldSubmit) return false;
3645
3712
  } finally {
3646
3713
  submitInFlightRef.current = false;
3647
3714
  }
3648
3715
  }
3649
- if (!isComposerEditorUsable(ed)) return;
3650
- if (!isCurrentDraftScope()) return;
3716
+ if (!isComposerEditorUsable(ed)) return false;
3717
+ if (!isCurrentDraftScope()) return false;
3651
3718
 
3652
3719
  // Composer mode: send with context via agent chat bridge
3653
3720
  if (composerMode) {
@@ -3668,15 +3735,25 @@ export function TiptapComposer({
3668
3735
  defaultValue: "Use the attached context.",
3669
3736
  })
3670
3737
  : "");
3738
+ const modeContext = config.getContext(modePrompt);
3739
+ const contextItemsText = contextSnapshot?.length
3740
+ ? formatPromptContextItems(contextSnapshot)
3741
+ : "";
3742
+ const context = contextItemsText
3743
+ ? `${modeContext}\n\n${contextItemsText}`
3744
+ : modeContext;
3671
3745
  if (attachments.length > 0) {
3672
3746
  composerRuntime.setText(
3673
- `${message}\n\n<context>\n${config.getContext(modePrompt)}\n</context>`,
3747
+ `${message}\n\n<context>\n${context}\n</context>`,
3674
3748
  );
3675
3749
  composerRuntime.send();
3676
3750
  } else {
3677
3751
  adapters.agentChat!.sendToAgentChat!({
3678
3752
  message,
3679
- context: config.getContext(modePrompt),
3753
+ context,
3754
+ ...(contextSnapshot === undefined
3755
+ ? {}
3756
+ : { contextItems: contextSnapshot }),
3680
3757
  mode:
3681
3758
  execMode === "plan"
3682
3759
  ? "plan"
@@ -3695,32 +3772,44 @@ export function TiptapComposer({
3695
3772
  cancelScheduledDraftPersist();
3696
3773
  clearComposerDraft(draftKey);
3697
3774
  closePopover();
3698
- return;
3775
+ return true;
3699
3776
  }
3700
3777
 
3701
3778
  if (onSubmit) {
3702
- if (submitInFlightRef.current) return;
3779
+ if (submitInFlightRef.current) return false;
3703
3780
  submitInFlightRef.current = true;
3704
3781
  try {
3705
- await onSubmit(text, references, attachments, { intent });
3706
- } catch {
3707
- // Hosts own their submit errors. Keep the draft and attachments
3708
- // available for recovery when a host rejects the submission.
3709
- return;
3782
+ await onSubmit(text, references, attachments, {
3783
+ intent,
3784
+ ...(contextSnapshot === undefined
3785
+ ? {}
3786
+ : { contextItems: contextSnapshot }),
3787
+ });
3788
+ } catch (error) {
3789
+ setContextSubmissionError(
3790
+ formatAttachmentError(
3791
+ error,
3792
+ t("agentChat.composer.submitFailed", {
3793
+ defaultValue: "Could not submit. Try again.",
3794
+ }),
3795
+ ),
3796
+ );
3797
+ return false;
3710
3798
  } finally {
3711
3799
  submitInFlightRef.current = false;
3712
3800
  }
3713
- if (!isCurrentDraftScope()) return;
3801
+ if (!isCurrentDraftScope()) return true;
3714
3802
  // Clear any pending attachments now that the host has them.
3715
3803
  void composerRuntime.clearAttachments().catch(() => {});
3716
3804
  if (!clearOnSubmit) {
3717
3805
  closePopover();
3718
- return;
3806
+ return true;
3719
3807
  }
3720
3808
  cancelActiveVoice();
3721
3809
  clearEditorAfterSubmit(submittingDraftSnapshot);
3722
- return;
3810
+ return true;
3723
3811
  } else {
3812
+ if (textOverride !== undefined) composerRuntime.setText(text);
3724
3813
  composerRuntime.send();
3725
3814
  }
3726
3815
  cancelActiveVoice();
@@ -3730,6 +3819,7 @@ export function TiptapComposer({
3730
3819
  cancelScheduledDraftPersist();
3731
3820
  clearComposerDraft(draftKey);
3732
3821
  closePopover();
3822
+ return true;
3733
3823
  },
3734
3824
  [
3735
3825
  closePopover,
@@ -4070,6 +4160,9 @@ export function TiptapComposer({
4070
4160
  {contextItems.map((item) => (
4071
4161
  <span
4072
4162
  key={item.key}
4163
+ data-context-key={item.key}
4164
+ data-context-status={item.status ?? "ready"}
4165
+ title={item.statusMessage}
4073
4166
  data-state={
4074
4167
  selectedContextItemKey === item.key ? "selected" : undefined
4075
4168
  }
@@ -4079,27 +4172,73 @@ export function TiptapComposer({
4079
4172
  : "border-border bg-muted/50"
4080
4173
  }`}
4081
4174
  >
4082
- <IconClipboardList className="h-3 w-3 shrink-0 text-muted-foreground" />
4083
- <span className="min-w-0 truncate">{item.title}</span>
4084
- <button
4085
- type="button"
4086
- onClick={() => {
4087
- selectedContextItemKeyRef.current = null;
4088
- setSelectedContextItemKey(null);
4089
- onRemoveContextItem?.(item.key);
4090
- }}
4091
- aria-label={t("agentChat.composer.removeContext", {
4092
- defaultValue: "Remove {{name}} context",
4093
- name: item.title,
4094
- })}
4095
- className="ms-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground"
4096
- >
4097
- <IconX className="h-3 w-3" />
4098
- </button>
4175
+ {item.status === "pending" ? (
4176
+ <IconLoader2
4177
+ aria-label={t("agentChat.composer.contextPending", {
4178
+ defaultValue: "Context pending",
4179
+ })}
4180
+ className="size-3 shrink-0 animate-spin motion-reduce:animate-none text-muted-foreground"
4181
+ />
4182
+ ) : item.status === "error" ? (
4183
+ <IconAlertCircle
4184
+ aria-label={t("agentChat.composer.contextError", {
4185
+ defaultValue: "Context failed",
4186
+ })}
4187
+ className="size-3 shrink-0 text-destructive"
4188
+ />
4189
+ ) : (
4190
+ <IconClipboardList className="h-3 w-3 shrink-0 text-muted-foreground" />
4191
+ )}
4192
+ {onInspectContextItem ? (
4193
+ <button
4194
+ type="button"
4195
+ onClick={() => onInspectContextItem(item.key)}
4196
+ className="min-w-0 truncate hover:underline"
4197
+ >
4198
+ {item.title}
4199
+ </button>
4200
+ ) : (
4201
+ <span className="min-w-0 truncate">{item.title}</span>
4202
+ )}
4203
+ {item.status === "error" && onRetryContextItem ? (
4204
+ <button
4205
+ type="button"
4206
+ onClick={() => onRetryContextItem(item.key)}
4207
+ aria-label={t("agentChat.composer.retryContext", {
4208
+ defaultValue: "Retry {{name}} context",
4209
+ name: item.title,
4210
+ })}
4211
+ className="flex size-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground"
4212
+ >
4213
+ <IconRefresh className="size-3" />
4214
+ </button>
4215
+ ) : null}
4216
+ {onRemoveContextItem && item.removable !== false ? (
4217
+ <button
4218
+ type="button"
4219
+ onClick={() => {
4220
+ selectedContextItemKeyRef.current = null;
4221
+ setSelectedContextItemKey(null);
4222
+ onRemoveContextItem?.(item.key);
4223
+ }}
4224
+ aria-label={t("agentChat.composer.removeContext", {
4225
+ defaultValue: "Remove {{name}} context",
4226
+ name: item.title,
4227
+ })}
4228
+ className="ms-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground"
4229
+ >
4230
+ <IconX className="h-3 w-3" />
4231
+ </button>
4232
+ ) : null}
4099
4233
  </span>
4100
4234
  ))}
4101
4235
  </div>
4102
4236
  )}
4237
+ {contextSubmissionError ? (
4238
+ <p role="alert" className="px-2 text-xs text-destructive">
4239
+ {contextSubmissionError}
4240
+ </p>
4241
+ ) : null}
4103
4242
  <div
4104
4243
  data-agent-composer-variant={layoutVariant}
4105
4244
  data-agent-composer-slot="editor-wrap"
@@ -4121,7 +4260,17 @@ export function TiptapComposer({
4121
4260
  className="agent-composer-toolbar flex items-center gap-1 px-2 py-1.5"
4122
4261
  >
4123
4262
  {attachButton ??
4124
- (plusMenuMode === "hidden" ? null : (
4263
+ (contextMenuItems !== undefined || plusMenuMode === "upload-only" ? (
4264
+ <ComposerContextMenu
4265
+ items={contextMenuItems ?? []}
4266
+ addAttachment={
4267
+ attachmentsEnabled ? addAttachmentForCurrentScope : undefined
4268
+ }
4269
+ attachmentAccept={composerRuntime.getState().attachmentAccept}
4270
+ onAttachmentError={onAttachmentError}
4271
+ disabled={disabled}
4272
+ />
4273
+ ) : plusMenuMode === "hidden" ? null : (
4125
4274
  <ComposerPlusMenu
4126
4275
  addAttachment={addAttachmentForCurrentScope}
4127
4276
  attachmentAccept={composerRuntime.getState().attachmentAccept}
@@ -4181,6 +4330,7 @@ export function TiptapComposer({
4181
4330
  type="button"
4182
4331
  onClick={() => void submitComposer("immediate")}
4183
4332
  disabled={!canSend}
4333
+ aria-label={sendButtonTooltip}
4184
4334
  data-agent-composer-slot="send-button"
4185
4335
  className="agent-composer-send-button shrink-0 flex h-7 w-7 items-center justify-center rounded-md bg-primary text-primary-foreground hover:opacity-90 transition-[opacity,transform] duration-150 active:scale-[0.97] disabled:opacity-30 disabled:cursor-not-allowed"
4186
4336
  >
@@ -0,0 +1,68 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ areComposerContextItemsReady,
5
+ snapshotComposerContextItems,
6
+ COMPOSER_CONTEXT_MAX_BYTES,
7
+ COMPOSER_CONTEXT_MAX_ITEMS,
8
+ } from "./context-items.js";
9
+
10
+ describe("persisted reference status", () => {
11
+ it("enforces item count and serialized UTF-8 byte limits without dropping fields", () => {
12
+ const item = { key: "brief", title: "Brief", context: "" };
13
+ expect(
14
+ snapshotComposerContextItems(
15
+ Array.from({ length: COMPOSER_CONTEXT_MAX_ITEMS }, () => item),
16
+ ),
17
+ ).toHaveLength(COMPOSER_CONTEXT_MAX_ITEMS);
18
+ expect(() =>
19
+ snapshotComposerContextItems(
20
+ Array.from({ length: COMPOSER_CONTEXT_MAX_ITEMS + 1 }, () => item),
21
+ ),
22
+ ).toThrow("item limit");
23
+ const overhead = new TextEncoder().encode(JSON.stringify([item])).length;
24
+ const exact = {
25
+ ...item,
26
+ context: "x".repeat(COMPOSER_CONTEXT_MAX_BYTES - overhead),
27
+ };
28
+ expect(snapshotComposerContextItems([exact])[0].context).toBe(
29
+ exact.context,
30
+ );
31
+ expect(() =>
32
+ snapshotComposerContextItems([
33
+ { ...exact, context: exact.context + "x" },
34
+ ]),
35
+ ).toThrow("size limit");
36
+ expect(() =>
37
+ snapshotComposerContextItems([
38
+ { ...item, context: "界".repeat(COMPOSER_CONTEXT_MAX_BYTES / 2) },
39
+ ]),
40
+ ).toThrow("size limit");
41
+ expect(() =>
42
+ snapshotComposerContextItems([
43
+ { ...item, statusMessage: "x".repeat(COMPOSER_CONTEXT_MAX_BYTES) },
44
+ ]),
45
+ ).toThrow("size limit");
46
+ });
47
+ it("permits steering with a failed persisted source while preserving its honest status", () => {
48
+ const item = {
49
+ key: "system-source:source-one",
50
+ title: "Source",
51
+ context: "systemId=qa, sourceId=source-one",
52
+ status: "error" as const,
53
+ statusMessage: "Read failed",
54
+ removable: false,
55
+ blocksSubmission: false,
56
+ };
57
+ expect(areComposerContextItemsReady([item])).toBe(true);
58
+ expect(snapshotComposerContextItems([item])).toEqual([item]);
59
+ });
60
+ it.each(["pending", "error"] as const)(
61
+ "still blocks a normal %s upload/context",
62
+ (status) => {
63
+ const item = { key: "upload", title: "Upload", context: "", status };
64
+ expect(areComposerContextItemsReady([item])).toBe(false);
65
+ expect(() => snapshotComposerContextItems([item])).toThrow("not ready");
66
+ },
67
+ );
68
+ });
@@ -0,0 +1,72 @@
1
+ import type { AgentChatContextItem } from "./runtime-adapters.js";
2
+
3
+ export type ComposerContextSnapshot = readonly Readonly<AgentChatContextItem>[];
4
+
5
+ export const COMPOSER_CONTEXT_MAX_ITEMS = 32;
6
+ export const COMPOSER_CONTEXT_MAX_BYTES = 64 * 1024;
7
+
8
+ export class ComposerContextError extends Error {
9
+ constructor(
10
+ public readonly code: "not-ready" | "too-many-items" | "too-large",
11
+ ) {
12
+ super(
13
+ code === "not-ready"
14
+ ? "Composer context is not ready"
15
+ : code === "too-many-items"
16
+ ? "Composer context exceeds the item limit"
17
+ : "Composer context exceeds the size limit",
18
+ );
19
+ this.name = "ComposerContextError";
20
+ }
21
+ }
22
+
23
+ export function areComposerContextItemsReady(
24
+ items: readonly AgentChatContextItem[] | undefined,
25
+ ): boolean {
26
+ return !items?.some(
27
+ (item) =>
28
+ item.blocksSubmission !== false && item.status && item.status !== "ready",
29
+ );
30
+ }
31
+
32
+ export function snapshotComposerContextItems(
33
+ items: readonly AgentChatContextItem[],
34
+ ): ComposerContextSnapshot;
35
+ export function snapshotComposerContextItems(
36
+ items: readonly AgentChatContextItem[] | undefined,
37
+ ): ComposerContextSnapshot | undefined;
38
+ export function snapshotComposerContextItems(
39
+ items: readonly AgentChatContextItem[] | undefined,
40
+ ): ComposerContextSnapshot | undefined {
41
+ if (items === undefined) return undefined;
42
+ if (items.length > COMPOSER_CONTEXT_MAX_ITEMS) {
43
+ throw new ComposerContextError("too-many-items");
44
+ }
45
+ if (!areComposerContextItemsReady(items)) {
46
+ throw new ComposerContextError("not-ready");
47
+ }
48
+ const snapshot = Object.freeze(
49
+ items.map((item) =>
50
+ Object.freeze({
51
+ key: item.key,
52
+ title: item.title,
53
+ context: item.context,
54
+ ...(item.removable === undefined ? {} : { removable: item.removable }),
55
+ ...(item.blocksSubmission === undefined
56
+ ? {}
57
+ : { blocksSubmission: item.blocksSubmission }),
58
+ ...(item.status === undefined ? {} : { status: item.status }),
59
+ ...(item.statusMessage === undefined
60
+ ? {}
61
+ : { statusMessage: item.statusMessage }),
62
+ }),
63
+ ),
64
+ );
65
+ if (
66
+ new TextEncoder().encode(JSON.stringify(snapshot)).length >
67
+ COMPOSER_CONTEXT_MAX_BYTES
68
+ ) {
69
+ throw new ComposerContextError("too-large");
70
+ }
71
+ return snapshot;
72
+ }
@@ -1,4 +1,30 @@
1
1
  export { FileReference } from "./extensions/FileReference.js";
2
+ export {
3
+ ComposerContextMenu,
4
+ ComposerContextSearchInput,
5
+ type ComposerContextSearchInputProps,
6
+ type ComposerContextPageControls,
7
+ getComposerContextMenuEntries,
8
+ type ComposerContextMenuProps,
9
+ type ComposerContextMenuItem,
10
+ type ComposerContextMenuAction,
11
+ type ComposerContextMenuCategory,
12
+ type ComposerContextPickerConfig,
13
+ type ComposerContextPickerItem,
14
+ type ComposerContextPickerRequest,
15
+ type ComposerContextPickerResult,
16
+ type ComposerContextPickerSelection,
17
+ type ComposerContextPickerFooterAction,
18
+ } from "./ComposerContextMenu.js";
19
+ export {
20
+ areComposerContextItemsReady,
21
+ snapshotComposerContextItems,
22
+ ComposerContextError,
23
+ COMPOSER_CONTEXT_MAX_ITEMS,
24
+ COMPOSER_CONTEXT_MAX_BYTES,
25
+ type ComposerContextSnapshot,
26
+ } from "./context-items.js";
27
+ export type { AgentChatContextItem } from "./runtime-adapters.js";
2
28
  export { SkillReference } from "./extensions/SkillReference.js";
3
29
  export { MentionReference } from "./extensions/MentionReference.js";
4
30
  export {
@@ -19,6 +45,7 @@ export {
19
45
  displayableComposerModeMessage,
20
46
  getComposerSubmitIntentForEnterKey,
21
47
  handleComposerFileDrop,
48
+ getOversizedDocumentAttachmentError,
22
49
  insertComposerHardBreakAndScrollIntoView,
23
50
  type ComposerSubmitIntent,
24
51
  type TiptapComposerHandle,
@@ -78,11 +78,16 @@ export interface AgentChatContextItem {
78
78
  key: string;
79
79
  title: string;
80
80
  context: string;
81
+ status?: "ready" | "pending" | "error";
82
+ statusMessage?: string;
83
+ removable?: boolean;
84
+ blocksSubmission?: boolean;
81
85
  }
82
86
 
83
87
  export interface ComposerAgentChatMessage {
84
88
  message: string;
85
89
  context?: string;
90
+ contextItems?: readonly Readonly<AgentChatContextItem>[];
86
91
  mode?: "plan" | "act";
87
92
  submit?: boolean;
88
93
  }
@@ -352,7 +357,7 @@ export const AGENT_CHAT_INSERT_REFERENCE_MESSAGE_TYPE =
352
357
  "agent-native:insert-composer-reference";
353
358
 
354
359
  export function formatPromptContextItems(
355
- items: AgentChatContextItem[] | undefined,
360
+ items: readonly AgentChatContextItem[] | undefined,
356
361
  ): string {
357
362
  return (
358
363
  items