@kenhkl/agent-moss 0.12.8 → 0.13.0

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.
@@ -381,6 +381,8 @@ const LAYER_NAMES = {
381
381
  L1_heuristic: { name: t('L1 静态检测', 'L1 Heuristic Detection'), desc: t('正则/关键词匹配,仅看下一步动作', 'Regex/keyword matching; only inspects the next action') },
382
382
  L2_logic_rules: { name: t('L2 逻辑规则', 'L2 Logic Rules'), desc: t('read_before_write + 授权检测使用历史动作序列,敏感路径/危险模式仅匹配下一步', 'read_before_write + authorization checks use historical action sequences; sensitive paths/dangerous patterns match only the next action') },
383
383
  L3_llm_analysis: { name: t('L3 LLM 分析', 'L3 LLM Analysis'), desc: t('Skill 匹配 + LLM 语义判断', 'Skill matching + LLM semantic judgment') },
384
+ agent_rules_enabled: { name: t('Agent 定制规则', 'Agent Custom Rules'), desc: t('各 agent 专属规则总开关(xiaoo/opendesk 等分区)', 'Master switch for per-agent custom rules (xiaoo/opendesk partitions)') },
385
+ scenario_rules_enabled: { name: t('场景定制规则', 'Scenario Custom Rules'), desc: t('各场景专属规则总开关(bank 等部署级分区)', 'Master switch for per-scenario custom rules (bank etc.)') },
384
386
  };
