@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.5 → 0.4.8

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.
@@ -1 +1 @@
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;AAW/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAohGlD,CAAC"}
1
+ {"version":3,"file":"ChatBotContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatBotContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA+B/B,OAAO,EAEL,kBAAkB,EAClB,YAAY,EAEb,MAAM,uBAAuB,CAAC;AA0B/B,eAAO,MAAM,UAAU,0BAMtB,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAwgGlD,CAAC"}
@@ -6,11 +6,11 @@ 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
8
  import { authorizeTextFrontendTool, hasRecordedWalletVerification, validateWalletSession, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
9
- import { waitForAITPermissions } from '../aitPermissionPropagation';
10
9
  import { AuthorizationLoadingState } from '../authorizationLoadingState';
11
10
  import { PendingTextToolRetryController } from '../pendingTextToolRetry';
12
11
  import { completePiiScan } from '../piiMessageState';
13
12
  import { filterSuggestions } from '../suggestionState';
13
+ import { dispatchRuntimeAITCompleteReplacement, removeOneDeniedPermission, replaceCurrentMaximumDenyList, RuntimeAITMutationQueue, RuntimeAITRecomputeCoordinator, StaleRuntimeAITResponseError, runtimeAITContextKey, runtimeAITSubjectKey, runtimeAuthorizationMatchesContext, runtimeAuthorizationState, unavailableRuntimeAuthorization, useProviderRuntimeCommit, useRuntimeAITContextCommit, } from '../permissionState';
14
14
  const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
15
15
  const ChatBotContext = React.createContext(undefined);
16
16
  export const useChatBot = () => {
@@ -20,7 +20,7 @@ export const useChatBot = () => {
20
20
  }
21
21
  return context;
22
22
  };
23
- export const ChatBotProvider = ({ onMessage, onError, onToolUse, presetMessages = [], placeholder = 'Type a message...', className = '', maxRetries = 3, retryDelay = 2000, serviceId, apiKey, apiSecret, serviceToken, getAuthToken, environment = 'production', onVerifyWallet, permissionGroup, children,
23
+ export const ChatBotProvider = ({ onMessage, onError, onToolUse, presetMessages = [], placeholder = 'Type a message...', className = '', maxRetries = 3, retryDelay = 2000, serviceId, apiKey, apiSecret, serviceToken, getAuthToken, environment = 'production', onVerifyWallet, permissionGroup, roles, children,
24
24
  // AI Model related attributes
25
25
  onModelChange,
26
26
  // Storage mode configuration
@@ -37,8 +37,6 @@ idvBannerDismissSeconds = 86400,
37
37
  isStopRecordingOnSend = false,
38
38
  // Custom error message to display in chat
39
39
  customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
40
- // Set API hosts immediately based on environment (before any API calls)
41
- setApiHosts(environment);
42
40
  const nxtlinqApi = React.useMemo(() => (serviceToken || getAuthToken
43
41
  ? createNxtlinqApi({ apiKey, apiSecret, serviceToken, getAuthToken })
44
42
  : createNxtlinqApi(apiKey ?? '', apiSecret ?? '')), [apiKey, apiSecret, serviceToken, getAuthToken]);
@@ -90,7 +88,17 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
90
88
  const [hitAddress, setHitAddress] = React.useState(null);
91
89
  const [ait, setAit] = React.useState(null);
92
90
  const [permissions, setPermissions] = React.useState([]);
93
- const [availablePermissions, setAvailablePermissions] = React.useState([]);
91
+ const [maximumPermissions, setMaximumPermissions] = React.useState([]);
92
+ const [deniedPermissions, setDeniedPermissions] = React.useState([]);
93
+ const [runtimePermissionsUnavailable, setRuntimePermissionsUnavailable] = React.useState(false);
94
+ const [runtimeAuthorizationContextKey, setRuntimeAuthorizationContextKey] = React.useState(null);
95
+ const [servicePermissionCatalog, setServicePermissionCatalog] = React.useState([]);
96
+ const availablePermissions = React.useMemo(() => maximumPermissions.map((label) => servicePermissionCatalog.find((permission) => permission.label === label) ?? {
97
+ id: label,
98
+ label,
99
+ description: '',
100
+ groups: [],
101
+ }), [maximumPermissions, servicePermissionCatalog]);
94
102
  const [showPermissionForm, setShowPermissionForm] = React.useState(false);
95
103
  const [isPermissionFormOpen, setIsPermissionFormOpen] = React.useState(false);
96
104
  const [isAITLoading, setIsAITLoading] = React.useState(false);
@@ -99,6 +107,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
99
107
  const [walletInfo, setWalletInfo] = React.useState(null);
100
108
  const [isWalletLoading, setIsWalletLoading] = React.useState(false);
101
109
  const [isAutoConnecting, setIsAutoConnecting] = React.useState(false);
110
+ const runtimeRolesKey = JSON.stringify(roles ?? null);
102
111
  // Persistent data (always use localStorage)
103
112
  const [nxtlinqAITServiceAccessToken, setNxtlinqAITServiceAccessToken] = useLocalStorage('nxtlinqAITServiceAccessToken', '');
104
113
  const [pseudoId, setPseudoId] = useLocalStorage('pseudoId', uuidv4());
@@ -147,6 +156,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
147
156
  const hitAddressRef = React.useRef(hitAddress);
148
157
  const aitRef = React.useRef(ait);
149
158
  const permissionsRef = React.useRef(permissions);
159
+ const deniedPermissionsRef = React.useRef(deniedPermissions);
150
160
  const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
151
161
  const signerRef = React.useRef(signer);
152
162
  const authorizationLoadingStateRef = React.useRef(new AuthorizationLoadingState(isAITLoading, isAutoConnecting));
@@ -161,6 +171,9 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
161
171
  await sendMessageRef.current(request.content, 1, request.isPresetMessage, request.attachments);
162
172
  });
163
173
  }, []);
174
+ const runtimeRecomputeCoordinatorRef = React.useRef(new RuntimeAITRecomputeCoordinator());
175
+ const runtimeMutationQueueRef = React.useRef(new RuntimeAITMutationQueue());
176
+ useProviderRuntimeCommit(hitAddressRef, hitAddress, environment, setApiHosts);
164
177
  // Helper function to combine customUsername with Adilas customUserInfo
165
178
  const getFinalCustomUsername = React.useCallback((username) => {
166
179
  if (!username)
@@ -480,9 +493,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
480
493
  }
481
494
  }, []);
