@kenhkl/agent-moss 0.15.5 → 0.15.7

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
@@ -407,6 +407,9 @@ function isLiteralTextCommand(command) {
407
407
  return false;
408
408
  }
409
409
  }
410
+ if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
411
+ return false;
412
+ }
410
413
  return true;
411
414
  }
412
415
  function extractInlineCode(command) {
@@ -1307,22 +1310,45 @@ var init_logic_rules = __esm({
1307
1310
  }
1308
1311
  });
1309
1312
 
1313
+ // src/engine/asset-dir.ts
1314
+ import { existsSync as existsSync3 } from "fs";
1315
+ import { resolve as resolve2 } from "path";
1316
+ function assetDirCandidates(moduleDir, name) {
1317
+ return [
1318
+ resolve2(moduleDir, name),
1319
+ resolve2(moduleDir, "../../..", "agent_moss", name)
1320
+ ];
1321
+ }
1322
+ function resolveAssetDir(moduleDir, name) {
1323
+ for (const dir of assetDirCandidates(moduleDir, name)) {
1324
+ if (existsSync3(dir)) return dir;
1325
+ }
1326
+ return null;
1327
+ }
1328
+ var init_asset_dir = __esm({
1329
+ "src/engine/asset-dir.ts"() {
1330
+ "use strict";
1331
+ init_esm_shims();
1332
+ }
1333
+ });
1334
+
1310
1335
  // src/engine/skill-loader.ts
