@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.
Files changed (174) hide show
  1. package/agent-native.eject.json +1 -0
  2. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/components.js +124 -9
  4. package/dist/app/agentkit/react/components.js.map +1 -1
  5. package/dist/app/agentkit/react/context.d.ts +1 -1
  6. package/dist/app/agentkit/react/core-file-storage.d.ts +21 -0
  7. package/dist/app/agentkit/react/core-file-storage.d.ts.map +1 -0
  8. package/dist/app/agentkit/react/core-file-storage.js +3 -0
  9. package/dist/app/agentkit/react/core-file-storage.js.map +1 -0
  10. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  11. package/dist/app/auth/AuthPage.js +89 -10
  12. package/dist/app/auth/AuthPage.js.map +1 -1
  13. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  14. package/dist/app/chat/AgentKitAssistantChat.js +39 -58
  15. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  16. package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
  17. package/dist/app/chat/MultiTabAssistantChat.js +3 -1
  18. package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
  19. package/dist/app/chat/agentkit-chat/root.d.ts.map +1 -1
  20. package/dist/app/chat/agentkit-chat/root.js +66 -2
  21. package/dist/app/chat/agentkit-chat/root.js.map +1 -1
  22. package/dist/app/chat/chat/surface-types.d.ts +1 -0
  23. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  24. package/dist/app/chat/chat/surface-types.js.map +1 -1
  25. package/dist/app/chat/composer/runtime-adapters.d.ts.map +1 -1
  26. package/dist/app/chat/composer/runtime-adapters.js +2 -0
  27. package/dist/app/chat/composer/runtime-adapters.js.map +1 -1
  28. package/dist/app/chat/composer/use-agentkit-capabilities.d.ts +3 -1
  29. package/dist/app/chat/composer/use-agentkit-capabilities.d.ts.map +1 -1
  30. package/dist/app/chat/composer/use-agentkit-capabilities.js +11 -11
  31. package/dist/app/chat/composer/use-agentkit-capabilities.js.map +1 -1
  32. package/dist/app/chat/composer/use-workspace-composer-menu.d.ts +3 -0
  33. package/dist/app/chat/composer/use-workspace-composer-menu.d.ts.map +1 -0
  34. package/dist/app/chat/composer/use-workspace-composer-menu.js +80 -0
  35. package/dist/app/chat/composer/use-workspace-composer-menu.js.map +1 -0
  36. package/dist/app/chat/index.d.ts +1 -0
  37. package/dist/app/chat/index.d.ts.map +1 -1
  38. package/dist/app/chat/index.js +1 -0
  39. package/dist/app/chat/index.js.map +1 -1
  40. package/dist/app/chat/prepare-prompt-composer-agent-handoff.d.ts +5 -0
  41. package/dist/app/chat/prepare-prompt-composer-agent-handoff.d.ts.map +1 -0
  42. package/dist/app/chat/prepare-prompt-composer-agent-handoff.js +108 -0
  43. package/dist/app/chat/prepare-prompt-composer-agent-handoff.js.map +1 -0
  44. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  45. package/dist/app/i18n/catalogs/ar-SA.js +6 -1
  46. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  47. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  48. package/dist/app/i18n/catalogs/de-DE.js +6 -1
  49. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  50. package/dist/app/i18n/catalogs/en-US.d.ts +6 -1
  51. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  52. package/dist/app/i18n/catalogs/en-US.js +6 -1
  53. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  54. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  55. package/dist/app/i18n/catalogs/es-ES.js +6 -1
  56. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  57. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  58. package/dist/app/i18n/catalogs/fr-FR.js +6 -1
  59. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  60. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  61. package/dist/app/i18n/catalogs/hi-IN.js +6 -1
  62. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  63. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  64. package/dist/app/i18n/catalogs/ja-JP.js +6 -1
  65. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  66. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  67. package/dist/app/i18n/catalogs/ko-KR.js +6 -1
  68. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  69. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  70. package/dist/app/i18n/catalogs/pt-BR.js +6 -1
  71. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  72. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  73. package/dist/app/i18n/catalogs/zh-CN.js +6 -1
  74. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  75. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  76. package/dist/app/i18n/catalogs/zh-TW.js +6 -1
  77. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  78. package/dist/app/resources/McpIntegrationDialog.d.ts +3 -1
  79. package/dist/app/resources/McpIntegrationDialog.d.ts.map +1 -1
  80. package/dist/app/resources/McpIntegrationDialog.js +2 -2
  81. package/dist/app/resources/McpIntegrationDialog.js.map +1 -1
  82. package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
  83. package/dist/app/settings/useBuilderStatus.js +119 -40
  84. package/dist/app/settings/useBuilderStatus.js.map +1 -1
  85. package/dist/composer/ComposerContextMenu.d.ts +36 -2
  86. package/dist/composer/ComposerContextMenu.d.ts.map +1 -1
  87. package/dist/composer/ComposerContextMenu.js +108 -40
  88. package/dist/composer/ComposerContextMenu.js.map +1 -1
  89. package/dist/composer/ComposerContextPickerDialog.d.ts.map +1 -1
  90. package/dist/composer/ComposerContextPickerDialog.js +63 -26
  91. package/dist/composer/ComposerContextPickerDialog.js.map +1 -1
  92. package/dist/composer/ComposerPlusMenu.d.ts +5 -3
  93. package/dist/composer/ComposerPlusMenu.d.ts.map +1 -1
  94. package/dist/composer/ComposerPlusMenu.js +166 -121
  95. package/dist/composer/ComposerPlusMenu.js.map +1 -1
  96. package/dist/composer/MentionItemMedia.d.ts.map +1 -1
  97. package/dist/composer/MentionItemMedia.js +3 -1
  98. package/dist/composer/MentionItemMedia.js.map +1 -1
  99. package/dist/composer/PromptComposer.d.ts +1 -0
  100. package/dist/composer/PromptComposer.d.ts.map +1 -1
  101. package/dist/composer/PromptComposer.js +1 -0
  102. package/dist/composer/PromptComposer.js.map +1 -1
  103. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  104. package/dist/composer/TiptapComposer.js +117 -50
  105. package/dist/composer/TiptapComposer.js.map +1 -1
  106. package/dist/composer/index.d.ts +1 -0
  107. package/dist/composer/index.d.ts.map +1 -1
  108. package/dist/composer/runtime-adapters.d.ts +15 -0
  109. package/dist/composer/runtime-adapters.d.ts.map +1 -1
  110. package/dist/composer/runtime-adapters.js.map +1 -1
  111. package/dist/composer/use-composer-panel-placement.d.ts.map +1 -1
  112. package/dist/composer/use-composer-panel-placement.js +2 -4
  113. package/dist/composer/use-composer-panel-placement.js.map +1 -1
  114. package/dist/composer/use-mention-search.d.ts +7 -0
  115. package/dist/composer/use-mention-search.d.ts.map +1 -1
  116. package/dist/composer/use-mention-search.js +86 -19
  117. package/dist/composer/use-mention-search.js.map +1 -1
  118. package/dist/composer/useComposerContextPicker.d.ts +5 -1
  119. package/dist/composer/useComposerContextPicker.d.ts.map +1 -1
  120. package/dist/composer/useComposerContextPicker.js +18 -4
  121. package/dist/composer/useComposerContextPicker.js.map +1 -1
  122. package/package.json +4 -4
  123. package/src/app/agentkit/react/components.tsx +158 -11
  124. package/src/app/agentkit/react/composer-context.spec.tsx +180 -0
  125. package/src/app/agentkit/react/core-file-storage.tsx +14 -0
  126. package/src/app/auth/AuthPage.interaction.spec.tsx +143 -0
  127. package/src/app/auth/AuthPage.tsx +159 -13
  128. package/src/app/chat/AgentKitAssistantChat.spec.tsx +65 -3
  129. package/src/app/chat/AgentKitAssistantChat.tsx +57 -65
  130. package/src/app/chat/MultiTabAssistantChat.spec.tsx +21 -0
  131. package/src/app/chat/MultiTabAssistantChat.tsx +4 -0
  132. package/src/app/chat/agentkit-chat/root.tsx +91 -8
  133. package/src/app/chat/chat/surface-types.ts +1 -0
  134. package/src/app/chat/composer/runtime-adapters.tsx +2 -0
  135. package/src/app/chat/composer/use-agentkit-capabilities.hardening.spec.tsx +26 -1
  136. package/src/app/chat/composer/use-agentkit-capabilities.ts +11 -11
  137. package/src/app/chat/composer/use-agentkit-integration-menu.spec.tsx +2 -4
  138. package/src/app/chat/composer/use-workspace-composer-menu.spec.tsx +174 -0
  139. package/src/app/chat/composer/use-workspace-composer-menu.tsx +122 -0
  140. package/src/app/chat/index.ts +1 -0
  141. package/src/app/chat/prepare-prompt-composer-agent-handoff.spec.ts +162 -0
  142. package/src/app/chat/prepare-prompt-composer-agent-handoff.ts +134 -0
  143. package/src/app/i18n/catalogs/ar-SA.ts +6 -1
  144. package/src/app/i18n/catalogs/de-DE.ts +6 -1
  145. package/src/app/i18n/catalogs/en-US.ts +6 -1
  146. package/src/app/i18n/catalogs/es-ES.ts +6 -1
  147. package/src/app/i18n/catalogs/fr-FR.ts +6 -1
  148. package/src/app/i18n/catalogs/hi-IN.ts +6 -1
  149. package/src/app/i18n/catalogs/ja-JP.ts +6 -1
  150. package/src/app/i18n/catalogs/ko-KR.ts +6 -1
  151. package/src/app/i18n/catalogs/pt-BR.ts +6 -1
  152. package/src/app/i18n/catalogs/zh-CN.ts +6 -1
  153. package/src/app/i18n/catalogs/zh-TW.ts +6 -1
  154. package/src/app/resources/McpIntegrationDialog.tsx +5 -0
  155. package/src/app/settings/useBuilderStatus.spec.tsx +229 -13
  156. package/src/app/settings/useBuilderStatus.ts +145 -47
  157. package/src/composer/ComposerContextMenu.spec.tsx +95 -32
  158. package/src/composer/ComposerContextMenu.tsx +284 -73
  159. package/src/composer/ComposerContextPicker.spec.tsx +13 -14
  160. package/src/composer/ComposerContextPickerDialog.spec.tsx +40 -3
  161. package/src/composer/ComposerContextPickerDialog.tsx +158 -24
  162. package/src/composer/ComposerPlusMenu.parity.spec.tsx +442 -62
  163. package/src/composer/ComposerPlusMenu.tsx +322 -237
  164. package/src/composer/MentionItemMedia.tsx +3 -0
  165. package/src/composer/PromptComposer.context.spec.tsx +253 -82
  166. package/src/composer/PromptComposer.tsx +2 -0
  167. package/src/composer/TiptapComposer.spec.ts +6 -8
  168. package/src/composer/TiptapComposer.tsx +148 -55
  169. package/src/composer/index.ts +5 -0
  170. package/src/composer/runtime-adapters.tsx +18 -0
  171. package/src/composer/use-composer-panel-placement.ts +2 -4
  172. package/src/composer/use-mention-search.spec.tsx +99 -1
  173. package/src/composer/use-mention-search.ts +101 -24
  174. package/src/composer/useComposerContextPicker.ts +25 -5
