@kenhkl/agent-moss 0.12.2 → 0.12.3
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/static/index.html +80 -25
- package/package.json +1 -1
package/dist/static/index.html
CHANGED
|
@@ -323,6 +323,11 @@ async function api(path, opts = {}) {
|
|
|
323
323
|
function apiPut(path, body) { return api(path, { method: 'PUT', body: JSON.stringify(body) }); }
|
|
324
324
|
function apiPost(path, body) { return api(path, { method: 'POST', body: JSON.stringify(body) }); }
|
|
325
325
|
function apiDelete(path, body) { return api(path, { method: 'DELETE', body: JSON.stringify(body) }); }
|
|
326
|
+
/** 拉取并解包 env 覆盖映射(/api/env-overrides 返回 {overrides: {...}},此处解出净映射)。 */
|
|
327
|
+
async function loadEnvOverrides() {
|
|
328
|
+
const resp = await api('env-overrides');
|
|
329
|
+
return resp?.overrides || {};
|
|
330
|
+
}
|
|
326
331
|
|
|
327
332
|
let state = { config: {}, envOverrides: {}, currentPage: 'overview' };
|
|
328
333
|
const expandedCats = new Set();
|
|
@@ -435,8 +440,27 @@ function closeModal() {
|
|
|
435
440
|
// ===== Pages =====
|
|
436
441
|
async function refreshConfig() {
|
|
437
442
|
state.config = await api('config');
|
|
438
|
-
state.envOverrides = await
|
|
443
|
+
state.envOverrides = await loadEnvOverrides();
|
|
439
444
|
state.agentRules = await api('agents');
|
|
445
|
+
// 探测当前进程的 instance(AGENT_MOSS_INSTANCE,如 opendesk/xiaoo)。
|
|
446
|
+
// 有 instance 时 Agent Rules 页只显示当前 agent 的定制规则,避免出现别的 agent 的规则造成困惑;
|
|
447
|
+
// 无 instance(本地独立起服务/非 agent 调用)则显示全部。
|
|
448
|
+
state.instance = '';
|
|
449
|
+
try {
|
|
450
|
+
const h = await fetch('/api/v1/health', { signal: AbortSignal.timeout(2000) });
|
|
451
|
+
if (h.ok) {
|
|
452
|
+
const hd = await h.json();
|
|
453
|
+
state.instance = hd?.instance || '';
|
|
454
|
+
}
|
|
455
|
+
} catch { /* health 不可达,instance 保持空(显示全部) */ }
|
|
456
|
+
// 探测服务端是否开启了 console 访问 token 认证(AGENT_MOSS_CONSOLE_TOKEN)。
|
|
457
|
+
// 不带 token 裸请求一个 API 端点:返回 401 说明开启了认证 → 设置页显示"访问 Token"卡片;
|
|
458
|
+
// 返回 200 说明本机免认证 → 隐藏该卡片(单机使用无需配置,避免误导)。
|
|
459
|
+
state.consoleAuthRequired = false;
|
|
460
|
+
try {
|
|
461
|
+
const probe = await fetch('/console/api/env-overrides', { signal: AbortSignal.timeout(2000) });
|
|
462
|
+
state.consoleAuthRequired = probe.status === 401;
|
|
463
|
+
} catch { /* 探测失败,默认不显示 token 卡片 */ }
|
|
440
464
|
renderCurrent();
|
|
441
465
|
}
|
|
442
466
|
function renderCurrent() {
|
|
@@ -471,9 +495,9 @@ function render_overview() {
|
|
|
471
495
|
<div class="ag-card">
|
|
472
496
|
<div class="ag-section-title">${t('规则统计', 'Rule Statistics')}</div>
|
|
473
497
|
<div class="ag-stat-grid">
|
|
474
|
-
<div class="ag-stat-card"><div class="label">${t('L1 规则启用', 'L1 Rules Enabled')}</div><div class="value">${countEnabled(state.config.L1_rules)}/${countRules(state.config.L1_rules)}</div></div>
|
|
475
|
-
<div class="ag-stat-card"><div class="label">${t('L2 规则启用', 'L2 Rules Enabled')}</div><div class="value">${countEnabled(state.config.L2_rules)}/${countRules(state.config.L2_rules)}</div></div>
|
|
476
|
-
<div class="ag-stat-card"><div class="label">${t('L3 Skills 启用', 'L3 Skills Enabled')}</div><div class="value">${countEnabledSkills(state.config.L3_skills)}/${countSkills(state.config.L3_skills)}</div></div>
|
|
498
|
+
<div class="ag-stat-card"><div class="label">${t('L1 规则启用', 'L1 Rules Enabled')}</div><div class="value">${countEnabled(state.config.L1_rules, 'L1_heuristic')}/${countRules(state.config.L1_rules)}</div></div>
|
|
499
|
+
<div class="ag-stat-card"><div class="label">${t('L2 规则启用', 'L2 Rules Enabled')}</div><div class="value">${countEnabled(state.config.L2_rules, 'L2_logic_rules')}/${countRules(state.config.L2_rules)}</div></div>
|
|
500
|
+
<div class="ag-stat-card"><div class="label">${t('L3 Skills 启用', 'L3 Skills Enabled')}</div><div class="value">${countEnabledSkills(state.config.L3_skills, 'L3_llm_analysis')}/${countSkills(state.config.L3_skills)}</div></div>
|
|
477
501
|
</div>
|
|
478
502
|
</div>
|
|
479
503
|
<div class="ag-card">
|
|
@@ -518,13 +542,18 @@ async function loadOverviewToken(days) {
|
|
|
518
542
|
} catch { el('overview-token-summary').innerHTML = `<div class="ag-empty">${t('无数据', 'No data')}</div>`; }
|
|
519
543
|
}
|
|
520
544
|
function countRules(lr) { if (!lr) return 0; return Object.values(lr).reduce((s, c) => s + (c.rules ? c.rules.length : 0), 0); }
|
|
521
|
-
|
|
545
|
+
// 统计"启用"的规则数,口径与后端执行一致:层级开 && 大类开 && 单条规则开。
|
|
546
|
+
// layerKey 为层级总开关(如 L1_heuristic),关闭时返回 0。
|
|
547
|
+
function countEnabled(lr, layerKey) {
|
|
522
548
|
if (!lr) return 0;
|
|
549
|
+
if (layerKey && state.config.layers?.[layerKey] === false) return 0;
|
|
523
550
|
return Object.values(lr).reduce((s, c) => s + ((c.rules || []).filter(r => r.enabled && c.category_enabled !== false).length), 0);
|
|
524
551
|
}
|
|
525
552
|
function countSkills(ls) { if (!ls) return 0; return Object.values(ls).reduce((s, c) => s + (c.skills ? c.skills.length : 0), 0); }
|
|
526
|
-
|
|
553
|
+
// 同 countEnabled:层级关闭时 L3 skills 启用数归 0。
|
|
554
|
+
function countEnabledSkills(ls, layerKey) {
|
|
527
555
|
if (!ls) return 0;
|
|
556
|
+
if (layerKey && state.config.layers?.[layerKey] === false) return 0;
|
|
528
557
|
return Object.values(ls).reduce((s, c) => s + ((c.skills || []).filter(s => s.enabled && c.category_enabled !== false).length), 0);
|
|
529
558
|
}
|
|
530
559
|
|
|
@@ -532,10 +561,12 @@ function countEnabledSkills(ls) {
|
|
|
532
561
|
async function toggleLayer(key) {
|
|
533
562
|
try {
|
|
534
563
|
const cur = state.config.layers[key] !== false;
|
|
535
|
-
|
|
536
|
-
state.
|
|
537
|
-
|
|
538
|
-
|
|
564
|
+
// PUT /api/layers 只返回 {layers, env_overrides}(不含规则数据),
|
|
565
|
+
// 不能用它整体覆盖 state.config,否则 L1/L2/L3 规则会丢失变成 0/0。
|
|
566
|
+
await apiPut('layers', { layer_key: key, enabled: !cur });
|
|
567
|
+
// 重新拉取完整 config,保证规则统计/列表不丢
|
|
568
|
+
await refreshConfig();
|
|
569
|
+
showToast(`${LAYER_NAMES[key]?.name || key} ${!cur ? t('已启用', 'enabled') : t('已禁用', 'disabled')}`);
|
|
539
570
|
} catch (e) { showToast(t('操作失败: ', 'Operation failed: ') + e.message, 'error'); }
|
|
540
571
|
}
|
|
541
572
|
|
|
@@ -569,8 +600,13 @@ function toggleExpand(id) {
|
|
|
569
600
|
}
|
|
570
601
|
|
|
571
602
|
// ===== L1/L2 规则页 =====
|
|
603
|
+
// 规则层容器 -> 层级总开关 key 的映射(用于大类开关随层级开关联动)
|
|
604
|
+
const LAYER_SWITCH_KEY = { L1_rules: 'L1_heuristic', L2_rules: 'L2_logic_rules', L3_skills: 'L3_llm_analysis' };
|
|
572
605
|
function renderRuleLayer(layerKey, containerId, categoryNames) {
|
|
573
606
|
const layer = state.config[layerKey] || {};
|
|
607
|
+
// 层级总开关:关闭时整层大类开关随动显示为关(仅 UI 联动,不改后端数据)
|
|
608
|
+
const layerSwitchKey = LAYER_SWITCH_KEY[layerKey];
|
|
609
|
+
const layerEnabled = layerSwitchKey ? state.config.layers?.[layerSwitchKey] !== false : true;
|
|
574
610
|
const orderedEntries = [
|
|
575
611
|
...Object.keys(categoryNames).filter(k => k in layer).map(k => [k, layer[k]]),
|
|
576
612
|
...Object.entries(layer).filter(([k]) => !(k in categoryNames)),
|
|
@@ -580,13 +616,17 @@ function renderRuleLayer(layerKey, containerId, categoryNames) {
|
|
|
580
616
|
const displayName = categoryNames[catName] || catName;
|
|
581
617
|
const rules = catData.rules || [];
|
|
582
618
|
const catEnabled = catData.category_enabled !== false;
|
|
583
|
-
|
|
619
|
+
// 大类总开关显示状态 = 层级开 && 大类开;层级关时大类开关需禁用(无意义)
|
|
620
|
+
const catSwitchOn = layerEnabled && catEnabled;
|
|
621
|
+
const catSwitchDisabled = !layerEnabled;
|
|
622
|
+
// 大类计数与后端执行口径一致:大类总开关关闭时启用数归 0(分母仍为规则总数)
|
|
623
|
+
const enabledCount = catSwitchOn ? rules.filter(r => r.enabled).length : 0;
|
|
584
624
|
const expanded = expandedCats.has(`${containerId}-${catName}`);
|
|
585
625
|
html += `<div class="ag-card">
|
|
586
626
|
<div class="ag-cat-header" onclick="toggleExpand('${containerId}-${catName}')">
|
|
587
627
|
<div class="ag-cat-name">${escapeHtml(displayName)} <span class="ag-cat-count">${enabledCount}/${rules.length} ${t('启用', 'enabled')}</span></div>
|
|
588
628
|
<div class="ag-cat-controls">
|
|
589
|
-
<div onclick="event.stopPropagation();toggleCategory('${layerKey}','${catName}')">${toggle(
|
|
629
|
+
<div onclick="event.stopPropagation();toggleCategory('${layerKey}','${catName}')">${toggle(catSwitchOn, catSwitchDisabled, '')}</div>
|
|
590
630
|
<span class="ag-expand-icon ${expanded ? 'open' : ''}" id="expand-${containerId}-${catName}">▼</span>
|
|
591
631
|
</div>
|
|
592
632
|
</div>
|
|
@@ -678,7 +718,14 @@ const AGENT_CATEGORY_NAMES = {
|
|
|
678
718
|
};
|
|
679
719
|
function render_agents() {
|
|
680
720
|
const agents = (state.agentRules && state.agentRules.agents) || {};
|
|
681
|
-
|
|
721
|
+
// 有 instance(当前是某 agent 调用)时,只显示该 agent 的定制规则(即使无规则也显示空,
|
|
722
|
+
// 避免出现别的 agent 的规则造成困惑);无 instance(本地独立起/非 agent 调用)显示全部。
|
|
723
|
+
let ids;
|
|
724
|
+
if (state.instance) {
|
|
725
|
+
ids = agents[state.instance] ? [state.instance] : [];
|
|
726
|
+
} else {
|
|
727
|
+
ids = Object.keys(agents);
|
|
728
|
+
}
|
|
682
729
|
if (!ids.length) {
|
|
683
730
|
el('page-agents').innerHTML = `<div class="ag-card"><div class="ag-empty">${t('无 agent 定制规则', 'No agent custom rules')}</div></div>`;
|
|
684
731
|
return;
|
|
@@ -695,7 +742,8 @@ function render_agents() {
|
|
|
695
742
|
const rules = catData.rules || [];
|
|
696
743
|
const catEnabled = catData.category_enabled !== false;
|
|
697
744
|
const expanded = expandedCats.has(`agents-${agentId}-${catName}`);
|
|
698
|
-
|
|
745
|
+
// 大类计数与后端执行口径一致:大类总开关关闭时启用数归 0
|
|
746
|
+
const enCount = catEnabled ? rules.filter(r => r.enabled).length : 0;
|
|
699
747
|
html += `<div class="ag-cat-header" onclick="toggleExpand('agents-${agentId}-${catName}')">
|
|
700
748
|
<div class="ag-cat-name">${escapeHtml(AGENT_CATEGORY_NAMES[catName] || catName)} <span class="ag-cat-count">${enCount}/${rules.length} ${t('启用', 'enabled')}</span></div>
|
|
701
749
|
<div class="ag-cat-controls"><div onclick="event.stopPropagation();toggleAgentCategory('${agentId}','${catName}')">${toggle(catEnabled, false, '')}</div><span class="ag-expand-icon ${expanded ? 'open' : ''}">▼</span></div>
|
|
@@ -770,18 +818,24 @@ function showAddAgentRuleModal(agentId, cat) {
|
|
|
770
818
|
// ===== L3 Skills 页 =====
|
|
771
819
|
function render_l3() {
|
|
772
820
|
const l3 = state.config.L3_skills || {};
|
|
821
|
+
// 层级总开关:关闭时 L3 大类开关随动显示为关(仅 UI 联动,不改后端数据)
|
|
822
|
+
const layerEnabled = state.config.layers?.['L3_llm_analysis'] !== false;
|
|
773
823
|
let html = `<div class="ag-card" style="padding:8px 16px"><button class="ag-btn ag-btn-primary" onclick="showAddSkillModal()">+ ${t('新增 Skill', 'Add Skill')}</button></div>`;
|
|
774
824
|
for (const [catName, catData] of Object.entries(l3)) {
|
|
775
825
|
const displayName = L3_CATEGORY_NAMES[catName] || catName;
|
|
776
826
|
const skills = catData.skills || [];
|
|
777
827
|
const catEnabled = catData.category_enabled !== false;
|
|
778
|
-
|
|
828
|
+
// 大类总开关显示状态 = 层级开 && 大类开;层级关时大类开关需禁用(无意义)
|
|
829
|
+
const catSwitchOn = layerEnabled && catEnabled;
|
|
830
|
+
const catSwitchDisabled = !layerEnabled;
|
|
831
|
+
// 大类计数与后端执行口径一致:大类总开关关闭时启用数归 0
|
|
832
|
+
const enabledCount = catSwitchOn ? skills.filter(s => s.enabled).length : 0;
|
|
779
833
|
const expanded = expandedCats.has('page-l3-' + catName);
|
|
780
834
|
html += `<div class="ag-card">
|
|
781
835
|
<div class="ag-cat-header" onclick="toggleExpand('page-l3-${catName}')">
|
|
782
836
|
<div class="ag-cat-name">${escapeHtml(displayName)} <span class="ag-cat-count">${enabledCount}/${skills.length} ${t('启用', 'enabled')}</span></div>
|
|
783
837
|
<div class="ag-cat-controls">
|
|
784
|
-
<div onclick="event.stopPropagation();toggleSkillCategory('${catName}')">${toggle(
|
|
838
|
+
<div onclick="event.stopPropagation();toggleSkillCategory('${catName}')">${toggle(catSwitchOn, catSwitchDisabled, '')}</div>
|
|
785
839
|
<span class="ag-expand-icon ${expanded ? 'open' : ''}" id="expand-page-l3-${catName}">▼</span>
|
|
786
840
|
</div>
|
|
787
841
|
</div>
|
|
@@ -1113,28 +1167,29 @@ function render_settings() {
|
|
|
1113
1167
|
}
|
|
1114
1168
|
}
|
|
1115
1169
|
el('page-settings').innerHTML = `
|
|
1170
|
+
${Object.keys(eo).length > 0 ? `
|
|
1116
1171
|
<div class="ag-card">
|
|
1117
1172
|
<div class="ag-section-title">${t('环境变量覆盖', 'Environment Variable Overrides')}</div>
|
|
1118
1173
|
<div class="ag-section-desc">${t('环境变量优先级最高,会覆盖 Web UI 的设置。清除环境变量后 Web 设置才能生效。', 'Environment variables take highest priority and override Web UI settings. Web settings take effect only after clearing the environment variables.')}</div>
|
|
1119
1174
|
${envHtml}
|
|
1120
|
-
</div
|
|
1175
|
+
</div>` : ''}
|
|
1121
1176
|
<div class="ag-card">
|
|
1122
|
-
<div class="ag-section-title">${t('
|
|
1177
|
+
<div class="ag-section-title">${t('配置文件', 'Config File')}</div>
|
|
1178
|
+
<div class="ag-section-desc">${t('你在本控制台做的所有修改(层级开关、各类规则、Agent 定制)都会自动保存到下面的配置文件中。它是策略的“存档”,可备份、迁移,或复制到其他机器复用同一套安全策略。', 'All your changes here (layer switches, rules, agent customizations) are saved automatically to the config file below. It is the "source of truth" for your policy — you can back it up, migrate it, or copy it to another machine to reuse the same security policy.')}</div>
|
|
1123
1179
|
<div style="font-size:13px;line-height:1.8">
|
|
1124
|
-
<
|
|
1125
|
-
<p>${t('环境变量 AGENT_MOSS_RUNTIME_CONFIG_PATH 可指定自定义路径', 'Environment variable AGENT_MOSS_RUNTIME_CONFIG_PATH can set a custom path')}</p>
|
|
1126
|
-
<p>${t('优先级: 环境变量 > runtime JSON > config.json > 源码默认值', 'Priority: env var > runtime JSON > config.json > source defaults')}</p>
|
|
1180
|
+
<code>~/.config/agentmoss/agent_moss_runtime.json</code>
|
|
1127
1181
|
</div>
|
|
1128
1182
|
</div>
|
|
1183
|
+
${state.consoleAuthRequired ? `
|
|
1129
1184
|
<div class="ag-card">
|
|
1130
1185
|
<div class="ag-section-title">${t('访问 Token', 'Access Token')}</div>
|
|
1131
|
-
<div class="ag-section-desc">${t('
|
|
1132
|
-
<input class="ag-input" id="settings-token" placeholder="${t('
|
|
1186
|
+
<div class="ag-section-desc">${t('服务端已开启访问认证(设置了 AGENT_MOSS_CONSOLE_TOKEN)。在此填入服务端配置的 token,浏览器会用它访问控制台;token 不匹配则无法查看或修改规则。', 'The server requires an access token (AGENT_MOSS_CONSOLE_TOKEN is set). Enter the token configured on the server; the browser will send it with every console request. An invalid token blocks viewing or editing rules.')}</div>
|
|
1187
|
+
<input class="ag-input" id="settings-token" placeholder="${t('输入服务端配置的 token', 'Enter the server-configured token')}" value="${getToken()}">
|
|
1133
1188
|
<div style="margin-top:8px"><button class="ag-btn ag-btn-primary" onclick="localStorage.setItem('am_console_token',document.getElementById('settings-token').value);showToast(t('已保存','Saved'))">${t('保存', 'Save')}</button></div>
|
|
1134
|
-
</div
|
|
1189
|
+
</div>` : ''}
|
|
1135
1190
|
<div class="ag-card">
|
|
1136
1191
|
<div class="ag-section-title">${t('危险操作', 'Danger Zone')}</div>
|
|
1137
|
-
<button class="ag-btn ag-btn-danger" onclick="if(confirm(t('确定重置所有规则到出厂默认?所有自定义规则和开关设置将丢失。','Reset all rules to factory defaults? All custom rules and switch settings will be lost.'))){apiPost('reset').then(c=>{state.config=c;state.envOverrides=
|
|
1192
|
+
<button class="ag-btn ag-btn-danger" onclick="if(confirm(t('确定重置所有规则到出厂默认?所有自定义规则和开关设置将丢失。','Reset all rules to factory defaults? All custom rules and switch settings will be lost.'))){apiPost('reset').then(c=>{state.config=c;return loadEnvOverrides().then(o=>{state.envOverrides=o;renderCurrent();showToast(t('配置已重置到出厂默认','Configuration reset to factory defaults'))})}).catch(e=>showToast(t('重置失败: ','Reset failed: ')+e.message,'error'))}">${t('重置规则到出厂默认', 'Reset Rules to Factory Defaults')}</button>
|
|
1138
1193
|
<button class="ag-btn ag-btn-danger" style="margin-left:8px" onclick="resetTokenStats()">${t('清除 Token 统计', 'Clear Token Statistics')}</button>
|
|
1139
1194
|
</div>`;
|
|
1140
1195
|
}
|