@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.
@@ -51,6 +51,8 @@ interface AnalysisInput {
51
51
  * 测试可 import 此常量直接断言"无 agentId 时白名单不含某 skill",避免走 L3
52
52
  * LLM 调用导致测试脆弱(依赖 LLM 401 fail_open 兜底)。 */
53
53
  declare const TRUSTED_SKILLS_BASE: Set<string>;
54
+ /** 供测试隔离:重置 brain 单例 */
55
+ declare function _resetBrainForTest(): void;
54
56
  /**
55
57
  * Three-layer defense coordinator.
56
58
  * Layer 1 → fast-pass check → Layer 2 → Layer 3.
@@ -58,4 +60,4 @@ declare const TRUSTED_SKILLS_BASE: Set<string>;
58
60
  */
59
61
  declare function runAnalysis(input: AnalysisInput): Promise<SecurityJudgment>;
60
62
 
61
- export { type AnalysisInput, TRUSTED_SKILLS_BASE, runAnalysis };
63
+ export { type AnalysisInput, TRUSTED_SKILLS_BASE, _resetBrainForTest, runAnalysis };
@@ -126,6 +126,7 @@ __export(config_exports, {
126
126
  getProviderHeaders: () => getProviderHeaders,
127
127
  getRetryConfig: () => getRetryConfig,
128
128
  getTimeoutConfig: () => getTimeoutConfig,
129
+ isBrainEnabled: () => isBrainEnabled,
129
130
  isHeuristicEnabled: () => isHeuristicEnabled,
130
131
  isLLMAvailable: () => isLLMAvailable,
131
132
  isLogicRulesEnabled: () => isLogicRulesEnabled,
@@ -245,11 +246,11 @@ function isLLMAvailable() {
245
246
  }
246
247
  async function probeLLMConnection(timeoutMs = 8e3) {
247
248
  if (!isLLMAvailable()) return false;
248
- const OpenAI2 = (await import("openai")).default;
249
+ const OpenAI3 = (await import("openai")).default;
249
250
  const config = getLLMConfig();
250
251
  const provider = detectProvider(config.baseUrl, config.provider);
251
252
  const extraHeaders = getProviderHeaders(provider);
252
- const client = new OpenAI2({
253
+ const client = new OpenAI3({
253
254
  baseURL: config.baseUrl,
254
255
  apiKey: config.apiKey,
255
256
  timeout: timeoutMs,
@@ -307,6 +308,9 @@ function getProviderHeaders(provider) {
307
308
  function isHeuristicEnabled() {
308
309
  return process.env.AGENT_MOSS_DISABLE_HEURISTIC !== "1";
309
310
  }
311
+ function isBrainEnabled() {
312
+ return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
313
+ }
310
314
  function isLogicRulesEnabled() {
311
315
  return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
312
316
  }
@@ -3930,6 +3934,533 @@ async function analyzeWithLLM(prompt, config) {
3930
3934
  // src/engine/coordinator.ts
3931
3935
  init_config();
3932
3936
  init_agents();
3937
+
3938
+ // src/brain/index.ts
3939
+ init_esm_shims();
3940
+
3941
+ // src/brain/rule-store.ts
3942
+ init_esm_shims();
3943
+ import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
3944
+ import { join as join5 } from "path";
3945
+ import { homedir as homedir3 } from "os";
3946
+ var DEFAULT_RULES_PATH = join5(homedir3(), ".agentmoss", "brain", "rules.json");
3947
+ var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
3948
+ var STATUS_DRAFT = "draft";
3949
+ var STATUS_APPROVED = "approved";
3950
+ var STATUS_ACTIVE = "active";
3951
+ var STATUS_DISPUTED = "disputed";
3952
+ var STATUS_STALE = "stale";
3953
+ var STATUS_ARCHIVED = "archived";
3954
+ var STATUS_REJECTED = "rejected";
3955
+ function getRulesPath() {
3956
+ const envPath = process.env[ENV_RULES_PATH];
3957
+ if (envPath) return envPath;
3958
+ return DEFAULT_RULES_PATH;
3959
+ }
3960
+ function uuid() {
3961
+ try {
3962
+ return crypto.randomUUID();
3963
+ } catch {
3964
+ return `rule_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
3965
+ }
3966
+ }
3967
+ var BrainRuleStore = class {
3968
+ path;
3969
+ data;
3970
+ constructor(path3) {
3971
+ this.path = path3 || getRulesPath();
3972
+ this.data = this.load();
3973
+ }
3974
+ load() {
3975
+ if (!existsSync9(this.path)) {
3976
+ return { version: 1, rules: [], pattern_candidates: [] };
3977
+ }
3978
+ try {
3979
+ const raw = JSON.parse(readFileSync10(this.path, "utf-8"));
3980
+ return {
3981
+ version: raw.version || 1,
3982
+ rules: raw.rules || [],
3983
+ pattern_candidates: raw.pattern_candidates || []
3984
+ };
3985
+ } catch {
3986
+ return { version: 1, rules: [], pattern_candidates: [] };
3987
+ }
3988
+ }
3989
+ save() {
3990
+ const dir = this.path.substring(0, this.path.lastIndexOf("/"));
3991
+ if (dir) mkdirSync3(dir, { recursive: true });
3992
+ writeFileSync3(this.path, JSON.stringify(this.data, null, 2), "utf-8");
3993
+ }
3994
+ addRule(rule) {
3995
+ const now = Date.now();
3996
+ const full = {
3997
+ id: rule.id || uuid(),
3998
+ layer: rule.layer,
3999
+ ruleType: rule.ruleType || "deny_pattern",
4000
+ pattern: rule.pattern,
4001
+ scope: rule.scope || "all",
4002
+ source: rule.source || "llm_extracted",
4003
+ sourceEventIds: rule.sourceEventIds || [],
4004
+ confidence: rule.confidence || 0,
4005
+ status: rule.status || STATUS_DRAFT,
4006
+ createdAt: rule.createdAt || now,
4007
+ lastTriggeredAt: rule.lastTriggeredAt || 0,
4008
+ triggerCount: rule.triggerCount || 0,
4009
+ disputedCount: rule.disputedCount || 0,
4010
+ explanation: rule.explanation || "",
4011
+ originNode: rule.originNode || "local"
4012
+ };
4013
+ this.data.rules.push(full);
4014
+ this.save();
4015
+ return full.id;
4016
+ }
4017
+ approveRule(ruleId) {
4018
+ const rule = this.getRule(ruleId);
4019
+ if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
4020
+ if (rule.status !== STATUS_DRAFT) throw new Error(`\u53EA\u6709 draft \u72B6\u6001\u53EF\u5BA1\u6838\uFF0C\u5F53\u524D: ${rule.status}`);
4021
+ rule.status = STATUS_ACTIVE;
4022
+ this.save();
4023
+ }
4024
+ rejectRule(ruleId) {
4025
+ const rule = this.getRule(ruleId);
4026
+ if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
4027
+ if (rule.status !== STATUS_DRAFT) throw new Error(`\u53EA\u6709 draft \u72B6\u6001\u53EF\u62D2\u7EDD\uFF0C\u5F53\u524D: ${rule.status}`);
4028
+ rule.status = STATUS_REJECTED;
4029
+ this.save();
4030
+ }
4031
+ getRule(ruleId) {
4032
+ return this.data.rules.find((r) => r.id === ruleId);
4033
+ }
4034
+ getActiveRules(layer) {
4035
+ return this.data.rules.filter((r) => r.status === STATUS_ACTIVE && (layer === void 0 || r.layer === layer));
4036
+ }
4037
+ getDraftRules() {
4038
+ return this.data.rules.filter((r) => r.status === STATUS_DRAFT).sort((a, b) => b.createdAt - a.createdAt);
4039
+ }
4040
+ recordTrigger(ruleId) {
4041
+ const rule = this.getRule(ruleId);
4042
+ if (!rule) return;
4043
+ rule.triggerCount += 1;
4044
+ rule.lastTriggeredAt = Date.now();
4045
+ this.save();
4046
+ }
4047
+ // --- pattern_candidates ---
4048
+ addPatternCandidate(pattern, layer, scope, sourceEventId, confidence) {
4049
+ const now = Date.now();
4050
+ const existing = this.data.pattern_candidates.find(
4051
+ (c) => c.pattern === pattern && c.layer === layer && c.scope === scope
4052
+ );
4053
+ if (existing) {
4054
+ if (existing.reviewStatus === "rejected") return 0;
4055
+ existing.occurrenceCount += 1;
4056
+ existing.lastSeenAt = now;
4057
+ existing.sourceEventIds.unshift(sourceEventId);
4058
+ this.save();
4059
+ return existing.occurrenceCount;
4060
+ }
4061
+ this.data.pattern_candidates.push({
4062
+ pattern,
4063
+ layer,
4064
+ scope,
4065
+ sourceEventIds: [sourceEventId],
4066
+ confidence,
4067
+ occurrenceCount: 1,
4068
+ firstSeenAt: now,
4069
+ lastSeenAt: now,
4070
+ reviewStatus: "pending",
4071
+ reviewReason: "",
4072
+ reviewedAt: 0
4073
+ });
4074
+ this.save();
4075
+ return 1;
4076
+ }
4077
+ getPatternCandidates(minOccurrences = 3) {
4078
+ return this.data.pattern_candidates.filter((c) => c.occurrenceCount >= minOccurrences && c.reviewStatus !== "rejected").sort((a, b) => b.occurrenceCount - a.occurrenceCount);
4079
+ }
4080
+ getPendingReviewCandidates(minOccurrences = 3) {
4081
+ return this.data.pattern_candidates.filter((c) => c.occurrenceCount >= minOccurrences && c.reviewStatus === "pending").sort((a, b) => b.occurrenceCount - a.occurrenceCount);
4082
+ }
4083
+ isExcludedPattern(pattern, layer, scope) {
4084
+ const c = this.data.pattern_candidates.find(
4085
+ (c2) => c2.pattern === pattern && c2.layer === layer && c2.scope === scope
4086
+ );
4087
+ return !!c && c.reviewStatus === "rejected";
4088
+ }
4089
+ setPatternReviewStatus(pattern, layer, scope, status, reason = "") {
4090
+ const c = this.data.pattern_candidates.find(
4091
+ (c2) => c2.pattern === pattern && c2.layer === layer && c2.scope === scope
4092
+ );
4093
+ if (c) {
4094
+ c.reviewStatus = status;
4095
+ c.reviewReason = reason;
4096
+ c.reviewedAt = Date.now();
4097
+ this.save();
4098
+ }
4099
+ }
4100
+ removePatternCandidate(pattern, layer, scope) {
4101
+ this.data.pattern_candidates = this.data.pattern_candidates.filter(
4102
+ (c) => !(c.pattern === pattern && c.layer === layer && c.scope === scope)
4103
+ );
4104
+ this.save();
4105
+ }
4106
+ getStats() {
4107
+ const count = (status) => this.data.rules.filter((r) => r.status === status).length;
4108
+ return {
4109
+ draft: count(STATUS_DRAFT),
4110
+ approved: count(STATUS_APPROVED),
4111
+ active: count(STATUS_ACTIVE),
4112
+ disputed: count(STATUS_DISPUTED),
4113
+ stale: count(STATUS_STALE),
4114
+ archived: count(STATUS_ARCHIVED),
4115
+ rejected: count(STATUS_REJECTED),
4116
+ total: this.data.rules.length,
4117
+ by_layer: {
4118
+ 1: this.data.rules.filter((r) => r.layer === 1 && r.status === STATUS_ACTIVE).length,
4119
+ 2: this.data.rules.filter((r) => r.layer === 2 && r.status === STATUS_ACTIVE).length
4120
+ },
4121
+ pattern_candidates: this.data.pattern_candidates.length
4122
+ };
4123
+ }
4124
+ };
4125
+
4126
+ // src/brain/pattern-extractor.ts
4127
+ init_esm_shims();
4128
+ var PatternExtractor = class {
4129
+ ruleStore;
4130
+ minConfidence;
4131
+ minOccurrences;
4132
+ builtinPatterns;
4133
+ reviewFn;
4134
+ constructor(ruleStore, opts) {
4135
+ this.ruleStore = ruleStore;
4136
+ this.minConfidence = opts?.minConfidence ?? 0.9;
4137
+ this.minOccurrences = opts?.minOccurrences ?? 3;
4138
+ this.builtinPatterns = opts?.builtinPatterns ?? [];
4139
+ this.reviewFn = opts?.review ?? null;
4140
+ }
4141
+ async extract(event, llmResult) {
4142
+ const result = { rulesCreated: 0, patternsRecorded: 0, skippedReason: "", deduped: [] };
4143
+ const confidence = llmResult.confidence ?? 0;
4144
+ const reason = llmResult.reason ?? "";
4145
+ const riskType = llmResult.riskType ?? "";
4146
+ const actionDetail = llmResult.actionDetail ?? event.actionDetail ?? "";
4147
+ const eventId = event.eventId ?? "";
4148
+ if (confidence < this.minConfidence) {
4149
+ result.skippedReason = `confidence ${confidence} < ${this.minConfidence}`;
4150
+ return result;
4151
+ }
4152
+ const patterns = this.extractPatterns(actionDetail, reason, riskType);
4153
+ if (patterns.length === 0) {
4154
+ result.skippedReason = "no extractable pattern";
4155
+ return result;
4156
+ }
4157
+ for (const p of patterns) {
4158
+ if (this.isCoveredByBuiltin(actionDetail)) {
4159
+ result.deduped.push(p.pattern);
4160
+ continue;
4161
+ }
4162
+ const count = this.ruleStore.addPatternCandidate(
4163
+ p.pattern,
4164
+ p.layer,
4165
+ p.scope,
4166
+ eventId,
4167
+ confidence
4168
+ );
4169
+ if (count === 0) {
4170
+ result.deduped.push(p.pattern);
4171
+ continue;
4172
+ }
4173
+ result.patternsRecorded += 1;
4174
+ if (count >= this.minOccurrences) {
4175
+ const candidates = this.ruleStore.getPatternCandidates(this.minOccurrences);
4176
+ const matched = candidates.find((c) => c.pattern === p.pattern && c.layer === p.layer);
4177
+ if (!matched) continue;
4178
+ if (this.reviewFn) {
4179
+ let review;
4180
+ try {
4181
+ review = await this.reviewFn({
4182
+ pattern: p.pattern,
4183
+ layer: p.layer,
4184
+ ruleType: p.ruleType,
4185
+ actionDetail,
4186
+ reason
4187
+ });
4188
+ } catch {
4189
+ review = null;
4190
+ }
4191
+ if (review === null) {
4192
+ continue;
4193
+ }
4194
+ if (!review.shouldKeep) {
4195
+ this.ruleStore.setPatternReviewStatus(
4196
+ p.pattern,
4197
+ p.layer,
4198
+ p.scope,
4199
+ "rejected",
4200
+ review.reason || "LLM \u5BA1\u6838\u62D2\u7EDD"
4201
+ );
4202
+ result.deduped.push(p.pattern);
4203
+ continue;
4204
+ }
4205
+ this.ruleStore.setPatternReviewStatus(
4206
+ p.pattern,
4207
+ p.layer,
4208
+ p.scope,
4209
+ "approved",
4210
+ review.reason || "LLM \u5BA1\u6838\u901A\u8FC7"
4211
+ );
4212
+ }
4213
+ this.ruleStore.addRule({
4214
+ layer: p.layer,
4215
+ ruleType: p.ruleType,
4216
+ pattern: p.pattern,
4217
+ scope: p.scope,
4218
+ source: "llm_extracted",
4219
+ sourceEventIds: eventId ? [eventId] : [],
4220
+ confidence: matched.confidence,
4221
+ status: STATUS_DRAFT,
4222
+ explanation: `\u4ECE ${count} \u6B21 LLM Deny \u4E2D\u63D0\u53D6(\u7ECF\u5BA1\u6838): ${reason.slice(0, 200)}`
4223
+ });
4224
+ this.ruleStore.removePatternCandidate(p.pattern, p.layer, p.scope);
4225
+ result.rulesCreated += 1;
4226
+ }
4227
+ }
4228
+ return result;
4229
+ }
4230
+ extractPatterns(actionDetail, reason, riskType) {
4231
+ const patterns = [];
4232
+ const detailLower = actionDetail.toLowerCase();
4233
+ const seen = /* @__PURE__ */ new Set();
4234
+ const sensitivePathRe = /(\/(?:etc|root)(?:\/[^\s;|&"'<>`$]*)?|(?:[^\s;|&"'<>`$]*)?\/\.ssh\/[^\s;|&"'<>`$]*|(?:[^\s;|&"'<>`$]*)?\/\.aws\/[^\s;|&"'<>`$]*|\S+\.(?:pem|key|p12|pfx|id_rsa|authorized_keys)\b)/;
4235
+ const m = detailLower.match(sensitivePathRe);
4236
+ if (m) {
4237
+ const gen = this.generalizePath(m[1]);
4238
+ if (!seen.has(gen)) {
4239
+ seen.add(gen);
4240
+ patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
4241
+ }
4242
+ }
4243
+ 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/;
4244
+ const cmdM = detailLower.match(dangerousCmdRe);
4245
+ if (cmdM) {
4246
+ const gen = this.generalizeCommand(cmdM[1]);
4247
+ const seenKey = `cmd:${gen}`;
4248
+ if (!seen.has(seenKey)) {
4249
+ seen.add(seenKey);
4250
+ patterns.push({ pattern: gen, layer: 1, scope: "execve", ruleType: "deny_command" });
4251
+ }
4252
+ }
4253
+ const reasonLower = (reason || "").toLowerCase();
4254
+ if (reasonLower.includes("inject") || reasonLower.includes("\u6570\u636E\u6CE8\u5165")) {
4255
+ patterns.push({
4256
+ pattern: "BEHAVIOR_CHAIN:external_read\u2192sensitive_write",
4257
+ layer: 2,
4258
+ scope: "all",
4259
+ ruleType: "deny_pattern"
4260
+ });
4261
+ }
4262
+ if (reasonLower.includes("reconnaissance") || reasonLower.includes("\u4FA6\u5BDF") || reasonLower.includes("\u63D0\u6743")) {
4263
+ patterns.push({
4264
+ pattern: "BEHAVIOR_CHAIN:multi_config_read\u2192privilege_action",
4265
+ layer: 2,
4266
+ scope: "all",
4267
+ ruleType: "deny_pattern"
4268
+ });
4269
+ }
4270
+ const pathPrefix = `(?:^|[\\s"'(=])`;
4271
+ const pathBody = "(\\/(?:[a-zA-Z0-9_.-]+\\/)+[a-zA-Z0-9_.-]+)";
4272
+ const pathSuffix = `(?=[\\s"')\uFF0C\u3002\uFF09\uFF1B;]|$)`;
4273
+ for (const src of [detailLower, reasonLower]) {
4274
+ const pathRe = new RegExp(pathPrefix + pathBody + pathSuffix, "g");
4275
+ let mm;
4276
+ while ((mm = pathRe.exec(src)) !== null) {
4277
+ const path3 = mm[1];
4278
+ if (path3.split("/").length < 3) continue;
4279
+ if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(path3)) continue;
4280
+ const gen = this.generalizePath(path3);
4281
+ if (seen.has(gen)) continue;
4282
+ seen.add(gen);
4283
+ patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
4284
+ }
4285
+ }
4286
+ return patterns;
4287
+ }
4288
+ isCoveredByBuiltin(actionDetail) {
4289
+ if (this.builtinPatterns.length === 0 || !actionDetail) return false;
4290
+ for (const pat of this.builtinPatterns) {
4291
+ try {
4292
+ if (new RegExp(pat, "i").test(actionDetail)) return true;
4293
+ } catch {
4294
+ continue;
4295
+ }
4296
+ }
4297
+ return false;
4298
+ }
4299
+ generalizePath(path3) {
4300
+ const p = path3.toLowerCase();
4301
+ if (/\/etc\/(shadow|passwd|sudoers|security|ssh)/.test(p)) {
4302
+ return "(?:^|[^a-z0-9_-])etc/(?:shadow|passwd|sudoers|security/|ssh/)";
4303
+ }
4304
+ if (p.includes("/etc/")) {
4305
+ return "(?:^|[^a-z0-9_-])etc/";
4306
+ }
4307
+ if (p.includes("/root/")) {
4308
+ return "(?:^|[^a-z0-9_-])root/\\.(?:ssh|gnupg|aws|config)/";
4309
+ }
4310
+ if (p.includes(".ssh/")) {
4311
+ return "(?:^|[^a-z0-9_-])[\\w./-]*/\\.ssh/";
4312
+ }
4313
+ if (p.includes(".aws/")) {
4314
+ return "(?:^|[^a-z0-9_-])[\\w./-]*/\\.aws/";
4315
+ }
4316
+ if (/\.(pem|key|p12|pfx|id_rsa|authorized_keys)\b/.test(p)) {
4317
+ return "[\\w./-]*\\.(?:pem|key|p12|pfx|id_rsa|authorized_keys)(?:$|[^a-z0-9])";
4318
+ }
4319
+ const escaped = p.replace(/^\//, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4320
+ return `(?:^|[^a-z0-9_-])${escaped}(?:/|\\b)`;
4321
+ }
4322
+ generalizeCommand(cmd) {
4323
+ if (cmd.includes("rm") && cmd.includes("r") && cmd.includes("f")) {
4324
+ return "rm\\s+(?:-[a-zA-Z]*r[a-zA-Z]*f|--recursive)\\s+/";
4325
+ }
4326
+ if (cmd.includes("chmod")) return "chmod\\s+777";
4327
+ if (cmd.includes("curl") || cmd.includes("wget")) {
4328
+ return "(?:curl|wget)\\s+[^|]*\\|\\s*(?:ba)?sh";
4329
+ }
4330
+ if (cmd.includes("mkfs")) return "mkfs\\.";
4331
+ if (cmd.includes("dd")) return "dd\\s+if=";
4332
+ return cmd.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4333
+ }
4334
+ };
4335
+
4336
+ // src/brain/reviewer.ts
4337
+ init_esm_shims();
4338
+ init_config();
4339
+ import OpenAI2 from "openai";
4340
+ 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
4341
+
4342
+ ## \u63D0\u53D6\u7684\u6A21\u5F0F\uFF08\u5019\u9009\uFF09
4343
+ - \u6A21\u5F0F: {pattern}
4344
+ - \u6240\u5C5E\u5C42: Layer {layer}\uFF081=\u7279\u5F81\u5339\u914D\uFF0C2=\u884C\u4E3A\u94FE\uFF09
4345
+ - \u7C7B\u578B: {ruleType}
4346
+
4347
+ ## \u539F\u59CB\u88AB\u62D2\u7EDD\u7684\u64CD\u4F5C
4348
+ {actionDetail}
4349
+
4350
+ ## LLM \u5F53\u65F6\u7684\u62D2\u7EDD\u7406\u7531
4351
+ {reason}
4352
+
4353
+ ## \u5224\u65AD\u51C6\u5219
4354
+ 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
4355
+ 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
4356
+ 3. **\u4E0E\u4E09\u5C42\u89C4\u5219\u91CD\u590D**\uFF1A\u662F\u5426\u5DF2\u88AB\u73B0\u6709 L1/L2 \u89C4\u5219\u8986\u76D6\uFF1F
4357
+ 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
4358
+
4359
+ ## \u8F93\u51FA JSON\uFF08\u53EA\u8F93\u51FA JSON\uFF0C\u65E0\u5176\u4ED6\u6587\u5B57\uFF09
4360
+ {"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"}`;
4361
+ function createBrainReviewer(timeoutMs = 1e4) {
4362
+ return async (opts) => {
4363
+ if (process.env.AGENT_MOSS_BRAIN_REVIEW === "0") return null;
4364
+ if (!isLLMAvailable()) return null;
4365
+ const config = getLLMConfig();
4366
+ const provider = detectProvider(config.baseUrl, config.provider);
4367
+ const extraHeaders = getProviderHeaders(provider);
4368
+ const client = new OpenAI2({
4369
+ baseURL: config.baseUrl,
4370
+ apiKey: config.apiKey,
4371
+ timeout: timeoutMs,
4372
+ maxRetries: 1,
4373
+ defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
4374
+ });
4375
+ 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));
4376
+ try {
4377
+ const response = await client.chat.completions.create({
4378
+ model: config.model,
4379
+ temperature: config.temperature || 0.1,
4380
+ max_tokens: 256,
4381
+ messages: [
4382
+ { 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" },
4383
+ { role: "user", content: prompt }
4384
+ ]
4385
+ });
4386
+ const content = response.choices[0]?.message?.content || "";
4387
+ const parsed = parseReview(content);
4388
+ return { shouldKeep: parsed.shouldKeep, reason: parsed.reason };
4389
+ } catch (e) {
4390
+ console.warn(`[AgentMoss] Brain \u5BA1\u6838 LLM \u8C03\u7528\u5931\u8D25\uFF08\u964D\u7EA7\u4FDD\u6301\u5019\u9009\uFF09: ${e.message}`);
4391
+ return null;
4392
+ }
4393
+ };
4394
+ }
4395
+ function parseReview(content) {
4396
+ let text = content.trim();
4397
+ if (text.startsWith("```")) {
4398
+ text = text.replace(/^```[a-zA-Z]*\n?/, "").replace(/\n?```$/, "");
4399
+ }
4400
+ const match = text.match(/\{[\s\S]*\}/);
4401
+ if (!match) throw new Error(`\u5BA1\u6838\u54CD\u5E94\u65E0 JSON \u5BF9\u8C61: ${text.slice(0, 200)}`);
4402
+ const data = JSON.parse(match[0]);
4403
+ if (!("should_keep" in data) && !("shouldKeep" in data)) {
4404
+ throw new Error(`\u5BA1\u6838\u54CD\u5E94\u7F3A should_keep: ${text.slice(0, 200)}`);
4405
+ }
4406
+ let shouldKeep = data.shouldKeep ?? data.should_keep;
4407
+ if (typeof shouldKeep === "string") {
4408
+ shouldKeep = ["true", "1", "\u662F", "yes"].includes(shouldKeep.trim().toLowerCase());
4409
+ }
4410
+ return {
4411
+ shouldKeep: !!shouldKeep,
4412
+ confidence: Number(data.confidence ?? 0) || 0,
4413
+ reason: String(data.reason ?? data.suggested_rule ?? "") || ""
4414
+ };
4415
+ }
4416
+
4417
+ // src/brain/index.ts
4418
+ function checkBrainRules(store, opts) {
4419
+ if (!store) return null;
4420
+ const rules = store.getActiveRules(opts.layer);
4421
+ if (rules.length === 0) return null;
4422
+ const combined = `${opts.actionType} ${opts.actionDetail}`;
4423
+ for (const rule of rules) {
4424
+ try {
4425
+ if (new RegExp(rule.pattern, "i").test(combined)) {
4426
+ if (opts.layer === 2 && opts.actionHistory) {
4427
+ if (!checkLayer2Context(rule.pattern, opts.actionHistory, opts.actionDetail)) {
4428
+ continue;
4429
+ }
4430
+ }
4431
+ store.recordTrigger(rule.id);
4432
+ return {
4433
+ ruleId: rule.id,
4434
+ pattern: rule.pattern,
4435
+ explanation: rule.explanation,
4436
+ confidence: rule.confidence
4437
+ };
4438
+ }
4439
+ } catch {
4440
+ continue;
4441
+ }
4442
+ }
4443
+ return null;
4444
+ }
4445
+ function checkLayer2Context(pattern, actionHistory, actionDetail) {
4446
+ if (pattern.startsWith("BEHAVIOR_CHAIN:")) {
4447
+ const chainType = pattern.replace("BEHAVIOR_CHAIN:", "");
4448
+ if (chainType === "external_read\u2192sensitive_write") {
4449
+ const hasExternalRead = actionHistory.slice(-5).some((h) => /curl|wget|http/i.test(h.actionDetail ?? ""));
4450
+ const isSensitiveWrite = /\.ssh|\.gnupg|authorized_keys|bashrc|profile/i.test(actionDetail);
4451
+ return hasExternalRead && isSensitiveWrite;
4452
+ }
4453
+ if (chainType === "multi_config_read\u2192privilege_action") {
4454
+ const configReads = actionHistory.slice(-5).filter((h) => /\/etc\/|config|\.conf|\.yaml|\.json/i.test(h.actionDetail ?? "")).length;
4455
+ const isPrivilege = /sudo|su |chmod|chown/i.test(actionDetail);
4456
+ return configReads >= 2 && isPrivilege;
4457
+ }
4458
+ return false;
4459
+ }
4460
+ return true;
4461
+ }
4462
+
4463
+ // src/engine/coordinator.ts
3933
4464
  var FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
3934
4465
  "ask_user_question",
3935
4466
  "glob",
@@ -4014,6 +4545,23 @@ function extractRawCommand(detail) {
4014
4545
  function matchesAny(cmd, patterns) {
4015
4546
  return patterns.some((p) => p.test(cmd));
4016
4547
  }
4548
+ var _brainStore = null;
4549
+ var _brainExtractor = null;
4550
+ var _brainInitialized = false;
4551
+ function getBrainStore() {
4552
+ if (!isBrainEnabled()) return null;
4553
+ if (!_brainInitialized) {
4554
+ _brainStore = new BrainRuleStore();
4555
+ _brainExtractor = new PatternExtractor(_brainStore, { review: createBrainReviewer() });
4556
+ _brainInitialized = true;
4557
+ }
4558
+ return _brainStore;
4559
+ }
4560
+ function _resetBrainForTest() {
4561
+ _brainStore = null;
4562
+ _brainExtractor = null;
4563
+ _brainInitialized = false;
4564
+ }
4017
4565
  async function runAnalysis(input) {
4018
4566
  const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
4019
4567
  const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
@@ -4046,6 +4594,25 @@ async function runAnalysis(input) {
4046
4594
  }
4047
4595
  }
4048
4596
  }
4597
+ const brainStore = getBrainStore();
4598
+ if (brainStore) {
4599
+ const brainL1 = checkBrainRules(brainStore, {
4600
+ layer: 1,
4601
+ actionType: input.aNext.actionType,
4602
+ actionDetail: input.aNext.actionDetail
4603
+ });
4604
+ if (brainL1) {
4605
+ return {
4606
+ allowed: false,
4607
+ reason: `[Brain L1] ${brainL1.explanation}`,
4608
+ riskLevel: "high",
4609
+ riskType: "brain_rule",
4610
+ confidence: Math.round(brainL1.confidence * 100),
4611
+ source: "brain_rule",
4612
+ violatedLayers: ["brain_L1"]
4613
+ };
4614
+ }
4615
+ }
4049
4616
  const actionType = input.aNext.actionType.toLowerCase();
4050
4617
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
4051
4618
  const lowerDetail = rawDetail.toLowerCase();
@@ -4171,6 +4738,25 @@ async function runAnalysis(input) {
4171
4738
  }
4172
4739
  }
4173
4740
  }
4741
+ if (brainStore) {
4742
+ const brainL2 = checkBrainRules(brainStore, {
4743
+ layer: 2,
4744
+ actionType: input.aNext.actionType,
4745
+ actionDetail: input.aNext.actionDetail,
4746
+ actionHistory: input.actionHistory
4747
+ });
4748
+ if (brainL2) {
4749
+ return {
4750
+ allowed: false,
4751
+ reason: `[Brain L2] ${brainL2.explanation}`,
4752
+ riskLevel: "high",
4753
+ riskType: "brain_rule",
4754
+ confidence: Math.round(brainL2.confidence * 100),
4755
+ source: "brain_rule",
4756
+ violatedLayers: ["brain_L2"]
4757
+ };
4758
+ }
4759
+ }
4174
4760
  if (skipLLM || !isLLMAvailable()) {
4175
4761
  return {
4176
4762
  allowed: true,
@@ -4237,10 +4823,25 @@ async function runAnalysis(input) {
4237
4823
  violatedLayers: ["3"]
4238
4824
  };
4239
4825
  }
4826
+ if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
4827
+ try {
4828
+ await _brainExtractor.extract(
4829
+ { actionDetail: input.aNext.actionDetail, eventId: "" },
4830
+ {
4831
+ confidence: llmResult.confidence / 100,
4832
+ reason: llmResult.reason || "",
4833
+ riskType: llmResult.riskType || "",
4834
+ actionDetail: input.aNext.actionDetail
4835
+ }
4836
+ );
4837
+ } catch {
4838
+ }
4839
+ }
4240
4840
  return llmResult;
4241
4841
  }
4242
4842
  export {
4243
4843
  TRUSTED_SKILLS_BASE,
4844
+ _resetBrainForTest,
4244
4845
  runAnalysis
4245
4846
  };
4246
4847
  //# sourceMappingURL=coordinator.js.map