@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.3.4 → 2.0.0

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.
Files changed (136) hide show
  1. package/dist/context/ChatBotContext.d.ts.map +1 -1
  2. package/dist/context/ChatBotContext.js +179 -160
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/permissionState.d.ts +53 -0
  6. package/dist/permissionState.d.ts.map +1 -0
  7. package/dist/permissionState.js +217 -0
  8. package/dist/types/ChatBotTypes.d.ts +6 -5
  9. package/dist/types/ChatBotTypes.d.ts.map +1 -1
  10. package/dist/ui/ModelSelector.js +2 -2
  11. package/dist/ui/PermissionForm.d.ts.map +1 -1
  12. package/dist/ui/PermissionForm.js +14 -28
  13. package/package.json +3 -3
  14. package/src/context/ChatBotContext.tsx +252 -195
  15. package/src/index.ts +0 -1
  16. package/src/permissionState.ts +313 -0
  17. package/src/types/ChatBotTypes.ts +7 -7
  18. package/src/ui/ModelSelector.tsx +5 -5
  19. package/src/ui/PermissionForm.tsx +20 -31
  20. package/dist/NxtlinqAgentChat.d.ts +0 -26
  21. package/dist/NxtlinqAgentChat.d.ts.map +0 -1
  22. package/dist/NxtlinqAgentChat.js +0 -28
  23. package/dist/components/AgentAssistantShell.d.ts +0 -5
  24. package/dist/components/AgentAssistantShell.d.ts.map +0 -1
  25. package/dist/components/AgentAssistantShell.js +0 -52
  26. package/dist/components/AgentComposer.d.ts +0 -3
  27. package/dist/components/AgentComposer.d.ts.map +0 -1
  28. package/dist/components/AgentComposer.js +0 -60
  29. package/dist/components/AgentMessageList.d.ts +0 -3
  30. package/dist/components/AgentMessageList.d.ts.map +0 -1
  31. package/dist/components/AgentMessageList.js +0 -37
  32. package/dist/components/AgentRemoteAudio.d.ts +0 -4
  33. package/dist/components/AgentRemoteAudio.d.ts.map +0 -1
  34. package/dist/components/AgentRemoteAudio.js +0 -34
  35. package/dist/components/AgentVoiceBar.d.ts +0 -3
  36. package/dist/components/AgentVoiceBar.d.ts.map +0 -1
  37. package/dist/components/AgentVoiceBar.js +0 -91
  38. package/dist/components/PresetMessageChips.d.ts +0 -3
  39. package/dist/components/PresetMessageChips.d.ts.map +0 -1
  40. package/dist/components/PresetMessageChips.js +0 -23
  41. package/dist/context/AgentAssistantContext.d.ts +0 -32
  42. package/dist/context/AgentAssistantContext.d.ts.map +0 -1
  43. package/dist/context/AgentAssistantContext.js +0 -179
  44. package/dist/legacy/assets/images/adiSideItalicDataUri.d.ts +0 -2
  45. package/dist/legacy/assets/images/adiSideItalicDataUri.d.ts.map +0 -1
  46. package/dist/legacy/assets/images/adiSideItalicDataUri.js +0 -1
  47. package/dist/legacy/chatbot/ChatBot.d.ts +0 -5
  48. package/dist/legacy/chatbot/ChatBot.d.ts.map +0 -1
  49. package/dist/legacy/chatbot/ChatBot.js +0 -35
  50. package/dist/legacy/chatbot/context/ChatBotContext.d.ts +0 -5
  51. package/dist/legacy/chatbot/context/ChatBotContext.d.ts.map +0 -1
  52. package/dist/legacy/chatbot/context/ChatBotContext.js +0 -2922
  53. package/dist/legacy/chatbot/types/ChatBotTypes.d.ts +0 -166
  54. package/dist/legacy/chatbot/types/ChatBotTypes.d.ts.map +0 -1
  55. package/dist/legacy/chatbot/types/ChatBotTypes.js +0 -1
  56. package/dist/legacy/chatbot/ui/BerifyMeModal.d.ts +0 -17
  57. package/dist/legacy/chatbot/ui/BerifyMeModal.d.ts.map +0 -1
  58. package/dist/legacy/chatbot/ui/BerifyMeModal.js +0 -110
  59. package/dist/legacy/chatbot/ui/ChatBotHeader.d.ts +0 -15
  60. package/dist/legacy/chatbot/ui/ChatBotHeader.d.ts.map +0 -1
  61. package/dist/legacy/chatbot/ui/ChatBotHeader.js +0 -62
  62. package/dist/legacy/chatbot/ui/ChatBotUI.d.ts +0 -3
  63. package/dist/legacy/chatbot/ui/ChatBotUI.d.ts.map +0 -1
  64. package/dist/legacy/chatbot/ui/ChatBotUI.js +0 -557
  65. package/dist/legacy/chatbot/ui/MessageInput.d.ts +0 -3
  66. package/dist/legacy/chatbot/ui/MessageInput.d.ts.map +0 -1
  67. package/dist/legacy/chatbot/ui/MessageInput.js +0 -321
  68. package/dist/legacy/chatbot/ui/MessageList.d.ts +0 -4
  69. package/dist/legacy/chatbot/ui/MessageList.d.ts.map +0 -1
  70. package/dist/legacy/chatbot/ui/MessageList.js +0 -455
  71. package/dist/legacy/chatbot/ui/ModelSelector.d.ts +0 -4
  72. package/dist/legacy/chatbot/ui/ModelSelector.d.ts.map +0 -1
  73. package/dist/legacy/chatbot/ui/ModelSelector.js +0 -122
  74. package/dist/legacy/chatbot/ui/NotificationModal.d.ts +0 -15
  75. package/dist/legacy/chatbot/ui/NotificationModal.d.ts.map +0 -1
  76. package/dist/legacy/chatbot/ui/NotificationModal.js +0 -53
  77. package/dist/legacy/chatbot/ui/PermissionForm.d.ts +0 -8
  78. package/dist/legacy/chatbot/ui/PermissionForm.d.ts.map +0 -1
  79. package/dist/legacy/chatbot/ui/PermissionForm.js +0 -465
  80. package/dist/legacy/chatbot/ui/PresetMessages.d.ts +0 -4
  81. package/dist/legacy/chatbot/ui/PresetMessages.d.ts.map +0 -1
  82. package/dist/legacy/chatbot/ui/PresetMessages.js +0 -33
  83. package/dist/legacy/chatbot/ui/VoiceModePanel.d.ts +0 -3
  84. package/dist/legacy/chatbot/ui/VoiceModePanel.d.ts.map +0 -1
  85. package/dist/legacy/chatbot/ui/VoiceModePanel.js +0 -95
  86. package/dist/legacy/chatbot/ui/chatBotHeaderParts.d.ts +0 -15
  87. package/dist/legacy/chatbot/ui/chatBotHeaderParts.d.ts.map +0 -1
  88. package/dist/legacy/chatbot/ui/chatBotHeaderParts.js +0 -50
  89. package/dist/legacy/chatbot/ui/styles/isolatedStyles.d.ts +0 -73
  90. package/dist/legacy/chatbot/ui/styles/isolatedStyles.d.ts.map +0 -1
  91. package/dist/legacy/chatbot/ui/styles/isolatedStyles.js +0 -985
  92. package/dist/legacy/index.d.ts +0 -14
  93. package/dist/legacy/index.d.ts.map +0 -1
  94. package/dist/legacy/index.js +0 -12
  95. package/dist/theme/defaultTheme.d.ts +0 -3
  96. package/dist/theme/defaultTheme.d.ts.map +0 -1
  97. package/dist/theme/defaultTheme.js +0 -20
  98. package/dist/types.d.ts +0 -62
  99. package/dist/types.d.ts.map +0 -1
  100. package/dist/types.js +0 -1
  101. package/dist/voice/useVoiceConnectOrchestration.d.ts +0 -21
  102. package/dist/voice/useVoiceConnectOrchestration.d.ts.map +0 -1
  103. package/dist/voice/useVoiceConnectOrchestration.js +0 -86
  104. package/dist/voice/useVoiceMicState.d.ts +0 -15
  105. package/dist/voice/useVoiceMicState.d.ts.map +0 -1
  106. package/dist/voice/useVoiceMicState.js +0 -94
  107. package/dist/voice/useVoiceSilenceCommit.d.ts +0 -10
  108. package/dist/voice/useVoiceSilenceCommit.d.ts.map +0 -1
  109. package/dist/voice/useVoiceSilenceCommit.js +0 -67
  110. package/dist/voice/useVoiceTranscriptMessages.d.ts +0 -18
  111. package/dist/voice/useVoiceTranscriptMessages.d.ts.map +0 -1
  112. package/dist/voice/useVoiceTranscriptMessages.js +0 -132
  113. package/dist/voice/useWsRealtimeAudio.d.ts +0 -18
  114. package/dist/voice/useWsRealtimeAudio.d.ts.map +0 -1
  115. package/dist/voice/useWsRealtimeAudio.js +0 -102
  116. package/dist/voice/voiceMicConstants.d.ts +0 -4
  117. package/dist/voice/voiceMicConstants.d.ts.map +0 -1
  118. package/dist/voice/voiceMicConstants.js +0 -10
  119. package/dist/voice/voiceUserBubble.d.ts +0 -10
  120. package/dist/voice/voiceUserBubble.d.ts.map +0 -1
  121. package/dist/voice/voiceUserBubble.js +0 -52
  122. package/dist/voice/ws/BrowserWsPcmPlayer.d.ts +0 -23
  123. package/dist/voice/ws/BrowserWsPcmPlayer.d.ts.map +0 -1
  124. package/dist/voice/ws/BrowserWsPcmPlayer.js +0 -137
  125. package/dist/voice/ws/BrowserWsPcmRecorder.d.ts +0 -17
  126. package/dist/voice/ws/BrowserWsPcmRecorder.d.ts.map +0 -1
  127. package/dist/voice/ws/BrowserWsPcmRecorder.js +0 -71
  128. package/dist/voice/ws/float32ToPcm16.d.ts +0 -2
  129. package/dist/voice/ws/float32ToPcm16.d.ts.map +0 -1
  130. package/dist/voice/ws/float32ToPcm16.js +0 -8
  131. package/dist/voice/ws/voiceSilenceConstants.d.ts +0 -5
  132. package/dist/voice/ws/voiceSilenceConstants.d.ts.map +0 -1
  133. package/dist/voice/ws/voiceSilenceConstants.js +0 -4
  134. package/dist/voice/ws/wsRealtimeConstants.d.ts +0 -2
  135. package/dist/voice/ws/wsRealtimeConstants.d.ts.map +0 -1
  136. package/dist/voice/ws/wsRealtimeConstants.js +0 -1
