@agent-native/toolkit 0.225.2-nightly-20261106152255 → 0.226.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-native.eject.json +1 -0
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +124 -9
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/agentkit/react/context.d.ts +1 -1
- package/dist/app/agentkit/react/core-file-storage.d.ts +21 -0
- package/dist/app/agentkit/react/core-file-storage.d.ts.map +1 -0
- package/dist/app/agentkit/react/core-file-storage.js +3 -0
- package/dist/app/agentkit/react/core-file-storage.js.map +1 -0
- package/dist/app/auth/AuthPage.d.ts.map +1 -1
- package/dist/app/auth/AuthPage.js +89 -10
- package/dist/app/auth/AuthPage.js.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +39 -58
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/MultiTabAssistantChat.js +3 -1
- package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
- package/dist/app/chat/agentkit-chat/root.d.ts.map +1 -1
- package/dist/app/chat/agentkit-chat/root.js +66 -2
- package/dist/app/chat/agentkit-chat/root.js.map +1 -1
- package/dist/app/chat/chat/surface-types.d.ts +1 -0
- package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
- package/dist/app/chat/chat/surface-types.js.map +1 -1
- package/dist/app/chat/composer/runtime-adapters.d.ts.map +1 -1
- package/dist/app/chat/composer/runtime-adapters.js +2 -0
- package/dist/app/chat/composer/runtime-adapters.js.map +1 -1
- package/dist/app/chat/composer/use-agentkit-capabilities.d.ts +3 -1
- package/dist/app/chat/composer/use-agentkit-capabilities.d.ts.map +1 -1
- package/dist/app/chat/composer/use-agentkit-capabilities.js +11 -11
- package/dist/app/chat/composer/use-agentkit-capabilities.js.map +1 -1
- package/dist/app/chat/composer/use-workspace-composer-menu.d.ts +3 -0
- package/dist/app/chat/composer/use-workspace-composer-menu.d.ts.map +1 -0
- package/dist/app/chat/composer/use-workspace-composer-menu.js +80 -0
- package/dist/app/chat/composer/use-workspace-composer-menu.js.map +1 -0
- package/dist/app/chat/index.d.ts +1 -0
- package/dist/app/chat/index.d.ts.map +1 -1
- package/dist/app/chat/index.js +1 -0
- package/dist/app/chat/index.js.map +1 -1
- package/dist/app/chat/prepare-prompt-composer-agent-handoff.d.ts +5 -0
- package/dist/app/chat/prepare-prompt-composer-agent-handoff.d.ts.map +1 -0
- package/dist/app/chat/prepare-prompt-composer-agent-handoff.js +108 -0
- package/dist/app/chat/prepare-prompt-composer-agent-handoff.js.map +1 -0
- package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.js +6 -1
- package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.js +6 -1
- package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
- package/dist/app/i18n/catalogs/en-US.d.ts +6 -1
- package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/en-US.js +6 -1
- package/dist/app/i18n/catalogs/en-US.js.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.js +6 -1
- package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.js +6 -1
- package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.js +6 -1
- package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.js +6 -1
- package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.js +6 -1
- package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.js +6 -1
- package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.js +6 -1
- package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.js +6 -1
- package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
- package/dist/app/resources/McpIntegrationDialog.d.ts +3 -1
- package/dist/app/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/app/resources/McpIntegrationDialog.js +2 -2
- package/dist/app/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/app/settings/useBuilderStatus.js +119 -40
- package/dist/app/settings/useBuilderStatus.js.map +1 -1
- package/dist/composer/ComposerContextMenu.d.ts +36 -2
- package/dist/composer/ComposerContextMenu.d.ts.map +1 -1
- package/dist/composer/ComposerContextMenu.js +108 -40
- package/dist/composer/ComposerContextMenu.js.map +1 -1
- package/dist/composer/ComposerContextPickerDialog.d.ts.map +1 -1
- package/dist/composer/ComposerContextPickerDialog.js +63 -26
- package/dist/composer/ComposerContextPickerDialog.js.map +1 -1
- package/dist/composer/ComposerPlusMenu.d.ts +5 -3
- package/dist/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/composer/ComposerPlusMenu.js +166 -121
- package/dist/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/composer/MentionItemMedia.d.ts.map +1 -1
- package/dist/composer/MentionItemMedia.js +3 -1
- package/dist/composer/MentionItemMedia.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +1 -0
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +1 -0
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +117 -50
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/composer/index.d.ts +1 -0
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/runtime-adapters.d.ts +15 -0
- package/dist/composer/runtime-adapters.d.ts.map +1 -1
- package/dist/composer/runtime-adapters.js.map +1 -1
- package/dist/composer/use-composer-panel-placement.d.ts.map +1 -1
- package/dist/composer/use-composer-panel-placement.js +2 -4
- package/dist/composer/use-composer-panel-placement.js.map +1 -1
- package/dist/composer/use-mention-search.d.ts +7 -0
- package/dist/composer/use-mention-search.d.ts.map +1 -1
- package/dist/composer/use-mention-search.js +86 -19
- package/dist/composer/use-mention-search.js.map +1 -1
- package/dist/composer/useComposerContextPicker.d.ts +5 -1
- package/dist/composer/useComposerContextPicker.d.ts.map +1 -1
- package/dist/composer/useComposerContextPicker.js +18 -4
- package/dist/composer/useComposerContextPicker.js.map +1 -1
- package/package.json +4 -4
- package/src/app/agentkit/react/components.tsx +158 -11
- package/src/app/agentkit/react/composer-context.spec.tsx +180 -0
- package/src/app/agentkit/react/core-file-storage.tsx +14 -0
- package/src/app/auth/AuthPage.interaction.spec.tsx +143 -0
- package/src/app/auth/AuthPage.tsx +159 -13
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +65 -3
- package/src/app/chat/AgentKitAssistantChat.tsx +57 -65
- package/src/app/chat/MultiTabAssistantChat.spec.tsx +21 -0
- package/src/app/chat/MultiTabAssistantChat.tsx +4 -0
- package/src/app/chat/agentkit-chat/root.tsx +91 -8
- package/src/app/chat/chat/surface-types.ts +1 -0
- package/src/app/chat/composer/runtime-adapters.tsx +2 -0
- package/src/app/chat/composer/use-agentkit-capabilities.hardening.spec.tsx +26 -1
- package/src/app/chat/composer/use-agentkit-capabilities.ts +11 -11
- package/src/app/chat/composer/use-agentkit-integration-menu.spec.tsx +2 -4
- package/src/app/chat/composer/use-workspace-composer-menu.spec.tsx +174 -0
- package/src/app/chat/composer/use-workspace-composer-menu.tsx +122 -0
- package/src/app/chat/index.ts +1 -0
- package/src/app/chat/prepare-prompt-composer-agent-handoff.spec.ts +162 -0
- package/src/app/chat/prepare-prompt-composer-agent-handoff.ts +134 -0
- package/src/app/i18n/catalogs/ar-SA.ts +6 -1
- package/src/app/i18n/catalogs/de-DE.ts +6 -1
- package/src/app/i18n/catalogs/en-US.ts +6 -1
- package/src/app/i18n/catalogs/es-ES.ts +6 -1
- package/src/app/i18n/catalogs/fr-FR.ts +6 -1
- package/src/app/i18n/catalogs/hi-IN.ts +6 -1
- package/src/app/i18n/catalogs/ja-JP.ts +6 -1
- package/src/app/i18n/catalogs/ko-KR.ts +6 -1
- package/src/app/i18n/catalogs/pt-BR.ts +6 -1
- package/src/app/i18n/catalogs/zh-CN.ts +6 -1
- package/src/app/i18n/catalogs/zh-TW.ts +6 -1
- package/src/app/resources/McpIntegrationDialog.tsx +5 -0
- package/src/app/settings/useBuilderStatus.spec.tsx +229 -13
- package/src/app/settings/useBuilderStatus.ts +145 -47
- package/src/composer/ComposerContextMenu.spec.tsx +95 -32
- package/src/composer/ComposerContextMenu.tsx +284 -73
- package/src/composer/ComposerContextPicker.spec.tsx +13 -14
- package/src/composer/ComposerContextPickerDialog.spec.tsx +40 -3
- package/src/composer/ComposerContextPickerDialog.tsx +158 -24
- package/src/composer/ComposerPlusMenu.parity.spec.tsx +442 -62
- package/src/composer/ComposerPlusMenu.tsx +322 -237
- package/src/composer/MentionItemMedia.tsx +3 -0
- package/src/composer/PromptComposer.context.spec.tsx +253 -82
- package/src/composer/PromptComposer.tsx +2 -0
- package/src/composer/TiptapComposer.spec.ts +6 -8
- package/src/composer/TiptapComposer.tsx +148 -55
- package/src/composer/index.ts +5 -0
- package/src/composer/runtime-adapters.tsx +18 -0
- package/src/composer/use-composer-panel-placement.ts +2 -4
- package/src/composer/use-mention-search.spec.tsx +99 -1
- package/src/composer/use-mention-search.ts +101 -24
- package/src/composer/useComposerContextPicker.ts +25 -5
|
@@ -124,12 +124,14 @@ import {
|
|
|
124
124
|
snapshotComposerValue,
|
|
125
125
|
type AgentKitComposerSubmission,
|
|
126
126
|
} from "./composer-submission.js";
|
|
127
|
+
import { CoreFileStorageSetupContext } from "./core-file-storage.js";
|
|
127
128
|
export type { AgentKitComposerSubmission } from "./composer-submission.js";
|
|
128
129
|
|
|
129
130
|
import { splitAgentKitMessageContext } from "@agent-native/agentkit";
|
|
130
131
|
import type { AgentThreadState } from "@agent-native/agentkit/client";
|
|
131
132
|
import {
|
|
132
133
|
AgentKitCapabilityError,
|
|
134
|
+
AgentKitUploadError,
|
|
133
135
|
hasActiveAgentRuns,
|
|
134
136
|
isCurrentAgentSuggestion,
|
|
135
137
|
selectAgentSuggestions,
|
|
@@ -6506,6 +6508,100 @@ export function AgentKitComposer({
|
|
|
6506
6508
|
() => attachmentAdapter ?? createChatAttachmentAdapter(composerTranslate),
|
|
6507
6509
|
[attachmentAdapter, composerTranslate],
|
|
6508
6510
|
);
|
|
6511
|
+
const formatUploadError = (error: unknown): string => {
|
|
6512
|
+
const formatFailure = (failure: unknown): string => {
|
|
6513
|
+
const code =
|
|
6514
|
+
failure && typeof failure === "object" && "code" in failure
|
|
6515
|
+
? failure.code
|
|
6516
|
+
: undefined;
|
|
6517
|
+
const key =
|
|
6518
|
+
code === "upload_too_large"
|
|
6519
|
+
? "agentChat.composer.fileTooLarge"
|
|
6520
|
+
: code === "upload_session_expired"
|
|
6521
|
+
? "agentChat.composer.sessionExpired"
|
|
6522
|
+
: code === "upload_unsupported_type"
|
|
6523
|
+
? "agentChat.composer.unsupportedFileType"
|
|
6524
|
+
: code === "upload_network_error"
|
|
6525
|
+
? "agentChat.composer.uploadOffline"
|
|
6526
|
+
: code === "upload_unavailable"
|
|
6527
|
+
? "agentChat.composer.uploadUnavailable"
|
|
6528
|
+
: code === "upload_invalid_response"
|
|
6529
|
+
? "agentChat.composer.uploadFailed"
|
|
6530
|
+
: undefined;
|
|
6531
|
+
return key
|
|
6532
|
+
? composerTranslate(key, { size: 25 })
|
|
6533
|
+
: formatAttachmentError(failure, labels.dropFileFailed);
|
|
6534
|
+
};
|
|
6535
|
+
return error instanceof AgentKitUploadError
|
|
6536
|
+
? error.failures
|
|
6537
|
+
.map((failure) => `${failure.name}: ${formatFailure(failure.error)}`)
|
|
6538
|
+
.join("\n")
|
|
6539
|
+
: formatFailure(error);
|
|
6540
|
+
};
|
|
6541
|
+
const coreFileStorageSetup = useContext(CoreFileStorageSetupContext);
|
|
6542
|
+
const storageAnchorRef = useRef<HTMLDivElement>(null);
|
|
6543
|
+
const [storageState, setStorageState] = useState<
|
|
6544
|
+
"unknown" | "pending" | "ready" | "missing" | "unavailable"
|
|
6545
|
+
>("unknown");
|
|
6546
|
+
const [storageOpen, setStorageOpen] = useState(false);
|
|
6547
|
+
const storageOpenRef = useRef(false);
|
|
6548
|
+
const changeStorageOpen = useCallback((open: boolean) => {
|
|
6549
|
+
storageOpenRef.current = open;
|
|
6550
|
+
setStorageOpen(open);
|
|
6551
|
+
}, []);
|
|
6552
|
+
const storageScope = useMemo(
|
|
6553
|
+
() => ({ current: true }),
|
|
6554
|
+
[controller, threadId, canUpload, coreFileStorageSetup?.scopeKey],
|
|
6555
|
+
);
|
|
6556
|
+
useEffect(() => {
|
|
6557
|
+
storageScope.current = true;
|
|
6558
|
+
return () => {
|
|
6559
|
+
storageScope.current = false;
|
|
6560
|
+
};
|
|
6561
|
+
}, [storageScope, changeStorageOpen]);
|
|
6562
|
+
useEffect(() => {
|
|
6563
|
+
changeStorageOpen(false);
|
|
6564
|
+
setStorageState("unknown");
|
|
6565
|
+
}, [storageScope, changeStorageOpen]);
|
|
6566
|
+
const storageReadRef = useRef<{
|
|
6567
|
+
scope: typeof storageScope;
|
|
6568
|
+
pending: Promise<{ configured: boolean }>;
|
|
6569
|
+
} | null>(null);
|
|
6570
|
+
const requestCoreAttachment = useCallback(async () => {
|
|
6571
|
+
const isCurrentScope = () => storageScope.current;
|
|
6572
|
+
if (!canUpload || !isCurrentScope() || !coreFileStorageSetup)
|
|
6573
|
+
return "stale" as const;
|
|
6574
|
+
setStorageState("pending");
|
|
6575
|
+
changeStorageOpen(false);
|
|
6576
|
+
const previous = storageReadRef.current;
|
|
6577
|
+
const sharedRead =
|
|
6578
|
+
previous?.scope === storageScope ? previous.pending : null;
|
|
6579
|
+
const pending = sharedRead ?? coreFileStorageSetup.readStatus();
|
|
6580
|
+
storageReadRef.current = { scope: storageScope, pending };
|
|
6581
|
+
try {
|
|
6582
|
+
const status = await pending;
|
|
6583
|
+
if (!isCurrentScope()) return "stale" as const;
|
|
6584
|
+
setStorageState(status.configured ? "ready" : "missing");
|
|
6585
|
+
changeStorageOpen(!status.configured);
|
|
6586
|
+
return status.configured ? ("ready" as const) : ("missing" as const);
|
|
6587
|
+
} catch (error) {
|
|
6588
|
+
if (!isCurrentScope()) return "stale" as const;
|
|
6589
|
+
setStorageState("unavailable");
|
|
6590
|
+
changeStorageOpen(true);
|
|
6591
|
+
onAttachmentError?.(formatAttachmentError(error, labels.dropFileFailed));
|
|
6592
|
+
return "unavailable" as const;
|
|
6593
|
+
} finally {
|
|
6594
|
+
const currentRead = () => storageReadRef.current;
|
|
6595
|
+
if (currentRead()?.pending === pending) storageReadRef.current = null;
|
|
6596
|
+
}
|
|
6597
|
+
}, [
|
|
6598
|
+
canUpload,
|
|
6599
|
+
coreFileStorageSetup,
|
|
6600
|
+
storageScope,
|
|
6601
|
+
onAttachmentError,
|
|
6602
|
+
labels.dropFileFailed,
|
|
6603
|
+
changeStorageOpen,
|
|
6604
|
+
]);
|
|
6509
6605
|
const starting = useAgentKitSubmissionStarting(threadId);
|
|
6510
6606
|
const active = hasActiveRuns(thread) || starting;
|
|
6511
6607
|
const composerInitialText = editingMessage
|
|
@@ -6538,10 +6634,12 @@ export function AgentKitComposer({
|
|
|
6538
6634
|
return true;
|
|
6539
6635
|
};
|
|
6540
6636
|
const focusComposer = useCallback(() => {
|
|
6541
|
-
if (!autoFocus) return;
|
|
6637
|
+
if (!autoFocus || storageOpenRef.current) return;
|
|
6542
6638
|
composerRef.current?.focus();
|
|
6543
6639
|
if (typeof globalThis.requestAnimationFrame === "function") {
|
|
6544
|
-
globalThis.requestAnimationFrame(() =>
|
|
6640
|
+
globalThis.requestAnimationFrame(() => {
|
|
6641
|
+
if (!storageOpenRef.current) composerRef.current?.focus();
|
|
6642
|
+
});
|
|
6545
6643
|
}
|
|
6546
6644
|
}, [autoFocus]);
|
|
6547
6645
|
const reportAttachmentError = useCallback(
|
|
@@ -6650,6 +6748,25 @@ export function AgentKitComposer({
|
|
|
6650
6748
|
const onLocalSubmit = () => {
|
|
6651
6749
|
options.onLocalSubmit?.();
|
|
6652
6750
|
};
|
|
6751
|
+
if (files.length && !uploadGateRef.current.enabled) {
|
|
6752
|
+
throw new AgentKitCapabilityError(
|
|
6753
|
+
"uploads",
|
|
6754
|
+
"unavailable",
|
|
6755
|
+
uploadGateRef.current.reason ?? labels.error,
|
|
6756
|
+
);
|
|
6757
|
+
}
|
|
6758
|
+
if (files.length && coreFileStorageSetup) {
|
|
6759
|
+
const status = await requestCoreAttachment();
|
|
6760
|
+
if (status !== "ready") {
|
|
6761
|
+
throw new Error(
|
|
6762
|
+
composerTranslate(
|
|
6763
|
+
status === "missing"
|
|
6764
|
+
? "onboarding.fileStorage.title"
|
|
6765
|
+
: "onboarding.fileStorage.statusUnavailable",
|
|
6766
|
+
),
|
|
6767
|
+
);
|
|
6768
|
+
}
|
|
6769
|
+
}
|
|
6653
6770
|
if (!editingMessage && onSubmitOverride) {
|
|
6654
6771
|
const readinessEngine = options.engine ?? selectedEngine;
|
|
6655
6772
|
await controller.assertAiSetupReady({
|
|
@@ -6663,13 +6780,6 @@ export function AgentKitComposer({
|
|
|
6663
6780
|
await onSubmitOverride(text, files, references, submitOptions);
|
|
6664
6781
|
return;
|
|
6665
6782
|
}
|
|
6666
|
-
if (files.length && !uploadGateRef.current.enabled) {
|
|
6667
|
-
throw new AgentKitCapabilityError(
|
|
6668
|
-
"uploads",
|
|
6669
|
-
"unavailable",
|
|
6670
|
-
uploadGateRef.current.reason ?? labels.error,
|
|
6671
|
-
);
|
|
6672
|
-
}
|
|
6673
6783
|
const effort = options.effort;
|
|
6674
6784
|
const executionMode = options.requestMode ?? "act";
|
|
6675
6785
|
const contextItemsForSubmission = options.contextItems ?? contextItems;
|
|
@@ -6911,7 +7021,40 @@ export function AgentKitComposer({
|
|
|
6911
7021
|
const suggestions = selectAgentSuggestions(thread);
|
|
6912
7022
|
return (
|
|
6913
7023
|
<ComposerNoticeScope>
|
|
6914
|
-
<div
|
|
7024
|
+
<div
|
|
7025
|
+
ref={storageAnchorRef}
|
|
7026
|
+
className={`agentkit-composer-stack ${className ?? ""}`}
|
|
7027
|
+
>
|
|
7028
|
+
{coreFileStorageSetup && canUpload && (
|
|
7029
|
+
<>
|
|
7030
|
+
<coreFileStorageSetup.Setup
|
|
7031
|
+
open={storageOpen}
|
|
7032
|
+
anchorRef={storageAnchorRef}
|
|
7033
|
+
onOpenChange={(open) => {
|
|
7034
|
+
changeStorageOpen(open);
|
|
7035
|
+
if (!open) composerRef.current?.focus();
|
|
7036
|
+
}}
|
|
7037
|
+
onConnected={() => {
|
|
7038
|
+
void requestCoreAttachment();
|
|
7039
|
+
}}
|
|
7040
|
+
{...(storageState === "unavailable"
|
|
7041
|
+
? {
|
|
7042
|
+
status: "unavailable" as const,
|
|
7043
|
+
onRetry: () => {
|
|
7044
|
+
void requestCoreAttachment();
|
|
7045
|
+
},
|
|
7046
|
+
}
|
|
7047
|
+
: { status: "missing" as const })}
|
|
7048
|
+
/>
|
|
7049
|
+
{storageState === "pending" && (
|
|
7050
|
+
<span role="status" className="text-sm text-foreground">
|
|
7051
|
+
{composerTranslate("agentChat.common.loading", {
|
|
7052
|
+
defaultValue: "Loading...",
|
|
7053
|
+
})}
|
|
7054
|
+
</span>
|
|
7055
|
+
)}
|
|
7056
|
+
</>
|
|
7057
|
+
)}
|
|
6915
7058
|
{announcePendingSubmission && submissionPending ? (
|
|
6916
7059
|
<span
|
|
6917
7060
|
className="agentkit-visually-hidden"
|
|
@@ -7134,6 +7277,10 @@ export function AgentKitComposer({
|
|
|
7134
7277
|
stopButton={stopButton === undefined ? defaultStopButton : stopButton}
|
|
7135
7278
|
noticeOutlet
|
|
7136
7279
|
attachmentsEnabled={canUpload}
|
|
7280
|
+
onAttachmentRequest={onAttachmentRequest}
|
|
7281
|
+
contextButtonTooltipDisabled={
|
|
7282
|
+
contextButtonTooltipDisabled || storageOpen
|
|
7283
|
+
}
|
|
7137
7284
|
includeDefaultSlashCommands={includeDefaultSlashCommands ?? false}
|
|
7138
7285
|
includeDefaultSlashSkills={includeDefaultSlashSkills ?? false}
|
|
7139
7286
|
plusMenuMode={plusMenuMode ?? "full"}
|
|
@@ -7170,7 +7317,7 @@ export function AgentKitComposer({
|
|
|
7170
7317
|
dismissLabel={labels.dismissNotice}
|
|
7171
7318
|
>
|
|
7172
7319
|
<span {...SESSION_REPLAY_MASK_PROPS}>
|
|
7173
|
-
{sendOrCommandError
|
|
7320
|
+
{formatUploadError(sendOrCommandError)}
|
|
7174
7321
|
</span>
|
|
7175
7322
|
</ComposerNotice>
|
|
7176
7323
|
) : null}
|
|
@@ -18,6 +18,8 @@ import type {
|
|
|
18
18
|
AgentChatContextItem,
|
|
19
19
|
ComposerContextMenuItem,
|
|
20
20
|
} from "../../../agentkit.js";
|
|
21
|
+
import { ComposerRuntimeAdaptersProvider } from "../../../composer/runtime-adapters.js";
|
|
22
|
+
import { FileStorageSetupPopover } from "../../chat/FileStorageSetupPopover.js";
|
|
21
23
|
import {
|
|
22
24
|
AgentKitChat,
|
|
23
25
|
AgentKitComposer,
|
|
@@ -25,9 +27,14 @@ import {
|
|
|
25
27
|
} from "./components.js";
|
|
26
28
|
import { createAgentKitComposerSubmission } from "./composer-submission.js";
|
|
27
29
|
import { AgentKitProvider } from "./context.js";
|
|
30
|
+
import { CoreFileStorageSetupContext } from "./core-file-storage.js";
|
|
28
31
|
|
|
29
32
|
const capture = vi.hoisted(() => ({
|
|
30
33
|
props: undefined as PromptComposerProps | undefined,
|
|
34
|
+
storageStatus: vi.fn(),
|
|
35
|
+
storage: undefined as
|
|
36
|
+
| { open: boolean; status?: string; onRetry?: () => void }
|
|
37
|
+
| undefined,
|
|
31
38
|
}));
|
|
32
39
|
vi.mock("../../../agentkit.js", async (importOriginal) => {
|
|
33
40
|
const actual = await importOriginal<typeof import("../../../agentkit.js")>();
|
|
@@ -40,6 +47,20 @@ vi.mock("../../../agentkit.js", async (importOriginal) => {
|
|
|
40
47
|
};
|
|
41
48
|
});
|
|
42
49
|
|
|
50
|
+
vi.mock("@agent-native/core/client/uploads", () => ({
|
|
51
|
+
readFileUploadStatus: capture.storageStatus,
|
|
52
|
+
}));
|
|
53
|
+
vi.mock("../../chat/FileStorageSetupPopover.js", () => ({
|
|
54
|
+
FileStorageSetupPopover: (props: {
|
|
55
|
+
open: boolean;
|
|
56
|
+
status?: string;
|
|
57
|
+
onRetry?: () => void;
|
|
58
|
+
}) => {
|
|
59
|
+
capture.storage = props;
|
|
60
|
+
return null;
|
|
61
|
+
},
|
|
62
|
+
}));
|
|
63
|
+
|
|
43
64
|
let root: Root;
|
|
44
65
|
let container: HTMLDivElement;
|
|
45
66
|
let client: AgentKitClient;
|
|
@@ -50,6 +71,8 @@ beforeEach(() => {
|
|
|
50
71
|
document.body.append(container);
|
|
51
72
|
root = createRoot(container);
|
|
52
73
|
capture.props = undefined;
|
|
74
|
+
capture.storage = undefined;
|
|
75
|
+
capture.storageStatus.mockReset();
|
|
53
76
|
});
|
|
54
77
|
|
|
55
78
|
afterEach(async () => {
|
|
@@ -98,6 +121,163 @@ function withActiveRun(runtime: ReturnType<typeof transport>) {
|
|
|
98
121
|
}
|
|
99
122
|
|
|
100
123
|
describe("AgentKit composer context submission", () => {
|
|
124
|
+
it.each(["missing", "unavailable"] as const)(
|
|
125
|
+
"blocks raw-file custom submissions with visible %s storage state",
|
|
126
|
+
async (state) => {
|
|
127
|
+
client = new AgentKitClient({
|
|
128
|
+
transport: { ...transport(), capabilities: { uploads: true } },
|
|
129
|
+
});
|
|
130
|
+
if (state === "missing")
|
|
131
|
+
capture.storageStatus.mockResolvedValue({ configured: false });
|
|
132
|
+
else capture.storageStatus.mockRejectedValue(new Error("Status denied"));
|
|
133
|
+
const submit = vi.fn();
|
|
134
|
+
const upload = vi.spyOn(client, "uploadFiles");
|
|
135
|
+
await act(async () =>
|
|
136
|
+
root.render(
|
|
137
|
+
<AgentKitProvider controller={client} threadId="thread-1">
|
|
138
|
+
<CoreFileStorageSetupContext.Provider
|
|
139
|
+
value={{
|
|
140
|
+
scopeKey: "user-1:org-1",
|
|
141
|
+
readStatus: capture.storageStatus,
|
|
142
|
+
Setup: FileStorageSetupPopover,
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
<AgentKitComposer onSubmit={submit} />
|
|
146
|
+
</CoreFileStorageSetupContext.Provider>
|
|
147
|
+
</AgentKitProvider>,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
await act(async () => {
|
|
151
|
+
await expect(
|
|
152
|
+
capture.props?.onSubmit(
|
|
153
|
+
"Preserve draft",
|
|
154
|
+
[new File(["bytes"], "note.txt")],
|
|
155
|
+
[],
|
|
156
|
+
{},
|
|
157
|
+
),
|
|
158
|
+
).rejects.toThrow();
|
|
159
|
+
});
|
|
160
|
+
expect(submit).not.toHaveBeenCalled();
|
|
161
|
+
expect(upload).not.toHaveBeenCalled();
|
|
162
|
+
expect(capture.storage?.open).toBe(true);
|
|
163
|
+
expect(capture.storage?.status).toBe(state);
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
it("localizes create-upload failures while retaining the original draft and files", async () => {
|
|
167
|
+
const failure = Object.assign(new Error("upload_too_large"), {
|
|
168
|
+
code: "upload_too_large",
|
|
169
|
+
status: 413,
|
|
170
|
+
});
|
|
171
|
+
const runtime = {
|
|
172
|
+
...transport(),
|
|
173
|
+
capabilities: { uploads: true },
|
|
174
|
+
createUpload: vi.fn().mockRejectedValue(failure),
|
|
175
|
+
completeUpload: vi.fn(),
|
|
176
|
+
} satisfies AgentTransport;
|
|
177
|
+
const upload = vi.fn();
|
|
178
|
+
client = new AgentKitClient({ transport: runtime, upload });
|
|
179
|
+
const onLocalSubmit = vi.fn();
|
|
180
|
+
await act(async () =>
|
|
181
|
+
root.render(
|
|
182
|
+
<ComposerRuntimeAdaptersProvider
|
|
183
|
+
adapters={{ translate: (key) => `translated:${key}` }}
|
|
184
|
+
>
|
|
185
|
+
<AgentKitProvider controller={client} threadId="thread-1">
|
|
186
|
+
<AgentKitComposer initialText="Original draft" />
|
|
187
|
+
</AgentKitProvider>
|
|
188
|
+
</ComposerRuntimeAdaptersProvider>,
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
const files = [
|
|
192
|
+
new File(["Original bytes"], "note.txt", { type: "text/plain" }),
|
|
193
|
+
];
|
|
194
|
+
await act(async () => {
|
|
195
|
+
await expect(
|
|
196
|
+
capture.props?.onSubmit("Original draft", files, [], { onLocalSubmit }),
|
|
197
|
+
).rejects.toThrow();
|
|
198
|
+
});
|
|
199
|
+
expect(runtime.createUpload).toHaveBeenCalledOnce();
|
|
200
|
+
expect(upload).not.toHaveBeenCalled();
|
|
201
|
+
expect(runtime.startRun).not.toHaveBeenCalled();
|
|
202
|
+
expect(onLocalSubmit).not.toHaveBeenCalled();
|
|
203
|
+
expect(files[0].name).toBe("note.txt");
|
|
204
|
+
expect(container.textContent).toContain(
|
|
205
|
+
"translated:agentChat.composer.fileTooLarge",
|
|
206
|
+
);
|
|
207
|
+
expect(container.textContent).not.toContain("upload_too_large");
|
|
208
|
+
});
|
|
209
|
+
it("ignores a delayed storage response after the identity scope changes", async () => {
|
|
210
|
+
client = new AgentKitClient({
|
|
211
|
+
transport: { ...transport(), capabilities: { uploads: true } },
|
|
212
|
+
});
|
|
213
|
+
let resolveStatus: ((value: { configured: boolean }) => void) | undefined;
|
|
214
|
+
capture.storageStatus.mockImplementation(
|
|
215
|
+
() =>
|
|
216
|
+
new Promise((resolve) => {
|
|
217
|
+
resolveStatus = resolve;
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
220
|
+
const render = (scopeKey: string) =>
|
|
221
|
+
root.render(
|
|
222
|
+
<AgentKitProvider controller={client} threadId="thread-1">
|
|
223
|
+
<CoreFileStorageSetupContext.Provider
|
|
224
|
+
value={{
|
|
225
|
+
scopeKey,
|
|
226
|
+
readStatus: capture.storageStatus,
|
|
227
|
+
Setup: FileStorageSetupPopover,
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
<AgentKitComposer />
|
|
231
|
+
</CoreFileStorageSetupContext.Provider>
|
|
232
|
+
</AgentKitProvider>,
|
|
233
|
+
);
|
|
234
|
+
await act(async () => render("user-1:org-1"));
|
|
235
|
+
let submission: Promise<unknown> | undefined;
|
|
236
|
+
await act(async () => {
|
|
237
|
+
submission = capture.props?.onSubmit(
|
|
238
|
+
"draft",
|
|
239
|
+
[new File(["bytes"], "note.txt")],
|
|
240
|
+
[],
|
|
241
|
+
{},
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
await act(async () => render("user-2:org-2"));
|
|
245
|
+
await act(async () => {
|
|
246
|
+
resolveStatus?.({ configured: false });
|
|
247
|
+
await submission?.catch(() => undefined);
|
|
248
|
+
});
|
|
249
|
+
expect(capture.storage?.open).toBe(false);
|
|
250
|
+
expect(capture.props?.attachmentsEnabled).toBe(true);
|
|
251
|
+
});
|
|
252
|
+
it("preserves supported external upload adapters without querying Core storage", async () => {
|
|
253
|
+
client = new AgentKitClient({
|
|
254
|
+
transport: { ...transport(), capabilities: { uploads: true } },
|
|
255
|
+
});
|
|
256
|
+
await act(async () =>
|
|
257
|
+
root.render(
|
|
258
|
+
<AgentKitProvider controller={client} threadId="thread-1">
|
|
259
|
+
<AgentKitComposer />
|
|
260
|
+
</AgentKitProvider>,
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
expect(capture.props?.attachmentsEnabled).toBe(true);
|
|
264
|
+
expect(capture.props?.onAttachmentRequest).toBeUndefined();
|
|
265
|
+
expect(capture.storageStatus).not.toHaveBeenCalled();
|
|
266
|
+
});
|
|
267
|
+
it("keeps unsupported generic transports outside the Core storage setup flow", async () => {
|
|
268
|
+
client = new AgentKitClient({ transport: transport() });
|
|
269
|
+
await act(async () =>
|
|
270
|
+
root.render(
|
|
271
|
+
<AgentKitProvider controller={client} threadId="thread-1">
|
|
272
|
+
<AgentKitComposer />
|
|
273
|
+
</AgentKitProvider>,
|
|
274
|
+
),
|
|
275
|
+
);
|
|
276
|
+
expect(capture.props?.attachmentsEnabled).toBe(false);
|
|
277
|
+
expect(capture.props?.onAttachmentRequest).toBeUndefined();
|
|
278
|
+
expect(capture.storageStatus).not.toHaveBeenCalled();
|
|
279
|
+
});
|
|
280
|
+
|
|
101
281
|
it.each([
|
|
102
282
|
["immediate", "capability"],
|
|
103
283
|
["queued", "capability"],
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext, type ComponentType, type RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
type SetupProps = {
|
|
4
|
+
open: boolean;
|
|
5
|
+
anchorRef: RefObject<HTMLElement | null>;
|
|
6
|
+
onOpenChange: (open: boolean) => void;
|
|
7
|
+
onConnected: () => void;
|
|
8
|
+
} & ({ status: "missing" } | { status: "unavailable"; onRetry: () => void });
|
|
9
|
+
|
|
10
|
+
export const CoreFileStorageSetupContext = createContext<{
|
|
11
|
+
scopeKey: string;
|
|
12
|
+
readStatus: () => Promise<{ configured: boolean }>;
|
|
13
|
+
Setup: ComponentType<SetupProps>;
|
|
14
|
+
} | null>(null);
|
|
@@ -43,6 +43,149 @@ describe("AuthPage interactions", () => {
|
|
|
43
43
|
vi.unstubAllGlobals();
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
it.each(["control", "automatic"] as const)(
|
|
47
|
+
"shows Builder legal disclosure before Google-only signup only for the %s assignment",
|
|
48
|
+
async (variant) => {
|
|
49
|
+
vi.stubGlobal(
|
|
50
|
+
"fetch",
|
|
51
|
+
vi.fn(async (input: RequestInfo | URL) => ({
|
|
52
|
+
ok: true,
|
|
53
|
+
status: 200,
|
|
54
|
+
json: async () =>
|
|
55
|
+
String(input).includes("builder-signup-experiment")
|
|
56
|
+
? {
|
|
57
|
+
status: "assigned",
|
|
58
|
+
id: "a".repeat(32),
|
|
59
|
+
variant,
|
|
60
|
+
policy: "builder-signup-2026-11-06",
|
|
61
|
+
}
|
|
62
|
+
: { available: false },
|
|
63
|
+
})),
|
|
64
|
+
);
|
|
65
|
+
await act(async () => {
|
|
66
|
+
root.render(
|
|
67
|
+
<AuthPage
|
|
68
|
+
{...propsFromHtml(
|
|
69
|
+
getOnboardingHtml({
|
|
70
|
+
requestHost: "127.0.0.1",
|
|
71
|
+
requestPath: "/signup",
|
|
72
|
+
}),
|
|
73
|
+
)}
|
|
74
|
+
trackingApp="design"
|
|
75
|
+
googleOnly
|
|
76
|
+
showGoogle
|
|
77
|
+
identitySsoEnabled={false}
|
|
78
|
+
/>,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
expect(
|
|
82
|
+
container.textContent?.includes(
|
|
83
|
+
"Your Design signup also creates a Builder.io account",
|
|
84
|
+
),
|
|
85
|
+
).toBe(variant === "automatic");
|
|
86
|
+
expect(
|
|
87
|
+
container.querySelector<HTMLButtonElement>("#google-btn")?.disabled,
|
|
88
|
+
).toBe(false);
|
|
89
|
+
if (variant === "automatic") {
|
|
90
|
+
expect(
|
|
91
|
+
container.querySelector(
|
|
92
|
+
'a[href="https://www.builder.io/legal/terms"]',
|
|
93
|
+
),
|
|
94
|
+
).not.toBeNull();
|
|
95
|
+
expect(
|
|
96
|
+
container.querySelector(
|
|
97
|
+
'a[href="https://www.builder.io/legal/privacy"]',
|
|
98
|
+
),
|
|
99
|
+
).not.toBeNull();
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
it.each([200, 409])(
|
|
105
|
+
"opens Google immediately and preserves the existing flow when acceptance returns %s",
|
|
106
|
+
async (acceptStatus) => {
|
|
107
|
+
window.history.replaceState({}, "", "/signup");
|
|
108
|
+
const popup = {
|
|
109
|
+
closed: false,
|
|
110
|
+
location: { href: "" },
|
|
111
|
+
} as unknown as Window;
|
|
112
|
+
const open = vi.fn(() => popup);
|
|
113
|
+
vi.stubGlobal("open", open);
|
|
114
|
+
let accept: () => void = () => {
|
|
115
|
+
throw new Error("Acceptance has not started");
|
|
116
|
+
};
|
|
117
|
+
const oauthRequests = vi.fn();
|
|
118
|
+
let oauthUrl: string | undefined;
|
|
119
|
+
vi.stubGlobal(
|
|
120
|
+
"fetch",
|
|
121
|
+
vi.fn((input: RequestInfo | URL, init?: RequestInit) => {
|
|
122
|
+
const url = String(input);
|
|
123
|
+
if (
|
|
124
|
+
url.includes("builder-signup-experiment") &&
|
|
125
|
+
init?.method === "POST"
|
|
126
|
+
) {
|
|
127
|
+
return new Promise<Response>((resolve) => {
|
|
128
|
+
accept = () =>
|
|
129
|
+
resolve(
|
|
130
|
+
new Response(JSON.stringify({ status: "accepted" }), {
|
|
131
|
+
status: acceptStatus,
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (url.includes("google/auth-url")) {
|
|
137
|
+
oauthUrl = url;
|
|
138
|
+
oauthRequests();
|
|
139
|
+
return new Promise<Response>(() => {});
|
|
140
|
+
}
|
|
141
|
+
return Promise.resolve(
|
|
142
|
+
new Response(
|
|
143
|
+
JSON.stringify(
|
|
144
|
+
url.includes("builder-signup-experiment")
|
|
145
|
+
? {
|
|
146
|
+
status: "assigned",
|
|
147
|
+
id: "a".repeat(32),
|
|
148
|
+
variant: "automatic",
|
|
149
|
+
policy: "builder-signup-2026-11-06",
|
|
150
|
+
}
|
|
151
|
+
: { available: false },
|
|
152
|
+
),
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
await act(async () => {
|
|
158
|
+
root.render(
|
|
159
|
+
<AuthPage
|
|
160
|
+
{...propsFromHtml(
|
|
161
|
+
getOnboardingHtml({
|
|
162
|
+
requestHost: "127.0.0.1",
|
|
163
|
+
requestPath: "/signup",
|
|
164
|
+
}),
|
|
165
|
+
)}
|
|
166
|
+
trackingApp="design"
|
|
167
|
+
identitySsoEnabled={false}
|
|
168
|
+
googleViaIdentitySso={false}
|
|
169
|
+
googleAuthMode="popup"
|
|
170
|
+
showGoogle
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
const button = container.querySelector<HTMLButtonElement>("#google-btn");
|
|
175
|
+
await act(async () => button?.click());
|
|
176
|
+
expect(open).toHaveBeenCalledTimes(1);
|
|
177
|
+
expect(button?.getAttribute("aria-busy")).toBe("true");
|
|
178
|
+
expect(oauthRequests).not.toHaveBeenCalled();
|
|
179
|
+
await act(async () => accept());
|
|
180
|
+
expect(oauthRequests).toHaveBeenCalledTimes(1);
|
|
181
|
+
expect(
|
|
182
|
+
new URL(oauthUrl ?? "", "https://design.example.com").searchParams.has(
|
|
183
|
+
"builder_signup_attempt",
|
|
184
|
+
),
|
|
185
|
+
).toBe(acceptStatus === 200);
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
|
|
46
189
|
it("opens and closes the auth options without dropping focus from the localized toggle", async () => {
|
|
47
190
|
await act(async () => {
|
|
48
191
|
root.render(
|