@copilotkit/react-core 1.54.1 → 1.55.0-next.7

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 (183) hide show
  1. package/CHANGELOG.md +117 -116
  2. package/dist/copilotkit-B3Mb1yVE.cjs +7975 -0
  3. package/dist/copilotkit-B3Mb1yVE.cjs.map +1 -0
  4. package/dist/copilotkit-DBzgOMby.d.cts +2182 -0
  5. package/dist/copilotkit-DBzgOMby.d.cts.map +1 -0
  6. package/dist/copilotkit-DNYSFuz5.mjs +7562 -0
  7. package/dist/copilotkit-DNYSFuz5.mjs.map +1 -0
  8. package/dist/copilotkit-Dy5w3qEV.d.mts +2182 -0
  9. package/dist/copilotkit-Dy5w3qEV.d.mts.map +1 -0
  10. package/dist/index.cjs +27 -28
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +3 -3
  13. package/dist/index.d.cts.map +1 -1
  14. package/dist/index.d.mts +3 -3
  15. package/dist/index.d.mts.map +1 -1
  16. package/dist/index.mjs +4 -5
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/index.umd.js +1941 -35
  19. package/dist/index.umd.js.map +1 -1
  20. package/dist/v2/index.cjs +77 -7
  21. package/dist/v2/index.css +1 -2
  22. package/dist/v2/index.d.cts +6 -4
  23. package/dist/v2/index.d.mts +6 -4
  24. package/dist/v2/index.mjs +7 -4
  25. package/dist/v2/index.umd.js +5725 -24
  26. package/dist/v2/index.umd.js.map +1 -1
  27. package/package.json +37 -9
  28. package/scripts/scope-preflight.mjs +101 -0
  29. package/src/components/CopilotListeners.tsx +2 -6
  30. package/src/components/copilot-provider/copilot-messages.tsx +1 -1
  31. package/src/components/copilot-provider/copilotkit-props.tsx +1 -1
  32. package/src/components/copilot-provider/copilotkit.tsx +4 -4
  33. package/src/context/copilot-messages-context.tsx +1 -1
  34. package/src/hooks/__tests__/use-coagent-config.test.ts +2 -2
  35. package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +2 -2
  36. package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +3 -7
  37. package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +1 -1
  38. package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +4 -4
  39. package/src/hooks/use-agent-nodename.ts +1 -1
  40. package/src/hooks/use-coagent-state-render-bridge.tsx +1 -4
  41. package/src/hooks/use-coagent.ts +1 -1
  42. package/src/hooks/use-configure-chat-suggestions.tsx +2 -2
  43. package/src/hooks/use-copilot-chat-suggestions.tsx +2 -2
  44. package/src/hooks/use-copilot-chat_internal.ts +2 -2
  45. package/src/hooks/use-copilot-readable.ts +1 -1
  46. package/src/hooks/use-frontend-tool.ts +2 -2
  47. package/src/hooks/use-human-in-the-loop.ts +2 -2
  48. package/src/hooks/use-langgraph-interrupt.ts +2 -5
  49. package/src/hooks/use-lazy-tool-renderer.tsx +1 -1
  50. package/src/hooks/use-render-tool-call.ts +1 -1
  51. package/src/lib/copilot-task.ts +1 -1
  52. package/src/setupTests.ts +18 -14
  53. package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +176 -0
  54. package/src/v2/__tests__/globalSetup.ts +14 -0
  55. package/src/v2/__tests__/setup.ts +93 -0
  56. package/src/v2/__tests__/utils/test-helpers.tsx +470 -0
  57. package/src/v2/a2ui/A2UIMessageRenderer.tsx +206 -0
  58. package/src/v2/components/CopilotKitInspector.tsx +50 -0
  59. package/src/v2/components/MCPAppsActivityRenderer.tsx +785 -0
  60. package/src/v2/components/WildcardToolCallRender.tsx +86 -0
  61. package/src/v2/components/__tests__/license-warning-banner.test.tsx +46 -0
  62. package/src/v2/components/chat/CopilotChat.tsx +431 -0
  63. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +375 -0
  64. package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +350 -0
  65. package/src/v2/components/chat/CopilotChatInput.tsx +1302 -0
  66. package/src/v2/components/chat/CopilotChatMessageView.tsx +556 -0
  67. package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +252 -0
  68. package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +59 -0
  69. package/src/v2/components/chat/CopilotChatSuggestionView.tsx +133 -0
  70. package/src/v2/components/chat/CopilotChatToggleButton.tsx +171 -0
  71. package/src/v2/components/chat/CopilotChatToolCallsView.tsx +40 -0
  72. package/src/v2/components/chat/CopilotChatUserMessage.tsx +388 -0
  73. package/src/v2/components/chat/CopilotChatView.tsx +598 -0
  74. package/src/v2/components/chat/CopilotModalHeader.tsx +129 -0
  75. package/src/v2/components/chat/CopilotPopup.tsx +81 -0
  76. package/src/v2/components/chat/CopilotPopupView.tsx +317 -0
  77. package/src/v2/components/chat/CopilotSidebar.tsx +76 -0
  78. package/src/v2/components/chat/CopilotSidebarView.tsx +255 -0
  79. package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +1113 -0
  80. package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +73 -0
  81. package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +432 -0
  82. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +150 -0
  83. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +624 -0
  84. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +702 -0
  85. package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +107 -0
  86. package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +929 -0
  87. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +986 -0
  88. package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +1004 -0
  89. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +169 -0
  90. package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +530 -0
  91. package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +782 -0
  92. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +2413 -0
  93. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +621 -0
  94. package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +853 -0
  95. package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +1050 -0
  96. package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +484 -0
  97. package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +612 -0
  98. package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +502 -0
  99. package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +1011 -0
  100. package/src/v2/components/chat/__tests__/setup.ts +1 -0
  101. package/src/v2/components/chat/index.ts +79 -0
  102. package/src/v2/components/index.ts +7 -0
  103. package/src/v2/components/license-warning-banner.tsx +198 -0
  104. package/src/v2/components/ui/button.tsx +123 -0
  105. package/src/v2/components/ui/dropdown-menu.tsx +258 -0
  106. package/src/v2/components/ui/tooltip.tsx +60 -0
  107. package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +152 -0
  108. package/src/v2/hooks/__tests__/standard-schema.test.tsx +282 -0
  109. package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +132 -0
  110. package/src/v2/hooks/__tests__/use-agent-context.test.tsx +401 -0
  111. package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +44 -0
  112. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +205 -0
  113. package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +148 -0
  114. package/src/v2/hooks/__tests__/use-component.test.tsx +123 -0
  115. package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +696 -0
  116. package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +153 -0
  117. package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +167 -0
  118. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +2129 -0
  119. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +1261 -0
  120. package/src/v2/hooks/__tests__/use-interrupt.test.tsx +397 -0
  121. package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +56 -0
  122. package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +192 -0
  123. package/src/v2/hooks/__tests__/use-render-tool.test.tsx +259 -0
  124. package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +524 -0
  125. package/src/v2/hooks/__tests__/use-threads.test.tsx +433 -0
  126. package/src/v2/hooks/__tests__/zod-regression.test.tsx +311 -0
  127. package/src/v2/hooks/index.ts +18 -0
  128. package/src/v2/hooks/use-agent-context.tsx +45 -0
  129. package/src/v2/hooks/use-agent.tsx +155 -0
  130. package/src/v2/hooks/use-component.tsx +89 -0
  131. package/src/v2/hooks/use-configure-suggestions.tsx +187 -0
  132. package/src/v2/hooks/use-default-render-tool.tsx +254 -0
  133. package/src/v2/hooks/use-frontend-tool.tsx +43 -0
  134. package/src/v2/hooks/use-human-in-the-loop.tsx +81 -0
  135. package/src/v2/hooks/use-interrupt.tsx +305 -0
  136. package/src/v2/hooks/use-keyboard-height.tsx +67 -0
  137. package/src/v2/hooks/use-render-activity-message.tsx +73 -0
  138. package/src/v2/hooks/use-render-custom-messages.tsx +93 -0
  139. package/src/v2/hooks/use-render-tool-call.tsx +175 -0
  140. package/src/v2/hooks/use-render-tool.tsx +181 -0
  141. package/src/v2/hooks/use-suggestions.tsx +91 -0
  142. package/src/v2/hooks/use-threads.tsx +256 -0
  143. package/src/v2/hooks/useKatexStyles.ts +27 -0
  144. package/src/v2/index.css +1 -1
  145. package/src/v2/index.ts +18 -2
  146. package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +495 -0
  147. package/src/v2/lib/__tests__/renderSlot.test.tsx +588 -0
  148. package/src/v2/lib/react-core.ts +156 -0
  149. package/src/v2/lib/slots.tsx +143 -0
  150. package/src/v2/lib/transcription-client.ts +184 -0
  151. package/src/v2/lib/utils.ts +8 -0
  152. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +162 -0
  153. package/src/v2/providers/CopilotKitProvider.tsx +600 -0
  154. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +546 -0
  155. package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +101 -0
  156. package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +69 -0
  157. package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +881 -0
  158. package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +740 -0
  159. package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +642 -0
  160. package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +294 -0
  161. package/src/v2/providers/index.ts +14 -0
  162. package/src/v2/styles/globals.css +230 -0
  163. package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +525 -0
  164. package/src/v2/types/defineToolCallRenderer.ts +65 -0
  165. package/src/v2/types/frontend-tool.ts +8 -0
  166. package/src/v2/types/human-in-the-loop.ts +33 -0
  167. package/src/v2/types/index.ts +7 -0
  168. package/src/v2/types/interrupt.ts +15 -0
  169. package/src/v2/types/react-activity-message-renderer.ts +27 -0
  170. package/src/v2/types/react-custom-message-renderer.ts +17 -0
  171. package/src/v2/types/react-tool-call-renderer.ts +32 -0
  172. package/tsdown.config.ts +34 -10
  173. package/vitest.config.mjs +4 -3
  174. package/LICENSE +0 -21
  175. package/dist/copilotkit-BRPQ2sqS.d.cts +0 -670
  176. package/dist/copilotkit-BRPQ2sqS.d.cts.map +0 -1
  177. package/dist/copilotkit-C94ayZbs.cjs +0 -2161
  178. package/dist/copilotkit-C94ayZbs.cjs.map +0 -1
  179. package/dist/copilotkit-CwZMFmSK.d.mts +0 -670
  180. package/dist/copilotkit-CwZMFmSK.d.mts.map +0 -1
  181. package/dist/copilotkit-Yh_Ld_FX.mjs +0 -2031
  182. package/dist/copilotkit-Yh_Ld_FX.mjs.map +0 -1
  183. package/dist/v2/index.css.map +0 -1
