apteva 0.4.57 → 0.7.1

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 (142) hide show
  1. package/README.md +216 -54
  2. package/cli.js +35 -0
  3. package/install.js +92 -0
  4. package/package.json +15 -76
  5. package/LICENSE +0 -63
  6. package/bin/apteva.js +0 -196
  7. package/dist/ActivityPage.kxzzb4yc.js +0 -3
  8. package/dist/ApiDocsPage.zq998hbm.js +0 -4
  9. package/dist/App.55rea8mn.js +0 -61
  10. package/dist/App.5ywb23z4.js +0 -53
  11. package/dist/App.6thds120.js +0 -4
  12. package/dist/App.9tctxzqm.js +0 -8
  13. package/dist/App.a8r8ttaz.js +0 -4
  14. package/dist/App.agsv5bje.js +0 -4
  15. package/dist/App.cepapqmx.js +0 -4
  16. package/dist/App.dp041gb3.js +0 -221
  17. package/dist/App.fds72zb5.js +0 -4
  18. package/dist/App.fg9qj2dq.js +0 -4
  19. package/dist/App.ndfejbm9.js +0 -4
  20. package/dist/App.nxmfmq1h.js +0 -13
  21. package/dist/App.qdfyt8ba.js +0 -4
  22. package/dist/App.x2d0ygt6.js +0 -4
  23. package/dist/App.yt9p4nr3.js +0 -20
  24. package/dist/App.zn4mw16t.js +0 -1
  25. package/dist/ConnectionsPage.8r96ryw7.js +0 -3
  26. package/dist/McpPage.3cwh0gnd.js +0 -3
  27. package/dist/SettingsPage.ykgdh5ev.js +0 -3
  28. package/dist/SkillsPage.4np1s65b.js +0 -3
  29. package/dist/TasksPage.4g08t7p6.js +0 -3
  30. package/dist/TelemetryPage.72w9pwcp.js +0 -3
  31. package/dist/TestsPage.z4fk3r7r.js +0 -3
  32. package/dist/ThreadsPage.63tcajeh.js +0 -3
  33. package/dist/apteva-kit.css +0 -1
  34. package/dist/icon.png +0 -0
  35. package/dist/index.html +0 -16
  36. package/dist/styles.css +0 -1
  37. package/scripts/postinstall.mjs +0 -102
  38. package/src/auth/index.ts +0 -394
  39. package/src/auth/middleware.ts +0 -213
  40. package/src/binary.ts +0 -536
  41. package/src/channels/index.ts +0 -40
  42. package/src/channels/telegram.ts +0 -311
  43. package/src/crypto.ts +0 -301
  44. package/src/db-tests.ts +0 -174
  45. package/src/db.ts +0 -3133
  46. package/src/integrations/agentdojo.ts +0 -559
  47. package/src/integrations/composio.ts +0 -437
  48. package/src/integrations/index.ts +0 -87
  49. package/src/integrations/skillsmp.ts +0 -318
  50. package/src/mcp-client.ts +0 -605
  51. package/src/mcp-handler.ts +0 -394
  52. package/src/mcp-platform.ts +0 -2403
  53. package/src/openapi.ts +0 -2410
  54. package/src/providers.ts +0 -597
  55. package/src/routes/api/agent-utils.ts +0 -890
  56. package/src/routes/api/agents.ts +0 -916
  57. package/src/routes/api/api-keys.ts +0 -95
  58. package/src/routes/api/channels.ts +0 -182
  59. package/src/routes/api/helpers.ts +0 -12
  60. package/src/routes/api/integrations.ts +0 -639
  61. package/src/routes/api/mcp.ts +0 -574
  62. package/src/routes/api/meta-agent.ts +0 -195
  63. package/src/routes/api/projects.ts +0 -112
  64. package/src/routes/api/providers.ts +0 -424
  65. package/src/routes/api/skills.ts +0 -537
  66. package/src/routes/api/system.ts +0 -333
  67. package/src/routes/api/telemetry.ts +0 -203
  68. package/src/routes/api/tests.ts +0 -148
  69. package/src/routes/api/triggers.ts +0 -518
  70. package/src/routes/api/users.ts +0 -148
  71. package/src/routes/api/webhooks.ts +0 -171
  72. package/src/routes/api.ts +0 -53
  73. package/src/routes/auth.ts +0 -251
  74. package/src/routes/share.ts +0 -86
  75. package/src/routes/static.ts +0 -131
  76. package/src/server.ts +0 -642
  77. package/src/test-runner.ts +0 -598
  78. package/src/triggers/agentdojo.ts +0 -253
  79. package/src/triggers/composio.ts +0 -264
  80. package/src/triggers/index.ts +0 -71
  81. package/src/tui/AgentList.tsx +0 -145
  82. package/src/tui/App.tsx +0 -102
  83. package/src/tui/Login.tsx +0 -104
  84. package/src/tui/api.ts +0 -72
  85. package/src/tui/index.tsx +0 -7
  86. package/src/web/App.tsx +0 -455
  87. package/src/web/components/activity/ActivityPage.tsx +0 -314
  88. package/src/web/components/activity/index.ts +0 -1
  89. package/src/web/components/agents/AgentCard.tsx +0 -189
  90. package/src/web/components/agents/AgentPanel.tsx +0 -2244
  91. package/src/web/components/agents/AgentsView.tsx +0 -180
  92. package/src/web/components/agents/CreateAgentModal.tsx +0 -475
  93. package/src/web/components/agents/index.ts +0 -4
  94. package/src/web/components/api/ApiDocsPage.tsx +0 -842
  95. package/src/web/components/auth/CreateAccountStep.tsx +0 -176
  96. package/src/web/components/auth/LoginPage.tsx +0 -91
  97. package/src/web/components/auth/index.ts +0 -2
  98. package/src/web/components/common/Icons.tsx +0 -250
  99. package/src/web/components/common/LoadingSpinner.tsx +0 -44
  100. package/src/web/components/common/Modal.tsx +0 -199
  101. package/src/web/components/common/Select.tsx +0 -97
  102. package/src/web/components/common/index.ts +0 -20
  103. package/src/web/components/connections/ConnectionsPage.tsx +0 -54
  104. package/src/web/components/connections/IntegrationsTab.tsx +0 -170
  105. package/src/web/components/connections/OverviewTab.tsx +0 -137
  106. package/src/web/components/connections/TriggersTab.tsx +0 -1346
  107. package/src/web/components/dashboard/Dashboard.tsx +0 -572
  108. package/src/web/components/dashboard/index.ts +0 -1
  109. package/src/web/components/index.ts +0 -21
  110. package/src/web/components/layout/ErrorBanner.tsx +0 -18
  111. package/src/web/components/layout/Header.tsx +0 -332
  112. package/src/web/components/layout/Sidebar.tsx +0 -231
  113. package/src/web/components/layout/index.ts +0 -3
  114. package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
  115. package/src/web/components/mcp/McpPage.tsx +0 -2515
  116. package/src/web/components/mcp/index.ts +0 -1
  117. package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
  118. package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
  119. package/src/web/components/onboarding/index.ts +0 -1
  120. package/src/web/components/settings/SettingsPage.tsx +0 -2776
  121. package/src/web/components/settings/index.ts +0 -1
  122. package/src/web/components/skills/SkillsPage.tsx +0 -1200
  123. package/src/web/components/tasks/TasksPage.tsx +0 -1116
  124. package/src/web/components/tasks/index.ts +0 -1
  125. package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
  126. package/src/web/components/tests/TestsPage.tsx +0 -594
  127. package/src/web/components/threads/ThreadsPage.tsx +0 -315
  128. package/src/web/context/AuthContext.tsx +0 -242
  129. package/src/web/context/ProjectContext.tsx +0 -214
  130. package/src/web/context/TelemetryContext.tsx +0 -299
  131. package/src/web/context/ThemeContext.tsx +0 -90
  132. package/src/web/context/UIModeContext.tsx +0 -49
  133. package/src/web/context/index.ts +0 -12
  134. package/src/web/hooks/index.ts +0 -3
  135. package/src/web/hooks/useAgents.ts +0 -115
  136. package/src/web/hooks/useOnboarding.ts +0 -20
  137. package/src/web/hooks/useProviders.ts +0 -75
  138. package/src/web/icon.png +0 -0
  139. package/src/web/index.html +0 -16
  140. package/src/web/styles.css +0 -118
  141. package/src/web/themes.ts +0 -162
  142. package/src/web/types.ts +0 -298
