@kenhkl/agent-moss 0.16.12 → 0.16.13

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/server.js CHANGED
@@ -394,6 +394,52 @@ var init_runtime_config_bridge = __esm({
394
394
  }
395
395
  });
396
396
 
397
+ // src/infra/rules-mode.ts
398
+ function truthy(name) {
399
+ return TRUTHY.has((process.env[name] || "").trim().toLowerCase());
400
+ }
401
+ function getRulesMode() {
402
+ if (cached !== null) return cached;
403
+ const raw = (process.env[ENV_RULES_MODE] || "").trim().toLowerCase();
404
+ if (raw) {
405
+ if (VALID_MODES.has(raw)) {
406
+ cached = raw;
407
+ } else {
408
+ console.error(
409
+ `[AgentMoss] ${ENV_RULES_MODE}=${JSON.stringify(raw)} \u975E\u6CD5\uFF08\u53EA\u652F\u6301 hot / cold / builtin\uFF09\uFF1B\u4E3A\u5B89\u5168\u8D77\u89C1\u6309 builtin\uFF08\u4EC5\u5185\u7F6E\u89C4\u5219\uFF09\u5904\u7406`
410
+ );
411
+ cached = "builtin";
412
+ }
413
+ return cached;
414
+ }
415
+ if (truthy(ENV_RULES_BUILTIN_ONLY)) cached = "builtin";
416
+ else if (truthy(ENV_DISABLE_RULE_HOT_RELOAD)) cached = "cold";
417
+ else cached = "hot";
418
+ return cached;
419
+ }
420
+ function isHotReloadEnabled() {
421
+ return getRulesMode() === "hot";
422
+ }
423
+ function isRuntimeConfigDisabled() {
424
+ return getRulesMode() !== "hot";
425
+ }
426
+ function isBuiltinOnly() {
427
+ return getRulesMode() === "builtin";
428
+ }
429
+ var ENV_RULES_MODE, ENV_RULES_BUILTIN_ONLY, ENV_DISABLE_RULE_HOT_RELOAD, TRUTHY, VALID_MODES, cached;
430
+ var init_rules_mode = __esm({
431
+ "src/infra/rules-mode.ts"() {
432
+ "use strict";
433
+ init_esm_shims();
434
+ ENV_RULES_MODE = "AGENT_MOSS_RULES_MODE";
435
+ ENV_RULES_BUILTIN_ONLY = "AGENT_MOSS_RULES_BUILTIN_ONLY";
436
+ ENV_DISABLE_RULE_HOT_RELOAD = "AGENT_MOSS_DISABLE_RULE_HOT_RELOAD";
437
+ TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
438
+ VALID_MODES = /* @__PURE__ */ new Set(["hot", "cold", "builtin"]);
439
+ cached = null;
440
+ }
441
+ });
442
+
397
443
  // src/infra/rules-loader.ts
398
444
  import { readFileSync as readFileSync2, existsSync, readdirSync, statSync } from "fs";
399
445
  import { resolve, dirname, join as join2 } from "path";
@@ -407,6 +453,7 @@ function parseDirEnv(envName) {
407
453
  return raw.split(/[,;]/).map((s) => s.trim()).filter(Boolean).map((s) => resolve(s));
408
454
  }
