@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
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { describeModelForEngine } from "@agent-native/core/agent/model-catalog";
|
|
2
|
+
import {
|
|
3
|
+
getAgentNativeAgentKitUploadDriver,
|
|
4
|
+
clearAgentNativeAgentKitUploads,
|
|
5
|
+
} from "@agent-native/core/client/agentkit-chat/transport";
|
|
2
6
|
import { callAction } from "@agent-native/core/client/hooks";
|
|
3
7
|
import { useT } from "@agent-native/core/client/i18n";
|
|
4
|
-
import {
|
|
8
|
+
import { readFileUploadStatus } from "@agent-native/core/client/uploads";
|
|
9
|
+
import { useSession } from "@agent-native/core/client/use-session";
|
|
10
|
+
import { useMemo, useEffect } from "react";
|
|
5
11
|
|
|
12
|
+
import { CoreFileStorageSetupContext } from "../../agentkit/react/core-file-storage.js";
|
|
6
13
|
import { AgentKitRoot } from "../../agentkit/react/root.js";
|
|
7
14
|
import type { AgentKitRootProps } from "../../agentkit/react/root.js";
|
|
15
|
+
import { FileStorageSetupPopover } from "../FileStorageSetupPopover.js";
|
|
8
16
|
import { AgentKitActionWidget } from "./action-widget.js";
|
|
9
17
|
import { CoreAgentKitApproval } from "./approval-card.js";
|
|
10
18
|
import { agentKitWorkStepLabels } from "./work-step-labels.js";
|
|
@@ -31,6 +39,76 @@ function coreModelDisplayName(model: string): string {
|
|
|
31
39
|
|
|
32
40
|
export function CoreAgentKitRoot(props: AgentKitRootProps) {
|
|
33
41
|
const t = useT();
|
|
42
|
+
const nativeUpload = props.transport
|
|
43
|
+
? getAgentNativeAgentKitUploadDriver(props.transport)
|
|
44
|
+
: undefined;
|
|
45
|
+
const { session } = useSession();
|
|
46
|
+
const scopeKey = JSON.stringify([
|
|
47
|
+
session?.email ?? null,
|
|
48
|
+
session?.orgId ?? null,
|
|
49
|
+
session?.authUserId ?? null,
|
|
50
|
+
]);
|
|
51
|
+
const storageScope = useMemo(
|
|
52
|
+
() =>
|
|
53
|
+
nativeUpload
|
|
54
|
+
? {
|
|
55
|
+
scopeKey,
|
|
56
|
+
readStatus: readFileUploadStatus,
|
|
57
|
+
Setup: FileStorageSetupPopover,
|
|
58
|
+
}
|
|
59
|
+
: null,
|
|
60
|
+
[nativeUpload, scopeKey],
|
|
61
|
+
);
|
|
62
|
+
useEffect(
|
|
63
|
+
() => () => {
|
|
64
|
+
if (props.transport) clearAgentNativeAgentKitUploads(props.transport);
|
|
65
|
+
},
|
|
66
|
+
[props.transport, scopeKey],
|
|
67
|
+
);
|
|
68
|
+
const upload = useMemo(
|
|
69
|
+
() =>
|
|
70
|
+
nativeUpload
|
|
71
|
+
? async (...args: Parameters<typeof nativeUpload>) => {
|
|
72
|
+
try {
|
|
73
|
+
await nativeUpload(...args);
|
|
74
|
+
} catch (cause) {
|
|
75
|
+
const code =
|
|
76
|
+
cause && typeof cause === "object" && "code" in cause
|
|
77
|
+
? cause.code
|
|
78
|
+
: undefined;
|
|
79
|
+
const key =
|
|
80
|
+
code === "upload_too_large"
|
|
81
|
+
? "agentChat.composer.fileTooLarge"
|
|
82
|
+
: code === "upload_session_expired"
|
|
83
|
+
? "agentChat.composer.sessionExpired"
|
|
84
|
+
: code === "upload_unsupported_type"
|
|
85
|
+
? "agentChat.composer.unsupportedFileType"
|
|
86
|
+
: code === "upload_network_error"
|
|
87
|
+
? "agentChat.composer.uploadOffline"
|
|
88
|
+
: code === "upload_unavailable"
|
|
89
|
+
? "agentChat.composer.uploadUnavailable"
|
|
90
|
+
: "agentChat.composer.uploadFailed";
|
|
91
|
+
throw Object.assign(new Error(t(key, { size: 25 }), { cause }), {
|
|
92
|
+
code,
|
|
93
|
+
...(cause && typeof cause === "object" && "status" in cause
|
|
94
|
+
? { status: cause.status }
|
|
95
|
+
: {}),
|
|
96
|
+
...(cause && typeof cause === "object" && "retryable" in cause
|
|
97
|
+
? { retryable: cause.retryable }
|
|
98
|
+
: {}),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
: undefined,
|
|
103
|
+
[nativeUpload, t],
|
|
104
|
+
);
|
|
105
|
+
const clientOptions = useMemo(
|
|
106
|
+
() => ({
|
|
107
|
+
...props.clientOptions,
|
|
108
|
+
...(!props.clientOptions?.upload && upload ? { upload } : {}),
|
|
109
|
+
}),
|
|
110
|
+
[props.clientOptions, upload],
|
|
111
|
+
);
|
|
34
112
|
const slots = useMemo(
|
|
35
113
|
() => ({
|
|
36
114
|
...props.slots,
|
|
@@ -109,13 +187,18 @@ export function CoreAgentKitRoot(props: AgentKitRootProps) {
|
|
|
109
187
|
}),
|
|
110
188
|
[props.labels, t],
|
|
111
189
|
);
|
|
190
|
+
const rootProps: AgentKitRootProps = props.controller
|
|
191
|
+
? props
|
|
192
|
+
: { ...props, clientOptions };
|
|
112
193
|
return (
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
194
|
+
<CoreFileStorageSetupContext.Provider value={storageScope}>
|
|
195
|
+
<AgentKitRoot
|
|
196
|
+
{...rootProps}
|
|
197
|
+
slots={slots}
|
|
198
|
+
labels={labels}
|
|
199
|
+
loadRunUsage={props.loadRunUsage ?? loadCoreRunUsage}
|
|
200
|
+
formatModelName={props.formatModelName ?? coreModelDisplayName}
|
|
201
|
+
/>
|
|
202
|
+
</CoreFileStorageSetupContext.Provider>
|
|
120
203
|
);
|
|
121
204
|
}
|
|
@@ -51,6 +51,7 @@ export interface AssistantChatSendOptions {
|
|
|
51
51
|
trackInRunsTray?: boolean;
|
|
52
52
|
requestMode?: AgentRequestMode;
|
|
53
53
|
attachments?: AgentChatAttachment[];
|
|
54
|
+
references?: AgentChatMessage["references"];
|
|
54
55
|
/** Correlates with `AGENT_CHAT_SUBMIT_RESULT_EVENT` — see agent-chat.ts. */
|
|
55
56
|
submitMessageId?: string;
|
|
56
57
|
/** Drop a queued submit instead of handing it to chat after this time. */
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
import { BuilderSetupCard, BuilderSetupContent } from "../chat/run-recovery.js";
|
|
51
51
|
import { AssistantUiStaleIndexErrorBoundary } from "./assistant-ui-recovery.js";
|
|
52
52
|
import { coreComposerModelAdapters } from "./model-runtime-adapters.js";
|
|
53
|
+
import { useWorkspaceComposerMenu } from "./use-workspace-composer-menu.js";
|
|
53
54
|
|
|
54
55
|
const REALTIME_VOICE_REQUEST_SOURCE = "realtime-voice";
|
|
55
56
|
|
|
@@ -102,6 +103,7 @@ export const coreComposerAdapters: CoreComposerRuntimeAdapters = {
|
|
|
102
103
|
isTrustedFrameMessage,
|
|
103
104
|
},
|
|
104
105
|
resources: {
|
|
106
|
+
useWorkspaceMenu: useWorkspaceComposerMenu,
|
|
105
107
|
useOrg,
|
|
106
108
|
isMcpIntegrationAvailable: isMcpIntegrationCatalogAvailable,
|
|
107
109
|
useCreateMcpServer,
|
|
@@ -71,8 +71,9 @@ describe("AgentKit capability lifecycle", () => {
|
|
|
71
71
|
let container: HTMLDivElement;
|
|
72
72
|
let result: ReturnType<typeof useAgentKitCapabilities>;
|
|
73
73
|
let unmounted: boolean;
|
|
74
|
+
let enabled = true;
|
|
74
75
|
function Harness() {
|
|
75
|
-
result = useAgentKitCapabilities();
|
|
76
|
+
result = useAgentKitCapabilities({ enabled });
|
|
76
77
|
return null;
|
|
77
78
|
}
|
|
78
79
|
async function render() {
|
|
@@ -102,6 +103,7 @@ describe("AgentKit capability lifecycle", () => {
|
|
|
102
103
|
document.body.append(container);
|
|
103
104
|
root = createRoot(container);
|
|
104
105
|
unmounted = false;
|
|
106
|
+
enabled = true;
|
|
105
107
|
});
|
|
106
108
|
afterEach(async () => {
|
|
107
109
|
if (!unmounted) await act(async () => root.unmount());
|
|
@@ -110,6 +112,29 @@ describe("AgentKit capability lifecycle", () => {
|
|
|
110
112
|
vi.unstubAllGlobals();
|
|
111
113
|
});
|
|
112
114
|
|
|
115
|
+
it("loads only on demand while preserving identity and cached results when the menu closes", async () => {
|
|
116
|
+
enabled = false;
|
|
117
|
+
await render();
|
|
118
|
+
const identity = result.scopeKey;
|
|
119
|
+
expect(mocks.call).not.toHaveBeenCalled();
|
|
120
|
+
expect(result.integrationsLoading).toBe(false);
|
|
121
|
+
enabled = true;
|
|
122
|
+
await render();
|
|
123
|
+
await settle();
|
|
124
|
+
expect(result.data).toEqual(catalog);
|
|
125
|
+
enabled = false;
|
|
126
|
+
await render();
|
|
127
|
+
expect(result.scopeKey).toBe(identity);
|
|
128
|
+
expect(result.data).toEqual(catalog);
|
|
129
|
+
mocks.call.mockResolvedValueOnce(empty);
|
|
130
|
+
await act(async () => {
|
|
131
|
+
await result.refetchIntegrations();
|
|
132
|
+
});
|
|
133
|
+
await settle();
|
|
134
|
+
expect(result.scopeKey).toBe(identity);
|
|
135
|
+
expect(result.data).toEqual(empty);
|
|
136
|
+
});
|
|
137
|
+
|
|
113
138
|
it.each(["account", "organization", "app"] as const)(
|
|
114
139
|
"isolates data and cancels stale requests across a change of %s",
|
|
115
140
|
async (change) => {
|
|
@@ -25,16 +25,16 @@ export function agentKitCapabilityQueryKey(
|
|
|
25
25
|
] as const;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function useAgentKitCapabilities() {
|
|
28
|
+
export function useAgentKitCapabilities(options: { enabled?: boolean } = {}) {
|
|
29
29
|
const { session, status, error: sessionError } = useSession();
|
|
30
30
|
const queryClient = useQueryClient();
|
|
31
|
-
const
|
|
31
|
+
const authorized =
|
|
32
32
|
status === "authenticated" &&
|
|
33
33
|
Boolean(session?.email.trim()) &&
|
|
34
34
|
!agentNativeApiDisabledReason();
|
|
35
35
|
const queryKey = agentKitCapabilityQueryKey(
|
|
36
36
|
agentNativePath("/_agent-native/actions/get-agentkit-capabilities"),
|
|
37
|
-
|
|
37
|
+
authorized ? session : null,
|
|
38
38
|
);
|
|
39
39
|
const scopeKey = JSON.stringify(queryKey);
|
|
40
40
|
const scope = useMemo(() => ({ key: scopeKey }), [scopeKey]);
|
|
@@ -55,10 +55,9 @@ export function useAgentKitCapabilities() {
|
|
|
55
55
|
undefined,
|
|
56
56
|
{ method: "GET", signal },
|
|
57
57
|
),
|
|
58
|
-
enabled,
|
|
58
|
+
enabled: authorized && options.enabled !== false,
|
|
59
59
|
staleTime: 15_000,
|
|
60
60
|
gcTime: 0,
|
|
61
|
-
placeholderData: undefined,
|
|
62
61
|
refetchOnMount: "always",
|
|
63
62
|
refetchOnWindowFocus: "always",
|
|
64
63
|
retry: defaultActionQueryRetry,
|
|
@@ -66,7 +65,7 @@ export function useAgentKitCapabilities() {
|
|
|
66
65
|
});
|
|
67
66
|
|
|
68
67
|
useEffect(() => {
|
|
69
|
-
if (!
|
|
68
|
+
if (!authorized) return;
|
|
70
69
|
const invalidate = () => {
|
|
71
70
|
void queryClient.invalidateQueries({
|
|
72
71
|
queryKey: ["action", "get-agentkit-capabilities"],
|
|
@@ -86,12 +85,12 @@ export function useAgentKitCapabilities() {
|
|
|
86
85
|
unsubscribe();
|
|
87
86
|
removeCompleteListener();
|
|
88
87
|
};
|
|
89
|
-
}, [
|
|
88
|
+
}, [authorized, queryClient]);
|
|
90
89
|
|
|
91
90
|
const { refetch: refetchCapabilities } = capabilityQuery;
|
|
92
91
|
const refetchIntegrations = useCallback(async () => {
|
|
93
92
|
const assertCurrent = () => {
|
|
94
|
-
if (!
|
|
93
|
+
if (!authorized || activeScope.current !== scope) {
|
|
95
94
|
throw new DOMException("The operation was aborted.", "AbortError");
|
|
96
95
|
}
|
|
97
96
|
};
|
|
@@ -99,14 +98,15 @@ export function useAgentKitCapabilities() {
|
|
|
99
98
|
const result = await refetchCapabilities({ throwOnError: true });
|
|
100
99
|
assertCurrent();
|
|
101
100
|
return result;
|
|
102
|
-
}, [
|
|
101
|
+
}, [authorized, scope, refetchCapabilities]);
|
|
103
102
|
|
|
104
103
|
return {
|
|
105
104
|
...capabilityQuery,
|
|
106
105
|
data:
|
|
107
|
-
|
|
106
|
+
authorized && !capabilityQuery.isError ? capabilityQuery.data : undefined,
|
|
108
107
|
integrationsLoading:
|
|
109
|
-
|
|
108
|
+
options.enabled !== false &&
|
|
109
|
+
(status === "loading" || (authorized && capabilityQuery.isPending)),
|
|
110
110
|
integrationsError: sessionError ?? capabilityQuery.error,
|
|
111
111
|
scopeKey,
|
|
112
112
|
refetchIntegrations,
|
|
@@ -93,7 +93,6 @@ describe("shared AgentKit integration submenu", () => {
|
|
|
93
93
|
container.querySelector('button[aria-label="Add context"]')!,
|
|
94
94
|
"ArrowDown",
|
|
95
95
|
);
|
|
96
|
-
await key(row("Add context"), "ArrowRight");
|
|
97
96
|
expect(row("Integrations").getAttribute("aria-haspopup")).toBe("menu");
|
|
98
97
|
await key(row("Integrations"), "ArrowRight");
|
|
99
98
|
}
|
|
@@ -101,7 +100,7 @@ describe("shared AgentKit integration submenu", () => {
|
|
|
101
100
|
await render();
|
|
102
101
|
await open();
|
|
103
102
|
expect(pathname).toBe("/home");
|
|
104
|
-
expect(document.querySelectorAll('[role="menu"]')).toHaveLength(
|
|
103
|
+
expect(document.querySelectorAll('[role="menu"]')).toHaveLength(2);
|
|
105
104
|
expect(document.body.textContent).toContain(
|
|
106
105
|
"No integrations available for this app.",
|
|
107
106
|
);
|
|
@@ -130,7 +129,6 @@ describe("shared AgentKit integration submenu", () => {
|
|
|
130
129
|
"ArrowDown",
|
|
131
130
|
);
|
|
132
131
|
expect(document.body.textContent).not.toContain("GitHub");
|
|
133
|
-
await key(row("Add context"), "ArrowRight");
|
|
134
132
|
await key(row("Integrations"), "ArrowRight");
|
|
135
133
|
expect(row("GitHub").querySelector("svg")).toBeNull();
|
|
136
134
|
expect(row("Slack")).toBeDefined();
|
|
@@ -174,7 +172,7 @@ describe("shared AgentKit integration submenu", () => {
|
|
|
174
172
|
await render();
|
|
175
173
|
await open();
|
|
176
174
|
await key(row("Connect an integration…"), "Escape");
|
|
177
|
-
expect(document.querySelectorAll('[role="menu"]')).toHaveLength(
|
|
175
|
+
expect(document.querySelectorAll('[role="menu"]')).toHaveLength(1);
|
|
178
176
|
expect(document.activeElement).toBe(row("Integrations"));
|
|
179
177
|
expect(pathname).toBe("/home");
|
|
180
178
|
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { WorkspaceComposerMenuContribution } from "@agent-native/toolkit/composer/runtime-adapters";
|
|
2
|
+
// @vitest-environment happy-dom
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
import englishMessages from "../../i18n/catalogs/en-US.js";
|
|
8
|
+
import { useWorkspaceComposerMenu } from "./use-workspace-composer-menu.js";
|
|
9
|
+
|
|
10
|
+
const mocks = vi.hoisted(() => ({
|
|
11
|
+
capabilities: vi.fn(),
|
|
12
|
+
refetch: vi.fn(),
|
|
13
|
+
select: vi.fn(),
|
|
14
|
+
org: { orgId: "alpha", role: "member" },
|
|
15
|
+
}));
|
|
16
|
+
vi.mock("./use-agentkit-capabilities.js", () => ({
|
|
17
|
+
useAgentKitCapabilities: mocks.capabilities,
|
|
18
|
+
}));
|
|
19
|
+
vi.mock("@agent-native/core/client/i18n", () => ({
|
|
20
|
+
useT: () => (key: string) =>
|
|
21
|
+
(englishMessages as Record<string, string>)[
|
|
22
|
+
key.replace(/^agentChat\./, "")
|
|
23
|
+
],
|
|
24
|
+
}));
|
|
25
|
+
vi.mock("@agent-native/core/client/org", () => ({
|
|
26
|
+
useOrg: () => ({ data: mocks.org }),
|
|
27
|
+
}));
|
|
28
|
+
vi.mock("@agent-native/core/client/resources", () => ({
|
|
29
|
+
isMcpIntegrationCatalogAvailable: () => true,
|
|
30
|
+
useCreateMcpServer: () => ({ mutateAsync: vi.fn() }),
|
|
31
|
+
}));
|
|
32
|
+
vi.mock("../../resources/index.js", () => ({
|
|
33
|
+
McpIntegrationDialogDeferred: ({
|
|
34
|
+
open,
|
|
35
|
+
defaultScope,
|
|
36
|
+
}: {
|
|
37
|
+
open: boolean;
|
|
38
|
+
defaultScope: string;
|
|
39
|
+
}) =>
|
|
40
|
+
open ? (
|
|
41
|
+
<div role="dialog" data-scope={defaultScope}>
|
|
42
|
+
Connect tools
|
|
43
|
+
</div>
|
|
44
|
+
) : null,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
let root: ReturnType<typeof createRoot>;
|
|
48
|
+
let container: HTMLDivElement;
|
|
49
|
+
let contribution: WorkspaceComposerMenuContribution;
|
|
50
|
+
let enabled = false;
|
|
51
|
+
let state: {
|
|
52
|
+
scopeKey: string;
|
|
53
|
+
data:
|
|
54
|
+
| {
|
|
55
|
+
sources: { figma: { available: boolean } };
|
|
56
|
+
integrations: { id: string; label: string; kind: "mcp" }[];
|
|
57
|
+
}
|
|
58
|
+
| undefined;
|
|
59
|
+
integrationsLoading: boolean;
|
|
60
|
+
integrationsError: Error | null;
|
|
61
|
+
refetchIntegrations: typeof mocks.refetch;
|
|
62
|
+
};
|
|
63
|
+
function Harness() {
|
|
64
|
+
contribution = useWorkspaceComposerMenu({
|
|
65
|
+
enabled,
|
|
66
|
+
onSelectIntegration: mocks.select,
|
|
67
|
+
});
|
|
68
|
+
return (
|
|
69
|
+
<>
|
|
70
|
+
{contribution.items.map((item) => (
|
|
71
|
+
<button key={item.id} onClick={() => item.onSelect?.()}>
|
|
72
|
+
{item.label}
|
|
73
|
+
</button>
|
|
74
|
+
))}
|
|
75
|
+
{contribution.error && <span role="alert">{contribution.error}</span>}
|
|
76
|
+
{contribution.overlays}
|
|
77
|
+
</>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
async function render() {
|
|
81
|
+
await act(async () => root.render(<Harness />));
|
|
82
|
+
}
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
85
|
+
mocks.select.mockReset();
|
|
86
|
+
mocks.refetch.mockReset();
|
|
87
|
+
mocks.capabilities.mockReset();
|
|
88
|
+
state = {
|
|
89
|
+
scopeKey: "chat:member:alpha",
|
|
90
|
+
data: {
|
|
91
|
+
sources: { figma: { available: false } },
|
|
92
|
+
integrations: [{ id: "mcp:tools", label: "Tools", kind: "mcp" }],
|
|
93
|
+
},
|
|
94
|
+
integrationsLoading: false,
|
|
95
|
+
integrationsError: null,
|
|
96
|
+
refetchIntegrations: mocks.refetch,
|
|
97
|
+
};
|
|
98
|
+
mocks.capabilities.mockImplementation(() => state);
|
|
99
|
+
mocks.org = { orgId: "alpha", role: "member" };
|
|
100
|
+
enabled = false;
|
|
101
|
+
container = document.createElement("div");
|
|
102
|
+
document.body.append(container);
|
|
103
|
+
root = createRoot(container);
|
|
104
|
+
});
|
|
105
|
+
afterEach(async () => {
|
|
106
|
+
await act(async () => root.unmount());
|
|
107
|
+
container.remove();
|
|
108
|
+
vi.unstubAllGlobals();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("workspace composer contributions", () => {
|
|
112
|
+
it("stages real integration references and offers the shared setup primitive separately", async () => {
|
|
113
|
+
await render();
|
|
114
|
+
expect(mocks.capabilities).toHaveBeenLastCalledWith({ enabled: false });
|
|
115
|
+
enabled = true;
|
|
116
|
+
await render();
|
|
117
|
+
expect(mocks.capabilities).toHaveBeenLastCalledWith({ enabled: true });
|
|
118
|
+
await act(async () => container.querySelector("button")!.click());
|
|
119
|
+
expect(mocks.select).toHaveBeenCalledWith({
|
|
120
|
+
label: "Tools",
|
|
121
|
+
refType: "integration",
|
|
122
|
+
refId: "mcp:tools",
|
|
123
|
+
refPath: "mcp:tools",
|
|
124
|
+
source: "workspace-integration",
|
|
125
|
+
icon: "integration",
|
|
126
|
+
});
|
|
127
|
+
expect(
|
|
128
|
+
contribution.items.every((item) => item.section === "integrations"),
|
|
129
|
+
).toBe(true);
|
|
130
|
+
await act(async () =>
|
|
131
|
+
[...container.querySelectorAll("button")]
|
|
132
|
+
.find((button) => button.textContent === "Connect an integration…")!
|
|
133
|
+
.click(),
|
|
134
|
+
);
|
|
135
|
+
expect(
|
|
136
|
+
container.querySelector('[role="dialog"]')?.getAttribute("data-scope"),
|
|
137
|
+
).toBe("user");
|
|
138
|
+
});
|
|
139
|
+
it("starts organization administrators in their allowed shared scope", async () => {
|
|
140
|
+
mocks.org = { orgId: "alpha", role: "owner" };
|
|
141
|
+
await render();
|
|
142
|
+
await act(async () =>
|
|
143
|
+
[...container.querySelectorAll("button")]
|
|
144
|
+
.find((button) => button.textContent === "Connect an integration…")!
|
|
145
|
+
.click(),
|
|
146
|
+
);
|
|
147
|
+
expect(
|
|
148
|
+
container.querySelector('[role="dialog"]')?.getAttribute("data-scope"),
|
|
149
|
+
).toBe("org");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("rejects an old selection after identity changes away and back, and keeps unreadable catalogs visible as errors", async () => {
|
|
153
|
+
await render();
|
|
154
|
+
const select = contribution.items[0].onSelect!;
|
|
155
|
+
state = { ...state, scopeKey: "chat:member:beta" };
|
|
156
|
+
await render();
|
|
157
|
+
state = { ...state, scopeKey: "chat:member:alpha" };
|
|
158
|
+
await render();
|
|
159
|
+
expect(select).toThrow();
|
|
160
|
+
expect(mocks.select).not.toHaveBeenCalled();
|
|
161
|
+
state = {
|
|
162
|
+
...state,
|
|
163
|
+
data: undefined,
|
|
164
|
+
integrationsError: new Error("permission lookup unavailable"),
|
|
165
|
+
};
|
|
166
|
+
await render();
|
|
167
|
+
expect(container.querySelector('[role="alert"]')?.textContent).toBe(
|
|
168
|
+
"Could not load integrations.",
|
|
169
|
+
);
|
|
170
|
+
expect(contribution.items.map((item) => item.id)).toEqual([
|
|
171
|
+
"workspace-connect-integrations",
|
|
172
|
+
]);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import { useOrg } from "@agent-native/core/client/org";
|
|
3
|
+
import {
|
|
4
|
+
isMcpIntegrationCatalogAvailable,
|
|
5
|
+
useCreateMcpServer,
|
|
6
|
+
} from "@agent-native/core/client/resources";
|
|
7
|
+
import { mcpIntegrationLogo } from "@agent-native/core/client/resources/mcp-integration-logos";
|
|
8
|
+
import type { ComposerContextMenuItem } from "@agent-native/toolkit/composer";
|
|
9
|
+
import type {
|
|
10
|
+
WorkspaceComposerMenuContribution,
|
|
11
|
+
WorkspaceComposerMenuOptions,
|
|
12
|
+
} from "@agent-native/toolkit/composer/runtime-adapters";
|
|
13
|
+
import { IconPlugConnected, IconPlus } from "@tabler/icons-react";
|
|
14
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
15
|
+
|
|
16
|
+
import { McpIntegrationDialogDeferred } from "../../resources/index.js";
|
|
17
|
+
import { useAgentKitCapabilities } from "./use-agentkit-capabilities.js";
|
|
18
|
+
|
|
19
|
+
export function useWorkspaceComposerMenu({
|
|
20
|
+
onSelectIntegration,
|
|
21
|
+
onRestoreFocus,
|
|
22
|
+
enabled,
|
|
23
|
+
}: WorkspaceComposerMenuOptions): WorkspaceComposerMenuContribution {
|
|
24
|
+
const t = useT();
|
|
25
|
+
const capabilities = useAgentKitCapabilities({ enabled });
|
|
26
|
+
const { data: org } = useOrg();
|
|
27
|
+
const createMcp = useCreateMcpServer();
|
|
28
|
+
const canCreateOrgMcp =
|
|
29
|
+
!org?.orgId || org.role === "owner" || org.role === "admin";
|
|
30
|
+
const [open, setOpen] = useState(false);
|
|
31
|
+
const scope = useMemo(
|
|
32
|
+
() => ({ key: capabilities.scopeKey }),
|
|
33
|
+
[capabilities.scopeKey],
|
|
34
|
+
);
|
|
35
|
+
const latest = useRef({ capabilities, onSelectIntegration, scope });
|
|
36
|
+
latest.current = { capabilities, onSelectIntegration, scope };
|
|
37
|
+
const mounted = useRef(true);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
mounted.current = true;
|
|
40
|
+
return () => {
|
|
41
|
+
mounted.current = false;
|
|
42
|
+
};
|
|
43
|
+
}, []);
|
|
44
|
+
useEffect(() => setOpen(false), [scope]);
|
|
45
|
+
const items: ComposerContextMenuItem[] = (
|
|
46
|
+
capabilities.data?.integrations ?? []
|
|
47
|
+
).map((integration) => ({
|
|
48
|
+
id: `workspace-integration:${integration.id}`,
|
|
49
|
+
section: "integrations",
|
|
50
|
+
label: integration.label,
|
|
51
|
+
icon: mcpIntegrationLogo(integration.id) ? (
|
|
52
|
+
<img
|
|
53
|
+
src={mcpIntegrationLogo(integration.id)}
|
|
54
|
+
alt=""
|
|
55
|
+
className="size-4 object-contain"
|
|
56
|
+
/>
|
|
57
|
+
) : (
|
|
58
|
+
<IconPlugConnected />
|
|
59
|
+
),
|
|
60
|
+
onSelect: () => {
|
|
61
|
+
const current = latest.current;
|
|
62
|
+
if (
|
|
63
|
+
!mounted.current ||
|
|
64
|
+
current.scope !== scope ||
|
|
65
|
+
current.capabilities.integrationsError ||
|
|
66
|
+
current.capabilities.integrationsLoading ||
|
|
67
|
+
!current.capabilities.data?.integrations.some(
|
|
68
|
+
(entry) => entry.id === integration.id,
|
|
69
|
+
)
|
|
70
|
+
) {
|
|
71
|
+
throw new Error(t("agentChat.composer.integrations.loadFailed"));
|
|
72
|
+
}
|
|
73
|
+
current.onSelectIntegration({
|
|
74
|
+
label: integration.label,
|
|
75
|
+
refType: "integration",
|
|
76
|
+
refId: integration.id,
|
|
77
|
+
refPath: integration.id,
|
|
78
|
+
source: "workspace-integration",
|
|
79
|
+
icon: "integration",
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
83
|
+
if (isMcpIntegrationCatalogAvailable())
|
|
84
|
+
items.push({
|
|
85
|
+
id: "workspace-connect-integrations",
|
|
86
|
+
section: "integrations",
|
|
87
|
+
order: 1000,
|
|
88
|
+
opensDialog: true,
|
|
89
|
+
label: t("agentChat.composer.integrations.connect"),
|
|
90
|
+
icon: <IconPlus />,
|
|
91
|
+
onSelect: () => setOpen(true),
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
items,
|
|
95
|
+
scopeKey: capabilities.scopeKey,
|
|
96
|
+
loading: capabilities.integrationsLoading,
|
|
97
|
+
...(capabilities.integrationsError
|
|
98
|
+
? { error: t("agentChat.composer.integrations.loadFailed") }
|
|
99
|
+
: {}),
|
|
100
|
+
onRetry: capabilities.refetchIntegrations,
|
|
101
|
+
overlays: (
|
|
102
|
+
<McpIntegrationDialogDeferred
|
|
103
|
+
open={open}
|
|
104
|
+
onOpenChange={setOpen}
|
|
105
|
+
onCloseAutoFocus={(event) => {
|
|
106
|
+
if (
|
|
107
|
+
onRestoreFocus &&
|
|
108
|
+
mounted.current &&
|
|
109
|
+
latest.current.scope === scope
|
|
110
|
+
) {
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
onRestoreFocus();
|
|
113
|
+
}
|
|
114
|
+
}}
|
|
115
|
+
defaultScope={org?.orgId && !canCreateOrgMcp ? "user" : "org"}
|
|
116
|
+
canCreateOrgMcp={canCreateOrgMcp}
|
|
117
|
+
hasOrg={Boolean(org?.orgId)}
|
|
118
|
+
onCreateMcpServer={(args) => createMcp.mutateAsync(args)}
|
|
119
|
+
/>
|
|
120
|
+
),
|
|
121
|
+
};
|
|
122
|
+
}
|
package/src/app/chat/index.ts
CHANGED