@@ -23,6 +23,7 @@ import type {
23
23
  Attachment,
24
24
  ClientTtsVoiceSettings,
25
25
  Message,
26
+ RuntimeAITResult,
26
27
  ServicePermission,
27
28
  } from '@bytexbyte/nxtlinq-ai-agent-core-development';
28
29
  import {
@@ -31,6 +32,22 @@ import {
31
32
  ChatBotProps,
32
33
  PresetMessage
33
34
  } from '../types/ChatBotTypes';
35
+ import {
36
+ dispatchRuntimeAITCompleteReplacement,
37
+ removeOneDeniedPermission,
38
+ replaceCurrentMaximumDenyList,
39
+ RuntimeAITMutationQueue,
40
+ RuntimeAITRecomputeCoordinator,
41
+ StaleRuntimeAITResponseError,
42
+ runtimeAITContextKey,
43
+ runtimeAITSubjectKey,
44
+ runtimeAuthorizationMatchesContext,
45
+ runtimeAuthorizationState,
46
+ unavailableRuntimeAuthorization,
47
+ useProviderRuntimeCommit,
48
+ useRuntimeAITContextCommit,
49
+ type RuntimeAITContext,
50
+ } from '../permissionState';
34
51
 
35
52
  const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
36
53
 
@@ -61,6 +78,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
61
78
  environment = 'production',
62
79
  onVerifyWallet,
63
80
  permissionGroup,
81
+ roles,
64
82
  children,
65
83
  // AI Model related attributes
66
84
  onModelChange,
@@ -85,9 +103,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
85
103
  debugVoiceRms = false,
86
104
  sttGlossary,
87
105
  }) => {
88
- // Set API hosts immediately based on environment (before any API calls)
89
- setApiHosts(environment);
90
-
91
106
  const nxtlinqApi = React.useMemo(
92
107
  () => (serviceToken || getAuthToken
93
108
  ? createNxtlinqApi({ apiKey, apiSecret, serviceToken, getAuthToken })
@@ -147,7 +162,22 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
147
162
  const [hitAddress, setHitAddress] = React.useState<string | null>(null);
148
163
  const [ait, setAit] = React.useState<AIT | null>(null);
149
164
  const [permissions, setPermissions] = React.useState<string[]>([]);
150
- const [availablePermissions, setAvailablePermissions] = React.useState<ServicePermission[]>([]);
165
+ const [maximumPermissions, setMaximumPermissions] = React.useState<string[]>([]);
166
+ const [deniedPermissions, setDeniedPermissions] = React.useState<string[]>([]);
167
+ const [runtimePermissionsUnavailable, setRuntimePermissionsUnavailable] = React.useState(false);
168
+ const [runtimeAuthorizationContextKey, setRuntimeAuthorizationContextKey] =
169
+ React.useState<string | null>(null);
170
+ const [servicePermissionCatalog, setServicePermissionCatalog] = React.useState<ServicePermission[]>([]);
171
+ const availablePermissions = React.useMemo(
172
+ () => maximumPermissions.map((label) =>
173
+ servicePermissionCatalog.find((permission) => permission.label === label) ?? {
174
+ id: label,
175
+ label,
176
+ description: '',
177
+ groups: [],
178
+ }),
179
+ [maximumPermissions, servicePermissionCatalog],
180
+ );
151
181
  const [showPermissionForm, setShowPermissionForm] = React.useState(false);
152
182
  const [isPermissionFormOpen, setIsPermissionFormOpen] = React.useState(false);
153
183
  const [isAITLoading, setIsAITLoading] = React.useState(false);
@@ -156,6 +186,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
156
186
  const [walletInfo, setWalletInfo] = React.useState<any>(null);
157
187
  const [isWalletLoading, setIsWalletLoading] = React.useState(false);
158
188
  const [isAutoConnecting, setIsAutoConnecting] = React.useState(false);
189
+ const runtimeRolesKey = JSON.stringify(roles ?? null);
159
190
 
160
191
  // Persistent data (always use localStorage)
161
192
  const [nxtlinqAITServiceAccessToken, setNxtlinqAITServiceAccessToken] = useLocalStorage<string>('nxtlinqAITServiceAccessToken', '');
@@ -212,8 +243,17 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
212
243
  const hitAddressRef = React.useRef(hitAddress);
213
244
  const aitRef = React.useRef(ait);
214
245
  const permissionsRef = React.useRef(permissions);
246
+ const deniedPermissionsRef = React.useRef(deniedPermissions);
215
247
  const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
216
248
  const signerRef = React.useRef(signer);
249
+ const runtimeRecomputeCoordinatorRef = React.useRef(new RuntimeAITRecomputeCoordinator());
250
+ const runtimeMutationQueueRef = React.useRef(new RuntimeAITMutationQueue());
251
+ useProviderRuntimeCommit(
252
+ hitAddressRef,
253
+ hitAddress,
254
+ environment,
255
+ setApiHosts,
256
+ );
217
257
 
218
258
  // Helper function to combine customUsername with Adilas customUserInfo
219
259
  const getFinalCustomUsername = React.useCallback((username: string | undefined): string | undefined => {
@@ -546,10 +586,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
546
586
  }, []);
547
587
 
548
588
  // Update refs when state changes
549
- React.useEffect(() => {
550
- hitAddressRef.current = hitAddress;
551
- }, [hitAddress]);
552
-
553
589
  React.useEffect(() => {
554
590
  aitRef.current = ait;
555
591
  }, [ait]);
@@ -558,6 +594,10 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
558
594
  permissionsRef.current = permissions;
559
595
  }, [permissions]);
560
596
 
597
+ React.useEffect(() => {
598
+ deniedPermissionsRef.current = deniedPermissions;
599
+ }, [deniedPermissions]);
600
+
561
601
  React.useEffect(() => {
562
602
  nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
563
603
  }, [nxtlinqAITServiceAccessToken]);
@@ -979,7 +1019,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
979
1019
  console.error('Failed to fetch permissions:', result.error);
980
1020
  return undefined;
981
1021
  }
