ec-web 0.0.22 → 0.0.23
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/index.js +4 -2
- package/dist/ipc-client.js +5 -0
- package/dist/server.js +68 -148
- package/dist/sources/baseagent-detector.js +27 -102
- package/dist/sources/basic-settings.js +21 -4
- package/dist/sources/models.js +13 -6
- package/dist/sources/role-definitions.js +37 -12
- package/dist/static/app.js +81 -33
- package/dist/static/basic-settings.js +81 -14
- package/dist/static/index.html +1 -1
- package/package.json +1 -1
package/dist/sources/models.js
CHANGED
|
@@ -32,21 +32,27 @@ export function normalizeModelEntries(models) {
|
|
|
32
32
|
owned_by: model.owned_by,
|
|
33
33
|
created: model.created,
|
|
34
34
|
family: inferModelFamily(id),
|
|
35
|
-
status: isAlias ? 'alias' : '
|
|
35
|
+
status: isAlias ? 'alias' : model.verification || 'unverified',
|
|
36
|
+
verification: model.verification,
|
|
37
|
+
reasoningLevels: model.reasoningLevels,
|
|
38
|
+
aliasFor: model.aliasFor,
|
|
36
39
|
isAlias
|
|
37
40
|
});
|
|
38
41
|
}
|
|
39
42
|
return out;
|
|
40
43
|
}
|
|
41
|
-
export async function getModelCatalogSnapshot(baseagent = 'claude') {
|
|
44
|
+
export async function getModelCatalogSnapshot(baseagent = 'claude', self, refresh = false) {
|
|
42
45
|
const { getCatalog } = await getCatalogModule();
|
|
43
|
-
const catalog = await getCatalog(
|
|
46
|
+
const catalog = await getCatalog(self, baseagent, refresh);
|
|
44
47
|
const normalized = normalizeModelEntries(catalog.models || []);
|
|
45
48
|
return {
|
|
46
49
|
models: normalized,
|
|
47
50
|
catalogModels: normalized,
|
|
48
51
|
source: catalog.source || 'unknown',
|
|
49
|
-
lastUpdate:
|
|
52
|
+
lastUpdate: catalog.fetchedAt,
|
|
53
|
+
revision: catalog.revision,
|
|
54
|
+
freshness: catalog.freshness,
|
|
55
|
+
diagnostics: catalog.diagnostics,
|
|
50
56
|
};
|
|
51
57
|
}
|
|
52
58
|
function sendJson(res, status, payload) {
|
|
@@ -58,11 +64,12 @@ export async function handleModelsApi(req, res) {
|
|
|
58
64
|
const url = new URL(req.url || '', 'http://localhost');
|
|
59
65
|
if (req.method === 'GET' && url.pathname === '/api/models/catalog') {
|
|
60
66
|
const baseagent = url.searchParams.get('baseagent') || 'claude';
|
|
61
|
-
if (
|
|
67
|
+
if (!['claude', 'codex', 'ecagent', 'gemini'].includes(baseagent)) {
|
|
62
68
|
sendJson(res, 400, { success: false, error: 'Invalid baseagent' });
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
|
-
const
|
|
71
|
+
const self = url.searchParams.get('aid') || url.searchParams.get('self') || undefined;
|
|
72
|
+
const data = await getModelCatalogSnapshot(baseagent, self, url.searchParams.get('refresh') === 'true');
|
|
66
73
|
sendJson(res, 200, { success: true, data });
|
|
67
74
|
return;
|
|
68
75
|
}
|
|
@@ -278,7 +278,7 @@ function resolveRequestedAid(raw, agents) {
|
|
|
278
278
|
return agents.find(agent => agent?.aid)?.aid || null;
|
|
279
279
|
}
|
|
280
280
|
function isReservedRoleName(roleName) {
|
|
281
|
-
return roleName === 'owner' || roleName === 'admin';
|
|
281
|
+
return roleName === 'owner' || roleName === 'admin' || roleName === 'daemon-owner';
|
|
282
282
|
}
|
|
283
283
|
function isValidUserRoleName(roleName) {
|
|
284
284
|
return /^[a-z0-9_-]+$/.test(roleName) && !isReservedRoleName(roleName);
|
|
@@ -309,11 +309,12 @@ function normalizeDefaultRoleInput(value, current) {
|
|
|
309
309
|
return value;
|
|
310
310
|
return current ?? null;
|
|
311
311
|
}
|
|
312
|
-
function validateRoleDefinitionName(roleName) {
|
|
312
|
+
function validateRoleDefinitionName(roleName, options = {}) {
|
|
313
313
|
if (!roleName || !/^[a-z0-9_-]+$/.test(roleName))
|
|
314
314
|
return 'Invalid role name';
|
|
315
|
-
if (isReservedRoleName(roleName))
|
|
316
|
-
return 'owner/admin are management identities and cannot be user role definitions';
|
|
315
|
+
if (isReservedRoleName(roleName) && (!options.allowManagement || roleName === 'daemon-owner')) {
|
|
316
|
+
return 'owner/admin/daemon-owner are management identities and cannot be user role definitions';
|
|
317
|
+
}
|
|
317
318
|
return null;
|
|
318
319
|
}
|
|
319
320
|
function inferSelectionMode(allowedModels = []) {
|
|
@@ -557,12 +558,12 @@ async function filterAllowedCatalogModels(models, allowedModels) {
|
|
|
557
558
|
const { isModelAllowedByPatterns } = await getRoleConstraintsModule();
|
|
558
559
|
return models.filter(model => !model.isAlias && isModelAllowedByPatterns(model.id, allowedModels));
|
|
559
560
|
}
|
|
560
|
-
async function buildModelPermissionResponse(roleName, roleDef, override) {
|
|
561
|
+
async function buildModelPermissionResponse(roleName, roleDef, override, aid) {
|
|
561
562
|
const currentPerm = getModelPermission(roleDef);
|
|
562
563
|
const defaultModel = override?.defaultModel ?? currentPerm.default ?? '';
|
|
563
564
|
const allowOverride = override?.allowOverride ?? currentPerm.allowOverride ?? false;
|
|
564
565
|
const allowedModels = override?.allowedModels ?? (Array.isArray(currentPerm.allowedModels) ? currentPerm.allowedModels : []);
|
|
565
|
-
const catalog = await getModelCatalogSnapshot('claude');
|
|
566
|
+
const catalog = await getModelCatalogSnapshot('claude', aid);
|
|
566
567
|
const matchingModels = await filterAllowedCatalogModels(catalog.models, allowedModels);
|
|
567
568
|
return {
|
|
568
569
|
role: roleName,
|
|
@@ -653,6 +654,7 @@ export async function handleRoleDefinitionsApi(req, res, auth = {}) {
|
|
|
653
654
|
const aid = resolveRequestedAid(parsedUrl.query.aid, agents);
|
|
654
655
|
const modules = await getRolesModule();
|
|
655
656
|
const { readRolesConfig, read, ConfigTarget } = modules;
|
|
657
|
+
let authorizationDecision = null;
|
|
656
658
|
const roleRoute = urlPath.match(/^\/api\/role-definitions\/([^/]+)$/);
|
|
657
659
|
const roleNestedRoute = urlPath.match(/^\/api\/role-definitions\/([^/]+)\/([^/]+)$/);
|
|
658
660
|
const decodeRoleName = (raw) => {
|
|
@@ -679,6 +681,7 @@ export async function handleRoleDefinitionsApi(req, res, auth = {}) {
|
|
|
679
681
|
method: req.method,
|
|
680
682
|
path: urlPath,
|
|
681
683
|
});
|
|
684
|
+
authorizationDecision = decision;
|
|
682
685
|
if (!decision.allow) {
|
|
683
686
|
console.warn('[role-definitions] Role policy access forbidden:', {
|
|
684
687
|
method: req.method,
|
|
@@ -724,13 +727,33 @@ export async function handleRoleDefinitionsApi(req, res, auth = {}) {
|
|
|
724
727
|
try {
|
|
725
728
|
const incoming = await readJsonBody(req);
|
|
726
729
|
const currentConfig = readRolesConfig(aid || undefined);
|
|
730
|
+
// A complete snapshot may contain both management identities. Keep the
|
|
731
|
+
// same hierarchy guard used by the per-role endpoint for bulk updates.
|
|
732
|
+
if (!isDaemonOwnerAuth(read(ConfigTarget.Process) || {}, auth)
|
|
733
|
+
&& authorizationDecision?.subject?.role === 'admin'
|
|
734
|
+
&& incoming.roles && typeof incoming.roles === 'object'
|
|
735
|
+
&& Object.prototype.hasOwnProperty.call(incoming.roles, 'owner')) {
|
|
736
|
+
sendJson(res, 403, {
|
|
737
|
+
error: 'forbidden: agent admin cannot modify owner role policy',
|
|
738
|
+
code: 'PERMISSION_DENIED',
|
|
739
|
+
actorRole: 'admin',
|
|
740
|
+
});
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
727
743
|
if (incoming.roles && typeof incoming.roles === 'object') {
|
|
728
744
|
for (const [roleName, roleDef] of Object.entries(incoming.roles)) {
|
|
729
|
-
|
|
745
|
+
// The collection endpoint receives the complete snapshot, including
|
|
746
|
+
// management-role overrides. Creation of new management identities
|
|
747
|
+
// remains forbidden; updates are narrowed by the runtime ceiling.
|
|
748
|
+
const nameError = validateRoleDefinitionName(roleName, { allowManagement: true });
|
|
730
749
|
if (nameError) {
|
|
731
750
|
sendJson(res, 400, { error: `${nameError}: ${roleName}` });
|
|
732
751
|
return;
|
|
733
752
|
}
|
|
753
|
+
if (isReservedRoleName(roleName) && !Object.prototype.hasOwnProperty.call(currentConfig.roles, roleName)) {
|
|
754
|
+
sendJson(res, 404, { error: `Management role not found: ${roleName}` });
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
734
757
|
const validation = await validateRoleModelPermission(roleDef);
|
|
735
758
|
if (!validation.ok) {
|
|
736
759
|
sendJson(res, 400, { error: `Invalid model permissions for role ${roleName}`, errors: validation.errors });
|
|
@@ -842,7 +865,7 @@ export async function handleRoleDefinitionsApi(req, res, auth = {}) {
|
|
|
842
865
|
sendJson(res, 404, { success: false, error: 'Role not found' });
|
|
843
866
|
return;
|
|
844
867
|
}
|
|
845
|
-
const data = await buildModelPermissionResponse(roleName, roleDef);
|
|
868
|
+
const data = await buildModelPermissionResponse(roleName, roleDef, undefined, aid || undefined);
|
|
846
869
|
sendJson(res, 200, { success: true, data });
|
|
847
870
|
return;
|
|
848
871
|
}
|
|
@@ -861,7 +884,7 @@ export async function handleRoleDefinitionsApi(req, res, auth = {}) {
|
|
|
861
884
|
sendJson(res, 400, { success: false, error: 'Invalid model permissions', errors: validation.errors });
|
|
862
885
|
return;
|
|
863
886
|
}
|
|
864
|
-
const data = await buildModelPermissionResponse(roleName, roleDef, validation.value);
|
|
887
|
+
const data = await buildModelPermissionResponse(roleName, roleDef, validation.value, aid || undefined);
|
|
865
888
|
sendJson(res, 200, { success: true, data });
|
|
866
889
|
return;
|
|
867
890
|
}
|
|
@@ -891,13 +914,13 @@ export async function handleRoleDefinitionsApi(req, res, auth = {}) {
|
|
|
891
914
|
allowedModels: validation.value.allowedModels,
|
|
892
915
|
};
|
|
893
916
|
modules.roleService.writeRoleDefinition(aid, roleName, roleDef);
|
|
894
|
-
const data = await buildModelPermissionResponse(roleName, roleDef, validation.value);
|
|
917
|
+
const data = await buildModelPermissionResponse(roleName, roleDef, validation.value, aid || undefined);
|
|
895
918
|
sendJson(res, 200, { success: true, data });
|
|
896
919
|
return;
|
|
897
920
|
}
|
|
898
921
|
if (req.method === 'POST' && roleNestedRoute?.[2] === 'reset') {
|
|
899
922
|
const roleName = decodeRoleName(roleNestedRoute[1]);
|
|
900
|
-
if (!['admin', 'member', 'visitor'].includes(roleName)) {
|
|
923
|
+
if (!['owner', 'admin', 'member', 'visitor'].includes(roleName)) {
|
|
901
924
|
sendJson(res, 403, { error: 'Only builtin role overrides can be reset' });
|
|
902
925
|
return;
|
|
903
926
|
}
|
|
@@ -940,7 +963,9 @@ export async function handleRoleDefinitionsApi(req, res, auth = {}) {
|
|
|
940
963
|
try {
|
|
941
964
|
const roleName = decodeRoleName(roleRoute[1]);
|
|
942
965
|
const updates = await readJsonBody(req);
|
|
943
|
-
|
|
966
|
+
// owner/admin are reserved identities, but their policy definitions may
|
|
967
|
+
// be overridden (subject to the management-role ceiling).
|
|
968
|
+
const nameError = validateRoleDefinitionName(roleName, { allowManagement: true });
|
|
944
969
|
if (nameError) {
|
|
945
970
|
sendJson(res, 400, { error: nameError });
|
|
946
971
|
return;
|
package/dist/static/app.js
CHANGED
|
@@ -1976,6 +1976,58 @@ async function tryLocalAutoPair() {
|
|
|
1976
1976
|
return false;
|
|
1977
1977
|
}
|
|
1978
1978
|
|
|
1979
|
+
let authRecoveryPromise = null;
|
|
1980
|
+
|
|
1981
|
+
function authenticatedRequestUrl(pathOrUrl) {
|
|
1982
|
+
const raw = String(pathOrUrl || '');
|
|
1983
|
+
const target = raw.startsWith(BASE) ? raw : apiUrl(raw);
|
|
1984
|
+
const parsed = new URL(target, location.origin);
|
|
1985
|
+
const token = localStorage.getItem(TOKEN_KEY);
|
|
1986
|
+
if (token) parsed.searchParams.set('token', token);
|
|
1987
|
+
else parsed.searchParams.delete('token');
|
|
1988
|
+
return parsed.pathname + parsed.search + parsed.hash;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
function authenticatedRequestInit(init = {}) {
|
|
1992
|
+
const headers = new Headers(init.headers || {});
|
|
1993
|
+
const token = localStorage.getItem(TOKEN_KEY);
|
|
1994
|
+
if (token) headers.set('Authorization', `Bearer ${token}`);
|
|
1995
|
+
else headers.delete('Authorization');
|
|
1996
|
+
return { ...init, headers };
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
async function recoverHttpAuthentication() {
|
|
2000
|
+
if (authRecoveryPromise) return authRecoveryPromise;
|
|
2001
|
+
localStorage.removeItem(TOKEN_KEY);
|
|
2002
|
+
authRecoveryPromise = (async () => {
|
|
2003
|
+
if (await tryLocalAutoPair()) return true;
|
|
2004
|
+
showPairPage(t('pair.error.tokenInvalid'));
|
|
2005
|
+
return false;
|
|
2006
|
+
})().finally(() => {
|
|
2007
|
+
authRecoveryPromise = null;
|
|
2008
|
+
});
|
|
2009
|
+
return authRecoveryPromise;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
async function authFetch(pathOrUrl, init = {}) {
|
|
2013
|
+
const send = async () => {
|
|
2014
|
+
const token = localStorage.getItem(TOKEN_KEY);
|
|
2015
|
+
const response = await fetch(authenticatedRequestUrl(pathOrUrl), authenticatedRequestInit(init));
|
|
2016
|
+
return { response, token };
|
|
2017
|
+
};
|
|
2018
|
+
|
|
2019
|
+
let attempt = await send();
|
|
2020
|
+
if (attempt.response.status !== 401) return attempt.response;
|
|
2021
|
+
|
|
2022
|
+
const currentToken = localStorage.getItem(TOKEN_KEY);
|
|
2023
|
+
if (currentToken && currentToken !== attempt.token) {
|
|
2024
|
+
return (await send()).response;
|
|
2025
|
+
}
|
|
2026
|
+
if (!(await recoverHttpAuthentication())) return attempt.response;
|
|
2027
|
+
attempt = await send();
|
|
2028
|
+
return attempt.response;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
1979
2031
|
function showPairPage(hint) {
|
|
1980
2032
|
if (ws) { try { ws.close(); } catch {} ws = null; }
|
|
1981
2033
|
$('#pair-page').style.display = 'flex';
|
|
@@ -4138,12 +4190,7 @@ function agentOpNew() {
|
|
|
4138
4190
|
openAgentCreateDrawer();
|
|
4139
4191
|
}
|
|
4140
4192
|
|
|
4141
|
-
const AGENT_BASEAGENT_TYPES = ['claude', 'codex', 'gemini'];
|
|
4142
|
-
const AGENT_EFFORT_FALLBACK = {
|
|
4143
|
-
claude: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
4144
|
-
codex: ['low', 'medium', 'high'],
|
|
4145
|
-
gemini: [],
|
|
4146
|
-
};
|
|
4193
|
+
const AGENT_BASEAGENT_TYPES = ['claude', 'codex', 'gemini', 'ecagent'];
|
|
4147
4194
|
const AGENT_AID_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?){2,}$/;
|
|
4148
4195
|
let _agentEdit = null;
|
|
4149
4196
|
let _agentCreate = null;
|
|
@@ -4594,7 +4641,7 @@ async function agentFetchCreateModelOptions(baseagent) {
|
|
|
4594
4641
|
const catalog = await fetchModelCatalogData(baseagent);
|
|
4595
4642
|
return (catalog?.models || []).map(model => ({
|
|
4596
4643
|
value: model.id,
|
|
4597
|
-
label: model.id
|
|
4644
|
+
label: `${model.id}${model.verification === 'unverified' ? ' [unverified]' : ''}`,
|
|
4598
4645
|
}));
|
|
4599
4646
|
}
|
|
4600
4647
|
|
|
@@ -4684,7 +4731,8 @@ async function syncAgentCreateOptionFields(ctx, values) {
|
|
|
4684
4731
|
async function syncAgentCreateEffortOptions(ctx, baseagent, model, currentEffort, token) {
|
|
4685
4732
|
const effortSelect = ctx.backdrop.querySelector('#ag-create-effort');
|
|
4686
4733
|
if (!effortSelect) return;
|
|
4687
|
-
const
|
|
4734
|
+
const catalog = await fetchModelCatalogData(baseagent);
|
|
4735
|
+
const efforts = catalog.models.find(entry => entry.id === model)?.reasoningLevels || [];
|
|
4688
4736
|
if (_agentCreate !== ctx || ctx.optionLoadToken !== token) return;
|
|
4689
4737
|
effortSelect.innerHTML = agentSelectOptions(efforts, currentEffort, t('common.default'));
|
|
4690
4738
|
effortSelect.disabled = Boolean(ctx.saving);
|
|
@@ -5085,12 +5133,11 @@ async function syncAgentEffortOptions(ctx, baseagent, model, currentEffort, toke
|
|
|
5085
5133
|
setAgentSelectLoading(effortSelect, currentEffort, currentEffort ? `${currentEffort} (${t('common.loadingSuffix')})` : t('common.loading'));
|
|
5086
5134
|
try {
|
|
5087
5135
|
let efforts = await agentFetchOptions('effort', ctx.aid, { baseagent, model, effort: currentEffort || 'auto' });
|
|
5088
|
-
if (!efforts.length && AGENT_EFFORT_FALLBACK[baseagent]) efforts = AGENT_EFFORT_FALLBACK[baseagent];
|
|
5089
5136
|
if (_agentEdit !== ctx || ctx.optionLoadToken !== token) return;
|
|
5090
5137
|
effortSelect.innerHTML = agentSelectOptions(efforts, currentEffort, t('common.default'));
|
|
5091
5138
|
} catch (e) {
|
|
5092
5139
|
if (_agentEdit === ctx && ctx.optionLoadToken === token) {
|
|
5093
|
-
const fallback =
|
|
5140
|
+
const fallback = [];
|
|
5094
5141
|
effortSelect.innerHTML = agentSelectOptions(fallback, currentEffort, t('common.default'));
|
|
5095
5142
|
toast(e.message || String(e), true);
|
|
5096
5143
|
}
|
|
@@ -6883,12 +6930,11 @@ async function syncTriggerEffortOptions(ctx, baseagent, model, currentEffort, to
|
|
|
6883
6930
|
setAgentSelectLoading(effortSelect, currentEffort, currentEffort ? `${currentEffort} (${t('common.loadingSuffix')})` : t('common.loading'));
|
|
6884
6931
|
try {
|
|
6885
6932
|
let efforts = await agentFetchOptions('effort', ctx.original.agentAid, { baseagent, model, effort: currentEffort || 'auto' });
|
|
6886
|
-
if (!efforts.length && AGENT_EFFORT_FALLBACK[baseagent]) efforts = AGENT_EFFORT_FALLBACK[baseagent];
|
|
6887
6933
|
if (_triggerEdit !== ctx || ctx.optionLoadToken !== token) return;
|
|
6888
6934
|
effortSelect.innerHTML = agentSelectOptions(efforts, currentEffort, t('common.inherited'));
|
|
6889
6935
|
} catch (e) {
|
|
6890
6936
|
if (_triggerEdit === ctx && ctx.optionLoadToken === token) {
|
|
6891
|
-
const fallback =
|
|
6937
|
+
const fallback = [];
|
|
6892
6938
|
effortSelect.innerHTML = agentSelectOptions(fallback, currentEffort, t('common.inherited'));
|
|
6893
6939
|
toast(e.message || String(e), true);
|
|
6894
6940
|
}
|
|
@@ -7811,7 +7857,7 @@ async function loadUsageOverview(rangeType, customFrom, customTo) {
|
|
|
7811
7857
|
if (fromTs) params.set('from', String(fromTs));
|
|
7812
7858
|
if (toTs) params.set('to', String(toTs));
|
|
7813
7859
|
|
|
7814
|
-
const resp = await
|
|
7860
|
+
const resp = await authFetch('api/stats/overview?' + params.toString(), {
|
|
7815
7861
|
headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
|
|
7816
7862
|
});
|
|
7817
7863
|
data = resp.ok ? await resp.json() : null;
|
|
@@ -8016,7 +8062,7 @@ function initDetailQuery() {
|
|
|
8016
8062
|
|
|
8017
8063
|
async function loadDetailAgentList() {
|
|
8018
8064
|
try {
|
|
8019
|
-
const resp = await
|
|
8065
|
+
const resp = await authFetch('api/stats/agents', {
|
|
8020
8066
|
headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
|
|
8021
8067
|
});
|
|
8022
8068
|
if (!resp.ok) return;
|
|
@@ -8056,7 +8102,7 @@ async function loadDetailModelList() {
|
|
|
8056
8102
|
if (timeRange.fromTs) params.set('from', String(timeRange.fromTs));
|
|
8057
8103
|
if (timeRange.toTs) params.set('to', String(timeRange.toTs));
|
|
8058
8104
|
|
|
8059
|
-
const resp = await
|
|
8105
|
+
const resp = await authFetch('api/stats/models?' + params.toString(), {
|
|
8060
8106
|
headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
|
|
8061
8107
|
});
|
|
8062
8108
|
if (!resp.ok) return;
|
|
@@ -8109,7 +8155,7 @@ async function queryDetailUsage() {
|
|
|
8109
8155
|
params.set('offset', String(offset));
|
|
8110
8156
|
|
|
8111
8157
|
try {
|
|
8112
|
-
const resp = await
|
|
8158
|
+
const resp = await authFetch('api/stats/detail?' + params.toString(), {
|
|
8113
8159
|
headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
|
|
8114
8160
|
});
|
|
8115
8161
|
if (seq !== _detailQuerySeq) return; // 已有更新的查询发出,丢弃本次结果
|
|
@@ -8430,7 +8476,7 @@ async function loadExplorerModels() {
|
|
|
8430
8476
|
if (_expTimeRange.toTs) params.set('to', String(_expTimeRange.toTs));
|
|
8431
8477
|
|
|
8432
8478
|
try {
|
|
8433
|
-
const resp = await
|
|
8479
|
+
const resp = await authFetch('api/stats/models?' + params.toString(), {
|
|
8434
8480
|
headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
|
|
8435
8481
|
});
|
|
8436
8482
|
if (!resp.ok) return;
|
|
@@ -8467,7 +8513,7 @@ async function fetchExplorerOverviewData(filterParams) {
|
|
|
8467
8513
|
if (filterParams.peer) params.set('peer', filterParams.peer);
|
|
8468
8514
|
}
|
|
8469
8515
|
|
|
8470
|
-
const resp = await
|
|
8516
|
+
const resp = await authFetch('api/stats/overview?' + params.toString(), {
|
|
8471
8517
|
headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
|
|
8472
8518
|
});
|
|
8473
8519
|
const data = resp.ok ? await resp.json() : null;
|
|
@@ -8499,7 +8545,7 @@ async function loadExplorerSidebar() {
|
|
|
8499
8545
|
var token = localStorage.getItem(TOKEN_KEY);
|
|
8500
8546
|
var headers = { Authorization: 'Bearer ' + token };
|
|
8501
8547
|
try {
|
|
8502
|
-
var agentsResp = await
|
|
8548
|
+
var agentsResp = await authFetch('api/stats/agents', { headers });
|
|
8503
8549
|
var agents = agentsResp.ok ? await agentsResp.json() : [];
|
|
8504
8550
|
renderExplorerAgentList(agents);
|
|
8505
8551
|
|
|
@@ -8560,7 +8606,7 @@ async function loadPeersForAgent(agentAid) {
|
|
|
8560
8606
|
params.set('agent', agentAid);
|
|
8561
8607
|
// 不传递时间范围,获取该 agent 的所有 peers
|
|
8562
8608
|
|
|
8563
|
-
var resp = await
|
|
8609
|
+
var resp = await authFetch('api/stats/peers?' + params.toString(), { headers });
|
|
8564
8610
|
var peers = resp.ok ? await resp.json() : [];
|
|
8565
8611
|
renderExplorerPeerList(peers);
|
|
8566
8612
|
} catch {
|
|
@@ -8642,7 +8688,7 @@ async function runExplorerQuery() {
|
|
|
8642
8688
|
|
|
8643
8689
|
var data;
|
|
8644
8690
|
try {
|
|
8645
|
-
var resp = await
|
|
8691
|
+
var resp = await authFetch('api/stats/explorer?' + params.toString(), {
|
|
8646
8692
|
headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
|
|
8647
8693
|
});
|
|
8648
8694
|
if (!resp.ok) return;
|
|
@@ -9430,7 +9476,7 @@ async function updatePeerRoleOverride(agentAid, scope, targetId, role, groupId,
|
|
|
9430
9476
|
if (peerId) body.peerId = peerId;
|
|
9431
9477
|
if (role) body.role = role;
|
|
9432
9478
|
|
|
9433
|
-
const res = await
|
|
9479
|
+
const res = await authFetch(`api/assignments/peer/${encodeURIComponent(agentAid)}/${encodeURIComponent(targetId)}`, {
|
|
9434
9480
|
method: role ? 'PUT' : 'DELETE',
|
|
9435
9481
|
headers: {
|
|
9436
9482
|
'Content-Type': 'application/json',
|
|
@@ -9725,7 +9771,7 @@ function renderDefaultRoleSelector(data) {
|
|
|
9725
9771
|
const next = { private: privateSelect.value || null, group: groupSelect.value || null };
|
|
9726
9772
|
try {
|
|
9727
9773
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
9728
|
-
const writeRes = await
|
|
9774
|
+
const writeRes = await authFetch(roleDefinitionsApi('api/role-definitions'), {
|
|
9729
9775
|
method: 'PUT',
|
|
9730
9776
|
headers: {
|
|
9731
9777
|
'Content-Type': 'application/json',
|
|
@@ -9793,7 +9839,7 @@ function formatCommandPermissions(commandPermissions) {
|
|
|
9793
9839
|
async function fetchRoleOperationsCatalog() {
|
|
9794
9840
|
if (roleOperationsCache) return roleOperationsCache;
|
|
9795
9841
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
9796
|
-
const res = await
|
|
9842
|
+
const res = await authFetch('api/role-definitions/operations', {
|
|
9797
9843
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
9798
9844
|
});
|
|
9799
9845
|
if (!res.ok) throw new Error('Failed to load CLI operation catalog');
|
|
@@ -10315,8 +10361,9 @@ function createRoleCard(roleName, roleDef) {
|
|
|
10315
10361
|
const model = roleDef.permissions?.['baseagents.claude.model']?.default || '—';
|
|
10316
10362
|
const commandStats = getCommandPermissionStats(roleDef.commandPermissions);
|
|
10317
10363
|
|
|
10318
|
-
//
|
|
10319
|
-
|
|
10364
|
+
// Management identities and built-in user roles are both protected from
|
|
10365
|
+
// deletion; their local policy overrides can be reset instead.
|
|
10366
|
+
const builtinRoles = ['owner', 'admin', 'member', 'visitor'];
|
|
10320
10367
|
const isBuiltin = builtinRoles.includes(roleName);
|
|
10321
10368
|
|
|
10322
10369
|
card.innerHTML = `
|
|
@@ -10402,7 +10449,7 @@ function initRoleDefinitionsTab() {
|
|
|
10402
10449
|
|
|
10403
10450
|
try {
|
|
10404
10451
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
10405
|
-
const res = await
|
|
10452
|
+
const res = await authFetch(roleDefinitionsApi(`api/role-definitions/${roleName}/reset`), {
|
|
10406
10453
|
method: 'POST',
|
|
10407
10454
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
10408
10455
|
});
|
|
@@ -10447,7 +10494,7 @@ function initRoleDefinitionsTab() {
|
|
|
10447
10494
|
: replacement
|
|
10448
10495
|
? `?replacementRoleId=${encodeURIComponent(replacement)}`
|
|
10449
10496
|
: '';
|
|
10450
|
-
const res = await
|
|
10497
|
+
const res = await authFetch(roleDefinitionsApi(`api/role-definitions/${roleName}${replacementQuery}`), {
|
|
10451
10498
|
method: 'DELETE',
|
|
10452
10499
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
10453
10500
|
});
|
|
@@ -10600,7 +10647,7 @@ function renderRoleModelPermissionSection(roleDef) {
|
|
|
10600
10647
|
|
|
10601
10648
|
async function fetchRoleModelPermissionData(roleName) {
|
|
10602
10649
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
10603
|
-
const res = await
|
|
10650
|
+
const res = await authFetch(roleDefinitionsApi(`api/role-definitions/${encodeURIComponent(roleName)}/configurable-models`), {
|
|
10604
10651
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
10605
10652
|
});
|
|
10606
10653
|
if (!res.ok) throw new Error('Failed to load role model permissions');
|
|
@@ -10608,13 +10655,14 @@ async function fetchRoleModelPermissionData(roleName) {
|
|
|
10608
10655
|
return json.data;
|
|
10609
10656
|
}
|
|
10610
10657
|
|
|
10611
|
-
async function fetchModelCatalogData(baseagent = 'claude') {
|
|
10658
|
+
async function fetchModelCatalogData(baseagent = 'claude', aid = '') {
|
|
10612
10659
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
10613
|
-
const res = await
|
|
10660
|
+
const res = await authFetch(`api/models/catalog?baseagent=${encodeURIComponent(baseagent)}${aid ? `&aid=${encodeURIComponent(aid)}` : ''}`, {
|
|
10614
10661
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
10615
10662
|
});
|
|
10616
10663
|
if (!res.ok) throw new Error('Failed to load model catalog');
|
|
10617
10664
|
const json = await res.json();
|
|
10665
|
+
if (json.data?.diagnostics?.some(d => d.code === 'AUTH_FAILED')) throw new Error('Model gateway authentication failed');
|
|
10618
10666
|
return json.data;
|
|
10619
10667
|
}
|
|
10620
10668
|
|
|
@@ -10777,7 +10825,7 @@ async function initRoleModelPermissionEditor(container, roleName, roleDef) {
|
|
|
10777
10825
|
try {
|
|
10778
10826
|
const data = roleName && roleName !== '__new__'
|
|
10779
10827
|
? await fetchRoleModelPermissionData(roleName)
|
|
10780
|
-
: await fetchModelCatalogData();
|
|
10828
|
+
: await fetchModelCatalogData('claude', roleDefinitionsCurrentAgent || state.roleDefinitions?.aid || '');
|
|
10781
10829
|
catalogModels = data?.catalog?.models || data?.models || [];
|
|
10782
10830
|
} catch (err) {
|
|
10783
10831
|
console.warn('[roleDefinitions] Failed to load model catalog:', err);
|
|
@@ -11574,7 +11622,7 @@ async function saveRoleDefinition() {
|
|
|
11574
11622
|
const method = isNew ? 'POST' : 'PUT';
|
|
11575
11623
|
const url = isNew ? roleDefinitionsApi('api/role-definitions') : roleDefinitionsApi(`api/role-definitions/${encodeURIComponent(actualRoleName)}`);
|
|
11576
11624
|
|
|
11577
|
-
const res = await
|
|
11625
|
+
const res = await authFetch(url, {
|
|
11578
11626
|
method,
|
|
11579
11627
|
headers: {
|
|
11580
11628
|
'Content-Type': 'application/json',
|