385
387
  const L1_CATEGORY_NAMES = {
386
388
  dangerous_commands: t('⚡ 危险命令检测', '⚡ Dangerous Commands'),
@@ -609,8 +611,13 @@ function render_overview() {
609
611
  <div class="ag-section-title">${t('规则统计', 'Rule Statistics')}</div>
610
612
  <div class="ag-stat-grid">
611
613
  <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>
612
- <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>
614
+ <div class="ag-stat-card"><div class="label">${t('L2 规则启用', 'L2 Rules Enabled')}</div><div class="value">${countEnabledL2()}/${countRulesL2()}</div></div>
613
615
  <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>
616
+ <div class="ag-stat-card"><div class="label">${t('Agent 定制规则', 'Agent custom rules')}</div><div class="value">${countRules(state.agentRules?.agents || {})}</div></div>
617
+ <div class="ag-stat-card"><div class="label">${t('场景定制规则', 'Scenario custom rules')}</div><div class="value">${(state.scenarioRules?.scenarios ? countRules(state.scenarioRules.scenarios) : 0)}</div></div>
618
+ </div>
619
+ <div style="font-size:11px;color:var(--ag-text-secondary);margin-top:6px">
620
+ ${t('说明:LTL 时序规则属 L2(开关在 L2 页);Agent/场景为按 id 分区的定制规则,有独立总开关。', 'Note: LTL timing rules belong to L2 (toggle on L2 page); Agent/Scenario are id-partitioned custom rules with their own master switches.')}
614
621
  </div>
615
622
  </div>
616
623
  <div class="ag-card">
@@ -669,6 +676,19 @@ function countEnabledSkills(ls, layerKey) {
669
676
  if (layerKey && state.config.layers?.[layerKey] === false) return 0;
670
677
  return Object.values(ls).reduce((s, c) => s + ((c.skills || []).filter(s => s.enabled && c.category_enabled !== false).length), 0);
671
678
  }
679
+ // 方案B:L2 总规则数 = 文本规则 + LTL 时序规则(都属 L2 大类)
680
+ function countRulesL2() {
681
+ const text = countRules(state.config.L2_rules);
682
+ const ltl = (state.config.L2_4?.ltl_rules || []).length;
683
+ return text + ltl;
684
+ }
685
+ // L2 启用数 = 文本启用 + LTL 启用(层级总开关关闭时 LTL 归 0,与引擎 gating 一致)
686
+ function countEnabledL2() {
687
+ const text = countEnabled(state.config.L2_rules, 'L2_logic_rules');
688
+ const layerOn = state.config.layers?.L2_logic_rules !== false;
689
+ const ltlOn = layerOn ? (state.config.L2_4?.ltl_rules || []).filter(r => r.enabled !== false).length : 0;
690
+ return text + ltlOn;
691
+ }
672
692
 
673
693
  // ===== 层级开关 =====
674
694
  async function toggleLayer(key) {
@@ -715,16 +735,27 @@ function toggleExpand(id) {
715
735
  // ===== L1/L2 规则页 =====
716
736
  // 规则层容器 -> 层级总开关 key 的映射(用于大类开关随层级开关联动)
717
737
  const LAYER_SWITCH_KEY = { L1_rules: 'L1_heuristic', L2_rules: 'L2_logic_rules', L3_skills: 'L3_llm_analysis' };
718
- function renderRuleLayer(layerKey, containerId, categoryNames) {
738
+ function renderRuleLayer(layerKey, containerId, categoryNames, append = false, showAddBtn = true, strict = false) {
719
739
  const layer = state.config[layerKey] || {};
720
740
  // 层级总开关:关闭时整层大类开关随动显示为关(仅 UI 联动,不改后端数据)
721
741
  const layerSwitchKey = LAYER_SWITCH_KEY[layerKey];
722
742
  const layerEnabled = layerSwitchKey ? state.config.layers?.[layerSwitchKey] !== false : true;
723
- const orderedEntries = [
724
- ...Object.keys(categoryNames).filter(k => k in layer).map(k => [k, layer[k]]),
725
- ...Object.entries(layer).filter(([k]) => !(k in categoryNames)),
726
- ];
727
- let html = `<div class="ag-card" style="padding:8px 16px"><button class="ag-btn ag-btn-primary" onclick="showAddRuleModal('${layerKey}')">+ ${t('新增规则', 'Add Rule')}</button></div>`;
743
+ // strict:只渲染 categoryNames 白名单内的分类(不兜底 runtime 里多的 key),
744
+ // 供 render_l2 三段式(内置类 + 用户自定义分开)避免重复/英文透出。
745
+ let orderedEntries;
746
+ if (strict) {
747
+ orderedEntries = Object.keys(categoryNames)
748
+ .filter(k => k in layer)
749
+ .map(k => [k, layer[k]]);
750
+ } else {
751
+ orderedEntries = [
752
+ ...Object.keys(categoryNames).filter(k => k in layer).map(k => [k, layer[k]]),
753
+ ...Object.entries(layer).filter(([k]) => !(k in categoryNames)),
754
+ ];
755
+ }
756
+ let html = showAddBtn
757
+ ? `<div class="ag-card" style="padding:8px 16px"><button class="ag-btn ag-btn-primary" onclick="showAddRuleModal('${layerKey}')">+ ${t('新增规则', 'Add Rule')}</button></div>`
758
+ : '';
728
759
  for (const [catName, catData] of orderedEntries) {
729
760
  const displayName = categoryNames[catName] || catName;
730
761
  const rules = catData.rules || [];
@@ -753,7 +784,10 @@ function renderRuleLayer(layerKey, containerId, categoryNames) {
753
784
  }
754
785
  html += '</div></div>';
755
786
  }
756
- el(containerId).innerHTML = html;
787
+ const cont = el(containerId);
788
+ if (!cont) return;
789
+ if (append) cont.insertAdjacentHTML('beforeend', html);
790
+ else cont.innerHTML = html;
757
791
  }
758
792
  function renderRuleItem(layerKey, catName, r) {
759
793
  const isL2 = layerKey === 'L2_rules';
@@ -786,7 +820,49 @@ function renderRuleItem(layerKey, catName, r) {
786
820
  </div>`;
787
821
  }
788
822
  function render_l1() { renderRuleLayer('L1_rules', 'page-l1', L1_CATEGORY_NAMES); }
789
- function render_l2() { renderRuleLayer('L2_rules', 'page-l2', L2_CATEGORY_NAMES); }
823
+ function render_l2() {
824
+ // 顺序:内置 L2 类 → LTL 时序规则卡 → 用户自定义(永远最后)
825
+ // strict=true:三段各渲染自己的白名单,避免兜底把别的类目用英文透出(去重 bug)
826
+ const builtin = Object.fromEntries(Object.entries(L2_CATEGORY_NAMES).filter(([k]) => k !== 'user_custom'));
827
+ const customOnly = { user_custom: L2_CATEGORY_NAMES.user_custom };
828
+ renderRuleLayer('L2_rules', 'page-l2', builtin, false, true, true);
829
+ renderLtlCard('page-l2');
830
+ renderRuleLayer('L2_rules', 'page-l2', customOnly, true, false, true);
831
+ }
832
+ // LTL 时序攻击模式规则卡(L2.2/L2.4,并入 L2 页展示;数据源 state.config.L2_4.ltl_rules)
833
+ function renderLtlCard(containerId) {
834
+ const l24 = state.config.L2_4 || {};
835
+ const ltl = l24.ltl_rules || [];
836
+ const isL2On = state.config.layers?.L2_logic_rules !== false;
837
+ // 与其他分类一致:默认折叠,用户展开过则保持(expandedCats)
838
+ const expanded = expandedCats.has(`${containerId}-ltl_rules`);
839
+ let html = `<div class="ag-card">
840
+ <div class="ag-cat-header" onclick="toggleExpand('${containerId}-ltl_rules')">
841
+ <div class="ag-cat-name">${t('⏱️ 时序攻击模式 (LTL)', '⏱️ Timing Attack Patterns (LTL)')} <span class="ag-cat-count">${isL2On ? ltl.filter(r => r.enabled !== false).length : 0}/${ltl.length} ${t('启用', 'enabled')}</span></div>
842
+ <div class="ag-cat-controls">
843
+ <div onclick="event.stopPropagation()">${toggle(isL2On && ltl.some(r => r.enabled !== false), !isL2On, '')}</div>
844
+ <span class="ag-expand-icon ${expanded ? 'open' : ''}" id="expand-${containerId}-ltl_rules">▼</span>
845
+ </div>
846
+ </div>
847
+ <div style="font-size:12px;color:var(--ag-text-secondary);padding:0 16px 8px">${t('检测历史动作序列中的攻击模式:先发生 A、后发生 B(受 L2 层开关控制,当前', 'Detects attack patterns in history: A then B (gated by L2 layer switch, currently')} <b>${isL2On ? t('启用', 'enabled') : t('禁用', 'disabled')}</b>)</div>
848
+ <div class="ag-rule-list" id="list-${containerId}-ltl_rules" style="display:${expanded ? '' : 'none'}">`;
849
+ if (!ltl.length) {
850
+ html += `<div class="ag-empty">${t('无', 'None')}</div>`;
851
+ } else {
852
+ for (const r of ltl) {
853
+ const action = r.action === 'block' ? t('拦截', 'Block') : (r.action === 'approval' ? t('审批', 'Approval') : t(r.action || '', r.action || ''));
854
+ html += `<div class="ag-rule-item"><div class="ag-rule-body">
855
+ <div class="ag-rule-tags">${riskBadge(r.risk_level)}</div>
856
+ <div class="ag-rule-pattern"><span class="ag-rule-field-label">${t('检测', 'Detects')}:</span> ${escapeHtml(r.human || r.reason || r.id)}</div>
857
+ <div class="ag-rule-reason"><span class="ag-rule-field-label">${t('规则说明', 'Description')}:</span> ${escapeHtml(r.reason || '')} <span class="ag-rule-id">[${escapeHtml(r.id)}]</span></div>
858
+ <div class="ag-rule-id">${t('动作', 'Action')}: ${action}</div>
859
+ </div><div class="ag-rule-controls">${toggle(r.enabled !== false, false, `toggleRule('L2_4','ltl_rules','${r.id}',${!(r.enabled !== false)})`)}</div></div>`;
860
+ }
861
+ }
862
+ html += '</div></div>';
863
+ const cont = el(containerId);
864
+ if (cont) cont.insertAdjacentHTML('beforeend', html);
865
+ }
790
866
  async function toggleRule(layer, cat, id, en) {
791
867
  try {
792
868
  state.config = await apiPut('rules/enabled', { layer, category: cat, rule_id: id, enabled: en });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenhkl/agent-moss",
3
- "version": "0.12.8",
3
+ "version": "0.13.0",
4
4
  "description": "Multi-layer security analysis engine for AI agents (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",