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.
- package/dist/index.js +1 -1
- package/dist/paths.js +4 -0
- package/dist/server.js +35 -20
- package/dist/sources/msg.js +21 -4
- package/dist/sources/role-definitions.js +53 -29
- package/dist/sources/session-codex.js +23 -5
- package/dist/sources/session-ecagent.js +665 -0
- package/dist/sources/session-origin.js +16 -0
- package/dist/sources/session.js +69 -165
- package/dist/sources/system.js +1 -33
- package/dist/sources/triggers.js +1 -1
- package/dist/static/app.js +336 -96
- package/dist/static/basic-settings.js +3 -3
- package/dist/static/index.html +1 -1
- package/dist/static/style.css +37 -0
- package/package.json +1 -1
package/dist/static/app.js
CHANGED
|
@@ -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
|
-
//
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
.then(
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 || '
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 (!
|
|
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 || '
|
|
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
|
|
5303
|
-
const
|
|
5304
|
-
const
|
|
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 || '
|
|
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 || '
|
|
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', () =>
|
|
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 || '
|
|
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
|
|
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
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10761
|
+
function roleModelPermissionKey() {
|
|
10762
|
+
return state.roleDefinitions?.modelPermissionField || null;
|
|
10763
|
+
}
|
|
10549
10764
|
|
|
10550
|
-
function
|
|
10551
|
-
const perm = roleDef?.permissions?.[
|
|
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
|
|
10589
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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[
|
|
11608
|
-
...(currentDef.permissions?.[
|
|
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[
|
|
11853
|
+
delete permissions[modelPermission.field].selectionMode;
|
|
11614
11854
|
}
|
|
11615
11855
|
|
|
11616
11856
|
const commandPermissionsResult = collectCommandPermissions(body, currentDef.commandPermissions);
|