482
495
  // Update refs when state changes
483
- React.useEffect(() => {
484
- hitAddressRef.current = hitAddress;
485
- }, [hitAddress]);
486
496
  React.useEffect(() => {
487
497
  aitRef.current = ait;
488
498
  }, [ait]);
@@ -495,6 +505,9 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
495
505
  React.useEffect(() => {
496
506
  authorizationLoadingStateRef.current.setAutoConnecting(isAutoConnecting);
497
507
  }, [isAutoConnecting]);
508
+ React.useEffect(() => {
509
+ deniedPermissionsRef.current = deniedPermissions;
510
+ }, [deniedPermissions]);
498
511
  React.useEffect(() => {
499
512
  nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
500
513
  }, [nxtlinqAITServiceAccessToken]);
@@ -874,7 +887,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
874
887
  console.error('Failed to fetch permissions:', result.error);
875
888
  return undefined;
876
889
  }
877
- setAvailablePermissions(result.permissions);
890
+ setServicePermissionCatalog(result.permissions);
878
891
  return result.permissions;
879
892
  }
880
893
  catch (error) {
@@ -882,7 +895,74 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
882
895
  return undefined;
883
896
  }
884
897
  };
885
- // Refresh AIT
898
+ const clearRuntimeAuthorization = React.useCallback(() => {
899
+ const cleared = unavailableRuntimeAuthorization(deniedPermissionsRef.current);
900
+ const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
901
+ aitRef.current = null;
902
+ permissionsRef.current = [];
903
+ setAit(cleared.ait);
904
+ setPermissions(cleared.effectivePermissions);
905
+ setMaximumPermissions(cleared.maximumPermissions);
906
+ setRuntimePermissionsUnavailable(cleared.unavailable);
907
+ setRuntimeAuthorizationContextKey(activeContext ? runtimeAITContextKey(activeContext) : null);
908
+ }, []);
909
+ const applyRuntimeResult = React.useCallback((result, context) => {
910
+ const next = runtimeAuthorizationState(result, context.controller, context.serviceId);
911
+ aitRef.current = next.ait;
912
+ permissionsRef.current = next.effectivePermissions;
913
+ setAit(next.ait);
914
+ setPermissions(next.effectivePermissions);
915
+ setMaximumPermissions(next.maximumPermissions);
916
+ setDeniedPermissions(next.deniedPermissions);
917
+ deniedPermissionsRef.current = next.deniedPermissions;
918
+ setRuntimePermissionsUnavailable(false);
919
+ setRuntimeAuthorizationContextKey(runtimeAITContextKey(context));
920
+ return result;
921
+ }, []);
922
+ const runtimeContext = React.useMemo(() => hitAddress ? {
923
+ controller: hitAddress,
924
+ serviceId,
925
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
926
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
927
+ } : null, [hitAddress, permissionGroup, runtimeRolesKey, serviceId]);
928
+ const invalidateCommittedRuntimeContext = React.useCallback(() => {
929
+ const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
930
+ aitRef.current = null;
931
+ permissionsRef.current = [];
932
+ deniedPermissionsRef.current = [];
933
+ setAit(null);
934
+ setPermissions([]);
935
+ setMaximumPermissions([]);
936
+ setDeniedPermissions([]);
937
+ setRuntimePermissionsUnavailable(activeContext !== null);
938
+ setRuntimeAuthorizationContextKey(activeContext ? runtimeAITContextKey(activeContext) : null);
939
+ }, []);
940
+ useRuntimeAITContextCommit(runtimeRecomputeCoordinatorRef.current, runtimeContext, invalidateCommittedRuntimeContext);
941
+ const recomputeRuntimeAIT = React.useCallback(async (context) => {
942
+ const capturedContext = {
943
+ controller: context.controller,
944
+ serviceId: context.serviceId,
945
+ ...(context.roles !== undefined ? { roles: [...context.roles] } : {}),
946
+ ...(context.permissionGroup !== undefined ? { permissionGroup: context.permissionGroup } : {}),
947
+ };
948
+ return runtimeRecomputeCoordinatorRef.current.run(capturedContext, () => nxtlinqApi.ait.recomputeAIT(capturedContext), applyRuntimeResult, clearRuntimeAuthorization);
949
+ }, [applyRuntimeResult, clearRuntimeAuthorization, nxtlinqApi]);
950
+ const queueRuntimeAITRefresh = React.useCallback((context) => runtimeMutationQueueRef.current.run(runtimeAITSubjectKey(context), async () => {
951
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
952
+ return recomputeRuntimeAIT(context);
953
+ }), [recomputeRuntimeAIT]);
954
+ const convergeRuntimeAITSubject = React.useCallback(async (subjectKey) => {
955
+ const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
956
+ if (!activeContext || runtimeAITSubjectKey(activeContext) !== subjectKey) {
957
+ return;
958
+ }
959
+ await recomputeRuntimeAIT(activeContext);
960
+ }, [recomputeRuntimeAIT]);
961
+ const dispatchRuntimeDenyListReplacement = React.useCallback(async (context, nextDeniedPermissions) => dispatchRuntimeAITCompleteReplacement(runtimeRecomputeCoordinatorRef.current, context, () => nxtlinqApi.ait.updatePermissionDenyList({
962
+ ...context,
963
+ deniedPermissions: nextDeniedPermissions,
964
+ }), convergeRuntimeAITSubject, clearRuntimeAuthorization), [clearRuntimeAuthorization, convergeRuntimeAITSubject, nxtlinqApi]);
965
+ // Refresh the authoritative runtime AIT state.
886
966
  const refreshAIT = async (forceUpdatePermissions = false) => {
887
967
  const currentHitAddress = hitAddressRef.current || hitAddress;
888
968
  const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
@@ -891,8 +971,10 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
891
971
  permissionsRef.current = [];
892
972
  setAit(null);
893
973
  setPermissions([]);
974
+ setMaximumPermissions([]);
894
975
  setWalletInfo(null);
895
976
  authorizationLoadingStateRef.current.setAITLoading(false);
977
+ setRuntimePermissionsUnavailable(false);
896
978
  setIsAITLoading(false);
897
979
  return;
898
980
  }
