@elizaos/app-core 2.0.0-alpha.13 → 2.0.0-alpha.15

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 (125) hide show
  1. package/.turbo/turbo-build.log +0 -1
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +26 -12
  4. package/dist/api/client.d.ts +3 -3
  5. package/dist/api/client.d.ts.map +1 -1
  6. package/dist/api/client.js +24 -13
  7. package/dist/bridge/plugin-bridge.d.ts.map +1 -1
  8. package/dist/components/AvatarLoader.d.ts +3 -1
  9. package/dist/components/AvatarLoader.d.ts.map +1 -1
  10. package/dist/components/AvatarLoader.js +4 -1
  11. package/dist/components/BscTradePanel.d.ts +1 -1
  12. package/dist/components/BscTradePanel.d.ts.map +1 -1
  13. package/dist/components/CharacterView.d.ts.map +1 -1
  14. package/dist/components/CharacterView.js +50 -15
  15. package/dist/components/ChatView.js +1 -1
  16. package/dist/components/ConfigPageView.d.ts.map +1 -1
  17. package/dist/components/ConfigPageView.js +9 -8
  18. package/dist/components/ConversationsSidebar.js +1 -1
  19. package/dist/components/CustomActionEditor.js +1 -1
  20. package/dist/components/FineTuningView.d.ts.map +1 -1
  21. package/dist/components/FineTuningView.js +2 -2
  22. package/dist/components/GlobalEmoteOverlay.d.ts.map +1 -1
  23. package/dist/components/GlobalEmoteOverlay.js +1 -1
  24. package/dist/components/HeartbeatsView.js +1 -1
  25. package/dist/components/LoadingScreen.d.ts.map +1 -1
  26. package/dist/components/LoadingScreen.js +38 -7
  27. package/dist/components/OnboardingWizard.js +1 -1
  28. package/dist/components/PluginsView.d.ts.map +1 -1
  29. package/dist/components/PluginsView.js +4 -1
  30. package/dist/components/ShellOverlays.js +1 -1
  31. package/dist/components/VoiceConfigView.d.ts.map +1 -1
  32. package/dist/components/VoiceConfigView.js +4 -2
  33. package/dist/components/avatar/VrmEngine.d.ts +2 -0
  34. package/dist/components/avatar/VrmEngine.d.ts.map +1 -1
  35. package/dist/components/avatar/VrmEngine.js +14 -6
  36. package/dist/components/companion/CompanionSceneHost.d.ts +1 -1
  37. package/dist/components/companion/CompanionSceneHost.d.ts.map +1 -1
  38. package/dist/components/companion/CompanionSceneHost.js +1 -1
  39. package/dist/components/index.d.ts +10 -10
  40. package/dist/components/index.d.ts.map +1 -1
  41. package/dist/components/index.js +10 -10
  42. package/dist/components/inventory/TokensTable.js +2 -2
  43. package/dist/components/onboarding/IdentityStep.d.ts.map +1 -1
  44. package/dist/components/onboarding/IdentityStep.js +1 -1
  45. package/dist/components/shared/ShellHeaderControls.js +1 -1
  46. package/dist/config/config-field.d.ts.map +1 -1
  47. package/dist/config/config-field.js +7 -8
  48. package/dist/config/index.d.ts +1 -1
  49. package/dist/config/index.d.ts.map +1 -1
  50. package/dist/config/index.js +1 -1
  51. package/dist/hooks/useVoiceChat.d.ts.map +1 -1
  52. package/dist/hooks/useVoiceChat.js +3 -1
  53. package/dist/i18n/locales/en.json +1192 -1192
  54. package/dist/i18n/locales/es.json +1192 -1192
  55. package/dist/i18n/locales/ko.json +1192 -1192
  56. package/dist/i18n/locales/pt.json +1192 -1192
  57. package/dist/i18n/locales/zh-CN.json +1192 -1192
  58. package/dist/package.json +181 -0
  59. package/dist/platform/lifo.d.ts.map +1 -1
  60. package/dist/platform/lifo.js +4 -1
  61. package/dist/state/AppContext.d.ts.map +1 -1
  62. package/dist/state/AppContext.js +23 -6
  63. package/dist/state/internal.d.ts +1 -1
  64. package/dist/state/internal.d.ts.map +1 -1
  65. package/dist/state/internal.js +1 -1
  66. package/dist/state/parsers.d.ts.map +1 -1
  67. package/dist/state/parsers.js +3 -2
  68. package/dist/state/persistence.js +1 -1
  69. package/dist/styles/anime.css +6324 -0
  70. package/dist/styles/base.css +196 -0
  71. package/dist/styles/onboarding-game.css +738 -0
  72. package/dist/styles/styles.css +2087 -0
  73. package/dist/styles/xterm.css +241 -0
  74. package/package.json +4 -4
  75. package/src/App.tsx +35 -14
  76. package/src/ambient.d.ts +5 -5
  77. package/src/api/client.ts +36 -23
  78. package/src/bridge/plugin-bridge.ts +1 -1
  79. package/src/components/AvatarLoader.tsx +6 -0
  80. package/src/components/BscTradePanel.tsx +1 -1
  81. package/src/components/CharacterView.tsx +536 -367
  82. package/src/components/ChatView.tsx +3 -3
  83. package/src/components/ConfigPageView.tsx +9 -8
  84. package/src/components/ConversationsSidebar.tsx +1 -1
  85. package/src/components/CustomActionEditor.tsx +6 -6
  86. package/src/components/FineTuningView.tsx +6 -3
  87. package/src/components/GlobalEmoteOverlay.tsx +1 -4
  88. package/src/components/HeartbeatsView.tsx +1 -1
  89. package/src/components/InventoryView.tsx +2 -2
  90. package/src/components/LoadingScreen.tsx +39 -6
  91. package/src/components/OnboardingWizard.tsx +1 -1
  92. package/src/components/PluginsView.tsx +6 -0
  93. package/src/components/ShellOverlays.tsx +1 -1
  94. package/src/components/VoiceConfigView.tsx +4 -5
  95. package/src/components/avatar/VrmEngine.ts +25 -7
  96. package/src/components/companion/CompanionSceneHost.tsx +5 -1
  97. package/src/components/index.ts +10 -10
  98. package/src/components/inventory/TokensTable.tsx +2 -2
  99. package/src/components/onboarding/IdentityStep.tsx +9 -13
  100. package/src/components/shared/ShellHeaderControls.tsx +1 -1
  101. package/src/config/config-field.tsx +7 -8
  102. package/src/config/index.ts +3 -3
  103. package/src/hooks/useVoiceChat.ts +5 -3
  104. package/src/platform/lifo.ts +14 -4
  105. package/src/state/AppContext.tsx +24 -1
  106. package/src/state/internal.ts +6 -6
  107. package/src/state/parsers.ts +4 -3
  108. package/src/state/persistence.ts +1 -1
  109. package/test/app/MessageContent.test.tsx +42 -0
  110. package/test/app/bug-report-modal.test.tsx +3 -3
  111. package/test/app/chat-view.test.tsx +3 -3
  112. package/test/app/cloud-login-lock.test.ts +3 -2
  113. package/test/app/custom-actions-smoke.test.ts +3 -3
  114. package/test/app/onboarding-language.test.tsx +3 -3
  115. package/test/app/pages-navigation-smoke.e2e.test.ts +13 -8
  116. package/test/app/plugin-bridge.test.ts +1 -1
  117. package/test/app/provider-dropdown-default.test.tsx +2 -4
  118. package/test/app/restart-banner.test.tsx +3 -3
  119. package/test/app/shell-mode-switching.e2e.test.ts +6 -6
  120. package/test/app/shell-mode-tab-memory.test.tsx +1 -1
  121. package/test/app/startup-chat.e2e.test.ts +3 -3
  122. package/test/app/triggers-view.e2e.test.ts +3 -2
  123. package/test/app/wallet-api-save-lock.test.ts +2 -1
  124. package/test/utils/assistant-text.test.ts +64 -0
  125. package/test/utils/streaming-text.test.ts +89 -0
