@kenhkl/agent-moss 0.16.7 → 0.16.12
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/cli.d.ts +19 -1
- package/dist/cli.js +2438 -459
- package/dist/data/rules_template/README.md +133 -0
- package/dist/data/rules_template/extra.json +14 -0
- package/dist/data/rules_template/override.json +15 -0
- package/dist/engine/coordinator.js +785 -185
- package/dist/server.d.ts +18 -1
- package/dist/server.js +1620 -271
- package/dist/skills/data_exfiltration_guard.md +5 -1
- package/dist/skills/script_execution_guard.md +1 -0
- package/package.json +2 -1
|
@@ -406,58 +406,57 @@ function parseDirEnv(envName) {
|
|
|
406
406
|
if (!raw) return [];
|
|
407
407
|
return raw.split(/[,;]/).map((s) => s.trim()).filter(Boolean).map((s) => resolve(s));
|
|
408
408
|
}
|
|
409
|
-
function
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
warnedOverride.add(path5);
|
|
415
|
-
console.warn(
|
|
416
|
-
`[AgentMoss] \u89C4\u5219\u6587\u4EF6\u88AB\u8986\u76D6\u76EE\u5F55\u8986\u76D6: ${path5}\uFF08\u5185\u7F6E\u9ED8\u8BA4\u88AB\u5FFD\u7565\uFF09\u3002\u8BE5\u6587\u4EF6\u7531\u90E8\u7F72\u4FA7\u81EA\u884C\u7EF4\u62A4 \u2014\u2014 **\u5185\u7F6E\u89C4\u5219\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4E0A\u9762**\uFF1B\u5220\u9664\u8BE5\u6587\u4EF6\uFF08\u6216\u6E05\u7A7A AGENT_MOSS_OVERRIDE_RULES_DIR\uFF09\u5373\u6062\u590D\u5185\u7F6E\u89C4\u5219\u3002\u82E5\u53EA\u60F3\u9010\u6761\u542F\u505C/\u589E\u5220\uFF0C\u8BF7\u6539\u7528 runtime config\uFF08Console\uFF09\u3002`
|
|
417
|
-
);
|
|
418
|
-
const missing = (EXPECTED_KEYS[name] || []).filter((k) => !(k in data));
|
|
419
|
-
if (missing.length > 0) {
|
|
420
|
-
console.warn(
|
|
421
|
-
`\u26A0\uFE0F [AgentMoss] \u8986\u76D6\u6587\u4EF6 ${path5} \u7F3A\u5C11\u9876\u5C42\u952E [${missing.join(", ")}] \u2014\u2014 \u5BF9\u5E94\u7C7B\u522B\u4F1A**\u4E00\u6761\u89C4\u5219\u90FD\u4E0D\u8DD1**\uFF08\u9759\u9ED8\u5931\u6548\uFF09\u3002\u82E5\u53EA\u60F3\u6539\u5176\u4E2D\u4E00\u7C7B\uFF0C\u8BF7\u5148\u62F7\u8D1D\u5B8C\u6574\u7684\u5185\u7F6E\u6587\u4EF6\u518D\u6539\u3002`
|
|
422
|
-
);
|
|
423
|
-
}
|
|
409
|
+
function rulesDirs() {
|
|
410
|
+
const dirs = [];
|
|
411
|
+
for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
|
|
412
|
+
dirs.push(resolve(dataRootDir(), "rules"));
|
|
413
|
+
return [...new Set(dirs)];
|
|
424
414
|
}
|
|
425
415
|
function findRulesFile(name) {
|
|
426
|
-
for (const dir of overrideRulesDirs()) {
|
|
427
|
-
const p = resolve(dir, name);
|
|
428
|
-
if (existsSync(p)) return p;
|
|
429
|
-
}
|
|
430
416
|
for (const dir of RULES_DIR_CANDS) {
|
|
431
417
|
const p = resolve(dir, name);
|
|
432
418
|
if (existsSync(p)) return p;
|
|
433
419
|
}
|
|
434
420
|
return null;
|
|
435
421
|
}
|
|
436
|
-
function isOverridePath(name, p) {
|
|
437
|
-
return overrideRulesDirs().some((dir) => resolve(dir, name) === p);
|
|
438
|
-
}
|
|
439
422
|
function loadJson(name) {
|
|
440
423
|
const p = findRulesFile(name);
|
|
441
424
|
if (!p) return {};
|
|
442
|
-
const isOverride = isOverridePath(name, p);
|
|
443
|
-
let data;
|
|
444
425
|
try {
|
|
445
|
-
|
|
426
|
+
return JSON.parse(readFileSync2(p, "utf-8")) || {};
|
|
446
427
|
} catch {
|
|
447
|
-
if (isOverride) warnOverride(name, p, {});
|
|
448
428
|
return {};
|
|
449
429
|
}
|
|
450
|
-
if (isOverride) warnOverride(name, p, data);
|
|
451
|
-
return data;
|
|
452
430
|
}
|
|
453
431
|
function resolveScenarioRulesFile(scenarioId) {
|
|
454
432
|
const rel = join2("scenarios", scenarioId, "rules.json");
|
|
455
|
-
for (const dir of
|
|
433
|
+
for (const dir of rulesDirs()) {
|
|
456
434
|
const p = resolve(dir, rel);
|
|
457
|
-
if (existsSync(p))
|
|
435
|
+
if (!existsSync(p)) continue;
|
|
436
|
+
let reason = "";
|
|
437
|
+
try {
|
|
438
|
+
reason = invalidScenarioRulesReason(JSON.parse(readFileSync2(p, "utf-8")) || {});
|
|
439
|
+
} catch (err) {
|
|
440
|
+
reason = `\u8BFB\u53D6/\u89E3\u6790\u5931\u8D25: ${String(err)}`;
|
|
441
|
+
}
|
|
442
|
+
if (reason) {
|
|
443
|
+
console.error(
|
|
444
|
+
`[AgentMoss] \u274C \u573A\u666F\u89C4\u5219\u8986\u76D6\u6587\u4EF6\u7ED3\u6784\u4E0D\u53EF\u7528\uFF0C**\u5DF2\u5FFD\u7565\u5E76\u56DE\u9000\u5185\u7F6E**: ${p}
|
|
445
|
+
\u539F\u56E0: ${reason}`
|
|
446
|
+
);
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
console.warn(`[AgentMoss] \u26A0\uFE0F \u573A\u666F ${scenarioId} \u89C4\u5219\u88AB\u8986\u76D6: ${p}\uFF08\u5185\u7F6E\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4E0A\u9762\uFF09`);
|
|
450
|
+
return p;
|
|
458
451
|
}
|
|
459
452
|
return null;
|
|
460
453
|
}
|
|
454
|
+
function invalidScenarioRulesReason(data) {
|
|
455
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
456
|
+
return `\u9876\u5C42\u5FC5\u987B\u662F JSON \u5BF9\u8C61\uFF0C\u5B9E\u9645\u662F ${Array.isArray(data) ? "array" : typeof data}`;
|
|
457
|
+
}
|
|
458
|
+
return SCENARIO_LIST_KEYS.filter((k) => k in data && !Array.isArray(data[k])).map((k) => `"${k}" \u5E94\u4E3A\u6570\u7EC4\uFF08\u5BF9\u8C61\u6570\u7EC4\uFF09\uFF0C\u5B9E\u9645\u662F ${typeof data[k]}`).join("\uFF1B");
|
|
459
|
+
}
|
|
461
460
|
function loadL1() {
|
|
462
461
|
return loadJson("l1_rules.json");
|
|
463
462
|
}
|
|
@@ -465,10 +464,40 @@ function loadL2() {
|
|
|
465
464
|
return loadJson("l2_rules.json");
|
|
466
465
|
}
|
|
467
466
|
function loadL3() {
|
|
468
|
-
|
|
467
|
+
const data = { ...loadJson("l3_rules.json") };
|
|
468
|
+
try {
|
|
469
|
+
const ov = loadOverrideRules().skills;
|
|
470
|
+
const ex = loadExtraRules().skills || {};
|
|
471
|
+
if (ov && typeof ov === "object") {
|
|
472
|
+
for (const [cat, skills] of Object.entries(ov)) data[cat] = { ...skills };
|
|
473
|
+
}
|
|
474
|
+
if (ex && typeof ex === "object") {
|
|
475
|
+
for (const [cat, skills] of Object.entries(ex)) data[cat] = { ...data[cat] || {}, ...skills };
|
|
476
|
+
}
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
return data;
|
|
480
|
+
}
|
|
481
|
+
function loadMCRaw() {
|
|
482
|
+
const mc = { ...loadJson("mc_rules.json") };
|
|
483
|
+
let rules = [...mc.rules || []];
|
|
484
|
+
try {
|
|
485
|
+
const ov = loadOverrideRules().ltl_rules;
|
|
486
|
+
const ex = loadExtraRules().ltl_rules || [];
|
|
487
|
+
if (Array.isArray(ov)) rules = [...ov];
|
|
488
|
+
if (Array.isArray(ex) && ex.length > 0) {
|
|
489
|
+
const byId = /* @__PURE__ */ new Map();
|
|
490
|
+
for (const r of rules) if (r && r.id) byId.set(r.id, r);
|
|
491
|
+
for (const r of ex) if (r && r.id) byId.set(r.id, r);
|
|
492
|
+
rules = [...byId.values()];
|
|
493
|
+
}
|
|
494
|
+
} catch {
|
|
495
|
+
}
|
|
496
|
+
mc.rules = rules;
|
|
497
|
+
return mc;
|
|
469
498
|
}
|
|
470
499
|
function loadMC() {
|
|
471
|
-
const mc =
|
|
500
|
+
const mc = loadMCRaw();
|
|
472
501
|
const rules = mc.rules || [];
|
|
473
502
|
try {
|
|
474
503
|
const rt = runtimeConfigBridge.current;
|
|
@@ -488,140 +517,278 @@ function loadMC() {
|
|
|
488
517
|
return mc;
|
|
489
518
|
}
|
|
490
519
|
function loadToolRegistry() {
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
function extraRulesDirs() {
|
|
494
|
-
return parseDirEnv("AGENT_MOSS_EXTRA_RULES_DIR");
|
|
495
|
-
}
|
|
496
|
-
function listJsonFilesDeep(dir) {
|
|
497
|
-
const out = [];
|
|
498
|
-
let entries;
|
|
520
|
+
const data = { ...loadJson("tool_registry.json") };
|
|
521
|
+
let tools = { ...data.tools || {} };
|
|
499
522
|
try {
|
|
500
|
-
|
|
523
|
+
const ov = loadOverrideRules().tools;
|
|
524
|
+
const ex = loadExtraRules().tools || {};
|
|
525
|
+
if (ov && typeof ov === "object") tools = { ...ov };
|
|
526
|
+
if (ex && typeof ex === "object") tools = { ...tools, ...ex };
|
|
501
527
|
} catch {
|
|
502
|
-
return out;
|
|
503
528
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
529
|
+
data.tools = tools;
|
|
530
|
+
return data;
|
|
531
|
+
}
|
|
532
|
+
function scanStructEntry(cat, value, path5) {
|
|
533
|
+
if (cat === "skills") {
|
|
534
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
535
|
+
console.error(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7684 skills \u5E94\u4E3A\u5BF9\u8C61\uFF08{\u7C7B\u522B: {\u6280\u80FD\u540D: [\u5173\u952E\u8BCD]}}\uFF09\u2014\u2014 \u5DF2\u8DF3\u8FC7\u3002`);
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
const out = {};
|
|
539
|
+
for (const [skCat, skills] of Object.entries(value)) {
|
|
540
|
+
if (!skills || typeof skills !== "object" || Array.isArray(skills)) {
|
|
541
|
+
console.error(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7684 skills.${skCat} \u5E94\u4E3A\u5BF9\u8C61\uFF08\u6280\u80FD\u540D \u2192 \u5173\u952E\u8BCD\u6570\u7EC4\uFF09\u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u7C7B\u522B\u3002`);
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
const keep = {};
|
|
545
|
+
for (const [name, kws] of Object.entries(skills)) {
|
|
546
|
+
if (Array.isArray(kws)) keep[name] = kws;
|
|
547
|
+
else console.warn(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7684 skills.${skCat}.${name} \u5173\u952E\u8BCD\u4E0D\u662F\u6570\u7EC4 \u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u6761\u3002`);
|
|
548
|
+
}
|
|
549
|
+
if (Object.keys(keep).length > 0) out[skCat] = keep;
|
|
511
550
|
}
|
|
512
|
-
|
|
513
|
-
else if (name.toLowerCase().endsWith(".json")) out.push(p);
|
|
551
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
514
552
|
}
|
|
515
|
-
|
|
553
|
+
if (cat === "ltl_rules") {
|
|
554
|
+
if (!Array.isArray(value)) {
|
|
555
|
+
console.error(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7684 ltl_rules \u5E94\u4E3A\u5BF9\u8C61\u6570\u7EC4 \u2014\u2014 \u5DF2\u8DF3\u8FC7\u3002`);
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
const keep = value.filter((r) => r && typeof r === "object" && r.id);
|
|
559
|
+
if (keep.length !== value.length) {
|
|
560
|
+
console.warn(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7684 ltl_rules \u6709\u6761\u76EE\u7F3A id \u6216\u4E0D\u662F\u5BF9\u8C61 \u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u6761\u3002`);
|
|
561
|
+
}
|
|
562
|
+
return keep.length > 0 ? keep : null;
|
|
563
|
+
}
|
|
564
|
+
if (cat === "tools") {
|
|
565
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
566
|
+
console.error(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7684 tools \u5E94\u4E3A\u5BF9\u8C61\uFF08{\u5DE5\u5177\u540D: {actionClass, ...}}\uFF09\u2014\u2014 \u5DF2\u8DF3\u8FC7\u3002`);
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
const keep = {};
|
|
570
|
+
for (const [k, v] of Object.entries(value)) {
|
|
571
|
+
if (v && typeof v === "object" && !Array.isArray(v)) keep[k] = v;
|
|
572
|
+
else console.warn(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7684 tools.${k} \u4E0D\u662F\u5BF9\u8C61 \u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u6761\u3002`);
|
|
573
|
+
}
|
|
574
|
+
return Object.keys(keep).length > 0 ? keep : null;
|
|
575
|
+
}
|
|
576
|
+
return null;
|
|
516
577
|
}
|
|
517
|
-
function
|
|
518
|
-
const
|
|
519
|
-
const
|
|
520
|
-
|
|
578
|
+
function scanRulesFile(path5, kind) {
|
|
579
|
+
const out = {};
|
|
580
|
+
const label = kind === "override" ? "\u8986\u76D6" : "\u8FFD\u52A0";
|
|
581
|
+
let parsed;
|
|
582
|
+
try {
|
|
583
|
+
parsed = JSON.parse(readFileSync2(path5, "utf-8"));
|
|
584
|
+
} catch (e) {
|
|
585
|
+
console.error(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u635F\u574F/\u4E0D\u53EF\u8BFB\uFF0C\u5DF2\u8DF3\u8FC7\uFF08${kind} \u89C4\u5219\u4E0D\u751F\u6548\uFF09: ${e.message}`);
|
|
586
|
+
return out;
|
|
587
|
+
}
|
|
521
588
|
const classifyFlat = (r) => {
|
|
522
589
|
for (const [keys, cat] of EXTRA_FLAT_FIELD) {
|
|
523
590
|
if (r && typeof r === "object" && keys.every((k) => r[k])) return cat;
|
|
524
591
|
}
|
|
525
592
|
return null;
|
|
526
593
|
};
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
594
|
+
let pairs = [];
|
|
595
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
596
|
+
pairs = Object.entries(parsed);
|
|
597
|
+
} else if (Array.isArray(parsed)) {
|
|
598
|
+
let flatBad = 0;
|
|
599
|
+
for (const r of parsed) {
|
|
600
|
+
const cat = classifyFlat(r);
|
|
601
|
+
if (cat === null) {
|
|
602
|
+
flatBad += 1;
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
pairs.push([cat, [r]]);
|
|
606
|
+
}
|
|
607
|
+
if (flatBad > 0) {
|
|
608
|
+
console.warn(
|
|
609
|
+
`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u6709 ${flatBad} \u6761\u5E73\u94FA\u6761\u76EE\u65E0\u6CD5\u5F52\u7C7B\uFF08\u88F8 pattern \u4E0D\u5E26 risk_level/risk_type \u65F6\u533A\u5206\u4E0D\u4E86 L1 \u5371\u9669\u547D\u4EE4\u4E0E L2 \u6388\u6743\u7C7B\uFF09\u2014\u2014 \u8BF7\u6539\u7528\u5BF9\u8C61\u5F62\u5F0F\uFF1A{"\u7C7B\u522B\u952E": [\u6761\u76EE...]}\u3002`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
} else {
|
|
613
|
+
console.error(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u9876\u5C42\u5E94\u662F\u5BF9\u8C61\uFF08\u7C7B\u522B\u952E\u2192\u6761\u76EE\u6570\u7EC4\uFF09\u6216\u6570\u7EC4\uFF0C\u5DF2\u8DF3\u8FC7\u3002`);
|
|
614
|
+
return out;
|
|
615
|
+
}
|
|
616
|
+
for (const [cat, rules] of pairs) {
|
|
617
|
+
if (String(cat).startsWith("_")) continue;
|
|
618
|
+
if (STRUCT_KEYS.includes(cat)) {
|
|
619
|
+
const got = scanStructEntry(cat, rules, path5);
|
|
620
|
+
if (got !== null) out[cat] = got;
|
|
533
621
|
continue;
|
|
534
622
|
}
|
|
535
|
-
|
|
536
|
-
if (
|
|
537
|
-
pairs = Object.entries(parsed);
|
|
538
|
-
} else if (Array.isArray(parsed)) {
|
|
539
|
-
let flatBad = 0;
|
|
540
|
-
for (const r of parsed) {
|
|
541
|
-
const cat = classifyFlat(r);
|
|
542
|
-
if (cat === null) {
|
|
543
|
-
flatBad += 1;
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
pairs.push([cat, [r]]);
|
|
547
|
-
}
|
|
548
|
-
if (flatBad > 0) {
|
|
549
|
-
console.warn(
|
|
550
|
-
`[AgentMoss] \u8FFD\u52A0\u89C4\u5219\u6587\u4EF6 ${p} \u6709 ${flatBad} \u6761\u5E73\u94FA\u6761\u76EE\u65E0\u6CD5\u5F52\u7C7B\uFF08\u88F8 pattern \u4E0D\u5E26 risk_level/risk_type \u65F6\u533A\u5206\u4E0D\u4E86 L1 \u5371\u9669\u547D\u4EE4\u4E0E L2 \u6388\u6743\u7C7B\uFF09\u2014\u2014 \u8BF7\u6539\u7528\u5BF9\u8C61\u5F62\u5F0F\uFF1A{"\u7C7B\u522B\u952E": [\u6761\u76EE...]}\u3002`
|
|
551
|
-
);
|
|
552
|
-
}
|
|
553
|
-
} else {
|
|
623
|
+
const required = EXTRA_CATEGORIES[cat];
|
|
624
|
+
if (!required) {
|
|
554
625
|
console.warn(
|
|
555
|
-
`[AgentMoss] \
|
|
626
|
+
`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u542B\u672A\u77E5\u7C7B\u522B\u952E "${cat}"\uFF08\u53EF\u7528\uFF1A${Object.keys(EXTRA_CATEGORIES).join(", ")}\uFF09\u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u7C7B\u522B\u3002`
|
|
627
|
+
);
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (rules && typeof rules === "object" && !Array.isArray(rules)) {
|
|
631
|
+
console.error(
|
|
632
|
+
`[AgentMoss] \u274C \u89C4\u5219\u6587\u4EF6 ${path5} \u7684\u7C7B\u522B "${cat}" \u662F**\u5BF9\u8C61**\uFF0C\u5E94\u4E3A**\u5BF9\u8C61\u6570\u7EC4** \u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u7C7B\u522B\u3002
|
|
633
|
+
\u6B63\u786E\u5199\u6CD5: {"${cat}": [{"pattern": "...", "risk_level": "high", ...}]}
|
|
634
|
+
\u4F60\u5199\u7684\u50CF\u662F runtime config \u7684\u683C\u5F0F\uFF08{"category_enabled": ..., "rules": [...]}\uFF09\u2014\u2014\u90A3\u662F ~/.config/agentmoss/agent_moss_runtime.json \u7684\u683C\u5F0F\uFF0C\u4E0D\u80FD\u76F4\u63A5\u5F53\u89C4\u5219\u6587\u4EF6\u7528\u3002`
|
|
556
635
|
);
|
|
557
636
|
continue;
|
|
558
637
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
638
|
+
if (!Array.isArray(rules)) {
|
|
639
|
+
console.warn(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7684\u7C7B\u522B "${cat}" \u5E94\u4E3A\u6570\u7EC4\uFF0C\u5B9E\u9645\u662F ${typeof rules} \u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u7C7B\u522B\u3002`);
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const items = [];
|
|
643
|
+
for (const r of rules) {
|
|
644
|
+
if (!r || typeof r !== "object" || !required.every((k) => r[k])) {
|
|
563
645
|
console.warn(
|
|
564
|
-
`[AgentMoss] \
|
|
646
|
+
`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7C7B\u522B ${cat} \u6709\u6761\u76EE\u7F3A\u5FC5\u9700\u5B57\u6BB5 [${required.join(", ")}] \u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u6761\u3002`
|
|
565
647
|
);
|
|
566
648
|
continue;
|
|
567
649
|
}
|
|
568
|
-
|
|
569
|
-
|
|
650
|
+
items.push(r);
|
|
651
|
+
}
|
|
652
|
+
if (items.length > 0) out[cat] = (out[cat] || []).concat(items);
|
|
653
|
+
}
|
|
654
|
+
if (Object.keys(out).length > 0) {
|
|
655
|
+
console.info(
|
|
656
|
+
`[AgentMoss] \u5DF2\u52A0\u8F7D${label}\u89C4\u5219\u6587\u4EF6 ${path5}\uFF1A` + Object.entries(out).map(([k, v]) => `${k}\xD7${v.length}`).join(", ")
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
return out;
|
|
660
|
+
}
|
|
661
|
+
function scanRuleDirs() {
|
|
662
|
+
const override = {};
|
|
663
|
+
const extra = {};
|
|
664
|
+
const loaded = [];
|
|
665
|
+
const ignored = [];
|
|
666
|
+
for (const d of rulesDirs()) {
|
|
667
|
+
if (!existsSync(d)) continue;
|
|
668
|
+
let entries = [];
|
|
669
|
+
try {
|
|
670
|
+
entries = readdirSync(d).sort();
|
|
671
|
+
} catch {
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
for (const name of entries) {
|
|
675
|
+
const f = resolve(d, name);
|
|
676
|
+
try {
|
|
677
|
+
if (statSync(f).isDirectory()) continue;
|
|
678
|
+
} catch {
|
|
570
679
|
continue;
|
|
571
680
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
)
|
|
577
|
-
|
|
681
|
+
if (name === OVERRIDE_FILENAME) {
|
|
682
|
+
const got = scanRulesFile(f, "override");
|
|
683
|
+
if (Object.keys(got).length > 0) loaded.push(f);
|
|
684
|
+
for (const [cat, items] of Object.entries(got)) {
|
|
685
|
+
if (Array.isArray(items)) {
|
|
686
|
+
override[cat] = (override[cat] || []).concat(items);
|
|
687
|
+
} else {
|
|
688
|
+
override[cat] = { ...override[cat] || {}, ...items };
|
|
689
|
+
}
|
|
578
690
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
);
|
|
585
|
-
|
|
691
|
+
} else if (name === EXTRA_FILENAME) {
|
|
692
|
+
const got = scanRulesFile(f, "extra");
|
|
693
|
+
if (Object.values(got).some((v) => v.length > 0)) loaded.push(f);
|
|
694
|
+
for (const [cat, items] of Object.entries(got)) {
|
|
695
|
+
if (Array.isArray(items)) {
|
|
696
|
+
extra[cat] = (extra[cat] || []).concat(items);
|
|
697
|
+
} else {
|
|
698
|
+
extra[cat] = { ...extra[cat] || {}, ...items };
|
|
586
699
|
}
|
|
587
|
-
seenIds.set(rid, p);
|
|
588
700
|
}
|
|
589
|
-
|
|
590
|
-
|
|
701
|
+
} else if (name.toLowerCase().endsWith(".json")) {
|
|
702
|
+
ignored.push(f);
|
|
591
703
|
}
|
|
592
704
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
705
|
+
}
|
|
706
|
+
if (Object.keys(override).length > 0 && identicalToBuiltin(override)) {
|
|
707
|
+
const key = "override-identical::" + Object.entries(override).map(([c, v]) => `${c}=${v.length}`).sort().join("|");
|
|
708
|
+
if (!warnedIgnored.has(key)) {
|
|
709
|
+
warnedIgnored.add(key);
|
|
710
|
+
console.warn(
|
|
711
|
+
`[AgentMoss] \u2139\uFE0F ${OVERRIDE_FILENAME} \u4E0E**\u5185\u7F6E\u89C4\u5219\u5B8C\u5168\u4E00\u81F4**\uFF08` + Object.entries(override).sort().map(([c, v]) => `${c}\xD7${v.length}`).join(", ") + `\uFF09\u2014\u2014 \u770B\u8D77\u6765\u662F\u6A21\u677F\u539F\u6837\u62F7\u8FDB\u6765\u6CA1\u6539\u3002
|
|
712
|
+
\u5B83\u73B0\u5728\u7684\u6548\u679C = \u5185\u7F6E\u89C4\u5219\uFF1B\u4F46**\u4EE5\u540E\u5347\u7EA7\u5185\u7F6E\u89C4\u5219\u4E0D\u4F1A\u53CD\u6620\u5230\u8FD9\u4EFD\u4E0A**\uFF08\u88AB\u9489\u4F4F\u4E86\uFF09\u3002
|
|
713
|
+
\u53EA\u60F3\u8FFD\u52A0\u89C4\u5219\uFF1A\u5220\u6389 ${OVERRIDE_FILENAME}\uFF0C\u6539\u7528 ${EXTRA_FILENAME}\u3002`
|
|
714
|
+
);
|
|
596
715
|
}
|
|
597
716
|
}
|
|
598
|
-
|
|
717
|
+
if (Object.keys(override).length > 0) {
|
|
718
|
+
const key = "override-summary::" + Object.entries(override).map(([c, v]) => `${c}=${v.length}`).sort().join("|");
|
|
719
|
+
if (!warnedIgnored.has(key)) {
|
|
720
|
+
warnedIgnored.add(key);
|
|
721
|
+
const l1 = loadL1();
|
|
722
|
+
const l2 = loadL2();
|
|
723
|
+
const builtinCounts = {
|
|
724
|
+
dangerous_commands: (l1.dangerous_commands || []).length,
|
|
725
|
+
injection_detection: (l1.injection_detection || []).length,
|
|
726
|
+
sensitive_path_access: (l1.sensitive_path_access || []).length,
|
|
727
|
+
intent_consistency: (l2.intent_consistency || []).length,
|
|
728
|
+
password_modify_consent: (l2.password_modify_consent || []).length,
|
|
729
|
+
user_deletion_consent: (l2.user_deletion_consent || []).length
|
|
730
|
+
};
|
|
731
|
+
console.warn(`[AgentMoss] \u26A0\uFE0F ${OVERRIDE_FILENAME} \u751F\u6548\uFF08**\u6574\u4EFD\u66FF\u6362**\uFF0C\u672A\u5217\u51FA\u7684\u5185\u7F6E\u89C4\u5219\u4E0D\u518D\u751F\u6548\uFF09\uFF1A`);
|
|
732
|
+
for (const [cat, items] of Object.entries(override).sort()) {
|
|
733
|
+
console.warn(` \u7C7B\u522B ${cat}\uFF1A\u5185\u7F6E ${builtinCounts[cat] ?? 0} \u6761 \u2192 \u4F60\u7684 ${items.length} \u6761`);
|
|
734
|
+
}
|
|
735
|
+
console.warn(` \u53EA\u60F3**\u8FFD\u52A0**\u89C4\u5219\u8BF7\u6539\u7528 ${EXTRA_FILENAME}\uFF1B\u8981\u6539\u67D0\u4E00\u6761\u5185\u7F6E\u89C4\u5219\uFF0C\u8BF7\u5148\u62F7\u8D1D\u5185\u7F6E\u6587\u4EF6\u518D\u6539\u3002`);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (ignored.length > 0) {
|
|
739
|
+
const key = "ignored::" + ignored.join("|");
|
|
740
|
+
if (!warnedIgnored.has(key)) {
|
|
741
|
+
warnedIgnored.add(key);
|
|
742
|
+
console.warn(
|
|
743
|
+
`[AgentMoss] \u26A0\uFE0F \u89C4\u5219\u76EE\u5F55\u91CC\u6709 ${ignored.length} \u4E2A json **\u4E0D\u4F1A\u88AB\u8BFB\u53D6**\uFF08\u53EA\u6709 ${OVERRIDE_FILENAME} \u4E0E ${EXTRA_FILENAME} \u4F1A\u88AB\u8BFB\uFF09: ${ignored.join(", ")}
|
|
744
|
+
\u8981\u8986\u76D6\u5185\u7F6E\u89C4\u5219 \u2192 \u5185\u5BB9\u653E\u8FDB ${OVERRIDE_FILENAME}\uFF1B\u8981\u8FFD\u52A0 \u2192 \u653E\u8FDB ${EXTRA_FILENAME}\u3002`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return { override, extra, loaded, ignored };
|
|
599
749
|
}
|
|
600
|
-
function
|
|
601
|
-
const
|
|
602
|
-
const
|
|
603
|
-
|
|
750
|
+
function rulesCached() {
|
|
751
|
+
const dirs = rulesDirs();
|
|
752
|
+
const sig = RULES_DIR_ENVS.map((e) => `${e}=${process.env[e] || ""}`).join("|") + "|" + dirs.join("|");
|
|
753
|
+
const files = [];
|
|
604
754
|
for (const d of dirs) {
|
|
605
|
-
if (existsSync(d))
|
|
606
|
-
|
|
755
|
+
if (!existsSync(d)) continue;
|
|
756
|
+
try {
|
|
757
|
+
for (const name of readdirSync(d).sort()) {
|
|
758
|
+
if (name.toLowerCase().endsWith(".json")) files.push(resolve(d, name));
|
|
759
|
+
}
|
|
760
|
+
} catch {
|
|
761
|
+
}
|
|
607
762
|
}
|
|
608
|
-
|
|
609
|
-
const fp = files.map((p) => {
|
|
763
|
+
const fp = JSON.stringify(files.map((p) => {
|
|
610
764
|
try {
|
|
611
765
|
const st = statSync(p);
|
|
612
766
|
return [p, st.mtimeMs, st.size];
|
|
613
767
|
} catch {
|
|
614
768
|
return [p, -1, -1];
|
|
615
769
|
}
|
|
616
|
-
});
|
|
617
|
-
if (
|
|
618
|
-
|
|
770
|
+
}));
|
|
771
|
+
if (_RULES_CACHE && _RULES_CACHE.sig === sig && _RULES_CACHE.fp === fp) return _RULES_CACHE;
|
|
772
|
+
_RULES_CACHE = { sig, fp, ...scanRuleDirs() };
|
|
773
|
+
return _RULES_CACHE;
|
|
774
|
+
}
|
|
775
|
+
function identicalToBuiltin(override) {
|
|
776
|
+
const l1 = loadL1();
|
|
777
|
+
const l2 = loadL2();
|
|
778
|
+
for (const [cat, items] of Object.entries(override)) {
|
|
779
|
+
const builtin = l1[cat] ?? l2[cat];
|
|
780
|
+
if (!Array.isArray(builtin) || builtin.length !== items.length) return false;
|
|
781
|
+
if (JSON.stringify(builtin) !== JSON.stringify(items)) return false;
|
|
619
782
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
783
|
+
return Object.keys(override).length > 0;
|
|
784
|
+
}
|
|
785
|
+
function loadOverrideRules() {
|
|
786
|
+
return rulesCached().override;
|
|
623
787
|
}
|
|
624
|
-
|
|
788
|
+
function loadExtraRules() {
|
|
789
|
+
return rulesCached().extra;
|
|
790
|
+
}
|
|
791
|
+
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;
|
|
625
792
|
var init_rules_loader = __esm({
|
|
626
793
|
"src/infra/rules-loader.ts"() {
|
|
627
794
|
"use strict";
|
|
@@ -630,20 +797,13 @@ var init_rules_loader = __esm({
|
|
|
630
797
|
init_runtime_config_bridge();
|
|
631
798
|
__filename2 = fileURLToPath2(import.meta.url);
|
|
632
799
|
__dirname2 = dirname(__filename2);
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
"l3_rules.json": [],
|
|
637
|
-
"mc_rules.json": ["rules"],
|
|
638
|
-
"tool_registry.json": ["tools"]
|
|
639
|
-
};
|
|
640
|
-
warnedOverride = /* @__PURE__ */ new Set();
|
|
800
|
+
OVERRIDE_FILENAME = "override.json";
|
|
801
|
+
EXTRA_FILENAME = "extra.json";
|
|
802
|
+
RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
|
|
641
803
|
RULES_DIR_CANDS = (() => {
|
|
642
804
|
const cands = [
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
// mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
|
|
646
|
-
resolve(dataRootDir(), "rules"),
|
|
805
|
+
// 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
|
|
806
|
+
// 规则目录只认 override.json / extra.json(见 rulesDirs())。
|
|
647
807
|
// dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
|
|
648
808
|
resolve(__dirname2, "../rules"),
|
|
649
809
|
resolve(__dirname2, "rules"),
|
|
@@ -656,6 +816,7 @@ var init_rules_loader = __esm({
|
|
|
656
816
|
];
|
|
657
817
|
return cands.filter((c) => existsSync(c));
|
|
658
818
|
})();
|
|
819
|
+
SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
|
|
659
820
|
EXTRA_CATEGORIES = {
|
|
660
821
|
dangerous_commands: ["pattern"],
|
|
661
822
|
injection_detection: ["keyword", "lang"],
|
|
@@ -664,6 +825,7 @@ var init_rules_loader = __esm({
|
|
|
664
825
|
password_modify_consent: ["pattern"],
|
|
665
826
|
user_deletion_consent: ["pattern"]
|
|
666
827
|
};
|
|
828
|
+
STRUCT_KEYS = ["skills", "ltl_rules", "tools"];
|
|
667
829
|
EXTRA_FLAT_FIELD = [
|
|
668
830
|
[["path"], "sensitive_path_access"],
|
|
669
831
|
[["keyword", "lang"], "injection_detection"],
|
|
@@ -671,7 +833,8 @@ var init_rules_loader = __esm({
|
|
|
671
833
|
[["pattern", "risk_level"], "dangerous_commands"],
|
|
672
834
|
[["pattern", "risk_type"], "dangerous_commands"]
|
|
673
835
|
];
|
|
674
|
-
|
|
836
|
+
_RULES_CACHE = null;
|
|
837
|
+
warnedIgnored = /* @__PURE__ */ new Set();
|
|
675
838
|
}
|
|
676
839
|
});
|
|
677
840
|
|
|
@@ -2029,18 +2192,34 @@ function _mergeUserCustom(primary, runtime, layers, key) {
|
|
|
2029
2192
|
const extra = _userCustomRules(runtime, layers, key);
|
|
2030
2193
|
return extra.length > 0 ? [...primary, ...extra] : [...primary];
|
|
2031
2194
|
}
|
|
2032
|
-
function
|
|
2195
|
+
function _dedup(rules, category) {
|
|
2196
|
+
const keys = _DEDUP_KEYS[category];
|
|
2197
|
+
if (!keys) return rules;
|
|
2198
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2199
|
+
const out = [];
|
|
2200
|
+
for (const r of rules) {
|
|
2201
|
+
const k = keys.map((x) => String(r?.[x] ?? "")).join("\0");
|
|
2202
|
+
if (seen.has(k)) continue;
|
|
2203
|
+
seen.add(k);
|
|
2204
|
+
out.push(r);
|
|
2205
|
+
}
|
|
2206
|
+
return out;
|
|
2207
|
+
}
|
|
2208
|
+
function _mergeRulesFromDirs(primary, category) {
|
|
2209
|
+
let base = [...primary];
|
|
2033
2210
|
try {
|
|
2211
|
+
const override = loadOverrideRules()[category];
|
|
2212
|
+
if (override !== void 0 && override !== null) base = [...override];
|
|
2034
2213
|
const extra = loadExtraRules()[category] || [];
|
|
2035
|
-
return
|
|
2214
|
+
return _dedup([...base, ...extra], category);
|
|
2036
2215
|
} catch {
|
|
2037
|
-
return
|
|
2216
|
+
return base;
|
|
2038
2217
|
}
|
|
2039
2218
|
}
|
|
2040
2219
|
function getEnabledL1CommandPatterns(runtime, osType) {
|
|
2041
2220
|
const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
|
|
2042
2221
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2043
|
-
rules =
|
|
2222
|
+
rules = _mergeRulesFromDirs(rules, "dangerous_commands");
|
|
2044
2223
|
rules = _mergeUserCustom(rules, runtime, "L1_rules", "pattern");
|
|
2045
2224
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2046
2225
|
return rules.map((r) => ({
|
|
@@ -2056,7 +2235,7 @@ function getEnabledL2SensitivePaths(runtime, osType) {
|
|
|
2056
2235
|
cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
|
|
2057
2236
|
}
|
|
2058
2237
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2059
|
-
rules =
|
|
2238
|
+
rules = _mergeRulesFromDirs(rules, "sensitive_path_access");
|
|
2060
2239
|
rules = _mergeUserCustom(rules, runtime, ["L1_rules", "L2_rules"], "path");
|
|
2061
2240
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2062
2241
|
return rules.map((r) => {
|
|
@@ -2441,25 +2620,11 @@ function generateSourceDefaults() {
|
|
|
2441
2620
|
};
|
|
2442
2621
|
}
|
|
2443
2622
|
function _ltlRulesToRuntime() {
|
|
2444
|
-
const cands = [
|
|
2445
|
-
getRuntimeConfigDir() && join3(getRuntimeConfigDir(), "rules"),
|
|
2446
|
-
join3(__dirname4, "../rules"),
|
|
2447
|
-
join3(__dirname4, "rules"),
|
|
2448
|
-
join3(__dirname4, "../../../agent_moss/rules"),
|
|
2449
|
-
join3(__dirname4, "../../../../agent_moss/rules"),
|
|
2450
|
-
join3(process.cwd(), "agent_moss/rules")
|
|
2451
|
-
].filter(Boolean);
|
|
2452
2623
|
let mc = {};
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
|
|
2458
|
-
} catch {
|
|
2459
|
-
mc = {};
|
|
2460
|
-
}
|
|
2461
|
-
break;
|
|
2462
|
-
}
|
|
2624
|
+
try {
|
|
2625
|
+
mc = loadMCRaw();
|
|
2626
|
+
} catch {
|
|
2627
|
+
mc = {};
|
|
2463
2628
|
}
|
|
2464
2629
|
const rules = mc.rules || [];
|
|
2465
2630
|
return {
|
|
@@ -2688,7 +2853,7 @@ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
|
|
|
2688
2853
|
function getEnabledL1InjectionKeywords(runtime, osType) {
|
|
2689
2854
|
const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
|
|
2690
2855
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2691
|
-
rules =
|
|
2856
|
+
rules = _mergeRulesFromDirs(rules, "injection_detection");
|
|
2692
2857
|
rules = _mergeUserCustom(rules, runtime, "L1_rules", "keyword");
|
|
2693
2858
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2694
2859
|
return rules.map((r) => ({
|
|
@@ -2700,7 +2865,7 @@ function getEnabledL1InjectionKeywords(runtime, osType) {
|
|
|
2700
2865
|
function getEnabledL2IntentPatterns(runtime, osType) {
|
|
2701
2866
|
const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
|
|
2702
2867
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2703
|
-
rules =
|
|
2868
|
+
rules = _mergeRulesFromDirs(rules, "intent_consistency");
|
|
2704
2869
|
rules = _mergeUserCustom(rules, runtime, "L2_rules", "intent_keywords");
|
|
2705
2870
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2706
2871
|
return rules.map((r) => ({
|
|
@@ -2712,7 +2877,7 @@ function getEnabledL2IntentPatterns(runtime, osType) {
|
|
|
2712
2877
|
function getEnabledL2PasswordPatterns(runtime) {
|
|
2713
2878
|
const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
|
|
2714
2879
|
const rules = _mergeUserCustom(
|
|
2715
|
-
|
|
2880
|
+
_mergeRulesFromDirs(filterEnabledRules(cat.rules || [], cat.category_enabled), "password_modify_consent"),
|
|
2716
2881
|
runtime,
|
|
2717
2882
|
"L2_rules",
|
|
2718
2883
|
"pattern"
|
|
@@ -2722,7 +2887,7 @@ function getEnabledL2PasswordPatterns(runtime) {
|
|
|
2722
2887
|
function getEnabledL2UserDeletionPatterns(runtime) {
|
|
2723
2888
|
const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
|
|
2724
2889
|
const rules = _mergeUserCustom(
|
|
2725
|
-
|
|
2890
|
+
_mergeRulesFromDirs(filterEnabledRules(cat.rules || [], cat.category_enabled), "user_deletion_consent"),
|
|
2726
2891
|
runtime,
|
|
2727
2892
|
"L2_rules",
|
|
2728
2893
|
"pattern"
|
|
@@ -2759,7 +2924,7 @@ function getEnabledL1CommandPatternsOrDefault(runtime, osType) {
|
|
|
2759
2924
|
return getEnabledL1CommandPatterns(runtime, osType);
|
|
2760
2925
|
}
|
|
2761
2926
|
return _mergeUserCustom(
|
|
2762
|
-
|
|
2927
|
+
_mergeRulesFromDirs(_filterDefaultsByOs(_l1CommandPatternsToRules(), osType), "dangerous_commands"),
|
|
2763
2928
|
runtime,
|
|
2764
2929
|
"L1_rules",
|
|
2765
2930
|
"pattern"
|
|
@@ -2771,7 +2936,7 @@ function getEnabledL1InjectionKeywordsOrDefault(runtime, osType) {
|
|
|
2771
2936
|
return getEnabledL1InjectionKeywords(runtime, osType);
|
|
2772
2937
|
}
|
|
2773
2938
|
return _mergeUserCustom(
|
|
2774
|
-
|
|
2939
|
+
_mergeRulesFromDirs(_l1InjectionKeywordsToRules(), "injection_detection"),
|
|
2775
2940
|
runtime,
|
|
2776
2941
|
"L1_rules",
|
|
2777
2942
|
"keyword"
|
|
@@ -2783,7 +2948,7 @@ function getEnabledL2SensitivePathsOrDefault(runtime, osType) {
|
|
|
2783
2948
|
return getEnabledL2SensitivePaths(runtime, osType);
|
|
2784
2949
|
}
|
|
2785
2950
|
return _mergeUserCustom(
|
|
2786
|
-
|
|
2951
|
+
_mergeRulesFromDirs(_filterDefaultsByOs(_l2SensitivePathsToRules(), osType), "sensitive_path_access"),
|
|
2787
2952
|
runtime,
|
|
2788
2953
|
["L1_rules", "L2_rules"],
|
|
2789
2954
|
"path"
|
|
@@ -2795,7 +2960,7 @@ function getEnabledL2IntentPatternsOrDefault(runtime, osType) {
|
|
|
2795
2960
|
return getEnabledL2IntentPatterns(runtime, osType);
|
|
2796
2961
|
}
|
|
2797
2962
|
return _mergeUserCustom(
|
|
2798
|
-
|
|
2963
|
+
_mergeRulesFromDirs(_filterDefaultsByOs(_l2IntentPatternsToRules(), osType), "intent_consistency"),
|
|
2799
2964
|
runtime,
|
|
2800
2965
|
"L2_rules",
|
|
2801
2966
|
"intent_keywords"
|
|
@@ -3186,7 +3351,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
|
|
|
3186
3351
|
saveRuntimeConfig(runtime);
|
|
3187
3352
|
return runtime;
|
|
3188
3353
|
}
|
|
3189
|
-
var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, runtimeConfigCache, hasRuntimeConfigCache, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, _effectiveCache, SKILL_ID_RE;
|
|
3354
|
+
var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, runtimeConfigCache, _DEDUP_KEYS, hasRuntimeConfigCache, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, _effectiveCache, SKILL_ID_RE;
|
|
3190
3355
|
var init_runtime_config = __esm({
|
|
3191
3356
|
"src/infra/runtime-config.ts"() {
|
|
3192
3357
|
"use strict";
|
|
@@ -3219,6 +3384,14 @@ var init_runtime_config = __esm({
|
|
|
3219
3384
|
deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
|
|
3220
3385
|
};
|
|
3221
3386
|
runtimeConfigCache = null;
|
|
3387
|
+
_DEDUP_KEYS = {
|
|
3388
|
+
dangerous_commands: ["pattern"],
|
|
3389
|
+
password_modify_consent: ["pattern"],
|
|
3390
|
+
user_deletion_consent: ["pattern"],
|
|
3391
|
+
sensitive_path_access: ["path"],
|
|
3392
|
+
injection_detection: ["keyword", "lang"],
|
|
3393
|
+
intent_consistency: ["intent_keywords", "reason"]
|
|
3394
|
+
};
|
|
3222
3395
|
hasRuntimeConfigCache = /* @__PURE__ */ new Map();
|
|
3223
3396
|
_AGENTS_DIR_PROD = join3(__dirname4, "agents");
|
|
3224
3397
|
_AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
|
|
@@ -3244,6 +3417,8 @@ var init_runtime_config = __esm({
|
|
|
3244
3417
|
var config_exports = {};
|
|
3245
3418
|
__export(config_exports, {
|
|
3246
3419
|
detectProvider: () => detectProvider,
|
|
3420
|
+
getAgentMossSetting: () => getAgentMossSetting,
|
|
3421
|
+
getCacheOptions: () => getCacheOptions,
|
|
3247
3422
|
getCustomRules: () => getCustomRules,
|
|
3248
3423
|
getLLMConfig: () => getLLMConfig,
|
|
3249
3424
|
getLLMFailMode: () => getLLMFailMode,
|
|
@@ -3255,6 +3430,8 @@ __export(config_exports, {
|
|
|
3255
3430
|
isHeuristicEnabled: () => isHeuristicEnabled,
|
|
3256
3431
|
isLLMAvailable: () => isLLMAvailable,
|
|
3257
3432
|
isLogicRulesEnabled: () => isLogicRulesEnabled,
|
|
3433
|
+
isPolicyGenEnabled: () => isPolicyGenEnabled,
|
|
3434
|
+
isPolicySaveEnabled: () => isPolicySaveEnabled,
|
|
3258
3435
|
probeLLMConnection: () => probeLLMConnection,
|
|
3259
3436
|
updateLLMConfig: () => updateLLMConfig,
|
|
3260
3437
|
userRuleVariantPattern: () => userRuleVariantPattern,
|
|
@@ -3277,8 +3454,10 @@ function resolveConfigPath() {
|
|
|
3277
3454
|
const userConfig = path2.join(getConfigDir(), "config.json");
|
|
3278
3455
|
if (fs2.existsSync(userConfig)) return userConfig;
|
|
3279
3456
|
const defaultCandidates = [
|
|
3280
|
-
path2.join(
|
|
3281
|
-
path2.join(
|
|
3457
|
+
path2.join(__configModuleDir, "config.json.example"),
|
|
3458
|
+
path2.join(__configModuleDir, "..", "config.json.example"),
|
|
3459
|
+
path2.join(__configModuleDir, "..", "..", "agent_moss", "infra", "config.json.example"),
|
|
3460
|
+
path2.join(__configModuleDir, "..", "..", "..", "agent_moss", "infra", "config.json.example")
|
|
3282
3461
|
];
|
|
3283
3462
|
const defaultConfig = defaultCandidates.find((p) => fs2.existsSync(p));
|
|
3284
3463
|
if (defaultConfig) {
|
|
@@ -3287,8 +3466,8 @@ function resolveConfigPath() {
|
|
|
3287
3466
|
const pkgConfig = loadConfigFile(defaultConfig);
|
|
3288
3467
|
const safeConfig = pkgConfig ? { ...pkgConfig, llm: { ...pkgConfig.llm, api_key: "" } } : {};
|
|
3289
3468
|
fs2.writeFileSync(userConfig, JSON.stringify(safeConfig, null, 4), "utf-8");
|
|
3290
|
-
console.
|
|
3291
|
-
console.
|
|
3469
|
+
console.error(`[AgentMoss] \u5DF2\u521B\u5EFA\u7528\u6237\u914D\u7F6E\u6587\u4EF6: ${userConfig}`);
|
|
3470
|
+
console.error(`[AgentMoss] \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\u914D\u7F6E\u4F60\u7684 LLM API Key`);
|
|
3292
3471
|
}
|
|
3293
3472
|
return userConfig;
|
|
3294
3473
|
}
|
|
@@ -3460,7 +3639,32 @@ function layerEnabledOrDefault(key) {
|
|
|
3460
3639
|
}
|
|
3461
3640
|
}
|
|
3462
3641
|
function isBrainEnabled() {
|
|
3463
|
-
return
|
|
3642
|
+
return getAgentMossSetting("AGENT_MOSS_BRAIN_ENABLED", "") === "1";
|
|
3643
|
+
}
|
|
3644
|
+
function getAgentMossSetting(key, def = "") {
|
|
3645
|
+
const envValue = process.env[key];
|
|
3646
|
+
if (envValue !== void 0) return envValue;
|
|
3647
|
+
const cfg = loadConfigFile(resolveConfigPath());
|
|
3648
|
+
if (cfg && key in cfg) return String(cfg[key]);
|
|
3649
|
+
return def;
|
|
3650
|
+
}
|
|
3651
|
+
function isPolicyGenEnabled() {
|
|
3652
|
+
return getAgentMossSetting("AGENT_MOSS_ENABLE_POLICY_GEN", "") === "1";
|
|
3653
|
+
}
|
|
3654
|
+
function getCacheOptions() {
|
|
3655
|
+
let enabled = true;
|
|
3656
|
+
let maxSize = 1e3;
|
|
3657
|
+
const cfg = loadConfigFile(resolveConfigPath());
|
|
3658
|
+
const cache3 = cfg?.cache;
|
|
3659
|
+
if (cache3 && typeof cache3 === "object") {
|
|
3660
|
+
if (typeof cache3.enabled === "boolean") enabled = cache3.enabled;
|
|
3661
|
+
if (typeof cache3.max_size === "number" && cache3.max_size > 0) maxSize = cache3.max_size;
|
|
3662
|
+
}
|
|
3663
|
+
if (process.env.AGENT_MOSS_DISABLE_CACHE === "1") enabled = false;
|
|
3664
|
+
return { enabled, maxSize };
|
|
3665
|
+
}
|
|
3666
|
+
function isPolicySaveEnabled() {
|
|
3667
|
+
return getAgentMossSetting("AGENT_MOSS_SAVE_POLICY", "1") !== "0";
|
|
3464
3668
|
}
|
|
3465
3669
|
function isLogicRulesEnabled() {
|
|
3466
3670
|
if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
|
|
@@ -3469,13 +3673,20 @@ function isLogicRulesEnabled() {
|
|
|
3469
3673
|
function isFormalObserveMode() {
|
|
3470
3674
|
return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
|
|
3471
3675
|
}
|
|
3472
|
-
var timeoutConfig, retryConfig, llmConfig,
|
|
3676
|
+
var __configModuleDir, timeoutConfig, retryConfig, llmConfig, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
|
|
3473
3677
|
var init_config = __esm({
|
|
3474
3678
|
"src/config.ts"() {
|
|
3475
3679
|
"use strict";
|
|
3476
3680
|
init_esm_shims();
|
|
3477
3681
|
init_paths();
|
|
3478
3682
|
init_runtime_config();
|
|
3683
|
+
__configModuleDir = (() => {
|
|
3684
|
+
try {
|
|
3685
|
+
return path2.dirname(fileURLToPath5(import.meta.url));
|
|
3686
|
+
} catch {
|
|
3687
|
+
return typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
3688
|
+
}
|
|
3689
|
+
})();
|
|
3479
3690
|
llmConfig = (() => {
|
|
3480
3691
|
const configPath = resolveConfigPath();
|
|
3481
3692
|
const fileConfig = fs2.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
|
|
@@ -3493,13 +3704,6 @@ var init_config = __esm({
|
|
|
3493
3704
|
timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
|
|
3494
3705
|
};
|
|
3495
3706
|
})();
|
|
3496
|
-
__configModuleDir = (() => {
|
|
3497
|
-
try {
|
|
3498
|
-
return path2.dirname(fileURLToPath5(import.meta.url));
|
|
3499
|
-
} catch {
|
|
3500
|
-
return typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
3501
|
-
}
|
|
3502
|
-
})();
|
|
3503
3707
|
USER_RULES_PATHS = [
|
|
3504
3708
|
path2.join(__configModuleDir, "rules", "user_rules.json"),
|
|
3505
3709
|
// 源在 ts/src/rules(一般不存在)
|
|
@@ -7286,6 +7490,56 @@ var BrainRuleStore = class {
|
|
|
7286
7490
|
getRule(ruleId) {
|
|
7287
7491
|
return this.data.rules.find((r) => r.id === ruleId);
|
|
7288
7492
|
}
|
|
7493
|
+
/** 质疑:active → disputed,并把 disputed_count +1(镜像 Python dispute_rule)。 */
|
|
7494
|
+
disputeRule(ruleId, _reason = "") {
|
|
7495
|
+
const rule = this.getRule(ruleId);
|
|
7496
|
+
if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
|
|
7497
|
+
if (rule.status !== STATUS_ACTIVE) throw new Error(`\u53EA\u6709 active \u72B6\u6001\u53EF\u8D28\u7591\uFF0C\u5F53\u524D: ${rule.status}`);
|
|
7498
|
+
rule.status = STATUS_DISPUTED;
|
|
7499
|
+
rule.disputedCount += 1;
|
|
7500
|
+
this.save();
|
|
7501
|
+
}
|
|
7502
|
+
// ==================== 生命周期(curator 用,镜像 Python rule_store) ====================
|
|
7503
|
+
/** 长期未触发的 active/disputed 规则 → stale。 */
|
|
7504
|
+
markStale(staleDays = 30) {
|
|
7505
|
+
const cutoff = Date.now() - staleDays * 864e5;
|
|
7506
|
+
for (const r of this.data.rules) {
|
|
7507
|
+
if ((r.status === STATUS_ACTIVE || r.status === STATUS_DISPUTED) && r.lastTriggeredAt > 0 && r.lastTriggeredAt < cutoff) {
|
|
7508
|
+
r.status = STATUS_STALE;
|
|
7509
|
+
}
|
|
7510
|
+
}
|
|
7511
|
+
this.save();
|
|
7512
|
+
}
|
|
7513
|
+
/** 长期 stale 的规则 → archived。 */
|
|
7514
|
+
markArchived(archiveDays = 90) {
|
|
7515
|
+
const cutoff = Date.now() - archiveDays * 864e5;
|
|
7516
|
+
for (const r of this.data.rules) {
|
|
7517
|
+
if (r.status === STATUS_STALE && r.lastTriggeredAt > 0 && r.lastTriggeredAt < cutoff) {
|
|
7518
|
+
r.status = STATUS_ARCHIVED;
|
|
7519
|
+
}
|
|
7520
|
+
}
|
|
7521
|
+
this.save();
|
|
7522
|
+
}
|
|
7523
|
+
/** disputed 次数达阈值 → 自动降级 stale(自动纠错闭环)。 */
|
|
7524
|
+
autoDemoteDisputed(disputeThreshold = 3) {
|
|
7525
|
+
for (const r of this.data.rules) {
|
|
7526
|
+
if (r.status === STATUS_DISPUTED && r.disputedCount >= disputeThreshold) {
|
|
7527
|
+
r.status = STATUS_STALE;
|
|
7528
|
+
}
|
|
7529
|
+
}
|
|
7530
|
+
this.save();
|
|
7531
|
+
}
|
|
7532
|
+
// ==================== 中心化预留(镜像 Python) ====================
|
|
7533
|
+
/** 导出本地新增(非 central 来源)的 active 规则。 */
|
|
7534
|
+
getRulesForExport() {
|
|
7535
|
+
return this.data.rules.filter((r) => r.source !== "central" && r.status === STATUS_ACTIVE);
|
|
7536
|
+
}
|
|
7537
|
+
/** 导入中心下发规则(强制标 central 来源,避免被再次回流)。 */
|
|
7538
|
+
importRules(rules) {
|
|
7539
|
+
for (const rule of rules) {
|
|
7540
|
+
this.addRule({ ...rule, source: "central", originNode: "central" });
|
|
7541
|
+
}
|
|
7542
|
+
}
|
|
7289
7543
|
getActiveRules(layer) {
|
|
7290
7544
|
return this.data.rules.filter((r) => r.status === STATUS_ACTIVE && (layer === void 0 || r.layer === layer));
|
|
7291
7545
|
}
|
|
@@ -7386,12 +7640,14 @@ var PatternExtractor = class {
|
|
|
7386
7640
|
minOccurrences;
|
|
7387
7641
|
builtinPatterns;
|
|
7388
7642
|
reviewFn;
|
|
7643
|
+
skillManager;
|
|
7389
7644
|
constructor(ruleStore, opts) {
|
|
7390
7645
|
this.ruleStore = ruleStore;
|
|
7391
7646
|
this.minConfidence = opts?.minConfidence ?? 0.9;
|
|
7392
7647
|
this.minOccurrences = opts?.minOccurrences ?? 3;
|
|
7393
7648
|
this.builtinPatterns = opts?.builtinPatterns ?? [];
|
|
7394
7649
|
this.reviewFn = opts?.review ?? null;
|
|
7650
|
+
this.skillManager = opts?.skillManager ?? null;
|
|
7395
7651
|
}
|
|
7396
7652
|
async extract(event, llmResult) {
|
|
7397
7653
|
const result = { rulesCreated: 0, patternsRecorded: 0, skippedReason: "", deduped: [] };
|
|
@@ -7480,8 +7736,96 @@ var PatternExtractor = class {
|
|
|
7480
7736
|
result.rulesCreated += 1;
|
|
7481
7737
|
}
|
|
7482
7738
|
}
|
|
7739
|
+
if (this.skillManager) {
|
|
7740
|
+
const skill = this.buildSkill(actionDetail, reason, riskType, eventId);
|
|
7741
|
+
if (skill) {
|
|
7742
|
+
const id = this.skillManager.addSkill(skill);
|
|
7743
|
+
if (id) {
|
|
7744
|
+
result.skillsCreated = (result.skillsCreated ?? 0) + 1;
|
|
7745
|
+
}
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7483
7748
|
return result;
|
|
7484
7749
|
}
|
|
7750
|
+
/**
|
|
7751
|
+
* 生成候选 Skill;不值得沉淀时返回 null(镜像 Python `_extract_skill` 的三道价值评估)。
|
|
7752
|
+
*
|
|
7753
|
+
* 评估 1:reason 太短(<50 字)说明没有可复用的判断逻辑,不沉淀;
|
|
7754
|
+
* 评估 2:同名 skill 已存在 → 跳过;
|
|
7755
|
+
* 评估 3:与已有 active skill 关键词重叠 >= 2 → 跳过(防同类案例重复沉淀,
|
|
7756
|
+
* 如 cat /etc/shadow 与 cat /etc/sudoers 生成两个近乎重复的 skill)。
|
|
7757
|
+
*/
|
|
7758
|
+
buildSkill(actionDetail, reason, riskType, eventId) {
|
|
7759
|
+
if (!reason || reason.length < 50) return null;
|
|
7760
|
+
if (!riskType) return null;
|
|
7761
|
+
const detailWords = (actionDetail.toLowerCase().match(/[a-z]+/g) || []).slice(0, 3);
|
|
7762
|
+
const name = detailWords.length > 0 ? `brain_${riskType}_${detailWords.join("_")}` : `brain_${riskType}`;
|
|
7763
|
+
if (this.skillManager.getSkillMeta(name)) return null;
|
|
7764
|
+
const keywords = this.extractSkillKeywords(actionDetail, reason, riskType);
|
|
7765
|
+
if (this.isCoveredByExistingSkill(keywords)) return null;
|
|
7766
|
+
return {
|
|
7767
|
+
name,
|
|
7768
|
+
category: this.categorizeRisk(riskType),
|
|
7769
|
+
content: this.generateSkillContent(name, actionDetail, reason, riskType),
|
|
7770
|
+
keywords,
|
|
7771
|
+
sourceEventIds: eventId ? [eventId] : [],
|
|
7772
|
+
status: "active"
|
|
7773
|
+
};
|
|
7774
|
+
}
|
|
7775
|
+
/** 关键词提取(镜像 Python `_extract_keywords`)。 */
|
|
7776
|
+
extractSkillKeywords(actionDetail, reason, riskType) {
|
|
7777
|
+
const keywords = /* @__PURE__ */ new Set();
|
|
7778
|
+
if (riskType) keywords.add(riskType);
|
|
7779
|
+
for (const w of (actionDetail.toLowerCase().match(/[a-z_]{3,}/g) || []).slice(0, 10)) keywords.add(w);
|
|
7780
|
+
for (const w of (reason.toLowerCase().match(/[a-z_]{3,}/g) || []).slice(0, 5)) keywords.add(w);
|
|
7781
|
+
return [...keywords];
|
|
7782
|
+
}
|
|
7783
|
+
/** 与已有 active skill 关键词重叠 >= 2 → 视为已覆盖(镜像 `_is_covered_by_existing_skill`)。 */
|
|
7784
|
+
isCoveredByExistingSkill(keywords) {
|
|
7785
|
+
if (keywords.length === 0) return false;
|
|
7786
|
+
let existing = [];
|
|
7787
|
+
try {
|
|
7788
|
+
existing = this.skillManager.getActiveSkills();
|
|
7789
|
+
} catch {
|
|
7790
|
+
return false;
|
|
7791
|
+
}
|
|
7792
|
+
const cand = new Set(keywords.map((k) => k.toLowerCase()));
|
|
7793
|
+
for (const meta of existing) {
|
|
7794
|
+
const metaKeywords = (meta.keywords || []).map((k) => String(k).toLowerCase());
|
|
7795
|
+
if (metaKeywords.length === 0) continue;
|
|
7796
|
+
const overlap = metaKeywords.filter((k) => cand.has(k)).length;
|
|
7797
|
+
if (overlap >= 2) return true;
|
|
7798
|
+
}
|
|
7799
|
+
return false;
|
|
7800
|
+
}
|
|
7801
|
+
/** 风险类型 → skill 分类(镜像 Python `_categorize_risk`)。 */
|
|
7802
|
+
categorizeRisk(riskType) {
|
|
7803
|
+
if (!riskType) return "edge_case";
|
|
7804
|
+
if (["inject", "backdoor", "persist"].some((k) => riskType.includes(k))) return "attack_pattern";
|
|
7805
|
+
if (["exfil", "leak", "export"].some((k) => riskType.includes(k))) return "attack_pattern";
|
|
7806
|
+
return "defense_strategy";
|
|
7807
|
+
}
|
|
7808
|
+
/** skill 正文(与 Python `_generate_skill_content` 同结构,便于双端对拍)。 */
|
|
7809
|
+
generateSkillContent(name, actionDetail, reason, riskType) {
|
|
7810
|
+
return `# ${name}
|
|
7811
|
+
|
|
7812
|
+
## \u6765\u6E90
|
|
7813
|
+
Brain \u81EA\u52A8\u63D0\u53D6\uFF08LLM \u5206\u6790\u6C89\u6DC0\uFF09
|
|
7814
|
+
|
|
7815
|
+
## \u573A\u666F
|
|
7816
|
+
\u5F53 Agent \u6267\u884C\u4EE5\u4E0B\u7C7B\u578B\u64CD\u4F5C\u65F6\u89E6\u53D1\uFF1A
|
|
7817
|
+
- \u98CE\u9669\u7C7B\u578B: ${riskType}
|
|
7818
|
+
- \u64CD\u4F5C\u7279\u5F81: ${actionDetail.slice(0, 200)}
|
|
7819
|
+
|
|
7820
|
+
## \u5224\u65AD\u4F9D\u636E
|
|
7821
|
+
${reason.slice(0, 500)}
|
|
7822
|
+
|
|
7823
|
+
## \u5904\u7406\u5EFA\u8BAE
|
|
7824
|
+
- \u4F18\u5148\u68C0\u67E5\u64CD\u4F5C\u7684\u4E0A\u4E0B\u6587\u548C\u610F\u56FE
|
|
7825
|
+
- \u7ED3\u5408 action_history \u5224\u65AD\u662F\u5426\u5B58\u5728\u6076\u610F\u94FE
|
|
7826
|
+
- \u5FC5\u8981\u65F6 Deny \u5E76\u8BB0\u5F55\u539F\u56E0
|
|
7827
|
+
`;
|
|
7828
|
+
}
|
|
7485
7829
|
extractPatterns(actionDetail, reason, riskType) {
|
|
7486
7830
|
const patterns = [];
|
|
7487
7831
|
const detailLower = actionDetail.toLowerCase();
|
|
@@ -7669,6 +8013,209 @@ function parseReview(content) {
|
|
|
7669
8013
|
};
|
|
7670
8014
|
}
|
|
7671
8015
|
|
|
8016
|
+
// src/brain/skill-manager.ts
|
|
8017
|
+
init_esm_shims();
|
|
8018
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, renameSync, writeFileSync as writeFileSync4 } from "fs";
|
|
8019
|
+
import { homedir as homedir5 } from "os";
|
|
8020
|
+
import { join as join8 } from "path";
|
|
8021
|
+
import { randomUUID } from "crypto";
|
|
8022
|
+
var DEFAULT_SKILL_META_PATH = join8(homedir5(), ".agentmoss", "brain", "skill_meta.json");
|
|
8023
|
+
var ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
|
|
8024
|
+
function getMetaPath(override) {
|
|
8025
|
+
return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
|
|
8026
|
+
}
|
|
8027
|
+
var BrainSkillManager = class {
|
|
8028
|
+
constructor(skillsDir, metaPath) {
|
|
8029
|
+
this.skillsDir = skillsDir;
|
|
8030
|
+
this.metaPath = getMetaPath(metaPath);
|
|
8031
|
+
try {
|
|
8032
|
+
mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
|
|
8033
|
+
} catch {
|
|
8034
|
+
}
|
|
8035
|
+
this.load();
|
|
8036
|
+
this.cleanupGhostSkills();
|
|
8037
|
+
}
|
|
8038
|
+
skillsDir;
|
|
8039
|
+
skills = [];
|
|
8040
|
+
metaPath;
|
|
8041
|
+
load() {
|
|
8042
|
+
if (!existsSync13(this.metaPath)) return;
|
|
8043
|
+
try {
|
|
8044
|
+
const data = JSON.parse(readFileSync13(this.metaPath, "utf-8"));
|
|
8045
|
+
this.skills = data.skills || [];
|
|
8046
|
+
} catch {
|
|
8047
|
+
this.skills = [];
|
|
8048
|
+
}
|
|
8049
|
+
}
|
|
8050
|
+
save() {
|
|
8051
|
+
const out = { version: 1, skills: this.skills };
|
|
8052
|
+
const tmp = `${this.metaPath}.tmp`;
|
|
8053
|
+
try {
|
|
8054
|
+
writeFileSync4(tmp, JSON.stringify(out, null, 2), "utf-8");
|
|
8055
|
+
renameSync(tmp, this.metaPath);
|
|
8056
|
+
} catch {
|
|
8057
|
+
}
|
|
8058
|
+
}
|
|
8059
|
+
/**
|
|
8060
|
+
* 清理"幽灵 skill":meta 记 active 但磁盘 .md 已不存在。
|
|
8061
|
+
* (沿用 Python 的处理:置 stale,避免污染关键词表并阻塞同名新 skill 生成。)
|
|
8062
|
+
*/
|
|
8063
|
+
cleanupGhostSkills() {
|
|
8064
|
+
let changed = false;
|
|
8065
|
+
for (const sk of this.skills) {
|
|
8066
|
+
if (sk.status !== "active") continue;
|
|
8067
|
+
if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
|
|
8068
|
+
sk.status = "stale";
|
|
8069
|
+
changed = true;
|
|
8070
|
+
}
|
|
8071
|
+
}
|
|
8072
|
+
if (changed) this.save();
|
|
8073
|
+
}
|
|
8074
|
+
/** 新增 Skill:写 .md + 记元数据。目录缺失或同名已存在时跳过(返回 "")。 */
|
|
8075
|
+
addSkill(skill) {
|
|
8076
|
+
const id = skill.id || randomUUID();
|
|
8077
|
+
const name = skill.name || `brain_${id.slice(0, 8)}`;
|
|
8078
|
+
if (!existsSync13(this.skillsDir)) return "";
|
|
8079
|
+
const mdPath = join8(this.skillsDir, `${name}.md`);
|
|
8080
|
+
if (existsSync13(mdPath)) return "";
|
|
8081
|
+
try {
|
|
8082
|
+
writeFileSync4(mdPath, skill.content, "utf-8");
|
|
8083
|
+
} catch {
|
|
8084
|
+
return "";
|
|
8085
|
+
}
|
|
8086
|
+
this.skills.push({
|
|
8087
|
+
id,
|
|
8088
|
+
name,
|
|
8089
|
+
category: skill.category || "",
|
|
8090
|
+
keywords: skill.keywords || [],
|
|
8091
|
+
source_event_ids: skill.sourceEventIds || [],
|
|
8092
|
+
status: skill.status || "draft",
|
|
8093
|
+
created_at: skill.createdAt || Date.now() / 1e3,
|
|
8094
|
+
usage_count: skill.usageCount || 0,
|
|
8095
|
+
origin_node: skill.originNode || "local"
|
|
8096
|
+
});
|
|
8097
|
+
this.save();
|
|
8098
|
+
return id;
|
|
8099
|
+
}
|
|
8100
|
+
getSkillMeta(name) {
|
|
8101
|
+
return this.skills.find((s) => s.name === name);
|
|
8102
|
+
}
|
|
8103
|
+
getActiveSkills() {
|
|
8104
|
+
return this.skills.filter((s) => s.status === "active").sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
|
|
8105
|
+
}
|
|
8106
|
+
recordUsage(name) {
|
|
8107
|
+
const sk = this.getSkillMeta(name);
|
|
8108
|
+
if (!sk) return;
|
|
8109
|
+
sk.usage_count = (sk.usage_count || 0) + 1;
|
|
8110
|
+
this.save();
|
|
8111
|
+
}
|
|
8112
|
+
/** 长期未使用(usage_count=0)且创建超过 staleDays 的 active skill → stale。 */
|
|
8113
|
+
markStale(staleDays = 60) {
|
|
8114
|
+
const cutoff = Date.now() / 1e3 - staleDays * 86400;
|
|
8115
|
+
let changed = false;
|
|
8116
|
+
for (const sk of this.skills) {
|
|
8117
|
+
if (sk.status === "active" && (sk.usage_count || 0) === 0 && (sk.created_at || 0) < cutoff) {
|
|
8118
|
+
sk.status = "stale";
|
|
8119
|
+
changed = true;
|
|
8120
|
+
}
|
|
8121
|
+
}
|
|
8122
|
+
if (changed) this.save();
|
|
8123
|
+
}
|
|
8124
|
+
/** 导出本地 active skill(含正文),供回流中心。 */
|
|
8125
|
+
exportSkills() {
|
|
8126
|
+
const out = [];
|
|
8127
|
+
for (const sk of this.skills) {
|
|
8128
|
+
if (sk.origin_node !== "local" || sk.status !== "active") continue;
|
|
8129
|
+
const mdPath = join8(this.skillsDir, `${sk.name}.md`);
|
|
8130
|
+
if (!existsSync13(mdPath)) continue;
|
|
8131
|
+
out.push({
|
|
8132
|
+
name: sk.name,
|
|
8133
|
+
category: sk.category || "",
|
|
8134
|
+
content: readFileSync13(mdPath, "utf-8"),
|
|
8135
|
+
keywords: sk.keywords || []
|
|
8136
|
+
});
|
|
8137
|
+
}
|
|
8138
|
+
return out;
|
|
8139
|
+
}
|
|
8140
|
+
/** 导入中心下发 skill(标 central,避免被再次回流)。 */
|
|
8141
|
+
importSkills(skills) {
|
|
8142
|
+
for (const s of skills) {
|
|
8143
|
+
this.addSkill({
|
|
8144
|
+
name: s.name,
|
|
8145
|
+
category: s.category || "",
|
|
8146
|
+
content: s.content,
|
|
8147
|
+
keywords: s.keywords || [],
|
|
8148
|
+
status: "active",
|
|
8149
|
+
originNode: "central"
|
|
8150
|
+
});
|
|
8151
|
+
}
|
|
8152
|
+
}
|
|
8153
|
+
/** 保留 close 签名(JSON 落盘已逐次持久化)。 */
|
|
8154
|
+
close() {
|
|
8155
|
+
this.save();
|
|
8156
|
+
}
|
|
8157
|
+
};
|
|
8158
|
+
|
|
8159
|
+
// src/brain/curator.ts
|
|
8160
|
+
init_esm_shims();
|
|
8161
|
+
var BrainCurator = class {
|
|
8162
|
+
constructor(ruleStore, skillManager) {
|
|
8163
|
+
this.ruleStore = ruleStore;
|
|
8164
|
+
this.skillManager = skillManager;
|
|
8165
|
+
}
|
|
8166
|
+
ruleStore;
|
|
8167
|
+
skillManager;
|
|
8168
|
+
/**
|
|
8169
|
+
* 执行一次维护,返回报告。
|
|
8170
|
+
*
|
|
8171
|
+
* autoApprove 默认 true:brain 提取的规则达阈值后自动生效(学习闭环);
|
|
8172
|
+
* 需人工审核时设 false(由 Console 手动 approve)。
|
|
8173
|
+
*/
|
|
8174
|
+
runMaintenance(opts = {}) {
|
|
8175
|
+
const {
|
|
8176
|
+
staleRuleDays = 30,
|
|
8177
|
+
archiveRuleDays = 90,
|
|
8178
|
+
staleSkillDays = 60,
|
|
8179
|
+
disputeThreshold = 3,
|
|
8180
|
+
autoApprove = true
|
|
8181
|
+
} = opts;
|
|
8182
|
+
const autoApproved = autoApprove ? this.autoApproveDrafts() : [];
|
|
8183
|
+
this.ruleStore.markStale(staleRuleDays);
|
|
8184
|
+
this.ruleStore.markArchived(archiveRuleDays);
|
|
8185
|
+
this.ruleStore.autoDemoteDisputed(disputeThreshold);
|
|
8186
|
+
this.skillManager.markStale(staleSkillDays);
|
|
8187
|
+
return {
|
|
8188
|
+
rule_stats: this.ruleStore.getStats(),
|
|
8189
|
+
auto_approved: autoApproved
|
|
8190
|
+
};
|
|
8191
|
+
}
|
|
8192
|
+
/** draft → active(仅未 disputed 的 draft)。返回被激活规则的 pattern 前缀。 */
|
|
8193
|
+
autoApproveDrafts() {
|
|
8194
|
+
const approved = [];
|
|
8195
|
+
let drafts;
|
|
8196
|
+
try {
|
|
8197
|
+
drafts = this.ruleStore.getDraftRules();
|
|
8198
|
+
} catch {
|
|
8199
|
+
return approved;
|
|
8200
|
+
}
|
|
8201
|
+
for (const rule of drafts) {
|
|
8202
|
+
try {
|
|
8203
|
+
this.ruleStore.approveRule(rule.id);
|
|
8204
|
+
approved.push(rule.pattern.slice(0, 60));
|
|
8205
|
+
} catch {
|
|
8206
|
+
}
|
|
8207
|
+
}
|
|
8208
|
+
return approved;
|
|
8209
|
+
}
|
|
8210
|
+
};
|
|
8211
|
+
|
|
8212
|
+
// src/brain/contributor.ts
|
|
8213
|
+
init_esm_shims();
|
|
8214
|
+
init_asset_dir();
|
|
8215
|
+
import { dirname as dirname9 } from "path";
|
|
8216
|
+
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
8217
|
+
var __dirname8 = dirname9(fileURLToPath9(import.meta.url));
|
|
8218
|
+
|
|
7672
8219
|
// src/brain/index.ts
|
|
7673
8220
|
function checkBrainRules(store, opts) {
|
|
7674
8221
|
if (!store) return null;
|
|
@@ -7715,6 +8262,10 @@ function checkLayer2Context(pattern, actionHistory, actionDetail) {
|
|
|
7715
8262
|
return true;
|
|
7716
8263
|
}
|
|
7717
8264
|
|
|
8265
|
+
// src/engine/coordinator.ts
|
|
8266
|
+
init_asset_dir();
|
|
8267
|
+
init_runtime_config_bridge();
|
|
8268
|
+
|
|
7718
8269
|
// src/engine/formal-engine/index.ts
|
|
7719
8270
|
init_esm_shims();
|
|
7720
8271
|
|
|
@@ -8684,20 +9235,69 @@ function matchesAny(cmd, patterns) {
|
|
|
8684
9235
|
}
|
|
8685
9236
|
var _brainStore = null;
|
|
8686
9237
|
var _brainExtractor = null;
|
|
9238
|
+
var _brainSkillManager = null;
|
|
9239
|
+
var _brainCurator = null;
|
|
8687
9240
|
var _brainInitialized = false;
|
|
8688
|
-
|
|
9241
|
+
var _brainLastMaintenance = 0;
|
|
9242
|
+
var BRAIN_MAINT_INTERVAL_MS = 3e5;
|
|
9243
|
+
function getBrainStore(osType) {
|
|
8689
9244
|
if (!isBrainEnabled()) return null;
|
|
8690
9245
|
if (!_brainInitialized) {
|
|
8691
9246
|
_brainStore = new BrainRuleStore();
|
|
8692
|
-
|
|
9247
|
+
const skillsDir = resolveAssetDir(__dirname, "skills");
|
|
9248
|
+
_brainSkillManager = skillsDir ? new BrainSkillManager(skillsDir) : null;
|
|
9249
|
+
_brainExtractor = new PatternExtractor(_brainStore, {
|
|
9250
|
+
review: createBrainReviewer(),
|
|
9251
|
+
// 内置规则去重集(对齐 Python coordinator._ensure_brain 收集的 builtin_patterns):
|
|
9252
|
+
// 危险命令正则 + 敏感路径(deny_* 或 credential)。
|
|
9253
|
+
// 历史 bug:TS 这里只传 review,不传 builtinPatterns → 提取器拿不到内置规则,
|
|
9254
|
+
// `isCoveredByBuiltin` 恒 False → 把 L1/L2 本来就能拦的案例重复沉淀成 Brain 规则。
|
|
9255
|
+
builtinPatterns: collectBrainBuiltinPatterns(osType),
|
|
9256
|
+
skillManager: _brainSkillManager
|
|
9257
|
+
});
|
|
9258
|
+
if (_brainSkillManager) {
|
|
9259
|
+
_brainCurator = new BrainCurator(_brainStore, _brainSkillManager);
|
|
9260
|
+
}
|
|
8693
9261
|
_brainInitialized = true;
|
|
8694
9262
|
}
|
|
9263
|
+
const now = Date.now();
|
|
9264
|
+
if (_brainCurator && now - _brainLastMaintenance >= BRAIN_MAINT_INTERVAL_MS) {
|
|
9265
|
+
try {
|
|
9266
|
+
_brainCurator.runMaintenance({ autoApprove: true });
|
|
9267
|
+
} catch {
|
|
9268
|
+
}
|
|
9269
|
+
_brainLastMaintenance = now;
|
|
9270
|
+
}
|
|
8695
9271
|
return _brainStore;
|
|
8696
9272
|
}
|
|
9273
|
+
function collectBrainBuiltinPatterns(osType) {
|
|
9274
|
+
const bridge = runtimeConfigBridge.current;
|
|
9275
|
+
if (!bridge) return [];
|
|
9276
|
+
const out = [];
|
|
9277
|
+
try {
|
|
9278
|
+
for (const cp of bridge.effectiveDangerousPatterns(osType)) {
|
|
9279
|
+
if (cp?.pattern?.source) out.push(cp.pattern.source);
|
|
9280
|
+
}
|
|
9281
|
+
} catch {
|
|
9282
|
+
}
|
|
9283
|
+
try {
|
|
9284
|
+
for (const sp of bridge.effectiveSensitivePaths(osType)) {
|
|
9285
|
+
const denyMode = String(sp?.denyMode || "");
|
|
9286
|
+
if (["deny_both", "deny_write", "deny_read"].includes(denyMode) || sp?.credential) {
|
|
9287
|
+
if (sp?.path) out.push(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
9288
|
+
}
|
|
9289
|
+
}
|
|
9290
|
+
} catch {
|
|
9291
|
+
}
|
|
9292
|
+
return out.filter(Boolean);
|
|
9293
|
+
}
|
|
8697
9294
|
function _resetBrainForTest() {
|
|
8698
9295
|
_brainStore = null;
|
|
8699
9296
|
_brainExtractor = null;
|
|
9297
|
+
_brainSkillManager = null;
|
|
9298
|
+
_brainCurator = null;
|
|
8700
9299
|
_brainInitialized = false;
|
|
9300
|
+
_brainLastMaintenance = 0;
|
|
8701
9301
|
}
|
|
8702
9302
|
var formalEngine = new FormalEngine();
|
|
8703
9303
|
async function runAnalysis(input) {
|
|
@@ -8750,7 +9350,7 @@ async function runAnalysis(input) {
|
|
|
8750
9350
|
}
|
|
8751
9351
|
}
|
|
8752
9352
|
}
|
|
8753
|
-
const brainStore = getBrainStore();
|
|
9353
|
+
const brainStore = getBrainStore(input.osType);
|
|
8754
9354
|
if (brainStore) {
|
|
8755
9355
|
const brainL1 = checkBrainRules(brainStore, {
|
|
8756
9356
|
layer: 1,
|