@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.2 → 0.4.4

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.
@@ -0,0 +1,9 @@
1
+ export declare class AuthorizationLoadingState {
2
+ private aitLoading;
3
+ private autoConnecting;
4
+ constructor(aitLoading?: boolean, autoConnecting?: boolean);
5
+ setAITLoading(value: boolean): void;
6
+ setAutoConnecting(value: boolean): void;
7
+ isLoading(): boolean;
8
+ }
9
+ //# sourceMappingURL=authorizationLoadingState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorizationLoadingState.d.ts","sourceRoot":"","sources":["../src/authorizationLoadingState.ts"],"names":[],"mappings":"AAAA,qBAAa,yBAAyB;IACpC,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,cAAc,CAAU;gBAEpB,UAAU,UAAQ,EAAE,cAAc,UAAQ;IAKtD,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAInC,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAIvC,SAAS,IAAI,OAAO;CAGrB"}
@@ -0,0 +1,15 @@
1
+ export class AuthorizationLoadingState {
2
+ constructor(aitLoading = false, autoConnecting = false) {
3
+ this.aitLoading = aitLoading;
4
+ this.autoConnecting = autoConnecting;
5
+ }
6
+ setAITLoading(value) {
7
+ this.aitLoading = value;
8
+ }
9
+ setAutoConnecting(value) {
10
+ this.autoConnecting = value;
11
+ }
12
+ isLoading() {
13
+ return this.aitLoading || this.autoConnecting;
14
+ }
15
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA6B/B,OAAO,EAEL,kBAAkB,EAClB,YAAY,EAEb,MAAM,uBAAuB,CAAC;AAM/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CA25FlD,CAAC"}
1
+ {"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA8B/B,OAAO,EAEL,kBAAkB,EAClB,YAAY,EAEb,MAAM,uBAAuB,CAAC;AAQ/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAw+FlD,CAAC"}
@@ -5,7 +5,9 @@ import * as React from 'react';
5
5
  import { flushSync } from 'react-dom';
6
6
  import { v4 as uuidv4 } from 'uuid';
7
7
  import { createNxtlinqApi, setApiHosts, synthesizeSpeechToBuffer, streamSpeechToAudioContext, useLocalStorage, useSessionStorage, useSpeechToTextFromMic, useVoiceMode, metakeepClient, getEthers, sleep, walletTextUtils, } from '@bytexbyte/nxtlinq-ai-agent-web-development';
8
- import { authorizeTextFrontendTool, hasRecordedWalletVerification, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
8
+ import { authorizeTextFrontendTool, hasRecordedWalletVerification, validateWalletSession, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
9
+ import { AuthorizationLoadingState } from '../authorizationLoadingState';
10
+ import { PendingTextToolRetryController } from '../pendingTextToolRetry';
9
11
  const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
10
12
  const ChatBotContext = React.createContext(undefined);
11
13
  export const useChatBot = () => {
@@ -144,6 +146,18 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
144
146
  const permissionsRef = React.useRef(permissions);
145
147
  const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
146
148
  const signerRef = React.useRef(signer);
149
+ const authorizationLoadingStateRef = React.useRef(new AuthorizationLoadingState(isAITLoading, isAutoConnecting));
150
+ const pendingTextToolRetryRef = React.useRef(new PendingTextToolRetryController());
151
+ const sendMessageRef = React.useRef(async () => { });
152
+ const retryPendingTextTool = React.useCallback(() => {
153
+ const controller = pendingTextToolRetryRef.current;
154
+ return controller.retry(async (request) => {
155
+ // This attempt consumes the old request. A still-blocked preflight will
156
+ // record it again with the next required recovery step.
157
+ controller.clear();
158
+ await sendMessageRef.current(request.content, 1, request.isPresetMessage, request.attachments);
159
+ });
160
+ }, []);
147
161
  // Helper function to combine customUsername with Adilas customUserInfo
148
162
  const getFinalCustomUsername = React.useCallback((username) => {
149
163
  if (!username)
@@ -471,6 +485,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
471
485
  React.useEffect(() => {
472
486
  permissionsRef.current = permissions;
473
487
  }, [permissions]);
488
+ React.useEffect(() => {
489
+ authorizationLoadingStateRef.current.setAITLoading(isAITLoading);
490
+ }, [isAITLoading]);
491
+ React.useEffect(() => {
492
+ authorizationLoadingStateRef.current.setAutoConnecting(isAutoConnecting);
493
+ }, [isAutoConnecting]);
474
494
  React.useEffect(() => {
475
495
  nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
476
496
  }, [nxtlinqAITServiceAccessToken]);
@@ -860,20 +880,26 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
860
880
  };
861
881
  // Refresh AIT
862
882
  const refreshAIT = async (forceUpdatePermissions = false) => {
863
- if (!hitAddress) {
883
+ const currentHitAddress = hitAddressRef.current || hitAddress;
884
+ const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
885
+ if (!currentHitAddress) {
886
+ aitRef.current = null;
887
+ permissionsRef.current = [];
864
888
  setAit(null);
865
889
  setPermissions([]);
866
890
  setWalletInfo(null);
891
+ authorizationLoadingStateRef.current.setAITLoading(false);
867
892
  setIsAITLoading(false);
868
893
  return;
869
894
  }
895
+ authorizationLoadingStateRef.current.setAITLoading(true);
870
896
  setIsAITLoading(true);
871
897
  try {
872
898
  let walletAllowsAITLookup = !requireWalletIDVVerification;
873
899
  // Get wallet info first - always try to get wallet info if we have a token
874
- if (nxtlinqAITServiceAccessToken) {
900
+ if (currentToken) {
875
901
  try {
876
- const walletResponse = await nxtlinqApi.wallet.getWallet({ address: hitAddress }, nxtlinqAITServiceAccessToken);
902
+ const walletResponse = await nxtlinqApi.wallet.getWallet({ address: currentHitAddress }, currentToken);
877
903
  if (!('error' in walletResponse)) {
878
904
  setWalletInfo(walletResponse);
879
905
  walletAllowsAITLookup = !requireWalletIDVVerification
@@ -895,42 +921,55 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
895
921
  }
896
922
  // AIT existence is not identity verification. In strict mode, stop before
897
923
  // the legacy AIT lookup can synthesize a custom wallet record.
898
- if (nxtlinqAITServiceAccessToken && !walletAllowsAITLookup) {
924
+ if (currentToken && !walletAllowsAITLookup) {
925
+ aitRef.current = null;
926
+ permissionsRef.current = [];
899
927
  setAit(null);
900
928
  setPermissions([]);
901
929
  return;
902
930
  }
903
931
  // Only try to fetch AIT if we have a token
904
- if (nxtlinqAITServiceAccessToken) {
932
+ if (currentToken) {
905
933
  const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
906
934
  serviceId,
907
- controller: hitAddress,
935
+ controller: currentHitAddress,
908
936
  customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
909
- }, nxtlinqAITServiceAccessToken);
937
+ }, currentToken);
910
938
  if ('error' in response) {
911
939
  console.error('Failed to fetch AIT:', response.error);
940
+ aitRef.current = null;
941
+ permissionsRef.current = [];
912
942
  setAit(null);
913
943
  setPermissions([]);
914
944
  return;
915
945
  }
946
+ aitRef.current = response;
916
947
  setAit(response);
917
948
  if (!isPermissionFormOpen || forceUpdatePermissions) {
918
949
  const newPermissions = response.metadata?.permissions || [];
950
+ permissionsRef.current = newPermissions;
919
951
  setPermissions(newPermissions);
920
952
  }
921
953
  }
922
954
  else {
923
955
  // No token available, clear AIT data
956
+ aitRef.current = null;
957
+ permissionsRef.current = [];
924
958
  setAit(null);
925
959
  setPermissions([]);
926
960
  }
927
961
  }
928
962
  catch (error) {
929
963
  console.error('Failed to fetch AIT:', error);
964
+ aitRef.current = null;
965
+ permissionsRef.current = [];
930
966
  setAit(null);
931
967
  setPermissions([]);
932
968
  }
933
969
  finally {
970
+ // Keep the authorization snapshot current before refreshAIT resolves.
971
+ // React may not commit setState until the immediate pending-tool retry.
972
+ authorizationLoadingStateRef.current.setAITLoading(false);
934
973
  setIsAITLoading(false);
935
974
  }
936
975
  };
@@ -940,17 +979,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
940
979
  return false;
941
980
  if (!nxtlinqAITServiceAccessToken)
942
981
  return true;
943
- try {
944
- const payload = JSON.parse(atob(nxtlinqAITServiceAccessToken.split('.')[1]));
945
- const address = payload.address;
946
- if (address !== hitAddress)
947
- return true;
948
- return false;
949
- }
950
- catch (error) {
951
- console.error('Error parsing token:', error);
952
- return true;
953
- }
982
+ return !validateWalletSession(nxtlinqAITServiceAccessToken, hitAddress).valid;
954
983
  }, [hitAddress, nxtlinqAITServiceAccessToken]);
955
984
  // Connect wallet
956
985
  const connectWallet = React.useCallback(async (autoShowSignInMessage = true) => {
@@ -1080,14 +1109,19 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1080
1109
  if (autoShowSuccessMessage) {
1081
1110
  showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
1082
1111
  }
1083
- return;
1112
+ return false;
1084
1113
  }
1085
1114
  if (!signerToUse) {
1086
1115
  console.log('No signer available, returning early');
1087
1116
  if (autoShowSuccessMessage) {
1088
1117
  showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
1089
1118
  }
1090
- return;
1119
+ return false;
1120
+ }
1121
+ const currentToken = nxtlinqAITServiceAccessTokenRef.current;
1122
+ if (currentToken && validateWalletSession(currentToken, addressToUse).valid) {
1123
+ await refreshAIT();
1124
+ return true;
1091
1125
  }
1092
1126
  try {
1093
1127
  const nonceResponse = await nxtlinqApi.auth.getNonce({ address: addressToUse });
@@ -1095,7 +1129,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1095
1129
  if (autoShowSuccessMessage) {
1096
1130
  showError(normalizeErrorForUser(nonceResponse.error));
1097
1131
  }
1098
- return;
1132
+ return false;
1099
1133
  }
1100
1134
  const payload = {
1101
1135
  address: addressToUse,
@@ -1112,22 +1146,25 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1112
1146
  if (autoShowSuccessMessage) {
1113
1147
  showError(normalizeErrorForUser(response.error));
1114
1148
  }
1115
- return;
1149
+ return false;
1116
1150
  }
1117
1151
  const { accessToken } = response;
1118
1152
  setNxtlinqAITServiceAccessToken(accessToken);
1153
+ nxtlinqAITServiceAccessTokenRef.current = accessToken;
1119
1154
  // Wait for state to update before refreshing AIT
1120
1155
  await new Promise(resolve => setTimeout(resolve, 100));
1121
1156
  await refreshAIT();
1122
1157
  if (autoShowSuccessMessage) {
1123
1158
  showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
1124
1159
  }
1160
+ return true;
1125
1161
  }
1126
1162
  catch (error) {
1127
1163
  console.error('Failed to sign in:', error);
1128
1164
  if (autoShowSuccessMessage) {
1129
1165
  showError('Failed to sign in. Please try again.');
1130
1166
  }
1167
+ return false;
1131
1168
  }
1132
1169
  };
1133
1170
  // Check permissions
@@ -1146,7 +1183,9 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1146
1183
  // wallet-address subject until issuance/migration supports it.
1147
1184
  externalId: undefined,
1148
1185
  requireWalletIDVVerification,
1149
- loading: isAITLoading || isAutoConnecting,
1186
+ // Use synchronously maintained refs so a retry immediately following
1187
+ // refreshAIT() does not observe the previous React render's loading state.
1188
+ loading: authorizationLoadingStateRef.current.isLoading(),
1150
1189
  },
1151
1190
  ...authFields(),
1152
1191
  customUsername: (!requireWalletIDVVerification && customUsername)
@@ -1178,6 +1217,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1178
1217
  case 'wallet_verification_required':
1179
1218
  recovery.content = 'Please verify your wallet identity before using this tool.';
1180
1219
  recovery.button = 'verifyWallet';
1220
+ setIsPermissionFormOpen(true);
1221
+ setShowPermissionForm(true);
1181
1222
  break;
1182
1223
  case 'ait_not_found':
1183
1224
  recovery.content = 'No AIT found for your wallet. Please open settings to configure your AIT.';
@@ -1230,8 +1271,14 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1230
1271
  // Updated sendMessage function to support different AI models and attachments
1231
1272
  const sendMessage = async (content, retryCount = 0, isPresetMessage = false, attachments, clientPipelineOverride) => {
1232
1273
  const hasContent = content.trim() || (attachments && attachments.length > 0);
1233
- if (!hasContent || isLoading)
1274
+ // Recovery retries may run immediately after a completion handler calls
1275
+ // setIsLoading(false), before React has committed that state update.
1276
+ if (!hasContent || (retryCount === 0 && isLoading))
1234
1277
  return;
1278
+ if (retryCount === 0) {
1279
+ // A new user turn supersedes any older interrupted tool request.
1280
+ pendingTextToolRetryRef.current.clear();
1281
+ }
1235
1282
  setPendingAutoTts(null);
1236
1283
  const turnExternalId = typeof window !== 'undefined'
1237
1284
  ? localStorage.getItem('walletAddress') || undefined
@@ -1524,9 +1571,17 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1524
1571
  const isToolAllowed = await hasPermission(toolUse.name);
1525
1572
  // Typed preflight already emitted the only permitted recovery UI.
1526
1573
  if (!isToolAllowed) {
1574
+ pendingTextToolRetryRef.current.set({
1575
+ content,
1576
+ isPresetMessage,
1577
+ attachments,
1578
+ });
1527
1579
  setIsLoading(false);
1528
1580
  return;
1529
1581
  }
1582
+ // Authorization passed. Clear before invoking the host callback so
1583
+ // concurrent completion events cannot execute this tool twice.
1584
+ pendingTextToolRetryRef.current.clear();
1530
1585
  // Create streaming message for tool execution
1531
1586
  const streamingMessageId = `streaming-${Date.now()}`;
1532
1587
  const streamingMessage = {
@@ -1819,6 +1874,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1819
1874
  setIsLoading(false);
1820
1875
  }
1821
1876
  };
1877
+ sendMessageRef.current = sendMessage;
1822
1878
  // Handle submit
1823
1879
  const handleSubmit = async (e, attachments) => {
1824
1880
  e.preventDefault();
@@ -2194,8 +2250,11 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2194
2250
  metadataHash,
2195
2251
  metadataCid,
2196
2252
  };
2253
+ const nextPermissions = newPermissions || permissions;
2254
+ aitRef.current = aitInfo;
2255
+ permissionsRef.current = nextPermissions;
2197
2256
  setAit(aitInfo);
2198
- setPermissions(newPermissions || permissions);
2257
+ setPermissions(nextPermissions);
2199
2258
  };
2200
2259
  // Auto enable AIT permission
2201
2260
  const enableAIT = async (toolName) => {
@@ -2308,6 +2367,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2308
2367
  setShowPermissionForm(false);
2309
2368
  setIsPermissionFormOpen(false);
2310
2369
  await refreshAIT(true);
2370
+ await retryPendingTextTool();
2311
2371
  }
2312
2372
  catch (error) {
2313
2373
  console.error('Failed to generate AIT:', error);
@@ -2364,6 +2424,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2364
2424
  customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
2365
2425
  }, token);
2366
2426
  if (!('error' in aitResponse)) {
2427
+ aitRef.current = aitResponse;
2367
2428
  setAit(aitResponse);
2368
2429
  }
2369
2430
  }