@@ -858,9 +858,9 @@ export function ChatView({ variant = "default" }: ChatViewProps) {
858
858
  data-no-camera-drag={isGameModal || undefined}
859
859
  style={{ zIndex: 1 }}
860
860
  >
861
- {chatPendingImages.map((img, i) => (
861
+ {chatPendingImages.map((img, imgIdx) => (
862
862
  <div
863
- key={`${img.name}-${i}`}
863
+ key={`${img.name}-${img.data}`}
864
864
  className="relative group w-16 h-16 shrink-0"
865
865
  >
866
866
  <img
@@ -872,7 +872,7 @@ export function ChatView({ variant = "default" }: ChatViewProps) {
872
872
  type="button"
873
873
  title={t("chatview.RemoveImage")}
874
874
  aria-label={`Remove image ${img.name}`}
875
- onClick={() => removeImage(i)}
875
+ onClick={() => removeImage(imgIdx)}
876
876
  className="absolute -top-1.5 -right-1.5 w-4 h-4 rounded-full bg-danger text-white text-[10px] flex items-center justify-center opacity-100 sm:opacity-0 sm:group-hover:opacity-100 focus-visible:opacity-100 transition-opacity cursor-pointer"
877
877
  >
878
878
  ×
@@ -6,11 +6,11 @@
6
6
  * 2. Secrets (modal)
7
7
  */
8
8
 
9
- import { useCallback, useEffect, useState } from "react";
10
9
  import {
11
10
  WALLET_RPC_PROVIDER_OPTIONS,
12
11
  type WalletRpcSelections,
13
12
  } from "@elizaos/autonomous/contracts/wallet";
13
+ import { useCallback, useEffect, useState } from "react";
14
14
  import { client } from "../api";
15
15
  import {
16
16
  ConfigRenderer,
@@ -468,18 +468,19 @@ export function ConfigPageView({ embedded = false }: { embedded?: boolean }) {
468
468
  }, []);
469
469
 
470
470
  /* ── RPC provider selection state ──────────────────────────────────── */
471
+ const initialRpc = resolveInitialWalletRpcSelections(walletConfig);
471
472
  const [selectedEvmRpc, setSelectedEvmRpc] =
472
- useState<WalletRpcSelections["evm"]>("eliza-cloud");
473
+ useState<WalletRpcSelections["evm"]>(initialRpc.evm);
473
474
  const [selectedBscRpc, setSelectedBscRpc] =
474
- useState<WalletRpcSelections["bsc"]>("eliza-cloud");
475
+ useState<WalletRpcSelections["bsc"]>(initialRpc.bsc);
475
476
  const [selectedSolanaRpc, setSelectedSolanaRpc] =
476
- useState<WalletRpcSelections["solana"]>("eliza-cloud");
477
+ useState<WalletRpcSelections["solana"]>(initialRpc.solana);
477
478
 
478
479
  useEffect(() => {
479
- const initialSelections = resolveInitialWalletRpcSelections(walletConfig);
480
- setSelectedEvmRpc(initialSelections.evm);
481
- setSelectedBscRpc(initialSelections.bsc);
482
- setSelectedSolanaRpc(initialSelections.solana);
480
+ const selections = resolveInitialWalletRpcSelections(walletConfig);
481
+ setSelectedEvmRpc(selections.evm);
482
+ setSelectedBscRpc(selections.bsc);
483
+ setSelectedSolanaRpc(selections.solana);
483
484
  }, [walletConfig]);
484
485
 
485
486
  const handleWalletSaveAll = useCallback(() => {
@@ -1,4 +1,3 @@
1
- import { useApp } from "../state";
2
1
  import {
3
2
  DropdownMenu,
4
3
  DropdownMenuContent,
@@ -6,6 +5,7 @@ import {
6
5
  DropdownMenuTrigger,
7
6
  } from "@elizaos/ui";
8
7
  import { useEffect, useRef, useState } from "react";
8
+ import { useApp } from "../state";
9
9
  import { ConversationListItem } from "./conversations/ConversationListItem";
10
10
 
11
11
  type ConversationsSidebarVariant = "default" | "game-modal";
@@ -989,15 +989,15 @@ export function CustomActionEditor({
989
989
  {t("customactioneditor.AddParameter")}
990
990
  </button>
991
991
  </div>
992
- {parameters.map((param, i) => (
992
+ {parameters.map((param, paramIdx) => (
993
993
  <div
994
- key={`${param.name}-${i}`}
994
+ key={`${param.name}-${param.description ?? ""}`}
995
995
  className="flex gap-2 items-start"
996
996
  >
997
997
  <input
998
998
  type="text"
999
999
  value={param.name}
1000
- onChange={(e) => updateParameter(i, "name", e.target.value)}
1000
+ onChange={(e) => updateParameter(paramIdx, "name", e.target.value)}
1001
1001
  placeholder={t("customactioneditor.paramName")}
1002
1002
  className="w-32 bg-surface border border-border px-2 py-1.5 text-sm text-txt placeholder:text-muted/50 outline-none focus:border-accent"
1003
1003
  />
@@ -1005,7 +1005,7 @@ export function CustomActionEditor({
1005
1005
  type="text"
1006
1006
  value={param.description}
1007
1007
  onChange={(e) =>
1008
- updateParameter(i, "description", e.target.value)
1008
+ updateParameter(paramIdx, "description", e.target.value)
1009
1009
  }
1010
1010
  placeholder={t("skillsview.Description")}
1011
1011
  className="flex-1 bg-surface border border-border px-2 py-1.5 text-sm text-txt placeholder:text-muted/50 outline-none focus:border-accent"
@@ -1015,7 +1015,7 @@ export function CustomActionEditor({
1015
1015
  type="checkbox"
1016
1016
  checked={param.required}
1017
1017
  onChange={(e) =>
1018
- updateParameter(i, "required", e.target.checked)
1018
+ updateParameter(paramIdx, "required", e.target.checked)
1019
1019
  }
1020
1020
  className="cursor-pointer"
1021
1021
  />
@@ -1024,7 +1024,7 @@ export function CustomActionEditor({
1024
1024
  </span>
1025
1025
  <button
1026
1026
  type="button"
1027
- onClick={() => removeParameter(i)}
1027
+ onClick={() => removeParameter(paramIdx)}
1028
1028
  className="px-2 text-muted hover:text-txt cursor-pointer"
1029
1029
  >
1030
1030
  {t("bugreportmodal.Times")}
@@ -15,7 +15,10 @@ import { useApp } from "@elizaos/app-core/state";
15
15
  import { confirmDesktopAction } from "@elizaos/app-core/utils";
16
16
  import { Button, Input } from "@elizaos/ui";
17
17
  import { useCallback, useEffect, useState } from "react";
18
- import { parsePositiveFloat, parsePositiveInteger } from "../utils/number-parsing";
18
+ import {
19
+ parsePositiveFloat,
20
+ parsePositiveInteger,
21
+ } from "../utils/number-parsing";
19
22
 
20
23
  const TRAINING_EVENT_KINDS = new Set<TrainingStreamEvent["kind"]>([
21
24
  "job_started",
@@ -1048,9 +1051,9 @@ export function FineTuningView() {
1048
1051
  {t("finetuningview.NoLiveEventsYet")}
1049
1052
  </div>
1050
1053
  ) : (
1051
- trainingEvents.map((event, index) => (
1054
+ trainingEvents.map((event) => (
1052
1055
  <div
1053
- key={`${event.ts}-${event.kind}-${index}`}
1056
+ key={`${event.ts}-${event.kind}-${String(event.message ?? "")}`}
1054
1057
  className="px-2 py-1.5 border-b border-border text-xs"
1055
1058
  >
1056
1059
  <span className="font-mono text-muted mr-2">
@@ -1,8 +1,5 @@
1
- import {
2
- APP_EMOTE_EVENT,
3
- type AppEmoteEventDetail,
4
- } from "../events";
5
1
  import { useEffect, useRef, useState } from "react";
2
+ import { APP_EMOTE_EVENT, type AppEmoteEventDetail } from "../events";
6
3
 
7
4
  const OVERLAY_LIFETIME_MS = 2400;
8
5
 
@@ -261,7 +261,7 @@ export function HeartbeatsView() {
261
261
  triggersLoading = false,
262
262
  triggersSaving = false,
263
263
  triggerRunsById = {},
264
- triggerHealth = null,
264
+ triggerHealth: _triggerHealth = null,
265
265
  triggerError = null,
266
266
  loadTriggers = async () => {},
267
267
  createTrigger = async () => null,
@@ -8,7 +8,7 @@
8
8
  import { useApp } from "@elizaos/app-core/state";
9
9
  import { useCallback, useState } from "react";
10
10
  import { TradePanel } from "./BscTradePanel";
11
- import { CHAIN_CONFIGS, resolveChainKey, type ChainKey } from "./chainConfig";
11
+ import { CHAIN_CONFIGS, type ChainKey, resolveChainKey } from "./chainConfig";
12
12
  import {
13
13
  BSC_GAS_THRESHOLD,
14
14
  loadTrackedBscTokens,
@@ -168,7 +168,7 @@ export function InventoryView({ inModal }: { inModal?: boolean } = {}) {
168
168
  : null;
169
169
 
170
170
  const headerWarning =
171
- chainFocus !== "all" && cfg?.legacyCustomChains?.includes(chainFocus)
171
+ chainFocus !== "all" && cfg?.legacyCustomChains?.includes(chainFocus as unknown as "evm" | "bsc" | "solana")
172
172
  ? {
173
173
  title: `${
174
174
  focusedChainLabel ??
@@ -24,6 +24,7 @@ export function LoadingScreen({
24
24
  vrmUrl,
25
25
  }: LoadingScreenProps) {
26
26
  const [vrmCached, setVrmCached] = useState(false);
27
+ const [fetchProgress, setFetchProgress] = useState(0);
27
28
  const [, setRuntimeElapsedSeconds] = useState(0);
28
29
 
29
30
  useEffect(() => {
@@ -40,16 +41,45 @@ export function LoadingScreen({
40
41
  useEffect(() => {
41
42
  if (!vrmUrl) return;
42
43
  const controller = new AbortController();
43
- fetch(vrmUrl, { signal: controller.signal })
44
- .then(() => setVrmCached(true))
45
- .catch(() => {
44
+
45
+ (async () => {
46
+ try {
47
+ const response = await fetch(vrmUrl, { signal: controller.signal });
48
+ const contentLength = Number(response.headers.get("content-length") || 0);
49
+
50
+ if (!contentLength || !response.body) {
51
+ setVrmCached(true);
52
+ return;
53
+ }
54
+
55
+ const reader = response.body.getReader();
56
+ let received = 0;
57
+
58
+ for (;;) {
59
+ const { done, value } = await reader.read();
60
+ if (done) break;
61
+ received += value.byteLength;
62
+ setFetchProgress(Math.min(received / contentLength, 1));
63
+ }
64
+
65
+ setVrmCached(true);
66
+ } catch {
46
67
  // Non-blocking — VRM will load normally later.
47
- });
68
+ }
69
+ })();
70
+
48
71
  return () => controller.abort();
49
72
  }, [vrmUrl]);
50
73
 
51
74
  const meta = PHASE_META[phase];
52
- const progress = vrmCached ? Math.max(meta.progress, 80) : meta.progress;
75
+ let progress: number;
76
+ if (vrmCached) {
77
+ progress = Math.max(meta.progress, 80);
78
+ } else if (fetchProgress > 0) {
79
+ progress = Math.max(meta.progress, Math.round(55 + fetchProgress * 25));
80
+ } else {
81
+ progress = meta.progress;
82
+ }
53
83
  const label = vrmCached && phase !== "ready" ? "Loading avatar" : meta.label;
54
84
 
55
85
  return (
@@ -64,7 +94,10 @@ export function LoadingScreen({
64
94
  <div className="loading-screen__progress-track">
65
95
  <div
66
96
  className="loading-screen__progress-fill"
67
- style={{ width: `${progress}%` }}
97
+ style={{
98
+ width: `${progress}%`,
99
+ transition: "width 1.5s ease-out",
100
+ }}
68
101
  />
69
102
  </div>
70
103
  <div className="loading-screen__percent">{progress} %</div>
@@ -12,11 +12,11 @@ import { useEffect } from "react";
12
12
  import { VrmStage } from "./companion/VrmStage";
13
13
  import { ActivateStep } from "./onboarding/ActivateStep";
14
14
  import { ConnectionStep } from "./onboarding/ConnectionStep";
15
+ import { IdentityStep } from "./onboarding/IdentityStep";
15
16
  import { OnboardingPanel } from "./onboarding/OnboardingPanel";
16
17
  import { OnboardingStepNav } from "./onboarding/OnboardingStepNav";
17
18
  import { PermissionsStep } from "./onboarding/PermissionsStep";
18
19
  import { RpcStep } from "./onboarding/RpcStep";
19
- import { IdentityStep } from "./onboarding/IdentityStep";
20
20
  import { WakeUpStep } from "./onboarding/WakeUpStep";
21
21
 
22
22
  export function OnboardingWizard() {
@@ -1949,6 +1949,8 @@ function PluginListView({ label, mode = "all", inModal }: PluginListViewProps) {
1949
1949
  setConnectorSelectedId(pluginId);
1950
1950
  setConnectorExpandedIds((prev) => {
1951
1951
  if (desktopConnectorLayout) {
1952
+ // Accordion: toggle off if already open, otherwise open this one only
1953
+ if (prev.has(pluginId)) return new Set();
1952
1954
  return new Set([pluginId]);
1953
1955
  }
1954
1956
  const next = new Set(prev);
@@ -2725,6 +2727,10 @@ function PluginListView({ label, mode = "all", inModal }: PluginListViewProps) {
2725
2727
  <div className="relative flex-1 min-w-[220px]">
2726
2728
  <Input
2727
2729
  type="text"
2730
+ name="plugin-search"
2731
+ autoComplete="off"
2732
+ data-1p-ignore
2733
+ data-lpignore="true"
2728
2734
  className="w-full bg-card/60 backdrop-blur-md shadow-inner pr-8 h-9 rounded-xl focus-visible:ring-accent border-border/40"
2729
2735
  placeholder={searchPlaceholder}
2730
2736
  value={pluginSearch}
@@ -1,10 +1,10 @@
1
+ import { GlobalEmoteOverlay } from "./GlobalEmoteOverlay";
1
2
  import {
2
3
  BugReportModal,
3
4
  CommandPalette,
4
5
  RestartBanner,
5
6
  ShortcutsOverlay,
6
7
  } from "./index";
7
- import { GlobalEmoteOverlay } from "./GlobalEmoteOverlay";
8
8
 
9
9
  export interface ActionNotice {
10
10
  text: string;
@@ -16,10 +16,7 @@ import {
16
16
  type VoiceMode,
17
17
  type VoiceProvider,
18
18
  } from "../api";
19
- import {
20
- getSwabblePlugin,
21
- type SwabbleConfig,
22
- } from "../bridge/native-plugins";
19
+ import { getSwabblePlugin, type SwabbleConfig } from "../bridge/native-plugins";
23
20
  import { dispatchWindowEvent, VOICE_CONFIG_UPDATED_EVENT } from "../events";
24
21
  import { useTimeout } from "../hooks";
25
22
  import { useApp } from "../state";
@@ -165,7 +162,9 @@ function WakeWordSection({
165
162
  await getSwabblePlugin().stop();
166
163
  setEnabled(false);
167
164
  } else {
168
- const result = await getSwabblePlugin().start({ config: buildConfig() });
165
+ const result = await getSwabblePlugin().start({
166
+ config: buildConfig(),
167
+ });
169
168
  if (result.started) setEnabled(true);
170
169
  }
171
170
  } catch {
@@ -10,6 +10,15 @@ import type {
10
10
  SplatMesh as SparkSplatMesh,
11
11
  } from "@sparkjsdev/spark";
12
12
  import * as THREE from "three";
13
+
14
+ /** Three.js NodeMaterial exposes emissiveNode/opacityNode but they are not in public MeshStandardMaterial types. */
15
+ interface MeshStandardMaterialWithNodeProps {
16
+ // biome-ignore lint/suspicious/noExplicitAny: THREE node types are not in public @types/three
17
+ emissiveNode?: any;
18
+ // biome-ignore lint/suspicious/noExplicitAny: THREE node types are not in public @types/three
19
+ opacityNode?: any;
20
+ }
21
+
13
22
  import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
14
23
  import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
15
24
  import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
@@ -2009,6 +2018,12 @@ export class VrmEngine {
2009
2018
  }
2010
2019
  activeEmote?.fadeOut(fadeDuration);
2011
2020
  }
2021
+
2022
+ /** Play a one-shot wave greeting after the VRM becomes visible. */
2023
+ playWaveGreeting(): void {
2024
+ this.playEmote("animations/emotes/waving-both-hands.glb.gz", 3, false);
2025
+ }
2026
+
2012
2027
  async loadVrmFromUrl(url: string, name?: string): Promise<void> {
2013
2028
  await this.whenReady();
2014
2029
  if (!this.scene) throw new Error("VrmEngine not initialized");
@@ -2119,9 +2134,13 @@ export class VrmEngine {
2119
2134
  await this.loadAndPlayIdle(vrm);
2120
2135
  if (!this.loadingAborted && this.vrm === vrm) {
2121
2136
  this.vrmReady = true;
2137
+ // Let the idle animation settle into a natural pose before revealing
2138
+ await new Promise((resolve) => setTimeout(resolve, 300));
2139
+ if (this.loadingAborted || this.vrm !== vrm) return;
2122
2140
  await this.playTeleportReveal(vrm);
2123
2141
  vrm.scene.visible = true;
2124
2142
  this.startPendingWorldReveal(true);
2143
+ this.playWaveGreeting();
2125
2144
  }
2126
2145
  } catch {
2127
2146
  if (!this.loadingAborted && this.vrm === vrm) {
@@ -2155,8 +2174,8 @@ export class VrmEngine {
2155
2174
  appliedNodeDissolve = true;
2156
2175
  mat.userData._dissolveApplied = true;
2157
2176
  mat.userData._origOpacityNode = mat.opacityNode ?? null;
2158
- // biome-ignore lint/suspicious/noExplicitAny: Three.js NodeMaterial emissiveNode is not in public types
2159
- mat.userData._origEmissiveNode = (mat as any).emissiveNode ?? null;
2177
+ mat.userData._origEmissiveNode =
2178
+ (mat as MeshStandardMaterialWithNodeProps).emissiveNode ?? null;
2160
2179
  mat.userData._origAlphaTest = mat.alphaTest;
2161
2180
 
2162
2181
  // World-space Y from TSL
@@ -2221,10 +2240,9 @@ export class VrmEngine {
2221
2240
  ? origOpacity.mul(dissolveAlpha)
2222
2241
  : dissolveAlpha;
2223
2242
 
2224
- // biome-ignore lint/suspicious/noExplicitAny: Three.js NodeMaterial emissiveNode is not in public types
2225
- const origEmissive = (mat as any).emissiveNode;
2226
- // biome-ignore lint/suspicious/noExplicitAny: Three.js NodeMaterial emissiveNode is not in public types
2227
- (mat as any).emissiveNode = origEmissive
2243
+ const matWithEmissive = mat as MeshStandardMaterialWithNodeProps;
2244
+ const origEmissive = matWithEmissive.emissiveNode;
2245
+ matWithEmissive.emissiveNode = origEmissive
2228
2246
  ? origEmissive.add(emissiveBoost)
2229
2247
  : emissiveBoost;
2230
2248
 
@@ -2429,7 +2447,7 @@ if (teleportNoise < teleportRatio) discard;
2429
2447
  mat.userData._origOpacityNode ?? null;
2430
2448
  }
2431
2449
  if (mat.userData._origEmissiveNode !== undefined) {
2432
- (mat as unknown as Record<string, unknown>).emissiveNode =
2450
+ (mat as MeshStandardMaterialWithNodeProps).emissiveNode =
2433
2451
  mat.userData._origEmissiveNode ?? null;
2434
2452
  }
2435
2453
  mat.alphaTest = mat.userData._origAlphaTest ?? 0;
@@ -1 +1,5 @@
1
- export { CompanionSceneHost, SharedCompanionScene, useSharedCompanionScene } from "../CompanionSceneHost";
1
+ export {
2
+ CompanionSceneHost,
3
+ SharedCompanionScene,
4
+ useSharedCompanionScene,
5
+ } from "../CompanionSceneHost";
@@ -1,6 +1,6 @@
1
- export * from "./ApiKeyConfig";
2
1
  export * from "./AdvancedPageView";
3
2
  export * from "./AgentActivityBox";
3
+ export * from "./ApiKeyConfig";
4
4
  export * from "./AppsPageView";
5
5
  export * from "./AppsView";
6
6
  export * from "./AvatarLoader";
@@ -25,15 +25,20 @@ export * from "./ConfigPageView";
25
25
  export * from "./ConfigSaveFooter";
26
26
  export * from "./ConfirmModal";
27
27
  export * from "./ConnectionFailedBanner";
28
- export * from "./ConversationsSidebar";
29
28
  export * from "./ConnectorsPageView";
30
- export * from "./confirm-delete-control";
29
+ export * from "./ConversationsSidebar";
31
30
  export * from "./CustomActionEditor";
32
31
  export * from "./CustomActionsPanel";
33
32
  export * from "./CustomActionsView";
33
+ export * from "./chainConfig";
34
+ export * from "./companion-shell-styles";
35
+ export * from "./confirm-delete-control";
36
+ export * from "./conversations/ConversationListItem";
37
+ export * from "./conversations/conversation-utils";
34
38
  export * from "./DatabasePageView";
35
39
  export * from "./DatabaseView";
36
40
  export * from "./ElizaCloudDashboard";
41
+ export * from "./ElizaCloudDashboard";
37
42
  export * from "./EmotePicker";
38
43
  export * from "./ErrorBoundary";
39
44
  export * from "./FineTuningView";
@@ -41,8 +46,8 @@ export * from "./format";
41
46
  export * from "./GameView";
42
47
  export * from "./GameViewOverlay";
43
48
  export * from "./GlobalEmoteOverlay";
44
- export * from "./HeartbeatsView";
45
49
  export * from "./Header";
50
+ export * from "./HeartbeatsView";
46
51
  export * from "./InventoryView";
47
52
  export * from "./KnowledgeView";
48
53
  export * from "./knowledge-upload-image";
@@ -56,7 +61,6 @@ export * from "./labels";
56
61
  export * from "./MediaGalleryView";
57
62
  export * from "./MediaSettingsSection";
58
63
  export * from "./MessageContent";
59
- export * from "./ElizaCloudDashboard";
60
64
  export * from "./OnboardingWizard";
61
65
  export * from "./PairingView";
62
66
  export * from "./PermissionsSection";
@@ -78,9 +82,9 @@ export * from "./SystemWarningBanner";
78
82
  export { useSharedCompanionScene } from "./shared-companion-scene-context";
79
83
  export * from "./skeletons";
80
84
  export * from "./ThemeToggle";
81
- export * from "./TriggersView";
82
85
  export * from "./TrajectoriesView";
83
86
  export * from "./TrajectoryDetailView";
87
+ export * from "./TriggersView";
84
88
  export * from "./trajectory-format";
85
89
  export * from "./ui-badges";
86
90
  export * from "./ui-switch";
@@ -88,7 +92,3 @@ export * from "./VectorBrowserView";
88
92
  export * from "./VoiceConfigView";
89
93
  export * from "./VrmStage";
90
94
  export * from "./WhatsAppQrOverlay";
91
- export * from "./chainConfig";
92
- export * from "./companion-shell-styles";
93
- export * from "./conversations/ConversationListItem";
94
- export * from "./conversations/conversation-utils";
@@ -131,11 +131,11 @@ export function TokensTable({
131
131
  </tr>
132
132
  </thead>
133
133
  <tbody>
134
- {visibleRows.map((row, idx) => {
134
+ {visibleRows.map((row) => {
135
135
  const contractAddress = row.contractAddress;
136
136
  return (
137
137
  <tr
138
- key={`${row.chain}-${row.symbol}-${idx}`}
138
+ key={`${row.chain}-${row.symbol}-${row.contractAddress ?? ""}`}
139
139
  className="border-b border-border last:border-b-0 hover:bg-bg-hover transition-colors"
140
140
  >
141
141
  {/* Logo */}
@@ -2,15 +2,13 @@ import { getVrmPreviewUrl, useApp } from "@elizaos/app-core/state";
2
2
  import { useCallback, useEffect } from "react";
3
3
 
4
4
  /** Maps catchphrases → character metadata for onboarding. */
5
- const IDENTITY_PRESETS: Record<
6
- string,
7
- { name: string; avatarIndex: number }
8
- > = {
9
- "Noted.": { name: "Rin", avatarIndex: 1 },
10
- "uwu~": { name: "Ai", avatarIndex: 2 },
11
- "lol k": { name: "Anzu", avatarIndex: 3 },
12
- "hehe~": { name: "Aya", avatarIndex: 4 },
13
- };
5
+ const IDENTITY_PRESETS: Record<string, { name: string; avatarIndex: number }> =
6
+ {
7
+ "Noted.": { name: "Rin", avatarIndex: 1 },
8
+ "uwu~": { name: "Ai", avatarIndex: 2 },
9
+ "lol k": { name: "Anzu", avatarIndex: 3 },
10
+ "hehe~": { name: "Aya", avatarIndex: 4 },
11
+ };
14
12
 
15
13
  export function IdentityStep() {
16
14
  const {
@@ -18,7 +16,7 @@ export function IdentityStep() {
18
16
  onboardingStyle,
19
17
  handleOnboardingNext,
20
18
  setState,
21
- t,
19
+ t: _t,
22
20
  } = useApp();
23
21
 
24
22
  const styles = onboardingOptions?.styles ?? [];
@@ -47,9 +45,7 @@ export function IdentityStep() {
47
45
 
48
46
  return (
49
47
  <>
50
- <div className="onboarding-section-title">
51
- Choose Your Agent
52
- </div>
48
+ <div className="onboarding-section-title">Choose Your Agent</div>
53
49
  <div className="onboarding-divider">
54
50
  <div className="onboarding-divider-diamond" />
55
51
  </div>
@@ -120,7 +120,7 @@ export function ShellHeaderControls({
120
120
  >
121
121
  <div className="flex shrink-0 items-center">
122
122
  <fieldset
123
- className="inline-flex items-center gap-0.5 rounded-xl border border-border/60 bg-transparent p-0.5 shadow-sm dark:border-border/70 dark:bg-transparent"
123
+ className="inline-flex items-center gap-0.5 rounded-xl border border-border/60 bg-transparent p-0.5 shadow-sm dark:border-border dark:bg-transparent"
124
124
  data-testid="ui-shell-toggle"
125
125
  aria-label="Switch shell view"
126
126
  >
@@ -98,17 +98,14 @@ function PasswordFieldInner({ fp: props }: { fp: FieldRenderProps }) {
98
98
 
99
99
  const [visible, setVisible] = useState(false);
100
100
  const [busy, setBusy] = useState(false);
101
+ const [fieldValue, setFieldValue] = useState("");
101
102
  const inputRef = useRef<HTMLInputElement>(null);
102
103
  const onReveal = props.onReveal;
103
104
 
104
105
  const handleToggle = useCallback(async () => {
105
- const input = inputRef.current;
106
- if (!input) return;
107
-
108
106
  if (visible) {
109
- // Currently showing -- hide it
107
+ // Hide just toggle visibility, value stays in React state
110
108
  setVisible(false);
111
- input.value = "";
112
109
  return;
113
110
  }
114
111
 
@@ -119,13 +116,14 @@ function PasswordFieldInner({ fp: props }: { fp: FieldRenderProps }) {
119
116
  setBusy(false);
120
117
  if (realValue != null) {
121
118
  setVisible(true);
122
- input.value = realValue;
119
+ setFieldValue(realValue);
120
+ props.onChange(realValue);
123
121
  }
124
122
  } else {
125
123
  // Fallback: just toggle type (shows whatever is in the input)
126
124
  setVisible(true);
127
125
  }
128
- }, [visible, onReveal]);
126
+ }, [visible, onReveal, props]);
129
127
 
130
128
  return (
131
129
  <div className="flex">
@@ -133,11 +131,12 @@ function PasswordFieldInner({ fp: props }: { fp: FieldRenderProps }) {
133
131
  ref={inputRef}
134
132
  className="flex-1 px-3 py-2 border border-[var(--border)] border-r-0 bg-[var(--card)] text-[13px] font-[var(--mono)] transition-all focus:border-[var(--accent)] focus:outline-none focus:ring-1 focus:ring-[var(--accent)] box-border h-[36px] rounded-l-sm placeholder:text-[var(--muted)] placeholder:opacity-60"
135
133
  type={visible ? "text" : "password"}
136
- defaultValue=""
134
+ value={fieldValue}
137
135
  placeholder={placeholder}
138
136
  data-config-key={props.key}
139
137
  data-field-type="password"
140
138
  onChange={(e) => {
139
+ setFieldValue(e.target.value);
141
140
  props.onChange(e.target.value);
142
141
  fireAction(props, "change");
143
142
  }}
@@ -1,11 +1,11 @@
1
1
  export * from "./config-catalog";
2
2
  export * from "./config-renderer";
3
- export * from "./ui-spec";
4
3
  export {
5
- UiRenderer,
6
- type UiRendererProps,
7
4
  evaluateUiVisibility,
8
5
  getSupportedComponents,
9
6
  runValidation as runUiValidation,
10
7
  sanitizeLinkHref,
8
+ UiRenderer,
9
+ type UiRendererProps,
11
10
  } from "./ui-renderer";
11
+ export * from "./ui-spec";
@@ -850,9 +850,11 @@ export function useVoiceChat(options: VoiceChatOptions): VoiceChatState {
850
850
  enabledRef.current = false;
851
851
 
852
852
  if (sttBackendRef.current === "talkmode") {
853
- await getTalkModePlugin().stop().catch(() => {
854
- /* ignore */
855
- });
853
+ await getTalkModePlugin()
854
+ .stop()
855
+ .catch(() => {
856
+ /* ignore */
857
+ });
856
858
  await new Promise((resolve) =>
857
859
  window.setTimeout(resolve, TALKMODE_STOP_SETTLE_MS),
858
860
  );