@@ -0,0 +1,153 @@
1
+ import React from "react";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
+ import { describe, it, expect, beforeEach, vi } from "vitest";
4
+ import { useDefaultRenderTool } from "../use-default-render-tool";
5
+ import { useRenderTool } from "../use-render-tool";
6
+
7
+ vi.mock("../use-render-tool", () => ({
8
+ useRenderTool: vi.fn(),
9
+ }));
10
+
11
+ const mockUseRenderTool = useRenderTool as ReturnType<typeof vi.fn>;
12
+
13
+ describe("useDefaultRenderTool", () => {
14
+ beforeEach(() => {
15
+ vi.clearAllMocks();
16
+ });
17
+
18
+ it("registers a wildcard renderer when called without config", () => {
19
+ const Harness: React.FC = () => {
20
+ useDefaultRenderTool();
21
+ return null;
22
+ };
23
+
24
+ render(<Harness />);
25
+
26
+ expect(mockUseRenderTool).toHaveBeenCalledTimes(1);
27
+ const [config] = mockUseRenderTool.mock.calls[0] as [
28
+ {
29
+ name: string;
30
+ render: (props: {
31
+ name: string;
32
+ parameters: unknown;
33
+ status: string;
34
+ result: string | undefined;
35
+ }) => React.ReactElement;
36
+ },
37
+ ];
38
+
39
+ expect(config.name).toBe("*");
40
+ expect(typeof config.render).toBe("function");
41
+ });
42
+
43
+ it("forwards custom render function and deps", () => {
44
+ const customRender = vi.fn(() => <div data-testid="custom">custom</div>);
45
+ const deps = ["compact"] as const;
46
+
47
+ const Harness: React.FC = () => {
48
+ useDefaultRenderTool(
49
+ {
50
+ render: customRender,
51
+ },
52
+ deps,
53
+ );
54
+ return null;
55
+ };
56
+
57
+ render(<Harness />);
58
+
59
+ expect(mockUseRenderTool).toHaveBeenCalledTimes(1);
60
+ const [config, forwardedDeps] = mockUseRenderTool.mock.calls[0] as [
61
+ { name: string; render: typeof customRender },
62
+ ReadonlyArray<unknown>,
63
+ ];
64
+
65
+ expect(config.name).toBe("*");
66
+ expect(config.render).toBe(customRender);
67
+ expect(forwardedDeps).toBe(deps);
68
+ });
69
+
70
+ it("default renderer shows status and expands to show parameters/result", () => {
71
+ const Harness: React.FC = () => {
72
+ useDefaultRenderTool();
73
+ return null;
74
+ };
75
+
76
+ render(<Harness />);
77
+
78
+ const [config] = mockUseRenderTool.mock.calls[0] as [
79
+ {
80
+ render: (props: {
81
+ name: string;
82
+ parameters: unknown;
83
+ status: string;
84
+ result: string | undefined;
85
+ }) => React.ReactElement;
86
+ },
87
+ ];
88
+
89
+ const DefaultRenderer = config.render as React.ComponentType<{
90
+ name: string;
91
+ parameters: unknown;
92
+ status: string;
93
+ result: string | undefined;
94
+ }>;
95
+
96
+ render(
97
+ <DefaultRenderer
98
+ name="searchDocs"
99
+ parameters={{ query: "copilot" }}
100
+ status="executing"
101
+ result={undefined}
102
+ />,
103
+ );
104
+
105
+ expect(screen.getByText("searchDocs")).toBeDefined();
106
+ expect(screen.getByText("Running")).toBeDefined();
107
+
108
+ fireEvent.click(screen.getByText("searchDocs"));
109
+ expect(screen.getByText("Arguments")).toBeDefined();
110
+ expect(screen.getByText(/copilot/)).toBeDefined();
111
+ });
112
+
113
+ it("default renderer shows done status and result payload", () => {
114
+ const Harness: React.FC = () => {
115
+ useDefaultRenderTool();
116
+ return null;
117
+ };
118
+
119
+ render(<Harness />);
120
+
121
+ const [config] = mockUseRenderTool.mock.calls[0] as [
122
+ {
123
+ render: (props: {
124
+ name: string;
125
+ parameters: unknown;
126
+ status: string;
127
+ result: string | undefined;
128
+ }) => React.ReactElement;
129
+ },
130
+ ];
131
+
132
+ const DefaultRenderer = config.render as React.ComponentType<{
133
+ name: string;
134
+ parameters: unknown;
135
+ status: string;
136
+ result: string | undefined;
137
+ }>;
138
+
139
+ render(
140
+ <DefaultRenderer
141
+ name="searchDocs"
142
+ parameters={{ query: "copilot" }}
143
+ status="complete"
144
+ result="done"
145
+ />,
146
+ );
147
+
148
+ expect(screen.getByText("Done")).toBeDefined();
149
+ fireEvent.click(screen.getByText("searchDocs"));
150
+ expect(screen.getByText("Result")).toBeDefined();
151
+ expect(screen.getByText("done")).toBeDefined();
152
+ });
153
+ });
@@ -0,0 +1,167 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { screen, fireEvent, waitFor } from "@testing-library/react";
3
+ import { z } from "zod";
4
+ import { useFrontendTool } from "../use-frontend-tool";
5
+ import { useCopilotKit } from "../../providers/CopilotKitProvider";
6
+ import { ReactFrontendTool } from "../../types";
7
+ import { CopilotKitCoreReact } from "../../lib/react-core";
8
+ import { renderWithCopilotKit } from "../../__tests__/utils/test-helpers";
9
+
10
+ /**
11
+ * Component that captures the copilotkit core ref for test assertions.
12
+ */
13
+ const CoreCapture: React.FC<{
14
+ onCore: (core: CopilotKitCoreReact) => void;
15
+ }> = ({ onCore }) => {
16
+ const { copilotkit } = useCopilotKit();
17
+ useEffect(() => {
18
+ onCore(copilotkit);
19
+ }, [copilotkit, onCore]);
20
+ return null;
21
+ };
22
+
23
+ describe("useFrontendTool available flag", () => {
24
+ it("registers tool with available: false on the core", async () => {
25
+ let coreRef: CopilotKitCoreReact | null = null;
26
+
27
+ const ToolComponent: React.FC = () => {
28
+ const tool: ReactFrontendTool<{ msg: string }> = {
29
+ name: "disabledTool",
30
+ description: "A disabled tool",
31
+ available: false,
32
+ parameters: z.object({ msg: z.string() }),
33
+ handler: async () => ({ result: "ok" }),
34
+ };
35
+ useFrontendTool(tool);
36
+ return null;
37
+ };
38
+
39
+ const ui = renderWithCopilotKit({
40
+ children: (
41
+ <>
42
+ <ToolComponent />
43
+ <CoreCapture
44
+ onCore={(c) => {
45
+ coreRef = c;
46
+ }}
47
+ />
48
+ </>
49
+ ),
50
+ });
51
+
52
+ await waitFor(() => {
53
+ expect(coreRef).not.toBeNull();
54
+ const tool = coreRef!.tools.find((t) => t.name === "disabledTool");
55
+ expect(tool).toBeDefined();
56
+ expect(tool!.available).toBe(false);
57
+ });
58
+
59
+ ui.unmount();
60
+ });
61
+
62
+ it("registers tool with available: true on the core", async () => {
63
+ let coreRef: CopilotKitCoreReact | null = null;
64
+
65
+ const ToolComponent: React.FC = () => {
66
+ const tool: ReactFrontendTool<{ msg: string }> = {
67
+ name: "enabledTool",
68
+ description: "An enabled tool",
69
+ available: true,
70
+ parameters: z.object({ msg: z.string() }),
71
+ handler: async () => ({ result: "ok" }),
72
+ };
73
+ useFrontendTool(tool);
74
+ return null;
75
+ };
76
+
77
+ const ui = renderWithCopilotKit({
78
+ children: (
79
+ <>
80
+ <ToolComponent />
81
+ <CoreCapture
82
+ onCore={(c) => {
83
+ coreRef = c;
84
+ }}
85
+ />
86
+ </>
87
+ ),
88
+ });
89
+
90
+ await waitFor(() => {
91
+ expect(coreRef).not.toBeNull();
92
+ const tool = coreRef!.tools.find((t) => t.name === "enabledTool");
93
+ expect(tool).toBeDefined();
94
+ expect(tool!.available).toBe(true);
95
+ });
96
+
97
+ ui.unmount();
98
+ });
99
+
100
+ it("re-registers tool when available toggles between true and false", async () => {
101
+ let coreRef: CopilotKitCoreReact | null = null;
102
+
103
+ const ToolWithToggle: React.FC = () => {
104
+ const [isEnabled, setIsEnabled] = useState(true);
105
+
106
+ const tool: ReactFrontendTool<{ data: string }> = {
107
+ name: "toggleTool",
108
+ description: "A toggleable tool",
109
+ available: isEnabled,
110
+ parameters: z.object({ data: z.string() }),
111
+ handler: async () => ({ ok: true }),
112
+ };
113
+ useFrontendTool(tool, [isEnabled]);
114
+
115
+ return (
116
+ <button
117
+ data-testid="toggle-btn"
118
+ onClick={() => setIsEnabled((prev) => !prev)}
119
+ >
120
+ {isEnabled ? "Disable" : "Enable"}
121
+ </button>
122
+ );
123
+ };
124
+
125
+ const ui = renderWithCopilotKit({
126
+ children: (
127
+ <>
128
+ <ToolWithToggle />
129
+ <CoreCapture
130
+ onCore={(c) => {
131
+ coreRef = c;
132
+ }}
133
+ />
134
+ </>
135
+ ),
136
+ });
137
+
138
+ // Tool should be registered as enabled initially
139
+ await waitFor(() => {
140
+ expect(coreRef).not.toBeNull();
141
+ const tool = coreRef!.tools.find((t) => t.name === "toggleTool");
142
+ expect(tool).toBeDefined();
143
+ expect(tool!.available).toBe(true);
144
+ });
145
+
146
+ // Toggle to disabled
147
+ fireEvent.click(screen.getByTestId("toggle-btn"));
148
+
149
+ // Tool should be re-registered as disabled
150
+ await waitFor(() => {
151
+ const tool = coreRef!.tools.find((t) => t.name === "toggleTool");
152
+ expect(tool).toBeDefined();
153
+ expect(tool!.available).toBe(false);
154
+ });
155
+
156
+ // Toggle back to enabled
157
+ fireEvent.click(screen.getByTestId("toggle-btn"));
158
+
159
+ await waitFor(() => {
160
+ const tool = coreRef!.tools.find((t) => t.name === "toggleTool");
161
+ expect(tool).toBeDefined();
162
+ expect(tool!.available).toBe(true);
163
+ });
164
+
165
+ ui.unmount();
166
+ });
167
+ });