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