@aomi-labs/react 0.3.17 → 0.3.19

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/dist/index.js CHANGED
@@ -174,6 +174,7 @@ var ThreadStore = class {
174
174
  };
175
175
  this.snapshot = this.buildSnapshot();
176
176
  this.emit();
177
+ return threadId;
177
178
  };
178
179
  this.updateThreadMetadata = (threadId, updates) => {
179
180
  const existing = this.state.threadMetadata.get(threadId);
@@ -275,13 +276,9 @@ function resolveAutoModel(models) {
275
276
  return (_a = models[0]) != null ? _a : null;
276
277
  }
277
278
 
278
- // packages/react/src/contexts/control-context.tsx
279
- import { jsx } from "react/jsx-runtime";
280
- var API_KEY_STORAGE_KEY = "aomi_api_key";
279
+ // packages/react/src/utils/client-session.ts
281
280
  var CLIENT_ID_STORAGE_KEY = "aomi_client_id";
282
- var PROVIDER_KEYS_STORAGE_KEY = "aomi_provider_keys";
283
- var MODEL_SELECTION_STORAGE_KEY = "aomi_model_selection";
284
- var PROVIDER_KEY_SECRET_PREFIX = "PROVIDER_KEY:";
281
+ var CONTROL_SESSION_PREFIX = "control:";
285
282
  function getOrCreateClientId() {
286
283
  var _a, _b, _c, _d, _e;
287
284
  try {
@@ -300,6 +297,17 @@ function getOrCreateClientId() {
300
297
  }
301
298
  return clientId;
302
299
  }
300
+ function getControlSessionId(clientId, fallbackSessionId) {
301
+ const trimmedClientId = clientId == null ? void 0 : clientId.trim();
302
+ return trimmedClientId ? `${CONTROL_SESSION_PREFIX}${trimmedClientId}` : fallbackSessionId;
303
+ }
304
+
305
+ // packages/react/src/contexts/control-context.tsx
306
+ import { jsx } from "react/jsx-runtime";
307
+ var API_KEY_STORAGE_KEY = "aomi_api_key";
308
+ var PROVIDER_KEYS_STORAGE_KEY = "aomi_provider_keys";
309
+ var MODEL_SELECTION_STORAGE_KEY = "aomi_model_selection";
310
+ var PROVIDER_KEY_SECRET_PREFIX = "PROVIDER_KEY:";
303
311
  function getDefaultApp(apps) {
304
312
  var _a;
305
313
  return apps.includes("default") ? "default" : (_a = apps[0]) != null ? _a : null;
@@ -392,6 +400,10 @@ function ControlContextProvider({
392
400
  const updateThreadMetadataRef = useRef(updateThreadMetadata);
393
401
  updateThreadMetadataRef.current = updateThreadMetadata;
394
402
  const callbacks = useRef(/* @__PURE__ */ new Set());
403
+ const getCurrentControlSessionId = useCallback(
404
+ () => getControlSessionId(stateRef.current.clientId, sessionIdRef.current),
405
+ []
406
+ );
395
407
  const currentThreadMetadata = getThreadMetadata(sessionId);
396
408
  const isProcessing = (_b = (_a = currentThreadMetadata == null ? void 0 : currentThreadMetadata.control) == null ? void 0 : _a.isProcessing) != null ? _b : false;
397
409
  useEffect(() => {
@@ -458,18 +470,21 @@ function ControlContextProvider({
458
470
  for (const [provider, entry] of Object.entries(keys)) {
459
471
  secrets[`${PROVIDER_KEY_SECRET_PREFIX}${provider}`] = entry.apiKey;
460
472
  }
461
- void aomiClientRef.current.ingestSecrets(state.clientId, secrets).catch((err) => {
473
+ void aomiClientRef.current.ingestSecrets(getCurrentControlSessionId(), state.clientId, secrets).catch((err) => {
462
474
  console.error("Failed to auto-ingest provider keys:", err);
463
475
  });
464
- }, [state.clientId, state.providerKeys]);
476
+ }, [getCurrentControlSessionId, state.clientId, state.providerKeys]);
465
477
  useEffect(() => {
466
478
  const fetchApps = async () => {
467
479
  var _a2;
468
480
  try {
469
- const apps = await aomiClientRef.current.getApps(sessionIdRef.current, {
470
- publicKey: publicKeyRef.current,
471
- apiKey: (_a2 = stateRef.current.apiKey) != null ? _a2 : void 0
472
- });
481
+ const apps = await aomiClientRef.current.getApps(
482
+ getCurrentControlSessionId(),
483
+ {
484
+ publicKey: publicKeyRef.current,
485
+ apiKey: (_a2 = stateRef.current.apiKey) != null ? _a2 : void 0
486
+ }
487
+ );
473
488
  const defaultApp = getDefaultApp(apps);
474
489
  setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
475
490
  authorizedApps: apps,
@@ -484,12 +499,12 @@ function ControlContextProvider({
484
499
  }
485
500
  };
486
501
  void fetchApps();
487
- }, [state.apiKey, publicKey]);
502
+ }, [getCurrentControlSessionId, state.apiKey, publicKey]);
488
503
  useEffect(() => {
489
504
  const fetchModels = async () => {
490
505
  try {
491
506
  const models = await aomiClientRef.current.getModels(
492
- sessionIdRef.current
507
+ getCurrentControlSessionId()
493
508
  );
494
509
  setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
495
510
  availableModels: models,
@@ -500,7 +515,7 @@ function ControlContextProvider({
500
515
  }
501
516
  };
502
517
  void fetchModels();
503
- }, []);
518
+ }, [getCurrentControlSessionId]);
504
519
  const setApiKey = useCallback((apiKey) => {
505
520
  setStateInternal((prev) => {
506
521
  const next = __spreadProps(__spreadValues({}, prev), { apiKey: apiKey === "" ? null : apiKey });
@@ -513,19 +528,24 @@ function ControlContextProvider({
513
528
  const clientId = stateRef.current.clientId;
514
529
  if (!clientId) throw new Error("clientId not initialized");
515
530
  const { handles } = await aomiClientRef.current.ingestSecrets(
531
+ getCurrentControlSessionId(),
516
532
  clientId,
517
533
  secrets
518
534
  );
519
535
  return handles;
520
536
  },
521
- []
537
+ [getCurrentControlSessionId]
522
538
  );
523
539
  const clearSecrets = useCallback(async () => {
524
540
  var _a2, _b2;
525
541
  const clientId = stateRef.current.clientId;
526
542
  if (!clientId) return;
527
- await ((_b2 = (_a2 = aomiClientRef.current).clearSecrets) == null ? void 0 : _b2.call(_a2, clientId));
528
- }, []);
543
+ await ((_b2 = (_a2 = aomiClientRef.current).clearSecrets) == null ? void 0 : _b2.call(
544
+ _a2,
545
+ getCurrentControlSessionId(),
546
+ clientId
547
+ ));
548
+ }, [getCurrentControlSessionId]);
529
549
  const setProviderKey = useCallback(
530
550
  async (provider, apiKey, label) => {
531
551
  const trimmed = apiKey.trim();
@@ -545,21 +565,26 @@ function ControlContextProvider({
545
565
  const clientId = stateRef.current.clientId;
546
566
  if (clientId) {
547
567
  try {
548
- await aomiClientRef.current.ingestSecrets(clientId, {
549
- [`${PROVIDER_KEY_SECRET_PREFIX}${provider}`]: trimmed
550
- });
568
+ await aomiClientRef.current.ingestSecrets(
569
+ getCurrentControlSessionId(),
570
+ clientId,
571
+ {
572
+ [`${PROVIDER_KEY_SECRET_PREFIX}${provider}`]: trimmed
573
+ }
574
+ );
551
575
  } catch (err) {
552
576
  console.error("Failed to ingest provider key:", err);
553
577
  }
554
578
  }
555
579
  },
556
- []
580
+ [getCurrentControlSessionId]
557
581
  );
558
582
  const removeProviderKey = useCallback(
559
583
  async (provider) => {
560
584
  const clientId = stateRef.current.clientId;
561
585
  if (clientId) {
562
586
  await aomiClientRef.current.deleteSecret(
587
+ getCurrentControlSessionId(),
563
588
  clientId,
564
589
  `${PROVIDER_KEY_SECRET_PREFIX}${provider}`
565
590
  );
@@ -571,7 +596,7 @@ function ControlContextProvider({
571
596
  return next;
572
597
  });
573
598
  },
574
- []
599
+ [getCurrentControlSessionId]
575
600
  );
576
601
  const getProviderKeys = useCallback(
577
602
  () => stateRef.current.providerKeys,
@@ -585,7 +610,7 @@ function ControlContextProvider({
585
610
  const getAvailableModels = useCallback(async () => {
586
611
  try {
587
612
  const models = await aomiClientRef.current.getModels(
588
- sessionIdRef.current
613
+ getCurrentControlSessionId()
589
614
  );
590
615
  setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
591
616
  availableModels: models,
@@ -596,14 +621,17 @@ function ControlContextProvider({
596
621
  console.error("Failed to fetch models:", error);
597
622
  return [];
598
623
  }
599
- }, []);
624
+ }, [getCurrentControlSessionId]);
600
625
  const getAuthorizedApps = useCallback(async () => {
601
626
  var _a2;
602
627
  try {
603
- const apps = await aomiClientRef.current.getApps(sessionIdRef.current, {
604
- publicKey: publicKeyRef.current,
605
- apiKey: (_a2 = stateRef.current.apiKey) != null ? _a2 : void 0
606
- });
628
+ const apps = await aomiClientRef.current.getApps(
629
+ getCurrentControlSessionId(),
630
+ {
631
+ publicKey: publicKeyRef.current,
632
+ apiKey: (_a2 = stateRef.current.apiKey) != null ? _a2 : void 0
633
+ }
634
+ );
607
635
  const defaultApp = getDefaultApp(apps);
608
636
  setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
609
637
  authorizedApps: apps,
@@ -618,7 +646,7 @@ function ControlContextProvider({
618
646
  }));
619
647
  return ["default"];
620
648
  }
621
- }, []);
649
+ }, [getCurrentControlSessionId]);
622
650
  const getCurrentThreadControl = useCallback(() => {
623
651
  var _a2;
624
652
  const metadata = getThreadMetadataRef.current(sessionIdRef.current);
@@ -1187,7 +1215,7 @@ function UserContextProvider({ children }) {
1187
1215
  }
1188
1216
 
1189
1217
  // packages/react/src/runtime/core.tsx
1190
- import { useCallback as useCallback7, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef8, useState as useState6 } from "react";
1218
+ import { useCallback as useCallback8, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef8, useState as useState7 } from "react";
1191
1219
  import {
1192
1220
  AssistantRuntimeProvider,
1193
1221
  useExternalStoreRuntime
@@ -1546,6 +1574,7 @@ function useRuntimeOrchestrator(aomiClient, options) {
1546
1574
  cleanups.push(forwardEvent("system_notice"));
1547
1575
  cleanups.push(forwardEvent("system_error"));
1548
1576
  cleanups.push(forwardEvent("async_callback"));
1577
+ cleanups.push(forwardEvent("user_state_request"));
1549
1578
  listenerCleanups.current.set(threadId, () => {
1550
1579
  for (const cleanup of cleanups) cleanup();
1551
1580
  });
@@ -1953,40 +1982,14 @@ function useWalletHandler({
1953
1982
  }
1954
1983
 
1955
1984
  // packages/react/src/runtime/user-state-provider.tsx
1956
- import { useEffect as useEffect3, useRef as useRef7 } from "react";
1985
+ import {
1986
+ useCallback as useCallback7,
1987
+ useEffect as useEffect3,
1988
+ useRef as useRef7,
1989
+ useState as useState6
1990
+ } from "react";
1991
+ import { UserState as UserStateHelpers } from "@aomi-labs/client";
1957
1992
  import { Fragment, jsx as jsx6 } from "react/jsx-runtime";
1958
- function stableStateString(state) {
1959
- return JSON.stringify(state != null ? state : {});
1960
- }
1961
- function RuntimeUserStateProvider({
1962
- children,
1963
- sessionManager,
1964
- getUserState,
1965
- onUserStateChange
1966
- }) {
1967
- const lastSerializedStateRef = useRef7("");
1968
- useEffect3(() => {
1969
- const applyToSessions = (next) => {
1970
- const serialized = stableStateString(next);
1971
- if (serialized === lastSerializedStateRef.current) {
1972
- return;
1973
- }
1974
- lastSerializedStateRef.current = serialized;
1975
- sessionManager.forEach((session) => {
1976
- session.resolveUserState(next);
1977
- });
1978
- };
1979
- applyToSessions(getUserState());
1980
- const unsubscribe = onUserStateChange((next) => {
1981
- applyToSessions(next);
1982
- });
1983
- return unsubscribe;
1984
- }, [getUserState, onUserStateChange, sessionManager]);
1985
- return /* @__PURE__ */ jsx6(Fragment, { children });
1986
- }
1987
-
1988
- // packages/react/src/runtime/core.tsx
1989
- import { jsx as jsx7 } from "react/jsx-runtime";
1990
1993
  var THREAD_PREFETCH_LIMIT = 5;
1991
1994
  var PREFETCH_IDLE_TIMEOUT_MS = 1500;
1992
1995
  function scheduleBackgroundTask(task) {
@@ -2003,87 +2006,42 @@ function scheduleBackgroundTask(task) {
2003
2006
  const timeoutId = runtimeGlobal.setTimeout(task, 0);
2004
2007
  return () => runtimeGlobal.clearTimeout(timeoutId);
2005
2008
  }
2006
- function AomiRuntimeCore({
2007
- children,
2008
- aomiClient
2009
- }) {
2010
- const threadContext = useThreadContext();
2011
- const eventContext = useEventContext();
2012
- const notificationContext = useNotification();
2013
- const { user, onUserStateChange, getUserState } = useUser();
2014
- const {
2015
- getControlState,
2016
- getCurrentThreadApp,
2017
- getPreferredThreadControl,
2018
- syncCurrentThreadControl
2019
- } = useControl();
2020
- const sessionManagerRef = useRef8(null);
2021
- const walletHandler = useWalletHandler({
2022
- getSession: () => {
2023
- var _a;
2024
- return (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadContext.currentThreadId);
2025
- }
2026
- });
2027
- const {
2028
- sessionManager,
2029
- getSession,
2030
- isRunning,
2031
- setIsRunning,
2032
- ensureInitialState,
2033
- sendMessage: orchestratorSendMessage,
2034
- cancelGeneration: orchestratorCancel,
2035
- closeSession,
2036
- closeIdleSessionsExcept,
2037
- closeAllSessions,
2038
- aomiClientRef
2039
- } = useRuntimeOrchestrator(aomiClient, {
2040
- getPublicKey: () => UserState3.isConnected(getUserState()) ? UserState3.address(getUserState()) : void 0,
2041
- getUserState,
2042
- getApp: getCurrentThreadApp,
2043
- getApiKey: () => getControlState().apiKey,
2044
- getClientId: () => {
2045
- var _a;
2046
- return (_a = getControlState().clientId) != null ? _a : void 0;
2047
- },
2048
- prepareThreadForSend: async (threadId) => {
2049
- await ensureBackendThread(threadId);
2050
- await syncCurrentThreadControl();
2051
- },
2052
- onPendingRequestsChange: walletHandler.setRequests,
2053
- onEvent: (event) => eventContext.dispatch(event)
2054
- });
2055
- sessionManagerRef.current = sessionManager;
2056
- const threadContextRef = useRef8(threadContext);
2057
- threadContextRef.current = threadContext;
2058
- const remoteThreadIdsRef = useRef8(/* @__PURE__ */ new Set());
2059
- const warmedThreadIdsRef = useRef8(/* @__PURE__ */ new Set());
2060
- const warmPromisesRef = useRef8(/* @__PURE__ */ new Map());
2061
- const prefetchCancelRef = useRef8(null);
2062
- const [isThreadLoading, setIsThreadLoading] = useState6(false);
2063
- const [isThreadListLoading, setIsThreadListLoading] = useState6(true);
2009
+ function stableStateString(state) {
2010
+ return JSON.stringify(state != null ? state : {});
2011
+ }
2012
+ function useWalletStateSync(context, sessions, remoteThreads) {
2013
+ const { getUserState, onUserStateChange, threadContextRef } = context;
2014
+ const { aomiClientRef } = sessions;
2015
+ const { remoteThreadIdsRef } = remoteThreads;
2064
2016
  const walletSnapshot = useCallback7(
2065
2017
  (nextUser) => {
2066
2018
  var _a;
2067
2019
  return {
2068
- address: UserState3.address(nextUser),
2069
- chain_id: UserState3.chainId(nextUser),
2070
- is_connected: (_a = UserState3.isConnected(nextUser)) != null ? _a : false,
2020
+ address: UserStateHelpers.address(nextUser),
2021
+ chain_id: UserStateHelpers.chainId(nextUser),
2022
+ is_connected: (_a = UserStateHelpers.isConnected(nextUser)) != null ? _a : false,
2071
2023
  ens_name: typeof nextUser.ens_name === "string" ? nextUser.ens_name : void 0
2072
2024
  };
2073
2025
  },
2074
2026
  [getUserState]
2075
2027
  );
2076
- const lastWalletStateRef = useRef8(walletSnapshot(getUserState()));
2077
- useEffect4(() => {
2028
+ const lastWalletStateRef = useRef7(walletSnapshot(getUserState()));
2029
+ useEffect3(() => {
2078
2030
  lastWalletStateRef.current = walletSnapshot(getUserState());
2079
2031
  const unsubscribe = onUserStateChange(async (newUser) => {
2032
+ var _a, _b;
2080
2033
  const nextWalletState = walletSnapshot(newUser);
2081
2034
  const prevWalletState = lastWalletStateRef.current;
2035
+ const previousAddress = (_a = prevWalletState.address) == null ? void 0 : _a.toLowerCase();
2036
+ const nextAddress = (_b = nextWalletState.address) == null ? void 0 : _b.toLowerCase();
2082
2037
  if (prevWalletState.address === nextWalletState.address && prevWalletState.chain_id === nextWalletState.chain_id && prevWalletState.is_connected === nextWalletState.is_connected && prevWalletState.ens_name === nextWalletState.ens_name) {
2083
2038
  return;
2084
2039
  }
2085
2040
  lastWalletStateRef.current = nextWalletState;
2086
- const sessionId = threadContext.currentThreadId;
2041
+ if (previousAddress !== void 0 && nextAddress !== void 0 && previousAddress !== nextAddress) {
2042
+ return;
2043
+ }
2044
+ const sessionId = threadContextRef.current.currentThreadId;
2087
2045
  if (!remoteThreadIdsRef.current.has(sessionId)) {
2088
2046
  return;
2089
2047
  }
@@ -2095,38 +2053,55 @@ function AomiRuntimeCore({
2095
2053
  });
2096
2054
  return unsubscribe;
2097
2055
  }, [
2098
- onUserStateChange,
2099
2056
  aomiClientRef,
2100
- threadContext.currentThreadId,
2101
2057
  getUserState,
2058
+ onUserStateChange,
2059
+ remoteThreadIdsRef,
2060
+ threadContextRef,
2102
2061
  walletSnapshot
2103
2062
  ]);
2104
- const warmThread = useCallback7(
2105
- async (threadId) => {
2106
- if (!remoteThreadIdsRef.current.has(threadId) || warmedThreadIdsRef.current.has(threadId)) {
2107
- return;
2108
- }
2109
- const existingPromise = warmPromisesRef.current.get(threadId);
2110
- if (existingPromise) {
2111
- return existingPromise;
2112
- }
2113
- const warmPromise = (async () => {
2114
- const userState = getUserState();
2115
- await aomiClientRef.current.createThread(
2116
- threadId,
2117
- UserState3.isConnected(userState) ? UserState3.address(userState) : void 0
2118
- );
2119
- warmedThreadIdsRef.current.add(threadId);
2120
- })();
2121
- warmPromisesRef.current.set(threadId, warmPromise);
2122
- try {
2123
- await warmPromise;
2124
- } finally {
2125
- warmPromisesRef.current.delete(threadId);
2126
- }
2127
- },
2128
- [aomiClientRef, getUserState]
2129
- );
2063
+ }
2064
+ function useUserStateRequestResponder(context, sessions) {
2065
+ const eventContext = useEventContext();
2066
+ const { getUserState, threadContextRef } = context;
2067
+ const { getSession } = sessions;
2068
+ useEffect3(() => {
2069
+ const unsubscribe = eventContext.subscribe("user_state_request", () => {
2070
+ var _a, _b;
2071
+ const sessionId = threadContextRef.current.currentThreadId;
2072
+ const session = getSession(sessionId);
2073
+ const payload = (_b = (_a = UserStateHelpers.reconcile(session.getUserState(), getUserState())) != null ? _a : session.getUserState()) != null ? _b : getUserState();
2074
+ eventContext.sendOutboundSystem({
2075
+ type: "user_state_response",
2076
+ sessionId,
2077
+ payload
2078
+ });
2079
+ });
2080
+ return unsubscribe;
2081
+ }, [eventContext, getSession, getUserState, threadContextRef]);
2082
+ }
2083
+ function useRemoteThreadListSync(context, sessions, remoteThreads) {
2084
+ const [isThreadListLoading, setIsThreadListLoading] = useState6(true);
2085
+ const prefetchCancelRef = useRef7(null);
2086
+ const lastConnectedAddressRef = useRef7(void 0);
2087
+ const {
2088
+ getControlState,
2089
+ threadContextRef,
2090
+ user
2091
+ } = context;
2092
+ const {
2093
+ aomiClientRef,
2094
+ closeAllSessions,
2095
+ ensureInitialState,
2096
+ sessionManager,
2097
+ setIsThreadLoading
2098
+ } = sessions;
2099
+ const {
2100
+ remoteThreadIdsRef,
2101
+ warmPromisesRef,
2102
+ warmedThreadIdsRef,
2103
+ warmThread
2104
+ } = remoteThreads;
2130
2105
  const scheduleThreadPrefetch = useCallback7(
2131
2106
  (threadIds) => {
2132
2107
  var _a;
@@ -2146,8 +2121,9 @@ function AomiRuntimeCore({
2146
2121
  }
2147
2122
  try {
2148
2123
  await warmThread(threadId);
2149
- if (cancelled || !remoteThreadIdsRef.current.has(threadId))
2124
+ if (cancelled || !remoteThreadIdsRef.current.has(threadId)) {
2150
2125
  return;
2126
+ }
2151
2127
  await ensureInitialState(threadId);
2152
2128
  } catch (error) {
2153
2129
  console.debug("Failed to prefetch thread:", threadId, error);
@@ -2160,81 +2136,16 @@ function AomiRuntimeCore({
2160
2136
  cancelScheduledTask();
2161
2137
  };
2162
2138
  },
2163
- [ensureInitialState, warmThread]
2164
- );
2165
- useEffect4(() => {
2166
- const unsubscribe = eventContext.subscribe("user_state_request", () => {
2167
- var _a, _b, _c;
2168
- const session = (_b = (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadContext.currentThreadId)) != null ? _b : getSession(threadContext.currentThreadId);
2169
- eventContext.sendOutboundSystem({
2170
- type: "user_state_response",
2171
- sessionId: threadContext.currentThreadId,
2172
- payload: (_c = session.getUserState()) != null ? _c : getUserState()
2173
- });
2174
- });
2175
- return unsubscribe;
2176
- }, [eventContext, threadContext.currentThreadId, getSession, getUserState]);
2177
- const ensureBackendThread = useCallback7(
2178
- async (threadId) => {
2179
- if (remoteThreadIdsRef.current.has(threadId)) return;
2180
- const userState = getUserState();
2181
- await aomiClientRef.current.createThread(
2182
- threadId,
2183
- UserState3.isConnected(userState) ? UserState3.address(userState) : void 0
2184
- );
2185
- remoteThreadIdsRef.current.add(threadId);
2186
- warmedThreadIdsRef.current.add(threadId);
2187
- },
2188
- [aomiClientRef, getUserState]
2189
- );
2190
- useEffect4(() => {
2191
- const threadId = threadContext.currentThreadId;
2192
- closeIdleSessionsExcept(threadId);
2193
- if (!remoteThreadIdsRef.current.has(threadId)) {
2194
- setIsThreadLoading(false);
2195
- return;
2196
- }
2197
- let cancelled = false;
2198
- setIsThreadLoading(true);
2199
- void (async () => {
2200
- try {
2201
- await warmThread(threadId);
2202
- if (!cancelled) {
2203
- await ensureInitialState(threadId);
2204
- }
2205
- } finally {
2206
- if (!cancelled) {
2207
- setIsThreadLoading(false);
2208
- }
2209
- }
2210
- })();
2211
- return () => {
2212
- cancelled = true;
2213
- };
2214
- }, [
2215
- closeIdleSessionsExcept,
2216
- ensureInitialState,
2217
- threadContext.currentThreadId,
2218
- warmThread
2219
- ]);
2220
- useEffect4(() => {
2221
- const threadId = threadContext.currentThreadId;
2222
- const currentMeta = threadContext.getThreadMetadata(threadId);
2223
- if (currentMeta && currentMeta.control.isProcessing !== isRunning) {
2224
- threadContext.updateThreadMetadata(threadId, {
2225
- control: __spreadProps(__spreadValues({}, currentMeta.control), {
2226
- isProcessing: isRunning
2227
- })
2228
- });
2229
- }
2230
- }, [isRunning, threadContext]);
2231
- const currentMessages = threadContext.getThreadMessages(
2232
- threadContext.currentThreadId
2139
+ [ensureInitialState, remoteThreadIdsRef, threadContextRef, warmThread]
2233
2140
  );
2234
- useEffect4(() => {
2235
- var _a;
2236
- const userAddress = UserState3.isConnected(user) ? UserState3.address(user) : void 0;
2141
+ useEffect3(() => {
2142
+ var _a, _b;
2143
+ const userAddress = UserStateHelpers.isConnected(user) ? UserStateHelpers.address(user) : void 0;
2144
+ const normalizedUserAddress = userAddress == null ? void 0 : userAddress.toLowerCase();
2145
+ const previousAddress = lastConnectedAddressRef.current;
2146
+ const walletChanged = previousAddress !== void 0 && normalizedUserAddress !== void 0 && previousAddress !== normalizedUserAddress;
2237
2147
  if (!userAddress) {
2148
+ lastConnectedAddressRef.current = void 0;
2238
2149
  const hadRemoteThreads = remoteThreadIdsRef.current.size > 0;
2239
2150
  const hadSessions = sessionManager.size > 0;
2240
2151
  setIsThreadListLoading(false);
@@ -2249,23 +2160,46 @@ function AomiRuntimeCore({
2249
2160
  }
2250
2161
  return;
2251
2162
  }
2163
+ lastConnectedAddressRef.current = normalizedUserAddress;
2164
+ const resetThreadId = walletChanged ? threadContextRef.current.resetToDefault() : void 0;
2165
+ if (walletChanged) {
2166
+ (_b = prefetchCancelRef.current) == null ? void 0 : _b.call(prefetchCancelRef);
2167
+ prefetchCancelRef.current = null;
2168
+ remoteThreadIdsRef.current.clear();
2169
+ warmedThreadIdsRef.current.clear();
2170
+ warmPromisesRef.current.clear();
2171
+ closeAllSessions();
2172
+ }
2252
2173
  let cancelled = false;
2253
2174
  setIsThreadListLoading(true);
2254
2175
  const fetchThreadList = async () => {
2255
- var _a2, _b, _c;
2176
+ var _a2, _b2, _c;
2256
2177
  try {
2257
2178
  const remoteThreadIdsAtFetchStart = new Set(remoteThreadIdsRef.current);
2258
- const threadList = await aomiClientRef.current.listThreads(userAddress);
2259
- if (cancelled) return;
2260
2179
  const currentContext = threadContextRef.current;
2180
+ const controlSessionId = getControlSessionId(
2181
+ getControlState().clientId,
2182
+ resetThreadId != null ? resetThreadId : currentContext.currentThreadId
2183
+ );
2184
+ const threadList = await aomiClientRef.current.listThreads(
2185
+ controlSessionId,
2186
+ userAddress
2187
+ );
2188
+ if (cancelled) return;
2261
2189
  const remoteThreadIds = /* @__PURE__ */ new Set();
2262
- const newMetadata = new Map(currentContext.allThreadsMetadata);
2263
- let maxChatNum = currentContext.threadCnt;
2190
+ const newMetadata = resetThreadId !== void 0 ? new Map(
2191
+ (() => {
2192
+ const resetMetadata = threadContextRef.current.getThreadMetadata(resetThreadId);
2193
+ return resetMetadata ? [[resetThreadId, resetMetadata]] : [];
2194
+ })()
2195
+ ) : new Map(currentContext.allThreadsMetadata);
2196
+ const baseThreadCount = resetThreadId !== void 0 ? 1 : currentContext.threadCnt;
2197
+ let maxChatNum = baseThreadCount;
2264
2198
  for (const thread of threadList) {
2265
2199
  remoteThreadIds.add(thread.session_id);
2266
2200
  const rawTitle = (_a2 = thread.title) != null ? _a2 : "";
2267
2201
  const title = isPlaceholderTitle(rawTitle) ? "" : rawTitle;
2268
- const lastActive = ((_b = newMetadata.get(thread.session_id)) == null ? void 0 : _b.lastActiveAt) || (/* @__PURE__ */ new Date()).toISOString();
2202
+ const lastActive = ((_b2 = newMetadata.get(thread.session_id)) == null ? void 0 : _b2.lastActiveAt) || (/* @__PURE__ */ new Date()).toISOString();
2269
2203
  const existingControl = (_c = newMetadata.get(thread.session_id)) == null ? void 0 : _c.control;
2270
2204
  newMetadata.set(thread.session_id, {
2271
2205
  title,
@@ -2293,16 +2227,17 @@ function AomiRuntimeCore({
2293
2227
  )
2294
2228
  );
2295
2229
  currentContext.setThreadMetadata(newMetadata);
2296
- if (maxChatNum > currentContext.threadCnt) {
2230
+ if (maxChatNum > baseThreadCount) {
2297
2231
  currentContext.setThreadCnt(maxChatNum);
2298
2232
  }
2299
2233
  scheduleThreadPrefetch(threadList.map((thread) => thread.session_id));
2300
- if (remoteThreadIds.has(currentContext.currentThreadId)) {
2234
+ const activeThreadId = threadContextRef.current.currentThreadId;
2235
+ if (remoteThreadIds.has(activeThreadId)) {
2301
2236
  setIsThreadLoading(true);
2302
2237
  try {
2303
- await warmThread(currentContext.currentThreadId);
2238
+ await warmThread(activeThreadId);
2304
2239
  if (!cancelled) {
2305
- await ensureInitialState(currentContext.currentThreadId);
2240
+ await ensureInitialState(activeThreadId);
2306
2241
  }
2307
2242
  } finally {
2308
2243
  if (!cancelled) {
@@ -2326,13 +2261,249 @@ function AomiRuntimeCore({
2326
2261
  prefetchCancelRef.current = null;
2327
2262
  };
2328
2263
  }, [
2329
- user,
2330
2264
  aomiClientRef,
2265
+ closeAllSessions,
2331
2266
  ensureInitialState,
2267
+ getControlState,
2268
+ remoteThreadIdsRef,
2332
2269
  scheduleThreadPrefetch,
2270
+ sessionManager,
2271
+ setIsThreadLoading,
2272
+ threadContextRef,
2273
+ user,
2274
+ warmPromisesRef,
2275
+ warmedThreadIdsRef,
2276
+ warmThread
2277
+ ]);
2278
+ return { isThreadListLoading };
2279
+ }
2280
+ function useRuntimeUserStateEffects({
2281
+ sessions: {
2282
+ aomiClientRef,
2283
+ sessionManager,
2284
+ getSession,
2285
+ closeAllSessions,
2286
+ ensureInitialState,
2287
+ setIsThreadLoading
2288
+ },
2289
+ remoteThreads
2290
+ }) {
2291
+ const threadContext = useThreadContext();
2292
+ const { user, getUserState, onUserStateChange } = useUser();
2293
+ const { getControlState } = useControl();
2294
+ const threadContextRef = useRef7(threadContext);
2295
+ threadContextRef.current = threadContext;
2296
+ const context = {
2297
+ getControlState,
2298
+ getUserState,
2299
+ onUserStateChange,
2300
+ threadContextRef,
2301
+ user
2302
+ };
2303
+ const sessions = {
2304
+ aomiClientRef,
2305
+ sessionManager,
2306
+ getSession,
2307
+ closeAllSessions,
2308
+ ensureInitialState,
2309
+ setIsThreadLoading
2310
+ };
2311
+ useWalletStateSync(context, sessions, remoteThreads);
2312
+ useUserStateRequestResponder(context, sessions);
2313
+ return useRemoteThreadListSync(context, sessions, remoteThreads);
2314
+ }
2315
+ function RuntimeUserStateProvider({
2316
+ children,
2317
+ sessionManager,
2318
+ getUserState,
2319
+ onUserStateChange
2320
+ }) {
2321
+ const lastSerializedStateRef = useRef7("");
2322
+ useEffect3(() => {
2323
+ const applyToSessions = (next) => {
2324
+ const serialized = stableStateString(next);
2325
+ if (serialized === lastSerializedStateRef.current) {
2326
+ return;
2327
+ }
2328
+ lastSerializedStateRef.current = serialized;
2329
+ sessionManager.forEach((session) => {
2330
+ session.resolveUserState(next);
2331
+ });
2332
+ };
2333
+ applyToSessions(getUserState());
2334
+ const unsubscribe = onUserStateChange((next) => {
2335
+ applyToSessions(next);
2336
+ });
2337
+ return unsubscribe;
2338
+ }, [getUserState, onUserStateChange, sessionManager]);
2339
+ return /* @__PURE__ */ jsx6(Fragment, { children });
2340
+ }
2341
+
2342
+ // packages/react/src/runtime/core.tsx
2343
+ import { jsx as jsx7 } from "react/jsx-runtime";
2344
+ function AomiRuntimeCore({
2345
+ children,
2346
+ aomiClient
2347
+ }) {
2348
+ const threadContext = useThreadContext();
2349
+ const eventContext = useEventContext();
2350
+ const notificationContext = useNotification();
2351
+ const { getUserState } = useUser();
2352
+ const {
2353
+ getControlState,
2354
+ getCurrentThreadApp,
2355
+ getPreferredThreadControl,
2356
+ syncCurrentThreadControl
2357
+ } = useControl();
2358
+ const sessionManagerRef = useRef8(null);
2359
+ const walletHandler = useWalletHandler({
2360
+ getSession: () => {
2361
+ var _a;
2362
+ return (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadContext.currentThreadId);
2363
+ }
2364
+ });
2365
+ const {
2366
+ sessionManager,
2367
+ getSession,
2368
+ isRunning,
2369
+ setIsRunning,
2370
+ ensureInitialState,
2371
+ sendMessage: orchestratorSendMessage,
2372
+ cancelGeneration: orchestratorCancel,
2373
+ closeSession,
2374
+ closeIdleSessionsExcept,
2375
+ closeAllSessions,
2376
+ aomiClientRef
2377
+ } = useRuntimeOrchestrator(aomiClient, {
2378
+ getPublicKey: () => UserState3.isConnected(getUserState()) ? UserState3.address(getUserState()) : void 0,
2379
+ getUserState,
2380
+ getApp: getCurrentThreadApp,
2381
+ getApiKey: () => getControlState().apiKey,
2382
+ getClientId: () => {
2383
+ var _a;
2384
+ return (_a = getControlState().clientId) != null ? _a : void 0;
2385
+ },
2386
+ prepareThreadForSend: async (threadId) => {
2387
+ await ensureBackendThread(threadId);
2388
+ await syncCurrentThreadControl();
2389
+ },
2390
+ onPendingRequestsChange: walletHandler.setRequests,
2391
+ onEvent: (event) => eventContext.dispatch(event)
2392
+ });
2393
+ sessionManagerRef.current = sessionManager;
2394
+ const threadContextRef = useRef8(threadContext);
2395
+ threadContextRef.current = threadContext;
2396
+ const remoteThreadIdsRef = useRef8(/* @__PURE__ */ new Set());
2397
+ const warmedThreadIdsRef = useRef8(/* @__PURE__ */ new Set());
2398
+ const warmPromisesRef = useRef8(/* @__PURE__ */ new Map());
2399
+ const [isThreadLoading, setIsThreadLoading] = useState7(false);
2400
+ const warmThread = useCallback8(
2401
+ async (threadId) => {
2402
+ if (!remoteThreadIdsRef.current.has(threadId) || warmedThreadIdsRef.current.has(threadId)) {
2403
+ return;
2404
+ }
2405
+ const existingPromise = warmPromisesRef.current.get(threadId);
2406
+ if (existingPromise) {
2407
+ return existingPromise;
2408
+ }
2409
+ const warmPromise = (async () => {
2410
+ const userState = getUserState();
2411
+ await aomiClientRef.current.createThread(
2412
+ threadId,
2413
+ UserState3.isConnected(userState) ? UserState3.address(userState) : void 0
2414
+ );
2415
+ warmedThreadIdsRef.current.add(threadId);
2416
+ })();
2417
+ warmPromisesRef.current.set(threadId, warmPromise);
2418
+ try {
2419
+ await warmPromise;
2420
+ } finally {
2421
+ warmPromisesRef.current.delete(threadId);
2422
+ }
2423
+ },
2424
+ [aomiClientRef, getUserState]
2425
+ );
2426
+ const ensureBackendThread = useCallback8(
2427
+ async (threadId) => {
2428
+ if (remoteThreadIdsRef.current.has(threadId)) return;
2429
+ const userState = getUserState();
2430
+ await aomiClientRef.current.createThread(
2431
+ threadId,
2432
+ UserState3.isConnected(userState) ? UserState3.address(userState) : void 0
2433
+ );
2434
+ remoteThreadIdsRef.current.add(threadId);
2435
+ warmedThreadIdsRef.current.add(threadId);
2436
+ },
2437
+ [aomiClientRef, getUserState]
2438
+ );
2439
+ const getRuntimeSession = useCallback8(
2440
+ (threadId) => {
2441
+ var _a, _b;
2442
+ return (_b = (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadId)) != null ? _b : getSession(threadId);
2443
+ },
2444
+ [getSession]
2445
+ );
2446
+ const { isThreadListLoading } = useRuntimeUserStateEffects({
2447
+ sessions: {
2448
+ aomiClientRef,
2449
+ sessionManager,
2450
+ getSession: getRuntimeSession,
2451
+ closeAllSessions,
2452
+ ensureInitialState,
2453
+ setIsThreadLoading
2454
+ },
2455
+ remoteThreads: {
2456
+ remoteThreadIdsRef,
2457
+ warmPromisesRef,
2458
+ warmedThreadIdsRef,
2459
+ warmThread
2460
+ }
2461
+ });
2462
+ useEffect4(() => {
2463
+ const threadId = threadContext.currentThreadId;
2464
+ closeIdleSessionsExcept(threadId);
2465
+ if (!remoteThreadIdsRef.current.has(threadId)) {
2466
+ setIsThreadLoading(false);
2467
+ return;
2468
+ }
2469
+ let cancelled = false;
2470
+ setIsThreadLoading(true);
2471
+ void (async () => {
2472
+ try {
2473
+ await warmThread(threadId);
2474
+ if (!cancelled) {
2475
+ await ensureInitialState(threadId);
2476
+ }
2477
+ } finally {
2478
+ if (!cancelled) {
2479
+ setIsThreadLoading(false);
2480
+ }
2481
+ }
2482
+ })();
2483
+ return () => {
2484
+ cancelled = true;
2485
+ };
2486
+ }, [
2487
+ closeIdleSessionsExcept,
2488
+ ensureInitialState,
2489
+ threadContext.currentThreadId,
2333
2490
  warmThread
2334
2491
  ]);
2335
- const isRemoteThread = useCallback7(
2492
+ useEffect4(() => {
2493
+ const threadId = threadContext.currentThreadId;
2494
+ const currentMeta = threadContext.getThreadMetadata(threadId);
2495
+ if (currentMeta && currentMeta.control.isProcessing !== isRunning) {
2496
+ threadContext.updateThreadMetadata(threadId, {
2497
+ control: __spreadProps(__spreadValues({}, currentMeta.control), {
2498
+ isProcessing: isRunning
2499
+ })
2500
+ });
2501
+ }
2502
+ }, [isRunning, threadContext]);
2503
+ const currentMessages = threadContext.getThreadMessages(
2504
+ threadContext.currentThreadId
2505
+ );
2506
+ const isRemoteThread = useCallback8(
2336
2507
  (threadId) => remoteThreadIdsRef.current.has(threadId),
2337
2508
  []
2338
2509
  );
@@ -2408,52 +2579,50 @@ function AomiRuntimeCore({
2408
2579
  });
2409
2580
  useEffect4(() => {
2410
2581
  return () => {
2411
- var _a;
2412
- (_a = prefetchCancelRef.current) == null ? void 0 : _a.call(prefetchCancelRef);
2413
2582
  closeAllSessions();
2414
2583
  };
2415
2584
  }, [closeAllSessions]);
2416
2585
  const userContext = useUser();
2417
- const sendMessage = useCallback7(
2586
+ const sendMessage = useCallback8(
2418
2587
  async (text) => {
2419
2588
  await orchestratorSendMessage(text, threadContext.currentThreadId);
2420
2589
  },
2421
2590
  [orchestratorSendMessage, threadContext.currentThreadId]
2422
2591
  );
2423
- const cancelGeneration = useCallback7(() => {
2592
+ const cancelGeneration = useCallback8(() => {
2424
2593
  void orchestratorCancel(threadContext.currentThreadId);
2425
2594
  }, [orchestratorCancel, threadContext.currentThreadId]);
2426
- const getMessages = useCallback7(
2595
+ const getMessages = useCallback8(
2427
2596
  (threadId) => {
2428
2597
  const id = threadId != null ? threadId : threadContext.currentThreadId;
2429
2598
  return threadContext.getThreadMessages(id);
2430
2599
  },
2431
2600
  [threadContext]
2432
2601
  );
2433
- const createThread = useCallback7(async () => {
2602
+ const createThread = useCallback8(async () => {
2434
2603
  await threadListAdapter.onSwitchToNewThread();
2435
2604
  return threadContextRef.current.currentThreadId;
2436
2605
  }, [threadListAdapter]);
2437
- const deleteThread = useCallback7(
2606
+ const deleteThread = useCallback8(
2438
2607
  async (threadId) => {
2439
2608
  closeSession(threadId);
2440
2609
  await threadListAdapter.onDelete(threadId);
2441
2610
  },
2442
2611
  [closeSession, threadListAdapter]
2443
2612
  );
2444
- const renameThread = useCallback7(
2613
+ const renameThread = useCallback8(
2445
2614
  async (threadId, title) => {
2446
2615
  await threadListAdapter.onRename(threadId, title);
2447
2616
  },
2448
2617
  [threadListAdapter]
2449
2618
  );
2450
- const archiveThread = useCallback7(
2619
+ const archiveThread = useCallback8(
2451
2620
  async (threadId) => {
2452
2621
  await threadListAdapter.onArchive(threadId);
2453
2622
  },
2454
2623
  [threadListAdapter]
2455
2624
  );
2456
- const selectThread = useCallback7(
2625
+ const selectThread = useCallback8(
2457
2626
  (threadId) => {
2458
2627
  if (threadContext.allThreadsMetadata.has(threadId)) {
2459
2628
  threadListAdapter.onSwitchToThread(threadId);
@@ -2463,7 +2632,7 @@ function AomiRuntimeCore({
2463
2632
  },
2464
2633
  [threadContext.allThreadsMetadata, threadListAdapter]
2465
2634
  );
2466
- const simulateBatchTransactions = useCallback7(
2635
+ const simulateBatchTransactions = useCallback8(
2467
2636
  async (transactions, options) => {
2468
2637
  var _a, _b;
2469
2638
  const session = (_b = (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadContext.currentThreadId)) != null ? _b : getSession(threadContext.currentThreadId);
@@ -2592,7 +2761,7 @@ function AomiRuntimeInner({
2592
2761
  }
2593
2762
 
2594
2763
  // packages/react/src/handlers/notification-handler.ts
2595
- import { useCallback as useCallback8, useEffect as useEffect5, useState as useState7 } from "react";
2764
+ import { useCallback as useCallback9, useEffect as useEffect5, useState as useState8 } from "react";
2596
2765
  var notificationIdCounter2 = 0;
2597
2766
  function generateNotificationId() {
2598
2767
  return `notif-${Date.now()}-${++notificationIdCounter2}`;
@@ -2601,7 +2770,7 @@ function useNotificationHandler({
2601
2770
  onNotification
2602
2771
  } = {}) {
2603
2772
  const { subscribe } = useEventContext();
2604
- const [notifications, setNotifications] = useState7([]);
2773
+ const [notifications, setNotifications] = useState8([]);
2605
2774
  useEffect5(() => {
2606
2775
  const unsubscribe = subscribe("notification", (event) => {
2607
2776
  var _a, _b;
@@ -2621,7 +2790,7 @@ function useNotificationHandler({
2621
2790
  return unsubscribe;
2622
2791
  }, [subscribe, onNotification]);
2623
2792
  const unhandledCount = notifications.filter((n) => !n.handled).length;
2624
- const markHandled = useCallback8((id) => {
2793
+ const markHandled = useCallback9((id) => {
2625
2794
  setNotifications(
2626
2795
  (prev) => prev.map((n) => n.id === id ? __spreadProps(__spreadValues({}, n), { handled: true }) : n)
2627
2796
  );