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.
@@ -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' : 'available',
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(undefined, baseagent);
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: new Date().toISOString()
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 (!/^[a-z0-9_-]+$/i.test(baseagent)) {
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 data = await getModelCatalogSnapshot(baseagent);
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
- const nameError = validateRoleDefinitionName(roleName);
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
- const nameError = validateRoleDefinitionName(roleName);
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;
@@ -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 efforts = AGENT_EFFORT_FALLBACK[baseagent] || [];
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 = AGENT_EFFORT_FALLBACK[baseagent] || [];
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 = AGENT_EFFORT_FALLBACK[baseagent] || [];
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 fetch(authApiUrl('api/stats/overview?' + params.toString()), {
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 fetch(authApiUrl('api/stats/agents'), {
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 fetch(authApiUrl('api/stats/models?' + params.toString()), {
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 fetch(authApiUrl('api/stats/detail?' + params.toString()), {
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 fetch(authApiUrl('api/stats/models?' + params.toString()), {
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 fetch(authApiUrl('api/stats/overview?' + params.toString()), {
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 fetch(authApiUrl('api/stats/agents'), { headers });
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 fetch(authApiUrl('api/stats/peers?' + params.toString()), { headers });
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 fetch(authApiUrl('api/stats/explorer?' + params.toString()), {
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 fetch(authApiUrl(`api/assignments/peer/${encodeURIComponent(agentAid)}/${encodeURIComponent(targetId)}`), {
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 fetch(roleDefinitionsApi('api/role-definitions'), {
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 fetch(authApiUrl('api/role-definitions/operations'), {
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
- const builtinRoles = ['admin', 'member', 'visitor'];
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 fetch(roleDefinitionsApi(`api/role-definitions/${roleName}/reset`), {
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 fetch(roleDefinitionsApi(`api/role-definitions/${roleName}${replacementQuery}`), {
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 fetch(roleDefinitionsApi(`api/role-definitions/${encodeURIComponent(roleName)}/configurable-models`), {
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 fetch(authApiUrl(`api/models/catalog?baseagent=${encodeURIComponent(baseagent)}`), {
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 fetch(url, {
11625
+ const res = await authFetch(url, {
11578
11626
  method,
11579
11627
  headers: {
11580
11628
  'Content-Type': 'application/json',