@kenhkl/agent-moss 0.12.2 → 0.12.4

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.
@@ -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 api('env-overrides');
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
- function countEnabled(lr) {
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
- function countEnabledSkills(ls) {
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
- state.config = await apiPut('layers', { layer_key: key, enabled: !cur });
536
- state.envOverrides = await api('env-overrides');
537
- renderCurrent();
538
- showToast(`${LAYER_NAMES[key]?.name || key} ${t('已', '')}${!cur ? t('启用', 'enabled') : t('禁用', 'disabled')}`);
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
- const enabledCount = rules.filter(r => r.enabled).length;
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(catEnabled, false, '')}</div>
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
- const ids = Object.keys(agents);
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
- const enCount = rules.filter(r => r.enabled).length;
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
- const enabledCount = skills.filter(s => s.enabled).length;
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(catEnabled, false, '')}</div>
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>
@@ -937,6 +991,7 @@ let tokenStatsDays = 7;
937
991
  let tokenRefreshTimer = null;
938
992
  let trendChartInstance = null;
939
993
  function render_token() {
994
+ tokenRecentPage = 1; // 切到 Token 页时重置最近调用分页到第 1 页
940
995
  el('page-token').innerHTML = `
941
996
  <div class="ag-card">
942
997
  <div class="ag-section-title">${t('Token 用量详细统计', 'Detailed Token Statistics')
@@ -1078,22 +1133,57 @@ function renderTokenTrend(data) {
1078
1133
  })),
1079
1134
  }, true);
1080
1135
  }
1136
+ // Token 最近调用分页状态
1137
+ let tokenRecentPage = 1;
1138
+ const TOKEN_RECENT_PAGE_SIZE = 10;
1139
+ let tokenRecentTotal = 0; // 最近调用总条数(供跳转页时计算总页数)
1081
1140
  async function loadTokenRecent() {
1082
1141
  const recentEl = el('token-recent');
1083
1142
  if (!recentEl) return;
1084
1143
  try {
1085
- const records = await api('token-stats/recent?limit=10');
1144
+ const offset = (tokenRecentPage - 1) * TOKEN_RECENT_PAGE_SIZE;
1145
+ const data = await api(`token-stats/recent?limit=${TOKEN_RECENT_PAGE_SIZE}&offset=${offset}`);
1146
+ const records = Array.isArray(data) ? data : (data?.records || []);
1147
+ const total = Array.isArray(data) ? records.length : (data?.total ?? records.length);
1148
+ tokenRecentTotal = total; // 供跳转页时计算总页数
1086
1149
  if (!records || records.length === 0) { recentEl.innerHTML = `<div class="ag-empty">${t('暂无记录', 'No records yet')}</div>`; return; }
1087
1150
  let html = '<table class="ag-table"><tr><th>' + t('时间', 'Time') + '</th><th>' + t('步骤', 'Step') + '</th><th>' + t('模型', 'Model') + '</th><th>' + t('输入', 'Input') + '</th><th>' + t('输出', 'Output') + '</th><th>' + t('总计', 'Total') + '</th></tr>';
1088
1151
  for (const r of records.reverse()) {
1089
1152
  const stepName = r.step === 'L3_security_judge' ? t('L3 安全判断', 'L3 Security Judge') : r.step === 'step2_policy_gen' ? t('策略生成', 'Policy Generation') : escapeHtml(r.step);
1090
- const ts = r.timestamp ? escapeHtml(r.timestamp.slice(0, 19)) : '';
1091
- html += '<tr><td>' + ts + '</td><td>' + stepName + '</td><td style="color:var(--ag-text-secondary)">' + escapeHtml(r.model || '') + '</td><td>' + r.prompt_tokens + '</td><td>' + r.completion_tokens + '</td><td><strong>' + r.total_tokens + '</strong></td></tr>';
1153
+ // timestamp 是 UTC ISO(带 Z),转本地时区显示
1154
+ let ts = '';
1155
+ try { ts = r.timestamp ? new Date(r.timestamp).toLocaleString() : ''; } catch { ts = r.timestamp || ''; }
1156
+ html += '<tr><td>' + escapeHtml(ts) + '</td><td>' + stepName + '</td><td style="color:var(--ag-text-secondary)">' + escapeHtml(r.model || '') + '</td><td>' + r.prompt_tokens + '</td><td>' + r.completion_tokens + '</td><td><strong>' + r.total_tokens + '</strong></td></tr>';
1092
1157
  }
1093
1158
  html += '</table>';
1159
+ // 分页控制条:上一页 / 页码显示 / 输入页码跳转 / 下一页
1160
+ const totalPages = Math.max(1, Math.ceil(total / TOKEN_RECENT_PAGE_SIZE));
1161
+ html += `<div style="margin-top:10px;display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap">
1162
+ <button class="ag-btn ag-btn-sm" onclick="tokenRecentPage<=1?null:loadTokenRecentPage(${tokenRecentPage - 1})" ${tokenRecentPage <= 1 ? 'disabled' : ''}>${t('上一页', 'Prev')}</button>
1163
+ <span style="font-size:var(--fs-md);color:var(--ag-text-secondary)">${t('第 ', 'Page ')}${tokenRecentPage} / ${totalPages}${t('(共 ', ' (total ')}${total}${t(' 条)', ')')}</span>
1164
+ <input id="token-recent-goto" type="number" min="1" max="${totalPages}" value="${tokenRecentPage}" style="width:60px;padding:3px 6px;font-size:var(--fs-md);border:1px solid var(--ag-input-border);border-radius:6px;background:var(--ag-input-bg);color:var(--ag-text);" placeholder="${tokenRecentPage}">
1165
+ <button class="ag-btn ag-btn-sm" onclick="goTokenRecentPage()">${t('跳转', 'Go')}</button>
1166
+ <button class="ag-btn ag-btn-sm" onclick="tokenRecentPage>=${totalPages}?null:loadTokenRecentPage(${tokenRecentPage + 1})" ${tokenRecentPage >= totalPages ? 'disabled' : ''}>${t('下一页', 'Next')}</button>
1167
+ </div>`;
1094
1168
  recentEl.innerHTML = html;
1095
1169
  } catch (e) { recentEl.innerHTML = `<div class="ag-empty">${t('加载失败', 'Load failed')}</div>`; }
1096
1170
  }
1171
+ function loadTokenRecentPage(page) {
1172
+ tokenRecentPage = page;
1173
+ loadTokenRecent();
1174
+ }
1175
+ /** 跳转到用户输入的页码 */
1176
+ async function goTokenRecentPage() {
1177
+ const input = el('token-recent-goto');
1178
+ if (!input) return;
1179
+ const page = parseInt(input.value, 10);
1180
+ if (!page || page < 1) { showToast(t('页码无效', 'Invalid page number'), 'error'); return; }
1181
+ // 计算总页数,超出则跳到最后一页
1182
+ const totalPages = Math.max(1, Math.ceil(tokenRecentTotal / TOKEN_RECENT_PAGE_SIZE));
1183
+ const target = Math.min(page, totalPages);
1184
+ tokenRecentPage = target;
1185
+ loadTokenRecent();
1186
+ }
1097
1187
  async function resetTokenStats() {
1098
1188
  if (!confirm(t('确定清除所有 Token 统计数据?', 'Clear all Token statistics?'))) return;
1099
1189
  try { await apiPost('token-stats/reset'); showToast(t('Token 统计已清除', 'Token statistics cleared')); loadTokenStats(7); }
@@ -1113,28 +1203,29 @@ function render_settings() {
1113
1203
  }
1114
1204
  }
1115
1205
  el('page-settings').innerHTML = `
1206
+ ${Object.keys(eo).length > 0 ? `
1116
1207
  <div class="ag-card">
1117
1208
  <div class="ag-section-title">${t('环境变量覆盖', 'Environment Variable Overrides')}</div>
1118
1209
  <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
1210
  ${envHtml}
1120
- </div>
1211
+ </div>` : ''}
1121
1212
  <div class="ag-card">
1122
- <div class="ag-section-title">${t('配置文件路径', 'Config File Path')}</div>
1213
+ <div class="ag-section-title">${t('配置文件', 'Config File')}</div>
1214
+ <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
1215
  <div style="font-size:13px;line-height:1.8">
1124
- <p>${t('配置文件: ', 'Config file: ')}~/.config/agentmoss/agent_moss_runtime.json</p>
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>
1216
+ <code>~/.config/agentmoss/agent_moss_runtime.json</code>
1127
1217
  </div>
1128
1218
  </div>
1219
+ ${state.consoleAuthRequired ? `
1129
1220
  <div class="ag-card">
1130
1221
  <div class="ag-section-title">${t('访问 Token', 'Access Token')}</div>
1131
- <div class="ag-section-desc">${t('此处设置的是浏览器本地 token,console 调用 API 时以 Authorization: Bearer &lt;token&gt; 携带。服务端是否要求认证取决于环境变量 AGENT_MOSS_CONSOLE_TOKEN:未设置则本机免认证;已设置则请求须携带相同值才放行。当前本地 token:', 'This sets the browser-local token used as Authorization: Bearer &lt;token&gt; on console API calls. Whether the server requires auth depends on AGENT_MOSS_CONSOLE_TOKEN: when unset, local access is allowed without auth; when set, requests must carry the matching value. Current local token: ')}${getToken() ? t('已设置', 'set') : t('未设置', 'not set')}</div>
1132
- <input class="ag-input" id="settings-token" placeholder="${t('输入新 token', 'Enter new token')}" value="${getToken()}">
1222
+ <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>
1223
+ <input class="ag-input" id="settings-token" placeholder="${t('输入服务端配置的 token', 'Enter the server-configured token')}" value="${getToken()}">
1133
1224
  <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>
1225
+ </div>` : ''}
1135
1226
  <div class="ag-card">
1136
1227
  <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=api('env-overrides');renderCurrent();showToast(t('配置已重置到出厂默认','Configuration reset to factory defaults'))}).catch(e=>showToast(t('重置失败: ','Reset failed: ')+e.message,'error'))}">${t('重置规则到出厂默认', 'Reset Rules to Factory Defaults')}</button>
1228
+ <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
1229
  <button class="ag-btn ag-btn-danger" style="margin-left:8px" onclick="resetTokenStats()">${t('清除 Token 统计', 'Clear Token Statistics')}</button>
1139
1230
  </div>`;
1140
1231
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenhkl/agent-moss",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "description": "Multi-layer security analysis engine for AI agents (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",