@@ -923,52 +1005,24 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
923
1005
  console.error('Failed to fetch wallet info:', error);
924
1006
  }
925
1007
  }
926
- // AIT existence is not identity verification. In strict mode, stop before
927
- // the legacy AIT lookup can synthesize a custom wallet record.
928
1008
  if (currentToken && !walletAllowsAITLookup) {
929
- aitRef.current = null;
930
- permissionsRef.current = [];
931
- setAit(null);
932
- setPermissions([]);
1009
+ clearRuntimeAuthorization();
933
1010
  return;
934
1011
  }
935
- // Only try to fetch AIT if we have a token
1012
+ // Recompute is the sole mint/reuse path and the only permission authority.
936
1013
  if (currentToken) {
937
- const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
938
- serviceId,
939
- controller: currentHitAddress,
940
- customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
941
- }, currentToken);
942
- if ('error' in response) {
943
- console.error('Failed to fetch AIT:', response.error);
944
- aitRef.current = null;
945
- permissionsRef.current = [];
946
- setAit(null);
947
- setPermissions([]);
948
- return;
949
- }
950
- aitRef.current = response;
951
- setAit(response);
952
- if (!isPermissionFormOpen || forceUpdatePermissions) {
953
- const newPermissions = response.metadata?.permissions || [];
954
- permissionsRef.current = newPermissions;
955
- setPermissions(newPermissions);
1014
+ if (runtimeContext) {
1015
+ await queueRuntimeAITRefresh(runtimeContext);
956
1016
  }
957
1017
  }
