@bytexbyte/nxtlinq-ai-agent-ui-react-development 0.4.5 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context/ChatBotContext.d.ts.map +1 -1
- package/dist/context/ChatBotContext.js +186 -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 +263 -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,
|
|
@@ -1858,6 +1932,14 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
1858
1932
|
|
|
1859
1933
|
// Typed preflight already emitted the only permitted recovery UI.
|
|
1860
1934
|
if (!isToolAllowed) {
|
|
1935
|
+
// Do not leave suggestions from the previous turn visible while
|
|
1936
|
+
// authorization recovery short-circuits the normal response path.
|
|
1937
|
+
setSuggestions([]);
|
|
1938
|
+
void updateSuggestions(
|
|
1939
|
+
pseudoId,
|
|
1940
|
+
localStorage.getItem('walletAddress') || undefined,
|
|
1941
|
+
content,
|
|
1942
|
+
);
|
|
1861
1943
|
pendingTextToolRetryRef.current.set({
|
|
1862
1944
|
content,
|
|
1863
1945
|
isPresetMessage,
|
|
@@ -2482,108 +2564,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2482
2564
|
}
|
|
2483
2565
|
};
|
|
2484
2566
|
|
|
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
2567
|
// Auto enable AIT permission
|
|
2588
2568
|
const enableAIT = async (toolName: string) => {
|
|
2589
2569
|
if (isAITEnabling) return false; // Prevent duplicate
|
|
@@ -2650,35 +2630,31 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2650
2630
|
}
|
|
2651
2631
|
|
|
2652
2632
|
try {
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2633
|
+
const context: RuntimeAITContext = {
|
|
2634
|
+
controller: currentHitAddress,
|
|
2635
|
+
serviceId,
|
|
2636
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2637
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2638
|
+
};
|
|
2639
|
+
const enabled = await runtimeMutationQueueRef.current.run(
|
|
2640
|
+
runtimeAITSubjectKey(context),
|
|
2641
|
+
async () => {
|
|
2642
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2643
|
+
const current = await recomputeRuntimeAIT(context);
|
|
2644
|
+
if (!current.maximumPermissions.includes(toolName)) {
|
|
2645
|
+
return false;
|
|
2646
|
+
}
|
|
2647
|
+
const nextDeniedPermissions = removeOneDeniedPermission(
|
|
2648
|
+
current.deniedPermissions,
|
|
2649
|
+
toolName,
|
|
2650
|
+
);
|
|
2651
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2652
|
+
await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
|
|
2653
|
+
return true;
|
|
2654
|
+
},
|
|
2672
2655
|
);
|
|
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.');
|
|
2656
|
+
if (!enabled) {
|
|
2657
|
+
showError(`Tool ${toolName} is not available for your current identity provider`);
|
|
2682
2658
|
setIsAITEnabling(false);
|
|
2683
2659
|
return false;
|
|
2684
2660
|
}
|
|
@@ -2686,9 +2662,13 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2686
2662
|
setIsAITEnabling(false);
|
|
2687
2663
|
return true;
|
|
2688
2664
|
} catch (error) {
|
|
2689
|
-
|
|
2665
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
2666
|
+
console.error('Failed to auto-enable AIT:', error);
|
|
2667
|
+
}
|
|
2690
2668
|
setIsAITEnabling(false);
|
|
2691
|
-
if (error instanceof
|
|
2669
|
+
if (error instanceof StaleRuntimeAITResponseError) {
|
|
2670
|
+
return false;
|
|
2671
|
+
} else if (error instanceof Error) {
|
|
2692
2672
|
showError(error.message);
|
|
2693
2673
|
} else {
|
|
2694
2674
|
showError('Failed to enable AIT permission. Please try again.');
|
|
@@ -2701,23 +2681,43 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2701
2681
|
const savePermissions = async (newPermissions?: string[]) => {
|
|
2702
2682
|
setIsDisabled(true);
|
|
2703
2683
|
try {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
const
|
|
2708
|
-
|
|
2684
|
+
if (!hitAddress) {
|
|
2685
|
+
throw new Error('Please connect your wallet first');
|
|
2686
|
+
}
|
|
2687
|
+
const context: RuntimeAITContext = {
|
|
2688
|
+
controller: hitAddress,
|
|
2689
|
+
serviceId,
|
|
2690
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2691
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2692
|
+
};
|
|
2693
|
+
const checkedPermissions = [...(newPermissions ?? permissions)];
|
|
2694
|
+
await runtimeMutationQueueRef.current.run(
|
|
2695
|
+
runtimeAITSubjectKey(context),
|
|
2696
|
+
async () => {
|
|
2697
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2698
|
+
const fresh = await recomputeRuntimeAIT(context);
|
|
2699
|
+
const nextDeniedPermissions = replaceCurrentMaximumDenyList(
|
|
2700
|
+
fresh.deniedPermissions,
|
|
2701
|
+
fresh.maximumPermissions,
|
|
2702
|
+
checkedPermissions,
|
|
2703
|
+
);
|
|
2704
|
+
runtimeRecomputeCoordinatorRef.current.assertContextActive(context);
|
|
2705
|
+
await dispatchRuntimeDenyListReplacement(context, nextDeniedPermissions);
|
|
2706
|
+
},
|
|
2709
2707
|
);
|
|
2710
|
-
|
|
2711
|
-
await generateAndRegisterAIT(validPermissions, false);
|
|
2712
2708
|
showSuccess('AIT permissions saved successfully! You can now use the AI agent with your configured permissions.');
|
|
2713
2709
|
setShowPermissionForm(false);
|
|
2714
2710
|
setIsPermissionFormOpen(false);
|
|
2715
2711
|
await refreshAIT(true);
|
|
2716
2712
|
await retryPendingTextTool();
|
|
2717
2713
|
} catch (error) {
|
|
2718
|
-
|
|
2714
|
+
if (!(error instanceof StaleRuntimeAITResponseError)) {
|
|
2715
|
+
console.error('Failed to update AIT deny-list:', error);
|
|
2716
|
+
}
|
|
2719
2717
|
setIsDisabled(false);
|
|
2720
|
-
if (error instanceof
|
|
2718
|
+
if (error instanceof StaleRuntimeAITResponseError) {
|
|
2719
|
+
return;
|
|
2720
|
+
} else if (error instanceof Error) {
|
|
2721
2721
|
showError(error.message);
|
|
2722
2722
|
} else {
|
|
2723
2723
|
showError('Failed to save permissions. Please try again.');
|
|
@@ -2766,15 +2766,12 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2766
2766
|
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
2767
2767
|
if (!('error' in walletResponse)) {
|
|
2768
2768
|
setWalletInfo(walletResponse);
|
|
2769
|
-
|
|
2770
|
-
serviceId,
|
|
2769
|
+
await queueRuntimeAITRefresh({
|
|
2771
2770
|
controller: address,
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
setAit(aitResponse);
|
|
2777
|
-
}
|
|
2771
|
+
serviceId,
|
|
2772
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2773
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2774
|
+
});
|
|
2778
2775
|
}
|
|
2779
2776
|
} finally {
|
|
2780
2777
|
setIsWalletLoading(false);
|
|
@@ -2806,15 +2803,12 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2806
2803
|
const walletResponse = await nxtlinqApi.wallet.getWallet({ address }, token);
|
|
2807
2804
|
if (!('error' in walletResponse)) {
|
|
2808
2805
|
setWalletInfo(walletResponse);
|
|
2809
|
-
|
|
2810
|
-
serviceId,
|
|
2806
|
+
await queueRuntimeAITRefresh({
|
|
2811
2807
|
controller: address,
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
setAit(aitResponse);
|
|
2817
|
-
}
|
|
2808
|
+
serviceId,
|
|
2809
|
+
...(roles !== undefined ? { roles: [...roles] } : {}),
|
|
2810
|
+
...(permissionGroup !== undefined ? { permissionGroup } : {}),
|
|
2811
|
+
});
|
|
2818
2812
|
}
|
|
2819
2813
|
} finally {
|
|
2820
2814
|
setIsWalletLoading(false);
|
|
@@ -2924,7 +2918,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
2924
2918
|
if (hitAddress && nxtlinqAITServiceAccessToken) {
|
|
2925
2919
|
refreshAIT();
|
|
2926
2920
|
}
|
|
2927
|
-
}, [hitAddress, nxtlinqAITServiceAccessToken]);
|
|
2921
|
+
}, [hitAddress, nxtlinqAITServiceAccessToken, permissionGroup, runtimeRolesKey, serviceId]);
|
|
2928
2922
|
|
|
2929
2923
|
// Set loading state when permission form opens
|
|
2930
2924
|
React.useEffect(() => {
|
|
@@ -3013,6 +3007,8 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3013
3007
|
updateExternalId()
|
|
3014
3008
|
}, [hitAddress, nxtlinqApi, authFields, pseudoId]);
|
|
3015
3009
|
|
|
3010
|
+
const runtimeAuthorizationMatchesRenderedContext =
|
|
3011
|
+
runtimeAuthorizationMatchesContext(runtimeAuthorizationContextKey, runtimeContext);
|
|
3016
3012
|
const contextValue: ChatBotContextType = {
|
|
3017
3013
|
// State
|
|
3018
3014
|
messages,
|
|
@@ -3020,9 +3016,16 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3020
3016
|
isLoading,
|
|
3021
3017
|
isOpen,
|
|
3022
3018
|
hitAddress,
|
|
3023
|
-
ait,
|
|
3024
|
-
permissions,
|
|
3025
|
-
|
|
3019
|
+
ait: runtimeAuthorizationMatchesRenderedContext ? ait : null,
|
|
3020
|
+
permissions: runtimeAuthorizationMatchesRenderedContext ? permissions : [],
|
|
3021
|
+
maximumPermissions: runtimeAuthorizationMatchesRenderedContext ? maximumPermissions : [],
|
|
3022
|
+
deniedPermissions: runtimeAuthorizationMatchesRenderedContext ? deniedPermissions : [],
|
|
3023
|
+
runtimePermissionsUnavailable: runtimeAuthorizationMatchesRenderedContext
|
|
3024
|
+
? runtimePermissionsUnavailable
|
|
3025
|
+
: runtimeContext !== null,
|
|
3026
|
+
availablePermissions: runtimeAuthorizationMatchesRenderedContext
|
|
3027
|
+
? availablePermissions
|
|
3028
|
+
: [],
|
|
3026
3029
|
showPermissionForm,
|
|
3027
3030
|
isPermissionFormOpen,
|
|
3028
3031
|
isAITLoading,
|
|
@@ -3059,7 +3062,6 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3059
3062
|
setIsOpen,
|
|
3060
3063
|
setShowPermissionForm,
|
|
3061
3064
|
setIsPermissionFormOpen,
|
|
3062
|
-
setPermissions,
|
|
3063
3065
|
setIsDisabled,
|
|
3064
3066
|
setIsWalletLoading,
|
|
3065
3067
|
setNotification,
|
|
@@ -3110,6 +3112,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3110
3112
|
onVerifyWallet: (method: 'berifyme' | 'custom') => handleVerifyWalletClick(method),
|
|
3111
3113
|
serviceId,
|
|
3112
3114
|
permissionGroup,
|
|
3115
|
+
roles,
|
|
3113
3116
|
|
|
3114
3117
|
// Props
|
|
3115
3118
|
props: {
|
|
@@ -3128,6 +3131,7 @@ export const ChatBotProvider: React.FC<ChatBotProps> = ({
|
|
|
3128
3131
|
getAuthToken,
|
|
3129
3132
|
onVerifyWallet,
|
|
3130
3133
|
permissionGroup,
|
|
3134
|
+
roles,
|
|
3131
3135
|
onModelChange,
|
|
3132
3136
|
storageMode,
|
|
3133
3137
|
requireWalletIDVVerification,
|