@@ -2386,7 +2447,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2386
2447
  }
2387
2448
  setIsLoading(false);
2388
2449
  showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
2389
- refreshAIT();
2450
+ await refreshAIT();
2451
+ await retryPendingTextTool();
2390
2452
  return;
2391
2453
  }
2392
2454
  showError(verifyWalletResponse.error);
@@ -2404,6 +2466,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2404
2466
  customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
2405
2467
  }, token);
2406
2468
  if (!('error' in aitResponse)) {
2469
+ aitRef.current = aitResponse;
2407
2470
  setAit(aitResponse);
2408
2471
  }
2409
2472
  }
@@ -2426,7 +2489,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2426
2489
  }
2427
2490
  setIsLoading(false);
2428
2491
  showSuccess('Wallet verification completed successfully! Your wallet is now verified and ready to use.');
2429
- refreshAIT();
2492
+ await refreshAIT();
2493
+ await retryPendingTextTool();
2430
2494
  return;
2431
2495
  }
2432
2496
  catch (error) {
@@ -2466,6 +2530,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2466
2530
  await refreshAIT();
2467
2531
  setIsLoading(false);
2468
2532
  showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
2533
+ await retryPendingTextTool();
2469
2534
  return;
2470
2535
  }
2471
2536
  // Handle specific error messages
