ec-web 0.0.21 → 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.
@@ -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';
@@ -2256,6 +2308,12 @@ function subscribeRoleManagementSubview() {
2256
2308
  function setRoleManagementSubview(subview, options = {}) {
2257
2309
  roleManagementSubview = subview === 'assignments' ? 'assignments' : 'definitions';
2258
2310
  localStorage.setItem(ROLE_SUBVIEW_KEY, roleManagementSubview);
2311
+ const newRoleButton = $('#btn-new-role');
2312
+ if (newRoleButton) {
2313
+ const visible = roleManagementSubview === 'definitions';
2314
+ newRoleButton.hidden = !visible;
2315
+ newRoleButton.setAttribute('aria-hidden', String(!visible));
2316
+ }
2259
2317
  document.querySelectorAll('[data-role-subview]').forEach(tab => {
2260
2318
  const active = tab.dataset.roleSubview === roleManagementSubview;
2261
2319
  tab.classList.toggle('active', active);
@@ -3815,7 +3873,7 @@ function renderBlocks(blocks, options = {}) {
3815
3873
  : '';
3816
3874
  out += `<details class="blk blk-tool" open><summary class="tool-head">${icon} <span class="tool-name">${esc(tool)}</span></summary>${toolParams}</details>`;
3817
3875
  } else if (b.kind === 'tool_result') {
3818
- const rejection = b.rejection?.kind === 'approval_rejected' ? b.rejection : undefined;
3876
+ const rejection = b.rejection && (b.rejection.kind === 'approval_rejected' || b.rejection.kind === 'policy_blocked') ? b.rejection : undefined;
3819
3877
  const cls = b.isError ? `blk-result err${rejection ? ' rejection' : ''}` : 'blk-result';
3820
3878
  const loadFull = b.truncated && b.resultKey
3821
3879
  ? `<button class="ctrl-btn result-load-full" type="button" data-result-key="${esc(b.resultKey)}">${t('sessions.loadFullResult') || '加载完整结果'}</button>`
@@ -3839,7 +3897,7 @@ function renderBlocks(blocks, options = {}) {
3839
3897
  : '';
3840
3898
  return '<div class="result-rejection-meta">' +
3841
3899
  `<span class="result-rejection-badge">⚠ ${t('sessions.block.rejection.title')}</span>` +
3842
- `<span>${rejection.source === 'evolcore-policy' ? t('sessions.block.rejection.source.policy') : t('sessions.block.rejection.source.approval')}</span>` +
3900
+ `<span>${rejection.kind === 'policy_blocked' || rejection.source === 'evolcore-policy' ? t('sessions.block.rejection.source.policy') : t('sessions.block.rejection.source.approval')}</span>` +
3843
3901
  `<span>${t('sessions.block.rejection.notExecuted')}</span>` +
3844
3902
  `<span>${t(`sessions.block.rejection.evidence.${evidenceKey}`)}</span>` +
3845
3903
  backendReason + auditText +
@@ -4132,12 +4190,7 @@ function agentOpNew() {
4132
4190
  openAgentCreateDrawer();
4133
4191
  }
4134
4192
 
4135
- const AGENT_BASEAGENT_TYPES = ['claude', 'codex', 'gemini'];
4136
- const AGENT_EFFORT_FALLBACK = {
4137
- claude: ['low', 'medium', 'high', 'xhigh', 'max'],
4138
- codex: ['low', 'medium', 'high'],
4139
- gemini: [],
4140
- };
4193
+ const AGENT_BASEAGENT_TYPES = ['claude', 'codex', 'gemini', 'ecagent'];
4141
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,}$/;
4142
4195
  let _agentEdit = null;
4143
4196
  let _agentCreate = null;
@@ -4588,7 +4641,7 @@ async function agentFetchCreateModelOptions(baseagent) {
4588
4641
  const catalog = await fetchModelCatalogData(baseagent);
4589
4642
  return (catalog?.models || []).map(model => ({
4590
4643
  value: model.id,
4591
- label: model.id,
4644
+ label: `${model.id}${model.verification === 'unverified' ? ' [unverified]' : ''}`,
4592
4645
  }));
4593
4646
  }
4594
4647
 
@@ -4678,7 +4731,8 @@ async function syncAgentCreateOptionFields(ctx, values) {
4678
4731
  async function syncAgentCreateEffortOptions(ctx, baseagent, model, currentEffort, token) {
4679
4732
  const effortSelect = ctx.backdrop.querySelector('#ag-create-effort');
4680
4733
  if (!effortSelect) return;
4681
- const efforts = AGENT_EFFORT_FALLBACK[baseagent] || [];
4734
+ const catalog = await fetchModelCatalogData(baseagent);
4735
+ const efforts = catalog.models.find(entry => entry.id === model)?.reasoningLevels || [];
4682
4736
  if (_agentCreate !== ctx || ctx.optionLoadToken !== token) return;
4683
4737
  effortSelect.innerHTML = agentSelectOptions(efforts, currentEffort, t('common.default'));
4684
4738
  effortSelect.disabled = Boolean(ctx.saving);
@@ -5079,12 +5133,11 @@ async function syncAgentEffortOptions(ctx, baseagent, model, currentEffort, toke
5079
5133
  setAgentSelectLoading(effortSelect, currentEffort, currentEffort ? `${currentEffort} (${t('common.loadingSuffix')})` : t('common.loading'));
5080
5134
  try {
5081
5135
  let efforts = await agentFetchOptions('effort', ctx.aid, { baseagent, model, effort: currentEffort || 'auto' });
5082
- if (!efforts.length && AGENT_EFFORT_FALLBACK[baseagent]) efforts = AGENT_EFFORT_FALLBACK[baseagent];
5083
5136
  if (_agentEdit !== ctx || ctx.optionLoadToken !== token) return;
5084
5137
  effortSelect.innerHTML = agentSelectOptions(efforts, currentEffort, t('common.default'));
5085
5138
  } catch (e) {
5086
5139
  if (_agentEdit === ctx && ctx.optionLoadToken === token) {
5087
- const fallback = AGENT_EFFORT_FALLBACK[baseagent] || [];
5140
+ const fallback = [];
5088
5141
  effortSelect.innerHTML = agentSelectOptions(fallback, currentEffort, t('common.default'));
5089
5142
  toast(e.message || String(e), true);
5090
5143
  }
@@ -6877,12 +6930,11 @@ async function syncTriggerEffortOptions(ctx, baseagent, model, currentEffort, to
6877
6930
  setAgentSelectLoading(effortSelect, currentEffort, currentEffort ? `${currentEffort} (${t('common.loadingSuffix')})` : t('common.loading'));
6878
6931
  try {
6879
6932
  let efforts = await agentFetchOptions('effort', ctx.original.agentAid, { baseagent, model, effort: currentEffort || 'auto' });
6880
- if (!efforts.length && AGENT_EFFORT_FALLBACK[baseagent]) efforts = AGENT_EFFORT_FALLBACK[baseagent];
6881
6933
  if (_triggerEdit !== ctx || ctx.optionLoadToken !== token) return;
6882
6934
  effortSelect.innerHTML = agentSelectOptions(efforts, currentEffort, t('common.inherited'));
6883
6935
  } catch (e) {
6884
6936
  if (_triggerEdit === ctx && ctx.optionLoadToken === token) {
6885
- const fallback = AGENT_EFFORT_FALLBACK[baseagent] || [];
6937
+ const fallback = [];
6886
6938
  effortSelect.innerHTML = agentSelectOptions(fallback, currentEffort, t('common.inherited'));
6887
6939
  toast(e.message || String(e), true);
6888
6940
  }
@@ -7805,7 +7857,7 @@ async function loadUsageOverview(rangeType, customFrom, customTo) {
7805
7857
  if (fromTs) params.set('from', String(fromTs));
7806
7858
  if (toTs) params.set('to', String(toTs));
7807
7859
 
7808
- const resp = await fetch(authApiUrl('api/stats/overview?' + params.toString()), {
7860
+ const resp = await authFetch('api/stats/overview?' + params.toString(), {
7809
7861
  headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
7810
7862
  });
7811
7863
  data = resp.ok ? await resp.json() : null;
@@ -8010,7 +8062,7 @@ function initDetailQuery() {
8010
8062
 
8011
8063
  async function loadDetailAgentList() {
8012
8064
  try {
8013
- const resp = await fetch(authApiUrl('api/stats/agents'), {
8065
+ const resp = await authFetch('api/stats/agents', {
8014
8066
  headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
8015
8067
  });
8016
8068
  if (!resp.ok) return;
@@ -8050,7 +8102,7 @@ async function loadDetailModelList() {
8050
8102
  if (timeRange.fromTs) params.set('from', String(timeRange.fromTs));
8051
8103
  if (timeRange.toTs) params.set('to', String(timeRange.toTs));
8052
8104
 
8053
- const resp = await fetch(authApiUrl('api/stats/models?' + params.toString()), {
8105
+ const resp = await authFetch('api/stats/models?' + params.toString(), {
8054
8106
  headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
8055
8107
  });
8056
8108
  if (!resp.ok) return;
@@ -8103,7 +8155,7 @@ async function queryDetailUsage() {
8103
8155
  params.set('offset', String(offset));
8104
8156
 
8105
8157
  try {
8106
- const resp = await fetch(authApiUrl('api/stats/detail?' + params.toString()), {
8158
+ const resp = await authFetch('api/stats/detail?' + params.toString(), {
8107
8159
  headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
8108
8160
  });
8109
8161
  if (seq !== _detailQuerySeq) return; // 已有更新的查询发出,丢弃本次结果
@@ -8424,7 +8476,7 @@ async function loadExplorerModels() {
8424
8476
  if (_expTimeRange.toTs) params.set('to', String(_expTimeRange.toTs));
8425
8477
 
8426
8478
  try {
8427
- const resp = await fetch(authApiUrl('api/stats/models?' + params.toString()), {
8479
+ const resp = await authFetch('api/stats/models?' + params.toString(), {
8428
8480
  headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
8429
8481
  });
8430
8482
  if (!resp.ok) return;
@@ -8461,7 +8513,7 @@ async function fetchExplorerOverviewData(filterParams) {
8461
8513
  if (filterParams.peer) params.set('peer', filterParams.peer);
8462
8514
  }
8463
8515
 
8464
- const resp = await fetch(authApiUrl('api/stats/overview?' + params.toString()), {
8516
+ const resp = await authFetch('api/stats/overview?' + params.toString(), {
8465
8517
  headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
8466
8518
  });
8467
8519
  const data = resp.ok ? await resp.json() : null;
@@ -8493,7 +8545,7 @@ async function loadExplorerSidebar() {
8493
8545
  var token = localStorage.getItem(TOKEN_KEY);
8494
8546
  var headers = { Authorization: 'Bearer ' + token };
8495
8547
  try {
8496
- var agentsResp = await fetch(authApiUrl('api/stats/agents'), { headers });
8548
+ var agentsResp = await authFetch('api/stats/agents', { headers });
8497
8549
  var agents = agentsResp.ok ? await agentsResp.json() : [];
8498
8550
  renderExplorerAgentList(agents);
8499
8551
 
@@ -8554,7 +8606,7 @@ async function loadPeersForAgent(agentAid) {
8554
8606
  params.set('agent', agentAid);
8555
8607
  // 不传递时间范围,获取该 agent 的所有 peers
8556
8608
 
8557
- var resp = await fetch(authApiUrl('api/stats/peers?' + params.toString()), { headers });
8609
+ var resp = await authFetch('api/stats/peers?' + params.toString(), { headers });
8558
8610
  var peers = resp.ok ? await resp.json() : [];
8559
8611
  renderExplorerPeerList(peers);
8560
8612
  } catch {
@@ -8636,7 +8688,7 @@ async function runExplorerQuery() {
8636
8688
 
8637
8689
  var data;
8638
8690
  try {
8639
- var resp = await fetch(authApiUrl('api/stats/explorer?' + params.toString()), {
8691
+ var resp = await authFetch('api/stats/explorer?' + params.toString(), {
8640
8692
  headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
8641
8693
  });
8642
8694
  if (!resp.ok) return;
@@ -9092,11 +9144,39 @@ function monDualLine(elId, varKey, times, isDark, title, series, fmtY, yRange) {
9092
9144
  }
9093
9145
 
9094
9146
  function renderRoles(data) {
9095
- if (!data) return;
9096
-
9097
9147
  const agentSelect = $('#roles-agent-select');
9148
+ const tbody = document.querySelector('#relations-table tbody');
9149
+ if (!data) {
9150
+ if (tbody) {
9151
+ tbody.innerHTML = '';
9152
+ const row = tbody.insertRow();
9153
+ const cell = row.insertCell();
9154
+ cell.colSpan = 7;
9155
+ cell.textContent = t('common.loading');
9156
+ cell.style.textAlign = 'center';
9157
+ cell.style.color = 'var(--dim)';
9158
+ cell.style.padding = '40px';
9159
+ }
9160
+ return;
9161
+ }
9098
9162
  if (!agentSelect) return;
9099
9163
 
9164
+ if (data.error) {
9165
+ agentSelect.innerHTML = `<option value="">${esc(t('roles.selectAgentPlaceholder'))}</option>`;
9166
+ rolesCurrentAgent = null;
9167
+ if (tbody) {
9168
+ tbody.innerHTML = '';
9169
+ const row = tbody.insertRow();
9170
+ const cell = row.insertCell();
9171
+ cell.colSpan = 7;
9172
+ cell.textContent = `${t('common.error')}: ${String(data.error)}`;
9173
+ cell.style.textAlign = 'center';
9174
+ cell.style.color = 'var(--red)';
9175
+ cell.style.padding = '40px';
9176
+ }
9177
+ return;
9178
+ }
9179
+
9100
9180
  agentSelect.innerHTML = '';
9101
9181
  const placeholderOption = document.createElement('option');
9102
9182
  placeholderOption.value = '';
@@ -9152,6 +9232,34 @@ function peerTypeText(peerType) {
9152
9232
  return '-';
9153
9233
  }
9154
9234
 
9235
+ function roleVerificationStatus(item) {
9236
+ const status = String(item?.verifyStatus || 'unknown').toLowerCase();
9237
+ if (status === 'agentmd' || status === 'verified') return 'verified';
9238
+ if (status === 'agentmd-unverified' || status === 'unverified') return 'agentmd-unverified';
9239
+ if (status === 'invalid') return 'invalid';
9240
+ return 'unknown';
9241
+ }
9242
+
9243
+ function roleVerificationMatches(item, filter) {
9244
+ if (!filter) return true;
9245
+ const status = roleVerificationStatus(item);
9246
+ return filter === 'verified' ? status === 'verified' : status === filter;
9247
+ }
9248
+
9249
+ function roleVerificationBadge(item) {
9250
+ const status = roleVerificationStatus(item);
9251
+ if (status === 'invalid') {
9252
+ return ` <span class="verify-badge verify-invalid" title="${esc(t('roles.verify.invalid'))}">⚠️ ${esc(t('roles.verify.invalidShort'))}</span>`;
9253
+ }
9254
+ if (status === 'agentmd-unverified') {
9255
+ return ` <span class="verify-badge verify-unverified" title="${esc(t('roles.verify.unverified'))}">⚠️</span>`;
9256
+ }
9257
+ if (status === 'unknown') {
9258
+ return ` <span class="verify-badge verify-unknown" title="${esc(t('roles.verify.unknown'))}">❓</span>`;
9259
+ }
9260
+ return '';
9261
+ }
9262
+
9155
9263
  function roleClass(role) {
9156
9264
  return String(role || 'none').replace(/[^a-z0-9_-]/gi, '-');
9157
9265
  }
@@ -9188,6 +9296,16 @@ function renderRelationsTable(data, filterAid = null) {
9188
9296
  if (!tbody) return;
9189
9297
 
9190
9298
  tbody.innerHTML = '';
9299
+ if (data?.error) {
9300
+ const row = tbody.insertRow();
9301
+ const cell = row.insertCell();
9302
+ cell.colSpan = 7;
9303
+ cell.textContent = `${t('common.error')}: ${String(data.error)}`;
9304
+ cell.style.textAlign = 'center';
9305
+ cell.style.color = 'var(--red)';
9306
+ cell.style.padding = '40px';
9307
+ return;
9308
+ }
9191
9309
  let conversations = data.conversations || [];
9192
9310
 
9193
9311
  if (filterAid) {
@@ -9215,6 +9333,16 @@ function renderRelationsTable(data, filterAid = null) {
9215
9333
  conversations = conversations.filter(conv => conv.chatType === rolesChatTypeFilter);
9216
9334
  }
9217
9335
 
9336
+ if (rolesVerifyFilter) {
9337
+ conversations = conversations
9338
+ .map(conv => conv.chatType === 'group'
9339
+ ? { ...conv, members: (conv.members || []).filter(member => roleVerificationMatches(member, rolesVerifyFilter)) }
9340
+ : conv)
9341
+ .filter(conv => conv.chatType === 'group'
9342
+ ? (conv.members || []).length > 0
9343
+ : roleVerificationMatches(conv, rolesVerifyFilter));
9344
+ }
9345
+
9218
9346
  conversations.sort((a, b) => {
9219
9347
  const lastDiff = (b.lastAt || 0) - (a.lastAt || 0);
9220
9348
  if (lastDiff !== 0) return lastDiff;
@@ -9244,10 +9372,12 @@ function renderPrivateConversationRow(tbody, conv) {
9244
9372
  const peerId = conv.peerId || conv.peerAid || conv.peerKey || conv.conversationId;
9245
9373
  const name = conv.peerName || conv.name || shortAid(peerId);
9246
9374
  const assignedRole = assignedRoleFor('private', conv);
9375
+ const verifyBadge = roleVerificationBadge(conv);
9376
+ const verifyClass = roleVerificationStatus(conv) === 'verified' ? '' : ` peer-${roleVerificationStatus(conv) === 'unknown' ? 'unknown' : 'unverified'}`;
9247
9377
  const row = tbody.insertRow();
9248
9378
  row.innerHTML = `
9249
9379
  <td><span class="chat-tag chat-tag-private">${esc(t('roles.chatType.private') || 'Private')}</span></td>
9250
- <td><strong>${esc(name)}</strong></td>
9380
+ <td class="${verifyClass.trim()}"><strong>${esc(name)}</strong>${verifyBadge}</td>
9251
9381
  <td><code>${esc(peerId)}</code></td>
9252
9382
  <td>${esc(peerTypeText(conv.peerType))}</td>
9253
9383
  <td><span class="role-badge role-${esc(roleClass(conv.role))}">${esc(displayRole(conv.role))}</span></td>
@@ -9312,11 +9442,13 @@ function renderGroupMemberRow(tbody, conv, member) {
9312
9442
  const peerId = member.peerId || member.peerAid || member.peerKey;
9313
9443
  const name = member.peerName || shortAid(peerId);
9314
9444
  const assignedRole = assignedRoleFor('group-member', member);
9445
+ const verifyBadge = roleVerificationBadge(member);
9446
+ const verifyClass = roleVerificationStatus(member) === 'verified' ? '' : ` peer-${roleVerificationStatus(member) === 'unknown' ? 'unknown' : 'unverified'}`;
9315
9447
  const row = tbody.insertRow();
9316
9448
  row.className = 'group-member-row';
9317
9449
  row.innerHTML = `
9318
9450
  <td></td>
9319
- <td><span class="group-member-indent">${esc(name)}</span></td>
9451
+ <td class="${verifyClass.trim()}"><span class="group-member-indent">${esc(name)}</span>${verifyBadge}</td>
9320
9452
  <td><code>${esc(peerId)}</code></td>
9321
9453
  <td>${esc(peerTypeText(member.peerType))}</td>
9322
9454
  <td><span class="role-badge role-${esc(roleClass(member.role))}">${esc(displayRole(member.role))}</span></td>
@@ -9344,7 +9476,7 @@ async function updatePeerRoleOverride(agentAid, scope, targetId, role, groupId,
9344
9476
  if (peerId) body.peerId = peerId;
9345
9477
  if (role) body.role = role;
9346
9478
 
9347
- const res = await fetch(authApiUrl(`api/assignments/peer/${encodeURIComponent(agentAid)}/${encodeURIComponent(targetId)}`), {
9479
+ const res = await authFetch(`api/assignments/peer/${encodeURIComponent(agentAid)}/${encodeURIComponent(targetId)}`, {
9348
9480
  method: role ? 'PUT' : 'DELETE',
9349
9481
  headers: {
9350
9482
  'Content-Type': 'application/json',
@@ -9639,7 +9771,7 @@ function renderDefaultRoleSelector(data) {
9639
9771
  const next = { private: privateSelect.value || null, group: groupSelect.value || null };
9640
9772
  try {
9641
9773
  const token = localStorage.getItem(TOKEN_KEY);
9642
- const writeRes = await fetch(roleDefinitionsApi('api/role-definitions'), {
9774
+ const writeRes = await authFetch(roleDefinitionsApi('api/role-definitions'), {
9643
9775
  method: 'PUT',
9644
9776
  headers: {
9645
9777
  'Content-Type': 'application/json',
@@ -9707,7 +9839,7 @@ function formatCommandPermissions(commandPermissions) {
9707
9839
  async function fetchRoleOperationsCatalog() {
9708
9840
  if (roleOperationsCache) return roleOperationsCache;
9709
9841
  const token = localStorage.getItem(TOKEN_KEY);
9710
- const res = await fetch(authApiUrl('api/role-definitions/operations'), {
9842
+ const res = await authFetch('api/role-definitions/operations', {
9711
9843
  headers: { 'Authorization': `Bearer ${token}` }
9712
9844
  });
9713
9845
  if (!res.ok) throw new Error('Failed to load CLI operation catalog');
@@ -10229,8 +10361,9 @@ function createRoleCard(roleName, roleDef) {
10229
10361
  const model = roleDef.permissions?.['baseagents.claude.model']?.default || '—';
10230
10362
  const commandStats = getCommandPermissionStats(roleDef.commandPermissions);
10231
10363
 
10232
- // 判断是否为内置角色
10233
- 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'];
10234
10367
  const isBuiltin = builtinRoles.includes(roleName);
10235
10368
 
10236
10369
  card.innerHTML = `
@@ -10316,7 +10449,7 @@ function initRoleDefinitionsTab() {
10316
10449
 
10317
10450
  try {
10318
10451
  const token = localStorage.getItem(TOKEN_KEY);
10319
- const res = await fetch(roleDefinitionsApi(`api/role-definitions/${roleName}/reset`), {
10452
+ const res = await authFetch(roleDefinitionsApi(`api/role-definitions/${roleName}/reset`), {
10320
10453
  method: 'POST',
10321
10454
  headers: { 'Authorization': `Bearer ${token}` }
10322
10455
  });
@@ -10361,7 +10494,7 @@ function initRoleDefinitionsTab() {
10361
10494
  : replacement
10362
10495
  ? `?replacementRoleId=${encodeURIComponent(replacement)}`
10363
10496
  : '';
10364
- const res = await fetch(roleDefinitionsApi(`api/role-definitions/${roleName}${replacementQuery}`), {
10497
+ const res = await authFetch(roleDefinitionsApi(`api/role-definitions/${roleName}${replacementQuery}`), {
10365
10498
  method: 'DELETE',
10366
10499
  headers: { 'Authorization': `Bearer ${token}` }
10367
10500
  });
@@ -10514,7 +10647,7 @@ function renderRoleModelPermissionSection(roleDef) {
10514
10647
 
10515
10648
  async function fetchRoleModelPermissionData(roleName) {
10516
10649
  const token = localStorage.getItem(TOKEN_KEY);
10517
- 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`), {
10518
10651
  headers: { 'Authorization': `Bearer ${token}` }
10519
10652
  });
10520
10653
  if (!res.ok) throw new Error('Failed to load role model permissions');
@@ -10522,13 +10655,14 @@ async function fetchRoleModelPermissionData(roleName) {
10522
10655
  return json.data;
10523
10656
  }
10524
10657
 
10525
- async function fetchModelCatalogData(baseagent = 'claude') {
10658
+ async function fetchModelCatalogData(baseagent = 'claude', aid = '') {
10526
10659
  const token = localStorage.getItem(TOKEN_KEY);
10527
- 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)}` : ''}`, {
10528
10661
  headers: { 'Authorization': `Bearer ${token}` }
10529
10662
  });
10530
10663
  if (!res.ok) throw new Error('Failed to load model catalog');
10531
10664
  const json = await res.json();
10665
+ if (json.data?.diagnostics?.some(d => d.code === 'AUTH_FAILED')) throw new Error('Model gateway authentication failed');
10532
10666
  return json.data;
10533
10667
  }
10534
10668
 
@@ -10691,7 +10825,7 @@ async function initRoleModelPermissionEditor(container, roleName, roleDef) {
10691
10825
  try {
10692
10826
  const data = roleName && roleName !== '__new__'
10693
10827
  ? await fetchRoleModelPermissionData(roleName)
10694
- : await fetchModelCatalogData();
10828
+ : await fetchModelCatalogData('claude', roleDefinitionsCurrentAgent || state.roleDefinitions?.aid || '');
10695
10829
  catalogModels = data?.catalog?.models || data?.models || [];
10696
10830
  } catch (err) {
10697
10831
  console.warn('[roleDefinitions] Failed to load model catalog:', err);
@@ -11488,7 +11622,7 @@ async function saveRoleDefinition() {
11488
11622
  const method = isNew ? 'POST' : 'PUT';
11489
11623
  const url = isNew ? roleDefinitionsApi('api/role-definitions') : roleDefinitionsApi(`api/role-definitions/${encodeURIComponent(actualRoleName)}`);
11490
11624
 
11491
- const res = await fetch(url, {
11625
+ const res = await authFetch(url, {
11492
11626
  method,
11493
11627
  headers: {
11494
11628
  'Content-Type': 'application/json',