@@ -1,424 +0,0 @@
1
- import { existsSync } from "fs";
2
- import { json, debug } from "./helpers";
3
-
4
- // Detect if running inside a Docker container
5
- async function isRunningInDocker(): Promise<boolean> {
6
- try {
7
- return existsSync("/.dockerenv") || existsSync("/run/.containerenv");
8
- } catch {
9
- return false;
10
- }
11
- }
12
- import { startAgentProcess, setAgentStatus } from "./agent-utils";
13
- import { AgentDB, UserDB } from "../../db";
14
- import { ProviderKeys, Onboarding, getProvidersWithStatus, PROVIDERS, type ProviderId } from "../../providers";
15
- import { createUser } from "../../auth";
16
- import { agentProcesses } from "../../server";
17
-
18
- export async function handleProviderRoutes(
19
- req: Request,
20
- path: string,
21
- method: string,
22
- authContext?: unknown,
23
- ): Promise<Response | null> {
24
- // ==================== PROVIDERS ====================
25
-
26
- // GET /api/providers - List supported providers and models with key status
27
- if (path === "/api/providers" && method === "GET") {
28
- const providers = getProvidersWithStatus();
29
- return json({ providers });
30
- }
31
-
32
- // GET /api/providers/ollama/models - Fetch available models from Ollama
33
- if (path === "/api/providers/ollama/models" && method === "GET") {
34
- // Get configured Ollama base URL or use default
35
- const ollamaUrl = ProviderKeys.getDecrypted("ollama") || "http://localhost:11434";
36
-
37
- try {
38
- const response = await fetch(`${ollamaUrl}/api/tags`, {
39
- method: "GET",
40
- headers: { "Accept": "application/json" },
41
- });
42
-
43
- if (!response.ok) {
44
- return json({ error: "Failed to connect to Ollama", models: [] }, 200);
45
- }
46
-
47
- const data = await response.json() as { models?: Array<{ name: string; size: number; modified_at: string }> };
48
- const models = (data.models || []).map((m: { name: string; size: number }) => ({
49
- value: m.name,
50
- label: m.name,
51
- size: m.size,
52
- }));
53
-
54
- return json({ models, connected: true });
55
- } catch (err) {
56
- // Ollama not running or not reachable
57
- return json({
58
- error: "Ollama not reachable. Make sure Ollama is running.",
59
- models: [],
60
- connected: false,
61
- }, 200);
62
- }
63
- }
64
-
65
- // GET /api/providers/ollama/status - Check if Ollama is running
66
- if (path === "/api/providers/ollama/status" && method === "GET") {
67
- const ollamaUrl = ProviderKeys.getDecrypted("ollama") || "http://localhost:11434";
68
- const isDocker = await isRunningInDocker();
69
-
70
- try {
71
- const response = await fetch(`${ollamaUrl}/api/tags`, {
72
- method: "GET",
73
- signal: AbortSignal.timeout(3000),
74
- });
75
-
76
- if (response.ok) {
77
- const data = await response.json() as { models?: Array<{ name: string }> };
78
- return json({
79
- connected: true,
80
- url: ollamaUrl,
81
- modelCount: data.models?.length || 0,
82
- isDocker,
83
- });
84
- }
85
- return json({ connected: false, url: ollamaUrl, error: "Ollama not responding", isDocker });
86
- } catch {
87
- return json({ connected: false, url: ollamaUrl, error: "Ollama not reachable", isDocker });
88
- }
89
- }
90
-
91
- // POST /api/providers/ollama/install - Install Ollama automatically
92
- if (path === "/api/providers/ollama/install" && method === "POST") {
93
- // Don't allow install inside Docker containers
94
- if (await isRunningInDocker()) {
95
- return json({ success: false, error: "Cannot install Ollama inside a Docker container. Configure an external Ollama URL instead." }, 400);
96
- }
97
-
98
- // Only supported on Linux/macOS
99
- const platform = process.platform;
100
- if (platform !== "linux" && platform !== "darwin") {
101
- return json({ success: false, error: "Auto-install is only supported on Linux and macOS. Please download from https://ollama.com/download" }, 400);
102
- }
103
-
104
- // Check if already installed
105
- try {
106
- const which = Bun.spawnSync(["which", "ollama"]);
107
- if (which.exitCode === 0) {
108
- // Already installed, just make sure it's running
109
- Bun.spawn(["ollama", "serve"], { stdout: "ignore", stderr: "ignore" });
110
- // Wait a moment for it to start
111
- await new Promise(r => setTimeout(r, 2000));
112
- return json({ success: true, message: "Ollama is already installed", alreadyInstalled: true });
113
- }
114
- } catch { /* not installed */ }
115
-
116
- // Install Ollama using official install script
117
- try {
118
- const proc = Bun.spawnSync(["bash", "-c", "curl -fsSL https://ollama.com/install.sh | sh"], {
119
- timeout: 120_000, // 2 minute timeout
120
- stderr: "pipe",
121
- stdout: "pipe",
122
- });
123
-
124
- if (proc.exitCode !== 0) {
125
- const stderr = proc.stderr.toString().trim();
126
- return json({ success: false, error: `Installation failed: ${stderr || "Unknown error"}` }, 500);
127
- }
128
-
129
- // Start Ollama serve in background
130
- Bun.spawn(["ollama", "serve"], { stdout: "ignore", stderr: "ignore" });
131
-
132
- // Wait for it to come up
133
- await new Promise(r => setTimeout(r, 3000));
134
-
135
- // Verify it's running
136
- try {
137
- const check = await fetch("http://localhost:11434/api/tags", { signal: AbortSignal.timeout(3000) });
138
- if (check.ok) {
139
- return json({ success: true, message: "Ollama installed and running" });
140
- }
141
- } catch { /* may still be starting */ }
142
-
143
- return json({ success: true, message: "Ollama installed. It may take a moment to start." });
144
- } catch (err: any) {
145
- return json({ success: false, error: `Installation failed: ${err.message}` }, 500);
146
- }
147
- }
148
-
149
- // POST /api/providers/ollama/pull - Pull a model
150
- if (path === "/api/providers/ollama/pull" && method === "POST") {
151
- const body = await req.json() as { model?: string };
152
- const model = body.model;
153
- if (!model) {
154
- return json({ error: "Model name required" }, 400);
155
- }
156
-
157
- try {
158
- const ollamaUrl = ProviderKeys.getDecrypted("ollama") || "http://localhost:11434";
159
- const response = await fetch(`${ollamaUrl}/api/pull`, {
160
- method: "POST",
161
- headers: { "Content-Type": "application/json" },
162
- body: JSON.stringify({ name: model }),
163
- });
164
-
165
- if (!response.ok) {
166
- return json({ success: false, error: "Failed to start model pull" }, 500);
167
- }
168
-
169
- return json({ success: true, message: `Pulling ${model}...` });
170
- } catch (err: any) {
171
- return json({ success: false, error: `Failed to pull model: ${err.message}` }, 500);
172
- }
173
- }
174
-
175
- // ==================== LOCAL VOICE PROVIDERS ====================
176
-
177
- // GET /api/providers/:id/status - Check if a local voice provider is running
178
- const localVoiceStatusMatch = path.match(/^\/api\/providers\/(speaches|whisper_cpp|kokoro|piper|fish_speech)\/status$/);
179
- if (localVoiceStatusMatch && method === "GET") {
180
- const providerId = localVoiceStatusMatch[1];
181
- const providerDef = PROVIDERS[providerId as ProviderId];
182
- const baseUrl = ProviderKeys.getDecrypted(providerId) ||
183
- ("defaultBaseUrl" in providerDef ? (providerDef as any).defaultBaseUrl : "");
184
- const isDocker = await isRunningInDocker();
185
-
186
- const healthEndpoints: Record<string, string> = {
187
- speaches: "/v1/models",
188
- whisper_cpp: "/",
189
- kokoro: "/v1/models",
190
- piper: "/api/voices",
191
- fish_speech: "/v1/models",
192
- };
193
-
194
- try {
195
- const response = await fetch(`${baseUrl}${healthEndpoints[providerId]}`, {
196
- method: "GET",
197
- signal: AbortSignal.timeout(3000),
198
- });
199
- if (response.ok) {
200
- return json({ connected: true, url: baseUrl, isDocker });
201
- }
202
- return json({ connected: false, url: baseUrl, error: "Not responding", isDocker });
203
- } catch {
204
- return json({ connected: false, url: baseUrl, error: "Not reachable", isDocker });
205
- }
206
- }
207
-
208
- // GET /api/providers/:id/models - Fetch available models from a local voice provider
209
- const localVoiceModelsMatch = path.match(/^\/api\/providers\/(speaches|kokoro|fish_speech)\/models$/);
210
- if (localVoiceModelsMatch && method === "GET") {
211
- const providerId = localVoiceModelsMatch[1];
212
- const providerDef = PROVIDERS[providerId as ProviderId];
213
- const baseUrl = ProviderKeys.getDecrypted(providerId) ||
214
- ("defaultBaseUrl" in providerDef ? (providerDef as any).defaultBaseUrl : "");
215
-
216
- try {
217
- const response = await fetch(`${baseUrl}/v1/models`, {
218
- method: "GET",
219
- headers: { "Accept": "application/json" },
220
- signal: AbortSignal.timeout(5000),
221
- });
222
-
223
- if (!response.ok) {
224
- return json({ error: "Failed to connect", models: [], connected: false }, 200);
225
- }
226
-
227
- const data = await response.json() as { data?: Array<{ id: string }> };
228
- const models = (data.data || []).map((m: { id: string }) => ({
229
- value: m.id,
230
- label: m.id,
231
- }));
232
-
233
- return json({ models, connected: true });
234
- } catch {
235
- return json({ error: "Not reachable", models: [], connected: false }, 200);
236
- }
237
- }
238
-
239
- // ==================== ONBOARDING ====================
240
-
241
- // GET /api/onboarding/status - Check onboarding status
242
- if (path === "/api/onboarding/status" && method === "GET") {
243
- return json(Onboarding.getStatus());
244
- }
245
-
246
- // POST /api/onboarding/complete - Mark onboarding as complete
247
- if (path === "/api/onboarding/complete" && method === "POST") {
248
- Onboarding.complete();
249
- return json({ success: true });
250
- }
251
-
252
- // POST /api/onboarding/reset - Reset onboarding (for testing)
253
- if (path === "/api/onboarding/reset" && method === "POST") {
254
- Onboarding.reset();
255
- return json({ success: true });
256
- }
257
-
258
- // POST /api/onboarding/user - Create first user during onboarding
259
- // This endpoint only works when no users exist (enforced by middleware)
260
- if (path === "/api/onboarding/user" && method === "POST") {
261
- debug("POST /api/onboarding/user");
262
- // Double-check no users exist
263
- if (UserDB.hasUsers()) {
264
- debug("Users already exist");
265
- return json({ error: "Users already exist" }, 403);
266
- }
267
-
268
- try {
269
- const body = await req.json();
270
- debug("Onboarding body:", JSON.stringify(body));
271
- const { username, password, email } = body;
272
-
273
- if (!username || !password) {
274
- debug("Missing username or password");
275
- return json({ error: "Username and password are required" }, 400);
276
- }
277
-
278
- // Create first user as admin
279
- debug("Creating user:", username);
280
- const result = await createUser({
281
- username,
282
- password,
283
- email: email || undefined, // Optional, for password recovery
284
- role: "admin",
285
- });
286
- debug("Create user result:", result.success, result.error);
287
-
288
- if (!result.success) {
289
- return json({ error: result.error }, 400);
290
- }
291
-
292
- return json({
293
- success: true,
294
- user: {
295
- id: result.user!.id,
296
- username: result.user!.username,
297
- role: result.user!.role,
298
- },
299
- }, 201);
300
- } catch (e) {
301
- debug("Onboarding error:", e);
302
- return json({ error: "Invalid request body" }, 400);
303
- }
304
- }
305
-
306
- // ==================== API KEYS ====================
307
-
308
- // GET /api/keys - List all configured provider keys (without actual keys)
309
- if (path === "/api/keys" && method === "GET") {
310
- return json({ keys: ProviderKeys.getAll() });
311
- }
312
-
313
- // GET /api/keys/:provider - Get all keys for a provider
314
- const getProviderKeysMatch = path.match(/^\/api\/keys\/([^/]+)$/);
315
-
316
- // POST /api/keys/:provider/test - Test an API key (must match before generic :provider routes)
317
- const testKeyMatch = path.match(/^\/api\/keys\/([^/]+)\/test$/);
318
- if (testKeyMatch && method === "POST") {
319
- const providerId = testKeyMatch[1];
320
-
321
- // Validate provider exists
322
- if (!PROVIDERS[providerId as ProviderId]) {
323
- return json({ error: "Unknown provider" }, 400);
324
- }
325
-
326
- try {
327
- const body = await req.json().catch(() => ({}));
328
- const { key } = body as { key?: string };
329
-
330
- // Test with provided key or stored key
331
- const result = await ProviderKeys.test(providerId, key);
332
- return json(result);
333
- } catch (e) {
334
- return json({ error: "Test failed" }, 500);
335
- }
336
- }
337
-
338
- // DELETE /api/keys/by-id/:id - Remove a specific API key by ID
339
- const deleteKeyByIdMatch = path.match(/^\/api\/keys\/by-id\/([^/]+)$/);
340
- if (deleteKeyByIdMatch && method === "DELETE") {
341
- const keyId = deleteKeyByIdMatch[1];
342
- const deleted = ProviderKeys.deleteById(keyId);
343
- return json({ success: deleted });
344
- }
345
-
346
- if (getProviderKeysMatch && method === "GET") {
347
- const providerId = getProviderKeysMatch[1];
348
- const keys = ProviderKeys.getAllByProvider(providerId);
349
- return json({ keys });
350
- }
351
-
352
- // POST /api/keys/:provider - Save an API key for a provider
353
- if (getProviderKeysMatch && method === "POST") {
354
- const providerId = getProviderKeysMatch[1];
355
-
356
- // Validate provider exists
357
- if (!PROVIDERS[providerId as ProviderId]) {
358
- return json({ error: "Unknown provider" }, 400);
359
- }
360
-
361
- try {
362
- const body = await req.json();
363
- const { key, project_id, name } = body as { key?: string; project_id?: string | null; name?: string | null };
364
-
365
- if (!key) {
366
- return json({ error: "API key is required" }, 400);
367
- }
368
-
369
- const result = await ProviderKeys.save(providerId, key, project_id || null, name || null);
370
- if (!result.success) {
371
- return json({ error: result.error }, 400);
372
- }
373
-
374
- // Restart any running agents that use this provider (including meta agent)
375
- const runningAgents = AgentDB.findAll().filter(
376
- a => a.status === "running" && a.provider === providerId
377
- );
378
-
379
- // Stop all agents first
380
- for (const agent of runningAgents) {
381
- const agentProc = agentProcesses.get(agent.id);
382
- if (agentProc) {
383
- agentProc.proc.kill();
384
- agentProcesses.delete(agent.id);
385
- }
386
- setAgentStatus(agent.id, "stopped", "provider_restart");
387
- }
388
-
389
- // Wait once for ports to be released
390
- if (runningAgents.length > 0) {
391
- await new Promise(r => setTimeout(r, 500));
392
- }
393
-
394
- // Restart all agents in parallel
395
- const restartResults = await Promise.all(
396
- runningAgents.map(async (agent) => {
397
- try {
398
- const startResult = await startAgentProcess(agent, { silent: true });
399
- return { id: agent.id, name: agent.name, success: startResult.success, error: startResult.error };
400
- } catch (e) {
401
- return { id: agent.id, name: agent.name, success: false, error: String(e) };
402
- }
403
- })
404
- );
405
-
406
- return json({
407
- success: true,
408
- message: "API key saved successfully",
409
- restartedAgents: restartResults.length > 0 ? restartResults : undefined,
410
- });
411
- } catch (e) {
412
- return json({ error: "Invalid request body" }, 400);
413
- }
414
- }
415
-
416
- // DELETE /api/keys/:provider - Remove a global API key
417
- if (getProviderKeysMatch && method === "DELETE") {
418
- const providerId = getProviderKeysMatch[1];
419
- const deleted = ProviderKeys.delete(providerId);
420
- return json({ success: deleted });
421
- }
422
-
423
- return null;
424
- }