@@ -2482,6 +2547,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2482
2547
  await refreshAIT();
2483
2548
  setIsLoading(false);
2484
2549
  showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
2550
+ await retryPendingTextTool();
2485
2551
  return;
2486
2552
  }
2487
2553
  catch (error) {
@@ -2518,6 +2584,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2518
2584
  // Set loading state when permission form opens
2519
2585
  React.useEffect(() => {
2520
2586
  if (isPermissionFormOpen && hitAddress) {
2587
+ authorizationLoadingStateRef.current.setAITLoading(true);
2521
2588
  setIsAITLoading(true);
2522
2589
  }
2523
2590
  }, [isPermissionFormOpen, hitAddress]);
@@ -2650,6 +2717,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2650
2717
  // Functions
2651
2718
  connectWallet,
2652
2719
  signInWallet,
2720
+ retryPendingTextTool,
2653
2721
  sendMessage,
2654
2722
  handleSubmit,
2655
2723
  handlePresetMessage,
@@ -0,0 +1,16 @@
1
+ import type { Attachment } from '@bytexbyte/nxtlinq-ai-agent-core-development';
2
+ export type PendingTextToolRequest = {
3
+ content: string;
4
+ isPresetMessage: boolean;
5
+ attachments?: Attachment[];
6
+ };
7
+ /** Keeps one interrupted text-tool request and serializes all resume attempts. */
8
+ export declare class PendingTextToolRetryController {
9
+ private pending;
10
+ private inFlight;
11
+ set(request: PendingTextToolRequest): void;
12
+ clear(): void;
13
+ hasPending(): boolean;
14
+ retry(execute: (request: PendingTextToolRequest) => Promise<void>): Promise<boolean>;
15
+ }
16
+ //# sourceMappingURL=pendingTextToolRetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pendingTextToolRetry.d.ts","sourceRoot":"","sources":["../src/pendingTextToolRetry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8CAA8C,CAAC;AAE/E,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;CAC5B,CAAC;AAEF,kFAAkF;AAClF,qBAAa,8BAA8B;IACzC,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,QAAQ,CAAiC;IAEjD,GAAG,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI;IAI1C,KAAK,IAAI,IAAI;IAIb,UAAU,IAAI,OAAO;IAIrB,KAAK,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAarF"}
@@ -0,0 +1,30 @@
1
+ /** Keeps one interrupted text-tool request and serializes all resume attempts. */
2
+ export class PendingTextToolRetryController {
3
+ constructor() {
4
+ this.pending = null;
5
+ this.inFlight = null;
6
+ }
7
+ set(request) {
8
+ this.pending = request;
9
+ }
10
+ clear() {
11
+ this.pending = null;
12
+ }
13
+ hasPending() {
14
+ return this.pending !== null;
15
+ }
16
+ retry(execute) {
17
+ if (this.inFlight)
18
+ return this.inFlight;
19
+ if (!this.pending)
20
+ return Promise.resolve(false);
21
+ const request = this.pending;
22
+ this.inFlight = (async () => {
23
+ await execute(request);
24
+ return true;
25
+ })().finally(() => {
26
+ this.inFlight = null;
27
+ });
28
+ return this.inFlight;
29
+ }
30
+ }
@@ -134,8 +134,9 @@ export interface ChatBotContextType {
134
134
  setAutoSendEnabled: (enabled: boolean) => void;
135
135
  setTextToSpeechEnabled: (enabled: boolean) => void;
136
136
  connectWallet: (autoShowSignInMessage?: boolean) => Promise<string | false | undefined>;
137
- signInWallet: (autoShowSuccessMessage?: boolean) => Promise<void>;
138
- sendMessage: (content: string, retryCount?: number) => Promise<void>;
137
+ signInWallet: (autoShowSuccessMessage?: boolean) => Promise<boolean>;
138
+ retryPendingTextTool: () => Promise<boolean>;
139
+ sendMessage: (content: string, retryCount?: number, isPresetMessage?: boolean, attachments?: Attachment[]) => Promise<void>;
139
140
  handleSubmit: (e: React.FormEvent, attachments?: Attachment[]) => Promise<void>;
140
141
  handlePresetMessage: (message: PresetMessage) => void;
141
142
  savePermissions: (newPermissions?: string[]) => Promise<void>;
@@ -165,7 +166,7 @@ export interface ChatBotContextType {
165
166
  interruptVoice: () => void;
166
167
  onSave: (newPermissions?: string[]) => Promise<void>;
167
168
  onConnectWallet: () => Promise<string | false | undefined>;
168
- onSignIn: () => Promise<void>;
169
+ onSignIn: () => Promise<boolean>;
169
170
  isNeedSignInWithWallet: boolean;
170
171
  onVerifyWallet: (method: 'berifyme' | 'custom') => Promise<void>;
171
172
  serviceId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ChatBotTypes.d.ts","sourceRoot":"","sources":["../../src/types/ChatBotTypes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChatBotTypes.d.ts","sourceRoot":"","sources":["../../src/types/ChatBotTypes.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/ui/MessageList.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAuT/B,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAyc/B,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/ui/MessageList.tsx"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAuT/B,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EA4c/B,CAAC"}
@@ -231,7 +231,7 @@ const PiiStatusHeader = React.memo(({ message }) => {
231
231
  return (_jsxs("div", { css: piiHeaderRow, children: [_jsx(ShieldIcon, {}), _jsx("span", { children: "\u2713 Protected" })] }));
232
232
  });
233
233
  export const MessageList = () => {
234
- const { messages, isLoading, isTtsProcessing, requiresGesture, retryTtsWithGesture, connectWallet, signInWallet, hitAddress, isAutoConnecting, isNeedSignInWithWallet, enableAIT, isAITLoading, isAITEnabling, sendMessage, permissions, availableModels, serviceId, piiDisplayMode, setShowPermissionForm, setIsPermissionFormOpen, } = useChatBot();
234
+ const { messages, isLoading, isTtsProcessing, requiresGesture, retryTtsWithGesture, connectWallet, signInWallet, retryPendingTextTool, hitAddress, isAutoConnecting, isNeedSignInWithWallet, enableAIT, isAITLoading, isAITEnabling, sendMessage, permissions, availableModels, serviceId, piiDisplayMode, setShowPermissionForm, setIsPermissionFormOpen, } = useChatBot();
235
235
  const messagesEndRef = React.useRef(null);
236
236
  const scrollToBottom = () => {
237
237
  messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
@@ -247,22 +247,26 @@ export const MessageList = () => {
247
247
  return;
248
248
  }
249
249
  if (buttonType === 'connectWallet') {
250
- connectWallet(true);
250
+ const connected = await connectWallet(false);
251
+ if (connected) {
252
+ // Continue the interrupted flow immediately. This opens the wallet
253
+ // signature UI instead of leaving the user on a stale connect prompt.
254
+ const signedIn = await signInWallet(true);
255
+ if (signedIn)
256
+ await retryPendingTextTool();
257
+ }
251
258
  }
252
259
  else if (buttonType === 'signIn') {
253
- signInWallet(true);
260
+ const signedIn = await signInWallet(true);
261
+ if (signedIn)
262
+ await retryPendingTextTool();
254
263
  }
255
264
  else if (buttonType === 'enableAIT') {
256
265
  const requiredPermission = message.metadata?.requiredPermission;
257
266
  if (requiredPermission) {
258
267
  const success = await enableAIT(requiredPermission);
259
268
  if (success) {
260
- // Find the last user message
261
- const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
262
- if (lastUserMsg && lastUserMsg.content) {
263
- // Re-execute the previous user message (retryCount=1 to skip duplicate PII log)
264
- await sendMessage(lastUserMsg.content, 1);
265
- }
269
+ await retryPendingTextTool();
266
270
  }
267
271
  }
268
272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytexbyte/nxtlinq-ai-agent-ui-react-development",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Official React Web UI for nxtlinq AI Agent — drop-in chat widget",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,21 @@
1
+ export class AuthorizationLoadingState {
2
+ private aitLoading: boolean;
3
+ private autoConnecting: boolean;
4
+
5
+ constructor(aitLoading = false, autoConnecting = false) {
6
+ this.aitLoading = aitLoading;
7
+ this.autoConnecting = autoConnecting;
8
+ }
9
+
10
+ setAITLoading(value: boolean): void {
11
+ this.aitLoading = value;
12
+ }
13
+
14
+ setAutoConnecting(value: boolean): void {
15
+ this.autoConnecting = value;
16
+ }
17
+
18
+ isLoading(): boolean {
19
+ return this.aitLoading || this.autoConnecting;
20
+ }
21
+ }
@@ -28,6 +28,7 @@ import type {
28
28
  import {
29
29
  authorizeTextFrontendTool,
30
30
  hasRecordedWalletVerification,
31
+ validateWalletSession,
31
32
  } from '@bytexbyte/nxtlinq-ai-agent-core-development';
32
33
  import {
33
34
  AIModel,
@@ -35,6 +36,8 @@ import {
35
36
  ChatBotProps,
36
37
  PresetMessage
37
38
  } from '../types/ChatBotTypes';
39
+ import { AuthorizationLoadingState } from '../authorizationLoadingState';
40
+ import { PendingTextToolRetryController } from '../pendingTextToolRetry';
38
41
 
39
42
  const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
40
43
 
@@ -216,6 +219,25 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
216
219
  const permissionsRef = React.useRef(permissions);
217
220
  const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
218
221
  const signerRef = React.useRef(signer);
222
+ const authorizationLoadingStateRef = React.useRef(
223
+ new AuthorizationLoadingState(isAITLoading, isAutoConnecting),
224
+ );
225
+ const pendingTextToolRetryRef = React.useRef(new PendingTextToolRetryController());
226
+ const sendMessageRef = React.useRef<ChatBotContextType['sendMessage']>(async () => {});
227
+ const retryPendingTextTool = React.useCallback(() => {
228
+ const controller = pendingTextToolRetryRef.current;
229
+ return controller.retry(async (request) => {
230
+ // This attempt consumes the old request. A still-blocked preflight will
231
+ // record it again with the next required recovery step.
232
+ controller.clear();
233
+ await sendMessageRef.current(
234
+ request.content,
235
+ 1,
236
+ request.isPresetMessage,
237
+ request.attachments,
238
+ );
239
+ });
240
+ }, []);
219
241
 
220
242
  // Helper function to combine customUsername with Adilas customUserInfo
221
243
  const getFinalCustomUsername = React.useCallback((username: string | undefined): string | undefined => {
@@ -560,6 +582,14 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
560
582
  permissionsRef.current = permissions;
561
583
  }, [permissions]);
562
584
 
585
+ React.useEffect(() => {
586
+ authorizationLoadingStateRef.current.setAITLoading(isAITLoading);
587
+ }, [isAITLoading]);
588
+
589
+ React.useEffect(() => {
590
+ authorizationLoadingStateRef.current.setAutoConnecting(isAutoConnecting);
591
+ }, [isAutoConnecting]);
592
+
563
593
  React.useEffect(() => {
564
594
  nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
565
595
  }, [nxtlinqAITServiceAccessToken]);
@@ -1000,22 +1030,28 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1000
1030
 
1001
1031
  // Refresh AIT
1002
1032
  const refreshAIT = async (forceUpdatePermissions = false) => {
1003
- if (!hitAddress) {
1033
+ const currentHitAddress = hitAddressRef.current || hitAddress;
1034
+ const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
1035
+ if (!currentHitAddress) {
1036
+ aitRef.current = null;
1037
+ permissionsRef.current = [];
1004
1038
  setAit(null);
1005
1039
  setPermissions([]);
1006
1040
  setWalletInfo(null);
1041
+ authorizationLoadingStateRef.current.setAITLoading(false);
1007
1042
  setIsAITLoading(false);
1008
1043
  return;
1009
1044
  }
1010
1045
 
1046
+ authorizationLoadingStateRef.current.setAITLoading(true);
1011
1047
  setIsAITLoading(true);
1012
1048
  try {
1013
1049
  let walletAllowsAITLookup = !requireWalletIDVVerification;
1014
1050
 
1015
1051
  // Get wallet info first - always try to get wallet info if we have a token
1016
- if (nxtlinqAITServiceAccessToken) {
1052
+ if (currentToken) {
1017
1053
  try {
1018
- const walletResponse = await nxtlinqApi.wallet.getWallet({ address: hitAddress }, nxtlinqAITServiceAccessToken);
1054
+ const walletResponse = await nxtlinqApi.wallet.getWallet({ address: currentHitAddress }, currentToken);
1019
1055
  if (!('error' in walletResponse)) {
1020
1056
  setWalletInfo(walletResponse);
1021
1057
  walletAllowsAITLookup = !requireWalletIDVVerification
@@ -1036,42 +1072,55 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1036
1072
 
1037
1073
  // AIT existence is not identity verification. In strict mode, stop before
1038
1074
  // the legacy AIT lookup can synthesize a custom wallet record.
1039
- if (nxtlinqAITServiceAccessToken && !walletAllowsAITLookup) {
1075
+ if (currentToken && !walletAllowsAITLookup) {
1076
+ aitRef.current = null;
1077
+ permissionsRef.current = [];
1040
1078
  setAit(null);
1041
1079
  setPermissions([]);
1042
1080
  return;
1043
1081
  }
1044
1082
 
1045
1083
  // Only try to fetch AIT if we have a token
1046
- if (nxtlinqAITServiceAccessToken) {
1084
+ if (currentToken) {
1047
1085
  const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
1048
1086
  serviceId,
1049
- controller: hitAddress,
1087
+ controller: currentHitAddress,
1050
1088
  customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
1051
- }, nxtlinqAITServiceAccessToken);
1089
+ }, currentToken);
1052
1090
 
1053
1091
  if ('error' in response) {
1054
1092
  console.error('Failed to fetch AIT:', response.error);
1093
+ aitRef.current = null;
1094
+ permissionsRef.current = [];
1055
1095
  setAit(null);
1056
1096
  setPermissions([]);
1057
1097
  return;
1058
1098
  }
1059
1099
 
1100
+ aitRef.current = response;
1060
1101
  setAit(response);
1061
1102
  if (!isPermissionFormOpen || forceUpdatePermissions) {
1062
1103
  const newPermissions = response.metadata?.permissions || [];
1104
+ permissionsRef.current = newPermissions;
1063
1105
  setPermissions(newPermissions);
1064
1106
  }
1065
1107
  } else {
1066
1108
  // No token available, clear AIT data
1109
+ aitRef.current = null;
1110
+ permissionsRef.current = [];
1067
1111
  setAit(null);
1068
1112
  setPermissions([]);
1069
1113
  }
1070
1114
  } catch (error) {
1071
1115
  console.error('Failed to fetch AIT:', error);
1116
+ aitRef.current = null;
1117
+ permissionsRef.current = [];
1072
1118
  setAit(null);
1073
1119
  setPermissions([]);
1074
1120
  } finally {
1121
+ // Keep the authorization snapshot current before refreshAIT resolves.
1122
+ // React may not commit setState until the immediate pending-tool retry.
1123
+ authorizationLoadingStateRef.current.setAITLoading(false);
1075
1124
  setIsAITLoading(false);
1076
1125
  }
1077
1126
  };
@@ -1080,17 +1129,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1080
1129
  const isNeedSignInWithWallet = React.useMemo(() => {
1081
1130
  if (!hitAddress) return false;
1082
1131
  if (!nxtlinqAITServiceAccessToken) return true;
1083
-
1084
- try {
1085
- const payload = JSON.parse(atob(nxtlinqAITServiceAccessToken.split('.')[1]));
1086
- const address = payload.address;
1087
- if (address !== hitAddress) return true;
1088
-
1089
- return false;
1090
- } catch (error) {
1091
- console.error('Error parsing token:', error);
1092
- return true;
1093
- }
1132
+ return !validateWalletSession(nxtlinqAITServiceAccessToken, hitAddress).valid;
1094
1133
  }, [hitAddress, nxtlinqAITServiceAccessToken]);
1095
1134
 
1096
1135
  // Connect wallet
@@ -1200,7 +1239,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1200
1239
  }
1201
1240
  }, [nxtlinqAITServiceAccessToken, refreshAIT, isNeedSignInWithWallet, requireWalletIDVVerification, customUsername, nxtlinqApi]);
1202
1241
 
1203
- const signInWallet = async (autoShowSuccessMessage = true) => {
1242
+ const signInWallet = async (autoShowSuccessMessage = true): Promise<boolean> => {
1204
1243
  // Use refs to get latest state values for consistency with hasPermission
1205
1244
  const currentHitAddress = hitAddressRef.current;
1206
1245
  const currentSigner = signerRef.current;
@@ -1233,7 +1272,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1233
1272
  if (autoShowSuccessMessage) {
1234
1273
  showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
1235
1274
  }
1236
- return;
1275
+ return false;
1237
1276
  }
1238
1277
 
1239
1278
  if (!signerToUse) {
@@ -1241,7 +1280,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1241
1280
  if (autoShowSuccessMessage) {
1242
1281
  showError(walletTextUtils.getWalletText('Please connect your wallet first.', serviceId));
1243
1282
  }
1244
- return;
1283
+ return false;
1284
+ }
1285
+
1286
+ const currentToken = nxtlinqAITServiceAccessTokenRef.current;
1287
+ if (currentToken && validateWalletSession(currentToken, addressToUse).valid) {
1288
+ await refreshAIT();
1289
+ return true;
1245
1290
  }
1246
1291
 
1247
1292
  try {
@@ -1250,7 +1295,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1250
1295
  if (autoShowSuccessMessage) {
1251
1296
  showError(normalizeErrorForUser(nonceResponse.error));
1252
1297
  }
1253
- return;
1298
+ return false;
1254
1299
  }
1255
1300
 
1256
1301
  const payload = {
@@ -1271,10 +1316,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1271
1316
  if (autoShowSuccessMessage) {
1272
1317
  showError(normalizeErrorForUser(response.error));
1273
1318
  }
1274
- return;
1319
+ return false;
1275
1320
  }
1276
1321
  const { accessToken } = response;
1277
1322
  setNxtlinqAITServiceAccessToken(accessToken);
1323
+ nxtlinqAITServiceAccessTokenRef.current = accessToken;
1278
1324
 
1279
1325
  // Wait for state to update before refreshing AIT
1280
1326
  await new Promise(resolve => setTimeout(resolve, 100));
@@ -1283,11 +1329,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1283
1329
  if (autoShowSuccessMessage) {
1284
1330
  showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
1285
1331
  }
1332
+ return true;
1286
1333
  } catch (error) {
1287
1334
  console.error('Failed to sign in:', error);
1288
1335
  if (autoShowSuccessMessage) {
1289
1336
  showError('Failed to sign in. Please try again.');
1290
1337
  }
1338
+ return false;
1291
1339
  }
1292
1340
  };
1293
1341
 
@@ -1310,7 +1358,9 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1310
1358
  // wallet-address subject until issuance/migration supports it.
1311
1359
  externalId: undefined,
1312
1360
  requireWalletIDVVerification,
1313
- loading: isAITLoading || isAutoConnecting,
1361
+ // Use synchronously maintained refs so a retry immediately following
1362
+ // refreshAIT() does not observe the previous React render's loading state.
1363
+ loading: authorizationLoadingStateRef.current.isLoading(),
1314
1364
  },
1315
1365
  ...authFields(),
1316
1366
  customUsername: (!requireWalletIDVVerification && customUsername)
@@ -1342,6 +1392,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1342
1392
  case 'wallet_verification_required':
1343
1393
  recovery.content = 'Please verify your wallet identity before using this tool.';
1344
1394
  recovery.button = 'verifyWallet';
1395
+ setIsPermissionFormOpen(true);
1396
+ setShowPermissionForm(true);
1345
1397
  break;
1346
1398
  case 'ait_not_found':
1347
1399
  recovery.content = 'No AIT found for your wallet. Please open settings to configure your AIT.';
@@ -1409,7 +1461,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1409
1461
  clientPipelineOverride?: Array<{ name: string; durationMs: number }>
1410
1462
  ): Promise<void> => {
1411
1463
  const hasContent = content.trim() || (attachments && attachments.length > 0);
1412
- if (!hasContent || isLoading) return;
1464
+ // Recovery retries may run immediately after a completion handler calls
1465
+ // setIsLoading(false), before React has committed that state update.
1466
+ if (!hasContent || (retryCount === 0 && isLoading)) return;
1467
+ if (retryCount === 0) {
1468
+ // A new user turn supersedes any older interrupted tool request.
1469
+ pendingTextToolRetryRef.current.clear();
1470
+ }
1413
1471
  setPendingAutoTts(null);
1414
1472
  const turnExternalId = typeof window !== 'undefined'
1415
1473
  ? localStorage.getItem('walletAddress') || undefined
@@ -1728,10 +1786,19 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1728
1786
 
1729
1787
  // Typed preflight already emitted the only permitted recovery UI.
1730
1788
  if (!isToolAllowed) {
1789
+ pendingTextToolRetryRef.current.set({
1790
+ content,
1791
+ isPresetMessage,
1792
+ attachments,
1793
+ });
1731
1794
  setIsLoading(false);
1732
1795
  return;
1733
1796
  }
1734
1797
 
1798
+ // Authorization passed. Clear before invoking the host callback so
1799
+ // concurrent completion events cannot execute this tool twice.
1800
+ pendingTextToolRetryRef.current.clear();
1801
+
1735
1802
  // Create streaming message for tool execution
1736
1803
  const streamingMessageId = `streaming-${Date.now()}`;
1737
1804
  const streamingMessage: Message = {
@@ -2041,6 +2108,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2041
2108
  setIsLoading(false);
2042
2109
  }
2043
2110
  };
2111
+ sendMessageRef.current = sendMessage;
2044
2112
 
2045
2113
  // Handle submit
2046
2114
  const handleSubmit = async (e: React.FormEvent, attachments?: Attachment[]) => {
@@ -2465,8 +2533,11 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2465
2533
  metadataCid,
2466
2534
  };
2467
2535
 
2536
+ const nextPermissions = newPermissions || permissions;
2537
+ aitRef.current = aitInfo;
2538
+ permissionsRef.current = nextPermissions;
2468
2539
  setAit(aitInfo);
2469
- setPermissions(newPermissions || permissions);
2540
+ setPermissions(nextPermissions);
2470
2541
  };
2471
2542
 
2472
2543
 
@@ -2595,6 +2666,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2595
2666
  setShowPermissionForm(false);
2596
2667
  setIsPermissionFormOpen(false);
2597
2668
  await refreshAIT(true);
2669
+ await retryPendingTextTool();
2598
2670
  } catch (error) {
2599
2671
  console.error('Failed to generate AIT:', error);
2600
2672
  setIsDisabled(false);
@@ -2653,6 +2725,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2653
2725
  customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
2654
2726
  }, token);
2655
2727
  if (!('error' in aitResponse)) {
2728
+ aitRef.current = aitResponse;
2656
2729
  setAit(aitResponse);
2657
2730
  }
2658
2731
  }
