@elizaos/autonomous 2.0.0-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 (241) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +270 -0
  3. package/src/actions/emote.ts +101 -0
  4. package/src/actions/restart.ts +101 -0
  5. package/src/actions/send-message.ts +168 -0
  6. package/src/actions/stream-control.ts +439 -0
  7. package/src/actions/switch-stream-source.ts +126 -0
  8. package/src/actions/terminal.ts +186 -0
  9. package/src/api/agent-admin-routes.ts +178 -0
  10. package/src/api/agent-lifecycle-routes.ts +129 -0
  11. package/src/api/agent-model.ts +143 -0
  12. package/src/api/agent-transfer-routes.ts +211 -0
  13. package/src/api/apps-routes.ts +210 -0
  14. package/src/api/auth-routes.ts +90 -0
  15. package/src/api/bsc-trade.ts +736 -0
  16. package/src/api/bug-report-routes.ts +161 -0
  17. package/src/api/character-routes.ts +421 -0
  18. package/src/api/cloud-billing-routes.ts +598 -0
  19. package/src/api/cloud-compat-routes.ts +192 -0
  20. package/src/api/cloud-routes.ts +529 -0
  21. package/src/api/cloud-status-routes.ts +234 -0
  22. package/src/api/compat-utils.ts +154 -0
  23. package/src/api/connector-health.ts +135 -0
  24. package/src/api/coordinator-wiring.ts +179 -0
  25. package/src/api/credit-detection.ts +47 -0
  26. package/src/api/database.ts +1357 -0
  27. package/src/api/diagnostics-routes.ts +389 -0
  28. package/src/api/drop-service.ts +205 -0
  29. package/src/api/early-logs.ts +111 -0
  30. package/src/api/http-helpers.ts +252 -0
  31. package/src/api/index.ts +85 -0
  32. package/src/api/knowledge-routes.ts +1189 -0
  33. package/src/api/knowledge-service-loader.ts +92 -0
  34. package/src/api/memory-bounds.ts +121 -0
  35. package/src/api/memory-routes.ts +349 -0
  36. package/src/api/merkle-tree.ts +239 -0
  37. package/src/api/models-routes.ts +72 -0
  38. package/src/api/nfa-routes.ts +169 -0
  39. package/src/api/nft-verify.ts +188 -0
  40. package/src/api/og-tracker.ts +72 -0
  41. package/src/api/parse-action-block.ts +145 -0
  42. package/src/api/permissions-routes.ts +222 -0
  43. package/src/api/plugin-validation.ts +355 -0
  44. package/src/api/provider-switch-config.ts +455 -0
  45. package/src/api/registry-routes.ts +165 -0
  46. package/src/api/registry-service.ts +292 -0
  47. package/src/api/route-helpers.ts +21 -0
  48. package/src/api/sandbox-routes.ts +1480 -0
  49. package/src/api/server.ts +17674 -0
  50. package/src/api/signal-routes.ts +265 -0
  51. package/src/api/stream-persistence.ts +297 -0
  52. package/src/api/stream-route-state.ts +48 -0
  53. package/src/api/stream-routes.ts +1046 -0
  54. package/src/api/stream-voice-routes.ts +208 -0
  55. package/src/api/streaming-text.ts +129 -0
  56. package/src/api/streaming-types.ts +23 -0
  57. package/src/api/subscription-routes.ts +283 -0
  58. package/src/api/terminal-run-limits.ts +31 -0
  59. package/src/api/training-backend-check.ts +40 -0
  60. package/src/api/training-routes.ts +314 -0
  61. package/src/api/training-service-like.ts +46 -0
  62. package/src/api/trajectory-routes.ts +714 -0
  63. package/src/api/trigger-routes.ts +438 -0
  64. package/src/api/twitter-verify.ts +226 -0
  65. package/src/api/tx-service.ts +193 -0
  66. package/src/api/wallet-dex-prices.ts +206 -0
  67. package/src/api/wallet-evm-balance.ts +989 -0
  68. package/src/api/wallet-routes.ts +505 -0
  69. package/src/api/wallet-rpc.ts +523 -0
  70. package/src/api/wallet-trading-profile.ts +694 -0
  71. package/src/api/wallet.ts +745 -0
  72. package/src/api/whatsapp-routes.ts +282 -0
  73. package/src/api/zip-utils.ts +130 -0
  74. package/src/auth/anthropic.ts +63 -0
  75. package/src/auth/apply-stealth.ts +38 -0
  76. package/src/auth/claude-code-stealth.ts +141 -0
  77. package/src/auth/credentials.ts +226 -0
  78. package/src/auth/index.ts +18 -0
  79. package/src/auth/openai-codex.ts +94 -0
  80. package/src/auth/types.ts +24 -0
  81. package/src/awareness/registry.ts +220 -0
  82. package/src/bin.ts +10 -0
  83. package/src/cli/index.ts +36 -0
  84. package/src/cli/parse-duration.ts +43 -0
  85. package/src/cloud/auth.test.ts +370 -0
  86. package/src/cloud/auth.ts +176 -0
  87. package/src/cloud/backup.test.ts +150 -0
  88. package/src/cloud/backup.ts +50 -0
  89. package/src/cloud/base-url.ts +45 -0
  90. package/src/cloud/bridge-client.test.ts +481 -0
  91. package/src/cloud/bridge-client.ts +307 -0
  92. package/src/cloud/cloud-manager.test.ts +223 -0
  93. package/src/cloud/cloud-manager.ts +151 -0
  94. package/src/cloud/cloud-proxy.test.ts +122 -0
  95. package/src/cloud/cloud-proxy.ts +52 -0
  96. package/src/cloud/index.ts +23 -0
  97. package/src/cloud/reconnect.test.ts +178 -0
  98. package/src/cloud/reconnect.ts +108 -0
  99. package/src/cloud/validate-url.test.ts +147 -0
  100. package/src/cloud/validate-url.ts +176 -0
  101. package/src/config/character-schema.ts +44 -0
  102. package/src/config/config.ts +149 -0
  103. package/src/config/env-vars.ts +86 -0
  104. package/src/config/includes.ts +196 -0
  105. package/src/config/index.ts +15 -0
  106. package/src/config/object-utils.ts +10 -0
  107. package/src/config/paths.ts +92 -0
  108. package/src/config/plugin-auto-enable.ts +520 -0
  109. package/src/config/schema.ts +1342 -0
  110. package/src/config/telegram-custom-commands.ts +99 -0
  111. package/src/config/types.agent-defaults.ts +342 -0
  112. package/src/config/types.agents.ts +112 -0
  113. package/src/config/types.gateway.ts +243 -0
  114. package/src/config/types.hooks.ts +124 -0
  115. package/src/config/types.messages.ts +201 -0
  116. package/src/config/types.milady.ts +791 -0
  117. package/src/config/types.tools.ts +416 -0
  118. package/src/config/types.ts +7 -0
  119. package/src/config/zod-schema.agent-runtime.ts +777 -0
  120. package/src/config/zod-schema.core.ts +778 -0
  121. package/src/config/zod-schema.hooks.ts +139 -0
  122. package/src/config/zod-schema.providers-core.ts +1126 -0
  123. package/src/config/zod-schema.session.ts +98 -0
  124. package/src/config/zod-schema.ts +865 -0
  125. package/src/contracts/apps.ts +46 -0
  126. package/src/contracts/awareness.ts +56 -0
  127. package/src/contracts/config.ts +172 -0
  128. package/src/contracts/drop.ts +21 -0
  129. package/src/contracts/index.ts +8 -0
  130. package/src/contracts/onboarding.ts +592 -0
  131. package/src/contracts/permissions.ts +52 -0
  132. package/src/contracts/verification.ts +9 -0
  133. package/src/contracts/wallet.ts +503 -0
  134. package/src/diagnostics/integration-observability.ts +132 -0
  135. package/src/emotes/catalog.ts +655 -0
  136. package/src/external-modules.d.ts +7 -0
  137. package/src/hooks/discovery.test.ts +357 -0
  138. package/src/hooks/discovery.ts +231 -0
  139. package/src/hooks/eligibility.ts +146 -0
  140. package/src/hooks/hooks.test.ts +320 -0
  141. package/src/hooks/index.ts +8 -0
  142. package/src/hooks/loader.test.ts +418 -0
  143. package/src/hooks/loader.ts +256 -0
  144. package/src/hooks/registry.test.ts +168 -0
  145. package/src/hooks/registry.ts +74 -0
  146. package/src/hooks/types.ts +121 -0
  147. package/src/index.ts +19 -0
  148. package/src/onboarding-presets.ts +828 -0
  149. package/src/plugins/custom-rtmp/index.ts +40 -0
  150. package/src/providers/admin-trust.ts +76 -0
  151. package/src/providers/session-bridge.ts +143 -0
  152. package/src/providers/session-utils.ts +42 -0
  153. package/src/providers/simple-mode.ts +113 -0
  154. package/src/providers/ui-catalog.ts +135 -0
  155. package/src/providers/workspace-provider.ts +213 -0
  156. package/src/providers/workspace.ts +497 -0
  157. package/src/runtime/agent-event-service.ts +57 -0
  158. package/src/runtime/cloud-onboarding.test.ts +489 -0
  159. package/src/runtime/cloud-onboarding.ts +408 -0
  160. package/src/runtime/core-plugins.ts +53 -0
  161. package/src/runtime/custom-actions.ts +605 -0
  162. package/src/runtime/eliza.ts +4941 -0
  163. package/src/runtime/embedding-presets.ts +73 -0
  164. package/src/runtime/index.ts +8 -0
  165. package/src/runtime/milady-plugin.ts +180 -0
  166. package/src/runtime/onboarding-names.ts +76 -0
  167. package/src/runtime/release-plugin-policy.ts +119 -0
  168. package/src/runtime/restart.ts +59 -0
  169. package/src/runtime/trajectory-persistence.ts +2584 -0
  170. package/src/runtime/version.ts +6 -0
  171. package/src/security/audit-log.ts +222 -0
  172. package/src/security/network-policy.ts +91 -0
  173. package/src/server/index.ts +6 -0
  174. package/src/services/agent-export.ts +976 -0
  175. package/src/services/app-manager.ts +755 -0
  176. package/src/services/browser-capture.ts +215 -0
  177. package/src/services/coding-agent-context.ts +355 -0
  178. package/src/services/fallback-training-service.ts +196 -0
  179. package/src/services/index.ts +17 -0
  180. package/src/services/mcp-marketplace.ts +327 -0
  181. package/src/services/plugin-manager-types.ts +185 -0
  182. package/src/services/privy-wallets.ts +352 -0
  183. package/src/services/registry-client-app-meta.ts +201 -0
  184. package/src/services/registry-client-endpoints.ts +253 -0
  185. package/src/services/registry-client-local.ts +485 -0
  186. package/src/services/registry-client-network.ts +173 -0
  187. package/src/services/registry-client-queries.ts +176 -0
  188. package/src/services/registry-client-types.ts +104 -0
  189. package/src/services/registry-client.ts +366 -0
  190. package/src/services/remote-signing-service.ts +261 -0
  191. package/src/services/sandbox-engine.ts +753 -0
  192. package/src/services/sandbox-manager.ts +503 -0
  193. package/src/services/self-updater.ts +213 -0
  194. package/src/services/signal-pairing.ts +189 -0
  195. package/src/services/signing-policy.ts +230 -0
  196. package/src/services/skill-catalog-client.ts +195 -0
  197. package/src/services/skill-marketplace.ts +909 -0
  198. package/src/services/stream-manager.ts +707 -0
  199. package/src/services/tts-stream-bridge.ts +465 -0
  200. package/src/services/update-checker.ts +163 -0
  201. package/src/services/version-compat.ts +367 -0
  202. package/src/services/whatsapp-pairing.ts +279 -0
  203. package/src/shared/ui-catalog-prompt.ts +1158 -0
  204. package/src/test-support/process-helpers.ts +35 -0
  205. package/src/test-support/route-test-helpers.ts +113 -0
  206. package/src/test-support/test-helpers.ts +304 -0
  207. package/src/testing/index.ts +3 -0
  208. package/src/triggers/action.ts +342 -0
  209. package/src/triggers/runtime.ts +432 -0
  210. package/src/triggers/scheduling.ts +472 -0
  211. package/src/triggers/types.ts +133 -0
  212. package/src/types/app-hyperscape-routes-shim.d.ts +29 -0
  213. package/src/types/external-modules.d.ts +7 -0
  214. package/src/utils/exec-safety.ts +23 -0
  215. package/src/utils/number-parsing.ts +112 -0
  216. package/src/utils/spoken-text.ts +65 -0
  217. package/src/version-resolver.ts +60 -0
  218. package/test/api/agent-admin-routes.test.ts +160 -0
  219. package/test/api/agent-lifecycle-routes.test.ts +164 -0
  220. package/test/api/agent-transfer-routes.test.ts +136 -0
  221. package/test/api/apps-routes.test.ts +140 -0
  222. package/test/api/auth-routes.test.ts +160 -0
  223. package/test/api/bug-report-routes.test.ts +88 -0
  224. package/test/api/knowledge-routes.test.ts +73 -0
  225. package/test/api/lifecycle.test.ts +342 -0
  226. package/test/api/memory-routes.test.ts +74 -0
  227. package/test/api/models-routes.test.ts +112 -0
  228. package/test/api/nfa-routes.test.ts +78 -0
  229. package/test/api/permissions-routes.test.ts +185 -0
  230. package/test/api/registry-routes.test.ts +157 -0
  231. package/test/api/signal-routes.test.ts +113 -0
  232. package/test/api/subscription-routes.test.ts +90 -0
  233. package/test/api/trigger-routes.test.ts +87 -0
  234. package/test/api/wallet-routes.observability.test.ts +191 -0
  235. package/test/api/wallet-routes.test.ts +502 -0
  236. package/test/diagnostics/integration-observability.test.ts +135 -0
  237. package/test/security/audit-log.test.ts +229 -0
  238. package/test/security/network-policy.test.ts +143 -0
  239. package/test/services/version-compat.test.ts +127 -0
  240. package/tsconfig.build.json +21 -0
  241. package/tsconfig.json +19 -0
