@agent-native/toolkit 0.225.1 → 0.226.0-nightly-20261106182619
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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useComposer, useComposerRuntime } from "@assistant-ui/react";
|
|
2
2
|
import {
|
|
3
3
|
IconCheck,
|
|
4
|
+
IconSearch,
|
|
4
5
|
IconChevronDown,
|
|
5
6
|
IconBulb,
|
|
6
7
|
IconClock,
|
|
@@ -113,7 +114,11 @@ import type {
|
|
|
113
114
|
ComposerMode,
|
|
114
115
|
AgentComposerLayoutVariant,
|
|
115
116
|
} from "./types.js";
|
|
116
|
-
import {
|
|
117
|
+
import {
|
|
118
|
+
useMentionSearch,
|
|
119
|
+
loadWorkspaceMentionItems,
|
|
120
|
+
MentionSearchError,
|
|
121
|
+
} from "./use-mention-search.js";
|
|
117
122
|
import { useSkills } from "./use-skills.js";
|
|
118
123
|
import {
|
|
119
124
|
RealtimeVoiceModeBoundary,
|
|
@@ -3229,7 +3234,7 @@ function TiptapComposerSurface({
|
|
|
3229
3234
|
settledQuery: settledMentionQuery,
|
|
3230
3235
|
} = useMentionSearch(
|
|
3231
3236
|
popover?.type === "@" ? popover.query : "",
|
|
3232
|
-
includeDefaultMentionSearch &&
|
|
3237
|
+
includeDefaultMentionSearch && popover?.type === "@",
|
|
3233
3238
|
);
|
|
3234
3239
|
const mentionQuery = popover?.type === "@" ? popover.query : "";
|
|
3235
3240
|
const filteredMentionItems = useMemo(
|
|
@@ -6455,51 +6460,140 @@ function TiptapComposerSurface({
|
|
|
6455
6460
|
const launchersDisabled = disabled || contextControlsDisabled;
|
|
6456
6461
|
|
|
6457
6462
|
const contextLauncherItems = useMemo(() => {
|
|
6458
|
-
const
|
|
6459
|
-
|
|
6460
|
-
const
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6463
|
+
const excludedTypes = new Set<string>();
|
|
6464
|
+
const visit = (entries: readonly ComposerContextMenuItem[]) => {
|
|
6465
|
+
for (const entry of entries) {
|
|
6466
|
+
if (entry.referenceType) excludedTypes.add(entry.referenceType);
|
|
6467
|
+
if (entry.referencePicker)
|
|
6468
|
+
excludedTypes.add(entry.referencePicker.referenceType);
|
|
6469
|
+
if (entry.children) visit(entry.children);
|
|
6470
|
+
}
|
|
6471
|
+
};
|
|
6472
|
+
visit(contextMenuItems ?? []);
|
|
6473
|
+
const pickerFor = (filter?: { referenceType: string; source: string }) => {
|
|
6474
|
+
const available = new Map<string, MentionItem>();
|
|
6475
|
+
return {
|
|
6476
|
+
identityScope: "workspace" as const,
|
|
6477
|
+
presentation: { type: "dialog" as const, mode: "action" as const },
|
|
6478
|
+
searchPlaceholder: t("agentChat.composer.menu.searchWorkspace", {
|
|
6479
|
+
defaultValue: "Search workspace…",
|
|
6480
|
+
}),
|
|
6481
|
+
load: async ({
|
|
6482
|
+
search,
|
|
6483
|
+
signal,
|
|
6484
|
+
}: {
|
|
6485
|
+
search: string;
|
|
6486
|
+
signal: AbortSignal;
|
|
6487
|
+
}) => {
|
|
6488
|
+
const remote =
|
|
6489
|
+
filter || includeDefaultMentionSearch
|
|
6490
|
+
? await loadWorkspaceMentionItems(
|
|
6491
|
+
search,
|
|
6492
|
+
adapters.resolvePath,
|
|
6493
|
+
signal,
|
|
6494
|
+
filter?.source,
|
|
6495
|
+
).catch((cause: unknown) => {
|
|
6496
|
+
if (signal.aborted) throw cause;
|
|
6497
|
+
if (cause instanceof MentionSearchError) throw cause;
|
|
6498
|
+
throw new Error(
|
|
6499
|
+
t("agentChat.composer.contextLoadFailed", {
|
|
6500
|
+
defaultValue: "Could not load context.",
|
|
6501
|
+
}),
|
|
6502
|
+
{ cause },
|
|
6503
|
+
);
|
|
6504
|
+
})
|
|
6505
|
+
: [];
|
|
6506
|
+
const entries = [
|
|
6507
|
+
...mentionItemsForQuery(hostMentionItems, search),
|
|
6508
|
+
...remote,
|
|
6509
|
+
].filter((item) =>
|
|
6510
|
+
filter
|
|
6511
|
+
? item.refType === filter.referenceType &&
|
|
6512
|
+
(!filter.source || item.source === filter.source)
|
|
6513
|
+
: !excludedTypes.has(item.refType),
|
|
6514
|
+
);
|
|
6515
|
+
if (signal.aborted)
|
|
6516
|
+
throw new DOMException("The operation was aborted.", "AbortError");
|
|
6517
|
+
available.clear();
|
|
6518
|
+
for (const item of entries) available.set(item.id, item);
|
|
6519
|
+
return {
|
|
6520
|
+
items: Array.from(available.values()).map((item) => ({
|
|
6521
|
+
id: item.id,
|
|
6522
|
+
title: item.label,
|
|
6523
|
+
...(item.description === undefined
|
|
6524
|
+
? {}
|
|
6525
|
+
: { description: item.description }),
|
|
6526
|
+
...(item.section === undefined
|
|
6527
|
+
? {}
|
|
6528
|
+
: {
|
|
6529
|
+
group:
|
|
6530
|
+
item.section === "Files"
|
|
6531
|
+
? "agentChat.mentions.sections.files"
|
|
6532
|
+
: item.section === "Agents"
|
|
6533
|
+
? "agentChat.mentions.sections.agents"
|
|
6534
|
+
: item.section === "Connected Agents"
|
|
6535
|
+
? "agentChat.mentions.sections.connectedAgents"
|
|
6536
|
+
: item.section,
|
|
6537
|
+
}),
|
|
6538
|
+
icon: (
|
|
6539
|
+
<MentionItemMedia
|
|
6540
|
+
{...(item.icon === undefined ? {} : { icon: item.icon })}
|
|
6541
|
+
{...(item.media === undefined ? {} : { media: item.media })}
|
|
6542
|
+
size="sm"
|
|
6543
|
+
/>
|
|
6544
|
+
),
|
|
6545
|
+
})),
|
|
6546
|
+
};
|
|
6547
|
+
},
|
|
6548
|
+
onSelect: (item: { id: string }) => {
|
|
6549
|
+
const reference = available.get(item.id);
|
|
6550
|
+
if (!reference)
|
|
6551
|
+
throw new Error(
|
|
6552
|
+
t("agentChat.composer.contextLoadFailed", {
|
|
6553
|
+
defaultValue: "Could not load context.",
|
|
6554
|
+
}),
|
|
6555
|
+
);
|
|
6556
|
+
insertReference(composerReferenceFromMentionItem(reference));
|
|
6557
|
+
},
|
|
6558
|
+
};
|
|
6475
6559
|
};
|
|
6560
|
+
const resolveEntries = (
|
|
6561
|
+
entries: readonly ComposerContextMenuItem[],
|
|
6562
|
+
): ComposerContextMenuItem[] =>
|
|
6563
|
+
entries.map((entry) => {
|
|
6564
|
+
if (entry.referencePicker) {
|
|
6565
|
+
const { referencePicker, ...rest } = entry;
|
|
6566
|
+
return { ...rest, picker: pickerFor(referencePicker) };
|
|
6567
|
+
}
|
|
6568
|
+
return entry.children
|
|
6569
|
+
? { ...entry, children: resolveEntries(entry.children) }
|
|
6570
|
+
: entry;
|
|
6571
|
+
});
|
|
6476
6572
|
return [
|
|
6477
|
-
...(contextMenuItems ?? []),
|
|
6478
|
-
...
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
:
|
|
6484
|
-
|
|
6485
|
-
:
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
.map(([section, children]) => ({
|
|
6493
|
-
id: `mentions:${section}`,
|
|
6494
|
-
label: sectionKeys[section]
|
|
6495
|
-
? t(`agentChat.mentions.sections.${sectionKeys[section]}`, {
|
|
6496
|
-
defaultValue: section,
|
|
6497
|
-
})
|
|
6498
|
-
: section,
|
|
6499
|
-
children,
|
|
6500
|
-
})),
|
|
6573
|
+
...resolveEntries(contextMenuItems ?? []),
|
|
6574
|
+
...(includeDefaultMentionSearch || hostMentionItems.length
|
|
6575
|
+
? [
|
|
6576
|
+
{
|
|
6577
|
+
id: "composer-workspace-search",
|
|
6578
|
+
section: "context" as const,
|
|
6579
|
+
order: 90,
|
|
6580
|
+
label: t("agentChat.composer.menu.searchWorkspace", {
|
|
6581
|
+
defaultValue: "Search workspace…",
|
|
6582
|
+
}),
|
|
6583
|
+
icon: <IconSearch />,
|
|
6584
|
+
picker: pickerFor(),
|
|
6585
|
+
},
|
|
6586
|
+
]
|
|
6587
|
+
: []),
|
|
6501
6588
|
];
|
|
6502
|
-
}, [
|
|
6589
|
+
}, [
|
|
6590
|
+
contextMenuItems,
|
|
6591
|
+
hostMentionItems,
|
|
6592
|
+
includeDefaultMentionSearch,
|
|
6593
|
+
adapters.resolvePath,
|
|
6594
|
+
insertReference,
|
|
6595
|
+
t,
|
|
6596
|
+
]);
|
|
6503
6597
|
|
|
6504
6598
|
const sendButton = (
|
|
6505
6599
|
<ComposerSendButton
|
|
@@ -6786,15 +6880,7 @@ function TiptapComposerSurface({
|
|
|
6786
6880
|
attachmentsEnabled={attachmentsEnabled}
|
|
6787
6881
|
open={contextMenuOpen}
|
|
6788
6882
|
onOpenChange={setContextMenuOpen}
|
|
6789
|
-
|
|
6790
|
-
error={
|
|
6791
|
-
mentionsError
|
|
6792
|
-
? t("agentChat.composer.contextLoadFailed", {
|
|
6793
|
-
defaultValue: "Could not load context.",
|
|
6794
|
-
})
|
|
6795
|
-
: undefined
|
|
6796
|
-
}
|
|
6797
|
-
onRetry={retryMentions}
|
|
6883
|
+
onSelectIntegration={insertReference}
|
|
6798
6884
|
onRestoreFocus={
|
|
6799
6885
|
launchersDisabled
|
|
6800
6886
|
? undefined
|
|
@@ -6909,8 +6995,15 @@ function TiptapComposerSurface({
|
|
|
6909
6995
|
commands={filteredCommands}
|
|
6910
6996
|
hint={hint}
|
|
6911
6997
|
isLoading={popover?.type === "@" ? mentionsLoading : skillsLoading}
|
|
6912
|
-
error={
|
|
6913
|
-
|
|
6998
|
+
error={
|
|
6999
|
+
mentionItemsError ??
|
|
7000
|
+
(mentionsError
|
|
7001
|
+
? t("agentChat.composer.contextLoadFailed", {
|
|
7002
|
+
defaultValue: "Could not load context.",
|
|
7003
|
+
})
|
|
7004
|
+
: undefined)
|
|
7005
|
+
}
|
|
7006
|
+
onRetry={onRetryMentionItems ?? retryMentions}
|
|
6914
7007
|
query={popover?.query ?? ""}
|
|
6915
7008
|
onSelectMention={handleSelectMention}
|
|
6916
7009
|
onSelectSkill={handleSelectSkill}
|
package/src/composer/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ReactNode,
|
|
9
9
|
} from "react";
|
|
10
10
|
|
|
11
|
+
import type { ComposerContextMenuItem } from "./ComposerContextMenu.js";
|
|
11
12
|
import type { MentionItemMedia } from "./types.js";
|
|
12
13
|
|
|
13
14
|
const referenceEventTargets = new WeakMap<Event, HTMLElement>();
|
|
@@ -188,6 +189,20 @@ export interface VoiceContextPack {
|
|
|
188
189
|
terms?: Array<{ term: string; replacement?: string }>;
|
|
189
190
|
}
|
|
190
191
|
|
|
192
|
+
export interface WorkspaceComposerMenuOptions {
|
|
193
|
+
enabled: boolean;
|
|
194
|
+
onRestoreFocus?: () => void;
|
|
195
|
+
onSelectIntegration: (reference: AgentComposerReference) => void;
|
|
196
|
+
}
|
|
197
|
+
export interface WorkspaceComposerMenuContribution {
|
|
198
|
+
scopeKey?: string;
|
|
199
|
+
items: readonly ComposerContextMenuItem[];
|
|
200
|
+
loading?: boolean;
|
|
201
|
+
error?: string;
|
|
202
|
+
onRetry?: () => void | Promise<unknown>;
|
|
203
|
+
overlays?: ReactNode;
|
|
204
|
+
}
|
|
205
|
+
|
|
191
206
|
export interface ComposerRuntimeAdapters {
|
|
192
207
|
resolvePath?: (path: string) => string;
|
|
193
208
|
translate?: ComposerTranslate;
|
|
@@ -242,6 +257,9 @@ export interface ComposerRuntimeAdapters {
|
|
|
242
257
|
isTrustedBuilderMessage?: (event: MessageEvent) => boolean;
|
|
243
258
|
};
|
|
244
259
|
resources?: {
|
|
260
|
+
useWorkspaceMenu?: (
|
|
261
|
+
options: WorkspaceComposerMenuOptions,
|
|
262
|
+
) => WorkspaceComposerMenuContribution;
|
|
245
263
|
useOrg?: () => {
|
|
246
264
|
data?: { orgId?: string | null; role?: string | null } | null;
|
|
247
265
|
};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { useLayoutEffect, useState, type RefObject } from "react";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const MIN_USABLE_PANEL_HEIGHT = 120;
|
|
5
|
-
const MAX_COMPOSER_PANEL_HEIGHT = 280;
|
|
3
|
+
const MAX_COMPOSER_PANEL_HEIGHT = 480;
|
|
6
4
|
|
|
7
5
|
export function useComposerPanelPlacement(
|
|
8
6
|
triggerRef: RefObject<HTMLElement | null>,
|
|
@@ -32,7 +30,7 @@ export function useComposerPanelPlacement(
|
|
|
32
30
|
const spaceAbove = Math.max(0, button.top - viewportTop - 24);
|
|
33
31
|
const spaceBelow = Math.max(0, viewportBottom - button.bottom - 24);
|
|
34
32
|
const side =
|
|
35
|
-
spaceAbove <
|
|
33
|
+
spaceAbove < MAX_COMPOSER_PANEL_HEIGHT && spaceBelow > spaceAbove
|
|
36
34
|
? "bottom"
|
|
37
35
|
: "top";
|
|
38
36
|
setPlacement({
|
|
@@ -4,7 +4,10 @@ import { act } from "react";
|
|
|
4
4
|
import { createRoot } from "react-dom/client";
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
useMentionSearch,
|
|
9
|
+
loadWorkspaceMentionItems,
|
|
10
|
+
} from "./use-mention-search.js";
|
|
8
11
|
|
|
9
12
|
const resolvePath = (path: string) => `/design${path}`;
|
|
10
13
|
const file = {
|
|
@@ -45,6 +48,68 @@ afterEach(async () => {
|
|
|
45
48
|
vi.unstubAllGlobals();
|
|
46
49
|
});
|
|
47
50
|
describe("shared launcher mention discovery", () => {
|
|
51
|
+
it("surfaces localized structured source failures and rejects malformed HTTP errors", async () => {
|
|
52
|
+
vi.stubGlobal(
|
|
53
|
+
"fetch",
|
|
54
|
+
vi
|
|
55
|
+
.fn()
|
|
56
|
+
.mockResolvedValueOnce(
|
|
57
|
+
new Response(
|
|
58
|
+
JSON.stringify({
|
|
59
|
+
error: {
|
|
60
|
+
code: "mention_source_unavailable",
|
|
61
|
+
message: "Impossible de charger les références.",
|
|
62
|
+
source: "dashboards",
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
{ status: 503 },
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
.mockResolvedValueOnce(new Response("not json", { status: 503 })),
|
|
69
|
+
);
|
|
70
|
+
await expect(
|
|
71
|
+
loadWorkspaceMentionItems(
|
|
72
|
+
"",
|
|
73
|
+
resolvePath,
|
|
74
|
+
new AbortController().signal,
|
|
75
|
+
"dashboards",
|
|
76
|
+
),
|
|
77
|
+
).rejects.toMatchObject({
|
|
78
|
+
message: "Impossible de charger les références.",
|
|
79
|
+
code: "mention_source_unavailable",
|
|
80
|
+
source: "dashboards",
|
|
81
|
+
});
|
|
82
|
+
await expect(
|
|
83
|
+
loadWorkspaceMentionItems(
|
|
84
|
+
"",
|
|
85
|
+
resolvePath,
|
|
86
|
+
new AbortController().signal,
|
|
87
|
+
"dashboards",
|
|
88
|
+
),
|
|
89
|
+
).rejects.toThrow("Mention search unavailable");
|
|
90
|
+
});
|
|
91
|
+
it("rejects a failed source after earlier batches instead of returning a partial workspace result", async () => {
|
|
92
|
+
vi.stubGlobal(
|
|
93
|
+
"fetch",
|
|
94
|
+
vi.fn(
|
|
95
|
+
async () =>
|
|
96
|
+
new Response(
|
|
97
|
+
JSON.stringify({ items: [file] }) +
|
|
98
|
+
"\n" +
|
|
99
|
+
JSON.stringify({
|
|
100
|
+
error: {
|
|
101
|
+
code: "mention_source_unavailable",
|
|
102
|
+
message: "Could not load workspace references.",
|
|
103
|
+
source: "dashboards",
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
),
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
await expect(
|
|
110
|
+
loadWorkspaceMentionItems("", resolvePath, new AbortController().signal),
|
|
111
|
+
).rejects.toThrow("Could not load workspace references.");
|
|
112
|
+
});
|
|
48
113
|
it("does not load until the launcher opens", async () => {
|
|
49
114
|
const fetch = vi.fn();
|
|
50
115
|
vi.stubGlobal("fetch", fetch);
|
|
@@ -66,6 +131,39 @@ describe("shared launcher mention discovery", () => {
|
|
|
66
131
|
expect(result.error).toBeNull();
|
|
67
132
|
expect(result.isLoading).toBe(false);
|
|
68
133
|
});
|
|
134
|
+
it.each([false, true])(
|
|
135
|
+
"keeps healthy streamed batches around a source error (earlier batch: %s)",
|
|
136
|
+
async (earlier) => {
|
|
137
|
+
const second = { ...file, id: "file:two", label: "Notes" };
|
|
138
|
+
const lines = [
|
|
139
|
+
...(earlier ? [{ items: [file] }] : []),
|
|
140
|
+
{ error: { message: "Source unavailable" } },
|
|
141
|
+
{ items: [second] },
|
|
142
|
+
];
|
|
143
|
+
vi.stubGlobal(
|
|
144
|
+
"fetch",
|
|
145
|
+
vi.fn(
|
|
146
|
+
async () =>
|
|
147
|
+
new Response(
|
|
148
|
+
new ReadableStream({
|
|
149
|
+
start(controller) {
|
|
150
|
+
for (const line of lines)
|
|
151
|
+
controller.enqueue(
|
|
152
|
+
new TextEncoder().encode(JSON.stringify(line) + "\n"),
|
|
153
|
+
);
|
|
154
|
+
controller.close();
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
),
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
await render();
|
|
161
|
+
expect(result.items).toEqual(earlier ? [file, second] : [second]);
|
|
162
|
+
expect(result.error?.message).toBe("Source unavailable");
|
|
163
|
+
expect(result.settledQuery).toBeNull();
|
|
164
|
+
expect(result.isLoading).toBe(false);
|
|
165
|
+
},
|
|
166
|
+
);
|
|
69
167
|
it.each([
|
|
70
168
|
new Response("{}"),
|
|
71
169
|
new Response("{broken"),
|
|
@@ -3,6 +3,95 @@ import { useState, useEffect, useRef, useCallback } from "react";
|
|
|
3
3
|
import { useComposerRuntimeAdapters } from "./runtime-adapters.js";
|
|
4
4
|
import type { MentionItem } from "./types.js";
|
|
5
5
|
|
|
6
|
+
export class MentionSearchError extends Error {
|
|
7
|
+
constructor(message: string, details: { code?: string; source?: string }) {
|
|
8
|
+
super(message);
|
|
9
|
+
Object.assign(this, details);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function parseMentionBatch(
|
|
14
|
+
line: string,
|
|
15
|
+
): { items: MentionItem[] } | { error: Error } {
|
|
16
|
+
const data: unknown = JSON.parse(line);
|
|
17
|
+
if (
|
|
18
|
+
data &&
|
|
19
|
+
typeof data === "object" &&
|
|
20
|
+
"error" in data &&
|
|
21
|
+
data.error &&
|
|
22
|
+
typeof data.error === "object" &&
|
|
23
|
+
"message" in data.error &&
|
|
24
|
+
typeof data.error.message === "string"
|
|
25
|
+
)
|
|
26
|
+
return {
|
|
27
|
+
error: new MentionSearchError(data.error.message, {
|
|
28
|
+
...("code" in data.error && typeof data.error.code === "string"
|
|
29
|
+
? { code: data.error.code }
|
|
30
|
+
: {}),
|
|
31
|
+
...("source" in data.error && typeof data.error.source === "string"
|
|
32
|
+
? { source: data.error.source }
|
|
33
|
+
: {}),
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
if (
|
|
37
|
+
!data ||
|
|
38
|
+
typeof data !== "object" ||
|
|
39
|
+
!("items" in data) ||
|
|
40
|
+
!Array.isArray(data.items) ||
|
|
41
|
+
!data.items.every(
|
|
42
|
+
(item: unknown) =>
|
|
43
|
+
item &&
|
|
44
|
+
typeof item === "object" &&
|
|
45
|
+
"id" in item &&
|
|
46
|
+
typeof item.id === "string" &&
|
|
47
|
+
"label" in item &&
|
|
48
|
+
typeof item.label === "string" &&
|
|
49
|
+
"refType" in item &&
|
|
50
|
+
typeof item.refType === "string",
|
|
51
|
+
)
|
|
52
|
+
) {
|
|
53
|
+
throw new Error("Invalid mention search response");
|
|
54
|
+
}
|
|
55
|
+
return { items: data.items as MentionItem[] };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function mentionResponseError(response: Response): Promise<Error> {
|
|
59
|
+
const fallback = new Error("Mention search unavailable");
|
|
60
|
+
const body = await response.text();
|
|
61
|
+
try {
|
|
62
|
+
const batch = parseMentionBatch(body);
|
|
63
|
+
return "error" in batch ? batch.error : fallback;
|
|
64
|
+
} catch {
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export async function loadWorkspaceMentionItems(
|
|
69
|
+
query: string,
|
|
70
|
+
resolvePath: (path: string) => string,
|
|
71
|
+
signal: AbortSignal,
|
|
72
|
+
source?: string,
|
|
73
|
+
): Promise<MentionItem[]> {
|
|
74
|
+
const response = await fetch(
|
|
75
|
+
resolvePath(
|
|
76
|
+
`/_agent-native/agent-chat/mentions?q=${encodeURIComponent(query)}${source ? `&source=${encodeURIComponent(source)}` : ""}`,
|
|
77
|
+
),
|
|
78
|
+
{ signal },
|
|
79
|
+
);
|
|
80
|
+
if (!response.ok) throw await mentionResponseError(response);
|
|
81
|
+
if (!response.body) throw new Error("Mention search unavailable");
|
|
82
|
+
const body = await response.text();
|
|
83
|
+
if (signal.aborted)
|
|
84
|
+
throw new DOMException("The operation was aborted.", "AbortError");
|
|
85
|
+
return body
|
|
86
|
+
.split("\n")
|
|
87
|
+
.filter((line) => line.trim())
|
|
88
|
+
.flatMap((line) => {
|
|
89
|
+
const batch = parseMentionBatch(line);
|
|
90
|
+
if ("error" in batch) throw batch.error;
|
|
91
|
+
return batch.items;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
6
95
|
export function useMentionSearch(
|
|
7
96
|
query: string,
|
|
8
97
|
enabled: boolean,
|
|
@@ -47,40 +136,26 @@ export function useMentionSearch(
|
|
|
47
136
|
),
|
|
48
137
|
{ signal: abort.signal },
|
|
49
138
|
);
|
|
50
|
-
if (!res.ok
|
|
139
|
+
if (!res.ok) throw await mentionResponseError(res);
|
|
140
|
+
if (!res.body) throw new Error("Mention search unavailable");
|
|
51
141
|
|
|
52
142
|
const reader = res.body.getReader();
|
|
53
143
|
const decoder = new TextDecoder();
|
|
54
144
|
let buf = "";
|
|
145
|
+
const sourceError: { current: Error | null } = { current: null };
|
|
55
146
|
const receive = (line: string) => {
|
|
56
147
|
if (!line.trim()) return;
|
|
57
|
-
const
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
!("items" in data) ||
|
|
62
|
-
!Array.isArray(data.items) ||
|
|
63
|
-
!data.items.every(
|
|
64
|
-
(item: unknown) =>
|
|
65
|
-
item &&
|
|
66
|
-
typeof item === "object" &&
|
|
67
|
-
"id" in item &&
|
|
68
|
-
typeof item.id === "string" &&
|
|
69
|
-
"label" in item &&
|
|
70
|
-
typeof item.label === "string" &&
|
|
71
|
-
"refType" in item &&
|
|
72
|
-
typeof item.refType === "string",
|
|
73
|
-
)
|
|
74
|
-
) {
|
|
75
|
-
throw new Error("Invalid mention search response");
|
|
148
|
+
const batch = parseMentionBatch(line);
|
|
149
|
+
if ("error" in batch) {
|
|
150
|
+
sourceError.current ??= batch.error;
|
|
151
|
+
return;
|
|
76
152
|
}
|
|
77
|
-
const batch = data.items as MentionItem[];
|
|
78
153
|
if (id !== requestIdRef.current || abort.signal.aborted) return;
|
|
79
154
|
setItems((previous) => {
|
|
80
155
|
const seen = new Set(previous.map((item) => item.id));
|
|
81
156
|
return [
|
|
82
157
|
...previous,
|
|
83
|
-
...batch.filter((item) => {
|
|
158
|
+
...batch.items.filter((item) => {
|
|
84
159
|
if (seen.has(item.id)) return false;
|
|
85
160
|
seen.add(item.id);
|
|
86
161
|
return true;
|
|
@@ -106,8 +181,10 @@ export function useMentionSearch(
|
|
|
106
181
|
}
|
|
107
182
|
// Only a completed search settles its query: a failed one is unknown,
|
|
108
183
|
// not empty, and must not read as "nothing matches".
|
|
109
|
-
if (id === requestIdRef.current && !abort.signal.aborted)
|
|
110
|
-
|
|
184
|
+
if (id === requestIdRef.current && !abort.signal.aborted) {
|
|
185
|
+
if (sourceError.current) setError(sourceError.current);
|
|
186
|
+
else setSettledQuery(query);
|
|
187
|
+
}
|
|
111
188
|
} catch (err: unknown) {
|
|
112
189
|
if (abort.signal.aborted) return;
|
|
113
190
|
if (id === requestIdRef.current)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useEffect,
|
|
3
|
+
useCallback,
|
|
3
4
|
useRef,
|
|
4
5
|
useState,
|
|
5
6
|
type ReactNode,
|
|
@@ -13,6 +14,8 @@ export interface ComposerContextPickerItem {
|
|
|
13
14
|
id: string;
|
|
14
15
|
title: string;
|
|
15
16
|
description?: string | null;
|
|
17
|
+
group?: string;
|
|
18
|
+
icon?: ReactNode;
|
|
16
19
|
preview?: ReactNode;
|
|
17
20
|
metadata?: ReactNode;
|
|
18
21
|
disabled?: boolean;
|
|
@@ -45,8 +48,10 @@ export type ComposerContextPickerFooterAction = {
|
|
|
45
48
|
export type ComposerContextPickerConfig = {
|
|
46
49
|
footerAction?: ComposerContextPickerFooterAction;
|
|
47
50
|
searchPlaceholder: string;
|
|
51
|
+
searchable?: boolean;
|
|
48
52
|
selectedIds?: readonly string[];
|
|
49
53
|
scopeKey?: string;
|
|
54
|
+
identityScope?: "workspace";
|
|
50
55
|
refreshKey?: string | number;
|
|
51
56
|
link?: {
|
|
52
57
|
label?: string;
|
|
@@ -84,7 +89,7 @@ export type ComposerContextPickerConfig = {
|
|
|
84
89
|
| { type: "dialog"; mode: "url" }
|
|
85
90
|
| {
|
|
86
91
|
type: "dialog";
|
|
87
|
-
mode: "single";
|
|
92
|
+
mode: "single" | "action";
|
|
88
93
|
layout?: "list" | "gallery";
|
|
89
94
|
};
|
|
90
95
|
onSelect: (
|
|
@@ -148,9 +153,17 @@ export function useComposerContextPicker({
|
|
|
148
153
|
const [chosen, setChosen] = useState<
|
|
149
154
|
ReadonlyMap<string, ComposerContextPickerItem>
|
|
150
155
|
>(() => new Map());
|
|
151
|
-
const
|
|
152
|
-
|
|
156
|
+
const linkMode = Boolean(config.link || presentation?.mode === "url");
|
|
157
|
+
const [previousLinkMode, setPreviousLinkMode] = useState(linkMode);
|
|
158
|
+
const [currentStage, setStage] = useState<"link" | "results">(
|
|
159
|
+
linkMode ? "link" : "results",
|
|
153
160
|
);
|
|
161
|
+
const stage =
|
|
162
|
+
previousLinkMode === linkMode
|
|
163
|
+
? currentStage
|
|
164
|
+
: linkMode
|
|
165
|
+
? "link"
|
|
166
|
+
: "results";
|
|
154
167
|
const [link, setLink] = useState("");
|
|
155
168
|
const [location, setLocation] = useState<PickerLocation>({
|
|
156
169
|
search: "",
|
|
@@ -174,13 +187,13 @@ export function useComposerContextPicker({
|
|
|
174
187
|
const keyRef = useRef(key);
|
|
175
188
|
keyRef.current = key;
|
|
176
189
|
|
|
177
|
-
const invalidateSelection = () => {
|
|
190
|
+
const invalidateSelection = useCallback(() => {
|
|
178
191
|
epoch.current++;
|
|
179
192
|
pendingSelection.current?.abort();
|
|
180
193
|
pendingSelection.current = null;
|
|
181
194
|
setSelecting(false);
|
|
182
195
|
setActionError(undefined);
|
|
183
|
-
};
|
|
196
|
+
}, []);
|
|
184
197
|
useEffect(
|
|
185
198
|
() => () => {
|
|
186
199
|
epoch.current++;
|
|
@@ -189,6 +202,13 @@ export function useComposerContextPicker({
|
|
|
189
202
|
[],
|
|
190
203
|
);
|
|
191
204
|
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (previousLinkMode === linkMode) return;
|
|
207
|
+
invalidateSelection();
|
|
208
|
+
setPreviousLinkMode(linkMode);
|
|
209
|
+
setStage(linkMode ? "link" : "results");
|
|
210
|
+
}, [previousLinkMode, linkMode, invalidateSelection]);
|
|
211
|
+
|
|
192
212
|
useEffect(() => {
|
|
193
213
|
if (!remote || stage !== "results") return;
|
|
194
214
|
const abort = new AbortController();
|