@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.5 → 0.4.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.
@@ -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,CAggGlD,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,
@@ -2211,85 +2229,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2211
2229
  setInputValue(message.text);
2212
2230
  }
2213
2231
  };
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
2232
  // Auto enable AIT permission
2294
2233
  const enableAIT = async (toolName) => {
2295
2234
  if (isAITEnabling)
@@ -2351,40 +2290,41 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2351
2290
  }
2352
2291
  }
2353
2292
  try {
2354
- // Get available permissions to find the tool
2355
- const availablePermissionLabels = availablePermissions.map(p => p.label);
2356
- if (!availablePermissionLabels.includes(toolName)) {
2293
+ const context = {
2294
+ controller: currentHitAddress,
2295
+ serviceId,
2296
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
2297
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
2298
+ };
2299
+ const enabled = await runtimeMutationQueueRef.current.run(runtimeAITSubjectKey(context), async () => {
2300
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2301
+ const current = await recomputeRuntimeAIT(context);
2302
+ if (!current.maximumPermissions.includes(toolName)) {
2303
+ return false;
2304
+ }
2305
+ const nextDeniedPermissions = removeOneDeniedPermission(current.deniedPermissions, toolName);
2306
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2307
+ await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
2308
+ return true;
2309
+ });
2310
+ if (!enabled) {
2357
2311
  showError(`Tool ${toolName} is not available for your current identity provider`);
2358
2312
  setIsAITEnabling(false);
2359
2313
  return false;
2360
2314
  }
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
2315
  showSuccess('AIT permission enabled successfully! You can now use the AI agent.');
2381
2316
  setIsAITEnabling(false);
2382
2317
  return true;
2383
2318
  }
2384
2319
  catch (error) {
2385
- console.error('Failed to auto-enable AIT:', error);
2320
+ if (!(error instanceof StaleRuntimeAITResponseError)) {
2321
+ console.error('Failed to auto-enable AIT:', error);
2322
+ }
2386
2323
  setIsAITEnabling(false);
2387
- if (error instanceof Error) {
2324
+ if (error instanceof StaleRuntimeAITResponseError) {
2325
+ return false;
2326
+ }
2327
+ else if (error instanceof Error) {
2388
2328
  showError(error.message);
2389
2329
  }
2390
2330
  else {
@@ -2397,11 +2337,23 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2397
2337
  const savePermissions = async (newPermissions) => {
2398
2338
  setIsDisabled(true);
2399
2339
  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);
2340
+ if (!hitAddress) {
2341
+ throw new Error('Please connect your wallet first');
2342
+ }
2343
+ const context = {
2344
+ controller: hitAddress,
2345
+ serviceId,
2346
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
2347
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
2348
+ };
2349
+ const checkedPermissions = [...(newPermissions ?? permissions)];
2350
+ await runtimeMutationQueueRef.current.run(runtimeAITSubjectKey(context), async () => {
2351
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2352
+ const fresh = await recomputeRuntimeAIT(context);
2353
+ const nextDeniedPermissions = replaceCurrentMaximumDenyList(fresh.deniedPermissions, fresh.maximumPermissions, checkedPermissions);
2354
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2355
+ await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
2356
+ });
2405
2357
  showSuccess('AIT permissions saved successfully! You can now use the AI agent with your configured permissions.');
2406
2358
  setShowPermissionForm(false);
2407
2359
  setIsPermissionFormOpen(false);
@@ -2409,9 +2361,14 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2409
2361
  await retryPendingTextTool();
2410
2362
  }
2411
2363
  catch (error) {
2412
- console.error('Failed to generate AIT:', error);
2364
+ if (!(error instanceof StaleRuntimeAITResponseError)) {
2365
+ console.error('Failed to update AIT deny-list:', error);
2366
+ }
2413
2367
  setIsDisabled(false);
2414
- if (error instanceof Error) {
2368
+ if (error instanceof StaleRuntimeAITResponseError) {
2369
+ return;
2370
+ }
2371
+ else if (error instanceof Error) {
2415
2372
  showError(error.message);
2416
2373
  }
2417
2374
  else {
@@ -2457,15 +2414,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2457
2414
  const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
2458
2415
  if (!('error' in walletResponse)) {
2459
2416
  setWalletInfo(walletResponse);
2460
- const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({
2461
- serviceId,
2417
+ await queueRuntimeAITRefresh({
2462
2418
  controller: address,
2463
- customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
2464
- }, token);
2465
- if (!('error' in aitResponse)) {
2466
- aitRef.current = aitResponse;
2467
- setAit(aitResponse);
2468
- }
2419
+ serviceId,
2420
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
2421
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
2422
+ });
2469
2423
  }
2470
2424
  }
2471
2425
  finally {
@@ -2499,15 +2453,12 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2499
2453
  const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
2500
2454
  if (!('error' in walletResponse)) {
2501
2455
  setWalletInfo(walletResponse);
2502
- const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({
2503
- serviceId,
2456
+ await queueRuntimeAITRefresh({
2504
2457
  controller: address,
2505
- customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
2506
- }, token);
2507
- if (!('error' in aitResponse)) {
2508
- aitRef.current = aitResponse;
2509
- setAit(aitResponse);
2510
- }
2458
+ serviceId,
2459
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
2460
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
2461
+ });
2511
2462
  }
2512
2463
  }
2513
2464
  finally {
@@ -2619,7 +2570,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2619
2570
  if (hitAddress && nxtlinqAITServiceAccessToken) {
2620
2571
  refreshAIT();
2621
2572
  }
2622
- }, [hitAddress, nxtlinqAITServiceAccessToken]);
2573
+ }, [hitAddress, nxtlinqAITServiceAccessToken, permissionGroup, runtimeRolesKey, serviceId]);
2623
2574
  // Set loading state when permission form opens
2624
2575
  React.useEffect(() => {
2625
2576
  if (isPermissionFormOpen && hitAddress) {
@@ -2699,6 +2650,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2699
2650
  };
2700
2651
  updateExternalId();
2701
2652
  }, [hitAddress, nxtlinqApi, authFields, pseudoId]);
2653
+ const runtimeAuthorizationMatchesRenderedContext = runtimeAuthorizationMatchesContext(runtimeAuthorizationContextKey, runtimeContext);
2702
2654
  const contextValue = {
2703
2655
  // State
2704
2656
  messages,
@@ -2706,9 +2658,16 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2706
2658
  isLoading,
2707
2659
  isOpen,
2708
2660
  hitAddress,
2709
- ait,
2710
- permissions,
2711
- availablePermissions,
2661
+ ait: runtimeAuthorizationMatchesRenderedContext ? ait : null,
2662
+ permissions: runtimeAuthorizationMatchesRenderedContext ? permissions : [],
2663
+ maximumPermissions: runtimeAuthorizationMatchesRenderedContext ? maximumPermissions : [],
2664
+ deniedPermissions: runtimeAuthorizationMatchesRenderedContext ? deniedPermissions : [],
2665
+ runtimePermissionsUnavailable: runtimeAuthorizationMatchesRenderedContext
2666
+ ? runtimePermissionsUnavailable
2667
+ : runtimeContext !== null,
2668
+ availablePermissions: runtimeAuthorizationMatchesRenderedContext
2669
+ ? availablePermissions
2670
+ : [],
2712
2671
  showPermissionForm,
2713
2672
  isPermissionFormOpen,
2714
2673
  isAITLoading,
@@ -2744,7 +2703,6 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2744
2703
  setIsOpen,
2745
2704
  setShowPermissionForm,
2746
2705
  setIsPermissionFormOpen,
2747
- setPermissions,
2748
2706
  setIsDisabled,
2749
2707
  setIsWalletLoading,
2750
2708
  setNotification,
@@ -2793,6 +2751,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2793
2751
  onVerifyWallet: (method) => handleVerifyWalletClick(method),
2794
2752
  serviceId,
2795
2753
  permissionGroup,
2754
+ roles,
2796
2755
  // Props
2797
2756
  props: {
2798
2757
  onMessage,
@@ -2810,6 +2769,7 @@ customError, voiceThresholds, debugVoiceRms = false, sttGlossary, }) => {
2810
2769
  getAuthToken,
2811
2770
  onVerifyWallet,
2812
2771
  permissionGroup,
2772
+ roles,
2813
2773
  onModelChange,
2814
2774
  storageMode,
2815
2775
  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"}