982
- setAvailablePermissions(result.permissions);
1022
+ setServicePermissionCatalog(result.permissions);
983
1023
  return result.permissions;
984
1024
  } catch (error) {
985
1025
  console.error('Error fetching permissions:', error);
@@ -987,12 +1027,116 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
987
1027
  }
988
1028
  };
989
1029
 
990
- // Refresh AIT
1030
+ const clearRuntimeAuthorization = React.useCallback(() => {
1031
+ const cleared = unavailableRuntimeAuthorization(deniedPermissionsRef.current);
1032
+ const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
1033
+ aitRef.current = null;
1034
+ permissionsRef.current = [];
1035
+ setAit(cleared.ait);
1036
+ setPermissions(cleared.effectivePermissions);
1037
+ setMaximumPermissions(cleared.maximumPermissions);
1038
+ setRuntimePermissionsUnavailable(cleared.unavailable);
1039
+ setRuntimeAuthorizationContextKey(
1040
+ activeContext ? runtimeAITContextKey(activeContext) : null,
1041
+ );
1042
+ }, []);
1043
+
1044
+ const applyRuntimeResult = React.useCallback((result: RuntimeAITResult, context: RuntimeAITContext) => {
1045
+ const next = runtimeAuthorizationState(result, context.controller, context.serviceId);
1046
+ aitRef.current = next.ait;
1047
+ permissionsRef.current = next.effectivePermissions;
1048
+ setAit(next.ait);
1049
+ setPermissions(next.effectivePermissions);
1050
+ setMaximumPermissions(next.maximumPermissions);
1051
+ setDeniedPermissions(next.deniedPermissions);
1052
+ deniedPermissionsRef.current = next.deniedPermissions;
1053
+ setRuntimePermissionsUnavailable(false);
1054
+ setRuntimeAuthorizationContextKey(runtimeAITContextKey(context));
1055
+ return result;
1056
+ }, []);
1057
+
1058
+ const runtimeContext = React.useMemo<RuntimeAITContext | null>(() => hitAddress ? {
1059
+ controller: hitAddress,
1060
+ serviceId,
1061
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
1062
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
1063
+ } : null, [hitAddress, permissionGroup, runtimeRolesKey, serviceId]);
1064
+ const invalidateCommittedRuntimeContext = React.useCallback(() => {
1065
+ const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
1066
+ aitRef.current = null;
1067
+ permissionsRef.current = [];
1068
+ deniedPermissionsRef.current = [];
1069
+ setAit(null);
1070
+ setPermissions([]);
1071
+ setMaximumPermissions([]);
1072
+ setDeniedPermissions([]);
1073
+ setRuntimePermissionsUnavailable(activeContext !== null);
1074
+ setRuntimeAuthorizationContextKey(
1075
+ activeContext ? runtimeAITContextKey(activeContext) : null,
1076
+ );
1077
+ }, []);
1078
+ useRuntimeAITContextCommit(
1079
+ runtimeRecomputeCoordinatorRef.current,
1080
+ runtimeContext,
1081
+ invalidateCommittedRuntimeContext,
1082
+ );
1083
+
1084
+ const recomputeRuntimeAIT = React.useCallback(async (context: RuntimeAITContext) => {
1085
+ const capturedContext: RuntimeAITContext = {
1086
+ controller: context.controller,
1087
+ serviceId: context.serviceId,
1088
+ ...(context.roles !== undefined ? { roles: [...context.roles] } : {}),
1089
+ ...(context.permissionGroup !== undefined ? { permissionGroup: context.permissionGroup } : {}),
1090
+ };
1091
+ return runtimeRecomputeCoordinatorRef.current.run(
1092
+ capturedContext,
1093
+ () => nxtlinqApi.ait.recomputeAIT(capturedContext),
1094
+ applyRuntimeResult,
1095
+ clearRuntimeAuthorization,
1096
+ );
1097
+ }, [applyRuntimeResult, clearRuntimeAuthorization, nxtlinqApi]);
1098
+
1099
+ const queueRuntimeAITRefresh = React.useCallback(
1100
+ (context: RuntimeAITContext) => runtimeMutationQueueRef.current.run(
1101
+ runtimeAITSubjectKey(context),
1102
+ async () => {
1103
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
1104
+ return recomputeRuntimeAIT(context);
1105
+ },
1106
+ ),
1107
+ [recomputeRuntimeAIT],
1108
+ );
1109
+
1110
+ const convergeRuntimeAITSubject = React.useCallback(async (subjectKey: string) => {
1111
+ const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
1112
+ if (!activeContext || runtimeAITSubjectKey(activeContext) !== subjectKey) {
1113
+ return;
1114
+ }
1115
+ await recomputeRuntimeAIT(activeContext);
1116
+ }, [recomputeRuntimeAIT]);
1117
+
1118
+ const dispatchRuntimeDenyListReplacement = React.useCallback(async (
1119
+ context: RuntimeAITContext,
1120
+ nextDeniedPermissions: string[],
1121
+ ) => dispatchRuntimeAITCompleteReplacement(
1122
+ runtimeRecomputeCoordinatorRef.current,
1123
+ context,
1124
+ () => nxtlinqApi.ait.updatePermissionDenyList({
1125
+ ...context,
1126
+ deniedPermissions: nextDeniedPermissions,
1127
+ }),
1128
+ convergeRuntimeAITSubject,
1129
+ clearRuntimeAuthorization,
1130
+ ), [clearRuntimeAuthorization, convergeRuntimeAITSubject, nxtlinqApi]);
1131
+
1132
+ // Refresh the authoritative runtime AIT state.
991
1133
  const refreshAIT = async (forceUpdatePermissions = false) => {
992
1134
  if (!hitAddress) {
993
1135
  setAit(null);
994
1136
  setPermissions([]);
1137
+ setMaximumPermissions([]);
995
1138
  setWalletInfo(null);
1139
+ setRuntimePermissionsUnavailable(false);
996
1140
  setIsAITLoading(false);
997
1141
  return;
998
1142
  }
@@ -1017,35 +1161,18 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1017
1161
  }