958
1018
  else {
959
- // No token available, clear AIT data
960
- aitRef.current = null;
961
- permissionsRef.current = [];
962
- setAit(null);
963
- setPermissions([]);
1019
+ clearRuntimeAuthorization();
964
1020
  }
965
1021
  }
966
1022
  catch (error) {
967
- console.error('Failed to fetch AIT:', error);
968
- aitRef.current = null;
969
- permissionsRef.current = [];
970
- setAit(null);
971
- setPermissions([]);
1023
+ if (!(error instanceof StaleRuntimeAITResponseError)) {
1024
+ console.error('Failed to recompute AIT:', error);
1025
+ }
972
1026
  }
973
1027
  finally {
974
1028
  // Keep the authorization snapshot current before refreshAIT resolves.
@@ -977,48 +1031,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
977
1031
  setIsAITLoading(false);
978
1032
  }
979
1033
  };
980
- const waitForPersistedAITPermissions = async (expectedPermissions) => {
981
- const currentHitAddress = hitAddressRef.current || hitAddress;
982
- const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
983
- if (!currentHitAddress || !currentToken)
984
- return false;
985
- authorizationLoadingStateRef.current.setAITLoading(true);
986
- setIsAITLoading(true);
987
- try {
988
- const persistedAIT = await waitForAITPermissions({
989
- expectedPermissions,
990
- readAIT: async () => {
991
- try {
992
- const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
993
- serviceId,
994
- controller: currentHitAddress,
995
- customUsername: (!requireWalletIDVVerification && customUsername)
996
- ? getFinalCustomUsername(customUsername)
997
- : undefined,
998
- }, currentToken);
999
- return 'error' in response ? undefined : response;
1000
- }
1001
- catch (error) {
1002
- console.warn('AIT permission propagation check failed:', error);
1003
- return undefined;
1004
- }
1005
- },
1006
- getPermissions: (currentAIT) => currentAIT.metadata?.permissions || [],
1007
- });
1008
- if (!persistedAIT)
1009
- return false;
1010
- const persistedPermissions = persistedAIT.metadata?.permissions || [];
1011
- aitRef.current = persistedAIT;
1012
- permissionsRef.current = persistedPermissions;
1013
- setAit(persistedAIT);
1014
- setPermissions(persistedPermissions);
1015
- return true;
1016
- }
1017
- finally {
1018
- authorizationLoadingStateRef.current.setAITLoading(false);
1019
- setIsAITLoading(false);
1020
- }
1021
- };
1022
1034
  // Check if user needs to sign in
