@elizaos/plugin-elizacloud 2.0.0-alpha.7 → 2.0.0-beta.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.
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/auto-enable.ts +17 -0
- package/dist/browser/index.browser.js +2 -21
- package/dist/browser/index.browser.js.map +5 -37
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.node.cjs +12173 -2271
- package/dist/cjs/index.node.js.map +135 -27
- package/dist/cloud/auth.d.ts +19 -0
- package/dist/cloud/auth.d.ts.map +1 -0
- package/dist/cloud/auth.js +330 -0
- package/dist/cloud/auth.js.map +12 -0
- package/dist/cloud/backup.d.ts +18 -0
- package/dist/cloud/backup.d.ts.map +1 -0
- package/dist/cloud/backup.js +63 -0
- package/dist/cloud/backup.js.map +10 -0
- package/dist/cloud/base-url.d.ts +3 -0
- package/dist/cloud/base-url.d.ts.map +1 -0
- package/dist/cloud/base-url.js +77 -0
- package/dist/cloud/base-url.js.map +10 -0
- package/dist/cloud/bridge-client.d.ts +126 -0
- package/dist/cloud/bridge-client.d.ts.map +1 -0
- package/dist/cloud/bridge-client.js +432 -0
- package/dist/cloud/bridge-client.js.map +11 -0
- package/dist/cloud/cloud-api-key.d.ts +26 -0
- package/dist/cloud/cloud-api-key.d.ts.map +1 -0
- package/dist/cloud/cloud-api-key.js +60 -0
- package/dist/cloud/cloud-api-key.js.map +10 -0
- package/dist/cloud/cloud-manager.d.ts +33 -0
- package/dist/cloud/cloud-manager.d.ts.map +1 -0
- package/dist/cloud/cloud-manager.js +853 -0
- package/dist/cloud/cloud-manager.js.map +16 -0
- package/dist/cloud/cloud-proxy.d.ts +20 -0
- package/dist/cloud/cloud-proxy.d.ts.map +1 -0
- package/dist/cloud/cloud-proxy.js +54 -0
- package/dist/cloud/cloud-proxy.js.map +10 -0
- package/dist/cloud/cloud-wallet.d.ts +94 -0
- package/dist/cloud/cloud-wallet.d.ts.map +1 -0
- package/dist/cloud/cloud-wallet.js +5195 -0
- package/dist/cloud/cloud-wallet.js.map +92 -0
- package/dist/cloud/index.d.ts +9 -0
- package/dist/cloud/index.d.ts.map +1 -0
- package/dist/cloud/index.js +30 -0
- package/dist/cloud/index.js.map +9 -0
- package/dist/cloud/reconnect.d.ts +26 -0
- package/dist/cloud/reconnect.d.ts.map +1 -0
- package/dist/cloud/reconnect.js +104 -0
- package/dist/cloud/reconnect.js.map +10 -0
- package/dist/cloud/validate-url.d.ts +2 -0
- package/dist/cloud/validate-url.d.ts.map +1 -0
- package/dist/cloud/validate-url.js +174 -0
- package/dist/cloud/validate-url.js.map +10 -0
- package/dist/cloud-providers/cloud-status.d.ts.map +1 -1
- package/dist/cloud-providers/cloud-status.js +78 -0
- package/dist/cloud-providers/cloud-status.js.map +10 -0
- package/dist/cloud-providers/container-health.d.ts.map +1 -1
- package/dist/cloud-providers/container-health.js +74 -0
- package/dist/cloud-providers/container-health.js.map +10 -0
- package/dist/cloud-providers/credit-balance.d.ts.map +1 -1
- package/dist/cloud-providers/credit-balance.js +85 -0
- package/dist/cloud-providers/credit-balance.js.map +10 -0
- package/dist/cloud-providers/index.d.ts.map +1 -1
- package/dist/cloud-providers/index.js +24 -0
- package/dist/cloud-providers/index.js.map +9 -0
- package/dist/cloud-providers/model-registry.d.ts.map +1 -1
- package/dist/cloud-providers/model-registry.js +71 -0
- package/dist/cloud-providers/model-registry.js.map +10 -0
- package/dist/index.browser.d.ts +4 -2
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12851 -0
- package/dist/index.js.map +145 -0
- package/dist/index.node.d.ts +15 -2
- package/dist/index.node.d.ts.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +169 -0
- package/dist/init.js.map +12 -0
- package/dist/lib/cloud-connection.d.ts +78 -0
- package/dist/lib/cloud-connection.d.ts.map +1 -0
- package/dist/lib/cloud-connection.js +731 -0
- package/dist/lib/cloud-connection.js.map +14 -0
- package/dist/lib/cloud-secrets.d.ts +23 -0
- package/dist/lib/cloud-secrets.d.ts.map +1 -0
- package/dist/lib/cloud-secrets.js +64 -0
- package/dist/lib/cloud-secrets.js.map +10 -0
- package/dist/lib/config-env.d.ts +5 -0
- package/dist/lib/config-env.d.ts.map +1 -0
- package/dist/lib/config-env.js +191 -0
- package/dist/lib/config-env.js.map +11 -0
- package/dist/lib/config-like.d.ts +40 -0
- package/dist/lib/config-like.d.ts.map +1 -0
- package/dist/lib/config-like.js +103 -0
- package/dist/lib/config-like.js.map +10 -0
- package/dist/lib/credential-type-map.d.ts +53 -0
- package/dist/lib/credential-type-map.d.ts.map +1 -0
- package/dist/lib/credential-type-map.js +88 -0
- package/dist/lib/credential-type-map.js.map +10 -0
- package/dist/lib/feature-flags.d.ts +2 -0
- package/dist/lib/feature-flags.d.ts.map +1 -0
- package/dist/lib/feature-flags.js +40 -0
- package/dist/lib/feature-flags.js.map +10 -0
- package/dist/lib/http.d.ts +22 -0
- package/dist/lib/http.d.ts.map +1 -0
- package/dist/lib/http.js +107 -0
- package/dist/lib/http.js.map +10 -0
- package/dist/lib/server-cloud-tts.d.ts +34 -0
- package/dist/lib/server-cloud-tts.d.ts.map +1 -0
- package/dist/lib/server-cloud-tts.js +549 -0
- package/dist/lib/server-cloud-tts.js.map +13 -0
- package/dist/lib/state-paths.d.ts +4 -0
- package/dist/lib/state-paths.d.ts.map +1 -0
- package/dist/lib/state-paths.js +52 -0
- package/dist/lib/state-paths.js.map +10 -0
- package/dist/lib/tts-debug.d.ts +4 -0
- package/dist/lib/tts-debug.d.ts.map +1 -0
- package/dist/lib/tts-debug.js +57 -0
- package/dist/lib/tts-debug.js.map +10 -0
- package/dist/models/embeddings.d.ts.map +1 -1
- package/dist/models/embeddings.js +319 -0
- package/dist/models/embeddings.js.map +13 -0
- package/dist/models/image.d.ts.map +1 -1
- package/dist/models/image.js +374 -0
- package/dist/models/image.js.map +14 -0
- package/dist/models/index.d.ts +1 -2
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +1386 -0
- package/dist/models/index.js.map +20 -0
- package/dist/models/research.d.ts.map +1 -1
- package/dist/models/research.js +324 -0
- package/dist/models/research.js.map +13 -0
- package/dist/models/speech.d.ts.map +1 -1
- package/dist/models/speech.js +273 -0
- package/dist/models/speech.js.map +13 -0
- package/dist/models/text.d.ts +5 -2
- package/dist/models/text.d.ts.map +1 -1
- package/dist/models/text.js +803 -0
- package/dist/models/text.js.map +15 -0
- package/dist/models/tokenization.d.ts.map +1 -1
- package/dist/models/tokenization.js +65 -0
- package/dist/models/tokenization.js.map +10 -0
- package/dist/models/transcription.d.ts.map +1 -1
- package/dist/models/transcription.js +283 -0
- package/dist/models/transcription.js.map +13 -0
- package/dist/node/index.d.ts +2 -2
- package/dist/node/index.node.js +12171 -2266
- package/dist/node/index.node.js.map +135 -27
- package/dist/onboarding.d.ts +35 -0
- package/dist/onboarding.d.ts.map +1 -0
- package/dist/onboarding.js +883 -0
- package/dist/onboarding.js.map +14 -0
- package/dist/plugin.d.ts +20 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +7611 -0
- package/dist/plugin.js.map +104 -0
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +127 -0
- package/dist/providers/openai.js.map +11 -0
- package/dist/register-routes.d.ts +2 -0
- package/dist/register-routes.d.ts.map +1 -0
- package/dist/register-routes.js +7612 -0
- package/dist/register-routes.js.map +105 -0
- package/dist/routes/cloud-billing-routes.d.ts +9 -0
- package/dist/routes/cloud-billing-routes.d.ts.map +1 -0
- package/dist/routes/cloud-billing-routes.js +807 -0
- package/dist/routes/cloud-billing-routes.js.map +14 -0
- package/dist/routes/cloud-compat-routes.d.ts +10 -0
- package/dist/routes/cloud-compat-routes.d.ts.map +1 -0
- package/dist/routes/cloud-compat-routes.js +538 -0
- package/dist/routes/cloud-compat-routes.js.map +14 -0
- package/dist/routes/cloud-features-routes.d.ts +9 -0
- package/dist/routes/cloud-features-routes.d.ts.map +1 -0
- package/dist/routes/cloud-features-routes.js +124 -0
- package/dist/routes/cloud-features-routes.js.map +11 -0
- package/dist/routes/cloud-provisioning.d.ts +14 -0
- package/dist/routes/cloud-provisioning.d.ts.map +1 -0
- package/dist/routes/cloud-provisioning.js +37 -0
- package/dist/routes/cloud-provisioning.js.map +10 -0
- package/dist/routes/cloud-relay-routes.d.ts +22 -0
- package/dist/routes/cloud-relay-routes.d.ts.map +1 -0
- package/dist/routes/cloud-relay-routes.js +60 -0
- package/dist/routes/cloud-relay-routes.js.map +10 -0
- package/dist/routes/cloud-routes-autonomous.d.ts +83 -0
- package/dist/routes/cloud-routes-autonomous.d.ts.map +1 -0
- package/dist/routes/cloud-routes-autonomous.js +6134 -0
- package/dist/routes/cloud-routes-autonomous.js.map +97 -0
- package/dist/routes/cloud-routes.d.ts +35 -0
- package/dist/routes/cloud-routes.d.ts.map +1 -0
- package/dist/routes/cloud-routes.js +6888 -0
- package/dist/routes/cloud-routes.js.map +100 -0
- package/dist/routes/cloud-status-routes-autonomous.d.ts +15 -0
- package/dist/routes/cloud-status-routes-autonomous.d.ts.map +1 -0
- package/dist/routes/cloud-status-routes-autonomous.js +396 -0
- package/dist/routes/cloud-status-routes-autonomous.js.map +13 -0
- package/dist/routes/cloud-status-routes.d.ts +4 -0
- package/dist/routes/cloud-status-routes.d.ts.map +1 -0
- package/dist/routes/cloud-status-routes.js +771 -0
- package/dist/routes/cloud-status-routes.js.map +15 -0
- package/dist/services/cloud-auth.d.ts +140 -5
- package/dist/services/cloud-auth.d.ts.map +1 -1
- package/dist/services/cloud-auth.js +363 -0
- package/dist/services/cloud-auth.js.map +12 -0
- package/dist/services/cloud-backup.d.ts.map +1 -1
- package/dist/services/cloud-backup.js +176 -0
- package/dist/services/cloud-backup.js.map +11 -0
- package/dist/services/cloud-bootstrap.d.ts +38 -0
- package/dist/services/cloud-bootstrap.d.ts.map +1 -0
- package/dist/services/cloud-bootstrap.js +84 -0
- package/dist/services/cloud-bootstrap.js.map +10 -0
- package/dist/services/cloud-bridge.d.ts +1 -1
- package/dist/services/cloud-bridge.d.ts.map +1 -1
- package/dist/services/cloud-bridge.js +308 -0
- package/dist/services/cloud-bridge.js.map +11 -0
- package/dist/services/cloud-container.d.ts.map +1 -1
- package/dist/services/cloud-container.js +241 -0
- package/dist/services/cloud-container.js.map +11 -0
- package/dist/services/cloud-credential-provider.d.ts +55 -0
- package/dist/services/cloud-credential-provider.d.ts.map +1 -0
- package/dist/services/cloud-credential-provider.js +190 -0
- package/dist/services/cloud-credential-provider.js.map +11 -0
- package/dist/services/cloud-managed-gateway-relay.d.ts +38 -0
- package/dist/services/cloud-managed-gateway-relay.d.ts.map +1 -0
- package/dist/services/cloud-managed-gateway-relay.js +479 -0
- package/dist/services/cloud-managed-gateway-relay.js.map +10 -0
- package/dist/services/cloud-model-registry.d.ts.map +1 -1
- package/dist/services/cloud-model-registry.js +175 -0
- package/dist/services/cloud-model-registry.js.map +10 -0
- package/dist/services/index.d.ts +3 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +29 -0
- package/dist/services/index.js.map +9 -0
- package/dist/types/cloud.d.ts +41 -19
- package/dist/types/cloud.d.ts.map +1 -1
- package/dist/types/cloud.js +52 -0
- package/dist/types/cloud.js.map +10 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +24 -0
- package/dist/types/index.js.map +9 -0
- package/dist/utils/cloud-api.d.ts +2 -27
- package/dist/utils/cloud-api.d.ts.map +1 -1
- package/dist/utils/cloud-api.js +33 -0
- package/dist/utils/cloud-api.js.map +10 -0
- package/dist/utils/cloud-sdk/client.d.ts +133 -0
- package/dist/utils/cloud-sdk/client.d.ts.map +1 -0
- package/dist/utils/cloud-sdk/client.js +3561 -0
- package/dist/utils/cloud-sdk/client.js.map +13 -0
- package/dist/utils/cloud-sdk/http.d.ts +37 -0
- package/dist/utils/cloud-sdk/http.d.ts.map +1 -0
- package/dist/utils/cloud-sdk/http.js +237 -0
- package/dist/utils/cloud-sdk/http.js.map +11 -0
- package/dist/utils/cloud-sdk/index.d.ts +6 -0
- package/dist/utils/cloud-sdk/index.d.ts.map +1 -0
- package/dist/utils/cloud-sdk/index.js +29 -0
- package/dist/utils/cloud-sdk/index.js.map +9 -0
- package/dist/utils/cloud-sdk/public-routes.d.ts +5377 -0
- package/dist/utils/cloud-sdk/public-routes.d.ts.map +1 -0
- package/dist/utils/cloud-sdk/public-routes.js +2950 -0
- package/dist/utils/cloud-sdk/public-routes.js.map +10 -0
- package/dist/utils/cloud-sdk/types.cloud-api.d.ts +101 -0
- package/dist/utils/cloud-sdk/types.cloud-api.d.ts.map +1 -0
- package/dist/utils/cloud-sdk/types.cloud-api.js +2 -0
- package/dist/utils/cloud-sdk/types.cloud-api.js.map +9 -0
- package/dist/utils/cloud-sdk/types.d.ts +655 -0
- package/dist/utils/cloud-sdk/types.d.ts.map +1 -0
- package/dist/utils/cloud-sdk/types.js +29 -0
- package/dist/utils/cloud-sdk/types.js.map +10 -0
- package/dist/utils/config.d.ts +7 -3
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +137 -0
- package/dist/utils/config.js.map +10 -0
- package/dist/utils/events.d.ts.map +1 -1
- package/dist/utils/events.js +43 -0
- package/dist/utils/events.js.map +10 -0
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +103 -0
- package/dist/utils/helpers.js.map +10 -0
- package/dist/utils/responses-output.d.ts +13 -0
- package/dist/utils/responses-output.d.ts.map +1 -0
- package/dist/utils/responses-output.js +102 -0
- package/dist/utils/responses-output.js.map +10 -0
- package/dist/utils/sdk-client.d.ts +5 -0
- package/dist/utils/sdk-client.d.ts.map +1 -0
- package/dist/utils/sdk-client.js +144 -0
- package/dist/utils/sdk-client.js.map +11 -0
- package/package.json +108 -19
- package/src/cloud/auth.ts +175 -0
- package/src/cloud/backup.ts +46 -0
- package/src/cloud/base-url.ts +62 -0
- package/src/cloud/bridge-client.ts +602 -0
- package/src/cloud/cloud-api-key.ts +80 -0
- package/src/cloud/cloud-manager.ts +163 -0
- package/src/cloud/cloud-proxy.ts +52 -0
- package/src/cloud/cloud-wallet.ts +341 -0
- package/src/cloud/index.ts +28 -0
- package/src/cloud/reconnect.ts +111 -0
- package/src/cloud/validate-url.ts +181 -0
- package/src/cloud-providers/cloud-status.ts +75 -0
- package/src/cloud-providers/container-health.ts +68 -0
- package/src/cloud-providers/credit-balance.ts +70 -0
- package/src/cloud-providers/index.ts +3 -0
- package/src/cloud-providers/model-registry.ts +74 -0
- package/src/index.browser.ts +10 -0
- package/src/index.node.ts +39 -0
- package/src/index.ts +347 -0
- package/src/init.ts +39 -0
- package/src/lib/cloud-connection.ts +663 -0
- package/src/lib/cloud-secrets.ts +58 -0
- package/src/lib/config-env.ts +168 -0
- package/src/lib/config-like.ts +149 -0
- package/src/lib/credential-type-map.ts +130 -0
- package/src/lib/feature-flags.ts +26 -0
- package/src/lib/http.ts +139 -0
- package/src/lib/server-cloud-tts.ts +609 -0
- package/src/lib/state-paths.ts +28 -0
- package/src/lib/tts-debug.ts +34 -0
- package/src/models/embeddings.ts +234 -0
- package/src/models/image.ts +219 -0
- package/src/models/index.ts +16 -0
- package/src/models/research.ts +265 -0
- package/src/models/speech.ts +78 -0
- package/src/models/text.ts +899 -0
- package/src/models/tokenization.ts +67 -0
- package/src/models/transcription.ts +97 -0
- package/src/onboarding.ts +396 -0
- package/src/plugin.ts +243 -0
- package/src/providers/openai.ts +16 -0
- package/src/register-routes.ts +6 -0
- package/src/routes/cloud-billing-routes.ts +754 -0
- package/src/routes/cloud-compat-routes.ts +314 -0
- package/src/routes/cloud-features-routes.ts +57 -0
- package/src/routes/cloud-provisioning.ts +37 -0
- package/src/routes/cloud-relay-routes.ts +89 -0
- package/src/routes/cloud-routes-autonomous.ts +996 -0
- package/src/routes/cloud-routes.ts +576 -0
- package/src/routes/cloud-status-routes-autonomous.ts +234 -0
- package/src/routes/cloud-status-routes.ts +73 -0
- package/src/services/cloud-auth.ts +567 -0
- package/src/services/cloud-backup.ts +208 -0
- package/src/services/cloud-bootstrap.ts +108 -0
- package/src/services/cloud-bridge.ts +386 -0
- package/src/services/cloud-container.ts +297 -0
- package/src/services/cloud-credential-provider.ts +210 -0
- package/src/services/cloud-managed-gateway-relay.ts +663 -0
- package/src/services/cloud-model-registry.ts +202 -0
- package/src/services/index.ts +17 -0
- package/{types → src/types}/cloud.ts +52 -29
- package/{types → src/types}/index.ts +6 -0
- package/src/utils/cloud-api.ts +10 -0
- package/src/utils/cloud-sdk/client.ts +735 -0
- package/src/utils/cloud-sdk/http.ts +291 -0
- package/src/utils/cloud-sdk/index.ts +23 -0
- package/src/utils/cloud-sdk/public-routes.ts +5070 -0
- package/src/utils/cloud-sdk/types.cloud-api.ts +120 -0
- package/src/utils/cloud-sdk/types.ts +762 -0
- package/src/utils/config.ts +174 -0
- package/src/utils/events.ts +37 -0
- package/src/utils/helpers.ts +107 -0
- package/src/utils/responses-output.ts +115 -0
- package/src/utils/sdk-client.ts +37 -0
- package/dist/actions/check-credits.d.ts +0 -6
- package/dist/actions/check-credits.d.ts.map +0 -1
- package/dist/actions/freeze-agent.d.ts +0 -9
- package/dist/actions/freeze-agent.d.ts.map +0 -1
- package/dist/actions/index.d.ts +0 -5
- package/dist/actions/index.d.ts.map +0 -1
- package/dist/actions/provision-agent.d.ts +0 -8
- package/dist/actions/provision-agent.d.ts.map +0 -1
- package/dist/actions/resume-agent.d.ts +0 -9
- package/dist/actions/resume-agent.d.ts.map +0 -1
- package/dist/build.d.ts +0 -3
- package/dist/build.d.ts.map +0 -1
- package/dist/generated/specs/specs.d.ts +0 -55
- package/dist/generated/specs/specs.d.ts.map +0 -1
- package/dist/models/object.d.ts +0 -4
- package/dist/models/object.d.ts.map +0 -1
- package/dist/utils/forwarded-settings.d.ts +0 -8
- package/dist/utils/forwarded-settings.d.ts.map +0 -1
|
@@ -0,0 +1,996 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import type http from "node:http";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
isCloudInferenceSelectedInConfig,
|
|
6
|
+
migrateLegacyRuntimeConfig,
|
|
7
|
+
} from "@elizaos/core";
|
|
8
|
+
import { logger } from "@elizaos/core";
|
|
9
|
+
import { normalizeCloudSiteUrl } from "../cloud/base-url.js";
|
|
10
|
+
import type {
|
|
11
|
+
CloudChainType,
|
|
12
|
+
CloudWalletDescriptor,
|
|
13
|
+
CloudWalletProvider,
|
|
14
|
+
} from "../cloud/bridge-client.js";
|
|
15
|
+
import {
|
|
16
|
+
ELIZA_CLOUD_CLIENT_ADDRESS_KEY_ENV,
|
|
17
|
+
getOrCreateClientAddressKey,
|
|
18
|
+
persistCloudWalletCache,
|
|
19
|
+
provisionCloudWallets,
|
|
20
|
+
} from "../cloud/cloud-wallet.js";
|
|
21
|
+
import { validateCloudBaseUrl } from "../cloud/validate-url.js";
|
|
22
|
+
import { persistConfigEnv } from "../lib/config-env";
|
|
23
|
+
import {
|
|
24
|
+
applyCanonicalOnboardingConfig,
|
|
25
|
+
isTimeoutError,
|
|
26
|
+
} from "../lib/config-like";
|
|
27
|
+
import { isCloudWalletEnabled } from "../lib/feature-flags";
|
|
28
|
+
import {
|
|
29
|
+
readJsonBody as parseJsonBody,
|
|
30
|
+
sendJson,
|
|
31
|
+
sendJsonError,
|
|
32
|
+
} from "../lib/http";
|
|
33
|
+
import { resolveStateDir } from "../lib/state-paths";
|
|
34
|
+
|
|
35
|
+
export interface CloudConfigLike {
|
|
36
|
+
cloud?: {
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
apiKey?: string;
|
|
39
|
+
baseUrl?: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface CloudClientLike {
|
|
44
|
+
listAgents: () => Promise<unknown>;
|
|
45
|
+
createAgent: (args: {
|
|
46
|
+
agentName: string;
|
|
47
|
+
agentConfig?: Record<string, unknown>;
|
|
48
|
+
environmentVars?: Record<string, string>;
|
|
49
|
+
}) => Promise<unknown>;
|
|
50
|
+
deleteAgent: (agentId: string) => Promise<unknown>;
|
|
51
|
+
getAgentWallet: (
|
|
52
|
+
agentId: string,
|
|
53
|
+
chain: CloudChainType,
|
|
54
|
+
) => Promise<CloudWalletDescriptor>;
|
|
55
|
+
provisionWallet: (input: {
|
|
56
|
+
chainType: CloudChainType;
|
|
57
|
+
clientAddress: string;
|
|
58
|
+
}) => Promise<{
|
|
59
|
+
walletId: string;
|
|
60
|
+
address: string;
|
|
61
|
+
chainType: CloudChainType;
|
|
62
|
+
provider: CloudWalletProvider;
|
|
63
|
+
}>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface ConnectedCloudAgentLike {
|
|
67
|
+
agentName: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface CloudManagerLike {
|
|
71
|
+
init?: () => Promise<void>;
|
|
72
|
+
replaceApiKey?: (apiKey: string) => Promise<void>;
|
|
73
|
+
getClient: () => CloudClientLike | null;
|
|
74
|
+
connect: (agentId: string) => Promise<ConnectedCloudAgentLike>;
|
|
75
|
+
disconnect: () => Promise<void>;
|
|
76
|
+
getStatus: () => unknown;
|
|
77
|
+
getActiveAgentId: () => string | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface RuntimeLike {
|
|
81
|
+
agentId: string;
|
|
82
|
+
character?: {
|
|
83
|
+
secrets?: Record<string, string | number | boolean>;
|
|
84
|
+
};
|
|
85
|
+
getService?: (name: string) => unknown;
|
|
86
|
+
setSetting?: (key: string, value: string | null) => unknown;
|
|
87
|
+
updateAgent?: (
|
|
88
|
+
agentId: string,
|
|
89
|
+
update: {
|
|
90
|
+
secrets: Record<string, string | number | boolean>;
|
|
91
|
+
},
|
|
92
|
+
) => Promise<unknown>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface IntegrationTelemetrySpanLike {
|
|
96
|
+
success: (args?: { statusCode?: number }) => void;
|
|
97
|
+
failure: (args?: {
|
|
98
|
+
statusCode?: number;
|
|
99
|
+
error?: unknown;
|
|
100
|
+
errorKind?: string;
|
|
101
|
+
}) => void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface CloudAuthLike {
|
|
105
|
+
authenticateWithApiKey?: (input: {
|
|
106
|
+
apiKey: string;
|
|
107
|
+
organizationId?: string;
|
|
108
|
+
userId?: string;
|
|
109
|
+
}) => unknown;
|
|
110
|
+
clearAuth?: () => unknown;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
type CreateTelemetrySpanLike = (meta: {
|
|
114
|
+
boundary: "cloud";
|
|
115
|
+
operation: string;
|
|
116
|
+
timeoutMs?: number;
|
|
117
|
+
}) => IntegrationTelemetrySpanLike | null | undefined;
|
|
118
|
+
|
|
119
|
+
export interface CloudRouteState {
|
|
120
|
+
config: CloudConfigLike;
|
|
121
|
+
cloudManager: CloudManagerLike | null;
|
|
122
|
+
runtime: RuntimeLike | null;
|
|
123
|
+
saveConfig?: (config: CloudConfigLike) => void;
|
|
124
|
+
createTelemetrySpan?: CreateTelemetrySpanLike;
|
|
125
|
+
/**
|
|
126
|
+
* Optional runtime restart hook. When Phase 8 lands the cloud-wallet
|
|
127
|
+
* provisioning integration, the cloud-login handler will call this to
|
|
128
|
+
* rebind plugin-wallet to the cloud provider. Threaded
|
|
129
|
+
* from server.ts the same way provider-switch-routes does.
|
|
130
|
+
*/
|
|
131
|
+
restartRuntime?: (reason: string) => Promise<boolean> | boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const UUID_RE =
|
|
135
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
136
|
+
|
|
137
|
+
const CLOUD_LOGIN_CREATE_TIMEOUT_MS = 10_000;
|
|
138
|
+
const CLOUD_LOGIN_POLL_TIMEOUT_MS = 10_000;
|
|
139
|
+
const CONFIG_ENV_FILENAME = "config.env";
|
|
140
|
+
const CONFIG_ENV_BAK_SUFFIX = ".bak";
|
|
141
|
+
const CLOUD_WALLET_ROLLBACK_ENV_KEYS = [
|
|
142
|
+
ELIZA_CLOUD_CLIENT_ADDRESS_KEY_ENV,
|
|
143
|
+
"ELIZA_CLOUD_EVM_ADDRESS",
|
|
144
|
+
"ELIZA_CLOUD_SOLANA_ADDRESS",
|
|
145
|
+
"ENABLE_EVM_PLUGIN",
|
|
146
|
+
"WALLET_SOURCE_EVM",
|
|
147
|
+
"WALLET_SOURCE_SOLANA",
|
|
148
|
+
] as const;
|
|
149
|
+
|
|
150
|
+
type CloudWalletRollbackEnvKey =
|
|
151
|
+
(typeof CLOUD_WALLET_ROLLBACK_ENV_KEYS)[number];
|
|
152
|
+
|
|
153
|
+
interface ConfigEnvRollbackSnapshot {
|
|
154
|
+
bakPath: string;
|
|
155
|
+
filePath: string;
|
|
156
|
+
originalRaw: string | null;
|
|
157
|
+
previousEnv: Partial<Record<CloudWalletRollbackEnvKey, string>>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function extractAgentId(pathname: string): string | null {
|
|
161
|
+
const id = pathname.split("/")[4];
|
|
162
|
+
return id && UUID_RE.test(id) ? id : null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function replaceMutableRoot<T extends object>(target: T, snapshot: T): void {
|
|
166
|
+
const targetRecord = target as Record<string, unknown>;
|
|
167
|
+
for (const key of Object.keys(targetRecord)) {
|
|
168
|
+
delete targetRecord[key];
|
|
169
|
+
}
|
|
170
|
+
Object.assign(
|
|
171
|
+
targetRecord,
|
|
172
|
+
structuredClone(snapshot as Record<string, unknown>),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getCloudAuth(runtime: RuntimeLike | null): CloudAuthLike | null {
|
|
177
|
+
if (typeof runtime?.getService !== "function") {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const service = runtime.getService("CLOUD_AUTH");
|
|
181
|
+
return service && typeof service === "object"
|
|
182
|
+
? (service as CloudAuthLike)
|
|
183
|
+
: null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function clearCloudAuth(runtime: RuntimeLike | null): CloudAuthLike | null {
|
|
187
|
+
const cloudAuth = getCloudAuth(runtime);
|
|
188
|
+
if (typeof cloudAuth?.clearAuth === "function") {
|
|
189
|
+
cloudAuth.clearAuth();
|
|
190
|
+
}
|
|
191
|
+
return cloudAuth;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function captureConfigEnvRollbackSnapshot(): Promise<ConfigEnvRollbackSnapshot> {
|
|
195
|
+
const filePath = path.join(resolveStateDir(), CONFIG_ENV_FILENAME);
|
|
196
|
+
const bakPath = `${filePath}${CONFIG_ENV_BAK_SUFFIX}`;
|
|
197
|
+
|
|
198
|
+
let originalRaw: string | null = null;
|
|
199
|
+
try {
|
|
200
|
+
originalRaw = await fs.readFile(filePath, "utf8");
|
|
201
|
+
} catch (err) {
|
|
202
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const previousEnv = Object.fromEntries(
|
|
208
|
+
CLOUD_WALLET_ROLLBACK_ENV_KEYS.flatMap((key) => {
|
|
209
|
+
const value = process.env[key];
|
|
210
|
+
return typeof value === "string" ? ([[key, value]] as const) : [];
|
|
211
|
+
}),
|
|
212
|
+
) as Partial<Record<CloudWalletRollbackEnvKey, string>>;
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
bakPath,
|
|
216
|
+
filePath,
|
|
217
|
+
originalRaw,
|
|
218
|
+
previousEnv,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function restoreConfigEnvRollbackSnapshot(
|
|
223
|
+
snapshot: ConfigEnvRollbackSnapshot,
|
|
224
|
+
): Promise<void> {
|
|
225
|
+
await fs.mkdir(path.dirname(snapshot.filePath), { recursive: true });
|
|
226
|
+
|
|
227
|
+
if (snapshot.originalRaw === null) {
|
|
228
|
+
await fs.rm(snapshot.filePath, { force: true });
|
|
229
|
+
await fs.rm(snapshot.bakPath, { force: true });
|
|
230
|
+
} else {
|
|
231
|
+
await fs.writeFile(snapshot.filePath, snapshot.originalRaw, {
|
|
232
|
+
encoding: "utf8",
|
|
233
|
+
mode: 0o600,
|
|
234
|
+
});
|
|
235
|
+
await fs.writeFile(snapshot.bakPath, snapshot.originalRaw, {
|
|
236
|
+
encoding: "utf8",
|
|
237
|
+
mode: 0o600,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const key of CLOUD_WALLET_ROLLBACK_ENV_KEYS) {
|
|
242
|
+
const previousValue = snapshot.previousEnv[key];
|
|
243
|
+
if (typeof previousValue === "string") {
|
|
244
|
+
process.env[key] = previousValue;
|
|
245
|
+
} else {
|
|
246
|
+
delete process.env[key];
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function saveConfigOrThrow(state: CloudRouteState): void {
|
|
252
|
+
if (!state.saveConfig) {
|
|
253
|
+
throw new Error("saveConfig not available");
|
|
254
|
+
}
|
|
255
|
+
state.saveConfig(state.config);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function readJsonBody<T = Record<string, unknown>>(
|
|
259
|
+
req: http.IncomingMessage,
|
|
260
|
+
res: http.ServerResponse,
|
|
261
|
+
): Promise<T | null> {
|
|
262
|
+
return (await parseJsonBody(req, res, {
|
|
263
|
+
maxBytes: 1_048_576,
|
|
264
|
+
tooLargeMessage: "Request body too large",
|
|
265
|
+
destroyOnTooLarge: true,
|
|
266
|
+
})) as T | null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function isRedirectResponse(response: Response): boolean {
|
|
270
|
+
return response.status >= 300 && response.status < 400;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function createNoopTelemetrySpan(): IntegrationTelemetrySpanLike {
|
|
274
|
+
return {
|
|
275
|
+
success: () => {},
|
|
276
|
+
failure: () => {},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function getTelemetrySpan(
|
|
281
|
+
state: CloudRouteState,
|
|
282
|
+
meta: {
|
|
283
|
+
boundary: "cloud";
|
|
284
|
+
operation: string;
|
|
285
|
+
timeoutMs?: number;
|
|
286
|
+
},
|
|
287
|
+
): IntegrationTelemetrySpanLike {
|
|
288
|
+
return state.createTelemetrySpan?.(meta) ?? createNoopTelemetrySpan();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function fetchWithTimeout(
|
|
292
|
+
input: string,
|
|
293
|
+
init: RequestInit,
|
|
294
|
+
timeoutMs: number,
|
|
295
|
+
): Promise<Response> {
|
|
296
|
+
return fetch(input, {
|
|
297
|
+
...init,
|
|
298
|
+
redirect: "manual",
|
|
299
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export async function handleCloudRoute(
|
|
304
|
+
req: http.IncomingMessage,
|
|
305
|
+
res: http.ServerResponse,
|
|
306
|
+
pathname: string,
|
|
307
|
+
method: string,
|
|
308
|
+
state: CloudRouteState,
|
|
309
|
+
): Promise<boolean> {
|
|
310
|
+
if (method === "POST" && pathname === "/api/cloud/login") {
|
|
311
|
+
const baseUrl = normalizeCloudSiteUrl(state.config.cloud?.baseUrl);
|
|
312
|
+
const urlError = await validateCloudBaseUrl(baseUrl);
|
|
313
|
+
if (urlError) {
|
|
314
|
+
sendJsonError(res, urlError);
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
const sessionId = crypto.randomUUID();
|
|
318
|
+
const loginCreateSpan = getTelemetrySpan(state, {
|
|
319
|
+
boundary: "cloud",
|
|
320
|
+
operation: "login_create_session",
|
|
321
|
+
timeoutMs: CLOUD_LOGIN_CREATE_TIMEOUT_MS,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
let createRes: Response;
|
|
325
|
+
try {
|
|
326
|
+
createRes = await fetchWithTimeout(
|
|
327
|
+
`${baseUrl}/api/auth/cli-session`,
|
|
328
|
+
{
|
|
329
|
+
method: "POST",
|
|
330
|
+
headers: { "Content-Type": "application/json" },
|
|
331
|
+
body: JSON.stringify({ sessionId }),
|
|
332
|
+
},
|
|
333
|
+
CLOUD_LOGIN_CREATE_TIMEOUT_MS,
|
|
334
|
+
);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
if (isTimeoutError(err)) {
|
|
337
|
+
loginCreateSpan.failure({ error: err, statusCode: 504 });
|
|
338
|
+
sendJsonError(res, "Eliza Cloud login request timed out", 504);
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
loginCreateSpan.failure({ error: err, statusCode: 502 });
|
|
342
|
+
sendJsonError(res, "Failed to reach Eliza Cloud", 502);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (isRedirectResponse(createRes)) {
|
|
347
|
+
loginCreateSpan.failure({
|
|
348
|
+
statusCode: createRes.status,
|
|
349
|
+
errorKind: "redirect_response",
|
|
350
|
+
});
|
|
351
|
+
sendJsonError(
|
|
352
|
+
res,
|
|
353
|
+
"Eliza Cloud login request was redirected; redirects are not allowed",
|
|
354
|
+
502,
|
|
355
|
+
);
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!createRes.ok) {
|
|
360
|
+
loginCreateSpan.failure({
|
|
361
|
+
statusCode: createRes.status,
|
|
362
|
+
errorKind: "http_error",
|
|
363
|
+
});
|
|
364
|
+
sendJsonError(res, "Failed to create auth session with Eliza Cloud", 502);
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
loginCreateSpan.success({ statusCode: createRes.status });
|
|
369
|
+
sendJson(res, {
|
|
370
|
+
ok: true,
|
|
371
|
+
sessionId,
|
|
372
|
+
browserUrl: `${baseUrl}/auth/cli-login?session=${encodeURIComponent(sessionId)}`,
|
|
373
|
+
});
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (method === "GET" && pathname.startsWith("/api/cloud/login/status")) {
|
|
378
|
+
const url = new URL(
|
|
379
|
+
req.url ?? "/",
|
|
380
|
+
`http://${req.headers.host ?? "localhost"}`,
|
|
381
|
+
);
|
|
382
|
+
const sessionId = url.searchParams.get("sessionId");
|
|
383
|
+
if (!sessionId) {
|
|
384
|
+
sendJsonError(res, "sessionId query parameter is required");
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const baseUrl = normalizeCloudSiteUrl(state.config.cloud?.baseUrl);
|
|
389
|
+
const urlError = await validateCloudBaseUrl(baseUrl);
|
|
390
|
+
if (urlError) {
|
|
391
|
+
sendJsonError(res, urlError);
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
const loginPollSpan = getTelemetrySpan(state, {
|
|
395
|
+
boundary: "cloud",
|
|
396
|
+
operation: "login_poll_status",
|
|
397
|
+
timeoutMs: CLOUD_LOGIN_POLL_TIMEOUT_MS,
|
|
398
|
+
});
|
|
399
|
+
let pollRes: Response;
|
|
400
|
+
try {
|
|
401
|
+
pollRes = await fetchWithTimeout(
|
|
402
|
+
`${baseUrl}/api/auth/cli-session/${encodeURIComponent(sessionId)}`,
|
|
403
|
+
{},
|
|
404
|
+
CLOUD_LOGIN_POLL_TIMEOUT_MS,
|
|
405
|
+
);
|
|
406
|
+
} catch (err) {
|
|
407
|
+
if (isTimeoutError(err)) {
|
|
408
|
+
loginPollSpan.failure({ error: err, statusCode: 504 });
|
|
409
|
+
sendJson(
|
|
410
|
+
res,
|
|
411
|
+
{
|
|
412
|
+
status: "error",
|
|
413
|
+
error: "Eliza Cloud status request timed out",
|
|
414
|
+
},
|
|
415
|
+
504,
|
|
416
|
+
);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
loginPollSpan.failure({ error: err, statusCode: 502 });
|
|
420
|
+
sendJson(
|
|
421
|
+
res,
|
|
422
|
+
{
|
|
423
|
+
status: "error",
|
|
424
|
+
error: "Failed to reach Eliza Cloud",
|
|
425
|
+
},
|
|
426
|
+
502,
|
|
427
|
+
);
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (isRedirectResponse(pollRes)) {
|
|
432
|
+
loginPollSpan.failure({
|
|
433
|
+
statusCode: pollRes.status,
|
|
434
|
+
errorKind: "redirect_response",
|
|
435
|
+
});
|
|
436
|
+
sendJson(
|
|
437
|
+
res,
|
|
438
|
+
{
|
|
439
|
+
status: "error",
|
|
440
|
+
error:
|
|
441
|
+
"Eliza Cloud status request was redirected; redirects are not allowed",
|
|
442
|
+
},
|
|
443
|
+
502,
|
|
444
|
+
);
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (!pollRes.ok) {
|
|
449
|
+
loginPollSpan.failure({
|
|
450
|
+
statusCode: pollRes.status,
|
|
451
|
+
errorKind: "http_error",
|
|
452
|
+
});
|
|
453
|
+
sendJson(
|
|
454
|
+
res,
|
|
455
|
+
pollRes.status === 404
|
|
456
|
+
? { status: "expired", error: "Session not found or expired" }
|
|
457
|
+
: {
|
|
458
|
+
status: "error",
|
|
459
|
+
error: `Eliza Cloud returned HTTP ${pollRes.status}`,
|
|
460
|
+
},
|
|
461
|
+
);
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
let data: {
|
|
466
|
+
status: string;
|
|
467
|
+
apiKey?: string;
|
|
468
|
+
keyPrefix?: string;
|
|
469
|
+
organizationId?: string;
|
|
470
|
+
userId?: string;
|
|
471
|
+
};
|
|
472
|
+
try {
|
|
473
|
+
data = (await pollRes.json()) as {
|
|
474
|
+
status: string;
|
|
475
|
+
apiKey?: string;
|
|
476
|
+
keyPrefix?: string;
|
|
477
|
+
organizationId?: string;
|
|
478
|
+
userId?: string;
|
|
479
|
+
};
|
|
480
|
+
} catch (parseErr) {
|
|
481
|
+
loginPollSpan.failure({ error: parseErr, statusCode: pollRes.status });
|
|
482
|
+
sendJson(
|
|
483
|
+
res,
|
|
484
|
+
{ status: "error", error: "Eliza Cloud returned invalid JSON" },
|
|
485
|
+
502,
|
|
486
|
+
);
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
loginPollSpan.success({ statusCode: pollRes.status });
|
|
490
|
+
|
|
491
|
+
if (data.status === "authenticated" && data.apiKey) {
|
|
492
|
+
const organizationId =
|
|
493
|
+
typeof data.organizationId === "string"
|
|
494
|
+
? data.organizationId.trim()
|
|
495
|
+
: undefined;
|
|
496
|
+
const userId =
|
|
497
|
+
typeof data.userId === "string" ? data.userId.trim() : undefined;
|
|
498
|
+
const cloudAuth = clearCloudAuth(state.runtime);
|
|
499
|
+
migrateLegacyRuntimeConfig(state.config as Record<string, unknown>);
|
|
500
|
+
const cloud = (state.config.cloud ?? {}) as NonNullable<
|
|
501
|
+
CloudConfigLike["cloud"]
|
|
502
|
+
>;
|
|
503
|
+
cloud.apiKey = data.apiKey;
|
|
504
|
+
(state.config as Record<string, unknown>).cloud = cloud;
|
|
505
|
+
applyCanonicalOnboardingConfig(state.config as never, {
|
|
506
|
+
linkedAccounts: {
|
|
507
|
+
elizacloud: {
|
|
508
|
+
status: "linked",
|
|
509
|
+
source: "api-key",
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
const cloudInferenceSelected = isCloudInferenceSelectedInConfig(
|
|
514
|
+
state.config as Record<string, unknown>,
|
|
515
|
+
);
|
|
516
|
+
migrateLegacyRuntimeConfig(state.config as Record<string, unknown>);
|
|
517
|
+
try {
|
|
518
|
+
if (state.saveConfig) {
|
|
519
|
+
state.saveConfig(state.config);
|
|
520
|
+
} else {
|
|
521
|
+
logger.warn(
|
|
522
|
+
"[cloud-login] saveConfig not available — config not persisted",
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
logger.info("[cloud-login] API key saved to config file");
|
|
526
|
+
} catch (saveErr) {
|
|
527
|
+
logger.error(`[cloud-login] Failed to save config: ${String(saveErr)}`);
|
|
528
|
+
sendJson(
|
|
529
|
+
res,
|
|
530
|
+
{ status: "error", error: "Authenticated but failed to save config" },
|
|
531
|
+
500,
|
|
532
|
+
);
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
process.env.ELIZAOS_CLOUD_API_KEY = data.apiKey;
|
|
537
|
+
if (cloudInferenceSelected) {
|
|
538
|
+
process.env.ELIZAOS_CLOUD_ENABLED = "true";
|
|
539
|
+
} else {
|
|
540
|
+
delete process.env.ELIZAOS_CLOUD_ENABLED;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (state.runtime) {
|
|
544
|
+
const character = state.runtime.character ?? {};
|
|
545
|
+
state.runtime.character = character;
|
|
546
|
+
if (!character.secrets) {
|
|
547
|
+
character.secrets = {};
|
|
548
|
+
}
|
|
549
|
+
const secrets = character.secrets as Record<string, string>;
|
|
550
|
+
secrets.ELIZAOS_CLOUD_API_KEY = data.apiKey;
|
|
551
|
+
if (userId) {
|
|
552
|
+
secrets.ELIZA_CLOUD_USER_ID = userId;
|
|
553
|
+
} else {
|
|
554
|
+
delete secrets.ELIZA_CLOUD_USER_ID;
|
|
555
|
+
}
|
|
556
|
+
if (organizationId) {
|
|
557
|
+
secrets.ELIZA_CLOUD_ORGANIZATION_ID = organizationId;
|
|
558
|
+
} else {
|
|
559
|
+
delete secrets.ELIZA_CLOUD_ORGANIZATION_ID;
|
|
560
|
+
}
|
|
561
|
+
if (cloudInferenceSelected) {
|
|
562
|
+
secrets.ELIZAOS_CLOUD_ENABLED = "true";
|
|
563
|
+
} else {
|
|
564
|
+
delete secrets.ELIZAOS_CLOUD_ENABLED;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (typeof state.runtime.setSetting === "function") {
|
|
568
|
+
state.runtime.setSetting("ELIZA_CLOUD_USER_ID", userId ?? null);
|
|
569
|
+
state.runtime.setSetting(
|
|
570
|
+
"ELIZA_CLOUD_ORGANIZATION_ID",
|
|
571
|
+
organizationId ?? null,
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (typeof state.runtime.updateAgent === "function") {
|
|
576
|
+
await state.runtime.updateAgent(state.runtime.agentId, {
|
|
577
|
+
secrets: { ...secrets },
|
|
578
|
+
});
|
|
579
|
+
logger.info("[cloud-login] API key persisted to agent DB record");
|
|
580
|
+
} else {
|
|
581
|
+
logger.warn(
|
|
582
|
+
"[cloud-login] runtime.updateAgent not available — agent DB secrets not persisted",
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (
|
|
588
|
+
state.cloudManager &&
|
|
589
|
+
typeof state.cloudManager.replaceApiKey === "function"
|
|
590
|
+
) {
|
|
591
|
+
await state.cloudManager.replaceApiKey(data.apiKey);
|
|
592
|
+
} else if (
|
|
593
|
+
state.cloudManager &&
|
|
594
|
+
!state.cloudManager.getClient() &&
|
|
595
|
+
typeof state.cloudManager.init === "function"
|
|
596
|
+
) {
|
|
597
|
+
await state.cloudManager.init();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (typeof cloudAuth?.authenticateWithApiKey === "function") {
|
|
601
|
+
cloudAuth.authenticateWithApiKey({
|
|
602
|
+
apiKey: data.apiKey,
|
|
603
|
+
organizationId,
|
|
604
|
+
userId,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Cloud-wallet remote-signing bridge (gated by ENABLE_CLOUD_WALLET).
|
|
609
|
+
// Failures here do NOT abort the cloud-login response — the API key
|
|
610
|
+
// is already saved. We log, rollback the partial wallet bind, and
|
|
611
|
+
// fall through so the user stays logged in.
|
|
612
|
+
if (isCloudWalletEnabled()) {
|
|
613
|
+
const rollbackConfigSnapshot = structuredClone(
|
|
614
|
+
state.config as Record<string, unknown>,
|
|
615
|
+
) as CloudConfigLike;
|
|
616
|
+
const rollbackEnvSnapshot = await captureConfigEnvRollbackSnapshot();
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
const bridge = state.cloudManager?.getClient();
|
|
620
|
+
const agentId = state.runtime?.agentId;
|
|
621
|
+
if (!bridge) {
|
|
622
|
+
throw new Error("cloud-wallet bridge unavailable");
|
|
623
|
+
}
|
|
624
|
+
if (!agentId) {
|
|
625
|
+
throw new Error("cloud-wallet runtime agentId missing");
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const { address: clientAddress, minted } =
|
|
629
|
+
await getOrCreateClientAddressKey();
|
|
630
|
+
if (minted) {
|
|
631
|
+
logger.info(
|
|
632
|
+
`[cloud-login] cloud-wallet: minted client_address ${clientAddress}`,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const descriptors = await provisionCloudWallets(bridge, {
|
|
637
|
+
agentId,
|
|
638
|
+
clientAddress,
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
persistCloudWalletCache(
|
|
642
|
+
state.config as Record<string, unknown>,
|
|
643
|
+
descriptors,
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
const cloudCfg = (state.config.cloud ?? {}) as Record<
|
|
647
|
+
string,
|
|
648
|
+
unknown
|
|
649
|
+
>;
|
|
650
|
+
cloudCfg.clientAddressPublicKey = clientAddress;
|
|
651
|
+
(state.config as Record<string, unknown>).cloud = cloudCfg;
|
|
652
|
+
saveConfigOrThrow(state);
|
|
653
|
+
|
|
654
|
+
if (descriptors.evm?.walletAddress) {
|
|
655
|
+
process.env.ELIZA_CLOUD_EVM_ADDRESS = descriptors.evm.walletAddress;
|
|
656
|
+
await persistConfigEnv(
|
|
657
|
+
"ELIZA_CLOUD_EVM_ADDRESS",
|
|
658
|
+
descriptors.evm.walletAddress,
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
if (descriptors.solana?.walletAddress) {
|
|
662
|
+
process.env.ELIZA_CLOUD_SOLANA_ADDRESS =
|
|
663
|
+
descriptors.solana.walletAddress;
|
|
664
|
+
await persistConfigEnv(
|
|
665
|
+
"ELIZA_CLOUD_SOLANA_ADDRESS",
|
|
666
|
+
descriptors.solana.walletAddress,
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
await persistConfigEnv("ENABLE_EVM_PLUGIN", "1");
|
|
671
|
+
if (descriptors.evm) {
|
|
672
|
+
await persistConfigEnv("WALLET_SOURCE_EVM", "cloud");
|
|
673
|
+
}
|
|
674
|
+
if (descriptors.solana) {
|
|
675
|
+
await persistConfigEnv("WALLET_SOURCE_SOLANA", "cloud");
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const wallet = ((state.config as Record<string, unknown>).wallet ??
|
|
679
|
+
{}) as Record<string, unknown>;
|
|
680
|
+
const primary = {
|
|
681
|
+
...((wallet.primary ?? {}) as Record<string, string>),
|
|
682
|
+
};
|
|
683
|
+
if (descriptors.evm) primary.evm = "cloud";
|
|
684
|
+
if (descriptors.solana) primary.solana = "cloud";
|
|
685
|
+
wallet.primary = primary;
|
|
686
|
+
(state.config as Record<string, unknown>).wallet = wallet;
|
|
687
|
+
saveConfigOrThrow(state);
|
|
688
|
+
|
|
689
|
+
logger.info(
|
|
690
|
+
`[cloud-login] cloud-wallet: provisioned ${Object.keys(descriptors).join(", ")} — applying runtime reload`,
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
const restarted = state.restartRuntime
|
|
694
|
+
? await Promise.resolve(state.restartRuntime("cloud-wallet-bound"))
|
|
695
|
+
: false;
|
|
696
|
+
if (!restarted) {
|
|
697
|
+
logger.warn(
|
|
698
|
+
"[cloud-login] cloud-wallet: restartRuntime not wired or restart declined — user must restart manually",
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
} catch (cloudWalletErr) {
|
|
702
|
+
try {
|
|
703
|
+
await restoreConfigEnvRollbackSnapshot(rollbackEnvSnapshot);
|
|
704
|
+
} catch (rollbackErr) {
|
|
705
|
+
logger.error(
|
|
706
|
+
`[cloud-login] cloud-wallet rollback failed: ${String(
|
|
707
|
+
rollbackErr,
|
|
708
|
+
)}`,
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
replaceMutableRoot(state.config, rollbackConfigSnapshot);
|
|
713
|
+
try {
|
|
714
|
+
saveConfigOrThrow(state);
|
|
715
|
+
} catch (saveRollbackErr) {
|
|
716
|
+
logger.error(
|
|
717
|
+
`[cloud-login] cloud-wallet config rollback failed: ${String(
|
|
718
|
+
saveRollbackErr,
|
|
719
|
+
)}`,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
logger.error(
|
|
724
|
+
`[cloud-login] cloud-wallet provision failed: ${String(
|
|
725
|
+
cloudWalletErr,
|
|
726
|
+
)}`,
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Return the cloud API key to the renderer so it can populate
|
|
732
|
+
// `globalThis.__ELIZA_CLOUD_AUTH_TOKEN__` and use the direct cloud
|
|
733
|
+
// path (`/api/v1/eliza/agents`) for agent creation/provisioning.
|
|
734
|
+
// Without this, every cloud op falls back to the proxy compat path,
|
|
735
|
+
// which creates agents in a namespace whose queue never drains
|
|
736
|
+
// (agents stay `status: "queued"` forever — onboarding hangs).
|
|
737
|
+
//
|
|
738
|
+
// ## Security trade-off — token in HTTP response body
|
|
739
|
+
//
|
|
740
|
+
// Sending an API key in the response body is a deliberate choice
|
|
741
|
+
// for our architecture, NOT an oversight:
|
|
742
|
+
// - The agent process and the renderer (Electrobun WebView) live
|
|
743
|
+
// on the same machine. They communicate over loopback HTTP
|
|
744
|
+
// (`127.0.0.1`). Any process that can sniff the loopback
|
|
745
|
+
// interface (tcpdump, attached debugger) already has the
|
|
746
|
+
// ambient privilege to read the renderer's memory or the
|
|
747
|
+
// vault directly — the HTTP body is not the weakest link.
|
|
748
|
+
// - The key IS the user's own secret (not server-owned), and
|
|
749
|
+
// the response only goes to the renderer the user is
|
|
750
|
+
// actively using.
|
|
751
|
+
// - Returning a short-lived "handle" that the renderer
|
|
752
|
+
// exchanges for the real token would just shift the same
|
|
753
|
+
// cleartext transit one hop, not eliminate it.
|
|
754
|
+
//
|
|
755
|
+
// The principled fix is to push the token over an Electrobun RPC
|
|
756
|
+
// channel (the same IPC the api-base-owner module uses for
|
|
757
|
+
// `pushApiBaseToRenderer`). That requires routing the cloud
|
|
758
|
+
// login outcome from the agent process to the Electrobun main
|
|
759
|
+
// process and back to the renderer — three-process choreography
|
|
760
|
+
// that doesn't fit this PR. Tracked as a follow-up.
|
|
761
|
+
logger.info(
|
|
762
|
+
`[cloud-login] sending API key to loopback renderer (single-user desktop trust model)`,
|
|
763
|
+
);
|
|
764
|
+
sendJson(res, {
|
|
765
|
+
status: "authenticated",
|
|
766
|
+
token: data.apiKey,
|
|
767
|
+
keyPrefix: data.keyPrefix,
|
|
768
|
+
organizationId,
|
|
769
|
+
userId,
|
|
770
|
+
});
|
|
771
|
+
} else {
|
|
772
|
+
sendJson(res, { status: data.status });
|
|
773
|
+
}
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (method === "GET" && pathname === "/api/cloud/agents") {
|
|
778
|
+
const client = state.cloudManager?.getClient();
|
|
779
|
+
if (!client) {
|
|
780
|
+
sendJsonError(res, "Not connected to Eliza Cloud", 401);
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
sendJson(res, { ok: true, agents: await client.listAgents() });
|
|
784
|
+
return true;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (method === "POST" && pathname === "/api/cloud/agents") {
|
|
788
|
+
const client = state.cloudManager?.getClient();
|
|
789
|
+
if (!client) {
|
|
790
|
+
sendJsonError(res, "Not connected to Eliza Cloud", 401);
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const body = await readJsonBody<{
|
|
795
|
+
agentName?: string;
|
|
796
|
+
agentConfig?: Record<string, unknown>;
|
|
797
|
+
environmentVars?: Record<string, string>;
|
|
798
|
+
}>(req, res);
|
|
799
|
+
if (!body) return true;
|
|
800
|
+
|
|
801
|
+
if (!body.agentName?.trim()) {
|
|
802
|
+
sendJsonError(res, "agentName is required");
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
let agent: unknown;
|
|
807
|
+
try {
|
|
808
|
+
agent = await client.createAgent({
|
|
809
|
+
agentName: body.agentName,
|
|
810
|
+
agentConfig: body.agentConfig,
|
|
811
|
+
environmentVars: body.environmentVars,
|
|
812
|
+
});
|
|
813
|
+
} catch (err) {
|
|
814
|
+
logger.error(`[cloud] createAgent failed: ${String(err)}`);
|
|
815
|
+
sendJson(
|
|
816
|
+
res,
|
|
817
|
+
{ ok: false, error: `Cloud createAgent failed: ${String(err)}` },
|
|
818
|
+
502,
|
|
819
|
+
);
|
|
820
|
+
return true;
|
|
821
|
+
}
|
|
822
|
+
sendJson(res, { ok: true, agent }, 201);
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (
|
|
827
|
+
method === "POST" &&
|
|
828
|
+
pathname.startsWith("/api/cloud/agents/") &&
|
|
829
|
+
pathname.endsWith("/provision")
|
|
830
|
+
) {
|
|
831
|
+
const agentId = extractAgentId(pathname);
|
|
832
|
+
if (!agentId || !state.cloudManager) {
|
|
833
|
+
sendJsonError(res, "Invalid agent ID or cloud not connected", 400);
|
|
834
|
+
return true;
|
|
835
|
+
}
|
|
836
|
+
let proxy: { agentName?: string };
|
|
837
|
+
try {
|
|
838
|
+
proxy = await state.cloudManager.connect(agentId);
|
|
839
|
+
} catch (err) {
|
|
840
|
+
logger.error(`[cloud] provision/connect failed: ${String(err)}`);
|
|
841
|
+
sendJson(
|
|
842
|
+
res,
|
|
843
|
+
{ ok: false, error: `Cloud provision failed: ${String(err)}` },
|
|
844
|
+
502,
|
|
845
|
+
);
|
|
846
|
+
return true;
|
|
847
|
+
}
|
|
848
|
+
sendJson(res, {
|
|
849
|
+
ok: true,
|
|
850
|
+
agentId,
|
|
851
|
+
agentName: proxy.agentName,
|
|
852
|
+
status: state.cloudManager.getStatus(),
|
|
853
|
+
});
|
|
854
|
+
return true;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (
|
|
858
|
+
method === "POST" &&
|
|
859
|
+
pathname.startsWith("/api/cloud/agents/") &&
|
|
860
|
+
pathname.endsWith("/shutdown")
|
|
861
|
+
) {
|
|
862
|
+
const agentId = extractAgentId(pathname);
|
|
863
|
+
if (!agentId || !state.cloudManager) {
|
|
864
|
+
sendJsonError(res, "Invalid agent ID or cloud not connected", 400);
|
|
865
|
+
return true;
|
|
866
|
+
}
|
|
867
|
+
const client = state.cloudManager.getClient();
|
|
868
|
+
if (!client) {
|
|
869
|
+
sendJsonError(res, "Not connected to Eliza Cloud", 401);
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
872
|
+
try {
|
|
873
|
+
if (state.cloudManager.getActiveAgentId() === agentId) {
|
|
874
|
+
await state.cloudManager.disconnect();
|
|
875
|
+
}
|
|
876
|
+
await client.deleteAgent(agentId);
|
|
877
|
+
} catch (err) {
|
|
878
|
+
logger.error(`[cloud] shutdown/deleteAgent failed: ${String(err)}`);
|
|
879
|
+
sendJson(
|
|
880
|
+
res,
|
|
881
|
+
{ ok: false, error: `Cloud shutdown failed: ${String(err)}` },
|
|
882
|
+
502,
|
|
883
|
+
);
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
sendJson(res, { ok: true, agentId, status: "stopped" });
|
|
887
|
+
return true;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (
|
|
891
|
+
method === "POST" &&
|
|
892
|
+
pathname.startsWith("/api/cloud/agents/") &&
|
|
893
|
+
pathname.endsWith("/connect")
|
|
894
|
+
) {
|
|
895
|
+
const agentId = extractAgentId(pathname);
|
|
896
|
+
if (!agentId || !state.cloudManager) {
|
|
897
|
+
sendJsonError(res, "Invalid agent ID or cloud not connected", 400);
|
|
898
|
+
return true;
|
|
899
|
+
}
|
|
900
|
+
let proxy: { agentName?: string };
|
|
901
|
+
try {
|
|
902
|
+
if (state.cloudManager.getActiveAgentId()) {
|
|
903
|
+
await state.cloudManager.disconnect();
|
|
904
|
+
}
|
|
905
|
+
proxy = await state.cloudManager.connect(agentId);
|
|
906
|
+
} catch (err) {
|
|
907
|
+
logger.error(`[cloud] connect failed: ${String(err)}`);
|
|
908
|
+
sendJson(
|
|
909
|
+
res,
|
|
910
|
+
{ ok: false, error: `Cloud connect failed: ${String(err)}` },
|
|
911
|
+
502,
|
|
912
|
+
);
|
|
913
|
+
return true;
|
|
914
|
+
}
|
|
915
|
+
sendJson(res, {
|
|
916
|
+
ok: true,
|
|
917
|
+
agentId,
|
|
918
|
+
agentName: proxy.agentName,
|
|
919
|
+
status: state.cloudManager.getStatus(),
|
|
920
|
+
});
|
|
921
|
+
return true;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (method === "POST" && pathname === "/api/cloud/disconnect") {
|
|
925
|
+
if (state.cloudManager) {
|
|
926
|
+
await state.cloudManager.disconnect();
|
|
927
|
+
}
|
|
928
|
+
const cloud = (state.config.cloud ?? {}) as NonNullable<
|
|
929
|
+
CloudConfigLike["cloud"]
|
|
930
|
+
>;
|
|
931
|
+
delete cloud.apiKey;
|
|
932
|
+
(state.config as Record<string, unknown>).cloud = cloud;
|
|
933
|
+
applyCanonicalOnboardingConfig(state.config as never, {
|
|
934
|
+
deploymentTarget: { runtime: "local" },
|
|
935
|
+
linkedAccounts: {
|
|
936
|
+
elizacloud: {
|
|
937
|
+
status: "unlinked",
|
|
938
|
+
source: "api-key",
|
|
939
|
+
},
|
|
940
|
+
},
|
|
941
|
+
clearRoutes: ["llmText", "tts", "media", "embeddings", "rpc"],
|
|
942
|
+
});
|
|
943
|
+
migrateLegacyRuntimeConfig(state.config as Record<string, unknown>);
|
|
944
|
+
|
|
945
|
+
try {
|
|
946
|
+
if (state.saveConfig) {
|
|
947
|
+
state.saveConfig(state.config);
|
|
948
|
+
} else {
|
|
949
|
+
logger.warn(
|
|
950
|
+
"[cloud-disconnect] saveConfig not available — config not persisted",
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
} catch (saveErr) {
|
|
954
|
+
logger.error(
|
|
955
|
+
`[cloud-disconnect] Failed to save config: ${String(saveErr)}`,
|
|
956
|
+
);
|
|
957
|
+
sendJson(
|
|
958
|
+
res,
|
|
959
|
+
{ ok: false, error: "Disconnected but failed to save config" },
|
|
960
|
+
500,
|
|
961
|
+
);
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
delete process.env.ELIZAOS_CLOUD_API_KEY;
|
|
966
|
+
delete process.env.ELIZAOS_CLOUD_ENABLED;
|
|
967
|
+
|
|
968
|
+
if (state.runtime) {
|
|
969
|
+
const character = state.runtime.character ?? {};
|
|
970
|
+
state.runtime.character = character;
|
|
971
|
+
if (!character.secrets) {
|
|
972
|
+
character.secrets = {};
|
|
973
|
+
}
|
|
974
|
+
const secrets = character.secrets as Record<
|
|
975
|
+
string,
|
|
976
|
+
string | number | boolean
|
|
977
|
+
>;
|
|
978
|
+
delete secrets.ELIZAOS_CLOUD_API_KEY;
|
|
979
|
+
delete secrets.ELIZAOS_CLOUD_ENABLED;
|
|
980
|
+
if (typeof state.runtime.updateAgent === "function") {
|
|
981
|
+
await state.runtime.updateAgent(state.runtime.agentId, {
|
|
982
|
+
secrets: { ...secrets },
|
|
983
|
+
});
|
|
984
|
+
} else {
|
|
985
|
+
logger.warn(
|
|
986
|
+
"[cloud-disconnect] updateAgent not available — runtime secrets not persisted",
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
sendJson(res, { ok: true, status: "disconnected" });
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return false;
|
|
996
|
+
}
|