1018
1162
  }
1019
1163
 
1020
- // Only try to fetch AIT if we have a token
1164
+ // Recompute is the sole mint/reuse path and the only permission authority.
1021
1165
  if (nxtlinqAITServiceAccessToken) {
1022
- const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
1023
- serviceId,
1024
- controller: hitAddress,
1025
- customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
1026
- }, nxtlinqAITServiceAccessToken);
1027
-
1028
- if ('error' in response) {
1029
- console.error('Failed to fetch AIT:', response.error);
1030
- setAit(null);
1031
- setPermissions([]);
1032
- return;
1033
- }
1034
-
1035
- setAit(response);
1036
- if (!isPermissionFormOpen || forceUpdatePermissions) {
1037
- const newPermissions = response.metadata?.permissions || [];
1038
- setPermissions(newPermissions);
1166
+ if (runtimeContext) {
1167
+ await queueRuntimeAITRefresh(runtimeContext);
1039
1168
  }
1040
1169
  } else {
1041
- // No token available, clear AIT data
1042
- setAit(null);
1043
- setPermissions([]);
1170
+ clearRuntimeAuthorization();
1044
1171
  }
1045
1172
  } catch (error) {
1046
- console.error('Failed to fetch AIT:', error);
1047
- setAit(null);
1048
- setPermissions([]);
1173
+ if (!(error instanceof StaleRuntimeAITResponseError)) {
1174
+ console.error('Failed to recompute AIT:', error);
1175
+ }
1049
1176
  } finally {
1050
1177
  setIsAITLoading(false);
1051
1178
  }