1023
1035
  const isNeedSignInWithWallet = React.useMemo(() => {
1024
1036
  if (!hitAddress)
@@ -1197,9 +1209,13 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1197
1209
  const { accessToken } = response;
1198
1210
  setNxtlinqAITServiceAccessToken(accessToken);
1199
1211
  nxtlinqAITServiceAccessTokenRef.current = accessToken;
1200
- // Wait for state to update before refreshing AIT
1201
- await new Promise(resolve => setTimeout(resolve, 100));
1202
- await refreshAIT();
1212
+ localStorage.setItem('nxtlinqAITServiceAccessToken', JSON.stringify(accessToken));
1213
+ await queueRuntimeAITRefresh({
1214
+ controller: addressToUse,
1215
+ serviceId,
1216
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
1217
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
1218
+ });
1203
1219
  if (autoShowSuccessMessage) {
1204
1220
  showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
1205
1221
  }
@@ -1234,6 +1250,8 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1234
1250
  loading: authorizationLoadingStateRef.current.isLoading(),
1235
1251
  },
1236
1252
  ...authFields(),
1253
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
1254
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
1237
1255
  customUsername: (!requireWalletIDVVerification && customUsername)
1238
1256
  ? getFinalCustomUsername(customUsername)
1239
1257
  : undefined,
@@ -1634,6 +1652,10 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
1634
1652
  const isToolAllowed = await hasPermission(toolUse.name);
1635
1653
  // Typed preflight already emitted the only permitted recovery UI.
1636
1654
  if (!isToolAllowed) {
1655
+ // Do not leave suggestions from the previous turn visible while
1656
+ // authorization recovery short-circuits the normal response path.
1657
+ setSuggestions([]);
1658
+ void updateSuggestions(pseudoId, localStorage.getItem('walletAddress') || undefined, content);
1637
1659
  pendingTextToolRetryRef.current.set({
1638
1660
  content,
1639
1661
  isPresetMessage,
@@ -2211,85 +2233,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2211
2233
  setInputValue(message.text);
2212
2234
  }
2213
2235
  };
2214
- // Generate and register AIT
2215
- const generateAndRegisterAIT = async (newPermissions, isFromAIAgent = false) => {
2216
- let currentSigner = signer;
2217
- let currentAddress = hitAddress;
2218
- // If we have an address but no signer, try to auto-connect wallet
2219
- if (currentAddress && !currentSigner) {
2220
- const storedWalletAddress = localStorage.getItem('walletAddress');
2221
- if (storedWalletAddress === currentAddress) {
2222
- try {
2223
- const autoConnectResult = await connectWallet(false); // Don't show sign-in message yet
2224
- if (autoConnectResult) {
2225
- // Wait for state to update
2226
- await new Promise(resolve => setTimeout(resolve, 1000));
2227
- // Get the updated signer and address
2228
- currentSigner = signerRef.current || signer;
2229
- currentAddress = hitAddressRef.current || hitAddress;
2230
- }
2231
- }
2232
- catch (error) {
2233
- console.error('Auto-connect failed during AIT generation:', error);
2234
- }
2235
- }
2236
- }
2237
- if (!currentSigner || !currentAddress) {
2238
- throw new Error('Missing signer or wallet address');
2239
- }
2240
- return generateAndRegisterAITWithSigner(newPermissions, isFromAIAgent, currentSigner, currentAddress);
2241
- };
2242
- // Generate and register AIT with explicit signer and address
2243
- const generateAndRegisterAITWithSigner = async (newPermissions, isFromAIAgent = false, explicitSigner, explicitAddress) => {
2244
- const currentSigner = explicitSigner || signer;
2245
- const currentAddress = explicitAddress || hitAddress;
2246
- if (!currentSigner || !currentAddress) {
2247
- throw new Error('Missing signer or wallet address');
2248
- }
2249
- // If AI Agent is creating, must check if user has existing AIT
2250
- if (isFromAIAgent && !aitRef.current) {
2251
- throw new Error('You must have an existing AIT before AI Agent can create new AITs');
2252
- }
2253
- const timestamp = Math.floor(Date.now() / 1000);
2254
- const aitId = `did:polygon:nxtlinq:${currentAddress}:${timestamp}`;
2255
- const metadata = {
2256
- permissions: newPermissions || permissions,
2257
- issuedBy: currentAddress,
2258
- };
2259
- const metadataStr = stringify(metadata);
2260
- // ethers v6: utils.keccak256 -> keccak256, utils.toUtf8Bytes -> toUtf8Bytes
2261
- const metadataHash = ethers.keccak256(ethers.toUtf8Bytes(metadataStr));
2262
- const uploadResponse = await nxtlinqApi.metadata.createMetadata(metadata, nxtlinqAITServiceAccessToken || '');
2263
- if ('error' in uploadResponse) {
2264
- throw new Error(`Failed to upload metadata: ${uploadResponse.error}`);
2265
- }
2266
- const { metadataCid } = uploadResponse;
2267
- const createAITParams = {
2268
- aitId,
2269
- controller: currentAddress,
2270
- serviceId,
2271
- metadataHash,
2272
- metadataCid,
2273
- isFromAIAgent: isFromAIAgent,
2274
- parentAITId: isFromAIAgent ? aitRef.current?.aitId : undefined,
2275
- };
2276
- const createAITResponse = await nxtlinqApi.ait.createAIT(createAITParams, nxtlinqAITServiceAccessToken || '');
2277
- if ('error' in createAITResponse) {
2278
- throw new Error(`Failed to create AIT: ${createAITResponse.error}`);
2279
- }
2280
- const aitInfo = {
2281
- aitId,
2282
- controller: currentAddress,
2283
- metadata,
2284
- metadataHash,
2285
- metadataCid,
2286
- };
2287
- const nextPermissions = newPermissions || permissions;
2288
- aitRef.current = aitInfo;
2289
- permissionsRef.current = nextPermissions;
2290
- setAit(aitInfo);
2291
- setPermissions(nextPermissions);
2292
- };
2293
2236
  // Auto enable AIT permission
2294
2237
  const enableAIT = async (toolName) => {
2295
2238
  if (isAITEnabling)
@@ -2351,40 +2294,41 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2351
2294
  }
2352
2295
  }
2353
2296
  try {
2354
- // Get available permissions to find the tool
2355
- const availablePermissionLabels = availablePermissions.map(p => p.label);
2356
- if (!availablePermissionLabels.includes(toolName)) {
2297
+ const context = {
2298
+ controller: currentHitAddress,
2299
+ serviceId,
2300
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
2301
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
2302
+ };
2303
+ const enabled = await runtimeMutationQueueRef.current.run(runtimeAITSubjectKey(context), async () => {
2304
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2305
+ const current = await recomputeRuntimeAIT(context);
2306
+ if (!current.maximumPermissions.includes(toolName)) {
2307
+ return false;
2308
+ }
2309
+ const nextDeniedPermissions = removeOneDeniedPermission(current.deniedPermissions, toolName);
2310
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2311
+ await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
2312
+ return true;
2313
+ });
2314
+ if (!enabled) {
2357
2315
  showError(`Tool ${toolName} is not available for your current identity provider`);
2358
2316
  setIsAITEnabling(false);
2359
2317
  return false;
2360
2318
  }
2361
- // Get current permissions from AIT metadata instead of React state
2362
- // This ensures we don't lose existing permissions when enabling new ones
2363
- const currentAITPermissions = aitRef.current?.metadata?.permissions || permissions;
2364
- const newPermissions = [...currentAITPermissions];
2365
- if (!newPermissions.includes(toolName)) {
2366
- newPermissions.push(toolName);
2367
- }
2368
- // Filter out deleted permissions before saving
2369
- const validPermissions = newPermissions.filter(permission => availablePermissionLabels.includes(permission));
2370
- // Generate and register AIT with new permissions
2371
- // For auto-enable, we should create a regular AIT (not AI Agent AIT) if user doesn't have existing AIT
2372
- const shouldCreateAsAIAgent = !!aitRef.current; // Only create as AI Agent if user already has an AIT
2373
- await generateAndRegisterAITWithSigner(validPermissions, shouldCreateAsAIAgent, currentSigner, currentHitAddress);
2374
- const permissionsPersisted = await waitForPersistedAITPermissions(validPermissions);
2375
- if (!permissionsPersisted) {
2376
- showWarning('AIT permission was submitted but is still synchronizing. Please try again shortly.');
2377
- setIsAITEnabling(false);
2378
- return false;
2379
- }
2380
2319
  showSuccess('AIT permission enabled successfully! You can now use the AI agent.');
2381
2320
  setIsAITEnabling(false);
2382
2321
  return true;
2383
2322
  }
2384
2323
  catch (error) {
2385
- console.error('Failed to auto-enable AIT:', error);
2324
+ if (!(error instanceof StaleRuntimeAITResponseError)) {
2325
+ console.error('Failed to auto-enable AIT:', error);
2326
+ }
2386
2327
  setIsAITEnabling(false);
2387
- if (error instanceof Error) {
2328
+ if (error instanceof StaleRuntimeAITResponseError) {
2329
+ return false;
2330
+ }
2331
+ else if (error instanceof Error) {
2388
2332
  showError(error.message);
2389
2333
  }
2390
2334
  else {
@@ -2397,11 +2341,23 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2397
2341
  const savePermissions = async (newPermissions) => {
2398
2342
  setIsDisabled(true);
2399
2343
  try {
2400
- // Filter out deleted permissions before saving
2401
- const permissionsToSave = newPermissions || permissions;
2402
- const availablePermissionLabels = availablePermissions.map(p => p.label);
2403
- const validPermissions = permissionsToSave.filter(permission => availablePermissionLabels.includes(permission));
2404
- await generateAndRegisterAIT(validPermissions, false);
2344
+ if (!hitAddress) {
2345
+ throw new Error('Please connect your wallet first');
2346
+ }
2347
+ const context = {
2348
+ controller: hitAddress,
2349
+ serviceId,
2350
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
2351
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
2352
+ };
2353
+ const checkedPermissions = [...(newPermissions ?? permissions)];
2354
+ await runtimeMutationQueueRef.current.run(runtimeAITSubjectKey(context), async () => {
2355
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2356
+ const fresh = await recomputeRuntimeAIT(context);
2357
+ const nextDeniedPermissions = replaceCurrentMaximumDenyList(fresh.deniedPermissions, fresh.maximumPermissions, checkedPermissions);
2358
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2359
+ await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
2360
+ });
2405
2361
  showSuccess('AIT permissions saved successfully! You can now use the AI agent with your configured permissions.');
2406
2362
  setShowPermissionForm(false);
2407
2363
  setIsPermissionFormOpen(false);
@@ -2409,9 +2365,14 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2409
2365
  await retryPendingTextTool();
2410
2366
  }
2411
2367
  catch (error) {
2412
- console.error('Failed to generate AIT:', error);
2368
+ if (!(error instanceof StaleRuntimeAITResponseError)) {
2369
+ console.error('Failed to update AIT deny-list:', error);
2370
+ }
2413
2371
  setIsDisabled(false);
2414
- if (error instanceof Error) {
2372
+ if (error instanceof StaleRuntimeAITResponseError) {
2373
+ return;
2374
+ }
2375
+ else if (error instanceof Error) {
2415
2376
  showError(error.message);
2416
2377
  }
2417
2378
  else {
@@ -2457,15 +2418,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2457
2418
  const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
2458
2419
  if (!('error' in walletResponse)) {
2459
2420
  setWalletInfo(walletResponse);
2460
- const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({
2461
- serviceId,
2421
+ await queueRuntimeAITRefresh({
2462
2422
  controller: address,
2463
- customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
2464
- }, token);
2465
- if (!('error' in aitResponse)) {
2466
- aitRef.current = aitResponse;
2467
- setAit(aitResponse);
2468
- }
2423
+ serviceId,
2424
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
2425
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
2426
+ });
2469
2427
  }
2470
2428
  }
2471
2429
  finally {
@@ -2499,15 +2457,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2499
2457
  const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
2500
2458
  if (!('error' in walletResponse)) {
2501
2459
  setWalletInfo(walletResponse);
2502
- const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({
2503
- serviceId,
2460
+ await queueRuntimeAITRefresh({
2504
2461
  controller: address,
2505
- customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
2506
- }, token);
2507
- if (!('error' in aitResponse)) {
2508
- aitRef.current = aitResponse;
2509
- setAit(aitResponse);
2510
- }
2462
+ serviceId,
2463
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
2464
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
2465
+ });
2511
2466
  }
2512
2467
  }
2513
2468
  finally {
@@ -2619,7 +2574,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2619
2574
  if (hitAddress && nxtlinqAITServiceAccessToken) {
2620
2575
  refreshAIT();
2621
2576
  }
2622
- }, [hitAddress, nxtlinqAITServiceAccessToken]);
2577
+ }, [hitAddress, nxtlinqAITServiceAccessToken, permissionGroup, runtimeRolesKey, serviceId]);
2623
2578
  // Set loading state when permission form opens
2624
2579
  React.useEffect(() => {
2625
2580
  if (isPermissionFormOpen && hitAddress) {
@@ -2699,6 +2654,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2699
2654
  };
2700
2655
  updateExternalId();
2701
2656
  }, [hitAddress, nxtlinqApi, authFields, pseudoId]);
2657
+ const runtimeAuthorizationMatchesRenderedContext = runtimeAuthorizationMatchesContext(runtimeAuthorizationContextKey, runtimeContext);
2702
2658
  const contextValue = {
2703
2659
  // State
2704
2660
  messages,
@@ -2706,9 +2662,16 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2706
2662
  isLoading,
2707
2663
  isOpen,
2708
2664
  hitAddress,
2709
- ait,
2710
- permissions,
2711
- availablePermissions,
2665
+ ait: runtimeAuthorizationMatchesRenderedContext ? ait : null,
2666
+ permissions: runtimeAuthorizationMatchesRenderedContext ? permissions : [],
2667
+ maximumPermissions: runtimeAuthorizationMatchesRenderedContext ? maximumPermissions : [],
2668
+ deniedPermissions: runtimeAuthorizationMatchesRenderedContext ? deniedPermissions : [],
2669
+ runtimePermissionsUnavailable: runtimeAuthorizationMatchesRenderedContext
2670
+ ? runtimePermissionsUnavailable
2671
+ : runtimeContext !== null,
2672
+ availablePermissions: runtimeAuthorizationMatchesRenderedContext
2673
+ ? availablePermissions
2674
+ : [],
2712
2675
  showPermissionForm,
2713
2676
  isPermissionFormOpen,
2714
2677
  isAITLoading,
@@ -2744,7 +2707,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2744
2707
  setIsOpen,
2745
2708
  setShowPermissionForm,
2746
2709
  setIsPermissionFormOpen,
2747
- setPermissions,
2748
2710
  setIsDisabled,
2749
2711
  setIsWalletLoading,
2750
2712
  setNotification,
@@ -2793,6 +2755,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2793
2755
  onVerifyWallet: (method) => handleVerifyWalletClick(method),
2794
2756
  serviceId,
2795
2757
  permissionGroup,
2758
+ roles,
2796
2759
  // Props
2797
2760
  props: {
2798
2761
  onMessage,
@@ -2810,6 +2773,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2810
2773
  getAuthToken,
2811
2774
  onVerifyWallet,
2812
2775
  permissionGroup,
2776
+ roles,
2813
2777
  onModelChange,
2814
2778
  storageMode,
2815
2779
  requireWalletIDVVerification,
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { ChatBot } from './ChatBot';
2
2
  export { ChatBotProvider, useChatBot } from './context/ChatBotContext';
3
3
  export { ChatBotUI, MessageInput, MessageList, ModelSelector, NotificationModal, PermissionForm, PresetMessages, BerifyMeModal, } from './ui/index';
4
- export type { AIModel, AITMetadata, ChatBotContextType, ChatBotProps, NovaError, NovaResponse, ToolCall, ToolUse, PresetMessage, } from './types/ChatBotTypes';
4
+ export type { AIModel, ChatBotContextType, ChatBotProps, NovaError, NovaResponse, ToolCall, ToolUse, PresetMessage, } from './types/ChatBotTypes';
5
5
  export type { AgentEnvironment, AgentConfig, Attachment, AgentResponse, Message, SendMessageOptions, NxtlinqAgentSnapshot, TextToolAuthorizationSnapshot, TextToolAuthorizationOutcome, VoiceSession, VoiceStatus, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
6
6
  export { NxtlinqAgent, setApiHosts, VoiceNotSupportedError, STORAGE_KEYS, authorizeTextFrontendTool, hasRecordedWalletVerification, validateWalletSession, } from '@bytexbyte/nxtlinq-ai-agent-core-development';
7
7
  export { createNxtlinqApi, useLocalStorage, useSessionStorage, useSpeechToTextFromMic, useVoiceMode, metakeepClient, getEthers, sleep, walletTextUtils, } from '@bytexbyte/nxtlinq-ai-agent-web-development';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EACL,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,OAAO,EACP,WAAW,EACX,kBAAkB,EAClB,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,aAAa,EACb,OAAO,EACP,kBAAkB,EAClB,oBAAoB,EACpB,6BAA6B,EAC7B,4BAA4B,EAC5B,YAAY,EACZ,WAAW,GACZ,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,sBAAsB,EACtB,YAAY,EACZ,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,KAAK,EACL,eAAe,GAChB,MAAM,6CAA6C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EACL,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,OAAO,EACP,kBAAkB,EAClB,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,UAAU,EACV,aAAa,EACb,OAAO,EACP,kBAAkB,EAClB,oBAAoB,EACpB,6BAA6B,EAC7B,4BAA4B,EAC5B,YAAY,EACZ,WAAW,GACZ,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,sBAAsB,EACtB,YAAY,EACZ,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,8CAA8C,CAAC;AAEtD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,SAAS,EACT,KAAK,EACL,eAAe,GAChB,MAAM,6CAA6C,CAAC"}