@elizaos/client 1.5.5-alpha.10

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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/dist/assets/empty-module-CLMscLYw.js +1 -0
  4. package/dist/assets/main-BBZ_3lkn.css +5999 -0
  5. package/dist/assets/main-C5zNUkXH.js +7 -0
  6. package/dist/assets/main-Dz64ENQg.js +614 -0
  7. package/dist/assets/react-vendor-DM5m98rr.js +545 -0
  8. package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
  9. package/dist/elizaos-avatar.png +0 -0
  10. package/dist/elizaos-icon.png +0 -0
  11. package/dist/elizaos-logo-light.png +0 -0
  12. package/dist/elizaos.webp +0 -0
  13. package/dist/favicon.ico +0 -0
  14. package/dist/images/agents/agent1.png +0 -0
  15. package/dist/images/agents/agent2.png +0 -0
  16. package/dist/images/agents/agent3.png +0 -0
  17. package/dist/images/agents/agent4.png +0 -0
  18. package/dist/images/agents/agent5.png +0 -0
  19. package/dist/index.html +14 -0
  20. package/index.html +24 -0
  21. package/package.json +159 -0
  22. package/postcss.config.js +3 -0
  23. package/public/elizaos-avatar.png +0 -0
  24. package/public/elizaos-icon.png +0 -0
  25. package/public/elizaos-logo-light.png +0 -0
  26. package/public/elizaos.webp +0 -0
  27. package/public/favicon.ico +0 -0
  28. package/public/images/agents/agent1.png +0 -0
  29. package/public/images/agents/agent2.png +0 -0
  30. package/public/images/agents/agent3.png +0 -0
  31. package/public/images/agents/agent4.png +0 -0
  32. package/public/images/agents/agent5.png +0 -0
  33. package/src/App.tsx +222 -0
  34. package/src/components/AgentDetailsPanel.tsx +147 -0
  35. package/src/components/ChatInputArea.tsx +196 -0
  36. package/src/components/ChatMessageListComponent.tsx +139 -0
  37. package/src/components/actionTool.tsx +186 -0
  38. package/src/components/add-agent-card.tsx +77 -0
  39. package/src/components/agent-action-viewer.tsx +816 -0
  40. package/src/components/agent-avatar-stack.tsx +121 -0
  41. package/src/components/agent-card.cy.tsx +259 -0
  42. package/src/components/agent-card.tsx +177 -0
  43. package/src/components/agent-creator.tsx +142 -0
  44. package/src/components/agent-log-viewer.tsx +645 -0
  45. package/src/components/agent-memory-edit-overlay.tsx +461 -0
  46. package/src/components/agent-memory-viewer.tsx +504 -0
  47. package/src/components/agent-settings.tsx +270 -0
  48. package/src/components/agent-sidebar.tsx +178 -0
  49. package/src/components/api-key-dialog.tsx +113 -0
  50. package/src/components/app-sidebar.tsx +685 -0
  51. package/src/components/array-input.tsx +116 -0
  52. package/src/components/audio-recorder.tsx +292 -0
  53. package/src/components/avatar-panel.tsx +141 -0
  54. package/src/components/character-form.tsx +1138 -0
  55. package/src/components/chat.tsx +1813 -0
  56. package/src/components/combobox.tsx +187 -0
  57. package/src/components/confirmation-dialog.tsx +59 -0
  58. package/src/components/connection-error-banner.tsx +101 -0
  59. package/src/components/connection-status.cy.tsx +73 -0
  60. package/src/components/connection-status.tsx +155 -0
  61. package/src/components/copy-button.tsx +35 -0
  62. package/src/components/delete-button.tsx +24 -0
  63. package/src/components/env-settings.tsx +261 -0
  64. package/src/components/group-card.tsx +160 -0
  65. package/src/components/group-panel.tsx +543 -0
  66. package/src/components/input-copy.tsx +21 -0
  67. package/src/components/logs-page.tsx +41 -0
  68. package/src/components/media-content.tsx +385 -0
  69. package/src/components/memory-graph.tsx +170 -0
  70. package/src/components/missing-secrets-dialog.tsx +72 -0
  71. package/src/components/onboarding-tour.tsx +247 -0
  72. package/src/components/page-title.tsx +8 -0
  73. package/src/components/plugins-panel.tsx +383 -0
  74. package/src/components/profile-card.tsx +66 -0
  75. package/src/components/profile-overlay.tsx +283 -0
  76. package/src/components/retry-button.tsx +28 -0
  77. package/src/components/secret-panel.tsx +1505 -0
  78. package/src/components/server-management.tsx +264 -0
  79. package/src/components/split-button.tsx +148 -0
  80. package/src/components/stop-agent-button.tsx +99 -0
  81. package/src/components/ui/alert-dialog.cy.tsx +333 -0
  82. package/src/components/ui/alert-dialog.tsx +115 -0
  83. package/src/components/ui/alert.tsx +49 -0
  84. package/src/components/ui/avatar.cy.tsx +180 -0
  85. package/src/components/ui/avatar.tsx +57 -0
  86. package/src/components/ui/badge.cy.tsx +146 -0
  87. package/src/components/ui/badge.tsx +43 -0
  88. package/src/components/ui/button.cy.tsx +177 -0
  89. package/src/components/ui/button.tsx +56 -0
  90. package/src/components/ui/card.cy.tsx +160 -0
  91. package/src/components/ui/card.tsx +73 -0
  92. package/src/components/ui/chat/animated-markdown.tsx +59 -0
  93. package/src/components/ui/chat/chat-bubble.tsx +178 -0
  94. package/src/components/ui/chat/chat-container.tsx +51 -0
  95. package/src/components/ui/chat/chat-input.cy.tsx +169 -0
  96. package/src/components/ui/chat/chat-input.tsx +47 -0
  97. package/src/components/ui/chat/chat-message-list.tsx +61 -0
  98. package/src/components/ui/chat/chat-tts-button.tsx +199 -0
  99. package/src/components/ui/chat/code-block.tsx +79 -0
  100. package/src/components/ui/chat/expandable-chat.tsx +131 -0
  101. package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
  102. package/src/components/ui/chat/markdown.tsx +209 -0
  103. package/src/components/ui/chat/message-loading.tsx +48 -0
  104. package/src/components/ui/checkbox.cy.tsx +170 -0
  105. package/src/components/ui/checkbox.tsx +30 -0
  106. package/src/components/ui/collapsible.cy.tsx +283 -0
  107. package/src/components/ui/collapsible.tsx +9 -0
  108. package/src/components/ui/command.cy.tsx +313 -0
  109. package/src/components/ui/command.tsx +143 -0
  110. package/src/components/ui/dialog.cy.tsx +279 -0
  111. package/src/components/ui/dialog.tsx +104 -0
  112. package/src/components/ui/dropdown-menu.cy.tsx +273 -0
  113. package/src/components/ui/dropdown-menu.tsx +281 -0
  114. package/src/components/ui/input.cy.tsx +82 -0
  115. package/src/components/ui/input.tsx +27 -0
  116. package/src/components/ui/label.cy.tsx +157 -0
  117. package/src/components/ui/label.tsx +19 -0
  118. package/src/components/ui/resizable.tsx +42 -0
  119. package/src/components/ui/scroll-area.cy.tsx +242 -0
  120. package/src/components/ui/scroll-area.tsx +46 -0
  121. package/src/components/ui/select.cy.tsx +277 -0
  122. package/src/components/ui/select.tsx +155 -0
  123. package/src/components/ui/separator.cy.tsx +145 -0
  124. package/src/components/ui/separator.tsx +29 -0
  125. package/src/components/ui/sheet.cy.tsx +324 -0
  126. package/src/components/ui/sheet.tsx +119 -0
  127. package/src/components/ui/sidebar.tsx +734 -0
  128. package/src/components/ui/skeleton.cy.tsx +149 -0
  129. package/src/components/ui/skeleton.tsx +17 -0
  130. package/src/components/ui/split-button.cy.tsx +274 -0
  131. package/src/components/ui/split-button.tsx +112 -0
  132. package/src/components/ui/switch.tsx +28 -0
  133. package/src/components/ui/tabs.cy.tsx +271 -0
  134. package/src/components/ui/tabs.tsx +53 -0
  135. package/src/components/ui/textarea.cy.tsx +136 -0
  136. package/src/components/ui/textarea.tsx +26 -0
  137. package/src/components/ui/toast.cy.tsx +209 -0
  138. package/src/components/ui/toast.tsx +126 -0
  139. package/src/components/ui/toaster.tsx +29 -0
  140. package/src/components/ui/tooltip.cy.tsx +244 -0
  141. package/src/components/ui/tooltip.tsx +30 -0
  142. package/src/config/agent-templates.ts +349 -0
  143. package/src/config/voice-models.ts +181 -0
  144. package/src/constants.ts +23 -0
  145. package/src/context/AuthContext.tsx +44 -0
  146. package/src/context/ConnectionContext.tsx +194 -0
  147. package/src/entry.tsx +9 -0
  148. package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
  149. package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
  150. package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
  151. package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
  152. package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
  153. package/src/hooks/use-agent-management.ts +130 -0
  154. package/src/hooks/use-agent-tab-state.ts +74 -0
  155. package/src/hooks/use-agent-update.ts +469 -0
  156. package/src/hooks/use-character-convert.ts +138 -0
  157. package/src/hooks/use-confirmation.ts +55 -0
  158. package/src/hooks/use-delete-agent.ts +123 -0
  159. package/src/hooks/use-dm-channels.ts +198 -0
  160. package/src/hooks/use-elevenlabs-voices.ts +83 -0
  161. package/src/hooks/use-file-upload.ts +224 -0
  162. package/src/hooks/use-mobile.tsx +19 -0
  163. package/src/hooks/use-onboarding.tsx +49 -0
  164. package/src/hooks/use-panel-width-state.ts +147 -0
  165. package/src/hooks/use-partial-update.ts +288 -0
  166. package/src/hooks/use-plugin-details.ts +462 -0
  167. package/src/hooks/use-plugins.ts +119 -0
  168. package/src/hooks/use-query-hooks.ts +1263 -0
  169. package/src/hooks/use-server-agents.ts +62 -0
  170. package/src/hooks/use-server-version.tsx +47 -0
  171. package/src/hooks/use-sidebar-state.ts +50 -0
  172. package/src/hooks/use-socket-chat.ts +264 -0
  173. package/src/hooks/use-toast.ts +260 -0
  174. package/src/hooks/use-version.tsx +64 -0
  175. package/src/index.css +146 -0
  176. package/src/lib/api-client-config.ts +53 -0
  177. package/src/lib/api-type-mappers.ts +196 -0
  178. package/src/lib/export-utils.ts +123 -0
  179. package/src/lib/logger.ts +19 -0
  180. package/src/lib/media-utils.ts +170 -0
  181. package/src/lib/pca.test.ts +17 -0
  182. package/src/lib/pca.ts +52 -0
  183. package/src/lib/socketio-manager.ts +664 -0
  184. package/src/lib/utils.ts +168 -0
  185. package/src/main.tsx +16 -0
  186. package/src/mocks/empty-module.ts +12 -0
  187. package/src/mocks/node-module.ts +57 -0
  188. package/src/polyfills.ts +37 -0
  189. package/src/routes/agent-detail.tsx +30 -0
  190. package/src/routes/agent-list.tsx +27 -0
  191. package/src/routes/agent-settings.tsx +48 -0
  192. package/src/routes/character-detail.tsx +52 -0
  193. package/src/routes/character-form.tsx +79 -0
  194. package/src/routes/character-list.tsx +38 -0
  195. package/src/routes/chat.tsx +128 -0
  196. package/src/routes/createAgent.tsx +13 -0
  197. package/src/routes/group-new.tsx +50 -0
  198. package/src/routes/group.tsx +29 -0
  199. package/src/routes/home.tsx +218 -0
  200. package/src/routes/not-found.tsx +71 -0
  201. package/src/test/setup.ts +154 -0
  202. package/src/types/crypto-browserify.d.ts +4 -0
  203. package/src/types/index.ts +13 -0
  204. package/src/types/rooms.ts +8 -0
  205. package/src/types.ts +84 -0
  206. package/src/vite-env.d.ts +40 -0
  207. package/tailwind.config.ts +90 -0
  208. package/tsconfig.json +10 -0
  209. package/vite.config.ts +102 -0