@@ -1250,10 +1377,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
1250
1377
  }
1251
1378
  const { accessToken } = response;
1252
1379
  setNxtlinqAITServiceAccessToken(accessToken);
1253
-
1254
- // Wait for state to update before refreshing AIT
1255
- await new Promise(resolve => setTimeout(resolve, 100));
1256
- await refreshAIT();
1380
+ localStorage.setItem('nxtlinqAITServiceAccessToken', JSON.stringify(accessToken));
1381
+ await queueRuntimeAITRefresh({
1382
+ controller: addressToUse,
1383
+ serviceId,
1384
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
1385
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
1386
+ });
1257
1387
 
1258
1388
  if (autoShowSuccessMessage) {
1259
1389
  showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
@@ -2717,109 +2847,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2717
2847
  }
2718
2848
  };
2719
2849
 
2720
- // Generate and register AIT
2721
- const generateAndRegisterAIT = async (newPermissions?: string[], isFromAIAgent = false) => {
2722
- let currentSigner = signer;
2723
- let currentAddress = hitAddress;
2724
-
2725
- // If we have an address but no signer, try to auto-connect wallet
2726
- if (currentAddress && !currentSigner) {
2727
- const storedWalletAddress = localStorage.getItem('walletAddress');
2728
- if (storedWalletAddress === currentAddress) {
2729
- try {
2730
- const autoConnectResult = await connectWallet(false); // Don't show sign-in message yet
2731
- if (autoConnectResult) {
2732
- // Wait for state to update
2733
- await new Promise(resolve => setTimeout(resolve, 1000));
2734
- // Get the updated signer and address
2735
- currentSigner = signerRef.current || signer;
2736
- currentAddress = hitAddressRef.current || hitAddress;
2737
- }
2738
- } catch (error) {
2739
- console.error('Auto-connect failed during AIT generation:', error);
2740
- }
2741
- }
2742
- }
2743
-
2744
- if (!currentSigner || !currentAddress) {
2745
- throw new Error('Missing signer or wallet address');
2746
- }
2747
-
2748
- return generateAndRegisterAITWithSigner(newPermissions, isFromAIAgent, currentSigner, currentAddress);
2749
- };
2750
-
2751
- // Generate and register AIT with explicit signer and address
2752
- const generateAndRegisterAITWithSigner = async (
2753
- newPermissions?: string[],
2754
- isFromAIAgent = false,
2755
- explicitSigner?: ethers.JsonRpcSigner,
2756
- explicitAddress?: string
2757
- ) => {
2758
- const currentSigner = explicitSigner || signer;
2759
- const currentAddress = explicitAddress || hitAddress;
2760
-
2761
- if (!currentSigner || !currentAddress) {
2762
- throw new Error('Missing signer or wallet address');
2763
- }
2764
-
2765
- // If AI Agent is creating, must check if user has existing AIT
2766
- if (isFromAIAgent && !aitRef.current) {
2767
- throw new Error('You must have an existing AIT before AI Agent can create new AITs');
2768
- }
2769
-
2770
- // aitId is assigned by AIT-Service (UUIDv7) and returned in the response. It must not be
2771
- // minted here: the client cannot guarantee uniqueness, and this value feeds parentAITId, which
2772
- // is part of the on-chain commitment -- a locally invented id would anchor a parent that does
2773
- // not exist.
2774
-
2775
- const metadata = {
2776
- permissions: newPermissions || permissions,
2777
- issuedBy: currentAddress,
2778
- };
2779
-
2780
- const metadataStr = stringify(metadata);
2781
- // ethers v6: utils.keccak256 -> keccak256, utils.toUtf8Bytes -> toUtf8Bytes
2782
- const metadataHash = ethers.keccak256(ethers.toUtf8Bytes(metadataStr));
2783
-
2784
- // AIT-Service no longer publishes metadata to IPFS: the registry is hash-only, integrity comes
2785
- // from the on-chain commitment, and /api/metadata has been removed. `permissions` now travels
2786
- // in the request body instead of being recovered from a CID, so nothing is uploaded here.
2787
-
2788
- const createAITParams = {
2789
- controller: currentAddress,
2790
- serviceId,
2791
- permissions: metadata.permissions,
2792
- metadataHash,
2793
- isFromAIAgent: isFromAIAgent,
2794
- parentAITId: isFromAIAgent ? aitRef.current?.aitId : undefined,
2795
- };
2796
-
2797
- const createAITResponse = await nxtlinqApi.ait.createAIT(createAITParams, nxtlinqAITServiceAccessToken || '');
2798
-
2799
- if ('error' in createAITResponse) {
2800
- throw new Error(`Failed to create AIT: ${createAITResponse.error}`);
2801
- }
2802
-
2803
- const aitId = (createAITResponse as { aitId?: string }).aitId;
2804
- if (!aitId) {
2805
- // Surfaced rather than substituted: this id becomes parentAITId on agent-created AITs, and
2806
- // parentAITId is committed on chain. Guessing one would anchor a dangling parent link.
2807
- throw new Error('AIT-Service did not return an aitId; refusing to invent one.');
2808
- }
2809
-
2810
- const aitInfo = {
2811
- aitId,
2812
- controller: currentAddress,
2813
- metadata,
2814
- metadataHash,
2815
- };
2816
-
2817
- setAit(aitInfo);
2818
- setPermissions(newPermissions || permissions);
2819
- };
2820
-
2821
-
2822
-
2823
2850
  // Auto enable AIT permission
