@kenhkl/agent-moss 0.12.6 → 0.12.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
@@ -136,6 +136,7 @@ __export(config_exports, {
136
136
  getProviderHeaders: () => getProviderHeaders,
137
137
  getRetryConfig: () => getRetryConfig,
138
138
  getTimeoutConfig: () => getTimeoutConfig,
139
+ isBrainEnabled: () => isBrainEnabled,
139
140
  isHeuristicEnabled: () => isHeuristicEnabled,
140
141
  isLLMAvailable: () => isLLMAvailable,
141
142
  isLogicRulesEnabled: () => isLogicRulesEnabled,
@@ -255,11 +256,11 @@ function isLLMAvailable() {
255
256
  }
256
257
  async function probeLLMConnection(timeoutMs = 8e3) {
257
258
  if (!isLLMAvailable()) return false;
258
- const OpenAI2 = (await import("openai")).default;
259
+ const OpenAI3 = (await import("openai")).default;
259
260
  const config = getLLMConfig();
260
261
  const provider = detectProvider(config.baseUrl, config.provider);
261
262
  const extraHeaders = getProviderHeaders(provider);
262
- const client = new OpenAI2({
263
+ const client = new OpenAI3({
263
264
  baseURL: config.baseUrl,
264
265
  apiKey: config.apiKey,
265
266
  timeout: timeoutMs,
@@ -317,6 +318,9 @@ function getProviderHeaders(provider) {
317
318
  function isHeuristicEnabled() {
318
319
  return process.env.AGENT_MOSS_DISABLE_HEURISTIC !== "1";
319
320
  }
321
+ function isBrainEnabled() {
322
+ return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
323
+ }
320
324
  function isLogicRulesEnabled() {
321
325
  return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
322
326
  }
@@ -3037,7 +3041,7 @@ var require_bindings = __commonJS({
3037
3041
  var fs4 = __require("fs");
3038
3042
  var path3 = __require("path");
3039
3043
  var fileURLToPath9 = require_file_uri_to_path();
3040
- var join6 = path3.join;
3044
+ var join7 = path3.join;
3041
3045
  var dirname8 = path3.dirname;
3042
3046
  var exists = fs4.accessSync && function(path4) {
3043
3047
  try {
@@ -3097,7 +3101,7 @@ var require_bindings = __commonJS({
3097
3101
  var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
3098
3102
  var tries = [], i = 0, l = opts.try.length, n, b, err;
3099
3103
  for (; i < l; i++) {
3100
- n = join6.apply(
3104
+ n = join7.apply(
3101
3105
  null,
3102
3106
  opts.try[i].map(function(p) {
3103
3107
  return opts[p] || p;
@@ -3158,7 +3162,7 @@ var require_bindings = __commonJS({
3158
3162
  if (dir === ".") {
3159
3163
  dir = process.cwd();
3160
3164
  }
3161
- if (exists(join6(dir, "package.json")) || exists(join6(dir, "node_modules"))) {
3165
+ if (exists(join7(dir, "package.json")) || exists(join7(dir, "node_modules"))) {
3162
3166
  return dir;
3163
3167
  }
3164
3168
  if (prev === dir) {
@@ -3167,7 +3171,7 @@ var require_bindings = __commonJS({
3167
3171
  );
3168
3172
  }
3169
3173
  prev = dir;
3170
- dir = join6(dir, "..");
3174
+ dir = join7(dir, "..");
3171
3175
  }
3172
3176
  };
3173
3177
  }
@@ -4791,6 +4795,533 @@ async function analyzeWithLLM(prompt, config) {
4791
4795
  // src/engine/coordinator.ts
4792
4796
  init_config();
4793
4797
  init_agents();
4798
+
4799
+ // src/brain/index.ts
4800
+ init_esm_shims();
4801
+
4802
+ // src/brain/rule-store.ts
4803
+ init_esm_shims();
4804
+ import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
4805
+ import { join as join5 } from "path";
4806
+ import { homedir as homedir3 } from "os";
4807
+ var DEFAULT_RULES_PATH = join5(homedir3(), ".agentmoss", "brain", "rules.json");
4808
+ var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
4809
+ var STATUS_DRAFT = "draft";
4810
+ var STATUS_APPROVED = "approved";
4811
+ var STATUS_ACTIVE = "active";
4812
+ var STATUS_DISPUTED = "disputed";
4813
+ var STATUS_STALE = "stale";
4814
+ var STATUS_ARCHIVED = "archived";
4815
+ var STATUS_REJECTED = "rejected";
4816
+ function getRulesPath() {
4817
+ const envPath = process.env[ENV_RULES_PATH];
4818
+ if (envPath) return envPath;
4819
+ return DEFAULT_RULES_PATH;
4820
+ }
4821
+ function uuid() {
4822
+ try {
4823
+ return crypto.randomUUID();
4824
+ } catch {
4825
+ return `rule_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
4826
+ }
4827
+ }
4828
+ var BrainRuleStore = class {
4829
+ path;
4830
+ data;
4831
+ constructor(path3) {
4832
+ this.path = path3 || getRulesPath();
4833
+ this.data = this.load();
4834
+ }
4835
+ load() {
4836
+ if (!existsSync9(this.path)) {
4837
+ return { version: 1, rules: [], pattern_candidates: [] };
4838
+ }
4839
+ try {
4840
+ const raw = JSON.parse(readFileSync10(this.path, "utf-8"));
4841
+ return {
4842
+ version: raw.version || 1,
4843
+ rules: raw.rules || [],
4844
+ pattern_candidates: raw.pattern_candidates || []
4845
+ };
4846
+ } catch {
4847
+ return { version: 1, rules: [], pattern_candidates: [] };
4848
+ }
4849
+ }
4850
+ save() {
4851
+ const dir = this.path.substring(0, this.path.lastIndexOf("/"));
4852
+ if (dir) mkdirSync3(dir, { recursive: true });
4853
+ writeFileSync3(this.path, JSON.stringify(this.data, null, 2), "utf-8");
4854
+ }
4855
+ addRule(rule) {
4856
+ const now = Date.now();
4857
+ const full = {
4858
+ id: rule.id || uuid(),
4859
+ layer: rule.layer,
4860
+ ruleType: rule.ruleType || "deny_pattern",
4861
+ pattern: rule.pattern,
4862
+ scope: rule.scope || "all",
4863
+ source: rule.source || "llm_extracted",
4864
+ sourceEventIds: rule.sourceEventIds || [],
4865
+ confidence: rule.confidence || 0,
4866
+ status: rule.status || STATUS_DRAFT,
4867
+ createdAt: rule.createdAt || now,
4868
+ lastTriggeredAt: rule.lastTriggeredAt || 0,
4869
+ triggerCount: rule.triggerCount || 0,
4870
+ disputedCount: rule.disputedCount || 0,
4871
+ explanation: rule.explanation || "",
4872
+ originNode: rule.originNode || "local"
4873
+ };
4874
+ this.data.rules.push(full);
4875
+ this.save();
4876
+ return full.id;
4877
+ }
4878
+ approveRule(ruleId) {
4879
+ const rule = this.getRule(ruleId);
4880
+ if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
4881
+ if (rule.status !== STATUS_DRAFT) throw new Error(`\u53EA\u6709 draft \u72B6\u6001\u53EF\u5BA1\u6838\uFF0C\u5F53\u524D: ${rule.status}`);
4882
+ rule.status = STATUS_ACTIVE;
4883
+ this.save();
4884
+ }
4885
+ rejectRule(ruleId) {
4886
+ const rule = this.getRule(ruleId);
4887
+ if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
4888
+ if (rule.status !== STATUS_DRAFT) throw new Error(`\u53EA\u6709 draft \u72B6\u6001\u53EF\u62D2\u7EDD\uFF0C\u5F53\u524D: ${rule.status}`);
4889
+ rule.status = STATUS_REJECTED;
4890
+ this.save();
4891
+ }
4892
+ getRule(ruleId) {
4893
+ return this.data.rules.find((r) => r.id === ruleId);
4894
+ }
4895
+ getActiveRules(layer) {
4896
+ return this.data.rules.filter((r) => r.status === STATUS_ACTIVE && (layer === void 0 || r.layer === layer));
4897
+ }
4898
+ getDraftRules() {
4899
+ return this.data.rules.filter((r) => r.status === STATUS_DRAFT).sort((a, b) => b.createdAt - a.createdAt);
4900
+ }
4901
+ recordTrigger(ruleId) {
4902
+ const rule = this.getRule(ruleId);
4903
+ if (!rule) return;
4904
+ rule.triggerCount += 1;
4905
+ rule.lastTriggeredAt = Date.now();
4906
+ this.save();
4907
+ }
4908
+ // --- pattern_candidates ---
4909
+ addPatternCandidate(pattern, layer, scope, sourceEventId, confidence) {
4910
+ const now = Date.now();
4911
+ const existing = this.data.pattern_candidates.find(
4912
+ (c) => c.pattern === pattern && c.layer === layer && c.scope === scope
4913
+ );
4914
+ if (existing) {
4915
+ if (existing.reviewStatus === "rejected") return 0;
4916
+ existing.occurrenceCount += 1;
4917
+ existing.lastSeenAt = now;
4918
+ existing.sourceEventIds.unshift(sourceEventId);
4919
+ this.save();
4920
+ return existing.occurrenceCount;
4921
+ }
4922
+ this.data.pattern_candidates.push({
4923
+ pattern,
4924
+ layer,
4925
+ scope,
4926
+ sourceEventIds: [sourceEventId],
4927
+ confidence,
4928
+ occurrenceCount: 1,
4929
+ firstSeenAt: now,
4930
+ lastSeenAt: now,
4931
+ reviewStatus: "pending",
4932
+ reviewReason: "",
4933
+ reviewedAt: 0
4934
+ });
4935
+ this.save();
4936
+ return 1;
4937
+ }
4938
+ getPatternCandidates(minOccurrences = 3) {
4939
+ return this.data.pattern_candidates.filter((c) => c.occurrenceCount >= minOccurrences && c.reviewStatus !== "rejected").sort((a, b) => b.occurrenceCount - a.occurrenceCount);
4940
+ }
4941
+ getPendingReviewCandidates(minOccurrences = 3) {
4942
+ return this.data.pattern_candidates.filter((c) => c.occurrenceCount >= minOccurrences && c.reviewStatus === "pending").sort((a, b) => b.occurrenceCount - a.occurrenceCount);
4943
+ }
4944
+ isExcludedPattern(pattern, layer, scope) {
4945
+ const c = this.data.pattern_candidates.find(
4946
+ (c2) => c2.pattern === pattern && c2.layer === layer && c2.scope === scope
4947
+ );
4948
+ return !!c && c.reviewStatus === "rejected";
4949
+ }
4950
+ setPatternReviewStatus(pattern, layer, scope, status, reason = "") {
4951
+ const c = this.data.pattern_candidates.find(
4952
+ (c2) => c2.pattern === pattern && c2.layer === layer && c2.scope === scope
4953
+ );
4954
+ if (c) {
4955
+ c.reviewStatus = status;
4956
+ c.reviewReason = reason;
4957
+ c.reviewedAt = Date.now();
4958
+ this.save();
4959
+ }
4960
+ }
4961
+ removePatternCandidate(pattern, layer, scope) {
4962
+ this.data.pattern_candidates = this.data.pattern_candidates.filter(
4963
+ (c) => !(c.pattern === pattern && c.layer === layer && c.scope === scope)
4964
+ );
4965
+ this.save();
4966
+ }
4967
+ getStats() {
4968
+ const count = (status) => this.data.rules.filter((r) => r.status === status).length;
4969
+ return {
4970
+ draft: count(STATUS_DRAFT),
4971
+ approved: count(STATUS_APPROVED),
4972
+ active: count(STATUS_ACTIVE),
4973
+ disputed: count(STATUS_DISPUTED),
4974
+ stale: count(STATUS_STALE),
4975
+ archived: count(STATUS_ARCHIVED),
4976
+ rejected: count(STATUS_REJECTED),
4977
+ total: this.data.rules.length,
4978
+ by_layer: {
4979
+ 1: this.data.rules.filter((r) => r.layer === 1 && r.status === STATUS_ACTIVE).length,
4980
+ 2: this.data.rules.filter((r) => r.layer === 2 && r.status === STATUS_ACTIVE).length
4981
+ },
4982
+ pattern_candidates: this.data.pattern_candidates.length
4983
+ };
4984
+ }
4985
+ };
4986
+
4987
+ // src/brain/pattern-extractor.ts
4988
+ init_esm_shims();
4989
+ var PatternExtractor = class {
4990
+ ruleStore;
4991
+ minConfidence;
4992
+ minOccurrences;
4993
+ builtinPatterns;
4994
+ reviewFn;
4995
+ constructor(ruleStore, opts) {
4996
+ this.ruleStore = ruleStore;
4997
+ this.minConfidence = opts?.minConfidence ?? 0.9;
4998
+ this.minOccurrences = opts?.minOccurrences ?? 3;
4999
+ this.builtinPatterns = opts?.builtinPatterns ?? [];
5000
+ this.reviewFn = opts?.review ?? null;
5001
+ }
5002
+ async extract(event, llmResult) {
5003
+ const result = { rulesCreated: 0, patternsRecorded: 0, skippedReason: "", deduped: [] };
5004
+ const confidence = llmResult.confidence ?? 0;
5005
+ const reason = llmResult.reason ?? "";
5006
+ const riskType = llmResult.riskType ?? "";
5007
+ const actionDetail = llmResult.actionDetail ?? event.actionDetail ?? "";
5008
+ const eventId = event.eventId ?? "";
5009
+ if (confidence < this.minConfidence) {
5010
+ result.skippedReason = `confidence ${confidence} < ${this.minConfidence}`;
5011
+ return result;
5012
+ }
5013
+ const patterns = this.extractPatterns(actionDetail, reason, riskType);
5014
+ if (patterns.length === 0) {
5015
+ result.skippedReason = "no extractable pattern";
5016
+ return result;
5017
+ }
5018
+ for (const p of patterns) {
5019
+ if (this.isCoveredByBuiltin(actionDetail)) {
5020
+ result.deduped.push(p.pattern);
5021
+ continue;
5022
+ }
5023
+ const count = this.ruleStore.addPatternCandidate(
5024
+ p.pattern,
5025
+ p.layer,
5026
+ p.scope,
5027
+ eventId,
5028
+ confidence
5029
+ );
5030
+ if (count === 0) {
5031
+ result.deduped.push(p.pattern);
5032
+ continue;
5033
+ }
5034
+ result.patternsRecorded += 1;
5035
+ if (count >= this.minOccurrences) {
5036
+ const candidates = this.ruleStore.getPatternCandidates(this.minOccurrences);
5037
+ const matched = candidates.find((c) => c.pattern === p.pattern && c.layer === p.layer);
5038
+ if (!matched) continue;
5039
+ if (this.reviewFn) {
5040
+ let review;
5041
+ try {
5042
+ review = await this.reviewFn({
5043
+ pattern: p.pattern,
5044
+ layer: p.layer,
5045
+ ruleType: p.ruleType,
5046
+ actionDetail,
5047
+ reason
5048
+ });
5049
+ } catch {
5050
+ review = null;
5051
+ }
5052
+ if (review === null) {
5053
+ continue;
5054
+ }
5055
+ if (!review.shouldKeep) {
5056
+ this.ruleStore.setPatternReviewStatus(
5057
+ p.pattern,
5058
+ p.layer,
5059
+ p.scope,
5060
+ "rejected",
5061
+ review.reason || "LLM \u5BA1\u6838\u62D2\u7EDD"
5062
+ );
5063
+ result.deduped.push(p.pattern);
5064
+ continue;
5065
+ }
5066
+ this.ruleStore.setPatternReviewStatus(
5067
+ p.pattern,
5068
+ p.layer,
5069
+ p.scope,
5070
+ "approved",
5071
+ review.reason || "LLM \u5BA1\u6838\u901A\u8FC7"
5072
+ );
5073
+ }
5074
+ this.ruleStore.addRule({
5075
+ layer: p.layer,
5076
+ ruleType: p.ruleType,
5077
+ pattern: p.pattern,
5078
+ scope: p.scope,
5079
+ source: "llm_extracted",
5080
+ sourceEventIds: eventId ? [eventId] : [],
5081
+ confidence: matched.confidence,
5082
+ status: STATUS_DRAFT,
5083
+ explanation: `\u4ECE ${count} \u6B21 LLM Deny \u4E2D\u63D0\u53D6(\u7ECF\u5BA1\u6838): ${reason.slice(0, 200)}`
5084
+ });
5085
+ this.ruleStore.removePatternCandidate(p.pattern, p.layer, p.scope);
5086
+ result.rulesCreated += 1;
5087
+ }
5088
+ }
5089
+ return result;
5090
+ }
5091
+ extractPatterns(actionDetail, reason, riskType) {
5092
+ const patterns = [];
5093
+ const detailLower = actionDetail.toLowerCase();
5094
+ const seen = /* @__PURE__ */ new Set();
5095
+ const sensitivePathRe = /(\/(?:etc|root)(?:\/[^\s;|&"'<>`$]*)?|(?:[^\s;|&"'<>`$]*)?\/\.ssh\/[^\s;|&"'<>`$]*|(?:[^\s;|&"'<>`$]*)?\/\.aws\/[^\s;|&"'<>`$]*|\S+\.(?:pem|key|p12|pfx|id_rsa|authorized_keys)\b)/;
5096
+ const m = detailLower.match(sensitivePathRe);
5097
+ if (m) {
5098
+ const gen = this.generalizePath(m[1]);
5099
+ if (!seen.has(gen)) {
5100
+ seen.add(gen);
5101
+ patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
5102
+ }
5103
+ }
5104
+ const dangerousCmdRe = /\b(rm\s+-[a-zA-Z]*r[a-zA-Z]*f|rm\s+--recursive|chmod\s+777|curl\s+[^|]*\|\s*(?:ba)?sh|wget\s+[^|]*\|\s*(?:ba)?sh|mkfs\.|dd\s+if=|>\s*\/dev\/sd)\b/;
5105
+ const cmdM = detailLower.match(dangerousCmdRe);
5106
+ if (cmdM) {
5107
+ const gen = this.generalizeCommand(cmdM[1]);
5108
+ const seenKey = `cmd:${gen}`;
5109
+ if (!seen.has(seenKey)) {
5110
+ seen.add(seenKey);
5111
+ patterns.push({ pattern: gen, layer: 1, scope: "execve", ruleType: "deny_command" });
5112
+ }
5113
+ }
5114
+ const reasonLower = (reason || "").toLowerCase();
5115
+ if (reasonLower.includes("inject") || reasonLower.includes("\u6570\u636E\u6CE8\u5165")) {
5116
+ patterns.push({
5117
+ pattern: "BEHAVIOR_CHAIN:external_read\u2192sensitive_write",
5118
+ layer: 2,
5119
+ scope: "all",
5120
+ ruleType: "deny_pattern"
5121
+ });
5122
+ }
5123
+ if (reasonLower.includes("reconnaissance") || reasonLower.includes("\u4FA6\u5BDF") || reasonLower.includes("\u63D0\u6743")) {
5124
+ patterns.push({
5125
+ pattern: "BEHAVIOR_CHAIN:multi_config_read\u2192privilege_action",
5126
+ layer: 2,
5127
+ scope: "all",
5128
+ ruleType: "deny_pattern"
5129
+ });
5130
+ }
5131
+ const pathPrefix = `(?:^|[\\s"'(=])`;
5132
+ const pathBody = "(\\/(?:[a-zA-Z0-9_.-]+\\/)+[a-zA-Z0-9_.-]+)";
5133
+ const pathSuffix = `(?=[\\s"')\uFF0C\u3002\uFF09\uFF1B;]|$)`;
5134
+ for (const src of [detailLower, reasonLower]) {
5135
+ const pathRe = new RegExp(pathPrefix + pathBody + pathSuffix, "g");
5136
+ let mm;
5137
+ while ((mm = pathRe.exec(src)) !== null) {
5138
+ const path3 = mm[1];
5139
+ if (path3.split("/").length < 3) continue;
5140
+ if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(path3)) continue;
5141
+ const gen = this.generalizePath(path3);
5142
+ if (seen.has(gen)) continue;
5143
+ seen.add(gen);
5144
+ patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
5145
+ }
5146
+ }
5147
+ return patterns;
5148
+ }
5149
+ isCoveredByBuiltin(actionDetail) {
5150
+ if (this.builtinPatterns.length === 0 || !actionDetail) return false;
5151
+ for (const pat of this.builtinPatterns) {
5152
+ try {
5153
+ if (new RegExp(pat, "i").test(actionDetail)) return true;
5154
+ } catch {
5155
+ continue;
5156
+ }
5157
+ }
5158
+ return false;
5159
+ }
5160
+ generalizePath(path3) {
5161
+ const p = path3.toLowerCase();
5162
+ if (/\/etc\/(shadow|passwd|sudoers|security|ssh)/.test(p)) {
5163
+ return "(?:^|[^a-z0-9_-])etc/(?:shadow|passwd|sudoers|security/|ssh/)";
5164
+ }
5165
+ if (p.includes("/etc/")) {
5166
+ return "(?:^|[^a-z0-9_-])etc/";
5167
+ }
5168
+ if (p.includes("/root/")) {
5169
+ return "(?:^|[^a-z0-9_-])root/\\.(?:ssh|gnupg|aws|config)/";
5170
+ }
5171
+ if (p.includes(".ssh/")) {
5172
+ return "(?:^|[^a-z0-9_-])[\\w./-]*/\\.ssh/";
5173
+ }
5174
+ if (p.includes(".aws/")) {
5175
+ return "(?:^|[^a-z0-9_-])[\\w./-]*/\\.aws/";
5176
+ }
5177
+ if (/\.(pem|key|p12|pfx|id_rsa|authorized_keys)\b/.test(p)) {
5178
+ return "[\\w./-]*\\.(?:pem|key|p12|pfx|id_rsa|authorized_keys)(?:$|[^a-z0-9])";
5179
+ }
5180
+ const escaped = p.replace(/^\//, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5181
+ return `(?:^|[^a-z0-9_-])${escaped}(?:/|\\b)`;
5182
+ }
5183
+ generalizeCommand(cmd) {
5184
+ if (cmd.includes("rm") && cmd.includes("r") && cmd.includes("f")) {
5185
+ return "rm\\s+(?:-[a-zA-Z]*r[a-zA-Z]*f|--recursive)\\s+/";
5186
+ }
5187
+ if (cmd.includes("chmod")) return "chmod\\s+777";
5188
+ if (cmd.includes("curl") || cmd.includes("wget")) {
5189
+ return "(?:curl|wget)\\s+[^|]*\\|\\s*(?:ba)?sh";
5190
+ }
5191
+ if (cmd.includes("mkfs")) return "mkfs\\.";
5192
+ if (cmd.includes("dd")) return "dd\\s+if=";
5193
+ return cmd.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5194
+ }
5195
+ };
5196
+
5197
+ // src/brain/reviewer.ts
5198
+ init_esm_shims();
5199
+ init_config();
5200
+ import OpenAI2 from "openai";
5201
+ var REVIEW_PROMPT = `\u4F60\u662F AgentMoss \u5B89\u5168\u89C4\u5219\u5BA1\u6838\u5668\u3002\u5224\u65AD\u4E0B\u5217"\u4ECE LLM \u62D2\u7EDD\u7ED3\u679C\u4E2D\u63D0\u53D6\u7684\u5B89\u5168\u6A21\u5F0F"\u662F\u5426\u503C\u5F97\u6C89\u6DC0\u4E3A\u672C\u5730\u89C4\u5219\u3002
5202
+
5203
+ ## \u63D0\u53D6\u7684\u6A21\u5F0F\uFF08\u5019\u9009\uFF09
5204
+ - \u6A21\u5F0F: {pattern}
5205
+ - \u6240\u5C5E\u5C42: Layer {layer}\uFF081=\u7279\u5F81\u5339\u914D\uFF0C2=\u884C\u4E3A\u94FE\uFF09
5206
+ - \u7C7B\u578B: {ruleType}
5207
+
5208
+ ## \u539F\u59CB\u88AB\u62D2\u7EDD\u7684\u64CD\u4F5C
5209
+ {actionDetail}
5210
+
5211
+ ## LLM \u5F53\u65F6\u7684\u62D2\u7EDD\u7406\u7531
5212
+ {reason}
5213
+
5214
+ ## \u5224\u65AD\u51C6\u5219
5215
+ 1. **\u662F\u5426\u8FC7\u9AD8\u6CDB\u5316**\uFF1A\u8FD9\u6761\u6A21\u5F0F\u662F\u5426\u4F1A\u8BEF\u62E6\u6B63\u5E38\u7684\u5408\u6CD5\u64CD\u4F5C\uFF1F
5216
+ 2. **\u662F\u5426\u7B26\u5408\u654F\u611F\u7279\u5F81**\uFF1A\u6307\u5411\u7684\u8DEF\u5F84/\u547D\u4EE4\u662F\u5426\u786E\u5B9E\u654F\u611F\uFF08\u51ED\u636E/\u7CFB\u7EDF\u6587\u4EF6/\u6076\u610F\u547D\u4EE4/\u5916\u4F20\uFF09\uFF1F
5217
+ 3. **\u4E0E\u4E09\u5C42\u89C4\u5219\u91CD\u590D**\uFF1A\u662F\u5426\u5DF2\u88AB\u73B0\u6709 L1/L2 \u89C4\u5219\u8986\u76D6\uFF1F
5218
+ 4. **\u662F\u4E0D\u662F\u771F\u5B9E\u653B\u51FB\u6A21\u5F0F**\uFF1A\u662F\u5426\u4EE3\u8868\u4E00\u79CD\u8FDE\u7EED\u7684\u653B\u51FB\u884C\u4E3A\u6A21\u5F0F\uFF1F
5219
+
5220
+ ## \u8F93\u51FA JSON\uFF08\u53EA\u8F93\u51FA JSON\uFF0C\u65E0\u5176\u4ED6\u6587\u5B57\uFF09
5221
+ {"should_keep": true/false, "confidence": 0-100, "reason": "\u7B80\u77ED\u7406\u7531\uFF08\u4E2D\u6587\uFF09", "suggested_rule": "\u82E5\u4FDD\u7559\u5219\u8F93\u51FA\u5EFA\u8BAE\u6B63\u5219\uFF0C\u5426\u5219\u7A7A\u4E32", "duplicate_of": "\u82E5\u91CD\u590D\u5219\u8BF4\u660E\u662F\u54EA\u4E2A\uFF0C\u5426\u5219\u7A7A\u4E32"}`;
5222
+ function createBrainReviewer(timeoutMs = 1e4) {
5223
+ return async (opts) => {
5224
+ if (process.env.AGENT_MOSS_BRAIN_REVIEW === "0") return null;
5225
+ if (!isLLMAvailable()) return null;
5226
+ const config = getLLMConfig();
5227
+ const provider = detectProvider(config.baseUrl, config.provider);
5228
+ const extraHeaders = getProviderHeaders(provider);
5229
+ const client = new OpenAI2({
5230
+ baseURL: config.baseUrl,
5231
+ apiKey: config.apiKey,
5232
+ timeout: timeoutMs,
5233
+ maxRetries: 1,
5234
+ defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
5235
+ });
5236
+ const prompt = REVIEW_PROMPT.replace("{pattern}", opts.pattern).replace("{layer}", String(opts.layer)).replace("{ruleType}", opts.ruleType).replace("{actionDetail}", (opts.actionDetail || "").slice(0, 400)).replace("{reason}", (opts.reason || "").slice(0, 400));
5237
+ try {
5238
+ const response = await client.chat.completions.create({
5239
+ model: config.model,
5240
+ temperature: config.temperature || 0.1,
5241
+ max_tokens: 256,
5242
+ messages: [
5243
+ { role: "system", content: "\u53EA\u8F93\u51FA\u7EAF JSON\uFF0C\u4E0D\u8981\u5305\u542B markdown \u6807\u8BB0\u3001\u4EE3\u7801\u5757\u6216\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\u3002" },
5244
+ { role: "user", content: prompt }
5245
+ ]
5246
+ });
5247
+ const content = response.choices[0]?.message?.content || "";
5248
+ const parsed = parseReview(content);
5249
+ return { shouldKeep: parsed.shouldKeep, reason: parsed.reason };
5250
+ } catch (e) {
5251
+ console.warn(`[AgentMoss] Brain \u5BA1\u6838 LLM \u8C03\u7528\u5931\u8D25\uFF08\u964D\u7EA7\u4FDD\u6301\u5019\u9009\uFF09: ${e.message}`);
5252
+ return null;
5253
+ }
5254
+ };
5255
+ }
5256
+ function parseReview(content) {
5257
+ let text = content.trim();
5258
+ if (text.startsWith("```")) {
5259
+ text = text.replace(/^```[a-zA-Z]*\n?/, "").replace(/\n?```$/, "");
5260
+ }
5261
+ const match = text.match(/\{[\s\S]*\}/);
5262
+ if (!match) throw new Error(`\u5BA1\u6838\u54CD\u5E94\u65E0 JSON \u5BF9\u8C61: ${text.slice(0, 200)}`);
5263
+ const data = JSON.parse(match[0]);
5264
+ if (!("should_keep" in data) && !("shouldKeep" in data)) {
5265
+ throw new Error(`\u5BA1\u6838\u54CD\u5E94\u7F3A should_keep: ${text.slice(0, 200)}`);
5266
+ }
5267
+ let shouldKeep = data.shouldKeep ?? data.should_keep;
5268
+ if (typeof shouldKeep === "string") {
5269
+ shouldKeep = ["true", "1", "\u662F", "yes"].includes(shouldKeep.trim().toLowerCase());
5270
+ }
5271
+ return {
5272
+ shouldKeep: !!shouldKeep,
5273
+ confidence: Number(data.confidence ?? 0) || 0,
5274
+ reason: String(data.reason ?? data.suggested_rule ?? "") || ""
5275
+ };
5276
+ }
5277
+
5278
+ // src/brain/index.ts
5279
+ function checkBrainRules(store, opts) {
5280
+ if (!store) return null;
5281
+ const rules = store.getActiveRules(opts.layer);
5282
+ if (rules.length === 0) return null;
5283
+ const combined = `${opts.actionType} ${opts.actionDetail}`;
5284
+ for (const rule of rules) {
5285
+ try {
5286
+ if (new RegExp(rule.pattern, "i").test(combined)) {
5287
+ if (opts.layer === 2 && opts.actionHistory) {
5288
+ if (!checkLayer2Context(rule.pattern, opts.actionHistory, opts.actionDetail)) {
5289
+ continue;
5290
+ }
5291
+ }
5292
+ store.recordTrigger(rule.id);
5293
+ return {
5294
+ ruleId: rule.id,
5295
+ pattern: rule.pattern,
5296
+ explanation: rule.explanation,
5297
+ confidence: rule.confidence
5298
+ };
5299
+ }
5300
+ } catch {
5301
+ continue;
5302
+ }
5303
+ }
5304
+ return null;
5305
+ }
5306
+ function checkLayer2Context(pattern, actionHistory, actionDetail) {
5307
+ if (pattern.startsWith("BEHAVIOR_CHAIN:")) {
5308
+ const chainType = pattern.replace("BEHAVIOR_CHAIN:", "");
5309
+ if (chainType === "external_read\u2192sensitive_write") {
5310
+ const hasExternalRead = actionHistory.slice(-5).some((h) => /curl|wget|http/i.test(h.actionDetail ?? ""));
5311
+ const isSensitiveWrite = /\.ssh|\.gnupg|authorized_keys|bashrc|profile/i.test(actionDetail);
5312
+ return hasExternalRead && isSensitiveWrite;
5313
+ }
5314
+ if (chainType === "multi_config_read\u2192privilege_action") {
5315
+ const configReads = actionHistory.slice(-5).filter((h) => /\/etc\/|config|\.conf|\.yaml|\.json/i.test(h.actionDetail ?? "")).length;
5316
+ const isPrivilege = /sudo|su |chmod|chown/i.test(actionDetail);
5317
+ return configReads >= 2 && isPrivilege;
5318
+ }
5319
+ return false;
5320
+ }
5321
+ return true;
5322
+ }
5323
+
5324
+ // src/engine/coordinator.ts
4794
5325
  var FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
4795
5326
  "ask_user_question",
4796
5327
  "glob",
@@ -4875,6 +5406,18 @@ function extractRawCommand(detail) {
4875
5406
  function matchesAny(cmd, patterns) {
4876
5407
  return patterns.some((p) => p.test(cmd));
4877
5408
  }
5409
+ var _brainStore = null;
5410
+ var _brainExtractor = null;
5411
+ var _brainInitialized = false;
5412
+ function getBrainStore() {
5413
+ if (!isBrainEnabled()) return null;
5414
+ if (!_brainInitialized) {
5415
+ _brainStore = new BrainRuleStore();
5416
+ _brainExtractor = new PatternExtractor(_brainStore, { review: createBrainReviewer() });
5417
+ _brainInitialized = true;
5418
+ }
5419
+ return _brainStore;
5420
+ }
4878
5421
  async function runAnalysis(input) {
4879
5422
  const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
4880
5423
  const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
@@ -4907,6 +5450,25 @@ async function runAnalysis(input) {
4907
5450
  }
4908
5451
  }
4909
5452
  }
5453
+ const brainStore = getBrainStore();
5454
+ if (brainStore) {
5455
+ const brainL1 = checkBrainRules(brainStore, {
5456
+ layer: 1,
5457
+ actionType: input.aNext.actionType,
5458
+ actionDetail: input.aNext.actionDetail
5459
+ });
5460
+ if (brainL1) {
5461
+ return {
5462
+ allowed: false,
5463
+ reason: `[Brain L1] ${brainL1.explanation}`,
5464
+ riskLevel: "high",
5465
+ riskType: "brain_rule",
5466
+ confidence: Math.round(brainL1.confidence * 100),
5467
+ source: "brain_rule",
5468
+ violatedLayers: ["brain_L1"]
5469
+ };
5470
+ }
5471
+ }
4910
5472
  const actionType = input.aNext.actionType.toLowerCase();
4911
5473
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
4912
5474
  const lowerDetail = rawDetail.toLowerCase();
@@ -5032,6 +5594,25 @@ async function runAnalysis(input) {
5032
5594
  }
5033
5595
  }
5034
5596
  }
5597
+ if (brainStore) {
5598
+ const brainL2 = checkBrainRules(brainStore, {
5599
+ layer: 2,
5600
+ actionType: input.aNext.actionType,
5601
+ actionDetail: input.aNext.actionDetail,
5602
+ actionHistory: input.actionHistory
5603
+ });
5604
+ if (brainL2) {
5605
+ return {
5606
+ allowed: false,
5607
+ reason: `[Brain L2] ${brainL2.explanation}`,
5608
+ riskLevel: "high",
5609
+ riskType: "brain_rule",
5610
+ confidence: Math.round(brainL2.confidence * 100),
5611
+ source: "brain_rule",
5612
+ violatedLayers: ["brain_L2"]
5613
+ };
5614
+ }
5615
+ }
5035
5616
  if (skipLLM || !isLLMAvailable()) {
5036
5617
  return {
5037
5618
  allowed: true,
@@ -5098,6 +5679,20 @@ async function runAnalysis(input) {
5098
5679
  violatedLayers: ["3"]
5099
5680
  };
5100
5681
  }
5682
+ if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
5683
+ try {
5684
+ await _brainExtractor.extract(
5685
+ { actionDetail: input.aNext.actionDetail, eventId: "" },
5686
+ {
5687
+ confidence: llmResult.confidence / 100,
5688
+ reason: llmResult.reason || "",
5689
+ riskType: llmResult.riskType || "",
5690
+ actionDetail: input.aNext.actionDetail
5691
+ }
5692
+ );
5693
+ } catch {
5694
+ }
5695
+ }
5101
5696
  return llmResult;
5102
5697
  }
5103
5698
 
@@ -5289,11 +5884,11 @@ init_esm_shims();
5289
5884
  init_runtime_config();
5290
5885
  init_token_stats();
5291
5886
  import { Hono } from "hono";
5292
- import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
5293
- import { join as join5, dirname as dirname7 } from "path";
5887
+ import { readFileSync as readFileSync11, existsSync as existsSync11 } from "fs";
5888
+ import { join as join6, dirname as dirname7 } from "path";
5294
5889
  import { fileURLToPath as fileURLToPath8 } from "url";
5295
5890
  var __dirname7 = dirname7(fileURLToPath8(import.meta.url));
5296
- var STATIC_DIR = join5(__dirname7, "static");
5891
+ var STATIC_DIR = join6(__dirname7, "static");
5297
5892
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
5298
5893
  function getConsoleToken() {
5299
5894
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -5317,9 +5912,9 @@ function createConsoleRouter() {
5317
5912
  const console2 = new Hono();
5318
5913
  console2.use("/api/*", authMiddleware());
5319
5914
  const serveIndex = (c) => {
5320
- const indexFile = join5(STATIC_DIR, "index.html");
5321
- if (existsSync10(indexFile)) {
5322
- return c.html(readFileSync10(indexFile, "utf-8"));
5915
+ const indexFile = join6(STATIC_DIR, "index.html");
5916
+ if (existsSync11(indexFile)) {
5917
+ return c.html(readFileSync11(indexFile, "utf-8"));
5323
5918
  }
5324
5919
  return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
5325
5920
  };
@@ -5410,13 +6005,13 @@ function createConsoleRouter() {
5410
6005
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
5411
6006
  return c.json({ error: "invalid skill_id" }, 400);
5412
6007
  }
5413
- const skillPath = join5(dirname7(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
5414
- if (!existsSync10(skillPath)) {
5415
- const builtinPath = join5(__dirname7, "..", "..", "agent_moss", "skills", `${skillId}.md`);
5416
- if (existsSync10(builtinPath)) return c.text(readFileSync10(builtinPath, "utf-8"));
6008
+ const skillPath = join6(dirname7(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
6009
+ if (!existsSync11(skillPath)) {
6010
+ const builtinPath = join6(__dirname7, "..", "..", "agent_moss", "skills", `${skillId}.md`);
6011
+ if (existsSync11(builtinPath)) return c.text(readFileSync11(builtinPath, "utf-8"));
5417
6012
  return c.json({ error: "skill not found" }, 404);
5418
6013
  }
5419
- return c.text(readFileSync10(skillPath, "utf-8"));
6014
+ return c.text(readFileSync11(skillPath, "utf-8"));
5420
6015
  });
5421
6016
  console2.get("/api/agents", (c) => {
5422
6017
  const rc = loadRuntimeConfig();