@@ -0,0 +1,194 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ ReactNode,
6
+ useEffect,
7
+ useRef,
8
+ useCallback,
9
+ } from 'react';
10
+ import { useToast } from '@/hooks/use-toast';
11
+ import SocketIOManager from '@/lib/socketio-manager';
12
+ import { updateApiClientApiKey } from '@/lib/api-client-config';
13
+ // Eliza client refresh functionality removed (not needed with direct client)
14
+
15
+ export const connectionStatusActions = {
16
+ setUnauthorized: (message: string) => {
17
+ console.warn('setUnauthorized called before ConnectionContext is ready', message);
18
+ },
19
+ setOfflineStatus: (isOffline: boolean) => {
20
+ console.warn('setOfflineStatus called before ConnectionContext is ready', isOffline);
21
+ },
22
+ };
23
+
24
+ export type ConnectionStatusType =
25
+ | 'loading'
26
+ | 'connected'
27
+ | 'reconnecting'
28
+ | 'error'
29
+ | 'unauthorized';
30
+
31
+ interface ConnectionContextType {
32
+ status: ConnectionStatusType;
33
+ error: string | null;
34
+ setUnauthorizedFromApi: (message: string) => void;
35
+ setOfflineStatusFromProvider: (isOffline: boolean) => void;
36
+ refreshApiClient: (newApiKey?: string | null) => void;
37
+ }
38
+
39
+ const ConnectionContext = createContext<ConnectionContextType | undefined>(undefined);
40
+
41
+ export const ConnectionProvider = ({ children }: { children: ReactNode }) => {
42
+ const { toast } = useToast();
43
+ const [status, setStatus] = useState<ConnectionStatusType>('loading');
44
+ const [error, setError] = useState<string | null>(null);
45
+ const isFirstConnect = useRef(true);
46
+ const socketManager = SocketIOManager.getInstance();
47
+
48
+ const setUnauthorizedFromApi = useCallback(
49
+ (message: string) => {
50
+ setStatus('unauthorized');
51
+ setError(message);
52
+ toast({
53
+ title: 'Authorization Required',
54
+ description: message || 'Please provide a valid API Key.',
55
+ variant: 'destructive',
56
+ });
57
+ },
58
+ [toast]
59
+ );
60
+
61
+ const setOfflineStatusFromProvider = useCallback(
62
+ (isOffline: boolean) => {
63
+ if (isOffline) {
64
+ if (status !== 'error' && status !== 'unauthorized') {
65
+ setStatus('error');
66
+ setError('Network connection appears to be offline.');
67
+ toast({
68
+ title: 'Network Offline',
69
+ description: 'Please check your internet connection.',
70
+ variant: 'destructive',
71
+ });
72
+ }
73
+ } else {
74
+ if (status === 'error' && error?.includes('offline')) {
75
+ }
76
+ }
77
+ },
78
+ [status, error, toast]
79
+ );
80
+
81
+ const refreshApiClient = useCallback((newApiKey?: string | null) => {
82
+ try {
83
+ // Update localStorage if a new API key is provided
84
+ if (newApiKey !== undefined) {
85
+ updateApiClientApiKey(newApiKey);
86
+ }
87
+
88
+ // Refresh the ElizaClient instance with new configuration
89
+ // Client refresh not needed with direct client pattern
90
+
91
+ console.log('API client refreshed with new configuration');
92
+ } catch (error) {
93
+ console.error('Failed to refresh API client:', error);
94
+ }
95
+ }, []);
96
+
97
+ useEffect(() => {
98
+ connectionStatusActions.setUnauthorized = setUnauthorizedFromApi;
99
+ connectionStatusActions.setOfflineStatus = setOfflineStatusFromProvider;
100
+ }, [setUnauthorizedFromApi, setOfflineStatusFromProvider]);
101
+
102
+ useEffect(() => {
103
+ const onConnect = () => {
104
+ setStatus('connected');
105
+ setError(null);
106
+ if (connectionStatusActions.setOfflineStatus) {
107
+ connectionStatusActions.setOfflineStatus(false);
108
+ }
109
+
110
+ if (isFirstConnect.current) {
111
+ isFirstConnect.current = false;
112
+ } else {
113
+ toast({
114
+ title: 'Connection Restored',
115
+ description: 'Successfully reconnected to the Eliza server.',
116
+ });
117
+ }
118
+ };
119
+
120
+ const onDisconnect = (reason: string) => {
121
+ setStatus('error');
122
+ setError(`Connection lost: ${reason}`);
123
+ if (connectionStatusActions.setOfflineStatus) {
124
+ connectionStatusActions.setOfflineStatus(true);
125
+ }
126
+ toast({
127
+ title: 'Connection Lost',
128
+ description: 'Attempting to reconnect to the Eliza server…',
129
+ variant: 'destructive',
130
+ });
131
+ };
132
+
133
+ const onReconnectAttempt = () => {
134
+ setStatus('reconnecting');
135
+ setError('Reconnecting...');
136
+ };
137
+
138
+ const onConnectError = (err: Error) => {
139
+ setStatus('error');
140
+ setError(err.message);
141
+ if (connectionStatusActions.setOfflineStatus) {
142
+ connectionStatusActions.setOfflineStatus(true);
143
+ }
144
+ };
145
+
146
+ const onUnauthorized = (reason: string) => {
147
+ setStatus('unauthorized');
148
+ setError(`Unauthorized: ${reason}`);
149
+ toast({ title: 'Unauthorized', description: 'Please log in again.', variant: 'destructive' });
150
+ };
151
+
152
+ socketManager.on('connect', onConnect);
153
+ socketManager.on('disconnect', onDisconnect);
154
+ socketManager.on('reconnect', onConnect);
155
+ socketManager.on('reconnect_attempt', onReconnectAttempt);
156
+ socketManager.on('connect_error', onConnectError);
157
+ socketManager.on('unauthorized', onUnauthorized);
158
+
159
+ if (SocketIOManager.isConnected()) {
160
+ onConnect();
161
+ }
162
+
163
+ return () => {
164
+ socketManager.off('connect', onConnect);
165
+ socketManager.off('disconnect', onDisconnect);
166
+ socketManager.off('reconnect', onConnect);
167
+ socketManager.off('reconnect_attempt', onReconnectAttempt);
168
+ socketManager.off('connect_error', onConnectError);
169
+ socketManager.off('unauthorized', onUnauthorized);
170
+ };
171
+ }, [toast, socketManager, setOfflineStatusFromProvider]);
172
+
173
+ return (
174
+ <ConnectionContext.Provider
175
+ value={{
176
+ status,
177
+ error,
178
+ setUnauthorizedFromApi,
179
+ setOfflineStatusFromProvider,
180
+ refreshApiClient,
181
+ }}
182
+ >
183
+ {children}
184
+ </ConnectionContext.Provider>
185
+ );
186
+ };
187
+
188
+ export const useConnection = () => {
189
+ const ctx = useContext(ConnectionContext);
190
+ if (!ctx) {
191
+ throw new Error('useConnection must be inside ConnectionProvider');
192
+ }
193
+ return ctx;
194
+ };
package/src/entry.tsx ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Entry point that ensures polyfills are loaded before anything else
3
+ * Use static imports so Vite HMR can properly track the graph.
4
+ */
5
+
6
+ import './polyfills';
7
+ import('./main');
8
+
9
+ export {};
@@ -0,0 +1,137 @@
1
+ import { renderHook, act, waitFor } from '@testing-library/react';
2
+ import { mock, describe, it, expect, beforeEach } from 'bun:test';
3
+ import { useAgentTabState } from '../use-agent-tab-state';
4
+
5
+ // Mock clientLogger
6
+ mock.module('../../lib/logger', () => ({
7
+ default: {
8
+ error: mock(),
9
+ },
10
+ }));
11
+
12
+ describe('useAgentTabState', () => {
13
+ beforeEach(() => {
14
+ // Clear localStorage before each test
15
+ localStorage.clear();
16
+ });
17
+
18
+ it('should initialize with details tab when no agentId is provided', () => {
19
+ const { result } = renderHook(() => useAgentTabState(undefined));
20
+
21
+ expect(result.current.currentTab).toBe('details');
22
+ });
23
+
24
+ it('should initialize with details tab for new agent', () => {
25
+ const agentId = '550e8400-e29b-41d4-a716-446655440000';
26
+ localStorage.setItem('eliza-agent-tab-states', '{}');
27
+
28
+ const { result } = renderHook(() => useAgentTabState(agentId));
29
+
30
+ expect(result.current.currentTab).toBe('details');
31
+ });
32
+
33
+ it('should load saved tab state for existing agent', async () => {
34
+ const agentId = '550e8400-e29b-41d4-a716-446655440000';
35
+ const savedStates = {
36
+ '550e8400-e29b-41d4-a716-446655440000': 'memories',
37
+ };
38
+ localStorage.setItem('eliza-agent-tab-states', JSON.stringify(savedStates));
39
+
40
+ const { result } = renderHook(() => useAgentTabState(agentId));
41
+
42
+ await waitFor(() => {
43
+ expect(result.current.currentTab).toBe('memories');
44
+ });
45
+ });
46
+
47
+ it('should save tab state when changed', async () => {
48
+ const agentId = '550e8400-e29b-41d4-a716-446655440000';
49
+ localStorage.setItem('eliza-agent-tab-states', '{}');
50
+
51
+ const { result } = renderHook(() => useAgentTabState(agentId));
52
+
53
+ act(() => {
54
+ result.current.setTab('actions');
55
+ });
56
+
57
+ await waitFor(() => {
58
+ expect(result.current.currentTab).toBe('actions');
59
+ });
60
+
61
+ expect(localStorage.getItem('eliza-agent-tab-states')).toBe(
62
+ JSON.stringify({ '550e8400-e29b-41d4-a716-446655440000': 'actions' })
63
+ );
64
+ });
65
+
66
+ it('should preserve other agent states when updating', () => {
67
+ const agentId = '550e8400-e29b-41d4-a716-446655440001';
68
+ const existingStates = {
69
+ '550e8400-e29b-41d4-a716-446655440000': 'memories',
70
+ };
71
+ localStorage.setItem('eliza-agent-tab-states', JSON.stringify(existingStates));
72
+
73
+ const { result } = renderHook(() => useAgentTabState(agentId));
74
+
75
+ act(() => {
76
+ result.current.setTab('logs');
77
+ });
78
+
79
+ expect(localStorage.getItem('eliza-agent-tab-states')).toBe(
80
+ JSON.stringify({
81
+ '550e8400-e29b-41d4-a716-446655440000': 'memories',
82
+ '550e8400-e29b-41d4-a716-446655440001': 'logs',
83
+ })
84
+ );
85
+ });
86
+
87
+ it('should handle localStorage errors gracefully', () => {
88
+ const agentId = '550e8400-e29b-41d4-a716-446655440000';
89
+ const originalGetItem = localStorage.getItem;
90
+ localStorage.getItem = () => {
91
+ throw new Error('localStorage not available');
92
+ };
93
+
94
+ const { result } = renderHook(() => useAgentTabState(agentId));
95
+
96
+ expect(result.current.currentTab).toBe('details');
97
+
98
+ // Restore original getItem
99
+ localStorage.getItem = originalGetItem;
100
+ });
101
+
102
+ it('should switch tab state when agentId changes', async () => {
103
+ const savedStates = {
104
+ '550e8400-e29b-41d4-a716-446655440000': 'memories',
105
+ '550e8400-e29b-41d4-a716-446655440001': 'actions',
106
+ };
107
+ localStorage.setItem('eliza-agent-tab-states', JSON.stringify(savedStates));
108
+
109
+ const { result, rerender } = renderHook(({ agentId }) => useAgentTabState(agentId), {
110
+ initialProps: { agentId: '550e8400-e29b-41d4-a716-446655440000' as any },
111
+ });
112
+
113
+ await waitFor(() => {
114
+ expect(result.current.currentTab).toBe('memories');
115
+ });
116
+
117
+ rerender({ agentId: '550e8400-e29b-41d4-a716-446655440001' as any });
118
+
119
+ await waitFor(() => {
120
+ expect(result.current.currentTab).toBe('actions');
121
+ });
122
+ });
123
+
124
+ it('should not save to localStorage when no agentId is provided', async () => {
125
+ const { result } = renderHook(() => useAgentTabState(undefined));
126
+
127
+ act(() => {
128
+ result.current.setTab('actions');
129
+ });
130
+
131
+ await waitFor(() => {
132
+ expect(result.current.currentTab).toBe('actions');
133
+ });
134
+
135
+ expect(localStorage.getItem('eliza-agent-tab-states')).toBe(null);
136
+ });
137
+ });
@@ -0,0 +1,250 @@
1
+ import { useAgentUpdate } from '../use-agent-update';
2
+ import { describe, test, expect, mock, beforeEach, afterEach } from 'bun:test';
3
+ import { renderHook } from '@testing-library/react';
4
+
5
+ // Mock the usePartialUpdate hook instead of React core hooks
6
+ mock.module('../use-partial-update', () => ({
7
+ usePartialUpdate: (initialValue: any) => {
8
+ let currentValue = { ...initialValue };
9
+
10
+ const updateFieldMock = mock((path: string, value: any) => {
11
+ // Simple implementation to track updates
12
+ const pathParts = path.split('.');
13
+
14
+ if (pathParts.length === 1) {
15
+ currentValue[path] = value;
16
+ } else {
17
+ // Handle nested paths (simplified)
18
+ const [first, ...rest] = pathParts;
19
+ if (!currentValue[first]) currentValue[first] = {};
20
+
21
+ // Very simple implementation - doesn't handle complex nested paths
22
+ let target = currentValue[first];
23
+ for (let i = 0; i < rest.length - 1; i++) {
24
+ if (!target[rest[i]]) target[rest[i]] = {};
25
+ target = target[rest[i]];
26
+ }
27
+ target[rest[rest.length - 1]] = value;
28
+ }
29
+ });
30
+
31
+ const addArrayItemMock = mock((path: string, item: any) => {
32
+ const pathParts = path.split('.');
33
+ if (pathParts.length === 1) {
34
+ if (!Array.isArray(currentValue[path])) {
35
+ currentValue[path] = [];
36
+ }
37
+ currentValue[path].push(item);
38
+ }
39
+ });
40
+
41
+ const removeArrayItemMock = mock();
42
+ const resetMock = mock();
43
+
44
+ const updateSettingsMock = mock((settings: any) => {
45
+ currentValue.settings = { ...currentValue.settings, ...settings };
46
+ });
47
+
48
+ return {
49
+ value: currentValue,
50
+ updateField: updateFieldMock,
51
+ addArrayItem: addArrayItemMock,
52
+ removeArrayItem: removeArrayItemMock,
53
+ reset: resetMock,
54
+ updateSettings: updateSettingsMock,
55
+ };
56
+ },
57
+ }));
58
+
59
+ // Define a simplified Agent type for our tests
60
+ type MockAgent = {
61
+ name: string;
62
+ username: string;
63
+ system: string;
64
+ bio: string[];
65
+ topics: string[];
66
+ adjectives: string[];
67
+ plugins: string[];
68
+ style: {
69
+ all: string[];
70
+ chat: string[];
71
+ post: string[];
72
+ };
73
+ settings: {
74
+ avatar?: string;
75
+ voice?: {
76
+ model?: string;
77
+ settings?: Record<string, any>;
78
+ };
79
+ secrets?: Record<string, string>;
80
+ [key: string]: any;
81
+ };
82
+ [key: string]: any;
83
+ };
84
+
85
+ describe('useAgentUpdate hook', () => {
86
+ beforeEach(() => {
87
+ // Clear all mocks before each test
88
+ mock.restore();
89
+ });
90
+
91
+ afterEach(() => {
92
+ // Clean up after each test
93
+ mock.restore();
94
+ });
95
+
96
+ test('importAgent should call the appropriate update functions for all template fields', () => {
97
+ // Create initial and template agents
98
+ const initialAgent: MockAgent = {
99
+ name: 'Initial Agent',
100
+ username: 'initial_agent',
101
+ system: 'Initial system prompt',
102
+ bio: ['Initial bio'],
103
+ topics: ['Initial topic'],
104
+ adjectives: ['Initial adjective'],
105
+ plugins: ['initial-plugin'],
106
+ style: {
107
+ all: ['Initial style all'],
108
+ chat: ['Initial style chat'],
109
+ post: ['Initial style post'],
110
+ },
111
+ settings: {
112
+ avatar: 'initial-avatar.png',
113
+ voice: {
114
+ model: 'initial-voice-model',
115
+ },
116
+ secrets: {
117
+ INITIAL_API_KEY: 'initial-key-value',
118
+ },
119
+ },
120
+ };
121
+
122
+ const templateAgent: MockAgent = {
123
+ name: 'Template Agent',
124
+ username: 'template_agent',
125
+ system: 'Template system prompt',
126
+ bio: ['Template bio 1', 'Template bio 2'],
127
+ topics: ['Template topic 1', 'Template topic 2'],
128
+ adjectives: ['Template adjective 1', 'Template adjective 2'],
129
+ plugins: ['template-plugin-1', 'template-plugin-2'],
130
+ style: {
131
+ all: ['Template style all 1', 'Template style all 2'],
132
+ chat: ['Template style chat 1', 'Template style chat 2'],
133
+ post: ['Template style post 1', 'Template style post 2'],
134
+ },
135
+ settings: {
136
+ avatar: 'template-avatar.png',
137
+ voice: {
138
+ model: 'template-voice-model',
139
+ },
140
+ secrets: {
141
+ TEMPLATE_API_KEY: 'template-key-value',
142
+ },
143
+ newSetting: 'new-template-value',
144
+ },
145
+ extraField: 'extra-field-value',
146
+ };
147
+
148
+ // Use renderHook to properly test the React hook
149
+ const { result } = renderHook(() => useAgentUpdate(initialAgent as any));
150
+
151
+ // Get the necessary functions
152
+ const { updateField, updateSettings } = result.current;
153
+
154
+ // Call importAgent
155
+ result.current.importAgent(templateAgent as any);
156
+
157
+ // Verify that updateField or updateSettings was called for each field in the template
158
+
159
+ // Check basic scalar fields
160
+ expect(updateField).toHaveBeenCalledWith('name', templateAgent.name);
161
+ expect(updateField).toHaveBeenCalledWith('username', templateAgent.username);
162
+ expect(updateField).toHaveBeenCalledWith('system', templateAgent.system);
163
+
164
+ // Check array fields
165
+ expect(updateField).toHaveBeenCalledWith('bio', templateAgent.bio);
166
+ expect(updateField).toHaveBeenCalledWith('topics', templateAgent.topics);
167
+ expect(updateField).toHaveBeenCalledWith('adjectives', templateAgent.adjectives);
168
+ expect(updateField).toHaveBeenCalledWith('plugins', templateAgent.plugins);
169
+
170
+ // Check style fields
171
+ expect(updateField).toHaveBeenCalledWith('style', {
172
+ all: templateAgent.style.all,
173
+ chat: templateAgent.style.chat,
174
+ post: templateAgent.style.post,
175
+ });
176
+
177
+ // Check settings
178
+ expect(updateSettings).toHaveBeenCalledWith(
179
+ expect.objectContaining({
180
+ ...initialAgent.settings,
181
+ ...templateAgent.settings,
182
+ })
183
+ );
184
+
185
+ // Check extra fields
186
+ expect(updateField).toHaveBeenCalledWith('extraField', templateAgent.extraField);
187
+ });
188
+
189
+ test('importAgent should handle custom fields and nested objects', () => {
190
+ // Create initial and template agents with complex nested structures
191
+ const initialAgent: MockAgent = {
192
+ name: 'Initial',
193
+ username: 'initial',
194
+ system: 'Initial',
195
+ bio: [],
196
+ topics: [],
197
+ adjectives: [],
198
+ plugins: [],
199
+ style: { all: [], chat: [], post: [] },
200
+ settings: {
201
+ complexObject: {
202
+ level1: {
203
+ value: 'initial',
204
+ },
205
+ },
206
+ },
207
+ };
208
+
209
+ const templateAgent: MockAgent = {
210
+ name: 'Template',
211
+ username: 'template',
212
+ system: 'Template',
213
+ bio: [],
214
+ topics: [],
215
+ adjectives: [],
216
+ plugins: [],
217
+ style: { all: [], chat: [], post: [] },
218
+ settings: {
219
+ complexObject: {
220
+ level1: {
221
+ value: 'template',
222
+ newField: 'new',
223
+ },
224
+ level2: 'new level',
225
+ },
226
+ },
227
+ customField: 'custom value',
228
+ };
229
+
230
+ // Use renderHook to properly test the React hook
231
+ const { result } = renderHook(() => useAgentUpdate(initialAgent as any));
232
+
233
+ // Get the necessary functions
234
+ const { updateField, updateSettings } = result.current;
235
+
236
+ // Call importAgent
237
+ result.current.importAgent(templateAgent as any);
238
+
239
+ // Verify updateSettings was called with the complex nested object
240
+ expect(updateSettings).toHaveBeenCalledWith(
241
+ expect.objectContaining({
242
+ ...initialAgent.settings,
243
+ ...templateAgent.settings,
244
+ })
245
+ );
246
+
247
+ // Verify custom field was updated
248
+ expect(updateField).toHaveBeenCalledWith('customField', templateAgent.customField);
249
+ });
250
+ });
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect, mock } from 'bun:test';
2
+ import { useConvertCharacter } from '../use-character-convert';
3
+
4
+ // Mock the usePlugins hook
5
+ const mockUsePlugins = mock();
6
+
7
+ mock.module('../use-plugins', () => ({
8
+ usePlugins: mockUsePlugins,
9
+ }));
10
+
11
+ describe('useConvertCharacter', () => {
12
+ it('should only include plugins that exist in availablePlugins', () => {
13
+ // Set up mock return value
14
+ mockUsePlugins.mockImplementation(() => ({
15
+ data: ['@elizaos/plugin-sql', '@elizaos/plugin-bootstrap', '@elizaos/plugin-discord'],
16
+ }));
17
+
18
+ const { convertCharacter } = useConvertCharacter();
19
+
20
+ const v1Character = {
21
+ name: 'Test Character',
22
+ clients: ['discord'],
23
+ modelProvider: 'google',
24
+ bio: ['Test bio'],
25
+ };
26
+
27
+ const result = convertCharacter(v1Character);
28
+
29
+ // Should only include plugins that exist in availablePlugins
30
+ expect(result.plugins).toEqual([
31
+ '@elizaos/plugin-bootstrap',
32
+ '@elizaos/plugin-discord',
33
+ '@elizaos/plugin-sql',
34
+ ]);
35
+ });
36
+
37
+ it('should not include non-existent plugins', () => {
38
+ // Set up mock return value with limited set
39
+ mockUsePlugins.mockImplementation(() => ({
40
+ data: ['@elizaos/plugin-sql'], // Only sql plugin available
41
+ }));
42
+
43
+ const { convertCharacter } = useConvertCharacter();
44
+
45
+ const v1Character = {
46
+ name: 'Test Character',
47
+ clients: ['discord', 'slack'], // These plugins don't exist
48
+ modelProvider: 'openai', // This plugin doesn't exist
49
+ bio: ['Test bio'],
50
+ };
51
+
52
+ const result = convertCharacter(v1Character);
53
+
54
+ // Should only include plugins that actually exist
55
+ expect(result.plugins).toEqual(['@elizaos/plugin-sql']);
56
+ expect(result.plugins).not.toContain('@elizaos/plugin-discord');
57
+ expect(result.plugins).not.toContain('@elizaos/plugin-slack');
58
+ expect(result.plugins).not.toContain('@elizaos/plugin-openai');
59
+ });
60
+
61
+ it('should handle empty availablePlugins gracefully', () => {
62
+ // Set up mock return value with empty array
63
+ mockUsePlugins.mockImplementation(() => ({
64
+ data: [],
65
+ }));
66
+
67
+ const { convertCharacter } = useConvertCharacter();
68
+
69
+ const v1Character = {
70
+ name: 'Test Character',
71
+ clients: ['discord'],
72
+ modelProvider: 'google',
73
+ bio: ['Test bio'],
74
+ };
75
+
76
+ const result = convertCharacter(v1Character);
77
+
78
+ // Should return empty plugins array when no plugins are available
79
+ expect(result.plugins).toEqual([]);
80
+ });
81
+
82
+ it('should handle mapped plugins correctly', () => {
83
+ // Set up mock return value including mapped ones
84
+ mockUsePlugins.mockImplementation(() => ({
85
+ data: ['@elizaos/plugin-google-genai', '@elizaos/plugin-sql'],
86
+ }));
87
+
88
+ const { convertCharacter } = useConvertCharacter();
89
+
90
+ const v1Character = {
91
+ name: 'Test Character',
92
+ modelProvider: 'google', // This should map to @elizaos/plugin-google-genai
93
+ bio: ['Test bio'],
94
+ };
95
+
96
+ const result = convertCharacter(v1Character);
97
+
98
+ // Should include the mapped plugin
99
+ expect(result.plugins).toContain('@elizaos/plugin-google-genai');
100
+ expect(result.plugins).toContain('@elizaos/plugin-sql');
101
+ });
102
+ });