2824
2851
  const enableAIT = async (toolName: string) => {
2825
2852
  if (isAITEnabling) return false; // Prevent duplicate
@@ -2886,40 +2913,45 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2886
2913
  }
2887
2914
 
2888
2915
  try {
2889
- // Get available permissions to find the tool
2890
- const availablePermissionLabels = availablePermissions.map(p => p.label);
2891
- if (!availablePermissionLabels.includes(toolName)) {
2916
+ const context: RuntimeAITContext = {
2917
+ controller: currentHitAddress,
2918
+ serviceId,
2919
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
2920
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
2921
+ };
2922
+ const enabled = await runtimeMutationQueueRef.current.run(
2923
+ runtimeAITSubjectKey(context),
2924
+ async () => {
2925
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2926
+ const current = await recomputeRuntimeAIT(context);
2927
+ if (!current.maximumPermissions.includes(toolName)) {
2928
+ return false;
2929
+ }
2930
+ const nextDeniedPermissions = removeOneDeniedPermission(
2931
+ current.deniedPermissions,
2932
+ toolName,
2933
+ );
2934
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2935
+ await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
2936
+ return true;
2937
+ },
2938
+ );
2939
+ if (!enabled) {
2892
2940
  showError(`Tool ${toolName} is not available for your current identity provider`);
2893
2941
  setIsAITEnabling(false);
2894
2942
  return false;
2895
2943
  }
2896
-
2897
- // Get current permissions from AIT metadata instead of React state
2898
- // This ensures we don't lose existing permissions when enabling new ones
2899
- const currentAITPermissions = aitRef.current?.metadata?.permissions || permissions;
2900
- const newPermissions = [...currentAITPermissions];
2901
- if (!newPermissions.includes(toolName)) {
2902
- newPermissions.push(toolName);
2903
- }
2904
-
2905
- // Filter out deleted permissions before saving
2906
- const validPermissions = newPermissions.filter(permission =>
2907
- availablePermissionLabels.includes(permission)
2908
- );
2909
-
2910
- // Generate and register AIT with new permissions
2911
- // For auto-enable, we should create a regular AIT (not AI Agent AIT) if user doesn't have existing AIT
2912
- const shouldCreateAsAIAgent = !!aitRef.current; // Only create as AI Agent if user already has an AIT
2913
-
2914
- await generateAndRegisterAITWithSigner(validPermissions, shouldCreateAsAIAgent, currentSigner, currentHitAddress);
2915
2944
  showSuccess('AIT permission enabled successfully! You can now use the AI agent.');
2916
- await refreshAIT(true);
2917
2945
  setIsAITEnabling(false);
2918
2946
  return true;
2919
2947
  } catch (error) {
2920
- console.error('Failed to auto-enable AIT:', error);
2948
+ if (!(error instanceof StaleRuntimeAITResponseError)) {
2949
+ console.error('Failed to auto-enable AIT:', error);
2950
+ }
2921
2951
  setIsAITEnabling(false);
2922
- if (error instanceof Error) {
2952
+ if (error instanceof StaleRuntimeAITResponseError) {
2953
+ return false;
2954
+ } else if (error instanceof Error) {
2923
2955
  showError(error.message);
2924
2956
  } else {
2925
2957
  showError('Failed to enable AIT permission. Please try again.');
@@ -2932,22 +2964,41 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2932
2964
  const savePermissions = async (newPermissions?: string[]) => {
2933
2965
  setIsDisabled(true);
2934
2966
  try {
2935
- // Filter out deleted permissions before saving
2936
- const permissionsToSave = newPermissions || permissions;
2937
- const availablePermissionLabels = availablePermissions.map(p => p.label);
2938
- const validPermissions = permissionsToSave.filter(permission =>
2939
- availablePermissionLabels.includes(permission)
2967
+ if (!hitAddress) {
2968
+ throw new Error('Please connect your wallet first');
2969
+ }
2970
+ const context: RuntimeAITContext = {
2971
+ controller: hitAddress,
2972
+ serviceId,
2973
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
2974
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
2975
+ };
2976
+ const checkedPermissions = [...(newPermissions ?? permissions)];
2977
+ await runtimeMutationQueueRef.current.run(
2978
+ runtimeAITSubjectKey(context),
2979
+ async () => {
2980
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2981
+ const fresh = await recomputeRuntimeAIT(context);
2982
+ const nextDeniedPermissions = replaceCurrentMaximumDenyList(
2983
+ fresh.deniedPermissions,
2984
+ fresh.maximumPermissions,
2985
+ checkedPermissions,
2986
+ );
2987
+ runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
2988
+ await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
2989
+ },
2940
2990
  );
2941
-
2942
- await generateAndRegisterAIT(validPermissions, false);
2943
2991
  showSuccess('AIT permissions saved successfully! You can now use the AI agent with your configured permissions.');
2944
2992
  setShowPermissionForm(false);
2945
2993
  setIsPermissionFormOpen(false);
2946
- await refreshAIT(true);
2947
2994
  } catch (error) {
2948
- console.error('Failed to generate AIT:', error);
2995
+ if (!(error instanceof StaleRuntimeAITResponseError)) {
2996
+ console.error('Failed to update AIT deny-list:', error);
2997
+ }
2949
2998
  setIsDisabled(false);
2950
- if (error instanceof Error) {
2999
+ if (error instanceof StaleRuntimeAITResponseError) {
3000
+ return;
3001
+ } else if (error instanceof Error) {
2951
3002
  showError(error.message);
2952
3003
  } else {
2953
3004
  showError('Failed to save permissions. Please try again.');
@@ -2996,14 +3047,12 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
2996
3047
  const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
2997
3048
  if (!('error' in walletResponse)) {
2998
3049
  setWalletInfo(walletResponse);
2999
- const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({
3000
- serviceId,
3050
+ await queueRuntimeAITRefresh({
3001
3051
  controller: address,
3002
- customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
3003
- }, token);
3004
- if (!('error' in aitResponse)) {
3005
- setAit(aitResponse);
3006
- }
3052
+ serviceId,
3053
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
3054
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
3055
+ });
3007
3056
  }
3008
3057
  } finally {
3009
3058
  setIsWalletLoading(false);
@@ -3034,14 +3083,12 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
3034
3083
  const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
3035
3084
  if (!('error' in walletResponse)) {
3036
3085
  setWalletInfo(walletResponse);
3037
- const aitResponse = await nxtlinqApi.ait.getAITByServiceIdAndController({
3038
- serviceId,
3086
+ await queueRuntimeAITRefresh({
3039
3087
  controller: address,
3040
- customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
3041
- }, token);
3042
- if (!('error' in aitResponse)) {
3043
- setAit(aitResponse);
3044
- }
3088
+ serviceId,
3089
+ ...(roles !== undefined ? { roles: [...roles] } : {}),
3090
+ ...(permissionGroup !== undefined ? { permissionGroup } : {}),
3091
+ });
3045
3092
  }
3046
3093
  } finally {
3047
3094
  setIsWalletLoading(false);
@@ -3148,7 +3195,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
3148
3195
  if (hitAddress && nxtlinqAITServiceAccessToken) {
3149
3196
  refreshAIT();
3150
3197
  }
3151
- }, [hitAddress, nxtlinqAITServiceAccessToken]);
3198
+ }, [hitAddress, nxtlinqAITServiceAccessToken, permissionGroup, runtimeRolesKey, serviceId]);
3152
3199
 
3153
3200
  // Set loading state when permission form opens
3154
3201
  React.useEffect(() => {
@@ -3236,6 +3283,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
3236
3283
  updateExternalId()
3237
3284
  }, [hitAddress, nxtlinqApi, authFields, pseudoId]);
3238
3285
 
3286
+ const runtimeAuthorizationMatchesRenderedContext =
3287
+ runtimeAuthorizationMatchesContext(runtimeAuthorizationContextKey, runtimeContext);
3239
3288
  const contextValue: ChatBotContextType = {
3240
3289
  // State
3241
3290
  messages,
@@ -3243,9 +3292,16 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
3243
3292
  isLoading,
3244
3293
  isOpen,
3245
3294
  hitAddress,
3246
- ait,
3247
- permissions,
3248
- availablePermissions,
3295
+ ait: runtimeAuthorizationMatchesRenderedContext ? ait : null,
3296
+ permissions: runtimeAuthorizationMatchesRenderedContext ? permissions : [],
3297
+ maximumPermissions: runtimeAuthorizationMatchesRenderedContext ? maximumPermissions : [],
3298
+ deniedPermissions: runtimeAuthorizationMatchesRenderedContext ? deniedPermissions : [],
3299
+ runtimePermissionsUnavailable: runtimeAuthorizationMatchesRenderedContext
3300
+ ? runtimePermissionsUnavailable
3301
+ : runtimeContext !== null,
3302
+ availablePermissions: runtimeAuthorizationMatchesRenderedContext
3303
+ ? availablePermissions
3304
+ : [],
3249
3305
  showPermissionForm,
3250
3306
  isPermissionFormOpen,
3251
3307
  isAITLoading,
@@ -3282,7 +3338,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
3282
3338
  setIsOpen,
3283
3339
  setShowPermissionForm,
3284
3340
  setIsPermissionFormOpen,
3285
- setPermissions,
3286
3341
  setIsDisabled,
3287
3342
  setIsWalletLoading,
3288
3343
  setNotification,
@@ -3332,6 +3387,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
3332
3387
  onVerifyWallet: (method: 'berifyme' | 'custom') => handleVerifyWalletClick(method),
3333
3388
  serviceId,
3334
3389
  permissionGroup,
3390
+ roles,
3335
3391
 
3336
3392
  // Props
3337
3393
  props: {
@@ -3350,6 +3406,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
3350
3406
  getAuthToken,
3351
3407
  onVerifyWallet,
3352
3408
  permissionGroup,
3409
+ roles,
3353
3410
  onModelChange,
3354
3411
  storageMode,
3355
3412
  requireWalletIDVVerification,
package/src/index.ts CHANGED
@@ -14,7 +14,6 @@ export {
14
14
 
15
15
  export type {
16
16
  AIModel,
17
- AITMetadata,
18
17
  ChatBotContextType,
19
18
  ChatBotProps,
20
19
  NovaError,