1311
- import { readFileSync as readFileSync3, readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
1312
- import { resolve as resolve2, dirname as dirname2 } from "path";
1336
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
1337
+ import { resolve as resolve3, dirname as dirname2 } from "path";
1313
1338
  import { fileURLToPath as fileURLToPath4 } from "url";
1314
1339
  function loadAllSkills() {
1315
1340
  if (cachedSkills) return cachedSkills;
1316
1341
  cachedSkills = {};
1317
- const dir = existsSync3(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
1318
- if (!existsSync3(dir)) {
1319
- console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR, "and fallback:", SKILLS_DIR_FALLBACK);
1342
+ const dir = resolveAssetDir(__dirname3, "skills");
1343
+ if (!dir) {
1344
+ const [primary, fallback] = assetDirCandidates(__dirname3, "skills");
1345
+ console.warn("[AgentMoss] Skills directory not found:", primary, "and fallback:", fallback);
1320
1346
  return cachedSkills;
1321
1347
  }
1322
1348
  const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
1323
1349
  for (const f of files) {
1324
1350
  const name = f.replace(".md", "");
1325
- cachedSkills[name] = readFileSync3(resolve2(dir, f), "utf-8");
1351
+ cachedSkills[name] = readFileSync3(resolve3(dir, f), "utf-8");
1326
1352
  }
1327
1353
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1328
1354
  return cachedSkills;
@@ -1376,16 +1402,15 @@ function formatSkillsForPrompt(skills) {
1376
1402
  ${s.skillContent}`
1377
1403
  ).join("\n\n---\n\n");
1378
1404
  }
1379
- var __filename3, __dirname3, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
1405
+ var __filename3, __dirname3, SKILL_KEYWORD_MAP, cachedSkills;
1380
1406
  var init_skill_loader = __esm({
1381
1407
  "src/engine/skill-loader.ts"() {
1382
1408
  "use strict";
1383
1409
  init_esm_shims();
1384
1410
  init_rules_loader();
1411
+ init_asset_dir();
1385
1412
  __filename3 = fileURLToPath4(import.meta.url);
1386
1413
  __dirname3 = dirname2(__filename3);
1387
- SKILLS_DIR = resolve2(__dirname3, "../skills");
1388
- SKILLS_DIR_FALLBACK = resolve2(__dirname3, "../../../agent_moss/skills");
1389
1414
  SKILL_KEYWORD_MAP = (() => {
1390
1415
  const map = {};
1391
1416
  const cats = loadL3();
@@ -2555,7 +2580,7 @@ var init_runtime_config = __esm({
2555
2580
 
2556
2581
  // src/agents/index.ts
2557
2582
  import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
2558
- import { resolve as resolve3, dirname as dirname4 } from "path";
2583
+ import { resolve as resolve4, dirname as dirname4 } from "path";
2559
2584
  import { fileURLToPath as fileURLToPath6 } from "url";
2560
2585
  function resolveAgentsDir() {
2561
2586
  if (existsSync5(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
@@ -2575,7 +2600,7 @@ function getAgentExtensions(agentId) {
2575
2600
  }
2576
2601
  if (ext === null) {
2577
2602
  const agentsDir = resolveAgentsDir();
2578
- const rulesFile = resolve3(agentsDir, agentId, "rules.json");
2603
+ const rulesFile = resolve4(agentsDir, agentId, "rules.json");
2579
2604
  if (!existsSync5(rulesFile)) {
2580
2605
  cache.set(agentId, null);
2581
2606
  return null;
@@ -2622,15 +2647,15 @@ var init_agents = __esm({
2622
2647
  init_esm_shims();
2623
2648
  __filename4 = fileURLToPath6(import.meta.url);
2624
2649
  __dirname5 = dirname4(__filename4);
2625
- AGENTS_DIR_PROD = resolve3(__dirname5, "agents");
2626
- AGENTS_DIR_DEV_FALLBACK = resolve3(__dirname5, "../../../agent_moss/agents");
2650
+ AGENTS_DIR_PROD = resolve4(__dirname5, "agents");
2651
+ AGENTS_DIR_DEV_FALLBACK = resolve4(__dirname5, "../../../agent_moss/agents");
2627
2652
  cache = /* @__PURE__ */ new Map();
2628
2653
  }
2629
2654
  });
2630
2655
 
2631
2656
  // src/scenarios/index.ts
2632
2657
  import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
2633
- import { resolve as resolve4, dirname as dirname5 } from "path";
2658
+ import { resolve as resolve5, dirname as dirname5 } from "path";
2634
2659
  import { fileURLToPath as fileURLToPath7 } from "url";
2635
2660
  function resolveScenariosDir() {
2636
2661
  if (existsSync6(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
@@ -2650,7 +2675,7 @@ function getScenarioExtensions(scenarioId) {
2650
2675
  }
2651
2676
  if (ext === null) {
2652
2677
  const scenariosDir = resolveScenariosDir();
2653
- const rulesFile = resolve4(scenariosDir, scenarioId, "rules.json");
2678
+ const rulesFile = resolve5(scenariosDir, scenarioId, "rules.json");
2654
2679
  if (!existsSync6(rulesFile)) {
2655
2680
  cache2.set(scenarioId, null);
2656
2681
  return null;
@@ -2703,8 +2728,8 @@ var init_scenarios = __esm({
2703
2728
  init_agents();
2704
2729
  __filename5 = fileURLToPath7(import.meta.url);
2705
2730
  __dirname6 = dirname5(__filename5);
2706
- SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
2707
- SCENARIOS_DIR_DEV_FALLBACK = resolve4(__dirname6, "../../../agent_moss/scenarios");
2731
+ SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
2732
+ SCENARIOS_DIR_DEV_FALLBACK = resolve5(__dirname6, "../../../agent_moss/scenarios");
2708
2733
  cache2 = /* @__PURE__ */ new Map();
2709
2734
  }
2710
2735
  });
@@ -2891,7 +2916,7 @@ function buildPathContext(actionDetail) {
2891
2916
  }
2892
2917
  function stripLiteralTextSegments(actionDetail) {
2893
2918
  const writeScriptExec = />\s*[/~]\S*\.(sh|py|pl|rb)\b/.test(actionDetail) && /(?:&&|\|\||;|\n|\|)\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)/.test(actionDetail);
2894
- const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)\b/.test(actionDetail);
2919
+ const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node|crontab|at)\b/.test(actionDetail);
2895
2920
  if (writeScriptExec || pipeToInterpreter) {
2896
2921
  return actionDetail;
2897
2922
  }
@@ -4032,13 +4057,13 @@ var require_backup = __commonJS({
4032
4057
  var runBackup = (backup, handler) => {
4033
4058
  let rate = 0;
4034
4059
  let useDefault = true;
4035
- return new Promise((resolve9, reject) => {
4060
+ return new Promise((resolve10, reject) => {
4036
4061
  setImmediate(function step() {
4037
4062
  try {
4038
4063
  const progress = backup.transfer(rate);
4039
4064
  if (!progress.remainingPages) {
4040
4065
  backup.close();
4041
- resolve9(progress);
4066
+ resolve10(progress);
4042
4067
  return;
4043
4068
  }
4044
4069
  if (useDefault) {
@@ -4517,25 +4542,25 @@ init_skill_loader();
4517
4542
 
4518
4543
  // src/engine/template-loader.ts
4519
4544
  init_esm_shims();
4545
+ init_asset_dir();
4520
4546
  import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
4521
- import { resolve as resolve5, dirname as dirname6 } from "path";
4547
+ import { resolve as resolve6, dirname as dirname6 } from "path";
4522
4548
  import { fileURLToPath as fileURLToPath8 } from "url";
4523
4549
  var __filename6 = fileURLToPath8(import.meta.url);
4524
4550
  var __dirname7 = dirname6(__filename6);
4525
- var TEMPLATES_DIR = resolve5(__dirname7, "../templates");
4526
- var TEMPLATES_DIR_FALLBACK = resolve5(__dirname7, "../../../agent_moss/templates");
4527
4551
  var TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
4528
4552
  var cachedTemplates = null;
4529
4553
  function loadTemplates() {
4530
4554
  if (cachedTemplates) return cachedTemplates;
4531
4555
  cachedTemplates = {};
4532
- const dir = existsSync7(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
4533
- if (!existsSync7(dir)) {
4534
- console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
4556
+ const dir = resolveAssetDir(__dirname7, "templates");
4557
+ if (!dir) {
4558
+ const [primary, fallback] = assetDirCandidates(__dirname7, "templates");
4559
+ console.warn("[AgentMoss] Templates directory not found:", primary, "and fallback:", fallback);
4535
4560
  return cachedTemplates;
4536
4561
  }
4537
4562
  for (const file of TEMPLATE_FILES) {
4538
- const filePath = resolve5(dir, file);
4563
+ const filePath = resolve6(dir, file);
4539
4564
  if (existsSync7(filePath)) {
4540
4565
  const key = file.replace(".txt", "").replace("_template", "");
4541
4566
  cachedTemplates[key] = readFileSync7(filePath, "utf-8");
@@ -4548,7 +4573,7 @@ function loadTemplates() {
4548
4573
  // src/engine/script-analyzer.ts
4549
4574
  init_esm_shims();
4550
4575
  import { readFileSync as readFileSync8 } from "fs";
4551
- import { resolve as resolve6, join as join4 } from "path";
4576
+ import { resolve as resolve7, join as join4 } from "path";
4552
4577
  var SUSPICIOUS_PATTERNS = [
4553
4578
  // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
4554
4579
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -4681,7 +4706,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4681
4706
  }
4682
4707
  function readScriptContent(scriptPath, maxLines = 500) {
4683
4708
  try {
4684
- const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
4709
+ const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve7(scriptPath);
4685
4710
  const raw = readFileSync8(expandedPath, "utf-8");
4686
4711
  const lines = raw.split("\n").slice(0, maxLines);
4687
4712
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -4738,7 +4763,7 @@ init_heuristic();
4738
4763
  init_logic_rules();
4739
4764
  init_inline_analyzer();
4740
4765
  import { statSync as statSync3, existsSync as existsSync9 } from "fs";
4741
- import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
4766
+ import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
4742
4767
  import { homedir as homedir4 } from "os";
4743
4768
 
4744
4769
  // src/engine/script-engine/tracker.ts
@@ -6441,7 +6466,7 @@ function resolvePath2(p) {
6441
6466
  if (expanded.startsWith("~/") || expanded === "~") {
6442
6467
  expanded = join6(homedir4(), expanded.slice(1));
6443
6468
  }
6444
- return resolve8(expanded);
6469
+ return resolve9(expanded);
6445
6470
  }
6446
6471
  function scanScriptChainWithL1(chain, input) {
6447
6472
  for (const node of chain.nodes) {
@@ -6817,7 +6842,7 @@ async function analyzeWithLLM(prompt, config) {
6817
6842
  }
6818
6843
  if (attempt < maxRetries) {
6819
6844
  console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
6820
- await new Promise((resolve9) => setTimeout(resolve9, retryCfg.retryInterval * 1e3));
6845
+ await new Promise((resolve10) => setTimeout(resolve10, retryCfg.retryInterval * 1e3));
6821
6846
  } else {
6822
6847
  clearTimeout(totalTimeoutTimer);
6823
6848
  console.error("[AgentMoss] LLM call failed after all retries:", err.message);
@@ -10046,12 +10071,12 @@ function createApp() {
10046
10071
  }
10047
10072
  async function findFreePort(startPort, host = "127.0.0.1", scan = 100) {
10048
10073
  for (let port = startPort; port < startPort + scan; port++) {
10049
- const free = await new Promise((resolve9) => {
10074
+ const free = await new Promise((resolve10) => {
10050
10075
  const server = net.createServer();
10051
10076
  server.listen(port, host, () => {
10052
- server.close(() => resolve9(true));
10077
+ server.close(() => resolve10(true));
10053
10078
  });
10054
- server.on("error", () => resolve9(false));
10079
+ server.on("error", () => resolve10(false));
10055
10080
  });
10056
10081
  if (free) {
10057
10082
  if (port !== startPort) {
@@ -10092,4 +10117,3 @@ export {
10092
10117
  //! P6 mc-ltl-rules LTL 模型检测
10093
10118
  //! P7 low-integrity-command-exec 低完整性命令执行
10094
10119
  //! P8 sensitive-data-aggregation 敏感数据聚合外传
10095
- //# sourceMappingURL=server.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenhkl/agent-moss",
3
- "version": "0.15.5",
3
+ "version": "0.15.7",
4
4
  "description": "Multi-layer security analysis engine for AI agents (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",
@@ -20,6 +20,7 @@
20
20
  },
21
21
  "files": [
22
22
  "dist",
23
+ "!dist/**/*.map",
23
24
  "README.md"
24
25
  ],
25
26
  "scripts": {
@@ -49,4 +50,4 @@
49
50
  "vitest": "^4.1.10"
50
51
  },
51
52
  "license": "MIT"
52
- }
53
+ }
@@ -1,105 +0,0 @@
1
- """三方 agent 定制化注册中心
2
-
3
- 按 `agent_id` 加载该 agent 的专属规则集(JSON 单一事实源)。
4
- 规则文件位置:`agent_moss/agents/<agent_id>/rules.json`
5
-
6
- Python 与 TypeScript 两端共享同一份 rules.json:
7
- - py: agent_moss/agents/__init__.py:get_agent_extensions
8
- - ts: ts/src/agents/index.ts:getAgentExtensions
9
-
10
- 使用:
11
- from agent_moss.agents import get_agent_extensions
12
- ext = get_agent_extensions("xiaoo")
13
- if ext:
14
- # 把 ext.extra_command_patterns 追加到通用规则之后
15
- ...
16
-
17
- 无 agent_id 或 rules.json 不存在时返回 None(通用模式)。
18
- """
19
-
20
- from pathlib import Path
21
- import threading
22
- import logging
23
-
24
- from .base import AgentExtensions, InjectionKeyword, CommandPattern, SensitivePath
25
-
26
- logger = logging.getLogger(__name__)
27
-
28
- _AGENTS_DIR = Path(__file__).parent
29
- _cache: dict[str, AgentExtensions | None] = {}
30
- _lock = threading.Lock()
31
-
32
-
33
- def get_agent_extensions(agent_id: str) -> AgentExtensions | None:
34
- """按 agent_id 加载该 agent 的专属规则集。
35
-
36
- Args:
37
- agent_id: 调用方 agent 标识,如 "xiaoo" / "opencode" / "opendesk"。
38
- 空字符串返回 None(通用模式)。
39
-
40
- Returns:
41
- AgentExtensions 实例,或 None(无 agent 专属规则)。
42
- """
43
- if not agent_id:
44
- return None
45
-
46
- # 双重检查锁定,避免并发重复 IO
47
- if agent_id in _cache:
48
- return _cache[agent_id]
49
-
50
- with _lock:
51
- if agent_id in _cache:
52
- return _cache[agent_id]
53
-
54
- # 统一存储:优先读 runtime_config 的 agent_rules 分区(用户可在 console
55
- # 查看/编辑/禁用/新增,享受与通用规则一致的合并升级)。回退 rules.json
56
- # (无 runtime_config 时,如直接起服务未 seed、或旧部署)。
57
- ext = None
58
- try:
59
- from ..infra.runtime_config import has_runtime_config, load_runtime_config, get_enabled_agent_extensions
60
- if has_runtime_config():
61
- ext = get_enabled_agent_extensions(load_runtime_config(), agent_id)
62
- except Exception:
63
- pass # runtime_config 不可用时回退 rules.json
64
-
65
- if ext is None:
66
- rules_file = _AGENTS_DIR / agent_id / "rules.json"
67
- if not rules_file.exists():
68
- logger.debug("agent %s 无 rules.json,走通用模式", agent_id)
69
- _cache[agent_id] = None
70
- return None
71
- try:
72
- ext = AgentExtensions.from_json(rules_file)
73
- except Exception as e:
74
- logger.error("加载 agent %s rules.json 失败: %s", agent_id, e)
75
- _cache[agent_id] = None
76
- return None
77
-
78
- _cache[agent_id] = ext
79
- logger.info(
80
- "加载 agent %s 定制规则: %d cmd patterns, %d injection keywords, "
81
- "%d sensitive paths, %d builtin safe skills, %d builtin safe action types",
82
- agent_id,
83
- len(ext.extra_command_patterns),
84
- len(ext.extra_injection_keywords),
85
- len(ext.extra_sensitive_paths),
86
- len(ext.builtin_safe_skills),
87
- len(ext.builtin_safe_action_types),
88
- )
89
- return ext
90
-
91
-
92
- def clear_agent_extensions_cache() -> None:
93
- """清空 agent 扩展缓存(用于测试或 rules.json 热加载)"""
94
- with _lock:
95
- _cache.clear()
96
-
97
-
98
- __all__ = [
99
- "AgentExtensions",
100
- "InjectionKeyword",
101
- "CommandPattern",
102
- "SensitivePath",
103
- "get_agent_extensions",
104
- "clear_agent_extensions_cache",
105
- ]
@@ -1,158 +0,0 @@
1
- """三方 agent 定制化扩展 — 数据载体
2
-
3
- `AgentExtensions` 描述某个宿主 agent(xiaoO / opencode / opendesk)在通用安全规则
4
- 之上需要追加的专属规则。规则以 JSON 单一事实源存储(agent_moss/agents/<id>/rules.json),
5
- Python 与 TypeScript 两端共享同一份文件。
6
-
7
- 设计原则:
8
- - 所有字段以 `extra_` 为前缀,表示"追加到通用规则之后",不覆盖通用规则。
9
- - 通用规则先匹配;命中即短路,避免重复检测。
10
- - agent 专属规则只做"通用规则没覆盖的宿主 agent 特有风险"。
11
-
12
- JSON schema(agent_moss/agents/<id>/rules.json):
13
- {
14
- "agent_id": "xiaoo",
15
- "description": "xiaoO 宿主 agent 专属安全规则",
16
- "extra_command_patterns": [CommandPattern...],
17
- "extra_injection_keywords": [InjectionKeyword...],
18
- "extra_sensitive_paths": [SensitivePath...],
19
- "builtin_safe_skills": ["xiaoo-guardian"],
20
- "builtin_safe_action_types": ["memory-", "todomgr-", "mailbox-getEmailList"],
21
- "extra_skills": []
22
- }
23
- """
24
-
25
- from dataclasses import dataclass, field
26
- from pathlib import Path
27
- import json
28
- import logging
29
-
30
- from agent_moss.profiles.base import CommandPattern, SensitivePath
31
-
32
- logger = logging.getLogger(__name__)
33
-
34
-
35
- @dataclass
36
- class InjectionKeyword:
37
- """Prompt 注入关键词条目"""
38
- keyword: str
39
- risk_level: str # low / medium / high / critical
40
- lang: str # en / zh
41
-
42
-
43
- @dataclass
44
- class AgentExtensions:
45
- """三方 agent 定制化扩展
46
-
47
- 由 `agent_moss.agents.get_agent_extensions(agent_id)` 按需加载。
48
- 缺失 rules.json 时返回 None(通用模式)。
49
- """
50
- agent_id: str
51
- description: str = ""
52
- extra_command_patterns: list[CommandPattern] = field(default_factory=list)
53
- extra_injection_keywords: list[InjectionKeyword] = field(default_factory=list)
54
- extra_sensitive_paths: list[SensitivePath] = field(default_factory=list)
55
- builtin_safe_skills: list[str] = field(default_factory=list)
56
- # agent 专属"完全安全操作"白名单(精确名或前缀,前缀以 '-' 结尾)→ 跳过 L2+L3。
57
- # 对齐 TS ts/src/agents/types.ts。前缀匹配(如 "memory-")走 startswith,精确名走 ==。
58
- builtin_safe_action_types: list[str] = field(default_factory=list)
59
- extra_skills: list[dict] = field(default_factory=list)
60
-
61
- @classmethod
62
- def merge(cls, *exts: "AgentExtensions | None") -> "AgentExtensions | None":
63
- """合并多个 AgentExtensions(如 agent 规则 + 场景规则),追加式拼接各 extra_* 列表。
64
-
65
- 全部为 None 时返回 None(通用模式)。非 None 时以第一个非空实例为骨架,
66
- 其余实例的 extra_* / builtin_safe_* 列表依次追加在后(不覆盖)。
67
- """
68
- non_null = [e for e in exts if e is not None]
69
- if not non_null:
70
- return None
71
- base = non_null[0]
72
- return cls(
73
- agent_id=base.agent_id,
74
- description=base.description,
75
- extra_command_patterns=[
76
- p for e in non_null for p in e.extra_command_patterns
77
- ],
78
- extra_injection_keywords=[
79
- k for e in non_null for k in e.extra_injection_keywords
80
- ],
81
- extra_sensitive_paths=[
82
- sp for e in non_null for sp in e.extra_sensitive_paths
83
- ],
84
- builtin_safe_skills=[
85
- s for e in non_null for s in e.builtin_safe_skills
86
- ],
87
- builtin_safe_action_types=[
88
- t for e in non_null for t in e.builtin_safe_action_types
89
- ],
90
- extra_skills=[
91
- sk for e in non_null for sk in e.extra_skills
92
- ],
93
- )
94
-
95
- @classmethod
96
- def from_json(cls, rules_file: Path, id_key: str = "agent_id") -> "AgentExtensions":
97
- """从 rules.json 加载 AgentExtensions。
98
-
99
- Args:
100
- rules_file: agent_moss/agents/<id>/rules.json 的绝对路径(场景规则传
101
- agent_moss/scenarios/<id>/rules.json,id_key="scenario_id")
102
- id_key: JSON 中的 id 字段名。agent 规则默认 "agent_id";
103
- 场景规则(agent_moss/scenarios/<id>/rules.json)用 "scenario_id"。
104
-
105
- Returns:
106
- AgentExtensions 实例
107
-
108
- Raises:
109
- ValueError: JSON 结构不合法
110
- """
111
- with open(rules_file, "r", encoding="utf-8") as f:
112
- data = json.load(f)
113
-
114
- agent_id = data.get(id_key, "")
115
- if not agent_id:
116
- raise ValueError(f"rules.json 缺少 {id_key} 字段: {rules_file}")
117
-
118
- # extra_command_patterns: dict → CommandPattern
119
- extra_cmd_patterns: list[CommandPattern] = []
120
- for p in data.get("extra_command_patterns", []):
121
- extra_cmd_patterns.append(CommandPattern(
122
- pattern=p["pattern"],
123
- risk_level=p["risk_level"],
124
- risk_type=p["risk_type"],
125
- reason=p["reason"],
126
- ))
127
-
128
- # extra_injection_keywords: dict → InjectionKeyword
129
- extra_inj_keywords: list[InjectionKeyword] = []
130
- for k in data.get("extra_injection_keywords", []):
131
- extra_inj_keywords.append(InjectionKeyword(
132
- keyword=k["keyword"],
133
- risk_level=k["risk_level"],
134
- lang=k.get("lang", "en"),
135
- ))
136
-
137
- # extra_sensitive_paths: dict → SensitivePath
138
- extra_sensitive_paths: list[SensitivePath] = []
139
- for sp in data.get("extra_sensitive_paths", []):
140
- extra_sensitive_paths.append(SensitivePath(
141
- path=sp["path"],
142
- risk_level=sp["risk_level"],
143
- desc=sp["desc"],
144
- credential=sp.get("credential", False),
145
- read_only=sp.get("read_only", False),
146
- deny_mode=sp.get("deny_mode", ""),
147
- ))
148
-
149
- return cls(
150
- agent_id=agent_id,
151
- description=data.get("description", ""),
152
- extra_command_patterns=extra_cmd_patterns,
153
- extra_injection_keywords=extra_inj_keywords,
154
- extra_sensitive_paths=extra_sensitive_paths,
155
- builtin_safe_skills=data.get("builtin_safe_skills", []),
156
- builtin_safe_action_types=data.get("builtin_safe_action_types", []),
157
- extra_skills=data.get("extra_skills", []),
158
- )