@@ -0,0 +1,307 @@
1
+ /**
2
+ * HTTP client for the Eliza Cloud Milady Sandbox API.
3
+ */
4
+
5
+ export interface CloudAgent {
6
+ id: string;
7
+ agentName: string;
8
+ status: string;
9
+ databaseStatus: string;
10
+ bridgeUrl?: string;
11
+ lastBackupAt?: string;
12
+ lastHeartbeatAt?: string;
13
+ errorMessage?: string;
14
+ errorCount?: number;
15
+ createdAt: string;
16
+ updatedAt: string;
17
+ }
18
+
19
+ export interface CloudAgentCreateParams {
20
+ agentName: string;
21
+ agentConfig?: Record<string, unknown>;
22
+ environmentVars?: Record<string, string>;
23
+ }
24
+
25
+ export interface ProvisionInfo {
26
+ id: string;
27
+ agentName: string;
28
+ status: string;
29
+ bridgeUrl?: string;
30
+ healthUrl?: string;
31
+ }
32
+
33
+ export interface BackupInfo {
34
+ id: string;
35
+ snapshotType: string;
36
+ sizeBytes: number | null;
37
+ createdAt: string;
38
+ }
39
+
40
+ export type ChatChannelType =
41
+ | "DM"
42
+ | "GROUP"
43
+ | "VOICE_DM"
44
+ | "VOICE_GROUP"
45
+ | "API";
46
+
47
+ interface ApiResponse<T> {
48
+ success: boolean;
49
+ data?: T;
50
+ error?: string;
51
+ }
52
+
53
+ function isRedirectResponse(response: Response): boolean {
54
+ return response.status >= 300 && response.status < 400;
55
+ }
56
+
57
+ export class ElizaCloudClient {
58
+ private baseUrl: string;
59
+ private apiKey: string;
60
+
61
+ constructor(baseUrl: string, apiKey: string) {
62
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
63
+ this.apiKey = apiKey;
64
+ }
65
+
66
+ async listAgents(): Promise<CloudAgent[]> {
67
+ const res = await this.request<CloudAgent[]>(
68
+ "GET",
69
+ "/api/v1/milady/agents",
70
+ );
71
+ return res.data ?? [];
72
+ }
73
+
74
+ async createAgent(params: CloudAgentCreateParams): Promise<CloudAgent> {
75
+ const res = await this.request<CloudAgent>(
76
+ "POST",
77
+ "/api/v1/milady/agents",
78
+ params,
79
+ );
80
+ if (!res.success || !res.data)
81
+ throw new Error(res.error ?? "Failed to create cloud agent");
82
+ return res.data;
83
+ }
84
+
85
+ async getAgent(agentId: string): Promise<CloudAgent> {
86
+ const res = await this.request<CloudAgent>(
87
+ "GET",
88
+ `/api/v1/milady/agents/${agentId}`,
89
+ );
90
+ if (!res.success || !res.data)
91
+ throw new Error(res.error ?? "Agent not found");
92
+ return res.data;
93
+ }
94
+
95
+ async deleteAgent(agentId: string): Promise<void> {
96
+ const res = await this.request<void>(
97
+ "DELETE",
98
+ `/api/v1/milady/agents/${agentId}`,
99
+ );
100
+ if (!res.success) throw new Error(res.error ?? "Failed to delete agent");
101
+ }
102
+
103
+ async provision(agentId: string): Promise<ProvisionInfo> {
104
+ const res = await this.request<ProvisionInfo>(
105
+ "POST",
106
+ `/api/v1/milady/agents/${agentId}/provision`,
107
+ );
108
+ if (!res.success || !res.data)
109
+ throw new Error(res.error ?? "Failed to provision sandbox");
110
+ return res.data;
111
+ }
112
+
113
+ async sendMessage(
114
+ agentId: string,
115
+ text: string,
116
+ roomId = "web-chat",
117
+ channelType: ChatChannelType = "DM",
118
+ ): Promise<string> {
119
+ const url = `${this.baseUrl}/api/v1/milady/agents/${agentId}/bridge`;
120
+ const response = await fetch(url, {
121
+ method: "POST",
122
+ headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey },
123
+ body: JSON.stringify({
124
+ jsonrpc: "2.0",
125
+ id: crypto.randomUUID(),
126
+ method: "message.send",
127
+ params: { text, roomId, channelType },
128
+ }),
129
+ redirect: "manual",
130
+ signal: AbortSignal.timeout(60_000),
131
+ });
132
+
133
+ if (isRedirectResponse(response)) {
134
+ throw new Error(
135
+ "Bridge request was redirected; redirects are not allowed",
136
+ );
137
+ }
138
+
139
+ if (!response.ok) {
140
+ const errorText = await response.text().catch(() => "");
141
+ throw new Error(
142
+ `Bridge request failed: HTTP ${response.status} ${errorText.slice(0, 200)}`,
143
+ );
144
+ }
145
+
146
+ const rpc = (await response.json()) as {
147
+ result?: { text?: string };
148
+ error?: { code: number; message: string };
149
+ };
150
+
151
+ if (rpc.error) throw new Error(rpc.error.message);
152
+ return rpc.result?.text ?? "(no response)";
153
+ }
154
+
155
+ async *sendMessageStream(
156
+ agentId: string,
157
+ text: string,
158
+ roomId = "web-chat",
159
+ channelType: ChatChannelType = "DM",
160
+ ): AsyncGenerator<{ type: string; data: Record<string, unknown> }> {
161
+ const url = `${this.baseUrl}/api/v1/milady/agents/${agentId}/stream`;
162
+ const response = await fetch(url, {
163
+ method: "POST",
164
+ headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey },
165
+ body: JSON.stringify({
166
+ jsonrpc: "2.0",
167
+ id: crypto.randomUUID(),
168
+ method: "message.send",
169
+ params: { text, roomId, channelType },
170
+ }),
171
+ redirect: "manual",
172
+ });
173
+
174
+ if (isRedirectResponse(response)) {
175
+ throw new Error(
176
+ "Stream request was redirected; redirects are not allowed",
177
+ );
178
+ }
179
+
180
+ if (!response.ok || !response.body) {
181
+ throw new Error(`Stream request failed: HTTP ${response.status}`);
182
+ }
183
+
184
+ const reader = response.body.getReader();
185
+ const decoder = new TextDecoder();
186
+ let buffer = "";
187
+
188
+ for (;;) {
189
+ const { done, value } = await reader.read();
190
+ if (done) break;
191
+
192
+ buffer += decoder.decode(value, { stream: true });
193
+ const parts = buffer.split("\n\n");
194
+ buffer = parts.pop() ?? "";
195
+
196
+ for (const part of parts) {
197
+ if (!part.trim()) continue;
198
+ let eventType = "message";
199
+ let eventData = "";
200
+
201
+ for (const line of part.split("\n")) {
202
+ if (line.startsWith("event: ")) eventType = line.slice(7).trim();
203
+ else if (line.startsWith("data: ")) eventData = line.slice(6);
204
+ }
205
+
206
+ if (eventData) {
207
+ let data: Record<string, unknown>;
208
+ try {
209
+ data = JSON.parse(eventData) as Record<string, unknown>;
210
+ } catch {
211
+ continue;
212
+ }
213
+ yield { type: eventType, data };
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ async snapshot(agentId: string): Promise<BackupInfo> {
220
+ const res = await this.request<BackupInfo>(
221
+ "POST",
222
+ `/api/v1/milady/agents/${agentId}/snapshot`,
223
+ );
224
+ if (!res.success || !res.data)
225
+ throw new Error(res.error ?? "Snapshot failed");
226
+ return res.data;
227
+ }
228
+
229
+ async listBackups(agentId: string): Promise<BackupInfo[]> {
230
+ const res = await this.request<BackupInfo[]>(
231
+ "GET",
232
+ `/api/v1/milady/agents/${agentId}/backups`,
233
+ );
234
+ return res.data ?? [];
235
+ }
236
+
237
+ async restore(agentId: string, backupId?: string): Promise<void> {
238
+ const res = await this.request<void>(
239
+ "POST",
240
+ `/api/v1/milady/agents/${agentId}/restore`,
241
+ backupId ? { backupId } : {},
242
+ );
243
+ if (!res.success) throw new Error(res.error ?? "Restore failed");
244
+ }
245
+
246
+ async heartbeat(agentId: string): Promise<boolean> {
247
+ const url = `${this.baseUrl}/api/v1/milady/agents/${agentId}/bridge`;
248
+ try {
249
+ const response = await fetch(url, {
250
+ method: "POST",
251
+ headers: {
252
+ "Content-Type": "application/json",
253
+ "X-Api-Key": this.apiKey,
254
+ },
255
+ body: JSON.stringify({ jsonrpc: "2.0", method: "heartbeat" }),
256
+ redirect: "manual",
257
+ signal: AbortSignal.timeout(10_000),
258
+ });
259
+ if (isRedirectResponse(response)) return false;
260
+ return response.ok;
261
+ } catch {
262
+ return false;
263
+ }
264
+ }
265
+
266
+ private async request<T>(
267
+ method: string,
268
+ path: string,
269
+ body?: unknown,
270
+ ): Promise<ApiResponse<T>> {
271
+ const headers: Record<string, string> = { "X-Api-Key": this.apiKey };
272
+ if (body !== undefined) headers["Content-Type"] = "application/json";
273
+
274
+ const response = await fetch(`${this.baseUrl}${path}`, {
275
+ method,
276
+ headers,
277
+ body: body !== undefined ? JSON.stringify(body) : undefined,
278
+ redirect: "manual",
279
+ signal: AbortSignal.timeout(30_000),
280
+ });
281
+
282
+ if (isRedirectResponse(response)) {
283
+ return {
284
+ success: false,
285
+ error: "Cloud API request was redirected; redirects are not allowed",
286
+ };
287
+ }
288
+
289
+ if (!response.ok) {
290
+ const text = await response.text().catch(() => "");
291
+ let parsed: Record<string, unknown> = {};
292
+ try {
293
+ parsed = JSON.parse(text);
294
+ } catch {
295
+ /* plain text */
296
+ }
297
+ return {
298
+ success: false,
299
+ error:
300
+ (parsed.error as string) ??
301
+ `HTTP ${response.status}: ${text.slice(0, 200)}`,
302
+ };
303
+ }
304
+
305
+ return (await response.json()) as ApiResponse<T>;
306
+ }
307
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Tests for cloud/cloud-manager.ts — the top-level orchestrator.
3
+ */
4
+
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+
7
+ // Must mock before importing CloudManager, and use factory functions
8
+ // that return class-like constructors so `new` works.
9
+ vi.mock("./bridge-client", () => {
10
+ return {
11
+ ElizaCloudClient: class MockElizaCloudClient {
12
+ _baseUrl: string;
13
+ _apiKey: string;
14
+ provision = vi.fn().mockResolvedValue({ id: "a1", status: "running" });
15
+ getAgent = vi.fn().mockResolvedValue({
16
+ id: "a1",
17
+ agentName: "TestBot",
18
+ status: "running",
19
+ });
20
+ snapshot = vi.fn().mockResolvedValue({ id: "bk-1" });
21
+ heartbeat = vi.fn().mockResolvedValue(true);
22
+ constructor(baseUrl: string, apiKey: string) {
23
+ this._baseUrl = baseUrl;
24
+ this._apiKey = apiKey;
25
+ }
26
+ },
27
+ };
28
+ });
29
+
30
+ vi.mock("./cloud-proxy", () => {
31
+ return {
32
+ CloudRuntimeProxy: class MockProxy {
33
+ agentName: string;
34
+ constructor(_client: unknown, _id: string, name: string) {
35
+ this.agentName = name;
36
+ }
37
+ },
38
+ };
39
+ });
40
+
41
+ vi.mock("./backup", () => {
42
+ return {
43
+ BackupScheduler: class MockBackup {
44
+ start = vi.fn();
45
+ stop = vi.fn();
46
+ finalSnapshot = vi.fn().mockResolvedValue(undefined);
47
+ },
48
+ };
49
+ });
50
+
51
+ vi.mock("./reconnect", () => {
52
+ return {
53
+ ConnectionMonitor: class MockMonitor {
54
+ start = vi.fn();
55
+ stop = vi.fn();
56
+ },
57
+ };
58
+ });
59
+
60
+ vi.mock("./validate-url", () => {
61
+ return {
62
+ validateCloudBaseUrl: vi.fn().mockResolvedValue(null),
63
+ };
64
+ });
65
+
66
+ import type { CloudConfig } from "../config/types.milady";
67
+ import { CloudManager } from "./cloud-manager";
68
+
69
+ afterEach(() => {
70
+ vi.clearAllMocks();
71
+ });
72
+
73
+ function cfg(overrides: Partial<CloudConfig> = {}): CloudConfig {
74
+ return {
75
+ enabled: true,
76
+ apiKey: "eliza_testkey",
77
+ baseUrl: "https://test.elizacloud.ai",
78
+ ...overrides,
79
+ };
80
+ }
81
+
82
+ describe("CloudManager", () => {
83
+ describe("init", () => {
84
+ it("creates client from config", async () => {
85
+ const mgr = new CloudManager(cfg());
86
+ await mgr.init();
87
+ expect(mgr.getClient()).not.toBeNull();
88
+ });
89
+
90
+ it("throws when apiKey is missing", async () => {
91
+ const mgr = new CloudManager(cfg({ apiKey: undefined }));
92
+ await expect(mgr.init()).rejects.toThrow(
93
+ "Cloud API key is not configured",
94
+ );
95
+ });
96
+
97
+ it("strips /api/v1 suffix from baseUrl", async () => {
98
+ const mgr = new CloudManager(
99
+ cfg({ baseUrl: "https://test.elizacloud.ai/api/v1" }),
100
+ );
101
+ await mgr.init();
102
+ expect((mgr.getClient() as unknown as Record<string, string>)._baseUrl).toBe(
103
+ "https://test.elizacloud.ai",
104
+ );
105
+ });
106
+
107
+ it("strips trailing slashes", async () => {
108
+ const mgr = new CloudManager(
109
+ cfg({ baseUrl: "https://test.elizacloud.ai///" }),
110
+ );
111
+ await mgr.init();
112
+ expect((mgr.getClient() as unknown as Record<string, string>)._baseUrl).toBe(
113
+ "https://test.elizacloud.ai",
114
+ );
115
+ });
116
+
117
+ it("defaults to elizacloud.ai when no baseUrl", async () => {
118
+ const mgr = new CloudManager(cfg({ baseUrl: undefined }));
119
+ await mgr.init();
120
+ expect((mgr.getClient() as unknown as Record<string, string>)._baseUrl).toBe(
121
+ "https://www.elizacloud.ai",
122
+ );
123
+ });
124
+ });
125
+
126
+ describe("connect", () => {
127
+ it("provisions and returns proxy", async () => {
128
+ const mgr = new CloudManager(cfg());
129
+ const proxy = await mgr.connect("agent-123");
130
+ expect(proxy.agentName).toBe("TestBot");
131
+ expect(mgr.getActiveAgentId()).toBe("agent-123");
132
+ expect(mgr.getStatus()).toBe("connected");
133
+ expect(mgr.getProxy()).not.toBeNull();
134
+ });
135
+
136
+ it("auto-inits if client not ready", async () => {
137
+ const mgr = new CloudManager(cfg());
138
+ expect(mgr.getClient()).toBeNull();
139
+ await mgr.connect("agent-123");
140
+ expect(mgr.getClient()).not.toBeNull();
141
+ });
142
+
143
+ it("fires status callbacks", async () => {
144
+ const statuses: string[] = [];
145
+ const mgr = new CloudManager(cfg(), {
146
+ onStatusChange: (s) => statuses.push(s),
147
+ });
148
+ await mgr.connect("agent-123");
149
+ expect(statuses).toContain("connecting");
150
+ expect(statuses).toContain("connected");
151
+ });
152
+
153
+ it("resets status and state on connection failure", async () => {
154
+ const mgr = new CloudManager(cfg());
155
+ await mgr.init();
156
+
157
+ const state = mgr as unknown as {
158
+ client: { provision: (...args: unknown[]) => Promise<unknown> };
159
+ };
160
+ state.client.provision = vi.fn(async () => {
161
+ throw new Error("provision failed");
162
+ });
163
+
164
+ await expect(mgr.connect("agent-123")).rejects.toThrow(
165
+ "provision failed",
166
+ );
167
+ expect(mgr.getStatus()).toBe("disconnected");
168
+ expect(mgr.getActiveAgentId()).toBeNull();
169
+ expect(mgr.getProxy()).toBeNull();
170
+ });
171
+ });
172
+
173
+ describe("disconnect", () => {
174
+ it("clears proxy and resets state", async () => {
175
+ const mgr = new CloudManager(cfg());
176
+ await mgr.connect("agent-123");
177
+ await mgr.disconnect();
178
+ expect(mgr.getProxy()).toBeNull();
179
+ expect(mgr.getActiveAgentId()).toBeNull();
180
+ expect(mgr.getStatus()).toBe("disconnected");
181
+ });
182
+
183
+ it("fires disconnected callback", async () => {
184
+ const statuses: string[] = [];
185
+ const mgr = new CloudManager(cfg(), {
186
+ onStatusChange: (s) => statuses.push(s),
187
+ });
188
+ await mgr.connect("agent-123");
189
+ statuses.length = 0;
190
+ await mgr.disconnect();
191
+ expect(statuses).toContain("disconnected");
192
+ });
193
+ });
194
+
195
+ describe("isEnabled", () => {
196
+ it("true when enabled + apiKey", () => {
197
+ expect(new CloudManager(cfg()).isEnabled()).toBe(true);
198
+ });
199
+ it("false when not enabled", () => {
200
+ expect(new CloudManager(cfg({ enabled: false })).isEnabled()).toBe(false);
201
+ });
202
+ it("false when no apiKey", () => {
203
+ expect(new CloudManager(cfg({ apiKey: undefined })).isEnabled()).toBe(
204
+ false,
205
+ );
206
+ });
207
+ });
208
+
209
+ describe("initial state", () => {
210
+ it("proxy is null", () => {
211
+ expect(new CloudManager(cfg()).getProxy()).toBeNull();
212
+ });
213
+ it("client is null", () => {
214
+ expect(new CloudManager(cfg()).getClient()).toBeNull();
215
+ });
216
+ it("agentId is null", () => {
217
+ expect(new CloudManager(cfg()).getActiveAgentId()).toBeNull();
218
+ });
219
+ it("status is disconnected", () => {
220
+ expect(new CloudManager(cfg()).getStatus()).toBe("disconnected");
221
+ });
222
+ });
223
+ });
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Top-level orchestrator for cloud integration.
3
+ * Manages client, proxy, backup scheduler, and connection monitor lifecycle.
4
+ */
5
+
6
+ import { logger } from "@elizaos/core";
7
+ import type { CloudConfig } from "../config/types.milady";
8
+ import { BackupScheduler } from "./backup";
9
+ import { normalizeCloudSiteUrl } from "./base-url";
10
+ import { ElizaCloudClient } from "./bridge-client";
11
+ import { CloudRuntimeProxy } from "./cloud-proxy";
12
+ import { ConnectionMonitor } from "./reconnect";
13
+ import { validateCloudBaseUrl } from "./validate-url";
14
+
15
+ export type CloudConnectionStatus =
16
+ | "disconnected"
17
+ | "connecting"
18
+ | "connected"
19
+ | "reconnecting"
20
+ | "error";
21
+
22
+ export interface CloudManagerCallbacks {
23
+ onStatusChange?: (status: CloudConnectionStatus) => void;
24
+ }
25
+
26
+ export class CloudManager {
27
+ private client: ElizaCloudClient | null = null;
28
+ private proxy: CloudRuntimeProxy | null = null;
29
+ private backupScheduler: BackupScheduler | null = null;
30
+ private connectionMonitor: ConnectionMonitor | null = null;
31
+ private status: CloudConnectionStatus = "disconnected";
32
+ private activeAgentId: string | null = null;
33
+
34
+ constructor(
35
+ private cloudConfig: CloudConfig,
36
+ private callbacks: CloudManagerCallbacks = {},
37
+ ) {}
38
+
39
+ async init(): Promise<void> {
40
+ const rawUrl = normalizeCloudSiteUrl(this.cloudConfig.baseUrl);
41
+ const apiKey = this.cloudConfig.apiKey;
42
+ if (!apiKey)
43
+ throw new Error(
44
+ "Cloud API key is not configured. Run cloud login first.",
45
+ );
46
+
47
+ const urlError = await validateCloudBaseUrl(rawUrl);
48
+ if (urlError) {
49
+ throw new Error(urlError);
50
+ }
51
+
52
+ const siteUrl = normalizeCloudSiteUrl(rawUrl);
53
+ this.client = new ElizaCloudClient(siteUrl, apiKey);
54
+ logger.info(`[cloud-manager] Client initialised (baseUrl=${siteUrl})`);
55
+ }
56
+
57
+ async connect(agentId: string): Promise<CloudRuntimeProxy> {
58
+ if (!this.client) await this.init();
59
+ if (!this.client) throw new Error("Cloud client failed to initialise");
60
+
61
+ this.setStatus("connecting");
62
+ this.activeAgentId = agentId;
63
+
64
+ try {
65
+ await this.client.provision(agentId);
66
+ const agent = await this.client.getAgent(agentId);
67
+
68
+ this.proxy = new CloudRuntimeProxy(this.client, agentId, agent.agentName);
69
+
70
+ this.backupScheduler = new BackupScheduler(
71
+ this.client,
72
+ agentId,
73
+ this.cloudConfig.backup?.autoBackupIntervalMs ?? 60_000,
74
+ );
75
+ this.backupScheduler.start();
76
+
77
+ this.connectionMonitor = new ConnectionMonitor(
78
+ this.client,
79
+ agentId,
80
+ {
81
+ onDisconnect: () => this.setStatus("reconnecting"),
82
+ onReconnect: () => this.setStatus("connected"),
83
+ onStatusChange: (s) => {
84
+ if (s === "connected") this.setStatus("connected");
85
+ else if (s === "reconnecting") this.setStatus("reconnecting");
86
+ else this.setStatus("error");
87
+ },
88
+ },
89
+ this.cloudConfig.bridge?.heartbeatIntervalMs ?? 30_000,
90
+ );
91
+ this.connectionMonitor.start();
92
+
93
+ this.setStatus("connected");
94
+ logger.info(
95
+ `[cloud-manager] Connected to cloud agent (agentId=${agentId}, agentName=${agent.agentName})`,
96
+ );
97
+ return this.proxy;
98
+ } catch (err) {
99
+ this.setStatus("error");
100
+ if (this.backupScheduler) {
101
+ this.backupScheduler.stop();
102
+ this.backupScheduler = null;
103
+ }
104
+ if (this.connectionMonitor) {
105
+ this.connectionMonitor.stop();
106
+ this.connectionMonitor = null;
107
+ }
108
+ this.proxy = null;
109
+ this.activeAgentId = null;
110
+ this.setStatus("disconnected");
111
+ throw err;
112
+ }
113
+ }
114
+
115
+ async disconnect(): Promise<void> {
116
+ if (this.backupScheduler) {
117
+ await this.backupScheduler.finalSnapshot();
118
+ this.backupScheduler.stop();
119
+ this.backupScheduler = null;
120
+ }
121
+ if (this.connectionMonitor) {
122
+ this.connectionMonitor.stop();
123
+ this.connectionMonitor = null;
124
+ }
125
+ this.proxy = null;
126
+ this.activeAgentId = null;
127
+ this.setStatus("disconnected");
128
+ }
129
+
130
+ getProxy(): CloudRuntimeProxy | null {
131
+ return this.proxy;
132
+ }
133
+ getClient(): ElizaCloudClient | null {
134
+ return this.client;
135
+ }
136
+ getActiveAgentId(): string | null {
137
+ return this.activeAgentId;
138
+ }
139
+ getStatus(): CloudConnectionStatus {
140
+ return this.status;
141
+ }
142
+ isEnabled(): boolean {
143
+ return Boolean(this.cloudConfig.enabled && this.cloudConfig.apiKey);
144
+ }
145
+
146
+ private setStatus(status: CloudConnectionStatus): void {
147
+ if (this.status === status) return;
148
+ this.status = status;
149
+ this.callbacks.onStatusChange?.(status);
150
+ }
151
+ }