@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.
- package/dist/context/ChatBotContext.d.ts.map +1 -1
- package/dist/context/ChatBotContext.js +182 -222
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types/ChatBotTypes.d.ts +6 -5
- package/dist/types/ChatBotTypes.d.ts.map +1 -1
- package/dist/ui/ModelSelector.js +2 -2
- package/dist/ui/PermissionForm.d.ts.map +1 -1
- package/dist/ui/PermissionForm.js +14 -28
- package/package.json +4 -4
- package/src/context/ChatBotContext.tsx +255 -259
- package/src/index.ts +0 -1
- package/src/permissionState.ts +313 -0
- package/src/types/ChatBotTypes.ts +6 -6
- package/src/ui/ModelSelector.tsx +5 -5
- package/src/ui/PermissionForm.tsx +19 -30
|
@@ -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 {
|
|
@@ -36,11 +37,26 @@ import {
|
|
|
36
37
|
ChatBotProps,
|
|
37
38
|
PresetMessage
|
|
38
39
|
} from '../types/ChatBotTypes';
|
|
39
|
-
import { waitForAITPermissions } from '../aitPermissionPropagation';
|
|
40
40
|
import { AuthorizationLoadingState } from '../authorizationLoadingState';
|
|
41
41
|
import { PendingTextToolRetryController } from '../pendingTextToolRetry';
|
|
42
42
|
import { completePiiScan } from '../piiMessageState';
|
|
43
43
|
import { filterSuggestions } from '../suggestionState';
|
|
44
|
+
import {
|
|
45
|
+
dispatchRuntimeAITCompleteReplacement,
|
|
46
|
+
removeOneDeniedPermission,
|
|
47
|
+
replaceCurrentMaximumDenyList,
|
|
48
|
+
RuntimeAITMutationQueue,
|
|
49
|
+
RuntimeAITRecomputeCoordinator,
|
|
50
|
+
StaleRuntimeAITResponseError,
|
|
51
|
+
runtimeAITContextKey,
|
|
52
|
+
runtimeAITSubjectKey,
|
|
53
|
+
runtimeAuthorizationMatchesContext,
|
|
54
|
+
runtimeAuthorizationState,
|
|
55
|
+
unavailableRuntimeAuthorization,
|
|
56
|
+
useProviderRuntimeCommit,
|
|
57
|
+
useRuntimeAITContextCommit,
|
|
58
|
+
type RuntimeAITContext,
|
|
59
|
+
} from '../permissionState';
|
|
44
60
|
|
|
45
61
|
const MIC_ENABLED_SESSION_KEY = 'chatbot-mic-enabled';
|
|
46
62
|
|
|
@@ -71,6 +87,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
71
87
|
environment = 'production',
|
|
72
88
|
onVerifyWallet,
|
|
73
89
|
permissionGroup,
|
|
90
|
+
roles,
|
|
74
91
|
children,
|
|
75
92
|
// AI Model related attributes
|
|
76
93
|
onModelChange,
|
|
@@ -93,9 +110,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
93
110
|
debugVoiceRms = false,
|
|
94
111
|
sttGlossary,
|
|
95
112
|
}) => {
|
|
96
|
-
// Set API hosts immediately based on environment (before any API calls)
|
|
97
|
-
setApiHosts(environment);
|
|
98
|
-
|
|
99
113
|
const nxtlinqApi = React.useMemo(
|
|
100
114
|
() => (serviceToken || getAuthToken
|
|
101
115
|
? createNxtlinqApi({ apiKey, apiSecret, serviceToken, getAuthToken })
|
|
@@ -155,7 +169,22 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
155
169
|
const [hitAddress, setHitAddress] = React.useState<string | null>(null);
|
|
156
170
|
const [ait, setAit] = React.useState<AIT | null>(null);
|
|
157
171
|
const [permissions, setPermissions] = React.useState<string[]>([]);
|
|
158
|
-
const [
|
|
172
|
+
const [maximumPermissions, setMaximumPermissions] = React.useState<string[]>([]);
|
|
173
|
+
const [deniedPermissions, setDeniedPermissions] = React.useState<string[]>([]);
|
|
174
|
+
const [runtimePermissionsUnavailable, setRuntimePermissionsUnavailable] = React.useState(false);
|
|
175
|
+
const [runtimeAuthorizationContextKey, setRuntimeAuthorizationContextKey] =
|
|
176
|
+
React.useState<string | null>(null);
|
|
177
|
+
const [servicePermissionCatalog, setServicePermissionCatalog] = React.useState<ServicePermission[]>([]);
|
|
178
|
+
const availablePermissions = React.useMemo(
|
|
179
|
+
() => maximumPermissions.map((label) =>
|
|
180
|
+
servicePermissionCatalog.find((permission) => permission.label === label) ?? {
|
|
181
|
+
id: label,
|
|
182
|
+
label,
|
|
183
|
+
description: '',
|
|
184
|
+
groups: [],
|
|
185
|
+
}),
|
|
186
|
+
[maximumPermissions, servicePermissionCatalog],
|
|
187
|
+
);
|
|
159
188
|
const [showPermissionForm, setShowPermissionForm] = React.useState(false);
|
|
160
189
|
const [isPermissionFormOpen, setIsPermissionFormOpen] = React.useState(false);
|
|
161
190
|
const [isAITLoading, setIsAITLoading] = React.useState(false);
|
|
@@ -164,6 +193,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
164
193
|
const [walletInfo, setWalletInfo] = React.useState<any>(null);
|
|
165
194
|
const [isWalletLoading, setIsWalletLoading] = React.useState(false);
|
|
166
195
|
const [isAutoConnecting, setIsAutoConnecting] = React.useState(false);
|
|
196
|
+
const runtimeRolesKey = JSON.stringify(roles ?? null);
|
|
167
197
|
|
|
168
198
|
// Persistent data (always use localStorage)
|
|
169
199
|
const [nxtlinqAITServiceAccessToken, setNxtlinqAITServiceAccessToken] = useLocalStorage<string>('nxtlinqAITServiceAccessToken', '');
|
|
@@ -220,6 +250,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
220
250
|
const hitAddressRef = React.useRef(hitAddress);
|
|
221
251
|
const aitRef = React.useRef(ait);
|
|
222
252
|
const permissionsRef = React.useRef(permissions);
|
|
253
|
+
const deniedPermissionsRef = React.useRef(deniedPermissions);
|
|
223
254
|
const nxtlinqAITServiceAccessTokenRef = React.useRef(nxtlinqAITServiceAccessToken);
|
|
224
255
|
const signerRef = React.useRef(signer);
|
|
225
256
|
const authorizationLoadingStateRef = React.useRef(
|
|
@@ -241,6 +272,14 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
241
272
|
);
|
|
242
273
|
});
|
|
243
274
|
}, []);
|
|
275
|
+
const runtimeRecomputeCoordinatorRef = React.useRef(new RuntimeAITRecomputeCoordinator());
|
|
276
|
+
const runtimeMutationQueueRef = React.useRef(new RuntimeAITMutationQueue());
|
|
277
|
+
useProviderRuntimeCommit(
|
|
278
|
+
hitAddressRef,
|
|
279
|
+
hitAddress,
|
|
280
|
+
environment,
|
|
281
|
+
setApiHosts,
|
|
282
|
+
);
|
|
244
283
|
|
|
245
284
|
// Helper function to combine customUsername with Adilas customUserInfo
|
|
246
285
|
const getFinalCustomUsername = React.useCallback((username: string | undefined): string | undefined => {
|
|
@@ -574,10 +613,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
574
613
|
}, []);
|
|
575
614
|
|
|
576
615
|
// Update refs when state changes
|
|
577
|
-
React.useEffect(() => {
|
|
578
|
-
hitAddressRef.current = hitAddress;
|
|
579
|
-
}, [hitAddress]);
|
|
580
|
-
|
|
581
616
|
React.useEffect(() => {
|
|
582
617
|
aitRef.current = ait;
|
|
583
618
|
}, [ait]);
|
|
@@ -594,6 +629,10 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
594
629
|
authorizationLoadingStateRef.current.setAutoConnecting(isAutoConnecting);
|
|
595
630
|
}, [isAutoConnecting]);
|
|
596
631
|
|
|
632
|
+
React.useEffect(() => {
|
|
633
|
+
deniedPermissionsRef.current = deniedPermissions;
|
|
634
|
+
}, [deniedPermissions]);
|
|
635
|
+
|
|
597
636
|
React.useEffect(() => {
|
|
598
637
|
nxtlinqAITServiceAccessTokenRef.current = nxtlinqAITServiceAccessToken;
|
|
599
638
|
}, [nxtlinqAITServiceAccessToken]);
|
|
@@ -1024,7 +1063,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1024
1063
|
console.error('Failed to fetch permissions:', result.error);
|
|
1025
1064
|
return undefined;
|
|
1026
1065
|
}
|
|
1027
|
-
|
|
1066
|
+
setServicePermissionCatalog(result.permissions);
|
|
1028
1067
|
return result.permissions;
|
|
1029
1068
|
} catch (error) {
|
|
1030
1069
|
console.error('Error fetching permissions:', error);
|
|
@@ -1032,7 +1071,109 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1032
1071
|
}
|
|
1033
1072
|
};
|
|
1034
1073
|
|
|
1035
|
-
|
|
1074
|
+
const clearRuntimeAuthorization = React.useCallback(() => {
|
|
1075
|
+
const cleared = unavailableRuntimeAuthorization(deniedPermissionsRef.current);
|
|
1076
|
+
const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
|
|
1077
|
+
aitRef.current = null;
|
|
1078
|
+
permissionsRef.current = [];
|
|
1079
|
+
setAit(cleared.ait);
|
|
1080
|
+
setPermissions(cleared.effectivePermissions);
|
|
1081
|
+
setMaximumPermissions(cleared.maximumPermissions);
|
|
1082
|
+
setRuntimePermissionsUnavailable(cleared.unavailable);
|
|
1083
|
+
setRuntimeAuthorizationContextKey(
|
|
1084
|
+
activeContext ? runtimeAITContextKey(activeContext) : null,
|
|
1085
|
+
);
|
|
1086
|
+
}, []);
|
|
1087
|
+
|
|
1088
|
+
const applyRuntimeResult = React.useCallback((result: RuntimeAITResult, context: RuntimeAITContext) => {
|
|
1089
|
+
const next = runtimeAuthorizationState(result, context.controller, context.serviceId);
|
|
1090
|
+
aitRef.current = next.ait;
|
|
1091
|
+
permissionsRef.current = next.effectivePermissions;
|
|
1092
|
+
setAit(next.ait);
|
|
1093
|
+
setPermissions(next.effectivePermissions);
|
|
1094
|
+
setMaximumPermissions(next.maximumPermissions);
|
|
1095
|
+
setDeniedPermissions(next.deniedPermissions);
|
|
1096
|
+
deniedPermissionsRef.current = next.deniedPermissions;
|
|
1097
|
+
setRuntimePermissionsUnavailable(false);
|
|
1098
|
+
setRuntimeAuthorizationContextKey(runtimeAITContextKey(context));
|
|
1099
|
+
return result;
|
|
1100
|
+
}, []);
|
|
1101
|
+
|
|
1102
|
+
const runtimeContext = React.useMemo<RuntimeAITContext | null>(() => hitAddress ? {
|
|
1103
|
+
controller: hitAddress,
|
|
1104
|
+
serviceId,
|
|
1105
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
1106
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
1107
|
+
} : null, [hitAddress, permissionGroup, runtimeRolesKey, serviceId]);
|
|
1108
|
+
const invalidateCommittedRuntimeContext = React.useCallback(() => {
|
|
1109
|
+
const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
|
|
1110
|
+
aitRef.current = null;
|
|
1111
|
+
permissionsRef.current = [];
|
|
1112
|
+
deniedPermissionsRef.current = [];
|
|
1113
|
+
setAit(null);
|
|
1114
|
+
setPermissions([]);
|
|
1115
|
+
setMaximumPermissions([]);
|
|
1116
|
+
setDeniedPermissions([]);
|
|
1117
|
+
setRuntimePermissionsUnavailable(activeContext !== null);
|
|
1118
|
+
setRuntimeAuthorizationContextKey(
|
|
1119
|
+
activeContext ? runtimeAITContextKey(activeContext) : null,
|
|
1120
|
+
);
|
|
1121
|
+
}, []);
|
|
1122
|
+
useRuntimeAITContextCommit(
|
|
1123
|
+
runtimeRecomputeCoordinatorRef.current,
|
|
1124
|
+
runtimeContext,
|
|
1125
|
+
invalidateCommittedRuntimeContext,
|
|
1126
|
+
);
|
|
1127
|
+
|
|
1128
|
+
const recomputeRuntimeAIT = React.useCallback(async (context: RuntimeAITContext) => {
|
|
1129
|
+
const capturedContext: RuntimeAITContext = {
|
|
1130
|
+
controller: context.controller,
|
|
1131
|
+
serviceId: context.serviceId,
|
|
1132
|
+
...(context.roles !== undefined ? { roles: [...context.roles] } : {}),
|
|
1133
|
+
...(context.permissionGroup !== undefined ? { permissionGroup: context.permissionGroup } : {}),
|
|
1134
|
+
};
|
|
1135
|
+
return runtimeRecomputeCoordinatorRef.current.run(
|
|
1136
|
+
capturedContext,
|
|
1137
|
+
() => nxtlinqApi.ait.recomputeAIT(capturedContext),
|
|
1138
|
+
applyRuntimeResult,
|
|
1139
|
+
clearRuntimeAuthorization,
|
|
1140
|
+
);
|
|
1141
|
+
}, [applyRuntimeResult, clearRuntimeAuthorization, nxtlinqApi]);
|
|
1142
|
+
|
|
1143
|
+
const queueRuntimeAITRefresh = React.useCallback(
|
|
1144
|
+
(context: RuntimeAITContext) => runtimeMutationQueueRef.current.run(
|
|
1145
|
+
runtimeAITSubjectKey(context),
|
|
1146
|
+
async () => {
|
|
1147
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
1148
|
+
return recomputeRuntimeAIT(context);
|
|
1149
|
+
},
|
|
1150
|
+
),
|
|
1151
|
+
[recomputeRuntimeAIT],
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
const convergeRuntimeAITSubject = React.useCallback(async (subjectKey: string) => {
|
|
1155
|
+
const activeContext = runtimeRecomputeCoordinatorRef.current.getActiveContext();
|
|
1156
|
+
if (!activeContext || runtimeAITSubjectKey(activeContext) !== subjectKey) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
await recomputeRuntimeAIT(activeContext);
|
|
1160
|
+
}, [recomputeRuntimeAIT]);
|
|
1161
|
+
|
|
1162
|
+
const dispatchRuntimeDenyListReplacement = React.useCallback(async (
|
|
1163
|
+
context: RuntimeAITContext,
|
|
1164
|
+
nextDeniedPermissions: string[],
|
|
1165
|
+
) => dispatchRuntimeAITCompleteReplacement(
|
|
1166
|
+
runtimeRecomputeCoordinatorRef.current,
|
|
1167
|
+
context,
|
|
1168
|
+
() => nxtlinqApi.ait.updatePermissionDenyList({
|
|
1169
|
+
...context,
|
|
1170
|
+
deniedPermissions: nextDeniedPermissions,
|
|
1171
|
+
}),
|
|
1172
|
+
convergeRuntimeAITSubject,
|
|
1173
|
+
clearRuntimeAuthorization,
|
|
1174
|
+
), [clearRuntimeAuthorization, convergeRuntimeAITSubject, nxtlinqApi]);
|
|
1175
|
+
|
|
1176
|
+
// Refresh the authoritative runtime AIT state.
|
|
1036
1177
|
const refreshAIT = async (forceUpdatePermissions = false) => {
|
|
1037
1178
|
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
1038
1179
|
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
@@ -1041,8 +1182,10 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1041
1182
|
permissionsRef.current = [];
|
|
1042
1183
|
setAit(null);
|
|
1043
1184
|
setPermissions([]);
|
|
1185
|
+
setMaximumPermissions([]);
|
|
1044
1186
|
setWalletInfo(null);
|
|
1045
1187
|
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
1188
|
+
setRuntimePermissionsUnavailable(false);
|
|
1046
1189
|
setIsAITLoading(false);
|
|
1047
1190
|
return;
|
|
1048
1191
|
}
|
|
@@ -1074,53 +1217,23 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1074
1217
|
}
|
|
1075
1218
|
}
|
|
1076
1219
|
|
|
1077
|
-
// AIT existence is not identity verification. In strict mode, stop before
|
|
1078
|
-
// the legacy AIT lookup can synthesize a custom wallet record.
|
|
1079
1220
|
if (currentToken && !walletAllowsAITLookup) {
|
|
1080
|
-
|
|
1081
|
-
permissionsRef.current = [];
|
|
1082
|
-
setAit(null);
|
|
1083
|
-
setPermissions([]);
|
|
1221
|
+
clearRuntimeAuthorization();
|
|
1084
1222
|
return;
|
|
1085
1223
|
}
|
|
1086
1224
|
|
|
1087
|
-
//
|
|
1225
|
+
// Recompute is the sole mint/reuse path and the only permission authority.
|
|
1088
1226
|
if (currentToken) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
controller: currentHitAddress,
|
|
1092
|
-
customUsername: (!requireWalletIDVVerification && customUsername) ? getFinalCustomUsername(customUsername) : undefined
|
|
1093
|
-
}, currentToken);
|
|
1094
|
-
|
|
1095
|
-
if ('error' in response) {
|
|
1096
|
-
console.error('Failed to fetch AIT:', response.error);
|
|
1097
|
-
aitRef.current = null;
|
|
1098
|
-
permissionsRef.current = [];
|
|
1099
|
-
setAit(null);
|
|
1100
|
-
setPermissions([]);
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
aitRef.current = response;
|
|
1105
|
-
setAit(response);
|
|
1106
|
-
if (!isPermissionFormOpen || forceUpdatePermissions) {
|
|
1107
|
-
const newPermissions = response.metadata?.permissions || [];
|
|
1108
|
-
permissionsRef.current = newPermissions;
|
|
1109
|
-
setPermissions(newPermissions);
|
|
1227
|
+
if (runtimeContext) {
|
|
1228
|
+
await queueRuntimeAITRefresh(runtimeContext);
|
|
1110
1229
|
}
|
|
1111
1230
|
} else {
|
|
1112
|
-
|
|
1113
|
-
aitRef.current = null;
|
|
1114
|
-
permissionsRef.current = [];
|
|
1115
|
-
setAit(null);
|
|
1116
|
-
setPermissions([]);
|
|
1231
|
+
clearRuntimeAuthorization();
|
|
1117
1232
|
}
|
|
1118
1233
|
} catch (error) {
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
setAit(null);
|
|
1123
|
-
setPermissions([]);
|
|
1234
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
1235
|
+
console.error('Failed to recompute AIT:', error);
|
|
1236
|
+
}
|
|
1124
1237
|
} finally {
|
|
1125
1238
|
// Keep the authorization snapshot current before refreshAIT resolves.
|
|
1126
1239
|
// React may not commit setState until the immediate pending-tool retry.
|
|
@@ -1129,50 +1242,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1129
1242
|
}
|
|
1130
1243
|
};
|
|
1131
1244
|
|
|
1132
|
-
const waitForPersistedAITPermissions = async (
|
|
1133
|
-
expectedPermissions: string[],
|
|
1134
|
-
): Promise<boolean> => {
|
|
1135
|
-
const currentHitAddress = hitAddressRef.current || hitAddress;
|
|
1136
|
-
const currentToken = nxtlinqAITServiceAccessTokenRef.current || nxtlinqAITServiceAccessToken;
|
|
1137
|
-
if (!currentHitAddress || !currentToken) return false;
|
|
1138
|
-
|
|
1139
|
-
authorizationLoadingStateRef.current.setAITLoading(true);
|
|
1140
|
-
setIsAITLoading(true);
|
|
1141
|
-
try {
|
|
1142
|
-
const persistedAIT = await waitForAITPermissions({
|
|
1143
|
-
expectedPermissions,
|
|
1144
|
-
readAIT: async () => {
|
|
1145
|
-
try {
|
|
1146
|
-
const response = await nxtlinqApi.ait.getAITByServiceIdAndController({
|
|
1147
|
-
serviceId,
|
|
1148
|
-
controller: currentHitAddress,
|
|
1149
|
-
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
1150
|
-
? getFinalCustomUsername(customUsername)
|
|
1151
|
-
: undefined,
|
|
1152
|
-
}, currentToken);
|
|
1153
|
-
return 'error' in response ? undefined : response;
|
|
1154
|
-
} catch (error) {
|
|
1155
|
-
console.warn('AIT permission propagation check failed:', error);
|
|
1156
|
-
return undefined;
|
|
1157
|
-
}
|
|
1158
|
-
},
|
|
1159
|
-
getPermissions: (currentAIT) => currentAIT.metadata?.permissions || [],
|
|
1160
|
-
});
|
|
1161
|
-
|
|
1162
|
-
if (!persistedAIT) return false;
|
|
1163
|
-
|
|
1164
|
-
const persistedPermissions = persistedAIT.metadata?.permissions || [];
|
|
1165
|
-
aitRef.current = persistedAIT;
|
|
1166
|
-
permissionsRef.current = persistedPermissions;
|
|
1167
|
-
setAit(persistedAIT);
|
|
1168
|
-
setPermissions(persistedPermissions);
|
|
1169
|
-
return true;
|
|
1170
|
-
} finally {
|
|
1171
|
-
authorizationLoadingStateRef.current.setAITLoading(false);
|
|
1172
|
-
setIsAITLoading(false);
|
|
1173
|
-
}
|
|
1174
|
-
};
|
|
1175
|
-
|
|
1176
1245
|
// Check if user needs to sign in
|
|
1177
1246
|
const isNeedSignInWithWallet = React.useMemo(() => {
|
|
1178
1247
|
if (!hitAddress) return false;
|
|
@@ -1369,10 +1438,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1369
1438
|
const { accessToken } = response;
|
|
1370
1439
|
setNxtlinqAITServiceAccessToken(accessToken);
|
|
1371
1440
|
nxtlinqAITServiceAccessTokenRef.current = accessToken;
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1441
|
+
localStorage.setItem('nxtlinqAITServiceAccessToken', JSON.stringify(accessToken));
|
|
1442
|
+
await queueRuntimeAITRefresh({
|
|
1443
|
+
controller: addressToUse,
|
|
1444
|
+
serviceId,
|
|
1445
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
1446
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
1447
|
+
});
|
|
1376
1448
|
|
|
1377
1449
|
if (autoShowSuccessMessage) {
|
|
1378
1450
|
showSuccess(walletTextUtils.getWalletText('Successfully signed in with your HIT wallet. You can now use the AI agent.', serviceId));
|
|
@@ -1411,6 +1483,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1411
1483
|
loading: authorizationLoadingStateRef.current.isLoading(),
|
|
1412
1484
|
},
|
|
1413
1485
|
...authFields(),
|
|
1486
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
1487
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
1414
1488
|
customUsername: (!requireWalletIDVVerification && customUsername)
|
|
1415
1489
|
? getFinalCustomUsername(customUsername)
|
|
1416
1490
|
: undefined,
|
|
@@ -2482,108 +2556,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2482
2556
|
}
|
|
2483
2557
|
};
|
|
2484
2558
|
|
|
2485
|
-
// Generate and register AIT
|
|
2486
|
-
const generateAndRegisterAIT = async (newPermissions?: string[], isFromAIAgent = false) => {
|
|
2487
|
-
let currentSigner = signer;
|
|
2488
|
-
let currentAddress = hitAddress;
|
|
2489
|
-
|
|
2490
|
-
// If we have an address but no signer, try to auto-connect wallet
|
|
2491
|
-
if (currentAddress && !currentSigner) {
|
|
2492
|
-
const storedWalletAddress = localStorage.getItem('walletAddress');
|
|
2493
|
-
if (storedWalletAddress === currentAddress) {
|
|
2494
|
-
try {
|
|
2495
|
-
const autoConnectResult = await connectWallet(false); // Don't show sign-in message yet
|
|
2496
|
-
if (autoConnectResult) {
|
|
2497
|
-
// Wait for state to update
|
|
2498
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
2499
|
-
// Get the updated signer and address
|
|
2500
|
-
currentSigner = signerRef.current || signer;
|
|
2501
|
-
currentAddress = hitAddressRef.current || hitAddress;
|
|
2502
|
-
}
|
|
2503
|
-
} catch (error) {
|
|
2504
|
-
console.error('Auto-connect failed during AIT generation:', error);
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
|
|
2509
|
-
if (!currentSigner || !currentAddress) {
|
|
2510
|
-
throw new Error('Missing signer or wallet address');
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
return generateAndRegisterAITWithSigner(newPermissions, isFromAIAgent, currentSigner, currentAddress);
|
|
2514
|
-
};
|
|
2515
|
-
|
|
2516
|
-
// Generate and register AIT with explicit signer and address
|
|
2517
|
-
const generateAndRegisterAITWithSigner = async (
|
|
2518
|
-
newPermissions?: string[],
|
|
2519
|
-
isFromAIAgent = false,
|
|
2520
|
-
explicitSigner?: ethers.JsonRpcSigner,
|
|
2521
|
-
explicitAddress?: string
|
|
2522
|
-
) => {
|
|
2523
|
-
const currentSigner = explicitSigner || signer;
|
|
2524
|
-
const currentAddress = explicitAddress || hitAddress;
|
|
2525
|
-
|
|
2526
|
-
if (!currentSigner || !currentAddress) {
|
|
2527
|
-
throw new Error('Missing signer or wallet address');
|
|
2528
|
-
}
|
|
2529
|
-
|
|
2530
|
-
// If AI Agent is creating, must check if user has existing AIT
|
|
2531
|
-
if (isFromAIAgent && !aitRef.current) {
|
|
2532
|
-
throw new Error('You must have an existing AIT before AI Agent can create new AITs');
|
|
2533
|
-
}
|
|
2534
|
-
|
|
2535
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
2536
|
-
const aitId = `did:polygon:nxtlinq:${currentAddress}:${timestamp}`;
|
|
2537
|
-
|
|
2538
|
-
const metadata = {
|
|
2539
|
-
permissions: newPermissions || permissions,
|
|
2540
|
-
issuedBy: currentAddress,
|
|
2541
|
-
};
|
|
2542
|
-
|
|
2543
|
-
const metadataStr = stringify(metadata);
|
|
2544
|
-
// ethers v6: utils.keccak256 -> keccak256, utils.toUtf8Bytes -> toUtf8Bytes
|
|
2545
|
-
const metadataHash = ethers.keccak256(ethers.toUtf8Bytes(metadataStr));
|
|
2546
|
-
|
|
2547
|
-
const uploadResponse = await nxtlinqApi.metadata.createMetadata(metadata, nxtlinqAITServiceAccessToken || '');
|
|
2548
|
-
if ('error' in uploadResponse) {
|
|
2549
|
-
throw new Error(`Failed to upload metadata: ${uploadResponse.error}`);
|
|
2550
|
-
}
|
|
2551
|
-
|
|
2552
|
-
const { metadataCid } = uploadResponse;
|
|
2553
|
-
|
|
2554
|
-
const createAITParams = {
|
|
2555
|
-
aitId,
|
|
2556
|
-
controller: currentAddress,
|
|
2557
|
-
serviceId,
|
|
2558
|
-
metadataHash,
|
|
2559
|
-
metadataCid,
|
|
2560
|
-
isFromAIAgent: isFromAIAgent,
|
|
2561
|
-
parentAITId: isFromAIAgent ? aitRef.current?.aitId : undefined,
|
|
2562
|
-
};
|
|
2563
|
-
|
|
2564
|
-
const createAITResponse = await nxtlinqApi.ait.createAIT(createAITParams, nxtlinqAITServiceAccessToken || '');
|
|
2565
|
-
|
|
2566
|
-
if ('error' in createAITResponse) {
|
|
2567
|
-
throw new Error(`Failed to create AIT: ${createAITResponse.error}`);
|
|
2568
|
-
}
|
|
2569
|
-
|
|
2570
|
-
const aitInfo = {
|
|
2571
|
-
aitId,
|
|
2572
|
-
controller: currentAddress,
|
|
2573
|
-
metadata,
|
|
2574
|
-
metadataHash,
|
|
2575
|
-
metadataCid,
|
|
2576
|
-
};
|
|
2577
|
-
|
|
2578
|
-
const nextPermissions = newPermissions || permissions;
|
|
2579
|
-
aitRef.current = aitInfo;
|
|
2580
|
-
permissionsRef.current = nextPermissions;
|
|
2581
|
-
setAit(aitInfo);
|
|
2582
|
-
setPermissions(nextPermissions);
|
|
2583
|
-
};
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
2559
|
// Auto enable AIT permission
|
|
2588
2560
|
const enableAIT = async (toolName: string) => {
|
|
2589
2561
|
if (isAITEnabling) return false; // Prevent duplicate
|
|
@@ -2650,35 +2622,31 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2650
2622
|
}
|
|
2651
2623
|
|
|
2652
2624
|
try {
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2625
|
+
const context: RuntimeAITContext = {
|
|
2626
|
+
controller: currentHitAddress,
|
|
2627
|
+
serviceId,
|
|
2628
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2629
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2630
|
+
};
|
|
2631
|
+
const enabled = await runtimeMutationQueueRef.current.run(
|
|
2632
|
+
runtimeAITSubjectKey(context),
|
|
2633
|
+
async () => {
|
|
2634
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2635
|
+
const current = await recomputeRuntimeAIT(context);
|
|
2636
|
+
if (!current.maximumPermissions.includes(toolName)) {
|
|
2637
|
+
return false;
|
|
2638
|
+
}
|
|
2639
|
+
const nextDeniedPermissions = removeOneDeniedPermission(
|
|
2640
|
+
current.deniedPermissions,
|
|
2641
|
+
toolName,
|
|
2642
|
+
);
|
|
2643
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2644
|
+
await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
|
|
2645
|
+
return true;
|
|
2646
|
+
},
|
|
2672
2647
|
);
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
// For auto-enable, we should create a regular AIT (not AI Agent AIT) if user doesn't have existing AIT
|
|
2676
|
-
const shouldCreateAsAIAgent = !!aitRef.current; // Only create as AI Agent if user already has an AIT
|
|
2677
|
-
|
|
2678
|
-
await generateAndRegisterAITWithSigner(validPermissions, shouldCreateAsAIAgent, currentSigner, currentHitAddress);
|
|
2679
|
-
const permissionsPersisted = await waitForPersistedAITPermissions(validPermissions);
|
|
2680
|
-
if (!permissionsPersisted) {
|
|
2681
|
-
showWarning('AIT permission was submitted but is still synchronizing. Please try again shortly.');
|
|
2648
|
+
if (!enabled) {
|
|
2649
|
+
showError(`Tool ${toolName} is not available for your current identity provider`);
|
|
2682
2650
|
setIsAITEnabling(false);
|
|
2683
2651
|
return false;
|
|
2684
2652
|
}
|
|
@@ -2686,9 +2654,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2686
2654
|
setIsAITEnabling(false);
|
|
2687
2655
|
return true;
|
|
2688
2656
|
} catch (error) {
|
|
2689
|
-
|
|
2657
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
2658
|
+
console.error('Failed to auto-enable AIT:', error);
|
|
2659
|
+
}
|
|
2690
2660
|
setIsAITEnabling(false);
|
|
2691
|
-
if (error instanceof
|
|
2661
|
+
if (error instanceof StaleRuntimeAITResponseError) {
|
|
2662
|
+
return false;
|
|
2663
|
+
} else if (error instanceof Error) {
|
|
2692
2664
|
showError(error.message);
|
|
2693
2665
|
} else {
|
|
2694
2666
|
showError('Failed to enable AIT permission. Please try again.');
|
|
@@ -2701,23 +2673,43 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2701
2673
|
const savePermissions = async (newPermissions?: string[]) => {
|
|
2702
2674
|
setIsDisabled(true);
|
|
2703
2675
|
try {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
const
|
|
2708
|
-
|
|
2676
|
+
if (!hitAddress) {
|
|
2677
|
+
throw new Error('Please connect your wallet first');
|
|
2678
|
+
}
|
|
2679
|
+
const context: RuntimeAITContext = {
|
|
2680
|
+
controller: hitAddress,
|
|
2681
|
+
serviceId,
|
|
2682
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2683
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2684
|
+
};
|
|
2685
|
+
const checkedPermissions = [...(newPermissions ?? permissions)];
|
|
2686
|
+
await runtimeMutationQueueRef.current.run(
|
|
2687
|
+
runtimeAITSubjectKey(context),
|
|
2688
|
+
async () => {
|
|
2689
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2690
|
+
const fresh = await recomputeRuntimeAIT(context);
|
|
2691
|
+
const nextDeniedPermissions = replaceCurrentMaximumDenyList(
|
|
2692
|
+
fresh.deniedPermissions,
|
|
2693
|
+
fresh.maximumPermissions,
|
|
2694
|
+
checkedPermissions,
|
|
2695
|
+
);
|
|
2696
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2697
|
+
await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
|
|
2698
|
+
},
|
|
2709
2699
|
);
|
|
2710
|
-
|
|
2711
|
-
await generateAndRegisterAIT(validPermissions, false);
|
|
2712
2700
|
showSuccess('AIT permissions saved successfully! You can now use the AI agent with your configured permissions.');
|
|
2713
2701
|
setShowPermissionForm(false);
|
|
2714
2702
|
setIsPermissionFormOpen(false);
|
|
2715
2703
|
await refreshAIT(true);
|
|
2716
2704
|
await retryPendingTextTool();
|
|
2717
2705
|
} catch (error) {
|
|
2718
|
-
|
|
2706
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
2707
|
+
console.error('Failed to update AIT deny-list:', error);
|
|
2708
|
+
}
|
|
2719
2709
|
setIsDisabled(false);
|
|
2720
|
-
if (error instanceof
|
|
2710
|
+
if (error instanceof StaleRuntimeAITResponseError) {
|
|
2711
|
+
return;
|
|
2712
|
+
} else if (error instanceof Error) {
|
|
2721
2713
|
showError(error.message);
|
|
2722
2714
|
} else {
|
|
2723
2715
|
showError('Failed to save permissions. Please try again.');
|
|
@@ -2766,15 +2758,12 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2766
2758
|
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
2767
2759
|
if (!('error' in walletResponse)) {
|
|
2768
2760
|
setWalletInfo(walletResponse);
|
|
2769
|
-
|
|
2770
|
-
serviceId,
|
|
2761
|
+
await queueRuntimeAITRefresh({
|
|
2771
2762
|
controller: address,
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
setAit(aitResponse);
|
|
2777
|
-
}
|
|
2763
|
+
serviceId,
|
|
2764
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2765
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2766
|
+
});
|
|
2778
2767
|
}
|
|
2779
2768
|
} finally {
|
|
2780
2769
|
setIsWalletLoading(false);
|
|
@@ -2806,15 +2795,12 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2806
2795
|
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
2807
2796
|
if (!('error' in walletResponse)) {
|
|
2808
2797
|
setWalletInfo(walletResponse);
|
|
2809
|
-
|
|
2810
|
-
serviceId,
|
|
2798
|
+
await queueRuntimeAITRefresh({
|
|
2811
2799
|
controller: address,
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
setAit(aitResponse);
|
|
2817
|
-
}
|
|
2800
|
+
serviceId,
|
|
2801
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2802
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2803
|
+
});
|
|
2818
2804
|
}
|
|
2819
2805
|
} finally {
|
|
2820
2806
|
setIsWalletLoading(false);
|
|
@@ -2924,7 +2910,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2924
2910
|
if (hitAddress && nxtlinqAITServiceAccessToken) {
|
|
2925
2911
|
refreshAIT();
|
|
2926
2912
|
}
|
|
2927
|
-
}, [hitAddress, nxtlinqAITServiceAccessToken]);
|
|
2913
|
+
}, [hitAddress, nxtlinqAITServiceAccessToken, permissionGroup, runtimeRolesKey, serviceId]);
|
|
2928
2914
|
|
|
2929
2915
|
// Set loading state when permission form opens
|
|
2930
2916
|
React.useEffect(() => {
|
|
@@ -3013,6 +2999,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3013
2999
|
updateExternalId()
|
|
3014
3000
|
}, [hitAddress, nxtlinqApi, authFields, pseudoId]);
|
|
3015
3001
|
|
|
3002
|
+
const runtimeAuthorizationMatchesRenderedContext =
|
|
3003
|
+
runtimeAuthorizationMatchesContext(runtimeAuthorizationContextKey, runtimeContext);
|
|
3016
3004
|
const contextValue: ChatBotContextType = {
|
|
3017
3005
|
// State
|
|
3018
3006
|
messages,
|
|
@@ -3020,9 +3008,16 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3020
3008
|
isLoading,
|
|
3021
3009
|
isOpen,
|
|
3022
3010
|
hitAddress,
|
|
3023
|
-
ait,
|
|
3024
|
-
permissions,
|
|
3025
|
-
|
|
3011
|
+
ait: runtimeAuthorizationMatchesRenderedContext ? ait : null,
|
|
3012
|
+
permissions: runtimeAuthorizationMatchesRenderedContext ? permissions : [],
|
|
3013
|
+
maximumPermissions: runtimeAuthorizationMatchesRenderedContext ? maximumPermissions : [],
|
|
3014
|
+
deniedPermissions: runtimeAuthorizationMatchesRenderedContext ? deniedPermissions : [],
|
|
3015
|
+
runtimePermissionsUnavailable: runtimeAuthorizationMatchesRenderedContext
|
|
3016
|
+
? runtimePermissionsUnavailable
|
|
3017
|
+
: runtimeContext !== null,
|
|
3018
|
+
availablePermissions: runtimeAuthorizationMatchesRenderedContext
|
|
3019
|
+
? availablePermissions
|
|
3020
|
+
: [],
|
|
3026
3021
|
showPermissionForm,
|
|
3027
3022
|
isPermissionFormOpen,
|
|
3028
3023
|
isAITLoading,
|
|
@@ -3059,7 +3054,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3059
3054
|
setIsOpen,
|
|
3060
3055
|
setShowPermissionForm,
|
|
3061
3056
|
setIsPermissionFormOpen,
|
|
3062
|
-
setPermissions,
|
|
3063
3057
|
setIsDisabled,
|
|
3064
3058
|
setIsWalletLoading,
|
|
3065
3059
|
setNotification,
|
|
@@ -3110,6 +3104,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3110
3104
|
onVerifyWallet: (method: 'berifyme' | 'custom') => handleVerifyWalletClick(method),
|
|
3111
3105
|
serviceId,
|
|
3112
3106
|
permissionGroup,
|
|
3107
|
+
roles,
|
|
3113
3108
|
|
|
3114
3109
|
// Props
|
|
3115
3110
|
props: {
|
|
@@ -3128,6 +3123,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3128
3123
|
getAuthToken,
|
|
3129
3124
|
onVerifyWallet,
|
|
3130
3125
|
permissionGroup,
|
|
3126
|
+
roles,
|
|
3131
3127
|
onModelChange,
|
|
3132
3128
|
storageMode,
|
|
3133
3129
|
requireWalletIDVVerification,
|