@@ -1,10 +1,22 @@
1
1
  import { useComposerRuntime } from "@assistant-ui/react";
2
- import { IconLoader2 } from "@tabler/icons-react";
2
+ import {
3
+ IconBolt,
4
+ IconCalendarTime,
5
+ IconCode,
6
+ IconLoader2,
7
+ IconPlugConnected,
8
+ IconSchool,
9
+ } from "@tabler/icons-react";
3
10
  import { useCallback, useEffect, useId, useRef, useState } from "react";
4
11
 
5
12
  import { uploadedSkillSlug } from "../skill-upload.js";
6
13
  import { Button } from "../ui/button.js";
7
- import { DropdownMenuGroup, DropdownMenuItem } from "../ui/dropdown-menu.js";
14
+ import {
15
+ Dialog,
16
+ DialogContent,
17
+ DialogHeader,
18
+ DialogTitle,
19
+ } from "../ui/dialog.js";
8
20
  import { Input } from "../ui/input.js";
9
21
  import { Label } from "../ui/label.js";
10
22
  import { Textarea } from "../ui/textarea.js";
@@ -14,9 +26,11 @@ import {
14
26
  ComposerContextMenu,
15
27
  type ComposerContextMenuItem,
16
28
  type ComposerContextMenuProps,
17
- type ComposerContextPageControls,
18
29
  } from "./ComposerContextMenu.js";
