ec-web 0.0.26 → 0.0.27

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.
@@ -197,6 +197,8 @@ const translations = {
197
197
  'agents.th.lastActivity': '最后活动',
198
198
  'agents.th.operations': '操作',
199
199
  'agents.th.projectPath': '项目路径',
200
+ 'agents.lifecycle.created': '待初始化',
201
+ 'agents.lifecycle.bootstrapping': '初始化中',
200
202
 
201
203
  // Agent operations
202
204
  'agents.op.stopping': '停止中…',
@@ -322,6 +324,13 @@ const translations = {
322
324
  'sessions.empty.noMatch': '无匹配会话',
323
325
  'sessions.empty.noSessions': '该项目暂无会话',
324
326
  'sessions.empty.noContent': '该会话暂无内容',
327
+ 'sessions.empty.noBaseagent': '尚未配置默认 Base Agent(agents/defaults.json 的 active_baseagent)',
328
+ 'sessions.baseagentNone': '未配置 Base Agent',
329
+ 'sessions.baseagentDefault': '默认',
330
+ 'baseagents.claude': 'Claude',
331
+ 'baseagents.codex': 'Codex',
332
+ 'baseagents.gemini': 'Gemini',
333
+ 'baseagents.ecagent': 'EC Agent',
325
334
  'sessions.loadOlder': '加载更早内容',
326
335
  'sessions.loadFullResult': '加载完整结果',
327
336
  'sessions.header.project': '项目',
@@ -1128,6 +1137,8 @@ const translations = {
1128
1137
  'agents.th.lastActivity': 'Last Activity',
1129
1138
  'agents.th.operations': 'Operations',
1130
1139
  'agents.th.projectPath': 'Project Path',
1140
+ 'agents.lifecycle.created': 'Created',
1141
+ 'agents.lifecycle.bootstrapping': 'Bootstrapping',
1131
1142
 
1132
1143
  // Agent operations
1133
1144
  'agents.op.stopping': 'Stopping…',
@@ -1253,6 +1264,13 @@ const translations = {
1253
1264
  'sessions.empty.noMatch': 'No matching sessions',
1254
1265
  'sessions.empty.noSessions': 'No sessions in this project',
1255
1266
  'sessions.empty.noContent': 'No content in this session',
1267
+ 'sessions.empty.noBaseagent': 'No default Base Agent configured (active_baseagent in agents/defaults.json)',
1268
+ 'sessions.baseagentNone': 'No Base Agent configured',
1269
+ 'sessions.baseagentDefault': 'default',
1270
+ 'baseagents.claude': 'Claude',
1271
+ 'baseagents.codex': 'Codex',
1272
+ 'baseagents.gemini': 'Gemini',
1273
+ 'baseagents.ecagent': 'EC Agent',
1256
1274
  'sessions.loadOlder': 'Load older content',
1257
1275
  'sessions.loadFullResult': 'Load full result',
1258
1276
  'sessions.header.project': 'Project',
@@ -2096,36 +2114,25 @@ function connect() {
2096
2114
  ws.onopen = () => {
2097
2115
  setConnStatus('● ' + t('status.connected'), 'ok');
2098
2116
  reconnectDelay = 1000;
2099
- // 获取可用的 baseagent
2100
- fetch(`${BASE}api/available-baseagents`)
2101
- .then(r => r.json())
2102
- .then(data => {
2103
- availableBaseagents = data;
2104
- // 如果当前没有选中 baseagent,默认选第一个可用的
2105
- if (!sessSel.baseagent) {
2106
- sessSel.baseagent = data.claude ? 'claude' : (data.codex ? 'codex' : null);
2107
- }
2108
- console.log('[ecweb] Available baseagents:', availableBaseagents, 'Selected:', sessSel.baseagent);
2109
- // 重新订阅当前视图(带上正确的参数)
2110
- if (currentView === 'session') {
2111
- subscribe('session', sessSel);
2112
- } else if (currentView === 'roleManagement') {
2113
- subscribeRoleManagementSubview();
2114
- } else if (currentView === 'system' && systemContentMode === 'monitor') {
2115
- subscribe('monitor', { range: monRange });
2116
- } else {
2117
- subscribe(currentView, pendingSub || {});
2118
- }
2119
- })
2120
- .catch(err => {
2121
- console.warn('[ecweb] Failed to fetch available-baseagents:', err);
2122
- // 失败时默认使用 claude
2123
- availableBaseagents = { claude: true, codex: false };
2124
- if (!sessSel.baseagent) sessSel.baseagent = 'claude';
2125
- if (currentView === 'roleManagement') subscribeRoleManagementSubview();
2126
- else if (currentView === 'system' && systemContentMode === 'monitor') subscribe('monitor', { range: monRange });
2127
- else subscribe(currentView, pendingSub || {});
2128
- });
2117
+ // 获取 base agent 配置(defaults.json 的 active_baseagent + baseagents)
2118
+ // 配置清单(defaults.json)与最近活跃 Agent 并行取,后者决定会话页默认后端。
2119
+ Promise.all([
2120
+ menuSend({ type: 'menu.query', name: 'baseagents' }).then(response => {
2121
+ const result = mResp(response);
2122
+ if (result.error) throw new Error(result.error.message || 'Baseagent query failed');
2123
+ return result.data;
2124
+ }).catch(err => {
2125
+ console.warn('[ecweb] Failed to fetch baseagents:', err);
2126
+ return null;
2127
+ }),
2128
+ loadActiveAgentBaseagent(),
2129
+ ]).then(([data, activeBaseagent]) => {
2130
+ applyBaseagentConfig(data);
2131
+ // 没有显式选择时跟随最近活跃 Agent 的后端(其次才是 defaults 的默认后端)。
2132
+ if (!sessSel.baseagent) sessSel.baseagent = activeBaseagent || defaultSessionBaseagent();
2133
+ console.log('[ecweb] Base agents:', baseagentConfig, 'Active agent:', activeAgentAid, 'Selected:', sessSel.baseagent);
2134
+ resubscribeCurrentView();
2135
+ });
2129
2136
  };
2130
2137
 
2131
2138
  ws.onmessage = (ev) => {
@@ -2178,6 +2185,8 @@ function connect() {
2178
2185
  } else {
2179
2186
  state[msg.view] = msg.data;
2180
2187
  }
2188
+ // agent 列表本身就带 baseagent + lastActivity,顺手更新"最近活跃 Agent"。
2189
+ if (msg.view === 'agents') noteAgentActivity(msg.data?.agents);
2181
2190
  const messageMatchesCurrentView = msg.view === currentView
2182
2191
  || (currentView === 'system' && systemContentMode === 'monitor' && msg.view === 'monitor')
2183
2192
  || (currentView === 'roleManagement' && msg.view === roleManagementViewKind());
@@ -2284,7 +2293,38 @@ let sessSourceFilter = 'all'; // all / trigger / agent / cli / user / unknown
2284
2293
  let sessChatMode = false; // false=完整视图,true=对话视图(折叠处理过程)
2285
2294
  let monRange = '2m'; // Monitor 时间窗口:2m / 10m / 1h
2286
2295
  let logSel = { source: null, level: 'all', module: 'all', search: '', limit: 500, follow: true };
2287
- let availableBaseagents = { claude: false, codex: false }; // 可用的 baseagent
2296
+ // ECWeb 界面的 base agent 展示名统一走这里的 i18n 键,而不是父包别名表:
2297
+ // 别名表面向 CLI/日志(claude → "Claude Code"、ecagent → "ecagent"),
2298
+ // 面板里应保持简洁一致(Claude / Codex / Gemini / EC Agent)。
2299
+ function baseagentDisplayLabel(id) {
2300
+ const key = `baseagents.${id}`;
2301
+ const label = t(key);
2302
+ return label === key ? (id || 'Baseagent') : label;
2303
+ }
2304
+
2305
+ function applyBaseagentConfig(data) {
2306
+ const list = Array.isArray(data?.baseagents) ? data.baseagents : [];
2307
+ baseagentConfig = {
2308
+ active: typeof data?.active === 'string' && data.active ? data.active : null,
2309
+ baseagents: list
2310
+ .filter(entry => entry && typeof entry.id === 'string' && entry.id)
2311
+ .map(entry => ({
2312
+ id: entry.id,
2313
+ label: baseagentDisplayLabel(entry.id),
2314
+ detected: entry.detected === true,
2315
+ configured: entry.configured === true,
2316
+ loaded: entry.loaded === true,
2317
+ active: entry.active === true,
2318
+ })),
2319
+ };
2320
+ }
2321
+
2322
+ function resubscribeCurrentView() {
2323
+ if (currentView === 'session') subscribe('session', sessSel);
2324
+ else if (currentView === 'roleManagement') subscribeRoleManagementSubview();
2325
+ else if (currentView === 'system' && systemContentMode === 'monitor') subscribe('monitor', { range: monRange });
2326
+ else subscribe(currentView, pendingSub || {});
2327
+ }
2288
2328
  let rolesCurrentAgent = null;
2289
2329
  let rolesSearchTerm = '';
2290
2330
  let rolesVerifyFilter = '';
@@ -2694,6 +2734,11 @@ function agentStateBadge(s, agStatus, connStatus) {
2694
2734
  return `<span class="state-badge connected">${t('status.connected')}</span>`;
2695
2735
  }
2696
2736
 
2737
+ function agentLifecycleBadge(lifecycle) {
2738
+ if (lifecycle !== 'created' && lifecycle !== 'bootstrapping') return '';
2739
+ return `<span class="lifecycle-badge ${lifecycle}">${t(`agents.lifecycle.${lifecycle}`)}</span>`;
2740
+ }
2741
+
2697
2742
  // 发送方式图标标记
2698
2743
  const MSG_KIND_META = {
2699
2744
  send: { icon: '💬', label: () => t('messages.msgKind.reply') },
@@ -3100,7 +3145,7 @@ function renderAgents(data) {
3100
3145
  ? agentOpsBusyHtml(ag.aid, _agentOps.get(ag.aid))
3101
3146
  : agentDisabledOpsHtml(ag.aid);
3102
3147
  html += `<tr class="ag-main">` +
3103
- `<td><div class="ag-id"><span class="dot off"></span><span class="ag-id-text"><span class="ag-name">${esc(ag.displayName || shortAid(ag.aid))}</span><span class="ag-aid">${esc(ag.aid)}</span></span></div></td>` +
3148
+ `<td><div class="ag-id"><span class="dot off"></span><span class="ag-id-text"><span class="ag-name">${esc(ag.displayName || shortAid(ag.aid))}${agentLifecycleBadge(ag.lifecycle)}</span><span class="ag-aid">${esc(ag.aid)}</span></span></div></td>` +
3104
3149
  `<td style="font-size:11px;font-family:monospace">${esc(ag.projectPath || '—')}</td>` +
3105
3150
  `<td class="agent-ops-cell">${ops}</td></tr>`;
3106
3151
  }
@@ -3146,7 +3191,7 @@ function renderAgents(data) {
3146
3191
  const model = ag.model || ag.baseagent || '—';
3147
3192
 
3148
3193
  const idCell = `<div class="ag-id"><span class="dot ${dotCls}" title="${esc(connStatus)}"></span>` +
3149
- `<span class="ag-id-text"><span class="ag-name">${esc(name)}</span>` +
3194
+ `<span class="ag-id-text"><span class="ag-name">${esc(name)}${agentLifecycleBadge(ag.lifecycle)}</span>` +
3150
3195
  `<span class="ag-aid">${esc(ag.aid)}</span></span></div>`;
3151
3196
 
3152
3197
  html += `<tr class="ag-main">` +
@@ -3394,7 +3439,7 @@ function renderMsg(data) {
3394
3439
  : '';
3395
3440
  peersHtml += `<div class="list-item${sel}" data-peer="${esc(p.peerId)}">` +
3396
3441
  `<div class="name">${groupMark}${partitionMark}<span class="msg-peer-title" title="${esc(chatTitle)}">${esc(shortId(chatTitle))}</span>${channelTag}</div>` +
3397
- `<div class="sub">${p.partition === 'thread' ? `${esc(shortId(displayName))} · ` : ''}↓${p.inbound} ↑${p.outbound} · ${fmtAgo(p.lastAt)}</div></div>`;
3442
+ `<div class="sub">${p.lable ? `${esc(p.lable)} · ` : ''}${p.partition === 'thread' ? `${esc(shortId(displayName))} · ` : ''}↓${p.inbound} ↑${p.outbound} · ${fmtAgo(p.lastAt)}</div></div>`;
3398
3443
  }
3399
3444
  }
3400
3445
  $('#msg-peers').innerHTML = peersHtml;
@@ -3494,6 +3539,108 @@ function filterSessionTranscripts(transcripts, showAll, sourceType = 'all') {
3494
3539
  });
3495
3540
  }
3496
3541
 
3542
+ // Base agent 清单来自 EvolCore 的 baseagents menu 查询;前端不维护候选数组。
3543
+ let baseagentConfig = { active: null, baseagents: [] };
3544
+
3545
+ function baseagentConfigLoaded() {
3546
+ return Array.isArray(baseagentConfig.baseagents) && baseagentConfig.baseagents.length > 0;
3547
+ }
3548
+
3549
+ function configuredBaseagents() {
3550
+ return baseagentConfig.baseagents || [];
3551
+ }
3552
+
3553
+ function configuredActiveBaseagent() {
3554
+ return baseagentConfig.active || null;
3555
+ }
3556
+
3557
+ // ── 最近活跃 Agent 的后端 ──
3558
+ // 数据来自 agent 列表(menu.query name=agent,与 agents 页面同源):每条带
3559
+ // baseagent + lastActivity。谁最近被驱动过,会话页就默认看谁的后端。
3560
+ let activeAgentBaseagent = null; // 最近活跃 Agent 的 base agent
3561
+ let activeAgentAid = null; // 对应的 Agent AID
3562
+
3563
+ function pickActiveAgentBaseagent(agents) {
3564
+ let best = null;
3565
+ for (const ag of agents || []) {
3566
+ const baseagent = String(ag?.baseagent || '').trim();
3567
+ const lastActivity = Number(ag?.lastActivity) || 0;
3568
+ if (!baseagent || lastActivity <= 0) continue;
3569
+ if (!best || lastActivity > best.lastActivity) {
3570
+ best = { baseagent, lastActivity, aid: ag.aid || null };
3571
+ }
3572
+ }
3573
+ return best;
3574
+ }
3575
+
3576
+ function noteAgentActivity(agents) {
3577
+ const best = pickActiveAgentBaseagent(agents);
3578
+ activeAgentBaseagent = best?.baseagent || null;
3579
+ activeAgentAid = best?.aid || null;
3580
+ return activeAgentBaseagent;
3581
+ }
3582
+
3583
+ /** 拉取 agent 列表并算出最近活跃 Agent 的后端(失败则返回 null,不猜测)。 */
3584
+ async function loadActiveAgentBaseagent() {
3585
+ try {
3586
+ const r = mResp(await menuSend({
3587
+ type: 'menu.query',
3588
+ name: 'agent',
3589
+ args: { options: 'all' },
3590
+ }));
3591
+ if (r.error) return null;
3592
+ const agents = Array.isArray(r.data?.agents) ? r.data.agents : (Array.isArray(r.data) ? r.data : []);
3593
+ return noteAgentActivity(agents);
3594
+ } catch {
3595
+ return null;
3596
+ }
3597
+ }
3598
+
3599
+ /** 会话页可选的 base agent:配置里有的、本机可用的,以及最近活跃 Agent 用到的。 */
3600
+ function sessionBaseagentCandidates() {
3601
+ const list = [...configuredBaseagents()];
3602
+ const seen = new Set(list.map(entry => entry.id));
3603
+ for (const id of [activeAgentBaseagent]) {
3604
+ if (!id || seen.has(id)) continue;
3605
+ seen.add(id);
3606
+ list.push({
3607
+ id,
3608
+ label: baseagentDisplayLabel(id),
3609
+ detected: true,
3610
+ configured: false,
3611
+ active: false,
3612
+ });
3613
+ }
3614
+ return list;
3615
+ }
3616
+
3617
+ /**
3618
+ * 会话页默认后端:跟随最近活跃的 Agent(含 trigger 等后台任务驱动的);
3619
+ * 没有任何活跃记录时才用 defaults 的默认后端。两者都没有就不猜(返回 null)。
3620
+ */
3621
+ function defaultSessionBaseagent() {
3622
+ if (activeAgentBaseagent) return activeAgentBaseagent;
3623
+ return configuredActiveBaseagent();
3624
+ }
3625
+
3626
+ // 会话视图列出「已配置或本机可用」的 base agent:清单来自 defaults.json 的
3627
+ // baseagents 与本机检测,不维护候选数组,也不注入任何默认后端。
3628
+ // 默认后端(defaults.active_baseagent)单独标注,避免它与"当前选中"混淆。
3629
+ function sessionBaseagentOptions(selected) {
3630
+ let html = '';
3631
+ for (const entry of sessionBaseagentCandidates()) {
3632
+ if (!entry.configured && !entry.detected && !entry.loaded && !entry.active) continue;
3633
+ const label = entry.active
3634
+ ? `${entry.label} (${t('sessions.baseagentDefault')})`
3635
+ : entry.label;
3636
+ html += `<option value="${esc(entry.id)}"${selected === entry.id ? ' selected' : ''}>${esc(label)}</option>`;
3637
+ }
3638
+ if (!html) {
3639
+ return `<option value="" selected disabled>${esc(t('sessions.baseagentNone'))}</option>`;
3640
+ }
3641
+ return html;
3642
+ }
3643
+
3497
3644
  function renderSessionSourceBadge(sourceType) {
3498
3645
  const type = ['trigger', 'agent', 'cli', 'user'].includes(sourceType) ? sourceType : 'unknown';
3499
3646
  const label = t(`sessions.source.${type}`);
@@ -3522,13 +3669,7 @@ function renderSession(data) {
3522
3669
  ).join('');
3523
3670
 
3524
3671
  // baseagent 下拉选择器(只显示可用的)
3525
- let baseagentOpts = '';
3526
- if (availableBaseagents.claude) {
3527
- baseagentOpts += `<option value="claude"${sessSel.baseagent === 'claude' ? ' selected' : ''}>Claude</option>`;
3528
- }
3529
- if (availableBaseagents.codex) {
3530
- baseagentOpts += `<option value="codex"${sessSel.baseagent === 'codex' ? ' selected' : ''}>Codex</option>`;
3531
- }
3672
+ const baseagentOpts = sessionBaseagentOptions(sessSel.baseagent);
3532
3673
  const sourceTypes = ['all', 'user', 'agent', 'trigger', 'cli', 'unknown'];
3533
3674
  const sourceOpts = sourceTypes.map(type => {
3534
3675
  const label = type === 'all' ? t('sessions.filter.sourceAll') : t(`sessions.source.${type}`);
@@ -3554,7 +3695,10 @@ function renderSession(data) {
3554
3695
  '<div class="sess-items">';
3555
3696
 
3556
3697
  if (!filtered.length) {
3557
- listHtml += '<div class="empty">' + (transcripts.length ? t('sessions.empty.noMatch') : t('sessions.empty.noSessions')) + '</div>';
3698
+ const emptyText = data.noConfiguredBaseagent
3699
+ ? t('sessions.empty.noBaseagent')
3700
+ : (transcripts.length ? t('sessions.empty.noMatch') : t('sessions.empty.noSessions'));
3701
+ listHtml += '<div class="empty">' + emptyText + '</div>';
3558
3702
  }
3559
3703
  for (const t of filtered) {
3560
3704
  const sel = t.id === sessSel.sessionId ? ' sel' : '';
@@ -3740,7 +3884,7 @@ const CAT_META = {
3740
3884
 
3741
3885
  function renderSessHeader(h, baseagent) {
3742
3886
  if (!h || !h.sessionId) return '';
3743
- const baseagentLabel = baseagent === 'codex' ? 'Codex' : baseagent === 'claude' ? 'Claude' : (baseagent || 'Baseagent');
3887
+ const baseagentLabel = (configuredBaseagents().find(entry => entry.id === baseagent)?.label) || baseagent || 'Baseagent';
3744
3888
  const title = `${baseagentLabel} ${h.sessionId}`;
3745
3889
  const tok = (h.inputTokens || h.outputTokens)
3746
3890
  ? `<span class="sh-stat">🔢 in ${fmtNum(h.inputTokens)} / out ${fmtNum(h.outputTokens)}</span>` : '';
@@ -4190,7 +4334,18 @@ function agentOpNew() {
4190
4334
  openAgentCreateDrawer();
4191
4335
  }
4192
4336
 
4193
- const AGENT_BASEAGENT_TYPES = ['claude', 'codex', 'gemini', 'ecagent'];
4337
+ // 可选 base agent 由 defaults.json 的 baseagents + 本机检测决定(见 applyBaseagentConfig),
4338
+ // 这里不再维护写死的候选数组。
4339
+ function agentBaseagentTypes() {
4340
+ const ids = configuredBaseagents()
4341
+ .filter(entry => entry.configured || entry.detected || entry.active)
4342
+ .map(entry => entry.id);
4343
+ return ids.length ? ids : [];
4344
+ }
4345
+
4346
+ function agentBaseagentLabel(id) {
4347
+ return configuredBaseagents().find(entry => entry.id === id)?.label || id;
4348
+ }
4194
4349
  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,}$/;
4195
4350
  let _agentEdit = null;
4196
4351
  let _agentCreate = null;
@@ -4206,7 +4361,7 @@ function agentSafeConfig(data) {
4206
4361
 
4207
4362
  function agentActiveBaseagent(data) {
4208
4363
  const safe = agentSafeConfig(data);
4209
- return String(safe.active_baseagent || data?.config?.active_baseagent || data?.config?.baseagent || 'claude');
4364
+ return String(safe.active_baseagent || data?.config?.active_baseagent || data?.config?.baseagent || configuredActiveBaseagent() || '');
4210
4365
  }
4211
4366
 
4212
4367
  function agentBaseagentBlock(data, type) {
@@ -4232,6 +4387,7 @@ function agentOriginalEditState(aid, data) {
4232
4387
  return {
4233
4388
  name: String(data?.identity?.name || data?.displayName || shortAid(aid)),
4234
4389
  owners: owners.map(String),
4390
+ fullaccess: safe.fullaccess?.enabled === true,
4235
4391
  projectPath,
4236
4392
  activeBaseagent: active,
4237
4393
  model: block.model == null ? '' : String(block.model),
@@ -4264,7 +4420,7 @@ function agentBaseagentOptionValues(items) {
4264
4420
  return values;
4265
4421
  }
4266
4422
 
4267
- function agentBaseagentOptions(active, options = AGENT_BASEAGENT_TYPES) {
4423
+ function agentBaseagentOptions(active, options = agentBaseagentTypes()) {
4268
4424
  const seen = new Set();
4269
4425
  const list = [];
4270
4426
  const push = value => {
@@ -4276,9 +4432,10 @@ function agentBaseagentOptions(active, options = AGENT_BASEAGENT_TYPES) {
4276
4432
  (options || []).forEach(push);
4277
4433
  const activeWasProvided = seen.has(String(active || '').trim());
4278
4434
  if (active) push(active);
4279
- if (!list.length) AGENT_BASEAGENT_TYPES.forEach(push);
4280
4435
  return list.map(type => {
4281
- const label = !activeWasProvided && type === active ? `${type} (${t('common.currentSuffix')})` : type;
4436
+ const label = !activeWasProvided && type === active
4437
+ ? `${agentBaseagentLabel(type)} (${t('common.currentSuffix')})`
4438
+ : agentBaseagentLabel(type);
4282
4439
  return `<option value="${esc(type)}"${type === active ? ' selected' : ''}>${esc(label)}</option>`;
4283
4440
  }).join('');
4284
4441
  }
@@ -4657,7 +4814,11 @@ function setAgentSelectError(select, current) {
4657
4814
  }
4658
4815
 
4659
4816
  function agentCreateDefaultBaseagent() {
4660
- return AGENT_BASEAGENT_TYPES.find(type => availableBaseagents && availableBaseagents[type] === true) || 'claude';
4817
+ // 新 Agent 默认继承 defaults.json 的 active_baseagent;没有配置时留空,
4818
+ // 由校验拦下,而不是悄悄替用户选一个后端。
4819
+ return configuredActiveBaseagent()
4820
+ || configuredBaseagents().find(entry => entry.detected)?.id
4821
+ || '';
4661
4822
  }
4662
4823
 
4663
4824
  function readAgentCreateValues(root) {
@@ -4679,7 +4840,7 @@ function validateAgentCreateValues(values) {
4679
4840
  if (!values.name) return t('agents.edit.validation.nameRequired');
4680
4841
  if (!values.owner) return t('agents.edit.validation.ownerRequired');
4681
4842
  if (!AGENT_AID_RE.test(values.owner)) return t('agents.edit.validation.invalidOwner');
4682
- if (!AGENT_BASEAGENT_TYPES.includes(values.baseagent)) return tf('agents.edit.validation.invalidBaseagent', { baseagent: values.baseagent });
4843
+ if (!agentBaseagentTypes().includes(values.baseagent)) return tf('agents.edit.validation.invalidBaseagent', { baseagent: values.baseagent });
4683
4844
  return '';
4684
4845
  }
4685
4846
 
@@ -4889,8 +5050,9 @@ function readAgentEditValues(root) {
4889
5050
  });
4890
5051
  return {
4891
5052
  owners: agentOwnersFromText(root.querySelector('#ag-edit-owners')?.value || ''),
5053
+ fullaccess: root.querySelector('#ag-edit-fullaccess')?.checked,
4892
5054
  channelOwners,
4893
- activeBaseagent: root.querySelector('#ag-edit-active')?.value || 'claude',
5055
+ activeBaseagent: root.querySelector('#ag-edit-active')?.value || configuredActiveBaseagent() || '',
4894
5056
  model: root.querySelector('#ag-edit-model')?.value.trim() || '',
4895
5057
  effort: root.querySelector('#ag-edit-effort')?.value.trim() || '',
4896
5058
  };
@@ -4952,6 +5114,9 @@ function collectAgentEditPatch(ctx) {
4952
5114
  const configPatch = {};
4953
5115
 
4954
5116
  if (!sameStringArray(values.owners, ctx.original.owners)) configPatch.owners = values.owners;
5117
+ if (values.fullaccess !== undefined && values.fullaccess !== ctx.original.fullaccess) {
5118
+ configPatch.fullaccess = { enabled: values.fullaccess };
5119
+ }
4955
5120
  if (values.channelOwners.length > 0) configPatch.channelOwners = values.channelOwners;
4956
5121
  if (values.activeBaseagent !== ctx.original.activeBaseagent) {
4957
5122
  if (Array.isArray(ctx.availableBaseagents) && ctx.availableBaseagents.length && !ctx.availableBaseagents.includes(values.activeBaseagent)) {
@@ -4977,6 +5142,8 @@ function collectAgentEditPatch(ctx) {
4977
5142
  function setAgentEditSaving(ctx, saving) {
4978
5143
  ctx.saving = saving;
4979
5144
  ctx.backdrop.querySelectorAll('button, input, textarea, select').forEach(el => { el.disabled = saving; });
5145
+ const fullaccess = ctx.backdrop.querySelector('#ag-edit-fullaccess');
5146
+ if (fullaccess) fullaccess.disabled = saving || ctx.data?.fullaccessStatus?.canConfigure !== true;
4980
5147
  const save = ctx.backdrop.querySelector('#ag-edit-save');
4981
5148
  const saveReload = ctx.backdrop.querySelector('#ag-edit-save-reload');
4982
5149
  if (save) save.textContent = saving ? t('common.saving') : t('common.save');
@@ -5169,6 +5336,25 @@ function openAgentConfigDrawer(aid, data) {
5169
5336
  const active = original.activeBaseagent;
5170
5337
  const titleName = original.name || shortAid(aid);
5171
5338
  const jsonText = esc(JSON.stringify(safe, null, 2));
5339
+ const fullaccessStatus = data?.fullaccessStatus || {};
5340
+ const fullaccessHelp = currentLang === 'zh-CN'
5341
+ ? '开启后,同时属于该 Agent Owner 和 daemon Owner 的直接私聊任务可访问宿主环境及 daemon 范围数据。'
5342
+ : 'Enables host and daemon scope for direct private tasks whose sender is both an Agent Owner and a daemon Owner.';
5343
+ const fullaccessHelpLabel = currentLang === 'zh-CN' ? 'Fullaccess 说明' : 'Fullaccess info';
5344
+ const fullaccessState = enabled => currentLang === 'zh-CN'
5345
+ ? (enabled ? '开启' : '关闭')
5346
+ : (enabled ? 'Enabled' : 'Disabled');
5347
+ const fullaccessSection = fullaccessStatus.hostEnabled ?
5348
+ `<section class="ag-edit-section">` +
5349
+ `<div class="ag-edit-fullaccess-row">` +
5350
+ `<span class="ag-edit-field-label">Owner Fullaccess<span class="basic-settings-tip" tabindex="0" role="img" aria-label="${esc(fullaccessHelpLabel)}" data-tooltip="${esc(fullaccessHelp)}">?</span></span>` +
5351
+ `<label class="sess-switch ag-edit-fullaccess-switch">` +
5352
+ `<span id="ag-edit-fullaccess-state" aria-live="polite">${fullaccessState(original.fullaccess)}</span>` +
5353
+ `<input type="checkbox" role="switch" aria-label="Owner Fullaccess" id="ag-edit-fullaccess" ${original.fullaccess ? 'checked' : ''} ${fullaccessStatus.canConfigure === true ? '' : 'disabled'}>` +
5354
+ `<span class="sess-switch-track" aria-hidden="true"><span class="sess-switch-thumb"></span></span>` +
5355
+ `</label>` +
5356
+ `</div>` +
5357
+ `</section>` : '';
5172
5358
 
5173
5359
  const html = `<div class="ag-edit-backdrop" id="ag-edit-backdrop">` +
5174
5360
  `<aside class="ag-edit-drawer" role="dialog" aria-modal="true" aria-labelledby="ag-edit-title">` +
@@ -5183,6 +5369,7 @@ function openAgentConfigDrawer(aid, data) {
5183
5369
  `<section class="ag-edit-section">` +
5184
5370
  `<label class="ag-edit-field"><span>Owners</span><textarea id="ag-edit-owners" rows="2" spellcheck="false">${esc(original.owners.join('\n'))}</textarea></label>` +
5185
5371
  `</section>` +
5372
+ fullaccessSection +
5186
5373
  `<section class="ag-edit-section">` +
5187
5374
  `<h3>${t('agents.edit.section.runtime')}</h3>` +
5188
5375
  `<div class="ag-edit-grid">` +
@@ -5235,6 +5422,9 @@ function openAgentConfigDrawer(aid, data) {
5235
5422
  backdrop.querySelector('#ag-edit-cancel').addEventListener('click', closeAgentConfigDrawer);
5236
5423
  backdrop.querySelector('#ag-edit-save').addEventListener('click', () => saveAgentConfigDrawer(ctx, false));
5237
5424
  backdrop.querySelector('#ag-edit-save-reload').addEventListener('click', () => saveAgentConfigDrawer(ctx, true));
5425
+ backdrop.querySelector('#ag-edit-fullaccess')?.addEventListener('change', (event) => {
5426
+ backdrop.querySelector('#ag-edit-fullaccess-state').textContent = fullaccessState(event.currentTarget.checked);
5427
+ });
5238
5428
  backdrop.querySelector('#ag-edit-active').addEventListener('change', () => syncAgentBaseagentFields(ctx));
5239
5429
  backdrop.querySelector('#ag-edit-model').addEventListener('change', () => {
5240
5430
  const baseagent = backdrop.querySelector('#ag-edit-active')?.value || ctx.original.activeBaseagent;
@@ -5297,11 +5487,17 @@ function agentHealthCard(ag) {
5297
5487
  }
5298
5488
 
5299
5489
  function systemBaseagentCards(baseagents) {
5300
- const list = Array.isArray(baseagents) ? baseagents : [];
5490
+ const list = Array.isArray(baseagents) ? baseagents : (baseagentConfig.baseagents || []);
5301
5491
  return list.map(ba => {
5302
- const ver = ba.version ? `・${ba.version}` : '';
5303
- const title = `Baseagent・${ba.active ? '✓ ' : ''}${ba.name || 'unknown'}${ver}`;
5304
- const detail = [ba.model, ba.effort].filter(Boolean).map(esc).join(' · ') || t('system.baseagent.unspecified');
5492
+ const id = ba.id || ba.name || 'unknown';
5493
+ const ver = ba.version ? ` · ${ba.version}` : '';
5494
+ const title = `Baseagent · ${ba.active ? '✓ ' : ''}${baseagentDisplayLabel(id)}${ver}`;
5495
+ const states = [
5496
+ ba.configured && (currentLang === 'zh-CN' ? '已配置' : 'configured'),
5497
+ ba.detected && (currentLang === 'zh-CN' ? '已检测' : 'detected'),
5498
+ ba.loaded && (currentLang === 'zh-CN' ? '已加载' : 'loaded'),
5499
+ ].filter(Boolean);
5500
+ const detail = [...[ba.model, ba.effort].filter(Boolean), ...states].map(esc).join(' · ') || t('system.baseagent.unspecified');
5305
5501
  return `<div class="cache-card"><div class="card-label">${esc(title)}</div><div class="card-val">${detail}</div></div>`;
5306
5502
  }).join('');
5307
5503
  }
@@ -5436,7 +5632,7 @@ function bindSystemEvents(el, data) {
5436
5632
  // 数据来自 daemon menu.query name=gateway(apiKey 已掩码)。
5437
5633
  // 写操作走 menuSend({name:'gateway', ...}):update/test/delete。
5438
5634
 
5439
- const GATEWAY_TYPE_ICON = { claude: '🟣', codex: '🟢', gemini: '🔵' };
5635
+ const GATEWAY_TYPE_ICON = { claude: '🟣', codex: '🟢', gemini: '🔵', ecagent: '⚪' };
5440
5636
 
5441
5637
  // 标记每条网关的运行时测试结果:`${scope}#${type}` → { ok, latency, modelCount, error }
5442
5638
  const _gwTest = new Map();
@@ -6072,7 +6268,7 @@ function triggerComparableScheduleValue(type, value) {
6072
6268
  function triggerActiveBaseagentForAgent(agentAid) {
6073
6269
  const list = state.agents?.agents || [];
6074
6270
  const ag = list.find(x => x.aid === agentAid || x.value === agentAid);
6075
- return ag?.baseagent || ag?.activeBaseagent || ag?.active_baseagent || 'claude';
6271
+ return ag?.baseagent || ag?.activeBaseagent || ag?.active_baseagent || configuredActiveBaseagent() || '';
6076
6272
  }
6077
6273
 
6078
6274
  function triggerScheduleLabel(type) {
@@ -6911,12 +7107,18 @@ function closeTriggerEditDrawer() {
6911
7107
  _triggerEdit = null;
6912
7108
  }
6913
7109
 
7110
+ function triggerUsesModelOptions(ctx) {
7111
+ const executionType = ctx.backdrop.querySelector('#trig-edit-type')?.value || ctx.original.executionType;
7112
+ return executionType !== 'script';
7113
+ }
7114
+
6914
7115
  async function syncTriggerOptionFields(ctx, values) {
7116
+ if (!triggerUsesModelOptions(ctx)) return;
6915
7117
  const modelSelect = ctx.backdrop.querySelector('#trig-edit-model');
6916
7118
  const effortSelect = ctx.backdrop.querySelector('#trig-edit-effort');
6917
7119
  const currentModel = values.model || '';
6918
7120
  const currentEffort = values.effort || '';
6919
- const baseagent = values.baseagent || ctx.original.baseagent || 'claude';
7121
+ const baseagent = values.baseagent || ctx.original.baseagent || configuredActiveBaseagent() || '';
6920
7122
  const token = (ctx.optionLoadToken || 0) + 1;
6921
7123
  ctx.optionLoadToken = token;
6922
7124
 
@@ -6925,37 +7127,39 @@ async function syncTriggerOptionFields(ctx, values) {
6925
7127
 
6926
7128
  try {
6927
7129
  const models = await agentFetchOptions('model', ctx.original.agentAid, { baseagent, model: currentModel });
6928
- if (_triggerEdit !== ctx || ctx.optionLoadToken !== token) return;
7130
+ if (_triggerEdit !== ctx || ctx.optionLoadToken !== token || !triggerUsesModelOptions(ctx)) return;
6929
7131
  modelSelect.innerHTML = agentSelectOptions(models, currentModel, t('common.inherited'));
6930
7132
  } catch (e) {
6931
- if (_triggerEdit === ctx && ctx.optionLoadToken === token) {
7133
+ if (_triggerEdit === ctx && ctx.optionLoadToken === token && triggerUsesModelOptions(ctx)) {
6932
7134
  modelSelect.innerHTML = agentSelectOptions([], currentModel, t('common.inherited'));
6933
7135
  toast(e.message || String(e), true);
6934
7136
  }
6935
7137
  } finally {
6936
- if (_triggerEdit === ctx && ctx.optionLoadToken === token) modelSelect.disabled = Boolean(ctx.saving);
7138
+ if (_triggerEdit === ctx && ctx.optionLoadToken === token && triggerUsesModelOptions(ctx)) modelSelect.disabled = Boolean(ctx.saving);
6937
7139
  }
6938
7140
 
7141
+ if (!triggerUsesModelOptions(ctx)) return;
6939
7142
  const selectedModel = modelSelect?.value || currentModel;
6940
7143
  await syncTriggerEffortOptions(ctx, baseagent, selectedModel, currentEffort, token);
6941
7144
  }
6942
7145
 
6943
7146
  async function syncTriggerEffortOptions(ctx, baseagent, model, currentEffort, token) {
7147
+ if (!triggerUsesModelOptions(ctx)) return;
6944
7148
  const effortSelect = ctx.backdrop.querySelector('#trig-edit-effort');
6945
7149
  if (!effortSelect) return;
6946
7150
  setAgentSelectLoading(effortSelect, currentEffort, currentEffort ? `${currentEffort} (${t('common.loadingSuffix')})` : t('common.loading'));
6947
7151
  try {
6948
7152
  let efforts = await agentFetchOptions('effort', ctx.original.agentAid, { baseagent, model, effort: currentEffort || 'auto' });
6949
- if (_triggerEdit !== ctx || ctx.optionLoadToken !== token) return;
7153
+ if (_triggerEdit !== ctx || ctx.optionLoadToken !== token || !triggerUsesModelOptions(ctx)) return;
6950
7154
  effortSelect.innerHTML = agentSelectOptions(efforts, currentEffort, t('common.inherited'));
6951
7155
  } catch (e) {
6952
- if (_triggerEdit === ctx && ctx.optionLoadToken === token) {
7156
+ if (_triggerEdit === ctx && ctx.optionLoadToken === token && triggerUsesModelOptions(ctx)) {
6953
7157
  const fallback = [];
6954
7158
  effortSelect.innerHTML = agentSelectOptions(fallback, currentEffort, t('common.inherited'));
6955
7159
  toast(e.message || String(e), true);
6956
7160
  }
6957
7161
  } finally {
6958
- if (_triggerEdit === ctx && ctx.optionLoadToken === token) effortSelect.disabled = Boolean(ctx.saving);
7162
+ if (_triggerEdit === ctx && ctx.optionLoadToken === token && triggerUsesModelOptions(ctx)) effortSelect.disabled = Boolean(ctx.saving);
6959
7163
  }
6960
7164
  }
6961
7165
 
@@ -7498,12 +7702,23 @@ function openTriggerEditDrawer(id, agent) {
7498
7702
  triggerRefreshEventDatalists(ctx);
7499
7703
  triggerUpdateFilterJsonPreview(ctx);
7500
7704
  });
7501
- backdrop.querySelector('#trig-edit-type').addEventListener('change', () => syncTriggerEditFields(ctx));
7705
+ backdrop.querySelector('#trig-edit-type').addEventListener('change', () => {
7706
+ syncTriggerEditFields(ctx);
7707
+ if (!triggerUsesModelOptions(ctx)) {
7708
+ ctx.optionLoadToken = (ctx.optionLoadToken || 0) + 1;
7709
+ return;
7710
+ }
7711
+ syncTriggerOptionFields(ctx, {
7712
+ ...ctx.original,
7713
+ model: backdrop.querySelector('#trig-edit-model')?.value || '',
7714
+ effort: backdrop.querySelector('#trig-edit-effort')?.value || '',
7715
+ });
7716
+ });
7502
7717
  backdrop.querySelector('#trig-edit-delivery-enabled').addEventListener('change', () => syncTriggerEditFields(ctx));
7503
7718
  backdrop.querySelector('#trig-edit-model').addEventListener('change', () => {
7504
7719
  const token = (ctx.optionLoadToken || 0) + 1;
7505
7720
  ctx.optionLoadToken = token;
7506
- syncTriggerEffortOptions(ctx, ctx.original.baseagent || 'claude', backdrop.querySelector('#trig-edit-model')?.value || '', backdrop.querySelector('#trig-edit-effort')?.value || '', token);
7721
+ syncTriggerEffortOptions(ctx, ctx.original.baseagent || configuredActiveBaseagent() || '', backdrop.querySelector('#trig-edit-model')?.value || '', backdrop.querySelector('#trig-edit-effort')?.value || '', token);
7507
7722
  });
7508
7723
  backdrop.querySelector('#trig-edit-session-strategy').addEventListener('change', () => {
7509
7724
  const strategy = backdrop.querySelector('#trig-edit-session-strategy')?.value;
@@ -7542,7 +7757,7 @@ function openTriggerEditDrawer(id, agent) {
7542
7757
  triggerEnsureEventCatalog(ctx).catch(e => {
7543
7758
  if (_triggerEdit === ctx) toast(e.message || String(e), true);
7544
7759
  });
7545
- syncTriggerOptionFields(ctx, original);
7760
+ if (triggerUsesModelOptions(ctx)) syncTriggerOptionFields(ctx, original);
7546
7761
  setTimeout(() => backdrop.querySelector('#trig-edit-name')?.focus(), 0);
7547
7762
  }
7548
7763
 
@@ -10374,7 +10589,7 @@ function createRoleCard(roleName, roleDef) {
10374
10589
  const icon = ROLE_ICONS[roleName] || '📋';
10375
10590
  const nameKey = ROLE_NAMES[roleName] || roleName;
10376
10591
  const permMode = roleDef.permissions?.permissionMode?.default || '—';
10377
- const model = roleDef.permissions?.['baseagents.claude.model']?.default || '—';
10592
+ const model = getRoleModelPermission(roleDef)?.default || '—';
10378
10593
  const commandStats = getCommandPermissionStats(roleDef.commandPermissions);
10379
10594
 
10380
10595
  // Management identities and built-in user roles are both protected from
@@ -10543,15 +10758,29 @@ function initRoleDefinitionsTab() {
10543
10758
  });
10544
10759
  }
10545
10760
 
10546
- const CLAUDE_MODEL_PERMISSION_KEY = 'baseagents.claude.model';
10547
- const CLAUDE_ALLOWED_MODELS_LEGACY_KEY = 'baseagents.claude.allowedModels';
10548
- const DEFAULT_MODEL_PATTERN_OPTIONS = ['*', 'claude-opus-*', 'claude-sonnet-*', 'claude-haiku-*'];
10761
+ function roleModelPermissionKey() {
10762
+ return state.roleDefinitions?.modelPermissionField || null;
10763
+ }
10549
10764
 
10550
- function getClaudeModelPermission(roleDef) {
10551
- const perm = roleDef?.permissions?.[CLAUDE_MODEL_PERMISSION_KEY];
10765
+ function getRoleModelPermission(roleDef, field = roleModelPermissionKey()) {
10766
+ const perm = roleDef?.permissions?.[field];
10552
10767
  return perm && typeof perm === 'object' ? perm : null;
10553
10768
  }
10554
10769
 
10770
+ function rolePermissionMeta(field) {
10771
+ const descriptor = state.roleDefinitions?.permissionFields?.find(item => item.value === field);
10772
+ if (!descriptor) return null;
10773
+ return {
10774
+ label: descriptor.label,
10775
+ type: descriptor.valueType === 'enum' ? 'select' : 'text',
10776
+ options: descriptor.options || [],
10777
+ };
10778
+ }
10779
+
10780
+ function isRoleModelPermissionField(field) {
10781
+ return /^baseagents\.[a-z0-9_-]+\.(?:model|allowedModels)$/.test(field);
10782
+ }
10783
+
10555
10784
  function normalizeAllowedModels(value) {
10556
10785
  if (!Array.isArray(value)) return [];
10557
10786
  const seen = new Set();
@@ -10585,17 +10814,21 @@ function modelAllowedByPatterns(model, allowedModels) {
10585
10814
  }
10586
10815
 
10587
10816
  function renderRoleModelPermissionSection(roleDef) {
10588
- const perm = getClaudeModelPermission(roleDef);
10589
- if (!perm) return '';
10817
+ const field = roleModelPermissionKey();
10818
+ if (!field) return '';
10819
+ const existing = getRoleModelPermission(roleDef, field);
10820
+ const perm = existing || {};
10590
10821
 
10591
10822
  const allowedModels = normalizeAllowedModels(perm.allowedModels);
10592
10823
  const selectionMode = inferModelSelectionMode(allowedModels);
10593
- const patternOptions = DEFAULT_MODEL_PATTERN_OPTIONS;
10824
+ const patternOptions = state.roleDefinitions?.modelPatternOptions || ['*'];
10594
10825
 
10595
10826
  return `
10596
- <div class="form-group perm-group model-permissions-panel" data-model-permissions>
10827
+ <div class="form-group perm-group model-permissions-panel" data-model-permissions data-model-key="${esc(field)}" data-model-baseagent="${esc(state.roleDefinitions.baseagent)}">
10828
+ ${existing ? '' : `<label><input type="checkbox" data-model-enable> ${currentLang === 'zh-CN' ? '设置模型策略' : 'Set model policy'}</label>`}
10829
+ <fieldset data-model-editor${existing ? '' : ' disabled hidden'} style="border:0;padding:0;margin:0;min-width:0">
10597
10830
  <div class="perm-header">
10598
- <label class="perm-label">${esc(t('roleDefs.model'))}</label>
10831
+ <label class="perm-label">${esc(state.roleDefinitions.baseagent)} · ${esc(t('roleDefs.model'))}</label>
10599
10832
  <label class="perm-override">
10600
10833
  <input type="checkbox"
10601
10834
  data-model-field="allowOverride"
@@ -10657,13 +10890,14 @@ function renderRoleModelPermissionSection(roleDef) {
10657
10890
  </div>
10658
10891
 
10659
10892
  <div class="model-preview" data-model-preview>Loading preview...</div>
10893
+ </fieldset>
10660
10894
  </div>
10661
10895
  `;
10662
10896
  }
10663
10897
 
10664
- async function fetchRoleModelPermissionData(roleName) {
10898
+ async function fetchRoleModelPermissionData(roleName, baseagent) {
10665
10899
  const token = localStorage.getItem(TOKEN_KEY);
10666
- const res = await authFetch(roleDefinitionsApi(`api/role-definitions/${encodeURIComponent(roleName)}/configurable-models`), {
10900
+ const res = await authFetch(roleDefinitionsApi(`api/role-definitions/${encodeURIComponent(roleName)}/configurable-models?baseagent=${encodeURIComponent(baseagent)}`), {
10667
10901
  headers: { 'Authorization': `Bearer ${token}` }
10668
10902
  });
10669
10903
  if (!res.ok) throw new Error('Failed to load role model permissions');
@@ -10671,7 +10905,9 @@ async function fetchRoleModelPermissionData(roleName) {
10671
10905
  return json.data;
10672
10906
  }
10673
10907
 
10674
- async function fetchModelCatalogData(baseagent = 'claude', aid = '') {
10908
+ async function fetchModelCatalogData(baseagent = null, aid = '') {
10909
+ baseagent = baseagent || configuredActiveBaseagent();
10910
+ if (!baseagent) throw new Error(t('sessions.baseagentNone'));
10675
10911
  const token = localStorage.getItem(TOKEN_KEY);
10676
10912
  const res = await authFetch(`api/models/catalog?baseagent=${encodeURIComponent(baseagent)}${aid ? `&aid=${encodeURIComponent(aid)}` : ''}`, {
10677
10913
  headers: { 'Authorization': `Bearer ${token}` }
@@ -10766,11 +11002,14 @@ function collectRoleModelPermission(container) {
10766
11002
  ? container
10767
11003
  : container.querySelector('[data-model-permissions]');
10768
11004
  if (!section) return null;
11005
+ const enable = section.querySelector('[data-model-enable]');
11006
+ if (enable && !enable.checked) return null;
10769
11007
 
10770
11008
  const defaultInput = section.querySelector('[data-model-field="default"]');
10771
11009
  const overrideInput = section.querySelector('[data-model-field="allowOverride"]');
10772
11010
 
10773
11011
  return {
11012
+ field: section.dataset.modelKey,
10774
11013
  default: defaultInput?.value.trim() || '',
10775
11014
  allowOverride: !!overrideInput?.checked,
10776
11015
  allowedModels: getRoleAllowedModelsFromList(section)
@@ -10834,14 +11073,14 @@ async function initRoleModelPermissionEditor(container, roleName, roleDef) {
10834
11073
  const section = container.querySelector('[data-model-permissions]');
10835
11074
  if (!section) return;
10836
11075
 
10837
- const initialAllowed = normalizeAllowedModels(getClaudeModelPermission(roleDef)?.allowedModels);
11076
+ const initialAllowed = normalizeAllowedModels(getRoleModelPermission(roleDef, section.dataset.modelKey)?.allowedModels);
10838
11077
  let catalogModels = [];
10839
11078
  section._initialAllowedModels = initialAllowed;
10840
11079
 
10841
11080
  try {
10842
11081
  const data = roleName && roleName !== '__new__'
10843
- ? await fetchRoleModelPermissionData(roleName)
10844
- : await fetchModelCatalogData('claude', roleDefinitionsCurrentAgent || state.roleDefinitions?.aid || '');
11082
+ ? await fetchRoleModelPermissionData(roleName, section.dataset.modelBaseagent)
11083
+ : await fetchModelCatalogData(section.dataset.modelBaseagent, roleDefinitionsCurrentAgent || state.roleDefinitions?.aid || '');
10845
11084
  catalogModels = data?.catalog?.models || data?.models || [];
10846
11085
  } catch (err) {
10847
11086
  console.warn('[roleDefinitions] Failed to load model catalog:', err);
@@ -10880,6 +11119,11 @@ async function initRoleModelPermissionEditor(container, roleName, roleDef) {
10880
11119
 
10881
11120
  section.addEventListener('change', (e) => {
10882
11121
  const target = e.target;
11122
+ if (target?.matches?.('[data-model-enable]')) {
11123
+ const editor = section.querySelector('[data-model-editor]');
11124
+ editor.disabled = !target.checked;
11125
+ editor.hidden = !target.checked;
11126
+ }
10883
11127
  if (target?.classList?.contains('model-pattern-checkbox') || target?.classList?.contains('model-explicit-checkbox')) {
10884
11128
  const allowed = new Set(getRoleAllowedModelsFromList(section));
10885
11129
  if (target.checked) allowed.add(target.value);
@@ -10914,8 +11158,6 @@ async function showNewRoleModal() {
10914
11158
  description: '',
10915
11159
  permissions: {
10916
11160
  permissionMode: { default: 'request', allowOverride: false },
10917
- 'baseagents.claude.model': { default: 'claude-sonnet-4', allowOverride: false, allowedModels: ['claude-sonnet-*', 'claude-haiku-*'] },
10918
- dispatch: { default: 'mention', allowOverride: false }
10919
11161
  },
10920
11162
  commandPermissions: {
10921
11163
  'category:read': { allow: true },
@@ -11051,9 +11293,9 @@ async function showNewRoleModal() {
11051
11293
  body.innerHTML += renderRoleModelPermissionSection(defaultDef);
11052
11294
 
11053
11295
  Object.keys(permissions).forEach(permKey => {
11054
- if (permKey === CLAUDE_MODEL_PERMISSION_KEY || permKey === CLAUDE_ALLOWED_MODELS_LEGACY_KEY) return;
11296
+ if (isRoleModelPermissionField(permKey)) return;
11055
11297
  const perm = permissions[permKey];
11056
- const meta = permissionMeta[permKey] || { label: permKey, type: 'text' };
11298
+ const meta = rolePermissionMeta(permKey) || permissionMeta[permKey] || { label: permKey, type: 'text' };
11057
11299
 
11058
11300
  body.innerHTML += `
11059
11301
  <div class="form-group perm-group">
@@ -11279,9 +11521,9 @@ async function showRoleEditModal(roleName, roleDef) {
11279
11521
  formHtml += renderRoleModelPermissionSection(roleDef);
11280
11522
 
11281
11523
  Object.keys(permissions).forEach(permKey => {
11282
- if (permKey === CLAUDE_MODEL_PERMISSION_KEY || permKey === CLAUDE_ALLOWED_MODELS_LEGACY_KEY) return;
11524
+ if (isRoleModelPermissionField(permKey)) return;
11283
11525
  const perm = permissions[permKey];
11284
- const meta = permissionMeta[permKey] || { label: permKey, type: 'text' };
11526
+ const meta = rolePermissionMeta(permKey) || permissionMeta[permKey] || { label: permKey, type: 'text' };
11285
11527
 
11286
11528
  formHtml += `
11287
11529
  <div class="form-group perm-group">
@@ -11525,8 +11767,6 @@ async function saveRoleDefinition() {
11525
11767
  description: '',
11526
11768
  permissions: {
11527
11769
  permissionMode: { default: 'request', allowOverride: false },
11528
- [CLAUDE_MODEL_PERMISSION_KEY]: { default: 'claude-sonnet-4', allowOverride: false, allowedModels: ['claude-sonnet-*', 'claude-haiku-*'] },
11529
- dispatch: { default: 'mention', allowOverride: false }
11530
11770
  },
11531
11771
  commandPermissions: {
11532
11772
  'category:read': { allow: true },
@@ -11546,7 +11786,7 @@ async function saveRoleDefinition() {
11546
11786
  const allowAccess = allowAccessSelect ? (allowAccessSelect.value === 'true') : true;
11547
11787
 
11548
11788
  // 收集所有权限配置
11549
- const permissions = {};
11789
+ const permissions = { ...(currentDef.permissions || {}) };
11550
11790
 
11551
11791
  // 获取所有权限项的键
11552
11792
  const permKeys = new Set();
@@ -11604,13 +11844,13 @@ async function saveRoleDefinition() {
11604
11844
  alert(modelValidationError);
11605
11845
  return;
11606
11846
  }
11607
- permissions[CLAUDE_MODEL_PERMISSION_KEY] = {
11608
- ...(currentDef.permissions?.[CLAUDE_MODEL_PERMISSION_KEY] || {}),
11847
+ permissions[modelPermission.field] = {
11848
+ ...(currentDef.permissions?.[modelPermission.field] || {}),
11609
11849
  default: modelPermission.default,
11610
11850
  allowOverride: modelPermission.allowOverride,
11611
11851
  allowedModels: modelPermission.allowedModels
11612
11852
  };
11613
- delete permissions[CLAUDE_MODEL_PERMISSION_KEY].selectionMode;
11853
+ delete permissions[modelPermission.field].selectionMode;
11614
11854
  }
11615
11855
 
11616
11856
  const commandPermissionsResult = collectCommandPermissions(body, currentDef.commandPermissions);