@@ -2673,7 +2746,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2673
2746
  }
2674
2747
  setIsLoading(false);
2675
2748
  showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
2676
- refreshAIT();
2749
+ await refreshAIT();
2750
+ await retryPendingTextTool();
2677
2751
  return;
2678
2752
  }
2679
2753
  showError(verifyWalletResponse.error);
@@ -2691,6 +2765,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2691
2765
  customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
2692
2766
  }, token);
2693
2767
  if (!('error' in aitResponse)) {
2768
+ aitRef.current = aitResponse;
2694
2769
  setAit(aitResponse);
2695
2770
  }
2696
2771
  }
@@ -2711,7 +2786,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2711
2786
  }
2712
2787
  setIsLoading(false);
2713
2788
  showSuccess('Wallet verification completed successfully! Your wallet is now verified and ready to use.');
2714
- refreshAIT();
2789
+ await refreshAIT();
2790
+ await retryPendingTextTool();
2715
2791
  return;
2716
2792
  } catch (error) {
2717
2793
  let msg = 'Verification failed';
@@ -2751,6 +2827,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2751
2827
  await refreshAIT();
2752
2828
  setIsLoading(false);
2753
2829
  showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
2830
+ await retryPendingTextTool();
2754
2831
  return;
2755
2832
  }