409
455
  function rulesDirs() {
456
+ if (isBuiltinOnly()) return [];
410
457
  const dirs = [];
411
458
  for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
412
459
  dirs.push(resolve(dataRootDir(), "rules"));
@@ -420,10 +467,16 @@ function findRulesFile(name) {
420
467
  return null;
421
468
  }
422
469
  function loadJson(name) {
470
+ if (!isHotReloadEnabled()) {
471
+ const cached2 = frozenJsonCache.get(name);
472
+ if (cached2 !== void 0) return cached2;
473
+ }
423
474
  const p = findRulesFile(name);
424
475
  if (!p) return {};
425
476
  try {
426
- return JSON.parse(readFileSync2(p, "utf-8")) || {};
477
+ const parsed = JSON.parse(readFileSync2(p, "utf-8")) || {};
478
+ if (!isHotReloadEnabled()) frozenJsonCache.set(name, parsed);
479
+ return parsed;
427
480
  } catch {
428
481
  return {};
429
482
  }
@@ -748,6 +801,7 @@ function scanRuleDirs() {
748
801
  return { override, extra, loaded, ignored };
749
802
  }
750
803
  function rulesCached() {
804
+ if (!isHotReloadEnabled() && _RULES_CACHE) return _RULES_CACHE;
751
805
  const dirs = rulesDirs();
752
806
  const sig = RULES_DIR_ENVS.map((e) => `${e}=${process.env[e] || ""}`).join("|") + "|" + dirs.join("|");
753
807
  const files = [];
@@ -783,9 +837,11 @@ function identicalToBuiltin(override) {
783
837
  return Object.keys(override).length > 0;
784
838
  }
785
839
  function loadOverrideRules() {
840
+ if (isBuiltinOnly()) return {};
786
841
  return rulesCached().override;
787
842
  }
788
843
  function loadExtraRules() {
844
+ if (isBuiltinOnly()) return {};
789
845
  return rulesCached().extra;
790
846
  }
791
847
  function ruleFilesLoaded() {
@@ -794,12 +850,13 @@ function ruleFilesLoaded() {
794
850
  function ignoredRuleFiles() {
795
851
  return rulesCached().ignored;
796
852
  }
797
- var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, RULES_DIR_CANDS, SCENARIO_LIST_KEYS, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
853
+ var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, RULES_DIR_CANDS, frozenJsonCache, SCENARIO_LIST_KEYS, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
798
854
  var init_rules_loader = __esm({
799
855
  "src/infra/rules-loader.ts"() {
800
856
  "use strict";
801
857
  init_esm_shims();
802
858
  init_paths();
859
+ init_rules_mode();
803
860
  init_runtime_config_bridge();
804
861
  __filename2 = fileURLToPath2(import.meta.url);
805
862
  __dirname2 = dirname(__filename2);
@@ -822,6 +879,7 @@ var init_rules_loader = __esm({
822
879
  ];
823
880
  return cands.filter((c) => existsSync(c));
824
881
  })();
882
+ frozenJsonCache = /* @__PURE__ */ new Map();
825
883
  SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
826
884
  EXTRA_CATEGORIES = {
827
885
  dangerous_commands: ["pattern"],
@@ -1904,6 +1962,7 @@ function userSkillsDir() {
1904
1962
  return resolve3(getDataRoot(), "agent_moss_skills");
1905
1963
  }
1906
1964
  function runtimeCustomSkillKeywords() {
1965
+ if (isRuntimeConfigDisabled()) return {};
1907
1966
  try {
1908
1967
  const rt = runtimeConfigBridge.current?.loadRuntimeConfig?.();
1909
1968
  if (!rt) return {};
@@ -1921,6 +1980,7 @@ function runtimeCustomSkillKeywords() {
1921
1980
  }
1922
1981
  }
1923
1982
  function loadUserSkill(name) {
1983
+ if (isRuntimeConfigDisabled()) return "";
1924
1984
  if (userSkillCache[name] !== void 0) return userSkillCache[name];
1925
1985
  try {
1926
1986
  const p = resolve3(userSkillsDir(), `${name}.md`);
@@ -1989,6 +2049,7 @@ var init_skill_loader = __esm({
1989
2049
  init_esm_shims();
1990
2050
  init_rules_loader();
1991
2051
  init_paths();
2052
+ init_rules_mode();
1992
2053
  init_asset_dir();
1993
2054
  init_runtime_config_bridge();
1994
2055
  __filename3 = fileURLToPath3(import.meta.url);
@@ -2049,6 +2110,7 @@ __export(runtime_config_exports, {
2049
2110
  getEnabledScenarioExtensions: () => getEnabledScenarioExtensions,
2050
2111
  getEnvOverrides: () => getEnvOverrides,
2051
2112
  getProfileOrDetect: () => getProfileOrDetect,
2113
+ getRuleSourceFingerprint: () => getRuleSourceFingerprint,
2052
2114
  getRuntimeConfigPath: () => getRuntimeConfigPath,
2053
2115
  getScenarioRules: () => getScenarioRules,
2054
2116
  hasRuntimeConfig: () => hasRuntimeConfig,
@@ -2083,7 +2145,22 @@ function getRuntimeConfigPath() {
2083
2145
  if (envPath) return envPath;
2084
2146
  return RUNTIME_CONFIG_PATH;
2085
2147
  }
2148
+ function getRuleSourceFingerprint() {
2149
+ if (isRuntimeConfigDisabled()) return `mode:${getRulesMode()}`;
2150
+ const p = getRuntimeConfigPath();
2151
+ try {
2152
+ const st = statSync2(p);
2153
+ return `${p}|${st.mtimeMs}|${st.size}`;
2154
+ } catch {
2155
+ return `${p}|absent`;
2156
+ }
2157
+ }
2086
2158
  function saveRuntimeConfig(config) {
2159
+ if (isRuntimeConfigDisabled()) {
2160
+ throw new Error(
2161
+ `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF1A\u4FEE\u6539\u89C4\u5219\u4E0D\u4F1A\u751F\u6548\uFF0C\u8BF7\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u5E76\u91CD\u542F\u670D\u52A1\u3002`
2162
+ );
2163
+ }
2087
2164
  const p = getRuntimeConfigPath();
2088
2165
  const dir = p.substring(0, p.lastIndexOf("/"));
2089
2166
  if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
@@ -2107,6 +2184,18 @@ function invalidateRuntimeConfigCache() {
2107
2184
  hasRuntimeConfigCache.clear();
2108
2185
  }
2109
2186
  function loadRuntimeConfig() {
2187
+ if (isRuntimeConfigDisabled()) {
2188
+ return {
2189
+ version: 1,
2190
+ layers: { ...DEFAULT_LAYERS },
2191
+ L1_rules: {},
2192
+ L2_rules: {},
2193
+ L3_skills: {},
2194
+ L2_2: {},
2195
+ agent_rules: {},
2196
+ scenario_rules: {}
2197
+ };
2198
+ }
2110
2199
  const p = getRuntimeConfigPath();
2111
2200
  if (!existsSync5(p)) {
2112
2201
  const seeded = mergeConfig({}, generateSourceDefaults());
@@ -2153,6 +2242,7 @@ function loadRuntimeConfig() {
2153
2242
  function isLayerEnabled(layerKey) {
2154
2243
  const disableKey = LAYER_DISABLE_ENV[layerKey];
2155
2244
  if (disableKey && process.env[disableKey] === "1") return false;
2245
+ if (isRuntimeConfigDisabled()) return true;
2156
2246
  const runtime = loadRuntimeConfig();
2157
2247
  const layers = runtime.layers || {};
2158
2248
  if (layerKey in layers) return !!layers[layerKey];
@@ -2253,6 +2343,7 @@ function getEnabledL2SensitivePaths(runtime, osType) {
2253
2343
  });
2254
2344
  }
2255
2345
  function hasRuntimeConfig() {
2346
+ if (isRuntimeConfigDisabled()) return false;
2256
2347
  const p = getRuntimeConfigPath();
2257
2348
  let key;
2258
2349
  try {
@@ -2261,8 +2352,8 @@ function hasRuntimeConfig() {
2261
2352
  } catch {
2262
2353
  return false;
2263
2354
  }
2264
- const cached = hasRuntimeConfigCache.get(key);
2265
- if (cached !== void 0) return cached;
2355
+ const cached2 = hasRuntimeConfigCache.get(key);
2356
+ if (cached2 !== void 0) return cached2;
2266
2357
  let ok = false;
2267
2358
  try {
2268
2359
  JSON.parse(readFileSync4(p, "utf-8"));
@@ -3142,6 +3233,11 @@ function deleteCustomRule(layer, category, ruleId) {
3142
3233
  }
3143
3234
  function addCustomSkill(skillId, category, keywords, content) {
3144
3235
  _validateSkillId(skillId);
3236
+ if (isRuntimeConfigDisabled()) {
3237
+ throw new Error(
3238
+ `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF0C\u4E0D\u5141\u8BB8\u65B0\u589E\u81EA\u5B9A\u4E49 skill\u3002`
3239
+ );
3240
+ }
3145
3241
  const runtime = loadRuntimeConfig();
3146
3242
  const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
3147
3243
  try {
@@ -3368,6 +3464,7 @@ var init_runtime_config = __esm({
3368
3464
  init_skill_loader();
3369
3465
  init_rules_loader();
3370
3466
  init_runtime_config_bridge();
3467
+ init_rules_mode();
3371
3468
  __dirname4 = dirname3(fileURLToPath4(import.meta.url));
3372
3469
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
3373
3470
  RUNTIME_CONFIG_DIR = runtimeDir();
@@ -3439,6 +3536,7 @@ __export(config_exports, {
3439
3536
  isPolicyGenEnabled: () => isPolicyGenEnabled,
3440
3537
  isPolicySaveEnabled: () => isPolicySaveEnabled,
3441
3538
  probeLLMConnection: () => probeLLMConnection,
3539
+ reloadCustomRules: () => reloadCustomRules,
3442
3540
  updateLLMConfig: () => updateLLMConfig,
3443
3541
  userRuleVariantPattern: () => userRuleVariantPattern,
3444
3542
  wildcardToRegExp: () => wildcardToRegExp
@@ -3519,17 +3617,19 @@ function wildcardToRegExp(pattern) {
3519
3617
  }
3520
3618
  function loadCustomRules() {
3521
3619
  const rules = [];
3522
- try {
3523
- const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3524
- if (raw) {
3525
- const parsed = JSON.parse(raw);
3526
- if (Array.isArray(parsed)) {
3527
- for (const r of parsed) {
3528
- if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3620
+ if (!isBuiltinOnly()) {
3621
+ try {
3622
+ const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3623
+ if (raw) {
3624
+ const parsed = JSON.parse(raw);
3625
+ if (Array.isArray(parsed)) {
3626
+ for (const r of parsed) {
3627
+ if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3628
+ }
3529
3629
  }
3530
3630
  }
3631
+ } catch {
3531
3632
  }
3532
- } catch {
3533
3633
  }
3534
3634
  for (const rulesPath of USER_RULES_PATHS) {
3535
3635
  try {
@@ -3558,6 +3658,9 @@ function userRuleVariantPattern(pattern) {
3558
3658
  }
3559
3659
  return null;
3560
3660
  }
3661
+ function reloadCustomRules() {
3662
+ customRules = loadCustomRules();
3663
+ }
3561
3664
  function getLLMConfig() {
3562
3665
  return llmConfig;
3563
3666
  }
@@ -3645,6 +3748,7 @@ function layerEnabledOrDefault(key) {
3645
3748
  }
3646
3749
  }
3647
3750
  function isBrainEnabled() {
3751
+ if (isRuntimeConfigDisabled()) return false;
3648
3752
  return getAgentMossSetting("AGENT_MOSS_BRAIN_ENABLED", "") === "1";
3649
3753
  }
3650
3754
  function getAgentMossSetting(key, def = "") {
@@ -3686,6 +3790,7 @@ var init_config = __esm({
3686
3790
  init_esm_shims();
3687
3791
  init_paths();
3688
3792
  init_runtime_config();
3793
+ init_rules_mode();
3689
3794
  __configModuleDir = (() => {
3690
3795
  try {
3691
3796
  return path2.dirname(fileURLToPath5(import.meta.url));
@@ -3742,7 +3847,9 @@ function resolveAgentsDir() {
3742
3847
  }
3743
3848
  function getAgentExtensions(agentId) {
3744
3849
  if (!agentId) return null;
3745
- if (cache.has(agentId)) return cache.get(agentId) ?? null;
3850
+ const fp = getRuleSourceFingerprint();
3851
+ const cached2 = cache.get(agentId);
3852
+ if (cached2 && cached2.fp === fp) return cached2.ext;
3746
3853
  let ext = null;
3747
3854
  try {
3748
3855
  if (hasRuntimeConfig()) {
@@ -3754,7 +3861,7 @@ function getAgentExtensions(agentId) {
3754
3861
  const agentsDir = resolveAgentsDir();
3755
3862
  const rulesFile = resolve4(agentsDir, agentId, "rules.json");
3756
3863
  if (!existsSync6(rulesFile)) {
3757
- cache.set(agentId, null);
3864
+ cache.set(agentId, { fp, ext: null });
3758
3865
  return null;
3759
3866
  }
3760
3867
  try {
@@ -3762,16 +3869,16 @@ function getAgentExtensions(agentId) {
3762
3869
  ext = JSON.parse(raw);
3763
3870
  if (!ext.agent_id) {
3764
3871
  console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
3765
- cache.set(agentId, null);
3872
+ cache.set(agentId, { fp, ext: null });
3766
3873
  return null;
3767
3874
  }
3768
3875
  } catch (e) {
3769
3876
  console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
3770
- cache.set(agentId, null);
3877
+ cache.set(agentId, { fp, ext: null });
3771
3878
  return null;
3772
3879
  }
3773
3880
  }
3774
- cache.set(agentId, ext);
3881
+ cache.set(agentId, { fp, ext });
3775
3882
  console.error(
3776
3883
  `[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills, ${ext.builtin_safe_action_types?.length ?? 0} builtin safe action types`
3777
3884
  );
@@ -3817,7 +3924,9 @@ function resolveScenariosDir() {
3817
3924
  }
3818
3925
  function getScenarioExtensions(scenarioId) {
3819
3926
  if (!scenarioId) return null;
3820
- if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
3927
+ const fp = getRuleSourceFingerprint();
3928
+ const cached2 = cache2.get(scenarioId);
3929
+ if (cached2 && cached2.fp === fp) return cached2.ext;
3821
3930
  let ext = null;
3822
3931
  let overrideHit = null;
3823
3932
  try {
@@ -3841,7 +3950,7 @@ function getScenarioExtensions(scenarioId) {
3841
3950
  );
3842
3951
  }
3843
3952
  if (!existsSync7(rulesFile)) {
3844
- cache2.set(scenarioId, null);
3953
+ cache2.set(scenarioId, { fp, ext: null });
3845
3954
  return null;
3846
3955
  }
3847
3956
  try {
@@ -3850,7 +3959,7 @@ function getScenarioExtensions(scenarioId) {
3850
3959
  const id = data.scenario_id || data.agent_id || "";
3851
3960
  if (!id) {
3852
3961
  console.warn(`[AgentMoss scenarios] rules.json \u7F3A\u5C11 scenario_id \u5B57\u6BB5: ${rulesFile}`);
3853
- cache2.set(scenarioId, null);
3962
+ cache2.set(scenarioId, { fp, ext: null });
3854
3963
  return null;
3855
3964
  }
3856
3965
  ext = {
@@ -3865,11 +3974,11 @@ function getScenarioExtensions(scenarioId) {
3865
3974
  };
3866
3975
  } catch (e) {
3867
3976
  console.error(`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} rules.json \u5931\u8D25:`, e.message);
3868
- cache2.set(scenarioId, null);
3977
+ cache2.set(scenarioId, { fp, ext: null });
3869
3978
  return null;
3870
3979
  }
3871
3980
  }
3872
- cache2.set(scenarioId, ext);
3981
+ cache2.set(scenarioId, { fp, ext });
3873
3982
  console.error(
3874
3983
  `[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths`
3875
3984
  );
@@ -7223,10 +7332,10 @@ async function runLLMAnalysis(input, previous) {
7223
7332
  const config = input.llmConfig?.apiKey ? { ...globalConfig, ...input.llmConfig } : globalConfig;
7224
7333
  const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
7225
7334
  const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
7226
- const cached = llmCache.get(cacheKey);
7227
- if (cached) {
7335
+ const cached2 = llmCache.get(cacheKey);
7336
+ if (cached2) {
7228
7337
  console.log("[AgentMoss] LLM cache hit");
7229
- return cached;
7338
+ return cached2;
7230
7339
  }
7231
7340
  const matchedSkills = matchSkills(input.aNext.actionType, input.aNext.actionDetail, input.reason);
7232
7341
  const templates = loadTemplates();
@@ -10814,12 +10923,12 @@ async function analyzeInner(input, mossLog) {
10814
10923
  }
10815
10924
  const cache3 = getDefaultCache();
10816
10925
  mossLog.startStep("step2_cache_lookup");
10817
- const cached = cache3.get(input.sessionId, input.promptSession);
10818
- if (cached !== null) {
10926
+ const cached2 = cache3.get(input.sessionId, input.promptSession);
10927
+ if (cached2 !== null) {
10819
10928
  mossLog.endStep("step2_cache_lookup", "\u7F13\u5B58\u547D\u4E2D");
10820
- savePolicyToFile(input.sessionId, cached);
10821
- const res = allowResult(cached, judgment);
10822
- mossLog.logResult("Allow", res.reason, cached);
10929
+ savePolicyToFile(input.sessionId, cached2);
10930
+ const res = allowResult(cached2, judgment);
10931
+ mossLog.logResult("Allow", res.reason, cached2);
10823
10932
  return res;
10824
10933
  }
10825
10934
  if (!isPolicyGenEnabled()) {
@@ -11578,6 +11687,7 @@ init_runtime_config();
11578
11687
  init_token_stats();
11579
11688
  init_rules_loader();
11580
11689
  init_asset_dir();
11690
+ init_rules_mode();
11581
11691
  import { Hono } from "hono";
11582
11692
  import { readFileSync as readFileSync18, existsSync as existsSync18, unlinkSync as unlinkSync4 } from "fs";
11583
11693
  import { join as join14, dirname as dirname12 } from "path";
@@ -11603,9 +11713,23 @@ function authMiddleware() {
11603
11713
  await next();
11604
11714
  };
11605
11715
  }
11716
+ function displayRuntimeConfig() {
11717
+ return isRuntimeConfigDisabled() ? generateSourceDefaults() : loadRuntimeConfig();
11718
+ }
11719
+ function builtinOnlyReadOnlyMiddleware() {
11720
+ return async (c, next) => {
11721
+ if (isRuntimeConfigDisabled() && !["GET", "HEAD", "OPTIONS"].includes(c.req.method)) {
11722
+ return c.json({
11723
+ detail: `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF1AConsole \u53EA\u8BFB\uFF0C\u4FEE\u6539\u4E0D\u4F1A\u4FDD\u5B58\u3002\u8BF7\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u5E76\u91CD\u542F\u670D\u52A1\u540E\u518D\u6539\u89C4\u5219\u3002`
11724
+ }, 403);
11725
+ }
11726
+ await next();
11727
+ };
11728
+ }
11606
11729
  function createConsoleRouter() {
11607
11730
  const console2 = new Hono();
11608
11731
  console2.use("/api/*", authMiddleware());
11732
+ console2.use("/api/*", builtinOnlyReadOnlyMiddleware());
11609
11733
  const serveIndex = (c) => {
11610
11734
  const indexFile = join14(STATIC_DIR, "index.html");
11611
11735
  if (existsSync18(indexFile)) {
@@ -11616,8 +11740,8 @@ function createConsoleRouter() {
11616
11740
  console2.get("/", serveIndex);
11617
11741
  console2.get("", serveIndex);
11618
11742
  console2.get("/api/layers", (c) => {
11619
- const rc = loadRuntimeConfig();
11620
- return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
11743
+ const rc = displayRuntimeConfig();
11744
+ return c.json({ layers: rc.layers, env_overrides: getEnvOverrides(), builtin_only: isBuiltinOnly(), rules_mode: getRulesMode() });
11621
11745
  });
11622
11746
  console2.put("/api/layers", async (c) => {
11623
11747
  const body = await c.req.json();
@@ -11652,7 +11776,7 @@ function createConsoleRouter() {
11652
11776
  const rules = mc.rules || [];
11653
11777
  let catEnabled = true;
11654
11778
  try {
11655
- const rt = loadRuntimeConfig();
11779
+ const rt = displayRuntimeConfig();
11656
11780
  catEnabled = rt.L2_2?.ltl_rules?.category_enabled !== false;
11657
11781
  } catch {
11658
11782
  }
@@ -11708,7 +11832,7 @@ function createConsoleRouter() {
11708
11832
  return rc;
11709
11833
  }
11710
11834
  console2.get("/api/rules", (c) => {
11711
- const rc = loadRuntimeConfig();
11835
+ const rc = displayRuntimeConfig();
11712
11836
  const layer = c.req.query("layer");
11713
11837
  const category = c.req.query("category");
11714
11838
  const out = {};
@@ -11773,8 +11897,8 @@ function createConsoleRouter() {
11773
11897
  return c.json(withL22View(rc));
11774
11898
  });
11775
11899
  console2.get("/api/skills", (c) => {
11776
- const rc = loadRuntimeConfig();
11777
- return c.json(rc.L3_skills);
11900
+ const rc = displayRuntimeConfig();
11901
+ return c.json(rc.L3_skills || {});
11778
11902
  });
11779
11903
  console2.put("/api/skills/enabled", async (c) => {
11780
11904
  const b = await c.req.json();
@@ -11806,7 +11930,10 @@ function createConsoleRouter() {
11806
11930
  }
11807
11931
  const userPath = join14(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
11808
11932
  const builtinDir = resolveAssetDir(__dirname10, "skills");
11809
- const candidates = [userPath, builtinDir ? join14(builtinDir, `${skillId}.md`) : ""].filter(Boolean);
11933
+ const candidates = [
11934
+ ...isRuntimeConfigDisabled() ? [] : [userPath],
11935
+ builtinDir ? join14(builtinDir, `${skillId}.md`) : ""
11936
+ ].filter(Boolean);
11810
11937
  for (const p of candidates) {
11811
11938
  if (existsSync18(p)) {
11812
11939
  return c.json({ skill_id: skillId, path: p, content: readFileSync18(p, "utf-8") });
@@ -11815,7 +11942,7 @@ function createConsoleRouter() {
11815
11942
  return c.json({ detail: `skill ${skillId} \u4E0D\u5B58\u5728` }, 404);
11816
11943
  });
11817
11944
  console2.get("/api/agents", (c) => {
11818
- const rc = loadRuntimeConfig();
11945
+ const rc = displayRuntimeConfig();
11819
11946
  return c.json({ agents: getAgentRules(rc) });
11820
11947
  });
11821
11948
  console2.put("/api/agents/rules/enabled", async (c) => {
@@ -11854,7 +11981,7 @@ function createConsoleRouter() {
11854
11981
  });
11855
11982
  const enabledScenarioIds = () => (process.env.AGENT_MOSS_SCENARIO || "").split(",").map((s) => s.trim()).filter(Boolean);
11856
11983
  console2.get("/api/scenarios", (c) => {
11857
- const all = getScenarioRules(loadRuntimeConfig());
11984
+ const all = getScenarioRules(displayRuntimeConfig());
11858
11985
  const enabled = enabledScenarioIds();
11859
11986
  if (!enabled.length) return c.json({ scenarios: {}, enabled: [] });
11860
11987
  const shown = {};
@@ -11896,8 +12023,8 @@ function createConsoleRouter() {
11896
12023
  return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
11897
12024
  });
11898
12025
  console2.get("/api/config", (c) => {
11899
- const rc = loadRuntimeConfig();
11900
- return c.json({ ...rc, L2_2: getL22View() });
12026
+ const rc = displayRuntimeConfig();
12027
+ return c.json({ ...rc, L2_2: getL22View(), builtin_only: isBuiltinOnly(), rules_mode: getRulesMode() });
11901
12028
  });
11902
12029
  console2.get("/api/env-overrides", (c) => {
11903
12030
  return c.json({ overrides: getEnvOverrides() });
@@ -11935,37 +12062,58 @@ function createConsoleRouter() {
11935
12062
 
11936
12063
  // src/server.ts
11937
12064
  init_runtime_config();
12065
+ init_rules_mode();
11938
12066
  init_rules_loader();
11939
12067
  function logDeploymentSelfCheck() {
11940
12068
  try {
12069
+ const mode = getRulesMode();
12070
+ const builtinOnly = isBuiltinOnly();
12071
+ const runtimeDisabled = isRuntimeConfigDisabled();
11941
12072
  console.log("================ AgentMoss \u90E8\u7F72\u81EA\u68C0 ================");
11942
- console.log(`runtime \u914D\u7F6E: ${getRuntimeConfigPath()}`);
11943
- const layerKeys = ["L1_heuristic", "L2_logic_rules", "L3_llm_analysis", "agent_rules_enabled", "scenario_rules_enabled"];
11944
- console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
11945
- try {
11946
- const dirs = rulesDirs().filter((d) => existsSync19(d));
11947
- if (dirs.length > 0) {
11948
- console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
11949
- }
11950
- const loaded = ruleFilesLoaded();
11951
- if (loaded.length > 0) {
11952
- const ov = loadOverrideRules();
11953
- const ex = loadExtraRules();
11954
- console.error(`\u26A0\uFE0F \u5DF2\u4ECE\u89C4\u5219\u76EE\u5F55\u52A0\u8F7D ${loaded.length} \u4E2A\u6587\u4EF6\uFF08\u6574\u4EFD\u66FF\u6362 / \u8FFD\u52A0\uFF0C\u5185\u7F6E\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4EEC\u4E0A\u9762\uFF09:`);
11955
- for (const f of loaded) console.error(` ${f}`);
11956
- const ovs = Object.entries(ov).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
11957
- if (ovs) console.error(` \u8986\u76D6\u7C7B\u522B: ${ovs}\uFF08**\u6574\u4EFD\u66FF\u6362**\uFF1A\u672A\u5217\u51FA\u7684\u5185\u7F6E\u89C4\u5219\u4E0D\u518D\u751F\u6548\uFF09`);
11958
- const exs = Object.entries(ex).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
11959
- if (exs) console.log(` \u8FFD\u52A0\u7C7B\u522B: ${exs}\uFF08\u5185\u7F6E\u4FDD\u7559\uFF0C\u8FFD\u52A0\u5728\u5176\u540E\u4E00\u8D77\u5224\u5B9A\uFF09`);
11960
- }
11961
- const ignored = ignoredRuleFiles();
11962
- if (ignored.length > 0) {
12073
+ if (runtimeDisabled) {
12074
+ console.log(`runtime \u914D\u7F6E: ${getRuntimeConfigPath()}\uFF08**\u5DF2\u5FFD\u7565**\uFF1A\u89C4\u5219\u6765\u6E90\u6A21\u5F0F=${mode}\uFF09`);
12075
+ if (builtinOnly) {
11963
12076
  console.error(
11964
- `\u26A0\uFE0F \u89C4\u5219\u76EE\u5F55\u91CC\u6709 ${ignored.length} \u4E2A json **\u4E0D\u4F1A\u88AB\u8BFB\u53D6**\uFF08\u53EA\u8BA4 ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09:`
12077
+ "\u89C4\u5219\u6765\u6E90: **\u4EC5\u5185\u7F6E** \u2014\u2014 \u4E0D\u8BFB runtime \u914D\u7F6E / Console \u81EA\u5B9A\u4E49 / \u89C4\u5219\u76EE\u5F55 / AGENT_MOSS_CUSTOM_RULES\uFF1B\u4E0D seed\u3001\u4E0D merge\u3001\u4E0D\u5199\u56DE\u3002"
11965
12078
  );
11966
- for (const f of ignored) console.error(` ${f}`);
12079
+ } else {
12080
+ console.error(
12081
+ "\u89C4\u5219\u6765\u6E90: **\u51B7\u542F\u52A8\uFF08cold\uFF09** \u2014\u2014 \u542F\u52A8\u65F6\u8BFB\u53D6\u5185\u7F6E\u89C4\u5219 + \u89C4\u5219\u76EE\u5F55 / AGENT_MOSS_CUSTOM_RULES / \u573A\u666F\uFF0C\u4E4B\u540E\u51BB\u7ED3\uFF1B\u4E0D\u8BFB runtime \u914D\u7F6E / Console\uFF0C\u4E0D seed\u3001\u4E0D merge\u3001\u4E0D\u5199\u56DE\u3002"
12082
+ );
12083
+ console.error("\u89C4\u5219\u76EE\u5F55\u5185\u5BB9\u6539\u52A8\u9700\u91CD\u542F\u670D\u52A1\u624D\u751F\u6548\u3002");
12084
+ }
12085
+ console.error("Console: \u53EA\u8BFB\uFF08\u89C4\u5219\u5199\u63A5\u53E3\u8FD4\u56DE 403\uFF09\uFF1B\u4FEE\u6539\u89C4\u5219\u9700\u91CD\u542F\u670D\u52A1\u5E76\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u3002");
12086
+ } else {
12087
+ console.log(`runtime \u914D\u7F6E: ${getRuntimeConfigPath()}`);
12088
+ }
12089
+ const layerKeys = ["L1_heuristic", "L2_logic_rules", "L3_llm_analysis", "agent_rules_enabled", "scenario_rules_enabled"];
12090
+ console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
12091
+ if (!builtinOnly) {
12092
+ try {
12093
+ const dirs = rulesDirs().filter((d) => existsSync19(d));
12094
+ if (dirs.length > 0) {
12095
+ console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
12096
+ }
12097
+ const loaded = ruleFilesLoaded();
12098
+ if (loaded.length > 0) {
12099
+ const ov = loadOverrideRules();
12100
+ const ex = loadExtraRules();
12101
+ console.error(`\u26A0\uFE0F \u5DF2\u4ECE\u89C4\u5219\u76EE\u5F55\u52A0\u8F7D ${loaded.length} \u4E2A\u6587\u4EF6\uFF08\u6574\u4EFD\u66FF\u6362 / \u8FFD\u52A0\uFF0C\u5185\u7F6E\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4EEC\u4E0A\u9762\uFF09:`);
12102
+ for (const f of loaded) console.error(` ${f}`);
12103
+ const ovs = Object.entries(ov).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
12104
+ if (ovs) console.error(` \u8986\u76D6\u7C7B\u522B: ${ovs}\uFF08**\u6574\u4EFD\u66FF\u6362**\uFF1A\u672A\u5217\u51FA\u7684\u5185\u7F6E\u89C4\u5219\u4E0D\u518D\u751F\u6548\uFF09`);
12105
+ const exs = Object.entries(ex).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
12106
+ if (exs) console.log(` \u8FFD\u52A0\u7C7B\u522B: ${exs}\uFF08\u5185\u7F6E\u4FDD\u7559\uFF0C\u8FFD\u52A0\u5728\u5176\u540E\u4E00\u8D77\u5224\u5B9A\uFF09`);
12107
+ }
12108
+ const ignored = ignoredRuleFiles();
12109
+ if (ignored.length > 0) {
12110
+ console.error(
12111
+ `\u26A0\uFE0F \u89C4\u5219\u76EE\u5F55\u91CC\u6709 ${ignored.length} \u4E2A json **\u4E0D\u4F1A\u88AB\u8BFB\u53D6**\uFF08\u53EA\u8BA4 ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09:`
12112
+ );
12113
+ for (const f of ignored) console.error(` ${f}`);
12114
+ }
12115
+ } catch {
11967
12116
  }
11968
- } catch {
11969
12117
  }
11970
12118
  const scenarioRaw = (process.env.AGENT_MOSS_SCENARIO || "").trim();
11971
12119
  if (scenarioRaw) {
@@ -201,7 +201,10 @@ body {
201
201
  .ag-link:hover { text-decoration: underline; }
202
202
 
203
203
  /* Toast */
204
- .ag-toast { position: fixed; top: 20px; right: 20px; padding: 12px 20px; border-radius: 8px; font-size: 13px; z-index: 200; box-shadow: 0 4px 12px rgba(0,0,0,.15); animation: ag-slide-in .3s ease; }
204
+ .ag-toast { position: fixed; top: 20px; right: 20px; padding: 12px 20px; border-radius: 8px; font-size: 13px; z-index: 10000; box-shadow: 0 4px 12px rgba(0,0,0,.15); animation: ag-slide-in .3s ease; }
205
+ /* 仅内置规则模式的顶部横幅是 sticky + z-index 9999;toast 必须在其之上,
206
+ 并下移一个横幅高度,否则错误提示会被横幅挡住。 */
207
+ body.builtin-only-mode .ag-toast { top: calc(20px + var(--am-builtin-banner-height, 48px)); }
205
208
  .ag-toast-success { background: var(--ag-green); color: #fff; }
206
209
  .ag-toast-error { background: var(--ag-red); color: #fff; }
207
210
  .ag-toast-warning { background: var(--ag-orange); color: #fff; }
@@ -246,6 +249,10 @@ body {
246
249
  </style>
247
250
  </head>
248
251
  <body>
252
+ <div id="builtin-only-banner" style="display:none;background:#b45309;color:#fff;padding:6px 14px;font-size:12px;line-height:1.5;text-align:center;position:sticky;top:0;z-index:9999">
253
+ 仅内置规则模式:已忽略 runtime 配置与 Console 自定义,修改规则不会生效。需重启服务并移除 <code>AGENT_MOSS_RULES_BUILTIN_ONLY</code> 后再改规则。<br>
254
+ Built-in rules only: runtime config and Console changes are ignored. Restart without <code>AGENT_MOSS_RULES_BUILTIN_ONLY</code> to edit rules.
255
+ </div>
249
256
  <div class="ag-wrap">
250
257
  <div class="ag-tabs" id="tabs">
251
258
  <div class="ag-tab active" data-page="overview">概览</div>
@@ -553,6 +560,24 @@ function agPrompt(title, opts = {}) {
553
560
  // ===== Pages =====
554
561
  async function refreshConfig() {
555
562
  state.config = await api('config');
563
+ // cold / builtin 模式:顶部显示只读横幅(写接口由后端统一 403)
564
+ const rulesMode = state.config.rules_mode || (state.config.builtin_only ? 'builtin' : 'hot');
565
+ if (rulesMode === 'cold' || rulesMode === 'builtin') {
566
+ const banner = el('builtin-only-banner');
567
+ if (banner) {
568
+ banner.innerHTML = rulesMode === 'cold'
569
+ ? '冷启动规则模式:runtime 配置与 Console 自定义已忽略;规则目录仅在启动时读取一次,修改规则目录需重启服务。<br>'
570
+ + 'Cold-start rules mode: runtime config and Console changes are ignored. Rule directories are loaded once at startup; restart to apply changes.'
571
+ : '仅内置规则模式:已忽略 runtime 配置与 Console 自定义,修改规则不会生效。需重启服务并移除 <code>AGENT_MOSS_RULES_BUILTIN_ONLY</code> 后再改规则。<br>'
572
+ + 'Built-in rules only: runtime config and Console changes are ignored. Restart without <code>AGENT_MOSS_RULES_BUILTIN_ONLY</code> to edit rules.';
573
+ banner.style.display = 'block';
574
+ // 记录横幅实际高度(窄屏会换行),让 toast 显示在横幅下方而不是被挡住
575
+ document.documentElement.style.setProperty(
576
+ '--am-builtin-banner-height', banner.offsetHeight + 'px'
577
+ );
578
+ document.body.classList.add('builtin-only-mode');
579
+ }
580
+ }
556
581
  state.envOverrides = await loadEnvOverrides();
557
582
  state.agentRules = await api('agents');
558
583
  // 场景定制规则:后端按 AGENT_MOSS_SCENARIO env 过滤(无 env 返回空 → 场景页显示"未启用"提示)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenhkl/agent-moss",
3
- "version": "0.16.12",
3
+ "version": "0.16.13",
4
4
  "description": "Multi-layer security analysis engine for AI agents (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",