19
- import { useComposerRuntimeAdapters } from "./runtime-adapters.js";
30
+ import {
31
+ useComposerRuntimeAdapters,
32
+ type AgentComposerReference,
33
+ } from "./runtime-adapters.js";
20
34
  import type { ComposerMode } from "./types.js";
21
35
 
22
36
  export interface ComposerTerminalModeControl {
@@ -29,6 +43,7 @@ interface ComposerPlusMenuProps extends Omit<
29
43
  ComposerContextMenuProps,
30
44
  "items"
31
45
  > {
46
+ onSelectIntegration?: (reference: AgentComposerReference) => void;
32
47
  onSelectMode?: (mode: ComposerMode) => void;
33
48
  attachmentsEnabled?: boolean;
34
49
  extensionTools?: boolean;
@@ -58,15 +73,46 @@ export function mergeComposerMenuItems(
58
73
  defaults: readonly ComposerContextMenuItem[],
59
74
  provided: readonly ComposerContextMenuItem[],
60
75
  ): ComposerContextMenuItem[] {
61
- const ids = new Set<string>();
62
- const visit = (items: readonly ComposerContextMenuItem[]) => {
63
- for (const item of items) {
64
- ids.add(item.id);
65
- if (item.children) visit(item.children);
66
- }
67
- };
68
- visit(provided);
69
- return [...provided, ...defaults.filter((item) => !ids.has(item.id))];
76
+ const defaultIds = new Set(defaults.map((item) => item.id));
77
+ const overrides = new Map<string, ComposerContextMenuItem>();
78
+ const seen = new Set<string>();
79
+ const retain = (
80
+ items: readonly ComposerContextMenuItem[],
81
+ inheritedDisabled = false,
82
+ ): ComposerContextMenuItem[] =>
83
+ items.flatMap((item): ComposerContextMenuItem[] => {
84
+ if (seen.has(item.id)) return [];
85
+ seen.add(item.id);
86
+ if (defaultIds.has(item.id)) {
87
+ overrides.set(
88
+ item.id,
89
+ inheritedDisabled ? { ...item, disabled: true } : item,
90
+ );
91
+ return [];
92
+ }
93
+ if (!item.children) return [item];
94
+ const children = retain(
95
+ item.children,
96
+ inheritedDisabled || item.disabled === true,
97
+ );
98
+ return children.length ? [{ ...item, children }] : [];
99
+ });
100
+ const context = retain(provided);
101
+ return [
102
+ ...context,
103
+ ...defaults.map((item) => {
104
+ const override = overrides.get(item.id);
105
+ const section = override?.section ?? item.section;
106
+ return override
107
+ ? {
108
+ ...override,
109
+ ...(item.order === undefined ? {} : { order: item.order }),
110
+ ...(section === undefined ? {} : { section }),
111
+ icon: override.icon ?? item.icon,
112
+ }
113
+ : item;
114
+ }),
115
+ ];
70
116
  }
71
117
 
72
118
  export function useComposerDefaultActions({
@@ -74,16 +120,22 @@ export function useComposerDefaultActions({
74
120
  extensionTools = false,
75
121
  disabled,
76
122
  onRestoreFocus,
123
+ integrationStatus,
77
124
  }: Pick<
78
125
  ComposerPlusMenuProps,
79
- "onSelectMode" | "extensionTools" | "disabled" | "onRestoreFocus"
126
+ | "onSelectMode"
127
+ | "extensionTools"
128
+ | "disabled"
129
+ | "onRestoreFocus"
130
+ | "integrationStatus"
80
131
  >) {
81
132
  const adapters = useComposerRuntimeAdapters();
82
- const t = adapters.translate!;
133
+ const t = adapters.translate;
83
134
  const resources = adapters.resources!;
84
135
  const [mcpDialogOpen, setMcpDialogOpen] = useState(false);
85
- const overlayOpen = useRef(false);
86
- const showMcpIntegrations = resources.isMcpIntegrationAvailable!();
136
+ const showMcpIntegrations =
137
+ !resources.useWorkspaceMenu &&
138
+ resources.isMcpIntegrationAvailable?.() === true;
87
139
  const { data: org } = resources.useOrg!();
88
140
  const canCreateOrgMcp =
89
141
  !org?.orgId || org.role === "owner" || org.role === "admin";
@@ -91,10 +143,6 @@ export function useComposerDefaultActions({
91
143
  const McpIntegrationDialog = resources.McpIntegrationDialog;
92
144
  const [view, setView] = useState<View>("menu");
93
145
  const skillFileInputRef = useRef<HTMLInputElement>(null);
94
- const skillControls = useRef<ComposerContextPageControls | null>(null);
95
- const skillFilePickerOpen = useRef(false);
96
- const skillPageDismissedWhilePickerOpen = useRef(false);
97
- const skillResumeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
98
146
  const skillEpoch = useRef(0);
99
147
  const skillRequest = useRef<AbortController | null>(null);
100
148
  const skillCloseTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -114,48 +162,24 @@ export function useComposerDefaultActions({
114
162
  if (skillCloseTimer.current !== null) clearTimeout(skillCloseTimer.current);
115
163
  skillCloseTimer.current = null;
116
164
  }, []);
117
- const cancelSkillWork = useCallback(() => {
118
- skillFilePickerOpen.current = false;
119
- skillPageDismissedWhilePickerOpen.current = false;
120
- if (skillResumeTimer.current !== null)
121
- clearTimeout(skillResumeTimer.current);
122
- skillResumeTimer.current = null;
123
- invalidateSkillWork();
124
- }, [invalidateSkillWork]);
125
- const finishSkillFilePicker = useCallback(() => {
126
- skillFilePickerOpen.current = false;
127
- if (!skillPageDismissedWhilePickerOpen.current) return;
128
- skillPageDismissedWhilePickerOpen.current = false;
129
- if (skillResumeTimer.current !== null)
130
- clearTimeout(skillResumeTimer.current);
131
- skillResumeTimer.current = setTimeout(() => {
132
- skillResumeTimer.current = null;
133
- skillControls.current?.onResume();
134
- }, 0);
135
- }, []);
136
- const dismissSkillPage = useCallback(() => {
137
- if (skillFilePickerOpen.current) {
138
- skillPageDismissedWhilePickerOpen.current = true;
139
- return;
140
- }
141
- cancelSkillWork();
142
- }, [cancelSkillWork]);
143
- useEffect(() => {
144
- const input = skillFileInputRef.current;
145
- input?.addEventListener("cancel", finishSkillFilePicker);
146
- return () => {
147
- input?.removeEventListener("cancel", finishSkillFilePicker);
148
- if (skillResumeTimer.current !== null)
149
- clearTimeout(skillResumeTimer.current);
150
- };
151
- }, [finishSkillFilePicker]);
165
+ const cancelSkillWork = invalidateSkillWork;
152
166
  useEffect(() => cancelSkillWork, [cancelSkillWork, adapters.resolvePath]);
153
167
  useEffect(() => {
154
168
  if (!disabled) return;
155
169
  cancelSkillWork();
170
+ setView("menu");
156
171
  setMcpDialogOpen(false);
157
- overlayOpen.current = false;
158
172
  }, [disabled, cancelSkillWork]);
173
+ useEffect(() => {
174
+ setView("menu");
175
+ return cancelSkillWork;
176
+ }, [integrationStatus?.scopeKey, cancelSkillWork]);
177
+ useEffect(() => {
178
+ const input = skillFileInputRef.current;
179
+ const restore = () => onRestoreFocus?.();
180
+ input?.addEventListener("cancel", restore);
181
+ return () => input?.removeEventListener("cancel", restore);
182
+ }, [onRestoreFocus]);
159
183
  const resetSkillUpload = () => {
160
184
  cancelSkillWork();
161
185
  setView("menu");
@@ -169,6 +193,8 @@ export function useComposerDefaultActions({
169
193
  invalidateSkillWork();
170
194
  const epoch = skillEpoch.current;
171
195
  setSkillUploadStatus(null);
196
+ setSkillUploadFileName(file.name);
197
+ setView("skill-upload");
172
198
  try {
173
199
  const content = await file.text();
174
200
  if (skillEpoch.current !== epoch) return;
@@ -238,7 +264,7 @@ export function useComposerDefaultActions({
238
264
  }),
239
265
  });
240
266
  skillCloseTimer.current = setTimeout(() => {
241
- if (skillEpoch.current === epoch) skillControls.current?.onClose();
267
+ if (skillEpoch.current === epoch) resetSkillUpload();
242
268
  }, 1200);
243
269
  } catch (error) {
244
270
  if (skillEpoch.current !== epoch) return;
@@ -262,6 +288,9 @@ export function useComposerDefaultActions({
262
288
  const items: ComposerContextMenuItem[] = [
263
289
  {
264
290
  id: "schedule-task",
291
+ section: "commands" as const,
292
+ order: 10,
293
+ icon: <IconCalendarTime />,
265
294
  label: t("agentChat.composer.menu.scheduleTask", {
266
295
  defaultValue: "Schedule Task",
267
296
  }),
@@ -275,6 +304,9 @@ export function useComposerDefaultActions({
275
304
  },
276
305
  {
277
306
  id: "create-automation",
307
+ section: "commands" as const,
308
+ order: 20,
309
+ icon: <IconBolt />,
278
310
  label: t("agentChat.composer.menu.createAutomation", {
279
311
  defaultValue: "Create Automation",
280
312
  }),
@@ -290,6 +322,9 @@ export function useComposerDefaultActions({
290
322
  ? [
291
323
  {
292
324
  id: "create-extension",
325
+ section: "commands" as const,
326
+ order: 40,
327
+ icon: <IconCode />,
293
328
  label: t("agentChat.composer.menu.createExtension", {
294
329
  defaultValue: "Create Extension",
295
330
  }),
@@ -307,6 +342,10 @@ export function useComposerDefaultActions({
307
342
  ? [
308
343
  {
309
344
  id: "integrations",
345
+ opensDialog: true,
346
+ section: "integrations" as const,
347
+ order: 50,
348
+ icon: <IconPlugConnected />,
310
349
  label: t("agentChat.composer.menu.integrations", {
311
350
  defaultValue: "Integrations",
312
351
  }),
@@ -316,7 +355,6 @@ export function useComposerDefaultActions({
316
355
  }),
317
356
  ],
318
357
  onSelect: () => {
319
- overlayOpen.current = true;
320
358
  setMcpDialogOpen(true);
321
359
  },
322
360
  },
@@ -324,6 +362,9 @@ export function useComposerDefaultActions({
324
362
  : []),
325
363
  {
326
364
  id: "create-skill",
365
+ section: "commands" as const,
366
+ order: 30,
367
+ icon: <IconSchool />,
327
368
  label: t("agentChat.composer.menu.createSkill", {
328
369
  defaultValue: "Create Skill",
329
370
  }),
@@ -335,155 +376,28 @@ export function useComposerDefaultActions({
335
376
  defaultValue: "Upload skill file",
336
377
  }),
337
378
  ],
338
- onSelect: resetSkillUpload,
339
- onDismiss: dismissSkillPage,
340
- render: (controls) => {
341
- skillControls.current = controls;
342
- return view === "menu" ? (
343
- <DropdownMenuGroup>
344
- <DropdownMenuItem
345
- disabled={!onSelectMode}
346
- onSelect={() => {
347
- controls.onClose();
348
- onSelectMode?.("skill");
349
- }}
350
- >
351
- {t("agentChat.composer.skill.createNew", {
352
- defaultValue: "Create new skill",
353
- })}
354
- </DropdownMenuItem>
355
- <DropdownMenuItem
356
- onSelect={(event) => {
357
- event.preventDefault();
358
- const input = skillFileInputRef.current;
359
- if (!input) return;
360
- skillPageDismissedWhilePickerOpen.current = false;
361
- skillFilePickerOpen.current = true;
362
- controls.onClose({ restoreFocus: false });
363
- input.click();
364
- }}
365
- >
366
- {t("agentChat.composer.skill.uploadFile", {
367
- defaultValue: "Upload skill file",
368
- })}
369
- </DropdownMenuItem>
370
- {skillUploadStatus && (
371
- <div role="alert" className="p-3 text-sm text-destructive">
372
- {skillUploadStatus.message}
373
- </div>
374
- )}
375
- </DropdownMenuGroup>
376
- ) : (
377
- <form
378
- className="grid min-w-0 gap-2 p-3"
379
- onSubmit={(event) => {
380
- event.preventDefault();
381
- event.stopPropagation();
382
- void submitSkillUpload();
383
- }}
384
- onKeyDown={(event) => {
385
- if (event.key !== "Escape" && event.key !== "Tab")
386
- event.stopPropagation();
387
- }}
388
- >
389
- <Button
390
- type="button"
391
- variant="ghost"
392
- size="sm"
393
- className="-ms-3 justify-self-start"
394
- onClick={resetSkillUpload}
395
- >
396
- {t("agentChat.composer.skill.back", { defaultValue: "Back" })}
397
- </Button>
398
- <p className="break-words text-xs text-muted-foreground">
399
- {t("agentChat.composer.skill.review", {
400
- name:
401
- skillUploadFileName ||
402
- t("agentChat.composer.skill.selectedFile", {
403
- defaultValue: "the selected file",
404
- }),
405
- defaultValue: `Review the content from ${skillUploadFileName || "the selected file"} before saving.`,
406
- })}
407
- </p>
408
- <Label htmlFor={`${formId}-name`}>
409
- {t("agentChat.composer.skill.name", {
410
- defaultValue: "Skill name",
411
- })}
412
- </Label>
413
- <Input
414
- id={`${formId}-name`}
415
- autoFocus
416
- value={skillUploadSlug}
417
- disabled={skillUploadBusy}
418
- onChange={(event) => setSkillUploadSlug(event.target.value)}
419
- placeholder="my-skill"
420
- />
421
- <p className="break-words text-xs text-muted-foreground">
422
- {t("agentChat.composer.skill.savedAt", {
423
- defaultValue: "Saved at",
424
- })}{" "}
425
- <span className="font-mono">
426
- skills/{slugifyName(skillUploadSlug || "uploaded-skill")}
427
- /SKILL.md
428
- </span>
429
- </p>
430
- <Label htmlFor={`${formId}-content`}>
431
- {t("agentChat.composer.skill.content", {
432
- defaultValue: "Content",
433
- })}
434
- </Label>
435
- <Textarea
436
- id={`${formId}-content`}
437
- value={skillUploadContent}
438
- disabled={skillUploadBusy}
439
- onChange={(event) => setSkillUploadContent(event.target.value)}
440
- rows={10}
441
- className="font-mono text-xs"
442
- />
443
- {skillUploadStatus && (
444
- <div
445
- role={skillUploadStatus.kind === "err" ? "alert" : "status"}
446
- className={cn(
447
- "break-words text-xs",
448
- skillUploadStatus.kind === "err"
449
- ? "text-destructive"
450
- : "text-muted-foreground",
451
- )}
452
- >
453
- {skillUploadStatus.message}
454
- </div>
455
- )}
456
- <div className="flex flex-wrap justify-end gap-2">
457
- <Button type="button" variant="ghost" onClick={resetSkillUpload}>
458
- {t("agentChat.common.cancel", { defaultValue: "Cancel" })}
459
- </Button>
460
- <Button
461
- type="submit"
462
- disabled={
463
- skillUploadBusy ||
464
- skillUploadStatus?.kind === "ok" ||
465
- !skillUploadContent.trim() ||
466
- !skillUploadSlug.trim()
467
- }
468
- >
469
- {skillUploadBusy ? (
470
- <IconLoader2 className="animate-spin" />
471
- ) : (
472
- t("agentChat.common.save", { defaultValue: "Save" })
473
- )}
474
- </Button>
475
- </div>
476
- </form>
477
- );
379
+ disabled: !onSelectMode,
380
+ onSelect: () => onSelectMode?.("skill"),
381
+ },
382
+ {
383
+ id: "upload-skill",
384
+ opensDialog: true,
385
+ section: "commands",
386
+ order: 35,
387
+ icon: <IconSchool />,
388
+ label: t("agentChat.composer.skill.uploadFile", {
389
+ defaultValue: "Upload skill file",
390
+ }),
391
+ onSelect: () => {
392
+ resetSkillUpload();
393
+ skillFileInputRef.current?.click();
478
394
  },
479
395
  },
480
396
  ];
481
397
 
482
398
  return {
483
399
  items,
484
- restoreFocus: () => {
485
- if (!overlayOpen.current) onRestoreFocus?.();
486
- },
400
+ restoreFocus: onRestoreFocus,
487
401
  overlays: (
488
402
  <>
489
403
  <input
@@ -494,15 +408,133 @@ export function useComposerDefaultActions({
494
408
  onChange={(event) => {
495
409
  const file = event.target.files?.[0];
496
410
  event.target.value = "";
497
- finishSkillFilePicker();
498
411
  if (file) void handleSkillFileSelected(file);
499
412
  }}
500
413
  />
414
+ <Dialog
415
+ open={view === "skill-upload"}
416
+ onOpenChange={(open) => {
417
+ if (!open) resetSkillUpload();
418
+ }}
419
+ >
420
+ <DialogContent
421
+ aria-describedby={undefined}
422
+ closeLabel={t("agentChat.common.close", { defaultValue: "Close" })}
423
+ onCloseAutoFocus={(event) => {
424
+ if (onRestoreFocus) {
425
+ event.preventDefault();
426
+ onRestoreFocus();
427
+ }
428
+ }}
429
+ >
430
+ <DialogHeader>
431
+ <DialogTitle>
432
+ {t("agentChat.composer.skill.uploadFile", {
433
+ defaultValue: "Upload skill file",
434
+ })}
435
+ </DialogTitle>
436
+ </DialogHeader>
437
+ <form
438
+ className="grid min-w-0 gap-2 p-3"
439
+ onSubmit={(event) => {
440
+ event.preventDefault();
441
+ event.stopPropagation();
442
+ void submitSkillUpload();
443
+ }}
444
+ onKeyDown={(event) => {
445
+ if (event.key !== "Escape" && event.key !== "Tab")
446
+ event.stopPropagation();
447
+ }}
448
+ >
449
+ <p className="break-words text-xs text-muted-foreground">
450
+ {t("agentChat.composer.skill.review", {
451
+ name:
452
+ skillUploadFileName ||
453
+ t("agentChat.composer.skill.selectedFile", {
454
+ defaultValue: "the selected file",
455
+ }),
456
+ defaultValue: `Review the content from ${skillUploadFileName || "the selected file"} before saving.`,
457
+ })}
458
+ </p>
459
+ <Label htmlFor={`${formId}-name`}>
460
+ {t("agentChat.composer.skill.name", {
461
+ defaultValue: "Skill name",
462
+ })}
463
+ </Label>
464
+ <Input
465
+ id={`${formId}-name`}
466
+ autoFocus
467
+ value={skillUploadSlug}
468
+ disabled={skillUploadBusy}
469
+ onChange={(event) => setSkillUploadSlug(event.target.value)}
470
+ placeholder="my-skill"
471
+ />
472
+ <p className="break-words text-xs text-muted-foreground">
473
+ {t("agentChat.composer.skill.savedAt", {
474
+ defaultValue: "Saved at",
475
+ })}{" "}
476
+ <span className="font-mono">
477
+ skills/{slugifyName(skillUploadSlug || "uploaded-skill")}
478
+ /SKILL.md
479
+ </span>
480
+ </p>
481
+ <Label htmlFor={`${formId}-content`}>
482
+ {t("agentChat.composer.skill.content", {
483
+ defaultValue: "Content",
484
+ })}
485
+ </Label>
486
+ <Textarea
487
+ id={`${formId}-content`}
488
+ value={skillUploadContent}
489
+ disabled={skillUploadBusy}
490
+ onChange={(event) => setSkillUploadContent(event.target.value)}
491
+ rows={10}
492
+ className="font-mono text-xs"
493
+ />
494
+ {skillUploadStatus && (
495
+ <div
496
+ role={skillUploadStatus.kind === "err" ? "alert" : "status"}
497
+ className={cn(
498
+ "break-words text-xs",
499
+ skillUploadStatus.kind === "err"
500
+ ? "text-destructive"
501
+ : "text-muted-foreground",
502
+ )}
503
+ >
504
+ {skillUploadStatus.message}
505
+ </div>
506
+ )}
507
+ <div className="flex flex-wrap justify-end gap-2">
508
+ <Button
509
+ type="button"
510
+ variant="ghost"
511
+ onClick={resetSkillUpload}
512
+ >
513
+ {t("agentChat.common.cancel", { defaultValue: "Cancel" })}
514
+ </Button>
515
+ <Button
516
+ type="submit"
517
+ disabled={
518
+ skillUploadBusy ||
519
+ skillUploadStatus?.kind === "ok" ||
520
+ !skillUploadContent.trim() ||
521
+ !skillUploadSlug.trim()
522
+ }
523
+ >
524
+ {skillUploadBusy ? (
525
+ <IconLoader2 className="animate-spin" />
526
+ ) : (
527
+ t("agentChat.common.save", { defaultValue: "Save" })
528
+ )}
529
+ </Button>
530
+ </div>
531
+ </form>
532
+ </DialogContent>
533
+ </Dialog>
501
534
  {McpIntegrationDialog ? (
502
535
  <McpIntegrationDialog
503
536
  open={mcpDialogOpen}
504
537
  onOpenChange={(open: boolean) => {
505
- overlayOpen.current = open;
506
538
  setMcpDialogOpen(open);
507
539
  if (!open) onRestoreFocus?.();
508
540
  }}
@@ -524,19 +556,11 @@ function ComposerFullContextMenu(props: ComposerPlusMenuProps) {
524
556
  defaults.items,
525
557
  props.contextMenuItems ?? [],
526
558
  );
527
- const defaultActionIds = new Set(defaults.items.map((item) => item.id));
528
- const menuActionItems = mergedItems.filter((item) =>
529
- defaultActionIds.has(item.id),
530
- );
531
- const contextItems = mergedItems.filter(
532
- (item) => !defaultActionIds.has(item.id),
533
- );
534
559
  return (
535
560
  <>
536
561
  <ComposerContextMenu
537
562
  {...props}
538
- items={contextItems}
539
- menuActionItems={menuActionItems}
563
+ items={mergedItems}
540
564
  addAttachment={
541
565
  props.attachmentsEnabled === false
542
566
  ? undefined
@@ -556,24 +580,72 @@ export function ComposerPlusMenu({
556
580
  contextMenuItems = [],
557
581
  terminalModeControl,
558
582
  attachmentsEnabled = true,
583
+ onSelectIntegration,
584
+ addAttachment,
585
+ onAttachmentRequest,
559
586
  ...props
560
587
  }: ComposerPlusMenuProps) {
561
588
  const runtime = useComposerRuntime();
562
- const t = useComposerRuntimeAdapters().translate!;
589
+ const adapters = useComposerRuntimeAdapters();
590
+ const t = adapters.translate;
591
+ const workspace = adapters.resources?.useWorkspaceMenu?.({
592
+ enabled: props.open === true && mode !== "terminal",
593
+ ...(props.onRestoreFocus ? { onRestoreFocus: props.onRestoreFocus } : {}),
594
+ onSelectIntegration: (reference) => {
595
+ if (!onSelectIntegration)
596
+ throw new Error(
597
+ t("agentChat.composer.contextActionFailed", {
598
+ defaultValue: "Could not add context.",
599
+ }),
600
+ );
601
+ onSelectIntegration(reference);
602
+ },
603
+ });
604
+ const scopeEntries = (
605
+ entries: readonly ComposerContextMenuItem[],
606
+ ): ComposerContextMenuItem[] =>
607
+ entries.map((entry) => {
608
+ if (
609
+ entry.picker &&
610
+ entry.picker.identityScope === "workspace" &&
611
+ workspace?.scopeKey !== undefined
612
+ )
613
+ return {
614
+ ...entry,
615
+ picker: { ...entry.picker, scopeKey: workspace.scopeKey },
616
+ };
617
+ return entry.children
618
+ ? { ...entry, children: scopeEntries(entry.children) }
619
+ : entry;
620
+ });
621
+ contextMenuItems = scopeEntries(
622
+ mode === "terminal"
623
+ ? contextMenuItems
624
+ : mergeComposerMenuItems(workspace?.items ?? [], contextMenuItems),
625
+ );
563
626
  if (mode === "full") {
564
627
  return (
565
- <ComposerFullContextMenu
566
- {...props}
567
- attachmentsEnabled={attachmentsEnabled}
568
- contextMenuItems={contextMenuItems}
569
- />
628
+ <>
629
+ <ComposerFullContextMenu
630
+ {...props}
631
+ {...(workspace ? { integrationStatus: workspace } : {})}
632
+ {...(addAttachment ? { addAttachment } : {})}
633
+ {...(onAttachmentRequest ? { onAttachmentRequest } : {})}
634
+ attachmentsEnabled={attachmentsEnabled}
635
+ contextMenuItems={contextMenuItems}
636
+ />
637
+ {workspace?.overlays}
638
+ </>
570
639
  );
571
640
  }
572
641
  const terminalItems: ComposerContextMenuItem[] = terminalModeControl
573
642
  ? [
574
643
  {
575
644
  id: "terminal-primary",
576
- label: t("agentPanel.newTerminal", { defaultValue: "New terminal" }),
645
+ order: 10,
646
+ label: t("agentPanel.newTerminal", {
647
+ defaultValue: "New terminal",
648
+ }),
577
649
  disabled:
578
650
  terminalModeControl.enabled && !terminalModeControl.onNewTerminal,
579
651
  onSelect: () => {
@@ -584,6 +656,7 @@ export function ComposerPlusMenu({
584
656
  },
585
657
  {
586
658
  id: "terminal-mode",
659
+ order: 20,
587
660
  label: t("agentPanel.cliTerminalMode", {
588
661
  defaultValue: "CLI terminal mode",
589
662
  }),
@@ -599,22 +672,34 @@ export function ComposerPlusMenu({
599
672
  : contextMenuItems;
600
673
  const terminalActionIds = new Set(terminalItems.map((item) => item.id));
601
674
  return (
602
- <ComposerContextMenu
603
- {...props}
604
- items={mergedTerminalItems.filter(
605
- (item) => !terminalActionIds.has(item.id),
606
- )}
607
- menuActionItems={mergedTerminalItems.filter((item) =>
608
- terminalActionIds.has(item.id),
609
- )}
610
- addAttachment={
611
- mode === "upload-only" && attachmentsEnabled
612
- ? (props.addAttachment ?? runtime.addAttachment)
613
- : undefined
614
- }
615
- onAttachmentRequest={
616
- mode === "upload-only" ? props.onAttachmentRequest : undefined
617
- }
618
- />
675
+ <>
676
+ <ComposerContextMenu
677
+ {...props}
678
+ {...(mode !== "terminal" && workspace
679
+ ? { integrationStatus: workspace }
680
+ : {})}
681
+ items={
682
+ mode === "terminal"
683
+ ? mergedTerminalItems.filter(
684
+ (item) => !terminalActionIds.has(item.id),
685
+ )
686
+ : contextMenuItems
687
+ }
688
+ menuActionItems={
689
+ mode === "terminal"
690
+ ? mergedTerminalItems.filter((item) =>
691
+ terminalActionIds.has(item.id),
692
+ )
693
+ : []
694
+ }
695
+ {...(mode === "upload-only" && attachmentsEnabled
696
+ ? { addAttachment: addAttachment ?? runtime.addAttachment }
697
+ : {})}
698
+ {...(mode === "upload-only" && onAttachmentRequest
699
+ ? { onAttachmentRequest }
700
+ : {})}
701
+ />
702
+ {workspace?.overlays}
703
+ </>
619
704
  );
620
705
  }