@agent-native/toolkit 0.21.3 → 0.22.0-nightly-20260926081002
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/agent-native.eject.json +11 -0
- package/dist/agentkit.d.ts +5 -1
- package/dist/agentkit.d.ts.map +1 -1
- package/dist/agentkit.js +2 -0
- package/dist/agentkit.js.map +1 -1
- package/dist/app-shell/PromptHome.d.ts +26 -0
- package/dist/app-shell/PromptHome.d.ts.map +1 -0
- package/dist/app-shell/PromptHome.js +19 -0
- package/dist/app-shell/PromptHome.js.map +1 -0
- package/dist/app-shell/PromptHome.spec.d.ts +2 -0
- package/dist/app-shell/PromptHome.spec.d.ts.map +1 -0
- package/dist/app-shell/PromptHome.spec.js +206 -0
- package/dist/app-shell/PromptHome.spec.js.map +1 -0
- package/dist/app-shell/TemplateLibraryGrid.d.ts +48 -0
- package/dist/app-shell/TemplateLibraryGrid.d.ts.map +1 -0
- package/dist/app-shell/TemplateLibraryGrid.js +20 -0
- package/dist/app-shell/TemplateLibraryGrid.js.map +1 -0
- package/dist/app-shell/TemplatePreviewDialog.d.ts +41 -0
- package/dist/app-shell/TemplatePreviewDialog.d.ts.map +1 -0
- package/dist/app-shell/TemplatePreviewDialog.js +50 -0
- package/dist/app-shell/TemplatePreviewDialog.js.map +1 -0
- package/dist/app-shell/TemplatePreviewDialog.spec.d.ts +2 -0
- package/dist/app-shell/TemplatePreviewDialog.spec.d.ts.map +1 -0
- package/dist/app-shell/TemplatePreviewDialog.spec.js +258 -0
- package/dist/app-shell/TemplatePreviewDialog.spec.js.map +1 -0
- package/dist/app-shell/index.d.ts +3 -0
- package/dist/app-shell/index.d.ts.map +1 -1
- package/dist/app-shell/index.js +3 -0
- package/dist/app-shell/index.js.map +1 -1
- package/dist/app-shell/prompt-home.css +159 -0
- package/dist/composer/ComposerContextMenu.d.ts +48 -0
- package/dist/composer/ComposerContextMenu.d.ts.map +1 -0
- package/dist/composer/ComposerContextMenu.js +295 -0
- package/dist/composer/ComposerContextMenu.js.map +1 -0
- package/dist/composer/ComposerContextMenu.spec.d.ts +2 -0
- package/dist/composer/ComposerContextMenu.spec.d.ts.map +1 -0
- package/dist/composer/ComposerContextMenu.spec.js +270 -0
- package/dist/composer/ComposerContextMenu.spec.js.map +1 -0
- package/dist/composer/ComposerContextMenuSearch.d.ts +11 -0
- package/dist/composer/ComposerContextMenuSearch.d.ts.map +1 -0
- package/dist/composer/ComposerContextMenuSearch.js +42 -0
- package/dist/composer/ComposerContextMenuSearch.js.map +1 -0
- package/dist/composer/ComposerContextPicker.d.ts +7 -0
- package/dist/composer/ComposerContextPicker.d.ts.map +1 -0
- package/dist/composer/ComposerContextPicker.js +71 -0
- package/dist/composer/ComposerContextPicker.js.map +1 -0
- package/dist/composer/ComposerContextPicker.spec.d.ts +2 -0
- package/dist/composer/ComposerContextPicker.spec.d.ts.map +1 -0
- package/dist/composer/ComposerContextPicker.spec.js +454 -0
- package/dist/composer/ComposerContextPicker.spec.js.map +1 -0
- package/dist/composer/ComposerContextPickerDialog.d.ts +8 -0
- package/dist/composer/ComposerContextPickerDialog.d.ts.map +1 -0
- package/dist/composer/ComposerContextPickerDialog.js +101 -0
- package/dist/composer/ComposerContextPickerDialog.js.map +1 -0
- package/dist/composer/ComposerContextPickerDialog.spec.d.ts +2 -0
- package/dist/composer/ComposerContextPickerDialog.spec.d.ts.map +1 -0
- package/dist/composer/ComposerContextPickerDialog.spec.js +329 -0
- package/dist/composer/ComposerContextPickerDialog.spec.js.map +1 -0
- package/dist/composer/ComposerContextSearchInput.d.ts +7 -0
- package/dist/composer/ComposerContextSearchInput.d.ts.map +1 -0
- package/dist/composer/ComposerContextSearchInput.js +18 -0
- package/dist/composer/ComposerContextSearchInput.js.map +1 -0
- package/dist/composer/PromptComposer.context.spec.d.ts +2 -0
- package/dist/composer/PromptComposer.context.spec.d.ts.map +1 -0
- package/dist/composer/PromptComposer.context.spec.js +383 -0
- package/dist/composer/PromptComposer.context.spec.js.map +1 -0
- package/dist/composer/PromptComposer.d.ts +19 -5
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +30 -25
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/PromptComposer.spec.js +24 -1
- package/dist/composer/PromptComposer.spec.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +13 -2
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +89 -32
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/composer/context-items.d.ts +12 -0
- package/dist/composer/context-items.d.ts.map +1 -0
- package/dist/composer/context-items.js +46 -0
- package/dist/composer/context-items.js.map +1 -0
- package/dist/composer/context-items.spec.d.ts +2 -0
- package/dist/composer/context-items.spec.d.ts.map +1 -0
- package/dist/composer/context-items.spec.js +43 -0
- package/dist/composer/context-items.spec.js.map +1 -0
- package/dist/composer/index.d.ts +4 -1
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/index.js +3 -1
- package/dist/composer/index.js.map +1 -1
- package/dist/composer/runtime-adapters.d.ts +6 -1
- package/dist/composer/runtime-adapters.d.ts.map +1 -1
- package/dist/composer/runtime-adapters.js.map +1 -1
- package/dist/composer/useComposerContextPicker.d.ts +111 -0
- package/dist/composer/useComposerContextPicker.d.ts.map +1 -0
- package/dist/composer/useComposerContextPicker.js +299 -0
- package/dist/composer/useComposerContextPicker.js.map +1 -0
- package/dist/design-system/default-adapter.d.ts.map +1 -1
- package/dist/design-system/default-adapter.js +13 -7
- package/dist/design-system/default-adapter.js.map +1 -1
- package/dist/design-system/default-adapter.spec.js +13 -0
- package/dist/design-system/default-adapter.spec.js.map +1 -1
- package/dist/design-system/types.d.ts +4 -1
- package/dist/design-system/types.d.ts.map +1 -1
- package/dist/styles.css +1 -0
- package/dist/ui/command.d.ts +8 -2
- package/dist/ui/command.d.ts.map +1 -1
- package/dist/ui/command.js +9 -2
- package/dist/ui/command.js.map +1 -1
- package/dist/ui/command.spec.js +20 -1
- package/dist/ui/command.spec.js.map +1 -1
- package/package.json +2 -1
- package/src/agentkit.ts +26 -0
- package/src/app-shell/PromptHome.spec.tsx +404 -0
- package/src/app-shell/PromptHome.tsx +111 -0
- package/src/app-shell/TemplateLibraryGrid.tsx +212 -0
- package/src/app-shell/TemplatePreviewDialog.spec.tsx +473 -0
- package/src/app-shell/TemplatePreviewDialog.tsx +224 -0
- package/src/app-shell/index.ts +3 -0
- package/src/app-shell/prompt-home.css +159 -0
- package/src/composer/ComposerContextMenu.spec.tsx +343 -0
- package/src/composer/ComposerContextMenu.tsx +610 -0
- package/src/composer/ComposerContextMenuSearch.tsx +69 -0
- package/src/composer/ComposerContextPicker.spec.tsx +520 -0
- package/src/composer/ComposerContextPicker.tsx +280 -0
- package/src/composer/ComposerContextPickerDialog.spec.tsx +405 -0
- package/src/composer/ComposerContextPickerDialog.tsx +274 -0
- package/src/composer/ComposerContextSearchInput.tsx +48 -0
- package/src/composer/PromptComposer.context.spec.tsx +577 -0
- package/src/composer/PromptComposer.spec.ts +24 -1
- package/src/composer/PromptComposer.tsx +60 -26
- package/src/composer/TiptapComposer.tsx +198 -48
- package/src/composer/context-items.spec.ts +68 -0
- package/src/composer/context-items.ts +72 -0
- package/src/composer/index.ts +27 -0
- package/src/composer/runtime-adapters.tsx +6 -1
- package/src/composer/useComposerContextPicker.ts +441 -0
- package/src/design-system/default-adapter.spec.tsx +23 -0
- package/src/design-system/default-adapter.tsx +52 -23
- package/src/design-system/types.ts +4 -1
- package/src/styles.css +1 -0
- package/src/ui/command.spec.tsx +33 -1
- package/src/ui/command.tsx +52 -9
|
@@ -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:
|
|
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
|
|
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 (
|
|
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${
|
|
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
|
|
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, {
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
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
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
<
|
|
4098
|
-
|
|
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 === "
|
|
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
|
+
}
|
package/src/composer/index.ts
CHANGED
|
@@ -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
|