2756
2833
  // Handle specific error messages
@@ -2766,6 +2843,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2766
2843
  await refreshAIT();
2767
2844
  setIsLoading(false);
2768
2845
  showSuccess(walletTextUtils.getWalletText('Wallet verification completed successfully! Your wallet is now verified and ready to use.', serviceId));
2846
+ await retryPendingTextTool();
2769
2847
  return;
2770
2848
  } catch (error) {
2771
2849
  console.error('Custom wallet verification failed:', error);
@@ -2804,6 +2882,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2804
2882
  // Set loading state when permission form opens
2805
2883
  React.useEffect(() => {
2806
2884
  if (isPermissionFormOpen && hitAddress) {
2885
+ authorizationLoadingStateRef.current.setAITLoading(true);
2807
2886
  setIsAITLoading(true);
2808
2887
  }
2809
2888
  }, [isPermissionFormOpen, hitAddress]);
@@ -2946,6 +3025,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2946
3025
  // Functions
2947
3026
  connectWallet,
2948
3027
  signInWallet,
3028
+ retryPendingTextTool,
2949
3029
  sendMessage,
2950
3030
  handleSubmit,
2951
3031
  handlePresetMessage,
@@ -0,0 +1,39 @@
1
+ import type { Attachment } from '@bytexbyte/nxtlinq-ai-agent-core-development';
2
+
3
+ export type PendingTextToolRequest = {
4
+ content: string;
5
+ isPresetMessage: boolean;
6
+ attachments?: Attachment[];
7
+ };
8
+
9
+ /** Keeps one interrupted text-tool request and serializes all resume attempts. */
10
+ export class PendingTextToolRetryController {
11
+ private pending: PendingTextToolRequest | null = null;
12
+ private inFlight: Promise<boolean> | null = null;
13
+
14
+ set(request: PendingTextToolRequest): void {
15
+ this.pending = request;
16
+ }
17
+
18
+ clear(): void {
19
+ this.pending = null;
20
+ }
21
+
22
+ hasPending(): boolean {
23
+ return this.pending !== null;
24
+ }
25
+
26
+ retry(execute: (request: PendingTextToolRequest) => Promise<void>): Promise<boolean> {
27
+ if (this.inFlight) return this.inFlight;
28
+ if (!this.pending) return Promise.resolve(false);
29
+
30
+ const request = this.pending;
31
+ this.inFlight = (async () => {
32
+ await execute(request);
33
+ return true;
34
+ })().finally(() => {
35
+ this.inFlight = null;
36
+ });
37
+ return this.inFlight;
38
+ }
39
+ }
@@ -163,8 +163,9 @@ export interface ChatBotContextType {
163
163
 
164
164
  // Functions
165
165
  connectWallet: (autoShowSignInMessage?: boolean) => Promise<string | false | undefined>;
166
- signInWallet: (autoShowSuccessMessage?: boolean) => Promise<void>;
167
- sendMessage: (content: string, retryCount?: number) => Promise<void>;
166
+ signInWallet: (autoShowSuccessMessage?: boolean) => Promise<boolean>;
167
+ retryPendingTextTool: () => Promise<boolean>;
168
+ sendMessage: (content: string, retryCount?: number, isPresetMessage?: boolean, attachments?: Attachment[]) => Promise<void>;
168
169
  handleSubmit: (e: React.FormEvent, attachments?: Attachment[]) => Promise<void>;
169
170
  handlePresetMessage: (message: PresetMessage) => void;
170
171
  savePermissions: (newPermissions?: string[]) => Promise<void>;
@@ -194,7 +195,7 @@ export interface ChatBotContextType {
194
195
  // Additional properties for PermissionForm
195
196
  onSave: (newPermissions?: string[]) => Promise<void>;
196
197
  onConnectWallet: () => Promise<string | false | undefined>;
197
- onSignIn: () => Promise<void>;
198
+ onSignIn: () => Promise<boolean>;
198
199
  isNeedSignInWithWallet: boolean;
199
200
  onVerifyWallet: (method: 'berifyme' | 'custom') => Promise<void>;
200
201
  serviceId: string;
@@ -203,4 +204,4 @@ export interface ChatBotContextType {
203
204
  // Props
204
205
  props: ChatBotProps;
205
206
  nxtlinqApi: AITApi;
206
- }
207
+ }
@@ -319,6 +319,7 @@ export const MessageList: React.FC = () => {
319
319
  retryTtsWithGesture,
320
320
  connectWallet,
321
321
  signInWallet,
322
+ retryPendingTextTool,
322
323
  hitAddress,
323
324
  isAutoConnecting,
324
325
  isNeedSignInWithWallet,
@@ -353,20 +354,22 @@ export const MessageList: React.FC = () => {
353
354
  }
354
355
 
355
356
  if (buttonType === 'connectWallet') {
356
- connectWallet(true);
357
+ const connected = await connectWallet(false);
358
+ if (connected) {
359
+ // Continue the interrupted flow immediately. This opens the wallet
360
+ // signature UI instead of leaving the user on a stale connect prompt.
361
+ const signedIn = await signInWallet(true);
362
+ if (signedIn) await retryPendingTextTool();
363
+ }
357
364
  } else if (buttonType === 'signIn') {
358
- signInWallet(true);
365
+ const signedIn = await signInWallet(true);
366
+ if (signedIn) await retryPendingTextTool();
359
367
  } else if (buttonType === 'enableAIT') {
360
368
  const requiredPermission = message.metadata?.requiredPermission;
361
369
  if (requiredPermission) {
362
370
  const success = await enableAIT(requiredPermission);
363
371
  if (success) {
364
- // Find the last user message
365
- const lastUserMsg = [...messages].reverse().find(m => m.role === 'user');
366
- if (lastUserMsg && lastUserMsg.content) {
367
- // Re-execute the previous user message (retryCount=1 to skip duplicate PII log)
368
- await sendMessage(lastUserMsg.content, 1);
369
- }
372
+ await retryPendingTextTool();
370
373
  }
371
374
  }
372
375
  } else if (buttonType === 'verifyWallet') {