@aomi-labs/react 0.3.5 → 0.3.7

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
@@ -17,6 +17,19 @@ var __spreadValues = (a, b) => {
17
17
  return a;
18
18
  };
19
19
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __restKey = (key) => typeof key === "symbol" ? key : key + "";
21
+ var __objRest = (source, exclude) => {
22
+ var target = {};
23
+ for (var prop in source)
24
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
25
+ target[prop] = source[prop];
26
+ if (source != null && __getOwnPropSymbols)
27
+ for (var prop of __getOwnPropSymbols(source)) {
28
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
29
+ target[prop] = source[prop];
30
+ }
31
+ return target;
32
+ };
20
33
 
21
34
  // packages/react/src/index.ts
22
35
  import { AomiClient as AomiClient2 } from "@aomi-labs/client";
@@ -210,6 +223,25 @@ var ThreadStore = class {
210
223
  // packages/react/src/contexts/control-context.tsx
211
224
  import { jsx } from "react/jsx-runtime";
212
225
  var API_KEY_STORAGE_KEY = "aomi_api_key";
226
+ var CLIENT_ID_STORAGE_KEY = "aomi_client_id";
227
+ var PROVIDER_KEYS_STORAGE_KEY = "aomi_provider_keys";
228
+ var PROVIDER_KEY_SECRET_PREFIX = "PROVIDER_KEY:";
229
+ function getOrCreateClientId() {
230
+ var _a, _b, _c, _d, _e;
231
+ try {
232
+ const storedClientId = (_a = globalThis.localStorage) == null ? void 0 : _a.getItem(CLIENT_ID_STORAGE_KEY);
233
+ if (storedClientId && storedClientId.trim().length > 0) {
234
+ return storedClientId;
235
+ }
236
+ } catch (e) {
237
+ }
238
+ const clientId = (_d = (_c = (_b = globalThis.crypto) == null ? void 0 : _b.randomUUID) == null ? void 0 : _c.call(_b)) != null ? _d : `client-${Date.now()}`;
239
+ try {
240
+ (_e = globalThis.localStorage) == null ? void 0 : _e.setItem(CLIENT_ID_STORAGE_KEY, clientId);
241
+ } catch (e) {
242
+ }
243
+ return clientId;
244
+ }
213
245
  function getDefaultApp(apps) {
214
246
  var _a;
215
247
  return apps.includes("default") ? "default" : (_a = apps[0]) != null ? _a : null;
@@ -239,11 +271,12 @@ function ControlContextProvider({
239
271
  var _a, _b;
240
272
  const [state, setStateInternal] = useState(() => ({
241
273
  apiKey: null,
242
- clientId: null,
274
+ clientId: getOrCreateClientId(),
243
275
  availableModels: [],
244
276
  authorizedApps: [],
245
277
  defaultModel: null,
246
- defaultApp: null
278
+ defaultApp: null,
279
+ providerKeys: {}
247
280
  }));
248
281
  const stateRef = useRef(state);
249
282
  stateRef.current = state;
@@ -261,10 +294,14 @@ function ControlContextProvider({
261
294
  const currentThreadMetadata = getThreadMetadata(sessionId);
262
295
  const isProcessing = (_b = (_a = currentThreadMetadata == null ? void 0 : currentThreadMetadata.control) == null ? void 0 : _a.isProcessing) != null ? _b : false;
263
296
  useEffect(() => {
264
- var _a2, _b2, _c;
265
- const clientId = (_c = (_b2 = (_a2 = globalThis.crypto) == null ? void 0 : _a2.randomUUID) == null ? void 0 : _b2.call(_a2)) != null ? _c : `client-${Date.now()}`;
266
- setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), { clientId }));
267
- }, []);
297
+ var _a2;
298
+ try {
299
+ if (state.clientId) {
300
+ (_a2 = globalThis.localStorage) == null ? void 0 : _a2.setItem(CLIENT_ID_STORAGE_KEY, state.clientId);
301
+ }
302
+ } catch (e) {
303
+ }
304
+ }, [state.clientId]);
268
305
  useEffect(() => {
269
306
  var _a2, _b2;
270
307
  try {
@@ -275,6 +312,17 @@ function ControlContextProvider({
275
312
  } catch (e) {
276
313
  }
277
314
  }, []);
315
+ useEffect(() => {
316
+ var _a2;
317
+ try {
318
+ const raw = (_a2 = globalThis.localStorage) == null ? void 0 : _a2.getItem(PROVIDER_KEYS_STORAGE_KEY);
319
+ if (raw) {
320
+ const parsed = JSON.parse(raw);
321
+ setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), { providerKeys: parsed }));
322
+ }
323
+ } catch (e) {
324
+ }
325
+ }, []);
278
326
  useEffect(() => {
279
327
  var _a2, _b2;
280
328
  try {
@@ -286,6 +334,33 @@ function ControlContextProvider({
286
334
  } catch (e) {
287
335
  }
288
336
  }, [state.apiKey]);
337
+ useEffect(() => {
338
+ var _a2, _b2;
339
+ try {
340
+ const keys = state.providerKeys;
341
+ if (Object.keys(keys).length > 0) {
342
+ (_a2 = globalThis.localStorage) == null ? void 0 : _a2.setItem(
343
+ PROVIDER_KEYS_STORAGE_KEY,
344
+ JSON.stringify(keys)
345
+ );
346
+ } else {
347
+ (_b2 = globalThis.localStorage) == null ? void 0 : _b2.removeItem(PROVIDER_KEYS_STORAGE_KEY);
348
+ }
349
+ } catch (e) {
350
+ }
351
+ }, [state.providerKeys]);
352
+ useEffect(() => {
353
+ if (!state.clientId) return;
354
+ const keys = stateRef.current.providerKeys;
355
+ if (Object.keys(keys).length === 0) return;
356
+ const secrets = {};
357
+ for (const [provider, entry] of Object.entries(keys)) {
358
+ secrets[`${PROVIDER_KEY_SECRET_PREFIX}${provider}`] = entry.apiKey;
359
+ }
360
+ void aomiClientRef.current.ingestSecrets(state.clientId, secrets).catch((err) => {
361
+ console.error("Failed to auto-ingest provider keys:", err);
362
+ });
363
+ }, [state.clientId, state.providerKeys]);
289
364
  useEffect(() => {
290
365
  const fetchApps = async () => {
291
366
  var _a2;
@@ -356,6 +431,65 @@ function ControlContextProvider({
356
431
  if (!clientId) return;
357
432
  await ((_b2 = (_a2 = aomiClientRef.current).clearSecrets) == null ? void 0 : _b2.call(_a2, clientId));
358
433
  }, []);
434
+ const setProviderKey = useCallback(
435
+ async (provider, apiKey, label) => {
436
+ const trimmed = apiKey.trim();
437
+ if (!trimmed) return;
438
+ const entry = {
439
+ apiKey: trimmed,
440
+ keyPrefix: trimmed.slice(0, 7),
441
+ label
442
+ };
443
+ setStateInternal((prev) => {
444
+ const next = __spreadProps(__spreadValues({}, prev), {
445
+ providerKeys: __spreadProps(__spreadValues({}, prev.providerKeys), { [provider]: entry })
446
+ });
447
+ callbacks.current.forEach((cb) => cb(next));
448
+ return next;
449
+ });
450
+ const clientId = stateRef.current.clientId;
451
+ if (clientId) {
452
+ try {
453
+ await aomiClientRef.current.ingestSecrets(clientId, {
454
+ [`${PROVIDER_KEY_SECRET_PREFIX}${provider}`]: trimmed
455
+ });
456
+ } catch (err) {
457
+ console.error("Failed to ingest provider key:", err);
458
+ }
459
+ }
460
+ },
461
+ []
462
+ );
463
+ const removeProviderKey = useCallback(
464
+ async (provider) => {
465
+ const clientId = stateRef.current.clientId;
466
+ if (clientId) {
467
+ await aomiClientRef.current.deleteSecret(
468
+ clientId,
469
+ `${PROVIDER_KEY_SECRET_PREFIX}${provider}`
470
+ );
471
+ }
472
+ setStateInternal((prev) => {
473
+ const _a2 = prev.providerKeys, { [provider]: _ } = _a2, rest = __objRest(_a2, [__restKey(provider)]);
474
+ const next = __spreadProps(__spreadValues({}, prev), { providerKeys: rest });
475
+ callbacks.current.forEach((cb) => cb(next));
476
+ return next;
477
+ });
478
+ },
479
+ []
480
+ );
481
+ const getProviderKeys = useCallback(
482
+ () => stateRef.current.providerKeys,
483
+ []
484
+ );
485
+ const hasProviderKey = useCallback(
486
+ (provider) => {
487
+ const keys = stateRef.current.providerKeys;
488
+ if (provider) return provider in keys;
489
+ return Object.keys(keys).length > 0;
490
+ },
491
+ []
492
+ );
359
493
  const getAvailableModels = useCallback(async () => {
360
494
  try {
361
495
  const models = await aomiClientRef.current.getModels(
@@ -414,7 +548,7 @@ function ControlContextProvider({
414
548
  )) != null ? _c : "default";
415
549
  }, []);
416
550
  const onModelSelect = useCallback(async (model) => {
417
- var _a2, _b2, _c, _d;
551
+ var _a2, _b2, _c, _d, _e;
418
552
  const threadId = sessionIdRef.current;
419
553
  const currentControl = (_b2 = (_a2 = getThreadMetadataRef.current(threadId)) == null ? void 0 : _a2.control) != null ? _b2 : initThreadControl();
420
554
  const isProcessing2 = currentControl.isProcessing;
@@ -455,7 +589,11 @@ function ControlContextProvider({
455
589
  const result = await aomiClientRef.current.setModel(
456
590
  threadId,
457
591
  model,
458
- { app, apiKey: (_d = stateRef.current.apiKey) != null ? _d : void 0 }
592
+ {
593
+ app,
594
+ apiKey: (_d = stateRef.current.apiKey) != null ? _d : void 0,
595
+ clientId: (_e = stateRef.current.clientId) != null ? _e : void 0
596
+ }
459
597
  );
460
598
  console.log("[control-context] onModelSelect backend result", result);
461
599
  } catch (err) {
@@ -538,6 +676,10 @@ function ControlContextProvider({
538
676
  setApiKey,
539
677
  ingestSecrets,
540
678
  clearSecrets,
679
+ setProviderKey,
680
+ removeProviderKey,
681
+ getProviderKeys,
682
+ hasProviderKey,
541
683
  getAvailableModels,
542
684
  getAuthorizedApps,
543
685
  getCurrentThreadControl,
@@ -560,62 +702,8 @@ import {
560
702
  createContext as createContext2,
561
703
  useCallback as useCallback2,
562
704
  useContext as useContext2,
563
- useEffect as useEffect2,
564
- useRef as useRef2,
565
- useState as useState2
705
+ useRef as useRef2
566
706
  } from "react";
567
- import {
568
- isInlineCall,
569
- isSystemNotice,
570
- isSystemError,
571
- isAsyncCallback
572
- } from "@aomi-labs/client";
573
-
574
- // packages/react/src/state/event-buffer.ts
575
- function createEventBuffer() {
576
- return {
577
- inboundQueue: [],
578
- outboundQueue: [],
579
- sseStatus: "disconnected",
580
- lastEventId: null,
581
- subscribers: /* @__PURE__ */ new Map()
582
- };
583
- }
584
- function enqueueInbound(state, event) {
585
- state.inboundQueue.push(__spreadProps(__spreadValues({}, event), {
586
- status: "pending",
587
- timestamp: Date.now()
588
- }));
589
- }
590
- function subscribe(state, type, callback) {
591
- if (!state.subscribers.has(type)) {
592
- state.subscribers.set(type, /* @__PURE__ */ new Set());
593
- }
594
- state.subscribers.get(type).add(callback);
595
- return () => {
596
- var _a;
597
- (_a = state.subscribers.get(type)) == null ? void 0 : _a.delete(callback);
598
- };
599
- }
600
- function dispatch(state, event) {
601
- const typeSubscribers = state.subscribers.get(event.type);
602
- if (typeSubscribers) {
603
- for (const callback of typeSubscribers) {
604
- callback(event);
605
- }
606
- }
607
- const allSubscribers = state.subscribers.get("*");
608
- if (allSubscribers) {
609
- for (const callback of allSubscribers) {
610
- callback(event);
611
- }
612
- }
613
- }
614
- function setSSEStatus(state, status) {
615
- state.sseStatus = status;
616
- }
617
-
618
- // packages/react/src/contexts/event-context.tsx
619
707
  import { jsx as jsx2 } from "react/jsx-runtime";
620
708
  var EventContextState = createContext2(null);
621
709
  function useEventContext() {
@@ -632,52 +720,32 @@ function EventContextProvider({
632
720
  aomiClient,
633
721
  sessionId
634
722
  }) {
635
- const bufferRef = useRef2(null);
636
- if (!bufferRef.current) {
637
- bufferRef.current = createEventBuffer();
638
- }
639
- const buffer = bufferRef.current;
640
- const [sseStatus, setSseStatus] = useState2("disconnected");
641
- useEffect2(() => {
642
- setSSEStatus(buffer, "connecting");
643
- setSseStatus("connecting");
644
- const unsubscribe = aomiClient.subscribeSSE(
645
- sessionId,
646
- (event) => {
647
- enqueueInbound(buffer, {
648
- type: event.type,
649
- sessionId: event.session_id,
650
- payload: event
651
- });
652
- const inboundEvent = {
653
- type: event.type,
654
- sessionId: event.session_id,
655
- payload: event,
656
- status: "fetched",
657
- timestamp: Date.now()
658
- };
659
- dispatch(buffer, inboundEvent);
660
- },
661
- (error) => {
662
- console.error("SSE error:", error);
663
- setSSEStatus(buffer, "disconnected");
664
- setSseStatus("disconnected");
665
- }
666
- );
667
- setSSEStatus(buffer, "connected");
668
- setSseStatus("connected");
669
- return () => {
670
- unsubscribe();
671
- setSSEStatus(buffer, "disconnected");
672
- setSseStatus("disconnected");
673
- };
674
- }, [aomiClient, sessionId, buffer]);
675
- const subscribeCallback = useCallback2(
723
+ const subscribersRef = useRef2(/* @__PURE__ */ new Map());
724
+ const subscribe = useCallback2(
676
725
  (type, callback) => {
677
- return subscribe(buffer, type, callback);
726
+ const subs = subscribersRef.current;
727
+ if (!subs.has(type)) {
728
+ subs.set(type, /* @__PURE__ */ new Set());
729
+ }
730
+ subs.get(type).add(callback);
731
+ return () => {
732
+ var _a;
733
+ (_a = subs.get(type)) == null ? void 0 : _a.delete(callback);
734
+ };
678
735
  },
679
- [buffer]
736
+ []
680
737
  );
738
+ const dispatchEvent = useCallback2((event) => {
739
+ const subs = subscribersRef.current;
740
+ const typeSubs = subs.get(event.type);
741
+ if (typeSubs) {
742
+ for (const cb of typeSubs) cb(event);
743
+ }
744
+ const wildcardSubs = subs.get("*");
745
+ if (wildcardSubs) {
746
+ for (const cb of wildcardSubs) cb(event);
747
+ }
748
+ }, []);
681
749
  const sendOutbound = useCallback2(
682
750
  async (event) => {
683
751
  try {
@@ -692,50 +760,14 @@ function EventContextProvider({
692
760
  },
693
761
  [aomiClient]
694
762
  );
695
- const dispatchSystemEvents = useCallback2(
696
- (sessionId2, events) => {
697
- var _a;
698
- for (const event of events) {
699
- let eventType;
700
- let payload;
701
- if (isInlineCall(event)) {
702
- eventType = event.InlineCall.type;
703
- payload = (_a = event.InlineCall.payload) != null ? _a : event.InlineCall;
704
- } else if (isSystemNotice(event)) {
705
- eventType = "system_notice";
706
- payload = { message: event.SystemNotice };
707
- } else if (isSystemError(event)) {
708
- eventType = "system_error";
709
- payload = { message: event.SystemError };
710
- } else if (isAsyncCallback(event)) {
711
- eventType = "async_callback";
712
- payload = event.AsyncCallback;
713
- } else {
714
- console.warn("Unknown system event type:", event);
715
- continue;
716
- }
717
- const inboundEvent = {
718
- type: eventType,
719
- sessionId: sessionId2,
720
- payload,
721
- status: "fetched",
722
- timestamp: Date.now()
723
- };
724
- enqueueInbound(buffer, {
725
- type: eventType,
726
- sessionId: sessionId2,
727
- payload
728
- });
729
- dispatch(buffer, inboundEvent);
730
- }
731
- },
732
- [buffer]
733
- );
734
763
  const contextValue = {
735
- subscribe: subscribeCallback,
764
+ subscribe,
765
+ dispatch: dispatchEvent,
736
766
  sendOutboundSystem: sendOutbound,
737
- dispatchInboundSystem: dispatchSystemEvents,
738
- sseStatus
767
+ // SSE is managed by ClientSession now — status is always "connected"
768
+ // when sessions are active. Individual session status can be queried
769
+ // from the session manager if needed.
770
+ sseStatus: "connected"
739
771
  };
740
772
  return /* @__PURE__ */ jsx2(EventContextState.Provider, { value: contextValue, children });
741
773
  }
@@ -745,7 +777,7 @@ import {
745
777
  createContext as createContext3,
746
778
  useCallback as useCallback3,
747
779
  useContext as useContext3,
748
- useState as useState3
780
+ useState as useState2
749
781
  } from "react";
750
782
  import { jsx as jsx3 } from "react/jsx-runtime";
751
783
  var NotificationContext = createContext3(null);
@@ -765,7 +797,7 @@ function generateId() {
765
797
  function NotificationContextProvider({
766
798
  children
767
799
  }) {
768
- const [notifications, setNotifications] = useState3([]);
800
+ const [notifications, setNotifications] = useState2([]);
769
801
  const showNotification = useCallback3((params) => {
770
802
  const id = generateId();
771
803
  const notification = __spreadProps(__spreadValues({}, params), {
@@ -846,7 +878,7 @@ import {
846
878
  useCallback as useCallback4,
847
879
  useContext as useContext5,
848
880
  useRef as useRef4,
849
- useState as useState4
881
+ useState as useState3
850
882
  } from "react";
851
883
  import { jsx as jsx5 } from "react/jsx-runtime";
852
884
  var UserContext = createContext5(void 0);
@@ -865,7 +897,7 @@ function useUser() {
865
897
  };
866
898
  }
867
899
  function UserContextProvider({ children }) {
868
- const [user, setUserState] = useState4({
900
+ const [user, setUserState] = useState3({
869
901
  isConnected: false,
870
902
  address: void 0,
871
903
  chainId: void 0,
@@ -943,14 +975,48 @@ function UserContextProvider({ children }) {
943
975
  }
944
976
 
945
977
  // packages/react/src/runtime/core.tsx
946
- import { useCallback as useCallback7, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef7 } from "react";
978
+ import { useCallback as useCallback7, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef7 } from "react";
947
979
  import {
948
980
  AssistantRuntimeProvider,
949
981
  useExternalStoreRuntime
950
982
  } from "@assistant-ui/react";
951
983
 
952
984
  // packages/react/src/runtime/orchestrator.ts
953
- import { useCallback as useCallback5, useRef as useRef5, useState as useState5 } from "react";
985
+ import { useCallback as useCallback5, useEffect as useEffect2, useRef as useRef5, useState as useState4 } from "react";
986
+
987
+ // packages/react/src/runtime/session-manager.ts
988
+ import { Session as ClientSession } from "@aomi-labs/client";
989
+ var SessionManager = class {
990
+ constructor(clientFactory) {
991
+ this.clientFactory = clientFactory;
992
+ this.sessions = /* @__PURE__ */ new Map();
993
+ }
994
+ getOrCreate(threadId, opts) {
995
+ let session = this.sessions.get(threadId);
996
+ if (session) return session;
997
+ session = new ClientSession(this.clientFactory(), __spreadProps(__spreadValues({}, opts), {
998
+ sessionId: threadId
999
+ }));
1000
+ this.sessions.set(threadId, session);
1001
+ return session;
1002
+ }
1003
+ get(threadId) {
1004
+ return this.sessions.get(threadId);
1005
+ }
1006
+ close(threadId) {
1007
+ const session = this.sessions.get(threadId);
1008
+ if (session) {
1009
+ session.close();
1010
+ this.sessions.delete(threadId);
1011
+ }
1012
+ }
1013
+ closeAll() {
1014
+ for (const [threadId, session] of this.sessions) {
1015
+ session.close();
1016
+ }
1017
+ this.sessions.clear();
1018
+ }
1019
+ };
954
1020
 
955
1021
  // packages/react/src/runtime/utils.ts
956
1022
  import { clsx } from "clsx";
@@ -1054,194 +1120,6 @@ var SUPPORTED_CHAINS = [
1054
1120
  ];
1055
1121
  var getChainInfo = (chainId) => chainId === void 0 ? void 0 : SUPPORTED_CHAINS.find((c) => c.id === chainId);
1056
1122
 
1057
- // packages/react/src/state/backend-state.ts
1058
- function createBackendState() {
1059
- return {
1060
- runningThreads: /* @__PURE__ */ new Set()
1061
- };
1062
- }
1063
- function resolveThreadId(_state, threadId) {
1064
- return threadId;
1065
- }
1066
- function setThreadRunning(state, threadId, running) {
1067
- if (running) {
1068
- state.runningThreads.add(threadId);
1069
- } else {
1070
- state.runningThreads.delete(threadId);
1071
- }
1072
- }
1073
- function isThreadRunning(state, threadId) {
1074
- return state.runningThreads.has(threadId);
1075
- }
1076
-
1077
- // packages/react/src/runtime/message-controller.ts
1078
- var MessageController = class {
1079
- constructor(config) {
1080
- this.config = config;
1081
- }
1082
- inbound(threadId, msgs) {
1083
- if (!msgs) return;
1084
- const threadMessages = [];
1085
- for (const msg of msgs) {
1086
- const threadMessage = toInboundMessage(msg);
1087
- if (threadMessage) {
1088
- threadMessages.push(threadMessage);
1089
- }
1090
- }
1091
- this.getThreadContextApi().setThreadMessages(threadId, threadMessages);
1092
- }
1093
- async outbound(message, threadId) {
1094
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
1095
- const backendState = this.config.backendStateRef.current;
1096
- const text = message.content.filter(
1097
- (part) => part.type === "text"
1098
- ).map(
1099
- (part) => part.text
1100
- ).join("\n");
1101
- if (!text) return;
1102
- const threadState = this.getThreadContextApi();
1103
- const existingMessages = threadState.getThreadMessages(threadId);
1104
- const userMessage = {
1105
- role: "user",
1106
- content: [{ type: "text", text }],
1107
- createdAt: /* @__PURE__ */ new Date()
1108
- };
1109
- threadState.setThreadMessages(threadId, [...existingMessages, userMessage]);
1110
- threadState.updateThreadMetadata(threadId, {
1111
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1112
- });
1113
- const backendThreadId = resolveThreadId(backendState, threadId);
1114
- const app = this.config.getApp();
1115
- const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
1116
- const apiKey = (_e = (_d = (_c = this.config).getApiKey) == null ? void 0 : _d.call(_c)) != null ? _e : void 0;
1117
- const clientId = (_g = (_f = this.config).getClientId) == null ? void 0 : _g.call(_f);
1118
- const userState = (_i = (_h = this.config).getUserState) == null ? void 0 : _i.call(_h);
1119
- try {
1120
- this.markRunning(threadId, true);
1121
- const response = await this.config.aomiClientRef.current.sendMessage(
1122
- backendThreadId,
1123
- text,
1124
- { app, publicKey, apiKey, userState, clientId }
1125
- );
1126
- if (response == null ? void 0 : response.messages) {
1127
- this.inbound(threadId, response.messages);
1128
- }
1129
- if (((_j = response == null ? void 0 : response.system_events) == null ? void 0 : _j.length) && this.config.onSyncEvents) {
1130
- this.config.onSyncEvents(backendThreadId, response.system_events);
1131
- }
1132
- if (response == null ? void 0 : response.is_processing) {
1133
- this.config.polling.start(threadId);
1134
- } else if (!this.config.polling.isPolling(threadId)) {
1135
- this.markRunning(threadId, false);
1136
- }
1137
- } catch (error) {
1138
- console.error("Failed to send message:", error);
1139
- this.markRunning(threadId, false);
1140
- }
1141
- }
1142
- async cancel(threadId) {
1143
- var _a;
1144
- this.config.polling.stop(threadId);
1145
- const backendState = this.config.backendStateRef.current;
1146
- const backendThreadId = resolveThreadId(backendState, threadId);
1147
- try {
1148
- const response = await this.config.aomiClientRef.current.interrupt(backendThreadId);
1149
- if (response == null ? void 0 : response.messages) {
1150
- this.inbound(threadId, response.messages);
1151
- }
1152
- if (((_a = response == null ? void 0 : response.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1153
- this.config.onSyncEvents(backendThreadId, response.system_events);
1154
- }
1155
- this.markRunning(threadId, false);
1156
- } catch (error) {
1157
- console.error("Failed to cancel:", error);
1158
- }
1159
- }
1160
- markRunning(threadId, running) {
1161
- var _a, _b;
1162
- setThreadRunning(this.config.backendStateRef.current, threadId, running);
1163
- if (this.config.threadContextRef.current.currentThreadId === threadId) {
1164
- (_b = (_a = this.config).setGlobalIsRunning) == null ? void 0 : _b.call(_a, running);
1165
- }
1166
- }
1167
- getThreadContextApi() {
1168
- const { getThreadMessages, setThreadMessages, updateThreadMetadata } = this.config.threadContextRef.current;
1169
- return { getThreadMessages, setThreadMessages, updateThreadMetadata };
1170
- }
1171
- };
1172
-
1173
- // packages/react/src/runtime/polling-controller.ts
1174
- var PollingController = class {
1175
- constructor(config) {
1176
- this.config = config;
1177
- this.intervals = /* @__PURE__ */ new Map();
1178
- var _a;
1179
- this.intervalMs = (_a = config.intervalMs) != null ? _a : 500;
1180
- }
1181
- start(threadId) {
1182
- var _a, _b;
1183
- const backendState = this.config.backendStateRef.current;
1184
- if (this.intervals.has(threadId)) return;
1185
- const backendThreadId = resolveThreadId(backendState, threadId);
1186
- setThreadRunning(backendState, threadId, true);
1187
- const tick = async () => {
1188
- var _a2, _b2, _c, _d;
1189
- if (!this.intervals.has(threadId)) return;
1190
- try {
1191
- console.log(
1192
- "[PollingController] Fetching state for threadId:",
1193
- threadId
1194
- );
1195
- const userState = (_b2 = (_a2 = this.config).getUserState) == null ? void 0 : _b2.call(_a2);
1196
- const clientId = (_d = (_c = this.config).getClientId) == null ? void 0 : _d.call(_c);
1197
- const state = await this.config.aomiClientRef.current.fetchState(
1198
- backendThreadId,
1199
- userState,
1200
- clientId
1201
- );
1202
- if (!this.intervals.has(threadId)) return;
1203
- this.handleState(threadId, state);
1204
- } catch (error) {
1205
- console.error("Polling error:", error);
1206
- this.stop(threadId);
1207
- }
1208
- };
1209
- const intervalId = setInterval(tick, this.intervalMs);
1210
- this.intervals.set(threadId, intervalId);
1211
- (_b = (_a = this.config).onStart) == null ? void 0 : _b.call(_a, threadId);
1212
- }
1213
- stop(threadId) {
1214
- var _a, _b;
1215
- const intervalId = this.intervals.get(threadId);
1216
- if (intervalId) {
1217
- clearInterval(intervalId);
1218
- this.intervals.delete(threadId);
1219
- }
1220
- setThreadRunning(this.config.backendStateRef.current, threadId, false);
1221
- (_b = (_a = this.config).onStop) == null ? void 0 : _b.call(_a, threadId);
1222
- }
1223
- isPolling(threadId) {
1224
- return this.intervals.has(threadId);
1225
- }
1226
- stopAll() {
1227
- for (const threadId of this.intervals.keys()) {
1228
- this.stop(threadId);
1229
- }
1230
- }
1231
- handleState(threadId, state) {
1232
- var _a;
1233
- if (((_a = state.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1234
- const backendState = this.config.backendStateRef.current;
1235
- const sessionId = resolveThreadId(backendState, threadId);
1236
- this.config.onSyncEvents(sessionId, state.system_events);
1237
- }
1238
- this.config.applyMessages(threadId, state.messages);
1239
- if (!state.is_processing) {
1240
- this.stop(threadId);
1241
- }
1242
- }
1243
- };
1244
-
1245
1123
  // packages/react/src/runtime/orchestrator.ts
1246
1124
  function useRuntimeOrchestrator(aomiClient, options) {
1247
1125
  const threadContext = useThreadContext();
@@ -1249,90 +1127,160 @@ function useRuntimeOrchestrator(aomiClient, options) {
1249
1127
  threadContextRef.current = threadContext;
1250
1128
  const aomiClientRef = useRef5(aomiClient);
1251
1129
  aomiClientRef.current = aomiClient;
1252
- const backendStateRef = useRef5(createBackendState());
1253
- const [isRunning, setIsRunning] = useState5(false);
1254
- const messageControllerRef = useRef5(null);
1255
- const pollingRef = useRef5(null);
1130
+ const [isRunning, setIsRunning] = useState4(false);
1131
+ const sessionManagerRef = useRef5(null);
1132
+ if (!sessionManagerRef.current) {
1133
+ sessionManagerRef.current = new SessionManager(() => aomiClientRef.current);
1134
+ }
1256
1135
  const pendingFetches = useRef5(/* @__PURE__ */ new Set());
1257
- if (!pollingRef.current) {
1258
- pollingRef.current = new PollingController({
1259
- aomiClientRef,
1260
- backendStateRef,
1261
- applyMessages: (threadId, msgs) => {
1262
- var _a;
1263
- (_a = messageControllerRef.current) == null ? void 0 : _a.inbound(threadId, msgs);
1264
- },
1265
- onSyncEvents: options.onSyncEvents,
1266
- getUserState: options.getUserState,
1267
- getClientId: options.getClientId,
1268
- onStart: (threadId) => {
1136
+ const listenerCleanups = useRef5(/* @__PURE__ */ new Map());
1137
+ const getSession = useCallback5(
1138
+ (threadId) => {
1139
+ var _a, _b, _c, _d, _e;
1140
+ const manager = sessionManagerRef.current;
1141
+ const existing = manager.get(threadId);
1142
+ if (existing) return existing;
1143
+ const session = manager.getOrCreate(threadId, {
1144
+ app: options.getApp(),
1145
+ publicKey: (_a = options.getPublicKey) == null ? void 0 : _a.call(options),
1146
+ apiKey: (_c = (_b = options.getApiKey) == null ? void 0 : _b.call(options)) != null ? _c : void 0,
1147
+ clientId: (_d = options.getClientId) == null ? void 0 : _d.call(options),
1148
+ userState: (_e = options.getUserState) == null ? void 0 : _e.call(options)
1149
+ });
1150
+ const cleanups = [];
1151
+ cleanups.push(
1152
+ session.on("messages", (msgs) => {
1153
+ const threadMessages = [];
1154
+ for (const msg of msgs) {
1155
+ const converted = toInboundMessage(msg);
1156
+ if (converted) threadMessages.push(converted);
1157
+ }
1158
+ threadContextRef.current.setThreadMessages(threadId, threadMessages);
1159
+ })
1160
+ );
1161
+ cleanups.push(
1162
+ session.on("processing_start", () => {
1163
+ if (threadContextRef.current.currentThreadId === threadId) {
1164
+ setIsRunning(true);
1165
+ }
1166
+ })
1167
+ );
1168
+ cleanups.push(
1169
+ session.on("processing_end", () => {
1170
+ if (threadContextRef.current.currentThreadId === threadId) {
1171
+ setIsRunning(false);
1172
+ }
1173
+ })
1174
+ );
1175
+ cleanups.push(
1176
+ session.on("wallet_tx_request", (req) => {
1177
+ var _a2;
1178
+ return (_a2 = options.onWalletRequest) == null ? void 0 : _a2.call(options, req);
1179
+ })
1180
+ );
1181
+ cleanups.push(
1182
+ session.on("wallet_eip712_request", (req) => {
1183
+ var _a2;
1184
+ return (_a2 = options.onWalletRequest) == null ? void 0 : _a2.call(options, req);
1185
+ })
1186
+ );
1187
+ cleanups.push(
1188
+ session.on("title_changed", ({ title }) => {
1189
+ threadContextRef.current.updateThreadMetadata(threadId, { title });
1190
+ })
1191
+ );
1192
+ const forwardEvent = (type) => session.on(type, (payload) => {
1193
+ var _a2;
1194
+ (_a2 = options.onEvent) == null ? void 0 : _a2.call(options, { type, payload, sessionId: threadId });
1195
+ });
1196
+ cleanups.push(forwardEvent("tool_update"));
1197
+ cleanups.push(forwardEvent("tool_complete"));
1198
+ cleanups.push(forwardEvent("system_notice"));
1199
+ cleanups.push(forwardEvent("system_error"));
1200
+ cleanups.push(forwardEvent("async_callback"));
1201
+ listenerCleanups.current.set(threadId, () => {
1202
+ for (const cleanup of cleanups) cleanup();
1203
+ });
1204
+ return session;
1205
+ },
1206
+ // Stable deps — option getters are refs
1207
+ []
1208
+ );
1209
+ const ensureInitialState = useCallback5(
1210
+ async (threadId) => {
1211
+ var _a;
1212
+ if (pendingFetches.current.has(threadId)) return;
1213
+ pendingFetches.current.add(threadId);
1214
+ try {
1215
+ const session = getSession(threadId);
1216
+ const userState = (_a = options.getUserState) == null ? void 0 : _a.call(options);
1217
+ if (userState) session.resolveUserState(userState);
1218
+ await session.fetchCurrentState();
1269
1219
  if (threadContextRef.current.currentThreadId === threadId) {
1270
- setIsRunning(true);
1220
+ setIsRunning(session.getIsProcessing());
1271
1221
  }
1272
- },
1273
- onStop: (threadId) => {
1222
+ } catch (error) {
1223
+ console.error("Failed to fetch initial state:", error);
1274
1224
  if (threadContextRef.current.currentThreadId === threadId) {
1275
1225
  setIsRunning(false);
1276
1226
  }
1227
+ } finally {
1228
+ pendingFetches.current.delete(threadId);
1277
1229
  }
1278
- });
1279
- }
1280
- if (!messageControllerRef.current) {
1281
- messageControllerRef.current = new MessageController({
1282
- aomiClientRef,
1283
- backendStateRef,
1284
- threadContextRef,
1285
- polling: pollingRef.current,
1286
- setGlobalIsRunning: setIsRunning,
1287
- getPublicKey: options.getPublicKey,
1288
- getApp: options.getApp,
1289
- getApiKey: options.getApiKey,
1290
- getClientId: options.getClientId,
1291
- getUserState: options.getUserState,
1292
- onSyncEvents: options.onSyncEvents
1293
- });
1294
- }
1295
- const ensureInitialState = useCallback5(async (threadId) => {
1296
- var _a, _b, _c, _d, _e;
1297
- if (pendingFetches.current.has(threadId)) return;
1298
- const backendThreadId = resolveThreadId(backendStateRef.current, threadId);
1299
- pendingFetches.current.add(threadId);
1300
- try {
1230
+ },
1231
+ [getSession]
1232
+ );
1233
+ const sendMessage = useCallback5(
1234
+ async (text, threadId) => {
1235
+ var _a;
1236
+ const session = getSession(threadId);
1301
1237
  const userState = (_a = options.getUserState) == null ? void 0 : _a.call(options);
1302
- const clientId = (_b = options.getClientId) == null ? void 0 : _b.call(options);
1303
- const state = await aomiClientRef.current.fetchState(
1304
- backendThreadId,
1305
- userState,
1306
- clientId
1307
- );
1308
- (_c = messageControllerRef.current) == null ? void 0 : _c.inbound(threadId, state.messages);
1309
- if (((_d = state.system_events) == null ? void 0 : _d.length) && options.onSyncEvents) {
1310
- options.onSyncEvents(backendThreadId, state.system_events);
1311
- }
1312
- if (threadContextRef.current.currentThreadId === threadId) {
1313
- if (state.is_processing) {
1314
- setIsRunning(true);
1315
- (_e = pollingRef.current) == null ? void 0 : _e.start(threadId);
1316
- } else {
1317
- setIsRunning(false);
1318
- }
1238
+ if (userState) session.resolveUserState(userState);
1239
+ const existingMessages = threadContextRef.current.getThreadMessages(threadId);
1240
+ const userMessage = {
1241
+ role: "user",
1242
+ content: [{ type: "text", text }],
1243
+ createdAt: /* @__PURE__ */ new Date()
1244
+ };
1245
+ threadContextRef.current.setThreadMessages(threadId, [
1246
+ ...existingMessages,
1247
+ userMessage
1248
+ ]);
1249
+ threadContextRef.current.updateThreadMetadata(threadId, {
1250
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1251
+ });
1252
+ await session.sendAsync(text);
1253
+ },
1254
+ [getSession]
1255
+ );
1256
+ const cancelGeneration = useCallback5(
1257
+ async (threadId) => {
1258
+ var _a;
1259
+ const session = (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadId);
1260
+ if (session) {
1261
+ await session.interrupt();
1319
1262
  }
1320
- } catch (error) {
1321
- console.error("Failed to fetch initial state:", error);
1322
- if (threadContextRef.current.currentThreadId === threadId) {
1323
- setIsRunning(false);
1263
+ },
1264
+ []
1265
+ );
1266
+ useEffect2(() => {
1267
+ return () => {
1268
+ var _a;
1269
+ (_a = sessionManagerRef.current) == null ? void 0 : _a.closeAll();
1270
+ for (const cleanup of listenerCleanups.current.values()) {
1271
+ cleanup();
1324
1272
  }
1325
- } finally {
1326
- pendingFetches.current.delete(threadId);
1327
- }
1273
+ listenerCleanups.current.clear();
1274
+ };
1328
1275
  }, []);
1329
1276
  return {
1330
- backendStateRef,
1331
- polling: pollingRef.current,
1332
- messageController: messageControllerRef.current,
1277
+ sessionManager: sessionManagerRef.current,
1278
+ getSession,
1333
1279
  isRunning,
1334
1280
  setIsRunning,
1335
1281
  ensureInitialState,
1282
+ sendMessage,
1283
+ cancelGeneration,
1336
1284
  aomiClientRef
1337
1285
  };
1338
1286
  }
@@ -1484,154 +1432,49 @@ function useAomiRuntime() {
1484
1432
  }
1485
1433
 
1486
1434
  // packages/react/src/handlers/wallet-handler.ts
1487
- import { useCallback as useCallback6, useEffect as useEffect3, useRef as useRef6, useState as useState6 } from "react";
1488
- import {
1489
- normalizeEip712Payload,
1490
- normalizeTxPayload
1491
- } from "@aomi-labs/client";
1492
-
1493
- // packages/react/src/state/wallet-buffer.ts
1494
- function createWalletBuffer() {
1495
- return { queue: [], nextId: 1 };
1496
- }
1497
- function enqueue(buffer, kind, payload) {
1498
- const request = {
1499
- id: `wreq-${buffer.nextId++}`,
1500
- kind,
1501
- payload,
1502
- status: "pending",
1503
- timestamp: Date.now()
1504
- };
1505
- buffer.queue.push(request);
1506
- return request;
1507
- }
1508
- function dequeue(buffer, id) {
1509
- const index = buffer.queue.findIndex((r) => r.id === id);
1510
- if (index === -1) return null;
1511
- return buffer.queue.splice(index, 1)[0];
1512
- }
1513
- function markProcessing(buffer, id) {
1514
- const request = buffer.queue.find((r) => r.id === id);
1515
- if (!request || request.status !== "pending") return false;
1516
- request.status = "processing";
1517
- return true;
1518
- }
1519
- function getAll(buffer) {
1520
- return [...buffer.queue];
1521
- }
1522
-
1523
- // packages/react/src/handlers/wallet-handler.ts
1435
+ import { useCallback as useCallback6, useRef as useRef6, useState as useState5 } from "react";
1524
1436
  function useWalletHandler({
1525
- sessionId,
1526
- onRequestComplete
1437
+ getSession
1527
1438
  }) {
1528
- const { subscribe: subscribe2, sendOutboundSystem: sendOutbound } = useEventContext();
1529
- const bufferRef = useRef6(createWalletBuffer());
1530
- const [pendingRequests, setPendingRequests] = useState6([]);
1531
- const syncState = useCallback6(() => {
1532
- setPendingRequests(getAll(bufferRef.current));
1439
+ const [pendingRequests, setPendingRequests] = useState5([]);
1440
+ const requestsRef = useRef6([]);
1441
+ const enqueueRequest = useCallback6((request) => {
1442
+ requestsRef.current = [...requestsRef.current, request];
1443
+ setPendingRequests(requestsRef.current);
1533
1444
  }, []);
1534
- useEffect3(() => {
1535
- const unsubscribe = subscribe2(
1536
- "wallet_tx_request",
1537
- (event) => {
1538
- const payload = normalizeTxPayload(event.payload);
1539
- if (!payload) {
1540
- console.warn("[aomi][wallet] Ignoring tx request with invalid payload", event.payload);
1541
- return;
1542
- }
1543
- enqueue(bufferRef.current, "transaction", payload);
1544
- syncState();
1545
- }
1546
- );
1547
- return unsubscribe;
1548
- }, [subscribe2, syncState]);
1549
- useEffect3(() => {
1550
- const unsubscribe = subscribe2(
1551
- "wallet_eip712_request",
1552
- (event) => {
1553
- var _a;
1554
- const payload = normalizeEip712Payload((_a = event.payload) != null ? _a : {});
1555
- enqueue(bufferRef.current, "eip712_sign", payload);
1556
- syncState();
1557
- }
1558
- );
1559
- return unsubscribe;
1560
- }, [subscribe2, syncState]);
1561
- const startProcessingCb = useCallback6(
1562
- (id) => {
1563
- markProcessing(bufferRef.current, id);
1564
- syncState();
1565
- },
1566
- [syncState]
1567
- );
1568
1445
  const resolveRequest = useCallback6(
1569
1446
  (id, result) => {
1570
- var _a;
1571
- const removed = dequeue(bufferRef.current, id);
1572
- if (!removed) return;
1573
- let outbound;
1574
- if (removed.kind === "transaction") {
1575
- outbound = sendOutbound({
1576
- type: "wallet:tx_complete",
1577
- sessionId,
1578
- payload: {
1579
- txHash: (_a = result.txHash) != null ? _a : "",
1580
- status: "success",
1581
- amount: result.amount
1582
- }
1583
- });
1584
- } else {
1585
- const eip712Payload = removed.payload;
1586
- outbound = sendOutbound({
1587
- type: "wallet_eip712_response",
1588
- sessionId,
1589
- payload: {
1590
- status: "success",
1591
- signature: result.signature,
1592
- description: eip712Payload.description
1593
- }
1594
- });
1447
+ const session = getSession();
1448
+ if (!session) {
1449
+ console.error("[wallet-handler] No session available to resolve request");
1450
+ return;
1595
1451
  }
1596
- outbound.then(() => onRequestComplete == null ? void 0 : onRequestComplete());
1597
- syncState();
1452
+ requestsRef.current = requestsRef.current.filter((r) => r.id !== id);
1453
+ setPendingRequests(requestsRef.current);
1454
+ void session.resolve(id, result).catch((err) => {
1455
+ console.error("[wallet-handler] Failed to resolve request:", err);
1456
+ });
1598
1457
  },
1599
- [sendOutbound, sessionId, syncState, onRequestComplete]
1458
+ [getSession]
1600
1459
  );
1601
1460
  const rejectRequest = useCallback6(
1602
1461
  (id, error) => {
1603
- const removed = dequeue(bufferRef.current, id);
1604
- if (!removed) return;
1605
- let outbound;
1606
- if (removed.kind === "transaction") {
1607
- outbound = sendOutbound({
1608
- type: "wallet:tx_complete",
1609
- sessionId,
1610
- payload: {
1611
- txHash: "",
1612
- status: "failed"
1613
- }
1614
- });
1615
- } else {
1616
- const eip712Payload = removed.payload;
1617
- outbound = sendOutbound({
1618
- type: "wallet_eip712_response",
1619
- sessionId,
1620
- payload: {
1621
- status: "failed",
1622
- error: error != null ? error : "EIP-712 signing failed",
1623
- description: eip712Payload.description
1624
- }
1625
- });
1462
+ const session = getSession();
1463
+ if (!session) {
1464
+ console.error("[wallet-handler] No session available to reject request");
1465
+ return;
1626
1466
  }
1627
- outbound.then(() => onRequestComplete == null ? void 0 : onRequestComplete());
1628
- syncState();
1467
+ requestsRef.current = requestsRef.current.filter((r) => r.id !== id);
1468
+ setPendingRequests(requestsRef.current);
1469
+ void session.reject(id, error).catch((err) => {
1470
+ console.error("[wallet-handler] Failed to reject request:", err);
1471
+ });
1629
1472
  },
1630
- [sendOutbound, sessionId, syncState, onRequestComplete]
1473
+ [getSession]
1631
1474
  );
1632
1475
  return {
1633
1476
  pendingRequests,
1634
- startProcessing: startProcessingCb,
1477
+ enqueueRequest,
1635
1478
  resolveRequest,
1636
1479
  rejectRequest
1637
1480
  };
@@ -1646,19 +1489,25 @@ function AomiRuntimeCore({
1646
1489
  const threadContext = useThreadContext();
1647
1490
  const eventContext = useEventContext();
1648
1491
  const notificationContext = useNotification();
1649
- const { dispatchInboundSystem: dispatchSystemEvents } = eventContext;
1650
1492
  const { user, onUserStateChange, getUserState } = useUser();
1651
1493
  const { getControlState, getCurrentThreadApp, clearSecrets } = useControl();
1494
+ const sessionManagerRef = useRef7(null);
1495
+ const walletHandler = useWalletHandler({
1496
+ getSession: () => {
1497
+ var _a;
1498
+ return (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadContext.currentThreadId);
1499
+ }
1500
+ });
1652
1501
  const {
1653
- backendStateRef,
1654
- polling,
1655
- messageController,
1502
+ sessionManager,
1503
+ getSession,
1656
1504
  isRunning,
1657
1505
  setIsRunning,
1658
1506
  ensureInitialState,
1507
+ sendMessage: orchestratorSendMessage,
1508
+ cancelGeneration: orchestratorCancel,
1659
1509
  aomiClientRef
1660
1510
  } = useRuntimeOrchestrator(aomiClient, {
1661
- onSyncEvents: dispatchSystemEvents,
1662
1511
  getPublicKey: () => getUserState().address,
1663
1512
  getUserState,
1664
1513
  getApp: getCurrentThreadApp,
@@ -1666,8 +1515,11 @@ function AomiRuntimeCore({
1666
1515
  getClientId: () => {
1667
1516
  var _a;
1668
1517
  return (_a = getControlState().clientId) != null ? _a : void 0;
1669
- }
1518
+ },
1519
+ onWalletRequest: (request) => walletHandler.enqueueRequest(request),
1520
+ onEvent: (event) => eventContext.dispatch(event)
1670
1521
  });
1522
+ sessionManagerRef.current = sessionManager;
1671
1523
  const walletSnapshot = useCallback7(
1672
1524
  (nextUser) => ({
1673
1525
  address: nextUser.address,
@@ -1678,7 +1530,7 @@ function AomiRuntimeCore({
1678
1530
  [getUserState]
1679
1531
  );
1680
1532
  const lastWalletStateRef = useRef7(walletSnapshot(getUserState()));
1681
- useEffect4(() => {
1533
+ useEffect3(() => {
1682
1534
  lastWalletStateRef.current = walletSnapshot(getUserState());
1683
1535
  const unsubscribe = onUserStateChange(async (newUser) => {
1684
1536
  const nextWalletState = walletSnapshot(newUser);
@@ -1705,17 +1557,10 @@ function AomiRuntimeCore({
1705
1557
  const threadContextRef = useRef7(threadContext);
1706
1558
  threadContextRef.current = threadContext;
1707
1559
  const currentThreadIdRef = useRef7(threadContext.currentThreadId);
1708
- useEffect4(() => {
1560
+ useEffect3(() => {
1709
1561
  currentThreadIdRef.current = threadContext.currentThreadId;
1710
1562
  }, [threadContext.currentThreadId]);
1711
- const onWalletRequestComplete = useCallback7(() => {
1712
- polling.start(currentThreadIdRef.current);
1713
- }, [polling]);
1714
- const walletHandler = useWalletHandler({
1715
- sessionId: threadContext.currentThreadId,
1716
- onRequestComplete: onWalletRequestComplete
1717
- });
1718
- useEffect4(() => {
1563
+ useEffect3(() => {
1719
1564
  const unsubscribe = eventContext.subscribe(
1720
1565
  "user_state_request",
1721
1566
  () => {
@@ -1728,14 +1573,10 @@ function AomiRuntimeCore({
1728
1573
  );
1729
1574
  return unsubscribe;
1730
1575
  }, [eventContext, threadContext.currentThreadId, getUserState]);
1731
- useEffect4(() => {
1576
+ useEffect3(() => {
1732
1577
  void ensureInitialState(threadContext.currentThreadId);
1733
1578
  }, [ensureInitialState, threadContext.currentThreadId]);
1734
- useEffect4(() => {
1735
- const threadId = threadContext.currentThreadId;
1736
- setIsRunning(isThreadRunning(backendStateRef.current, threadId));
1737
- }, [backendStateRef, setIsRunning, threadContext.currentThreadId]);
1738
- useEffect4(() => {
1579
+ useEffect3(() => {
1739
1580
  const threadId = threadContext.currentThreadId;
1740
1581
  const currentMeta = threadContext.getThreadMetadata(threadId);
1741
1582
  if (currentMeta && currentMeta.control.isProcessing !== isRunning) {
@@ -1749,7 +1590,7 @@ function AomiRuntimeCore({
1749
1590
  const currentMessages = threadContext.getThreadMessages(
1750
1591
  threadContext.currentThreadId
1751
1592
  );
1752
- useEffect4(() => {
1593
+ useEffect3(() => {
1753
1594
  const userAddress = user.address;
1754
1595
  if (!userAddress) return;
1755
1596
  const fetchThreadList = async () => {
@@ -1790,66 +1631,19 @@ function AomiRuntimeCore({
1790
1631
  }, [user.address, aomiClientRef]);
1791
1632
  const threadListAdapter = useMemo2(
1792
1633
  () => buildThreadListAdapter({
1793
- backendStateRef,
1794
1634
  aomiClientRef,
1795
1635
  threadContext,
1796
- currentThreadIdRef,
1797
- polling,
1798
- userAddress: user.address,
1799
- setIsRunning,
1800
- getApp: getCurrentThreadApp,
1801
- getApiKey: () => getControlState().apiKey,
1802
- getUserState
1636
+ setIsRunning
1803
1637
  }),
1804
1638
  [
1805
1639
  aomiClientRef,
1806
- polling,
1807
- user.address,
1808
- backendStateRef,
1809
1640
  setIsRunning,
1810
1641
  threadContext,
1811
1642
  threadContext.currentThreadId,
1812
- threadContext.allThreadsMetadata,
1813
- getControlState,
1814
- getCurrentThreadApp,
1815
- getUserState
1643
+ threadContext.allThreadsMetadata
1816
1644
  ]
1817
1645
  );
1818
- useEffect4(() => {
1819
- const backendState = backendStateRef.current;
1820
- const unsubscribe = eventContext.subscribe("title_changed", (event) => {
1821
- const sessionId = event.sessionId;
1822
- const payload = event.payload;
1823
- const newTitle = payload == null ? void 0 : payload.new_title;
1824
- if (typeof newTitle !== "string") return;
1825
- const targetThreadId = resolveThreadId(backendState, sessionId);
1826
- const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
1827
- if (process.env.NODE_ENV !== "production") {
1828
- console.debug("[aomi][sse] title_changed", {
1829
- sessionId,
1830
- newTitle,
1831
- normalizedTitle,
1832
- currentThreadId: threadContextRef.current.currentThreadId,
1833
- targetThreadId
1834
- });
1835
- }
1836
- threadContextRef.current.setThreadMetadata((prev) => {
1837
- var _a, _b;
1838
- const next = new Map(prev);
1839
- const existing = next.get(targetThreadId);
1840
- const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
1841
- next.set(targetThreadId, {
1842
- title: normalizedTitle,
1843
- status: nextStatus,
1844
- lastActiveAt: (_a = existing == null ? void 0 : existing.lastActiveAt) != null ? _a : (/* @__PURE__ */ new Date()).toISOString(),
1845
- control: (_b = existing == null ? void 0 : existing.control) != null ? _b : initThreadControl()
1846
- });
1847
- return next;
1848
- });
1849
- });
1850
- return unsubscribe;
1851
- }, [eventContext, backendStateRef]);
1852
- useEffect4(() => {
1646
+ useEffect3(() => {
1853
1647
  const showToolNotification = (eventType) => (event) => {
1854
1648
  const payload = event.payload;
1855
1649
  const toolName = typeof (payload == null ? void 0 : payload.tool_name) === "string" ? payload.tool_name : void 0;
@@ -1874,10 +1668,8 @@ function AomiRuntimeCore({
1874
1668
  unsubscribeComplete();
1875
1669
  };
1876
1670
  }, [eventContext, notificationContext]);
1877
- useEffect4(() => {
1878
- const unsubscribe = eventContext.subscribe("system_notice", (event) => {
1879
- const payload = event.payload;
1880
- const message = payload == null ? void 0 : payload.message;
1671
+ useEffect3(() => {
1672
+ const unsubscribe = eventContext.subscribe("system_notice", (_event) => {
1881
1673
  });
1882
1674
  return unsubscribe;
1883
1675
  }, [eventContext, notificationContext]);
@@ -1885,34 +1677,36 @@ function AomiRuntimeCore({
1885
1677
  messages: currentMessages,
1886
1678
  setMessages: (msgs) => threadContext.setThreadMessages(threadContext.currentThreadId, [...msgs]),
1887
1679
  isRunning,
1888
- onNew: (message) => messageController.outbound(message, threadContext.currentThreadId),
1889
- onCancel: () => messageController.cancel(threadContext.currentThreadId),
1680
+ onNew: async (message) => {
1681
+ const text = message.content.filter(
1682
+ (part) => part.type === "text"
1683
+ ).map((part) => part.text).join("\n");
1684
+ if (text) {
1685
+ await orchestratorSendMessage(text, threadContext.currentThreadId);
1686
+ }
1687
+ },
1688
+ onCancel: async () => {
1689
+ await orchestratorCancel(threadContext.currentThreadId);
1690
+ },
1890
1691
  convertMessage: (msg) => msg,
1891
1692
  adapters: { threadList: threadListAdapter }
1892
1693
  });
1893
- useEffect4(() => {
1694
+ useEffect3(() => {
1894
1695
  return () => {
1895
- polling.stopAll();
1696
+ sessionManager.closeAll();
1896
1697
  void clearSecrets();
1897
1698
  };
1898
- }, [polling, clearSecrets]);
1699
+ }, [sessionManager, clearSecrets]);
1899
1700
  const userContext = useUser();
1900
1701
  const sendMessage = useCallback7(
1901
1702
  async (text) => {
1902
- const appendMessage = {
1903
- role: "user",
1904
- content: [{ type: "text", text }]
1905
- };
1906
- await messageController.outbound(
1907
- appendMessage,
1908
- threadContext.currentThreadId
1909
- );
1703
+ await orchestratorSendMessage(text, threadContext.currentThreadId);
1910
1704
  },
1911
- [messageController, threadContext.currentThreadId]
1705
+ [orchestratorSendMessage, threadContext.currentThreadId]
1912
1706
  );
1913
1707
  const cancelGeneration = useCallback7(() => {
1914
- messageController.cancel(threadContext.currentThreadId);
1915
- }, [messageController, threadContext.currentThreadId]);
1708
+ void orchestratorCancel(threadContext.currentThreadId);
1709
+ }, [orchestratorCancel, threadContext.currentThreadId]);
1916
1710
  const getMessages = useCallback7(
1917
1711
  (threadId) => {
1918
1712
  const id = threadId != null ? threadId : threadContext.currentThreadId;
@@ -1926,9 +1720,10 @@ function AomiRuntimeCore({
1926
1720
  }, [threadListAdapter]);
1927
1721
  const deleteThread = useCallback7(
1928
1722
  async (threadId) => {
1723
+ sessionManager.close(threadId);
1929
1724
  await threadListAdapter.onDelete(threadId);
1930
1725
  },
1931
- [threadListAdapter]
1726
+ [threadListAdapter, sessionManager]
1932
1727
  );
1933
1728
  const renameThread = useCallback7(
1934
1729
  async (threadId, title) => {
@@ -1983,7 +1778,9 @@ function AomiRuntimeCore({
1983
1778
  clearAllNotifications: notificationContext.clearAll,
1984
1779
  // Wallet API
1985
1780
  pendingWalletRequests: walletHandler.pendingRequests,
1986
- startWalletRequest: walletHandler.startProcessing,
1781
+ startWalletRequest: () => {
1782
+ },
1783
+ // No-op: ClientSession manages processing state
1987
1784
  resolveWalletRequest: walletHandler.resolveRequest,
1988
1785
  rejectWalletRequest: walletHandler.rejectRequest,
1989
1786
  // Event API
@@ -2051,7 +1848,7 @@ function AomiRuntimeInner({
2051
1848
  }
2052
1849
 
2053
1850
  // packages/react/src/handlers/notification-handler.ts
2054
- import { useCallback as useCallback8, useEffect as useEffect5, useState as useState7 } from "react";
1851
+ import { useCallback as useCallback8, useEffect as useEffect4, useState as useState6 } from "react";
2055
1852
  var notificationIdCounter2 = 0;
2056
1853
  function generateNotificationId() {
2057
1854
  return `notif-${Date.now()}-${++notificationIdCounter2}`;
@@ -2059,10 +1856,10 @@ function generateNotificationId() {
2059
1856
  function useNotificationHandler({
2060
1857
  onNotification
2061
1858
  } = {}) {
2062
- const { subscribe: subscribe2 } = useEventContext();
2063
- const [notifications, setNotifications] = useState7([]);
2064
- useEffect5(() => {
2065
- const unsubscribe = subscribe2("notification", (event) => {
1859
+ const { subscribe } = useEventContext();
1860
+ const [notifications, setNotifications] = useState6([]);
1861
+ useEffect4(() => {
1862
+ const unsubscribe = subscribe("notification", (event) => {
2066
1863
  var _a, _b;
2067
1864
  const payload = event.payload;
2068
1865
  const notification = {
@@ -2071,14 +1868,14 @@ function useNotificationHandler({
2071
1868
  title: (_b = payload.title) != null ? _b : "Notification",
2072
1869
  body: payload.body,
2073
1870
  handled: false,
2074
- timestamp: event.timestamp,
1871
+ timestamp: Date.now(),
2075
1872
  sessionId: event.sessionId
2076
1873
  };
2077
1874
  setNotifications((prev) => [notification, ...prev]);
2078
1875
  onNotification == null ? void 0 : onNotification(notification);
2079
1876
  });
2080
1877
  return unsubscribe;
2081
- }, [subscribe2, onNotification]);
1878
+ }, [subscribe, onNotification]);
2082
1879
  const unhandledCount = notifications.filter((n) => !n.handled).length;
2083
1880
  const markHandled = useCallback8((id) => {
2084
1881
  setNotifications(