@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.cjs CHANGED
@@ -20,6 +20,19 @@ var __spreadValues = (a, b) => {
20
20
  return a;
21
21
  };
22
22
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+ var __restKey = (key) => typeof key === "symbol" ? key : key + "";
24
+ var __objRest = (source, exclude) => {
25
+ var target = {};
26
+ for (var prop in source)
27
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
28
+ target[prop] = source[prop];
29
+ if (source != null && __getOwnPropSymbols)
30
+ for (var prop of __getOwnPropSymbols(source)) {
31
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
32
+ target[prop] = source[prop];
33
+ }
34
+ return target;
35
+ };
23
36
  var __export = (target, all) => {
24
37
  for (var name in all)
25
38
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -37,7 +50,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
37
50
  // packages/react/src/index.ts
38
51
  var index_exports = {};
39
52
  __export(index_exports, {
40
- AomiClient: () => import_client4.AomiClient,
53
+ AomiClient: () => import_client3.AomiClient,
41
54
  AomiRuntimeProvider: () => AomiRuntimeProvider,
42
55
  ControlContextProvider: () => ControlContextProvider,
43
56
  EventContextProvider: () => EventContextProvider,
@@ -50,7 +63,7 @@ __export(index_exports, {
50
63
  getChainInfo: () => getChainInfo,
51
64
  getNetworkName: () => getNetworkName,
52
65
  initThreadControl: () => initThreadControl,
53
- toViemSignTypedDataArgs: () => import_client5.toViemSignTypedDataArgs,
66
+ toViemSignTypedDataArgs: () => import_client4.toViemSignTypedDataArgs,
54
67
  useAomiRuntime: () => useAomiRuntime,
55
68
  useControl: () => useControl,
56
69
  useCurrentThreadMessages: () => useCurrentThreadMessages,
@@ -63,12 +76,12 @@ __export(index_exports, {
63
76
  useWalletHandler: () => useWalletHandler
64
77
  });
65
78
  module.exports = __toCommonJS(index_exports);
79
+ var import_client3 = require("@aomi-labs/client");
66
80
  var import_client4 = require("@aomi-labs/client");
67
- var import_client5 = require("@aomi-labs/client");
68
81
 
69
82
  // packages/react/src/runtime/aomi-runtime.tsx
70
83
  var import_react11 = require("react");
71
- var import_client3 = require("@aomi-labs/client");
84
+ var import_client2 = require("@aomi-labs/client");
72
85
 
73
86
  // packages/react/src/contexts/control-context.tsx
74
87
  var import_react = require("react");
@@ -247,6 +260,25 @@ var ThreadStore = class {
247
260
  // packages/react/src/contexts/control-context.tsx
248
261
  var import_jsx_runtime = require("react/jsx-runtime");
249
262
  var API_KEY_STORAGE_KEY = "aomi_api_key";
263
+ var CLIENT_ID_STORAGE_KEY = "aomi_client_id";
264
+ var PROVIDER_KEYS_STORAGE_KEY = "aomi_provider_keys";
265
+ var PROVIDER_KEY_SECRET_PREFIX = "PROVIDER_KEY:";
266
+ function getOrCreateClientId() {
267
+ var _a, _b, _c, _d, _e;
268
+ try {
269
+ const storedClientId = (_a = globalThis.localStorage) == null ? void 0 : _a.getItem(CLIENT_ID_STORAGE_KEY);
270
+ if (storedClientId && storedClientId.trim().length > 0) {
271
+ return storedClientId;
272
+ }
273
+ } catch (e) {
274
+ }
275
+ const clientId = (_d = (_c = (_b = globalThis.crypto) == null ? void 0 : _b.randomUUID) == null ? void 0 : _c.call(_b)) != null ? _d : `client-${Date.now()}`;
276
+ try {
277
+ (_e = globalThis.localStorage) == null ? void 0 : _e.setItem(CLIENT_ID_STORAGE_KEY, clientId);
278
+ } catch (e) {
279
+ }
280
+ return clientId;
281
+ }
250
282
  function getDefaultApp(apps) {
251
283
  var _a;
252
284
  return apps.includes("default") ? "default" : (_a = apps[0]) != null ? _a : null;
@@ -276,11 +308,12 @@ function ControlContextProvider({
276
308
  var _a, _b;
277
309
  const [state, setStateInternal] = (0, import_react.useState)(() => ({
278
310
  apiKey: null,
279
- clientId: null,
311
+ clientId: getOrCreateClientId(),
280
312
  availableModels: [],
281
313
  authorizedApps: [],
282
314
  defaultModel: null,
283
- defaultApp: null
315
+ defaultApp: null,
316
+ providerKeys: {}
284
317
  }));
285
318
  const stateRef = (0, import_react.useRef)(state);
286
319
  stateRef.current = state;
@@ -298,10 +331,14 @@ function ControlContextProvider({
298
331
  const currentThreadMetadata = getThreadMetadata(sessionId);
299
332
  const isProcessing = (_b = (_a = currentThreadMetadata == null ? void 0 : currentThreadMetadata.control) == null ? void 0 : _a.isProcessing) != null ? _b : false;
300
333
  (0, import_react.useEffect)(() => {
301
- var _a2, _b2, _c;
302
- const clientId = (_c = (_b2 = (_a2 = globalThis.crypto) == null ? void 0 : _a2.randomUUID) == null ? void 0 : _b2.call(_a2)) != null ? _c : `client-${Date.now()}`;
303
- setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), { clientId }));
304
- }, []);
334
+ var _a2;
335
+ try {
336
+ if (state.clientId) {
337
+ (_a2 = globalThis.localStorage) == null ? void 0 : _a2.setItem(CLIENT_ID_STORAGE_KEY, state.clientId);
338
+ }
339
+ } catch (e) {
340
+ }
341
+ }, [state.clientId]);
305
342
  (0, import_react.useEffect)(() => {
306
343
  var _a2, _b2;
307
344
  try {
@@ -312,6 +349,17 @@ function ControlContextProvider({
312
349
  } catch (e) {
313
350
  }
314
351
  }, []);
352
+ (0, import_react.useEffect)(() => {
353
+ var _a2;
354
+ try {
355
+ const raw = (_a2 = globalThis.localStorage) == null ? void 0 : _a2.getItem(PROVIDER_KEYS_STORAGE_KEY);
356
+ if (raw) {
357
+ const parsed = JSON.parse(raw);
358
+ setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), { providerKeys: parsed }));
359
+ }
360
+ } catch (e) {
361
+ }
362
+ }, []);
315
363
  (0, import_react.useEffect)(() => {
316
364
  var _a2, _b2;
317
365
  try {
@@ -323,6 +371,33 @@ function ControlContextProvider({
323
371
  } catch (e) {
324
372
  }
325
373
  }, [state.apiKey]);
374
+ (0, import_react.useEffect)(() => {
375
+ var _a2, _b2;
376
+ try {
377
+ const keys = state.providerKeys;
378
+ if (Object.keys(keys).length > 0) {
379
+ (_a2 = globalThis.localStorage) == null ? void 0 : _a2.setItem(
380
+ PROVIDER_KEYS_STORAGE_KEY,
381
+ JSON.stringify(keys)
382
+ );
383
+ } else {
384
+ (_b2 = globalThis.localStorage) == null ? void 0 : _b2.removeItem(PROVIDER_KEYS_STORAGE_KEY);
385
+ }
386
+ } catch (e) {
387
+ }
388
+ }, [state.providerKeys]);
389
+ (0, import_react.useEffect)(() => {
390
+ if (!state.clientId) return;
391
+ const keys = stateRef.current.providerKeys;
392
+ if (Object.keys(keys).length === 0) return;
393
+ const secrets = {};
394
+ for (const [provider, entry] of Object.entries(keys)) {
395
+ secrets[`${PROVIDER_KEY_SECRET_PREFIX}${provider}`] = entry.apiKey;
396
+ }
397
+ void aomiClientRef.current.ingestSecrets(state.clientId, secrets).catch((err) => {
398
+ console.error("Failed to auto-ingest provider keys:", err);
399
+ });
400
+ }, [state.clientId, state.providerKeys]);
326
401
  (0, import_react.useEffect)(() => {
327
402
  const fetchApps = async () => {
328
403
  var _a2;
@@ -393,6 +468,65 @@ function ControlContextProvider({
393
468
  if (!clientId) return;
394
469
  await ((_b2 = (_a2 = aomiClientRef.current).clearSecrets) == null ? void 0 : _b2.call(_a2, clientId));
395
470
  }, []);
471
+ const setProviderKey = (0, import_react.useCallback)(
472
+ async (provider, apiKey, label) => {
473
+ const trimmed = apiKey.trim();
474
+ if (!trimmed) return;
475
+ const entry = {
476
+ apiKey: trimmed,
477
+ keyPrefix: trimmed.slice(0, 7),
478
+ label
479
+ };
480
+ setStateInternal((prev) => {
481
+ const next = __spreadProps(__spreadValues({}, prev), {
482
+ providerKeys: __spreadProps(__spreadValues({}, prev.providerKeys), { [provider]: entry })
483
+ });
484
+ callbacks.current.forEach((cb) => cb(next));
485
+ return next;
486
+ });
487
+ const clientId = stateRef.current.clientId;
488
+ if (clientId) {
489
+ try {
490
+ await aomiClientRef.current.ingestSecrets(clientId, {
491
+ [`${PROVIDER_KEY_SECRET_PREFIX}${provider}`]: trimmed
492
+ });
493
+ } catch (err) {
494
+ console.error("Failed to ingest provider key:", err);
495
+ }
496
+ }
497
+ },
498
+ []
499
+ );
500
+ const removeProviderKey = (0, import_react.useCallback)(
501
+ async (provider) => {
502
+ const clientId = stateRef.current.clientId;
503
+ if (clientId) {
504
+ await aomiClientRef.current.deleteSecret(
505
+ clientId,
506
+ `${PROVIDER_KEY_SECRET_PREFIX}${provider}`
507
+ );
508
+ }
509
+ setStateInternal((prev) => {
510
+ const _a2 = prev.providerKeys, { [provider]: _ } = _a2, rest = __objRest(_a2, [__restKey(provider)]);
511
+ const next = __spreadProps(__spreadValues({}, prev), { providerKeys: rest });
512
+ callbacks.current.forEach((cb) => cb(next));
513
+ return next;
514
+ });
515
+ },
516
+ []
517
+ );
518
+ const getProviderKeys = (0, import_react.useCallback)(
519
+ () => stateRef.current.providerKeys,
520
+ []
521
+ );
522
+ const hasProviderKey = (0, import_react.useCallback)(
523
+ (provider) => {
524
+ const keys = stateRef.current.providerKeys;
525
+ if (provider) return provider in keys;
526
+ return Object.keys(keys).length > 0;
527
+ },
528
+ []
529
+ );
396
530
  const getAvailableModels = (0, import_react.useCallback)(async () => {
397
531
  try {
398
532
  const models = await aomiClientRef.current.getModels(
@@ -451,7 +585,7 @@ function ControlContextProvider({
451
585
  )) != null ? _c : "default";
452
586
  }, []);
453
587
  const onModelSelect = (0, import_react.useCallback)(async (model) => {
454
- var _a2, _b2, _c, _d;
588
+ var _a2, _b2, _c, _d, _e;
455
589
  const threadId = sessionIdRef.current;
456
590
  const currentControl = (_b2 = (_a2 = getThreadMetadataRef.current(threadId)) == null ? void 0 : _a2.control) != null ? _b2 : initThreadControl();
457
591
  const isProcessing2 = currentControl.isProcessing;
@@ -492,7 +626,11 @@ function ControlContextProvider({
492
626
  const result = await aomiClientRef.current.setModel(
493
627
  threadId,
494
628
  model,
495
- { app, apiKey: (_d = stateRef.current.apiKey) != null ? _d : void 0 }
629
+ {
630
+ app,
631
+ apiKey: (_d = stateRef.current.apiKey) != null ? _d : void 0,
632
+ clientId: (_e = stateRef.current.clientId) != null ? _e : void 0
633
+ }
496
634
  );
497
635
  console.log("[control-context] onModelSelect backend result", result);
498
636
  } catch (err) {
@@ -575,6 +713,10 @@ function ControlContextProvider({
575
713
  setApiKey,
576
714
  ingestSecrets,
577
715
  clearSecrets,
716
+ setProviderKey,
717
+ removeProviderKey,
718
+ getProviderKeys,
719
+ hasProviderKey,
578
720
  getAvailableModels,
579
721
  getAuthorizedApps,
580
722
  getCurrentThreadControl,
@@ -594,53 +736,6 @@ function ControlContextProvider({
594
736
 
595
737
  // packages/react/src/contexts/event-context.tsx
596
738
  var import_react2 = require("react");
597
- var import_client = require("@aomi-labs/client");
598
-
599
- // packages/react/src/state/event-buffer.ts
600
- function createEventBuffer() {
601
- return {
602
- inboundQueue: [],
603
- outboundQueue: [],
604
- sseStatus: "disconnected",
605
- lastEventId: null,
606
- subscribers: /* @__PURE__ */ new Map()
607
- };
608
- }
609
- function enqueueInbound(state, event) {
610
- state.inboundQueue.push(__spreadProps(__spreadValues({}, event), {
611
- status: "pending",
612
- timestamp: Date.now()
613
- }));
614
- }
615
- function subscribe(state, type, callback) {
616
- if (!state.subscribers.has(type)) {
617
- state.subscribers.set(type, /* @__PURE__ */ new Set());
618
- }
619
- state.subscribers.get(type).add(callback);
620
- return () => {
621
- var _a;
622
- (_a = state.subscribers.get(type)) == null ? void 0 : _a.delete(callback);
623
- };
624
- }
625
- function dispatch(state, event) {
626
- const typeSubscribers = state.subscribers.get(event.type);
627
- if (typeSubscribers) {
628
- for (const callback of typeSubscribers) {
629
- callback(event);
630
- }
631
- }
632
- const allSubscribers = state.subscribers.get("*");
633
- if (allSubscribers) {
634
- for (const callback of allSubscribers) {
635
- callback(event);
636
- }
637
- }
638
- }
639
- function setSSEStatus(state, status) {
640
- state.sseStatus = status;
641
- }
642
-
643
- // packages/react/src/contexts/event-context.tsx
644
739
  var import_jsx_runtime2 = require("react/jsx-runtime");
645
740
  var EventContextState = (0, import_react2.createContext)(null);
646
741
  function useEventContext() {
@@ -657,52 +752,32 @@ function EventContextProvider({
657
752
  aomiClient,
658
753
  sessionId
659
754
  }) {
660
- const bufferRef = (0, import_react2.useRef)(null);
661
- if (!bufferRef.current) {
662
- bufferRef.current = createEventBuffer();
663
- }
664
- const buffer = bufferRef.current;
665
- const [sseStatus, setSseStatus] = (0, import_react2.useState)("disconnected");
666
- (0, import_react2.useEffect)(() => {
667
- setSSEStatus(buffer, "connecting");
668
- setSseStatus("connecting");
669
- const unsubscribe = aomiClient.subscribeSSE(
670
- sessionId,
671
- (event) => {
672
- enqueueInbound(buffer, {
673
- type: event.type,
674
- sessionId: event.session_id,
675
- payload: event
676
- });
677
- const inboundEvent = {
678
- type: event.type,
679
- sessionId: event.session_id,
680
- payload: event,
681
- status: "fetched",
682
- timestamp: Date.now()
683
- };
684
- dispatch(buffer, inboundEvent);
685
- },
686
- (error) => {
687
- console.error("SSE error:", error);
688
- setSSEStatus(buffer, "disconnected");
689
- setSseStatus("disconnected");
690
- }
691
- );
692
- setSSEStatus(buffer, "connected");
693
- setSseStatus("connected");
694
- return () => {
695
- unsubscribe();
696
- setSSEStatus(buffer, "disconnected");
697
- setSseStatus("disconnected");
698
- };
699
- }, [aomiClient, sessionId, buffer]);
700
- const subscribeCallback = (0, import_react2.useCallback)(
755
+ const subscribersRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
756
+ const subscribe = (0, import_react2.useCallback)(
701
757
  (type, callback) => {
702
- return subscribe(buffer, type, callback);
758
+ const subs = subscribersRef.current;
759
+ if (!subs.has(type)) {
760
+ subs.set(type, /* @__PURE__ */ new Set());
761
+ }
762
+ subs.get(type).add(callback);
763
+ return () => {
764
+ var _a;
765
+ (_a = subs.get(type)) == null ? void 0 : _a.delete(callback);
766
+ };
703
767
  },
704
- [buffer]
768
+ []
705
769
  );
770
+ const dispatchEvent = (0, import_react2.useCallback)((event) => {
771
+ const subs = subscribersRef.current;
772
+ const typeSubs = subs.get(event.type);
773
+ if (typeSubs) {
774
+ for (const cb of typeSubs) cb(event);
775
+ }
776
+ const wildcardSubs = subs.get("*");
777
+ if (wildcardSubs) {
778
+ for (const cb of wildcardSubs) cb(event);
779
+ }
780
+ }, []);
706
781
  const sendOutbound = (0, import_react2.useCallback)(
707
782
  async (event) => {
708
783
  try {
@@ -717,50 +792,14 @@ function EventContextProvider({
717
792
  },
718
793
  [aomiClient]
719
794
  );
720
- const dispatchSystemEvents = (0, import_react2.useCallback)(
721
- (sessionId2, events) => {
722
- var _a;
723
- for (const event of events) {
724
- let eventType;
725
- let payload;
726
- if ((0, import_client.isInlineCall)(event)) {
727
- eventType = event.InlineCall.type;
728
- payload = (_a = event.InlineCall.payload) != null ? _a : event.InlineCall;
729
- } else if ((0, import_client.isSystemNotice)(event)) {
730
- eventType = "system_notice";
731
- payload = { message: event.SystemNotice };
732
- } else if ((0, import_client.isSystemError)(event)) {
733
- eventType = "system_error";
734
- payload = { message: event.SystemError };
735
- } else if ((0, import_client.isAsyncCallback)(event)) {
736
- eventType = "async_callback";
737
- payload = event.AsyncCallback;
738
- } else {
739
- console.warn("Unknown system event type:", event);
740
- continue;
741
- }
742
- const inboundEvent = {
743
- type: eventType,
744
- sessionId: sessionId2,
745
- payload,
746
- status: "fetched",
747
- timestamp: Date.now()
748
- };
749
- enqueueInbound(buffer, {
750
- type: eventType,
751
- sessionId: sessionId2,
752
- payload
753
- });
754
- dispatch(buffer, inboundEvent);
755
- }
756
- },
757
- [buffer]
758
- );
759
795
  const contextValue = {
760
- subscribe: subscribeCallback,
796
+ subscribe,
797
+ dispatch: dispatchEvent,
761
798
  sendOutboundSystem: sendOutbound,
762
- dispatchInboundSystem: dispatchSystemEvents,
763
- sseStatus
799
+ // SSE is managed by ClientSession now — status is always "connected"
800
+ // when sessions are active. Individual session status can be queried
801
+ // from the session manager if needed.
802
+ sseStatus: "connected"
764
803
  };
765
804
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EventContextState.Provider, { value: contextValue, children });
766
805
  }
@@ -957,6 +996,40 @@ var import_react10 = require("@assistant-ui/react");
957
996
  // packages/react/src/runtime/orchestrator.ts
958
997
  var import_react6 = require("react");
959
998
 
999
+ // packages/react/src/runtime/session-manager.ts
1000
+ var import_client = require("@aomi-labs/client");
1001
+ var SessionManager = class {
1002
+ constructor(clientFactory) {
1003
+ this.clientFactory = clientFactory;
1004
+ this.sessions = /* @__PURE__ */ new Map();
1005
+ }
1006
+ getOrCreate(threadId, opts) {
1007
+ let session = this.sessions.get(threadId);
1008
+ if (session) return session;
1009
+ session = new import_client.Session(this.clientFactory(), __spreadProps(__spreadValues({}, opts), {
1010
+ sessionId: threadId
1011
+ }));
1012
+ this.sessions.set(threadId, session);
1013
+ return session;
1014
+ }
1015
+ get(threadId) {
1016
+ return this.sessions.get(threadId);
1017
+ }
1018
+ close(threadId) {
1019
+ const session = this.sessions.get(threadId);
1020
+ if (session) {
1021
+ session.close();
1022
+ this.sessions.delete(threadId);
1023
+ }
1024
+ }
1025
+ closeAll() {
1026
+ for (const [threadId, session] of this.sessions) {
1027
+ session.close();
1028
+ }
1029
+ this.sessions.clear();
1030
+ }
1031
+ };
1032
+
960
1033
  // packages/react/src/runtime/utils.ts
961
1034
  var import_clsx = require("clsx");
962
1035
  var import_tailwind_merge = require("tailwind-merge");
@@ -1059,194 +1132,6 @@ var SUPPORTED_CHAINS = [
1059
1132
  ];
1060
1133
  var getChainInfo = (chainId) => chainId === void 0 ? void 0 : SUPPORTED_CHAINS.find((c) => c.id === chainId);
1061
1134
 
1062
- // packages/react/src/state/backend-state.ts
1063
- function createBackendState() {
1064
- return {
1065
- runningThreads: /* @__PURE__ */ new Set()
1066
- };
1067
- }
1068
- function resolveThreadId(_state, threadId) {
1069
- return threadId;
1070
- }
1071
- function setThreadRunning(state, threadId, running) {
1072
- if (running) {
1073
- state.runningThreads.add(threadId);
1074
- } else {
1075
- state.runningThreads.delete(threadId);
1076
- }
1077
- }
1078
- function isThreadRunning(state, threadId) {
1079
- return state.runningThreads.has(threadId);
1080
- }
1081
-
1082
- // packages/react/src/runtime/message-controller.ts
1083
- var MessageController = class {
1084
- constructor(config) {
1085
- this.config = config;
1086
- }
1087
- inbound(threadId, msgs) {
1088
- if (!msgs) return;
1089
- const threadMessages = [];
1090
- for (const msg of msgs) {
1091
- const threadMessage = toInboundMessage(msg);
1092
- if (threadMessage) {
1093
- threadMessages.push(threadMessage);
1094
- }
1095
- }
1096
- this.getThreadContextApi().setThreadMessages(threadId, threadMessages);
1097
- }
1098
- async outbound(message, threadId) {
1099
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
1100
- const backendState = this.config.backendStateRef.current;
1101
- const text = message.content.filter(
1102
- (part) => part.type === "text"
1103
- ).map(
1104
- (part) => part.text
1105
- ).join("\n");
1106
- if (!text) return;
1107
- const threadState = this.getThreadContextApi();
1108
- const existingMessages = threadState.getThreadMessages(threadId);
1109
- const userMessage = {
1110
- role: "user",
1111
- content: [{ type: "text", text }],
1112
- createdAt: /* @__PURE__ */ new Date()
1113
- };
1114
- threadState.setThreadMessages(threadId, [...existingMessages, userMessage]);
1115
- threadState.updateThreadMetadata(threadId, {
1116
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1117
- });
1118
- const backendThreadId = resolveThreadId(backendState, threadId);
1119
- const app = this.config.getApp();
1120
- const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
1121
- const apiKey = (_e = (_d = (_c = this.config).getApiKey) == null ? void 0 : _d.call(_c)) != null ? _e : void 0;
1122
- const clientId = (_g = (_f = this.config).getClientId) == null ? void 0 : _g.call(_f);
1123
- const userState = (_i = (_h = this.config).getUserState) == null ? void 0 : _i.call(_h);
1124
- try {
1125
- this.markRunning(threadId, true);
1126
- const response = await this.config.aomiClientRef.current.sendMessage(
1127
- backendThreadId,
1128
- text,
1129
- { app, publicKey, apiKey, userState, clientId }
1130
- );
1131
- if (response == null ? void 0 : response.messages) {
1132
- this.inbound(threadId, response.messages);
1133
- }
1134
- if (((_j = response == null ? void 0 : response.system_events) == null ? void 0 : _j.length) && this.config.onSyncEvents) {
1135
- this.config.onSyncEvents(backendThreadId, response.system_events);
1136
- }
1137
- if (response == null ? void 0 : response.is_processing) {
1138
- this.config.polling.start(threadId);
1139
- } else if (!this.config.polling.isPolling(threadId)) {
1140
- this.markRunning(threadId, false);
1141
- }
1142
- } catch (error) {
1143
- console.error("Failed to send message:", error);
1144
- this.markRunning(threadId, false);
1145
- }
1146
- }
1147
- async cancel(threadId) {
1148
- var _a;
1149
- this.config.polling.stop(threadId);
1150
- const backendState = this.config.backendStateRef.current;
1151
- const backendThreadId = resolveThreadId(backendState, threadId);
1152
- try {
1153
- const response = await this.config.aomiClientRef.current.interrupt(backendThreadId);
1154
- if (response == null ? void 0 : response.messages) {
1155
- this.inbound(threadId, response.messages);
1156
- }
1157
- if (((_a = response == null ? void 0 : response.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1158
- this.config.onSyncEvents(backendThreadId, response.system_events);
1159
- }
1160
- this.markRunning(threadId, false);
1161
- } catch (error) {
1162
- console.error("Failed to cancel:", error);
1163
- }
1164
- }
1165
- markRunning(threadId, running) {
1166
- var _a, _b;
1167
- setThreadRunning(this.config.backendStateRef.current, threadId, running);
1168
- if (this.config.threadContextRef.current.currentThreadId === threadId) {
1169
- (_b = (_a = this.config).setGlobalIsRunning) == null ? void 0 : _b.call(_a, running);
1170
- }
1171
- }
1172
- getThreadContextApi() {
1173
- const { getThreadMessages, setThreadMessages, updateThreadMetadata } = this.config.threadContextRef.current;
1174
- return { getThreadMessages, setThreadMessages, updateThreadMetadata };
1175
- }
1176
- };
1177
-
1178
- // packages/react/src/runtime/polling-controller.ts
1179
- var PollingController = class {
1180
- constructor(config) {
1181
- this.config = config;
1182
- this.intervals = /* @__PURE__ */ new Map();
1183
- var _a;
1184
- this.intervalMs = (_a = config.intervalMs) != null ? _a : 500;
1185
- }
1186
- start(threadId) {
1187
- var _a, _b;
1188
- const backendState = this.config.backendStateRef.current;
1189
- if (this.intervals.has(threadId)) return;
1190
- const backendThreadId = resolveThreadId(backendState, threadId);
1191
- setThreadRunning(backendState, threadId, true);
1192
- const tick = async () => {
1193
- var _a2, _b2, _c, _d;
1194
- if (!this.intervals.has(threadId)) return;
1195
- try {
1196
- console.log(
1197
- "[PollingController] Fetching state for threadId:",
1198
- threadId
1199
- );
1200
- const userState = (_b2 = (_a2 = this.config).getUserState) == null ? void 0 : _b2.call(_a2);
1201
- const clientId = (_d = (_c = this.config).getClientId) == null ? void 0 : _d.call(_c);
1202
- const state = await this.config.aomiClientRef.current.fetchState(
1203
- backendThreadId,
1204
- userState,
1205
- clientId
1206
- );
1207
- if (!this.intervals.has(threadId)) return;
1208
- this.handleState(threadId, state);
1209
- } catch (error) {
1210
- console.error("Polling error:", error);
1211
- this.stop(threadId);
1212
- }
1213
- };
1214
- const intervalId = setInterval(tick, this.intervalMs);
1215
- this.intervals.set(threadId, intervalId);
1216
- (_b = (_a = this.config).onStart) == null ? void 0 : _b.call(_a, threadId);
1217
- }
1218
- stop(threadId) {
1219
- var _a, _b;
1220
- const intervalId = this.intervals.get(threadId);
1221
- if (intervalId) {
1222
- clearInterval(intervalId);
1223
- this.intervals.delete(threadId);
1224
- }
1225
- setThreadRunning(this.config.backendStateRef.current, threadId, false);
1226
- (_b = (_a = this.config).onStop) == null ? void 0 : _b.call(_a, threadId);
1227
- }
1228
- isPolling(threadId) {
1229
- return this.intervals.has(threadId);
1230
- }
1231
- stopAll() {
1232
- for (const threadId of this.intervals.keys()) {
1233
- this.stop(threadId);
1234
- }
1235
- }
1236
- handleState(threadId, state) {
1237
- var _a;
1238
- if (((_a = state.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1239
- const backendState = this.config.backendStateRef.current;
1240
- const sessionId = resolveThreadId(backendState, threadId);
1241
- this.config.onSyncEvents(sessionId, state.system_events);
1242
- }
1243
- this.config.applyMessages(threadId, state.messages);
1244
- if (!state.is_processing) {
1245
- this.stop(threadId);
1246
- }
1247
- }
1248
- };
1249
-
1250
1135
  // packages/react/src/runtime/orchestrator.ts
1251
1136
  function useRuntimeOrchestrator(aomiClient, options) {
1252
1137
  const threadContext = useThreadContext();
@@ -1254,90 +1139,160 @@ function useRuntimeOrchestrator(aomiClient, options) {
1254
1139
  threadContextRef.current = threadContext;
1255
1140
  const aomiClientRef = (0, import_react6.useRef)(aomiClient);
1256
1141
  aomiClientRef.current = aomiClient;
1257
- const backendStateRef = (0, import_react6.useRef)(createBackendState());
1258
1142
  const [isRunning, setIsRunning] = (0, import_react6.useState)(false);
1259
- const messageControllerRef = (0, import_react6.useRef)(null);
1260
- const pollingRef = (0, import_react6.useRef)(null);
1143
+ const sessionManagerRef = (0, import_react6.useRef)(null);
1144
+ if (!sessionManagerRef.current) {
1145
+ sessionManagerRef.current = new SessionManager(() => aomiClientRef.current);
1146
+ }
1261
1147
  const pendingFetches = (0, import_react6.useRef)(/* @__PURE__ */ new Set());
1262
- if (!pollingRef.current) {
1263
- pollingRef.current = new PollingController({
1264
- aomiClientRef,
1265
- backendStateRef,
1266
- applyMessages: (threadId, msgs) => {
1267
- var _a;
1268
- (_a = messageControllerRef.current) == null ? void 0 : _a.inbound(threadId, msgs);
1269
- },
1270
- onSyncEvents: options.onSyncEvents,
1271
- getUserState: options.getUserState,
1272
- getClientId: options.getClientId,
1273
- onStart: (threadId) => {
1148
+ const listenerCleanups = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
1149
+ const getSession = (0, import_react6.useCallback)(
1150
+ (threadId) => {
1151
+ var _a, _b, _c, _d, _e;
1152
+ const manager = sessionManagerRef.current;
1153
+ const existing = manager.get(threadId);
1154
+ if (existing) return existing;
1155
+ const session = manager.getOrCreate(threadId, {
1156
+ app: options.getApp(),
1157
+ publicKey: (_a = options.getPublicKey) == null ? void 0 : _a.call(options),
1158
+ apiKey: (_c = (_b = options.getApiKey) == null ? void 0 : _b.call(options)) != null ? _c : void 0,
1159
+ clientId: (_d = options.getClientId) == null ? void 0 : _d.call(options),
1160
+ userState: (_e = options.getUserState) == null ? void 0 : _e.call(options)
1161
+ });
1162
+ const cleanups = [];
1163
+ cleanups.push(
1164
+ session.on("messages", (msgs) => {
1165
+ const threadMessages = [];
1166
+ for (const msg of msgs) {
1167
+ const converted = toInboundMessage(msg);
1168
+ if (converted) threadMessages.push(converted);
1169
+ }
1170
+ threadContextRef.current.setThreadMessages(threadId, threadMessages);
1171
+ })
1172
+ );
1173
+ cleanups.push(
1174
+ session.on("processing_start", () => {
1175
+ if (threadContextRef.current.currentThreadId === threadId) {
1176
+ setIsRunning(true);
1177
+ }
1178
+ })
1179
+ );
1180
+ cleanups.push(
1181
+ session.on("processing_end", () => {
1182
+ if (threadContextRef.current.currentThreadId === threadId) {
1183
+ setIsRunning(false);
1184
+ }
1185
+ })
1186
+ );
1187
+ cleanups.push(
1188
+ session.on("wallet_tx_request", (req) => {
1189
+ var _a2;
1190
+ return (_a2 = options.onWalletRequest) == null ? void 0 : _a2.call(options, req);
1191
+ })
1192
+ );
1193
+ cleanups.push(
1194
+ session.on("wallet_eip712_request", (req) => {
1195
+ var _a2;
1196
+ return (_a2 = options.onWalletRequest) == null ? void 0 : _a2.call(options, req);
1197
+ })
1198
+ );
1199
+ cleanups.push(
1200
+ session.on("title_changed", ({ title }) => {
1201
+ threadContextRef.current.updateThreadMetadata(threadId, { title });
1202
+ })
1203
+ );
1204
+ const forwardEvent = (type) => session.on(type, (payload) => {
1205
+ var _a2;
1206
+ (_a2 = options.onEvent) == null ? void 0 : _a2.call(options, { type, payload, sessionId: threadId });
1207
+ });
1208
+ cleanups.push(forwardEvent("tool_update"));
1209
+ cleanups.push(forwardEvent("tool_complete"));
1210
+ cleanups.push(forwardEvent("system_notice"));
1211
+ cleanups.push(forwardEvent("system_error"));
1212
+ cleanups.push(forwardEvent("async_callback"));
1213
+ listenerCleanups.current.set(threadId, () => {
1214
+ for (const cleanup of cleanups) cleanup();
1215
+ });
1216
+ return session;
1217
+ },
1218
+ // Stable deps — option getters are refs
1219
+ []
1220
+ );
1221
+ const ensureInitialState = (0, import_react6.useCallback)(
1222
+ async (threadId) => {
1223
+ var _a;
1224
+ if (pendingFetches.current.has(threadId)) return;
1225
+ pendingFetches.current.add(threadId);
1226
+ try {
1227
+ const session = getSession(threadId);
1228
+ const userState = (_a = options.getUserState) == null ? void 0 : _a.call(options);
1229
+ if (userState) session.resolveUserState(userState);
1230
+ await session.fetchCurrentState();
1274
1231
  if (threadContextRef.current.currentThreadId === threadId) {
1275
- setIsRunning(true);
1232
+ setIsRunning(session.getIsProcessing());
1276
1233
  }
1277
- },
1278
- onStop: (threadId) => {
1234
+ } catch (error) {
1235
+ console.error("Failed to fetch initial state:", error);
1279
1236
  if (threadContextRef.current.currentThreadId === threadId) {
1280
1237
  setIsRunning(false);
1281
1238
  }
1239
+ } finally {
1240
+ pendingFetches.current.delete(threadId);
1282
1241
  }
1283
- });
1284
- }
1285
- if (!messageControllerRef.current) {
1286
- messageControllerRef.current = new MessageController({
1287
- aomiClientRef,
1288
- backendStateRef,
1289
- threadContextRef,
1290
- polling: pollingRef.current,
1291
- setGlobalIsRunning: setIsRunning,
1292
- getPublicKey: options.getPublicKey,
1293
- getApp: options.getApp,
1294
- getApiKey: options.getApiKey,
1295
- getClientId: options.getClientId,
1296
- getUserState: options.getUserState,
1297
- onSyncEvents: options.onSyncEvents
1298
- });
1299
- }
1300
- const ensureInitialState = (0, import_react6.useCallback)(async (threadId) => {
1301
- var _a, _b, _c, _d, _e;
1302
- if (pendingFetches.current.has(threadId)) return;
1303
- const backendThreadId = resolveThreadId(backendStateRef.current, threadId);
1304
- pendingFetches.current.add(threadId);
1305
- try {
1242
+ },
1243
+ [getSession]
1244
+ );
1245
+ const sendMessage = (0, import_react6.useCallback)(
1246
+ async (text, threadId) => {
1247
+ var _a;
1248
+ const session = getSession(threadId);
1306
1249
  const userState = (_a = options.getUserState) == null ? void 0 : _a.call(options);
1307
- const clientId = (_b = options.getClientId) == null ? void 0 : _b.call(options);
1308
- const state = await aomiClientRef.current.fetchState(
1309
- backendThreadId,
1310
- userState,
1311
- clientId
1312
- );
1313
- (_c = messageControllerRef.current) == null ? void 0 : _c.inbound(threadId, state.messages);
1314
- if (((_d = state.system_events) == null ? void 0 : _d.length) && options.onSyncEvents) {
1315
- options.onSyncEvents(backendThreadId, state.system_events);
1316
- }
1317
- if (threadContextRef.current.currentThreadId === threadId) {
1318
- if (state.is_processing) {
1319
- setIsRunning(true);
1320
- (_e = pollingRef.current) == null ? void 0 : _e.start(threadId);
1321
- } else {
1322
- setIsRunning(false);
1323
- }
1250
+ if (userState) session.resolveUserState(userState);
1251
+ const existingMessages = threadContextRef.current.getThreadMessages(threadId);
1252
+ const userMessage = {
1253
+ role: "user",
1254
+ content: [{ type: "text", text }],
1255
+ createdAt: /* @__PURE__ */ new Date()
1256
+ };
1257
+ threadContextRef.current.setThreadMessages(threadId, [
1258
+ ...existingMessages,
1259
+ userMessage
1260
+ ]);
1261
+ threadContextRef.current.updateThreadMetadata(threadId, {
1262
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1263
+ });
1264
+ await session.sendAsync(text);
1265
+ },
1266
+ [getSession]
1267
+ );
1268
+ const cancelGeneration = (0, import_react6.useCallback)(
1269
+ async (threadId) => {
1270
+ var _a;
1271
+ const session = (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadId);
1272
+ if (session) {
1273
+ await session.interrupt();
1324
1274
  }
1325
- } catch (error) {
1326
- console.error("Failed to fetch initial state:", error);
1327
- if (threadContextRef.current.currentThreadId === threadId) {
1328
- setIsRunning(false);
1275
+ },
1276
+ []
1277
+ );
1278
+ (0, import_react6.useEffect)(() => {
1279
+ return () => {
1280
+ var _a;
1281
+ (_a = sessionManagerRef.current) == null ? void 0 : _a.closeAll();
1282
+ for (const cleanup of listenerCleanups.current.values()) {
1283
+ cleanup();
1329
1284
  }
1330
- } finally {
1331
- pendingFetches.current.delete(threadId);
1332
- }
1285
+ listenerCleanups.current.clear();
1286
+ };
1333
1287
  }, []);
1334
1288
  return {
1335
- backendStateRef,
1336
- polling: pollingRef.current,
1337
- messageController: messageControllerRef.current,
1289
+ sessionManager: sessionManagerRef.current,
1290
+ getSession,
1338
1291
  isRunning,
1339
1292
  setIsRunning,
1340
1293
  ensureInitialState,
1294
+ sendMessage,
1295
+ cancelGeneration,
1341
1296
  aomiClientRef
1342
1297
  };
1343
1298
  }
@@ -1490,150 +1445,48 @@ function useAomiRuntime() {
1490
1445
 
1491
1446
  // packages/react/src/handlers/wallet-handler.ts
1492
1447
  var import_react8 = require("react");
1493
- var import_client2 = require("@aomi-labs/client");
1494
-
1495
- // packages/react/src/state/wallet-buffer.ts
1496
- function createWalletBuffer() {
1497
- return { queue: [], nextId: 1 };
1498
- }
1499
- function enqueue(buffer, kind, payload) {
1500
- const request = {
1501
- id: `wreq-${buffer.nextId++}`,
1502
- kind,
1503
- payload,
1504
- status: "pending",
1505
- timestamp: Date.now()
1506
- };
1507
- buffer.queue.push(request);
1508
- return request;
1509
- }
1510
- function dequeue(buffer, id) {
1511
- const index = buffer.queue.findIndex((r) => r.id === id);
1512
- if (index === -1) return null;
1513
- return buffer.queue.splice(index, 1)[0];
1514
- }
1515
- function markProcessing(buffer, id) {
1516
- const request = buffer.queue.find((r) => r.id === id);
1517
- if (!request || request.status !== "pending") return false;
1518
- request.status = "processing";
1519
- return true;
1520
- }
1521
- function getAll(buffer) {
1522
- return [...buffer.queue];
1523
- }
1524
-
1525
- // packages/react/src/handlers/wallet-handler.ts
1526
1448
  function useWalletHandler({
1527
- sessionId,
1528
- onRequestComplete
1449
+ getSession
1529
1450
  }) {
1530
- const { subscribe: subscribe2, sendOutboundSystem: sendOutbound } = useEventContext();
1531
- const bufferRef = (0, import_react8.useRef)(createWalletBuffer());
1532
1451
  const [pendingRequests, setPendingRequests] = (0, import_react8.useState)([]);
1533
- const syncState = (0, import_react8.useCallback)(() => {
1534
- setPendingRequests(getAll(bufferRef.current));
1452
+ const requestsRef = (0, import_react8.useRef)([]);
1453
+ const enqueueRequest = (0, import_react8.useCallback)((request) => {
1454
+ requestsRef.current = [...requestsRef.current, request];
1455
+ setPendingRequests(requestsRef.current);
1535
1456
  }, []);
1536
- (0, import_react8.useEffect)(() => {
1537
- const unsubscribe = subscribe2(
1538
- "wallet_tx_request",
1539
- (event) => {
1540
- const payload = (0, import_client2.normalizeTxPayload)(event.payload);
1541
- if (!payload) {
1542
- console.warn("[aomi][wallet] Ignoring tx request with invalid payload", event.payload);
1543
- return;
1544
- }
1545
- enqueue(bufferRef.current, "transaction", payload);
1546
- syncState();
1547
- }
1548
- );
1549
- return unsubscribe;
1550
- }, [subscribe2, syncState]);
1551
- (0, import_react8.useEffect)(() => {
1552
- const unsubscribe = subscribe2(
1553
- "wallet_eip712_request",
1554
- (event) => {
1555
- var _a;
1556
- const payload = (0, import_client2.normalizeEip712Payload)((_a = event.payload) != null ? _a : {});
1557
- enqueue(bufferRef.current, "eip712_sign", payload);
1558
- syncState();
1559
- }
1560
- );
1561
- return unsubscribe;
1562
- }, [subscribe2, syncState]);
1563
- const startProcessingCb = (0, import_react8.useCallback)(
1564
- (id) => {
1565
- markProcessing(bufferRef.current, id);
1566
- syncState();
1567
- },
1568
- [syncState]
1569
- );
1570
1457
  const resolveRequest = (0, import_react8.useCallback)(
1571
1458
  (id, result) => {
1572
- var _a;
1573
- const removed = dequeue(bufferRef.current, id);
1574
- if (!removed) return;
1575
- let outbound;
1576
- if (removed.kind === "transaction") {
1577
- outbound = sendOutbound({
1578
- type: "wallet:tx_complete",
1579
- sessionId,
1580
- payload: {
1581
- txHash: (_a = result.txHash) != null ? _a : "",
1582
- status: "success",
1583
- amount: result.amount
1584
- }
1585
- });
1586
- } else {
1587
- const eip712Payload = removed.payload;
1588
- outbound = sendOutbound({
1589
- type: "wallet_eip712_response",
1590
- sessionId,
1591
- payload: {
1592
- status: "success",
1593
- signature: result.signature,
1594
- description: eip712Payload.description
1595
- }
1596
- });
1459
+ const session = getSession();
1460
+ if (!session) {
1461
+ console.error("[wallet-handler] No session available to resolve request");
1462
+ return;
1597
1463
  }
1598
- outbound.then(() => onRequestComplete == null ? void 0 : onRequestComplete());
1599
- syncState();
1464
+ requestsRef.current = requestsRef.current.filter((r) => r.id !== id);
1465
+ setPendingRequests(requestsRef.current);
1466
+ void session.resolve(id, result).catch((err) => {
1467
+ console.error("[wallet-handler] Failed to resolve request:", err);
1468
+ });
1600
1469
  },
1601
- [sendOutbound, sessionId, syncState, onRequestComplete]
1470
+ [getSession]
1602
1471
  );
1603
1472
  const rejectRequest = (0, import_react8.useCallback)(
1604
1473
  (id, error) => {
1605
- const removed = dequeue(bufferRef.current, id);
1606
- if (!removed) return;
1607
- let outbound;
1608
- if (removed.kind === "transaction") {
1609
- outbound = sendOutbound({
1610
- type: "wallet:tx_complete",
1611
- sessionId,
1612
- payload: {
1613
- txHash: "",
1614
- status: "failed"
1615
- }
1616
- });
1617
- } else {
1618
- const eip712Payload = removed.payload;
1619
- outbound = sendOutbound({
1620
- type: "wallet_eip712_response",
1621
- sessionId,
1622
- payload: {
1623
- status: "failed",
1624
- error: error != null ? error : "EIP-712 signing failed",
1625
- description: eip712Payload.description
1626
- }
1627
- });
1474
+ const session = getSession();
1475
+ if (!session) {
1476
+ console.error("[wallet-handler] No session available to reject request");
1477
+ return;
1628
1478
  }
1629
- outbound.then(() => onRequestComplete == null ? void 0 : onRequestComplete());
1630
- syncState();
1479
+ requestsRef.current = requestsRef.current.filter((r) => r.id !== id);
1480
+ setPendingRequests(requestsRef.current);
1481
+ void session.reject(id, error).catch((err) => {
1482
+ console.error("[wallet-handler] Failed to reject request:", err);
1483
+ });
1631
1484
  },
1632
- [sendOutbound, sessionId, syncState, onRequestComplete]
1485
+ [getSession]
1633
1486
  );
1634
1487
  return {
1635
1488
  pendingRequests,
1636
- startProcessing: startProcessingCb,
1489
+ enqueueRequest,
1637
1490
  resolveRequest,
1638
1491
  rejectRequest
1639
1492
  };
@@ -1648,19 +1501,25 @@ function AomiRuntimeCore({
1648
1501
  const threadContext = useThreadContext();
1649
1502
  const eventContext = useEventContext();
1650
1503
  const notificationContext = useNotification();
1651
- const { dispatchInboundSystem: dispatchSystemEvents } = eventContext;
1652
1504
  const { user, onUserStateChange, getUserState } = useUser();
1653
1505
  const { getControlState, getCurrentThreadApp, clearSecrets } = useControl();
1506
+ const sessionManagerRef = (0, import_react9.useRef)(null);
1507
+ const walletHandler = useWalletHandler({
1508
+ getSession: () => {
1509
+ var _a;
1510
+ return (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadContext.currentThreadId);
1511
+ }
1512
+ });
1654
1513
  const {
1655
- backendStateRef,
1656
- polling,
1657
- messageController,
1514
+ sessionManager,
1515
+ getSession,
1658
1516
  isRunning,
1659
1517
  setIsRunning,
1660
1518
  ensureInitialState,
1519
+ sendMessage: orchestratorSendMessage,
1520
+ cancelGeneration: orchestratorCancel,
1661
1521
  aomiClientRef
1662
1522
  } = useRuntimeOrchestrator(aomiClient, {
1663
- onSyncEvents: dispatchSystemEvents,
1664
1523
  getPublicKey: () => getUserState().address,
1665
1524
  getUserState,
1666
1525
  getApp: getCurrentThreadApp,
@@ -1668,8 +1527,11 @@ function AomiRuntimeCore({
1668
1527
  getClientId: () => {
1669
1528
  var _a;
1670
1529
  return (_a = getControlState().clientId) != null ? _a : void 0;
1671
- }
1530
+ },
1531
+ onWalletRequest: (request) => walletHandler.enqueueRequest(request),
1532
+ onEvent: (event) => eventContext.dispatch(event)
1672
1533
  });
1534
+ sessionManagerRef.current = sessionManager;
1673
1535
  const walletSnapshot = (0, import_react9.useCallback)(
1674
1536
  (nextUser) => ({
1675
1537
  address: nextUser.address,
@@ -1710,13 +1572,6 @@ function AomiRuntimeCore({
1710
1572
  (0, import_react9.useEffect)(() => {
1711
1573
  currentThreadIdRef.current = threadContext.currentThreadId;
1712
1574
  }, [threadContext.currentThreadId]);
1713
- const onWalletRequestComplete = (0, import_react9.useCallback)(() => {
1714
- polling.start(currentThreadIdRef.current);
1715
- }, [polling]);
1716
- const walletHandler = useWalletHandler({
1717
- sessionId: threadContext.currentThreadId,
1718
- onRequestComplete: onWalletRequestComplete
1719
- });
1720
1575
  (0, import_react9.useEffect)(() => {
1721
1576
  const unsubscribe = eventContext.subscribe(
1722
1577
  "user_state_request",
@@ -1733,10 +1588,6 @@ function AomiRuntimeCore({
1733
1588
  (0, import_react9.useEffect)(() => {
1734
1589
  void ensureInitialState(threadContext.currentThreadId);
1735
1590
  }, [ensureInitialState, threadContext.currentThreadId]);
1736
- (0, import_react9.useEffect)(() => {
1737
- const threadId = threadContext.currentThreadId;
1738
- setIsRunning(isThreadRunning(backendStateRef.current, threadId));
1739
- }, [backendStateRef, setIsRunning, threadContext.currentThreadId]);
1740
1591
  (0, import_react9.useEffect)(() => {
1741
1592
  const threadId = threadContext.currentThreadId;
1742
1593
  const currentMeta = threadContext.getThreadMetadata(threadId);
@@ -1792,65 +1643,18 @@ function AomiRuntimeCore({
1792
1643
  }, [user.address, aomiClientRef]);
1793
1644
  const threadListAdapter = (0, import_react9.useMemo)(
1794
1645
  () => buildThreadListAdapter({
1795
- backendStateRef,
1796
1646
  aomiClientRef,
1797
1647
  threadContext,
1798
- currentThreadIdRef,
1799
- polling,
1800
- userAddress: user.address,
1801
- setIsRunning,
1802
- getApp: getCurrentThreadApp,
1803
- getApiKey: () => getControlState().apiKey,
1804
- getUserState
1648
+ setIsRunning
1805
1649
  }),
1806
1650
  [
1807
1651
  aomiClientRef,
1808
- polling,
1809
- user.address,
1810
- backendStateRef,
1811
1652
  setIsRunning,
1812
1653
  threadContext,
1813
1654
  threadContext.currentThreadId,
1814
- threadContext.allThreadsMetadata,
1815
- getControlState,
1816
- getCurrentThreadApp,
1817
- getUserState
1655
+ threadContext.allThreadsMetadata
1818
1656
  ]
1819
1657
  );
1820
- (0, import_react9.useEffect)(() => {
1821
- const backendState = backendStateRef.current;
1822
- const unsubscribe = eventContext.subscribe("title_changed", (event) => {
1823
- const sessionId = event.sessionId;
1824
- const payload = event.payload;
1825
- const newTitle = payload == null ? void 0 : payload.new_title;
1826
- if (typeof newTitle !== "string") return;
1827
- const targetThreadId = resolveThreadId(backendState, sessionId);
1828
- const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
1829
- if (process.env.NODE_ENV !== "production") {
1830
- console.debug("[aomi][sse] title_changed", {
1831
- sessionId,
1832
- newTitle,
1833
- normalizedTitle,
1834
- currentThreadId: threadContextRef.current.currentThreadId,
1835
- targetThreadId
1836
- });
1837
- }
1838
- threadContextRef.current.setThreadMetadata((prev) => {
1839
- var _a, _b;
1840
- const next = new Map(prev);
1841
- const existing = next.get(targetThreadId);
1842
- const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
1843
- next.set(targetThreadId, {
1844
- title: normalizedTitle,
1845
- status: nextStatus,
1846
- lastActiveAt: (_a = existing == null ? void 0 : existing.lastActiveAt) != null ? _a : (/* @__PURE__ */ new Date()).toISOString(),
1847
- control: (_b = existing == null ? void 0 : existing.control) != null ? _b : initThreadControl()
1848
- });
1849
- return next;
1850
- });
1851
- });
1852
- return unsubscribe;
1853
- }, [eventContext, backendStateRef]);
1854
1658
  (0, import_react9.useEffect)(() => {
1855
1659
  const showToolNotification = (eventType) => (event) => {
1856
1660
  const payload = event.payload;
@@ -1877,9 +1681,7 @@ function AomiRuntimeCore({
1877
1681
  };
1878
1682
  }, [eventContext, notificationContext]);
1879
1683
  (0, import_react9.useEffect)(() => {
1880
- const unsubscribe = eventContext.subscribe("system_notice", (event) => {
1881
- const payload = event.payload;
1882
- const message = payload == null ? void 0 : payload.message;
1684
+ const unsubscribe = eventContext.subscribe("system_notice", (_event) => {
1883
1685
  });
1884
1686
  return unsubscribe;
1885
1687
  }, [eventContext, notificationContext]);
@@ -1887,34 +1689,36 @@ function AomiRuntimeCore({
1887
1689
  messages: currentMessages,
1888
1690
  setMessages: (msgs) => threadContext.setThreadMessages(threadContext.currentThreadId, [...msgs]),
1889
1691
  isRunning,
1890
- onNew: (message) => messageController.outbound(message, threadContext.currentThreadId),
1891
- onCancel: () => messageController.cancel(threadContext.currentThreadId),
1692
+ onNew: async (message) => {
1693
+ const text = message.content.filter(
1694
+ (part) => part.type === "text"
1695
+ ).map((part) => part.text).join("\n");
1696
+ if (text) {
1697
+ await orchestratorSendMessage(text, threadContext.currentThreadId);
1698
+ }
1699
+ },
1700
+ onCancel: async () => {
1701
+ await orchestratorCancel(threadContext.currentThreadId);
1702
+ },
1892
1703
  convertMessage: (msg) => msg,
1893
1704
  adapters: { threadList: threadListAdapter }
1894
1705
  });
1895
1706
  (0, import_react9.useEffect)(() => {
1896
1707
  return () => {
1897
- polling.stopAll();
1708
+ sessionManager.closeAll();
1898
1709
  void clearSecrets();
1899
1710
  };
1900
- }, [polling, clearSecrets]);
1711
+ }, [sessionManager, clearSecrets]);
1901
1712
  const userContext = useUser();
1902
1713
  const sendMessage = (0, import_react9.useCallback)(
1903
1714
  async (text) => {
1904
- const appendMessage = {
1905
- role: "user",
1906
- content: [{ type: "text", text }]
1907
- };
1908
- await messageController.outbound(
1909
- appendMessage,
1910
- threadContext.currentThreadId
1911
- );
1715
+ await orchestratorSendMessage(text, threadContext.currentThreadId);
1912
1716
  },
1913
- [messageController, threadContext.currentThreadId]
1717
+ [orchestratorSendMessage, threadContext.currentThreadId]
1914
1718
  );
1915
1719
  const cancelGeneration = (0, import_react9.useCallback)(() => {
1916
- messageController.cancel(threadContext.currentThreadId);
1917
- }, [messageController, threadContext.currentThreadId]);
1720
+ void orchestratorCancel(threadContext.currentThreadId);
1721
+ }, [orchestratorCancel, threadContext.currentThreadId]);
1918
1722
  const getMessages = (0, import_react9.useCallback)(
1919
1723
  (threadId) => {
1920
1724
  const id = threadId != null ? threadId : threadContext.currentThreadId;
@@ -1928,9 +1732,10 @@ function AomiRuntimeCore({
1928
1732
  }, [threadListAdapter]);
1929
1733
  const deleteThread = (0, import_react9.useCallback)(
1930
1734
  async (threadId) => {
1735
+ sessionManager.close(threadId);
1931
1736
  await threadListAdapter.onDelete(threadId);
1932
1737
  },
1933
- [threadListAdapter]
1738
+ [threadListAdapter, sessionManager]
1934
1739
  );
1935
1740
  const renameThread = (0, import_react9.useCallback)(
1936
1741
  async (threadId, title) => {
@@ -1985,7 +1790,9 @@ function AomiRuntimeCore({
1985
1790
  clearAllNotifications: notificationContext.clearAll,
1986
1791
  // Wallet API
1987
1792
  pendingWalletRequests: walletHandler.pendingRequests,
1988
- startWalletRequest: walletHandler.startProcessing,
1793
+ startWalletRequest: () => {
1794
+ },
1795
+ // No-op: ClientSession manages processing state
1989
1796
  resolveWalletRequest: walletHandler.resolveRequest,
1990
1797
  rejectWalletRequest: walletHandler.rejectRequest,
1991
1798
  // Event API
@@ -2022,7 +1829,7 @@ function AomiRuntimeProvider({
2022
1829
  children,
2023
1830
  backendUrl = "http://localhost:8080"
2024
1831
  }) {
2025
- const aomiClient = (0, import_react11.useMemo)(() => new import_client3.AomiClient({ baseUrl: backendUrl }), [backendUrl]);
1832
+ const aomiClient = (0, import_react11.useMemo)(() => new import_client2.AomiClient({ baseUrl: backendUrl }), [backendUrl]);
2026
1833
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ThreadContextProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(NotificationContextProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(UserContextProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AomiRuntimeInner, { aomiClient, children }) }) }) });
2027
1834
  }
2028
1835
  function AomiRuntimeInner({
@@ -2061,10 +1868,10 @@ function generateNotificationId() {
2061
1868
  function useNotificationHandler({
2062
1869
  onNotification
2063
1870
  } = {}) {
2064
- const { subscribe: subscribe2 } = useEventContext();
1871
+ const { subscribe } = useEventContext();
2065
1872
  const [notifications, setNotifications] = (0, import_react12.useState)([]);
2066
1873
  (0, import_react12.useEffect)(() => {
2067
- const unsubscribe = subscribe2("notification", (event) => {
1874
+ const unsubscribe = subscribe("notification", (event) => {
2068
1875
  var _a, _b;
2069
1876
  const payload = event.payload;
2070
1877
  const notification = {
@@ -2073,14 +1880,14 @@ function useNotificationHandler({
2073
1880
  title: (_b = payload.title) != null ? _b : "Notification",
2074
1881
  body: payload.body,
2075
1882
  handled: false,
2076
- timestamp: event.timestamp,
1883
+ timestamp: Date.now(),
2077
1884
  sessionId: event.sessionId
2078
1885
  };
2079
1886
  setNotifications((prev) => [notification, ...prev]);
2080
1887
  onNotification == null ? void 0 : onNotification(notification);
2081
1888
  });
2082
1889
  return unsubscribe;
2083
- }, [subscribe2, onNotification]);
1890
+ }, [subscribe, onNotification]);
2084
1891
  const unhandledCount = notifications.filter((n) => !n.handled).length;
2085
1892
  const markHandled = (0, import_react12.useCallback)((id) => {
2086
1893
  setNotifications(