@kenhkl/agent-moss 0.16.9 → 0.16.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/cli.d.ts +19 -1
- package/dist/cli.js +2605 -478
- package/dist/data/agent_moss.service +4 -0
- 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 +917 -208
- package/dist/server.d.ts +18 -1
- package/dist/server.js +1809 -312
- package/dist/static/index.html +26 -1
- package/package.json +2 -1
|
@@ -394,6 +394,52 @@ var init_runtime_config_bridge = __esm({
|
|
|
394
394
|
}
|
|
395
395
|
});
|
|
396
396
|
|
|
397
|
+
// src/infra/rules-mode.ts
|
|
398
|
+
function truthy(name) {
|
|
399
|
+
return TRUTHY.has((process.env[name] || "").trim().toLowerCase());
|
|
400
|
+
}
|
|
401
|
+
function getRulesMode() {
|
|
402
|
+
if (cached !== null) return cached;
|
|
403
|
+
const raw = (process.env[ENV_RULES_MODE] || "").trim().toLowerCase();
|
|
404
|
+
if (raw) {
|
|
405
|
+
if (VALID_MODES.has(raw)) {
|
|
406
|
+
cached = raw;
|
|
407
|
+
} else {
|
|
408
|
+
console.error(
|
|
409
|
+
`[AgentMoss] ${ENV_RULES_MODE}=${JSON.stringify(raw)} \u975E\u6CD5\uFF08\u53EA\u652F\u6301 hot / cold / builtin\uFF09\uFF1B\u4E3A\u5B89\u5168\u8D77\u89C1\u6309 builtin\uFF08\u4EC5\u5185\u7F6E\u89C4\u5219\uFF09\u5904\u7406`
|
|
410
|
+
);
|
|
411
|
+
cached = "builtin";
|
|
412
|
+
}
|
|
413
|
+
return cached;
|
|
414
|
+
}
|
|
415
|
+
if (truthy(ENV_RULES_BUILTIN_ONLY)) cached = "builtin";
|
|
416
|
+
else if (truthy(ENV_DISABLE_RULE_HOT_RELOAD)) cached = "cold";
|
|
417
|
+
else cached = "hot";
|
|
418
|
+
return cached;
|
|
419
|
+
}
|
|
420
|
+
function isHotReloadEnabled() {
|
|
421
|
+
return getRulesMode() === "hot";
|
|
422
|
+
}
|
|
423
|
+
function isRuntimeConfigDisabled() {
|
|
424
|
+
return getRulesMode() !== "hot";
|
|
425
|
+
}
|
|
426
|
+
function isBuiltinOnly() {
|
|
427
|
+
return getRulesMode() === "builtin";
|
|
428
|
+
}
|
|
429
|
+
var ENV_RULES_MODE, ENV_RULES_BUILTIN_ONLY, ENV_DISABLE_RULE_HOT_RELOAD, TRUTHY, VALID_MODES, cached;
|
|
430
|
+
var init_rules_mode = __esm({
|
|
431
|
+
"src/infra/rules-mode.ts"() {
|
|
432
|
+
"use strict";
|
|
433
|
+
init_esm_shims();
|
|
434
|
+
ENV_RULES_MODE = "AGENT_MOSS_RULES_MODE";
|
|
435
|
+
ENV_RULES_BUILTIN_ONLY = "AGENT_MOSS_RULES_BUILTIN_ONLY";
|
|
436
|
+
ENV_DISABLE_RULE_HOT_RELOAD = "AGENT_MOSS_DISABLE_RULE_HOT_RELOAD";
|
|
437
|
+
TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
438
|
+
VALID_MODES = /* @__PURE__ */ new Set(["hot", "cold", "builtin"]);
|
|
439
|
+
cached = null;
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
397
443
|
// src/infra/rules-loader.ts
|
|
398
444
|
import { readFileSync as readFileSync2, existsSync, readdirSync, statSync } from "fs";
|
|
399
445
|
import { resolve, dirname, join as join2 } from "path";
|
|
@@ -406,58 +452,64 @@ function parseDirEnv(envName) {
|
|
|
406
452
|
if (!raw) return [];
|
|
407
453
|
return raw.split(/[,;]/).map((s) => s.trim()).filter(Boolean).map((s) => resolve(s));
|
|
408
454
|
}
|
|
409
|
-
function
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
}
|
|
455
|
+
function rulesDirs() {
|
|
456
|
+
if (isBuiltinOnly()) return [];
|
|
457
|
+
const dirs = [];
|
|
458
|
+
for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
|
|
459
|
+
dirs.push(resolve(dataRootDir(), "rules"));
|
|
460
|
+
return [...new Set(dirs)];
|
|
424
461
|
}
|
|
425
462
|
function findRulesFile(name) {
|
|
426
|
-
for (const dir of overrideRulesDirs()) {
|
|
427
|
-
const p = resolve(dir, name);
|
|
428
|
-
if (existsSync(p)) return p;
|
|
429
|
-
}
|
|
430
463
|
for (const dir of RULES_DIR_CANDS) {
|
|
431
464
|
const p = resolve(dir, name);
|
|
432
465
|
if (existsSync(p)) return p;
|
|
433
466
|
}
|
|
434
467
|
return null;
|
|
435
468
|
}
|
|
436
|
-
function isOverridePath(name, p) {
|
|
437
|
-
return overrideRulesDirs().some((dir) => resolve(dir, name) === p);
|
|
438
|
-
}
|
|
439
469
|
function loadJson(name) {
|
|
470
|
+
if (!isHotReloadEnabled()) {
|
|
471
|
+
const cached2 = frozenJsonCache.get(name);
|
|
472
|
+
if (cached2 !== void 0) return cached2;
|
|
473
|
+
}
|
|
440
474
|
const p = findRulesFile(name);
|
|
441
475
|
if (!p) return {};
|
|
442
|
-
const isOverride = isOverridePath(name, p);
|
|
443
|
-
let data;
|
|
444
476
|
try {
|
|
445
|
-
|
|
477
|
+
const parsed = JSON.parse(readFileSync2(p, "utf-8")) || {};
|
|
478
|
+
if (!isHotReloadEnabled()) frozenJsonCache.set(name, parsed);
|
|
479
|
+
return parsed;
|
|
446
480
|
} catch {
|
|
447
|
-
if (isOverride) warnOverride(name, p, {});
|
|
448
481
|
return {};
|
|
449
482
|
}
|
|
450
|
-
if (isOverride) warnOverride(name, p, data);
|
|
451
|
-
return data;
|
|
452
483
|
}
|
|
453
484
|
function resolveScenarioRulesFile(scenarioId) {
|
|
454
485
|
const rel = join2("scenarios", scenarioId, "rules.json");
|
|
455
|
-
for (const dir of
|
|
486
|
+
for (const dir of rulesDirs()) {
|
|
456
487
|
const p = resolve(dir, rel);
|
|
457
|
-
if (existsSync(p))
|
|
488
|
+
if (!existsSync(p)) continue;
|
|
489
|
+
let reason = "";
|
|
490
|
+
try {
|
|
491
|
+
reason = invalidScenarioRulesReason(JSON.parse(readFileSync2(p, "utf-8")) || {});
|
|
492
|
+
} catch (err) {
|
|
493
|
+
reason = `\u8BFB\u53D6/\u89E3\u6790\u5931\u8D25: ${String(err)}`;
|
|
494
|
+
}
|
|
495
|
+
if (reason) {
|
|
496
|
+
console.error(
|
|
497
|
+
`[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}
|
|
498
|
+
\u539F\u56E0: ${reason}`
|
|
499
|
+
);
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
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`);
|
|
503
|
+
return p;
|
|
458
504
|
}
|
|
459
505
|
return null;
|
|
460
506
|
}
|
|
507
|
+
function invalidScenarioRulesReason(data) {
|
|
508
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
509
|
+
return `\u9876\u5C42\u5FC5\u987B\u662F JSON \u5BF9\u8C61\uFF0C\u5B9E\u9645\u662F ${Array.isArray(data) ? "array" : typeof data}`;
|
|
510
|
+
}
|
|
511
|
+
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");
|
|
512
|
+
}
|
|
461
513
|
function loadL1() {
|
|
462
514
|
return loadJson("l1_rules.json");
|
|
463
515
|
}
|
|
@@ -465,10 +517,40 @@ function loadL2() {
|
|
|
465
517
|
return loadJson("l2_rules.json");
|
|
466
518
|
}
|
|
467
519
|
function loadL3() {
|
|
468
|
-
|
|
520
|
+
const data = { ...loadJson("l3_rules.json") };
|
|
521
|
+
try {
|
|
522
|
+
const ov = loadOverrideRules().skills;
|
|
523
|
+
const ex = loadExtraRules().skills || {};
|
|
524
|
+
if (ov && typeof ov === "object") {
|
|
525
|
+
for (const [cat, skills] of Object.entries(ov)) data[cat] = { ...skills };
|
|
526
|
+
}
|
|
527
|
+
if (ex && typeof ex === "object") {
|
|
528
|
+
for (const [cat, skills] of Object.entries(ex)) data[cat] = { ...data[cat] || {}, ...skills };
|
|
529
|
+
}
|
|
530
|
+
} catch {
|
|
531
|
+
}
|
|
532
|
+
return data;
|
|
533
|
+
}
|
|
534
|
+
function loadMCRaw() {
|
|
535
|
+
const mc = { ...loadJson("mc_rules.json") };
|
|
536
|
+
let rules = [...mc.rules || []];
|
|
537
|
+
try {
|
|
538
|
+
const ov = loadOverrideRules().ltl_rules;
|
|
539
|
+
const ex = loadExtraRules().ltl_rules || [];
|
|
540
|
+
if (Array.isArray(ov)) rules = [...ov];
|
|
541
|
+
if (Array.isArray(ex) && ex.length > 0) {
|
|
542
|
+
const byId = /* @__PURE__ */ new Map();
|
|
543
|
+
for (const r of rules) if (r && r.id) byId.set(r.id, r);
|
|
544
|
+
for (const r of ex) if (r && r.id) byId.set(r.id, r);
|
|
545
|
+
rules = [...byId.values()];
|
|
546
|
+
}
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
549
|
+
mc.rules = rules;
|
|
550
|
+
return mc;
|
|
469
551
|
}
|
|
470
552
|
function loadMC() {
|
|
471
|
-
const mc =
|
|
553
|
+
const mc = loadMCRaw();
|
|
472
554
|
const rules = mc.rules || [];
|
|
473
555
|
try {
|
|
474
556
|
const rt = runtimeConfigBridge.current;
|
|
@@ -488,162 +570,297 @@ function loadMC() {
|
|
|
488
570
|
return mc;
|
|
489
571
|
}
|
|
490
572
|
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;
|
|
573
|
+
const data = { ...loadJson("tool_registry.json") };
|
|
574
|
+
let tools = { ...data.tools || {} };
|
|
499
575
|
try {
|
|
500
|
-
|
|
576
|
+
const ov = loadOverrideRules().tools;
|
|
577
|
+
const ex = loadExtraRules().tools || {};
|
|
578
|
+
if (ov && typeof ov === "object") tools = { ...ov };
|
|
579
|
+
if (ex && typeof ex === "object") tools = { ...tools, ...ex };
|
|
501
580
|
} catch {
|
|
502
|
-
return out;
|
|
503
581
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
582
|
+
data.tools = tools;
|
|
583
|
+
return data;
|
|
584
|
+
}
|
|
585
|
+
function scanStructEntry(cat, value, path5) {
|
|
586
|
+
if (cat === "skills") {
|
|
587
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
588
|
+
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`);
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
const out = {};
|
|
592
|
+
for (const [skCat, skills] of Object.entries(value)) {
|
|
593
|
+
if (!skills || typeof skills !== "object" || Array.isArray(skills)) {
|
|
594
|
+
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`);
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
const keep = {};
|
|
598
|
+
for (const [name, kws] of Object.entries(skills)) {
|
|
599
|
+
if (Array.isArray(kws)) keep[name] = kws;
|
|
600
|
+
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`);
|
|
601
|
+
}
|
|
602
|
+
if (Object.keys(keep).length > 0) out[skCat] = keep;
|
|
511
603
|
}
|
|
512
|
-
|
|
513
|
-
else if (name.toLowerCase().endsWith(".json")) out.push(p);
|
|
604
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
514
605
|
}
|
|
515
|
-
|
|
606
|
+
if (cat === "ltl_rules") {
|
|
607
|
+
if (!Array.isArray(value)) {
|
|
608
|
+
console.error(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7684 ltl_rules \u5E94\u4E3A\u5BF9\u8C61\u6570\u7EC4 \u2014\u2014 \u5DF2\u8DF3\u8FC7\u3002`);
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
const keep = value.filter((r) => r && typeof r === "object" && r.id);
|
|
612
|
+
if (keep.length !== value.length) {
|
|
613
|
+
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`);
|
|
614
|
+
}
|
|
615
|
+
return keep.length > 0 ? keep : null;
|
|
616
|
+
}
|
|
617
|
+
if (cat === "tools") {
|
|
618
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
619
|
+
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`);
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
const keep = {};
|
|
623
|
+
for (const [k, v] of Object.entries(value)) {
|
|
624
|
+
if (v && typeof v === "object" && !Array.isArray(v)) keep[k] = v;
|
|
625
|
+
else console.warn(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path5} \u7684 tools.${k} \u4E0D\u662F\u5BF9\u8C61 \u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u6761\u3002`);
|
|
626
|
+
}
|
|
627
|
+
return Object.keys(keep).length > 0 ? keep : null;
|
|
628
|
+
}
|
|
629
|
+
return null;
|
|
516
630
|
}
|
|
517
|
-
function
|
|
518
|
-
const
|
|
519
|
-
const
|
|
520
|
-
|
|
631
|
+
function scanRulesFile(path5, kind) {
|
|
632
|
+
const out = {};
|
|
633
|
+
const label = kind === "override" ? "\u8986\u76D6" : "\u8FFD\u52A0";
|
|
634
|
+
let parsed;
|
|
635
|
+
try {
|
|
636
|
+
parsed = JSON.parse(readFileSync2(path5, "utf-8"));
|
|
637
|
+
} catch (e) {
|
|
638
|
+
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}`);
|
|
639
|
+
return out;
|
|
640
|
+
}
|
|
521
641
|
const classifyFlat = (r) => {
|
|
522
642
|
for (const [keys, cat] of EXTRA_FLAT_FIELD) {
|
|
523
643
|
if (r && typeof r === "object" && keys.every((k) => r[k])) return cat;
|
|
524
644
|
}
|
|
525
645
|
return null;
|
|
526
646
|
};
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
647
|
+
let pairs = [];
|
|
648
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
649
|
+
pairs = Object.entries(parsed);
|
|
650
|
+
} else if (Array.isArray(parsed)) {
|
|
651
|
+
let flatBad = 0;
|
|
652
|
+
for (const r of parsed) {
|
|
653
|
+
const cat = classifyFlat(r);
|
|
654
|
+
if (cat === null) {
|
|
655
|
+
flatBad += 1;
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
pairs.push([cat, [r]]);
|
|
659
|
+
}
|
|
660
|
+
if (flatBad > 0) {
|
|
661
|
+
console.warn(
|
|
662
|
+
`[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`
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
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`);
|
|
667
|
+
return out;
|
|
668
|
+
}
|
|
669
|
+
for (const [cat, rules] of pairs) {
|
|
670
|
+
if (String(cat).startsWith("_")) continue;
|
|
671
|
+
if (STRUCT_KEYS.includes(cat)) {
|
|
672
|
+
const got = scanStructEntry(cat, rules, path5);
|
|
673
|
+
if (got !== null) out[cat] = got;
|
|
533
674
|
continue;
|
|
534
675
|
}
|
|
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 {
|
|
676
|
+
const required = EXTRA_CATEGORIES[cat];
|
|
677
|
+
if (!required) {
|
|
554
678
|
console.warn(
|
|
555
|
-
`[AgentMoss] \
|
|
679
|
+
`[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`
|
|
680
|
+
);
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (rules && typeof rules === "object" && !Array.isArray(rules)) {
|
|
684
|
+
console.error(
|
|
685
|
+
`[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
|
|
686
|
+
\u6B63\u786E\u5199\u6CD5: {"${cat}": [{"pattern": "...", "risk_level": "high", ...}]}
|
|
687
|
+
\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
688
|
);
|
|
557
689
|
continue;
|
|
558
690
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
691
|
+
if (!Array.isArray(rules)) {
|
|
692
|
+
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`);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
const items = [];
|
|
696
|
+
for (const r of rules) {
|
|
697
|
+
if (!r || typeof r !== "object" || !required.every((k) => r[k])) {
|
|
563
698
|
console.warn(
|
|
564
|
-
`[AgentMoss] \
|
|
699
|
+
`[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
700
|
);
|
|
566
701
|
continue;
|
|
567
702
|
}
|
|
568
|
-
|
|
569
|
-
|
|
703
|
+
items.push(r);
|
|
704
|
+
}
|
|
705
|
+
if (items.length > 0) out[cat] = (out[cat] || []).concat(items);
|
|
706
|
+
}
|
|
707
|
+
if (Object.keys(out).length > 0) {
|
|
708
|
+
console.info(
|
|
709
|
+
`[AgentMoss] \u5DF2\u52A0\u8F7D${label}\u89C4\u5219\u6587\u4EF6 ${path5}\uFF1A` + Object.entries(out).map(([k, v]) => `${k}\xD7${v.length}`).join(", ")
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
return out;
|
|
713
|
+
}
|
|
714
|
+
function scanRuleDirs() {
|
|
715
|
+
const override = {};
|
|
716
|
+
const extra = {};
|
|
717
|
+
const loaded = [];
|
|
718
|
+
const ignored = [];
|
|
719
|
+
for (const d of rulesDirs()) {
|
|
720
|
+
if (!existsSync(d)) continue;
|
|
721
|
+
let entries = [];
|
|
722
|
+
try {
|
|
723
|
+
entries = readdirSync(d).sort();
|
|
724
|
+
} catch {
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
for (const name of entries) {
|
|
728
|
+
const f = resolve(d, name);
|
|
729
|
+
try {
|
|
730
|
+
if (statSync(f).isDirectory()) continue;
|
|
731
|
+
} catch {
|
|
570
732
|
continue;
|
|
571
733
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
)
|
|
577
|
-
|
|
734
|
+
if (name === OVERRIDE_FILENAME) {
|
|
735
|
+
const got = scanRulesFile(f, "override");
|
|
736
|
+
if (Object.keys(got).length > 0) loaded.push(f);
|
|
737
|
+
for (const [cat, items] of Object.entries(got)) {
|
|
738
|
+
if (Array.isArray(items)) {
|
|
739
|
+
override[cat] = (override[cat] || []).concat(items);
|
|
740
|
+
} else {
|
|
741
|
+
override[cat] = { ...override[cat] || {}, ...items };
|
|
742
|
+
}
|
|
578
743
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
);
|
|
585
|
-
|
|
744
|
+
} else if (name === EXTRA_FILENAME) {
|
|
745
|
+
const got = scanRulesFile(f, "extra");
|
|
746
|
+
if (Object.values(got).some((v) => v.length > 0)) loaded.push(f);
|
|
747
|
+
for (const [cat, items] of Object.entries(got)) {
|
|
748
|
+
if (Array.isArray(items)) {
|
|
749
|
+
extra[cat] = (extra[cat] || []).concat(items);
|
|
750
|
+
} else {
|
|
751
|
+
extra[cat] = { ...extra[cat] || {}, ...items };
|
|
586
752
|
}
|
|
587
|
-
seenIds.set(rid, p);
|
|
588
753
|
}
|
|
589
|
-
|
|
590
|
-
|
|
754
|
+
} else if (name.toLowerCase().endsWith(".json")) {
|
|
755
|
+
ignored.push(f);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (Object.keys(override).length > 0 && identicalToBuiltin(override)) {
|
|
760
|
+
const key = "override-identical::" + Object.entries(override).map(([c, v]) => `${c}=${v.length}`).sort().join("|");
|
|
761
|
+
if (!warnedIgnored.has(key)) {
|
|
762
|
+
warnedIgnored.add(key);
|
|
763
|
+
console.warn(
|
|
764
|
+
`[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
|
|
765
|
+
\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
|
|
766
|
+
\u53EA\u60F3\u8FFD\u52A0\u89C4\u5219\uFF1A\u5220\u6389 ${OVERRIDE_FILENAME}\uFF0C\u6539\u7528 ${EXTRA_FILENAME}\u3002`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (Object.keys(override).length > 0) {
|
|
771
|
+
const key = "override-summary::" + Object.entries(override).map(([c, v]) => `${c}=${v.length}`).sort().join("|");
|
|
772
|
+
if (!warnedIgnored.has(key)) {
|
|
773
|
+
warnedIgnored.add(key);
|
|
774
|
+
const l1 = loadL1();
|
|
775
|
+
const l2 = loadL2();
|
|
776
|
+
const builtinCounts = {
|
|
777
|
+
dangerous_commands: (l1.dangerous_commands || []).length,
|
|
778
|
+
injection_detection: (l1.injection_detection || []).length,
|
|
779
|
+
sensitive_path_access: (l1.sensitive_path_access || []).length,
|
|
780
|
+
intent_consistency: (l2.intent_consistency || []).length,
|
|
781
|
+
password_modify_consent: (l2.password_modify_consent || []).length,
|
|
782
|
+
user_deletion_consent: (l2.user_deletion_consent || []).length
|
|
783
|
+
};
|
|
784
|
+
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`);
|
|
785
|
+
for (const [cat, items] of Object.entries(override).sort()) {
|
|
786
|
+
console.warn(` \u7C7B\u522B ${cat}\uFF1A\u5185\u7F6E ${builtinCounts[cat] ?? 0} \u6761 \u2192 \u4F60\u7684 ${items.length} \u6761`);
|
|
591
787
|
}
|
|
788
|
+
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`);
|
|
592
789
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
790
|
+
}
|
|
791
|
+
if (ignored.length > 0) {
|
|
792
|
+
const key = "ignored::" + ignored.join("|");
|
|
793
|
+
if (!warnedIgnored.has(key)) {
|
|
794
|
+
warnedIgnored.add(key);
|
|
795
|
+
console.warn(
|
|
796
|
+
`[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(", ")}
|
|
797
|
+
\u8981\u8986\u76D6\u5185\u7F6E\u89C4\u5219 \u2192 \u5185\u5BB9\u653E\u8FDB ${OVERRIDE_FILENAME}\uFF1B\u8981\u8FFD\u52A0 \u2192 \u653E\u8FDB ${EXTRA_FILENAME}\u3002`
|
|
798
|
+
);
|
|
596
799
|
}
|
|
597
800
|
}
|
|
598
|
-
return {
|
|
801
|
+
return { override, extra, loaded, ignored };
|
|
599
802
|
}
|
|
600
|
-
function
|
|
601
|
-
|
|
602
|
-
const dirs =
|
|
603
|
-
|
|
803
|
+
function rulesCached() {
|
|
804
|
+
if (!isHotReloadEnabled() && _RULES_CACHE) return _RULES_CACHE;
|
|
805
|
+
const dirs = rulesDirs();
|
|
806
|
+
const sig = RULES_DIR_ENVS.map((e) => `${e}=${process.env[e] || ""}`).join("|") + "|" + dirs.join("|");
|
|
807
|
+
const files = [];
|
|
604
808
|
for (const d of dirs) {
|
|
605
|
-
if (existsSync(d))
|
|
606
|
-
|
|
809
|
+
if (!existsSync(d)) continue;
|
|
810
|
+
try {
|
|
811
|
+
for (const name of readdirSync(d).sort()) {
|
|
812
|
+
if (name.toLowerCase().endsWith(".json")) files.push(resolve(d, name));
|
|
813
|
+
}
|
|
814
|
+
} catch {
|
|
815
|
+
}
|
|
607
816
|
}
|
|
608
|
-
|
|
609
|
-
const fp = files.map((p) => {
|
|
817
|
+
const fp = JSON.stringify(files.map((p) => {
|
|
610
818
|
try {
|
|
611
819
|
const st = statSync(p);
|
|
612
820
|
return [p, st.mtimeMs, st.size];
|
|
613
821
|
} catch {
|
|
614
822
|
return [p, -1, -1];
|
|
615
823
|
}
|
|
616
|
-
});
|
|
617
|
-
if (
|
|
618
|
-
|
|
824
|
+
}));
|
|
825
|
+
if (_RULES_CACHE && _RULES_CACHE.sig === sig && _RULES_CACHE.fp === fp) return _RULES_CACHE;
|
|
826
|
+
_RULES_CACHE = { sig, fp, ...scanRuleDirs() };
|
|
827
|
+
return _RULES_CACHE;
|
|
828
|
+
}
|
|
829
|
+
function identicalToBuiltin(override) {
|
|
830
|
+
const l1 = loadL1();
|
|
831
|
+
const l2 = loadL2();
|
|
832
|
+
for (const [cat, items] of Object.entries(override)) {
|
|
833
|
+
const builtin = l1[cat] ?? l2[cat];
|
|
834
|
+
if (!Array.isArray(builtin) || builtin.length !== items.length) return false;
|
|
835
|
+
if (JSON.stringify(builtin) !== JSON.stringify(items)) return false;
|
|
619
836
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
837
|
+
return Object.keys(override).length > 0;
|
|
838
|
+
}
|
|
839
|
+
function loadOverrideRules() {
|
|
840
|
+
if (isBuiltinOnly()) return {};
|
|
841
|
+
return rulesCached().override;
|
|
842
|
+
}
|
|
843
|
+
function loadExtraRules() {
|
|
844
|
+
if (isBuiltinOnly()) return {};
|
|
845
|
+
return rulesCached().extra;
|
|
623
846
|
}
|
|
624
|
-
var __filename2, __dirname2,
|
|
847
|
+
var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, RULES_DIR_CANDS, frozenJsonCache, SCENARIO_LIST_KEYS, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
|
|
625
848
|
var init_rules_loader = __esm({
|
|
626
849
|
"src/infra/rules-loader.ts"() {
|
|
627
850
|
"use strict";
|
|
628
851
|
init_esm_shims();
|
|
629
852
|
init_paths();
|
|
853
|
+
init_rules_mode();
|
|
630
854
|
init_runtime_config_bridge();
|
|
631
855
|
__filename2 = fileURLToPath2(import.meta.url);
|
|
632
856
|
__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();
|
|
857
|
+
OVERRIDE_FILENAME = "override.json";
|
|
858
|
+
EXTRA_FILENAME = "extra.json";
|
|
859
|
+
RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
|
|
641
860
|
RULES_DIR_CANDS = (() => {
|
|
642
861
|
const cands = [
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
// mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
|
|
646
|
-
resolve(dataRootDir(), "rules"),
|
|
862
|
+
// 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
|
|
863
|
+
// 规则目录只认 override.json / extra.json(见 rulesDirs())。
|
|
647
864
|
// dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
|
|
648
865
|
resolve(__dirname2, "../rules"),
|
|
649
866
|
resolve(__dirname2, "rules"),
|
|
@@ -656,6 +873,8 @@ var init_rules_loader = __esm({
|
|
|
656
873
|
];
|
|
657
874
|
return cands.filter((c) => existsSync(c));
|
|
658
875
|
})();
|
|
876
|
+
frozenJsonCache = /* @__PURE__ */ new Map();
|
|
877
|
+
SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
|
|
659
878
|
EXTRA_CATEGORIES = {
|
|
660
879
|
dangerous_commands: ["pattern"],
|
|
661
880
|
injection_detection: ["keyword", "lang"],
|
|
@@ -664,6 +883,7 @@ var init_rules_loader = __esm({
|
|
|
664
883
|
password_modify_consent: ["pattern"],
|
|
665
884
|
user_deletion_consent: ["pattern"]
|
|
666
885
|
};
|
|
886
|
+
STRUCT_KEYS = ["skills", "ltl_rules", "tools"];
|
|
667
887
|
EXTRA_FLAT_FIELD = [
|
|
668
888
|
[["path"], "sensitive_path_access"],
|
|
669
889
|
[["keyword", "lang"], "injection_detection"],
|
|
@@ -671,7 +891,8 @@ var init_rules_loader = __esm({
|
|
|
671
891
|
[["pattern", "risk_level"], "dangerous_commands"],
|
|
672
892
|
[["pattern", "risk_type"], "dangerous_commands"]
|
|
673
893
|
];
|
|
674
|
-
|
|
894
|
+
_RULES_CACHE = null;
|
|
895
|
+
warnedIgnored = /* @__PURE__ */ new Set();
|
|
675
896
|
}
|
|
676
897
|
});
|
|
677
898
|
|
|
@@ -1735,6 +1956,7 @@ function userSkillsDir() {
|
|
|
1735
1956
|
return resolve3(getDataRoot(), "agent_moss_skills");
|
|
1736
1957
|
}
|
|
1737
1958
|
function runtimeCustomSkillKeywords() {
|
|
1959
|
+
if (isRuntimeConfigDisabled()) return {};
|
|
1738
1960
|
try {
|
|
1739
1961
|
const rt = runtimeConfigBridge.current?.loadRuntimeConfig?.();
|
|
1740
1962
|
if (!rt) return {};
|
|
@@ -1752,6 +1974,7 @@ function runtimeCustomSkillKeywords() {
|
|
|
1752
1974
|
}
|
|
1753
1975
|
}
|
|
1754
1976
|
function loadUserSkill(name) {
|
|
1977
|
+
if (isRuntimeConfigDisabled()) return "";
|
|
1755
1978
|
if (userSkillCache[name] !== void 0) return userSkillCache[name];
|
|
1756
1979
|
try {
|
|
1757
1980
|
const p = resolve3(userSkillsDir(), `${name}.md`);
|
|
@@ -1820,6 +2043,7 @@ var init_skill_loader = __esm({
|
|
|
1820
2043
|
init_esm_shims();
|
|
1821
2044
|
init_rules_loader();
|
|
1822
2045
|
init_paths();
|
|
2046
|
+
init_rules_mode();
|
|
1823
2047
|
init_asset_dir();
|
|
1824
2048
|
init_runtime_config_bridge();
|
|
1825
2049
|
__filename3 = fileURLToPath3(import.meta.url);
|
|
@@ -1880,6 +2104,7 @@ __export(runtime_config_exports, {
|
|
|
1880
2104
|
getEnabledScenarioExtensions: () => getEnabledScenarioExtensions,
|
|
1881
2105
|
getEnvOverrides: () => getEnvOverrides,
|
|
1882
2106
|
getProfileOrDetect: () => getProfileOrDetect,
|
|
2107
|
+
getRuleSourceFingerprint: () => getRuleSourceFingerprint,
|
|
1883
2108
|
getRuntimeConfigPath: () => getRuntimeConfigPath,
|
|
1884
2109
|
getScenarioRules: () => getScenarioRules,
|
|
1885
2110
|
hasRuntimeConfig: () => hasRuntimeConfig,
|
|
@@ -1914,7 +2139,22 @@ function getRuntimeConfigPath() {
|
|
|
1914
2139
|
if (envPath) return envPath;
|
|
1915
2140
|
return RUNTIME_CONFIG_PATH;
|
|
1916
2141
|
}
|
|
2142
|
+
function getRuleSourceFingerprint() {
|
|
2143
|
+
if (isRuntimeConfigDisabled()) return `mode:${getRulesMode()}`;
|
|
2144
|
+
const p = getRuntimeConfigPath();
|
|
2145
|
+
try {
|
|
2146
|
+
const st = statSync2(p);
|
|
2147
|
+
return `${p}|${st.mtimeMs}|${st.size}`;
|
|
2148
|
+
} catch {
|
|
2149
|
+
return `${p}|absent`;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
1917
2152
|
function saveRuntimeConfig(config) {
|
|
2153
|
+
if (isRuntimeConfigDisabled()) {
|
|
2154
|
+
throw new Error(
|
|
2155
|
+
`\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF1A\u4FEE\u6539\u89C4\u5219\u4E0D\u4F1A\u751F\u6548\uFF0C\u8BF7\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u5E76\u91CD\u542F\u670D\u52A1\u3002`
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
1918
2158
|
const p = getRuntimeConfigPath();
|
|
1919
2159
|
const dir = p.substring(0, p.lastIndexOf("/"));
|
|
1920
2160
|
if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
|
|
@@ -1938,6 +2178,18 @@ function invalidateRuntimeConfigCache() {
|
|
|
1938
2178
|
hasRuntimeConfigCache.clear();
|
|
1939
2179
|
}
|
|
1940
2180
|
function loadRuntimeConfig() {
|
|
2181
|
+
if (isRuntimeConfigDisabled()) {
|
|
2182
|
+
return {
|
|
2183
|
+
version: 1,
|
|
2184
|
+
layers: { ...DEFAULT_LAYERS },
|
|
2185
|
+
L1_rules: {},
|
|
2186
|
+
L2_rules: {},
|
|
2187
|
+
L3_skills: {},
|
|
2188
|
+
L2_2: {},
|
|
2189
|
+
agent_rules: {},
|
|
2190
|
+
scenario_rules: {}
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
1941
2193
|
const p = getRuntimeConfigPath();
|
|
1942
2194
|
if (!existsSync5(p)) {
|
|
1943
2195
|
const seeded = mergeConfig({}, generateSourceDefaults());
|
|
@@ -1984,6 +2236,7 @@ function loadRuntimeConfig() {
|
|
|
1984
2236
|
function isLayerEnabled(layerKey) {
|
|
1985
2237
|
const disableKey = LAYER_DISABLE_ENV[layerKey];
|
|
1986
2238
|
if (disableKey && process.env[disableKey] === "1") return false;
|
|
2239
|
+
if (isRuntimeConfigDisabled()) return true;
|
|
1987
2240
|
const runtime = loadRuntimeConfig();
|
|
1988
2241
|
const layers = runtime.layers || {};
|
|
1989
2242
|
if (layerKey in layers) return !!layers[layerKey];
|
|
@@ -2029,18 +2282,34 @@ function _mergeUserCustom(primary, runtime, layers, key) {
|
|
|
2029
2282
|
const extra = _userCustomRules(runtime, layers, key);
|
|
2030
2283
|
return extra.length > 0 ? [...primary, ...extra] : [...primary];
|
|
2031
2284
|
}
|
|
2032
|
-
function
|
|
2285
|
+
function _dedup(rules, category) {
|
|
2286
|
+
const keys = _DEDUP_KEYS[category];
|
|
2287
|
+
if (!keys) return rules;
|
|
2288
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2289
|
+
const out = [];
|
|
2290
|
+
for (const r of rules) {
|
|
2291
|
+
const k = keys.map((x) => String(r?.[x] ?? "")).join("\0");
|
|
2292
|
+
if (seen.has(k)) continue;
|
|
2293
|
+
seen.add(k);
|
|
2294
|
+
out.push(r);
|
|
2295
|
+
}
|
|
2296
|
+
return out;
|
|
2297
|
+
}
|
|
2298
|
+
function _mergeRulesFromDirs(primary, category) {
|
|
2299
|
+
let base = [...primary];
|
|
2033
2300
|
try {
|
|
2301
|
+
const override = loadOverrideRules()[category];
|
|
2302
|
+
if (override !== void 0 && override !== null) base = [...override];
|
|
2034
2303
|
const extra = loadExtraRules()[category] || [];
|
|
2035
|
-
return
|
|
2304
|
+
return _dedup([...base, ...extra], category);
|
|
2036
2305
|
} catch {
|
|
2037
|
-
return
|
|
2306
|
+
return base;
|
|
2038
2307
|
}
|
|
2039
2308
|
}
|
|
2040
2309
|
function getEnabledL1CommandPatterns(runtime, osType) {
|
|
2041
2310
|
const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
|
|
2042
2311
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2043
|
-
rules =
|
|
2312
|
+
rules = _mergeRulesFromDirs(rules, "dangerous_commands");
|
|
2044
2313
|
rules = _mergeUserCustom(rules, runtime, "L1_rules", "pattern");
|
|
2045
2314
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2046
2315
|
return rules.map((r) => ({
|
|
@@ -2056,7 +2325,7 @@ function getEnabledL2SensitivePaths(runtime, osType) {
|
|
|
2056
2325
|
cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
|
|
2057
2326
|
}
|
|
2058
2327
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2059
|
-
rules =
|
|
2328
|
+
rules = _mergeRulesFromDirs(rules, "sensitive_path_access");
|
|
2060
2329
|
rules = _mergeUserCustom(rules, runtime, ["L1_rules", "L2_rules"], "path");
|
|
2061
2330
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2062
2331
|
return rules.map((r) => {
|
|
@@ -2068,6 +2337,7 @@ function getEnabledL2SensitivePaths(runtime, osType) {
|
|
|
2068
2337
|
});
|
|
2069
2338
|
}
|
|
2070
2339
|
function hasRuntimeConfig() {
|
|
2340
|
+
if (isRuntimeConfigDisabled()) return false;
|
|
2071
2341
|
const p = getRuntimeConfigPath();
|
|
2072
2342
|
let key;
|
|
2073
2343
|
try {
|
|
@@ -2076,8 +2346,8 @@ function hasRuntimeConfig() {
|
|
|
2076
2346
|
} catch {
|
|
2077
2347
|
return false;
|
|
2078
2348
|
}
|
|
2079
|
-
const
|
|
2080
|
-
if (
|
|
2349
|
+
const cached2 = hasRuntimeConfigCache.get(key);
|
|
2350
|
+
if (cached2 !== void 0) return cached2;
|
|
2081
2351
|
let ok = false;
|
|
2082
2352
|
try {
|
|
2083
2353
|
JSON.parse(readFileSync4(p, "utf-8"));
|
|
@@ -2441,25 +2711,11 @@ function generateSourceDefaults() {
|
|
|
2441
2711
|
};
|
|
2442
2712
|
}
|
|
2443
2713
|
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
2714
|
let mc = {};
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
|
|
2458
|
-
} catch {
|
|
2459
|
-
mc = {};
|
|
2460
|
-
}
|
|
2461
|
-
break;
|
|
2462
|
-
}
|
|
2715
|
+
try {
|
|
2716
|
+
mc = loadMCRaw();
|
|
2717
|
+
} catch {
|
|
2718
|
+
mc = {};
|
|
2463
2719
|
}
|
|
2464
2720
|
const rules = mc.rules || [];
|
|
2465
2721
|
return {
|
|
@@ -2688,7 +2944,7 @@ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
|
|
|
2688
2944
|
function getEnabledL1InjectionKeywords(runtime, osType) {
|
|
2689
2945
|
const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
|
|
2690
2946
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2691
|
-
rules =
|
|
2947
|
+
rules = _mergeRulesFromDirs(rules, "injection_detection");
|
|
2692
2948
|
rules = _mergeUserCustom(rules, runtime, "L1_rules", "keyword");
|
|
2693
2949
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2694
2950
|
return rules.map((r) => ({
|
|
@@ -2700,7 +2956,7 @@ function getEnabledL1InjectionKeywords(runtime, osType) {
|
|
|
2700
2956
|
function getEnabledL2IntentPatterns(runtime, osType) {
|
|
2701
2957
|
const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
|
|
2702
2958
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2703
|
-
rules =
|
|
2959
|
+
rules = _mergeRulesFromDirs(rules, "intent_consistency");
|
|
2704
2960
|
rules = _mergeUserCustom(rules, runtime, "L2_rules", "intent_keywords");
|
|
2705
2961
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2706
2962
|
return rules.map((r) => ({
|
|
@@ -2712,7 +2968,7 @@ function getEnabledL2IntentPatterns(runtime, osType) {
|
|
|
2712
2968
|
function getEnabledL2PasswordPatterns(runtime) {
|
|
2713
2969
|
const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
|
|
2714
2970
|
const rules = _mergeUserCustom(
|
|
2715
|
-
|
|
2971
|
+
_mergeRulesFromDirs(filterEnabledRules(cat.rules || [], cat.category_enabled), "password_modify_consent"),
|
|
2716
2972
|
runtime,
|
|
2717
2973
|
"L2_rules",
|
|
2718
2974
|
"pattern"
|
|
@@ -2722,7 +2978,7 @@ function getEnabledL2PasswordPatterns(runtime) {
|
|
|
2722
2978
|
function getEnabledL2UserDeletionPatterns(runtime) {
|
|
2723
2979
|
const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
|
|
2724
2980
|
const rules = _mergeUserCustom(
|
|
2725
|
-
|
|
2981
|
+
_mergeRulesFromDirs(filterEnabledRules(cat.rules || [], cat.category_enabled), "user_deletion_consent"),
|
|
2726
2982
|
runtime,
|
|
2727
2983
|
"L2_rules",
|
|
2728
2984
|
"pattern"
|
|
@@ -2759,7 +3015,7 @@ function getEnabledL1CommandPatternsOrDefault(runtime, osType) {
|
|
|
2759
3015
|
return getEnabledL1CommandPatterns(runtime, osType);
|
|
2760
3016
|
}
|
|
2761
3017
|
return _mergeUserCustom(
|
|
2762
|
-
|
|
3018
|
+
_mergeRulesFromDirs(_filterDefaultsByOs(_l1CommandPatternsToRules(), osType), "dangerous_commands"),
|
|
2763
3019
|
runtime,
|
|
2764
3020
|
"L1_rules",
|
|
2765
3021
|
"pattern"
|
|
@@ -2771,7 +3027,7 @@ function getEnabledL1InjectionKeywordsOrDefault(runtime, osType) {
|
|
|
2771
3027
|
return getEnabledL1InjectionKeywords(runtime, osType);
|
|
2772
3028
|
}
|
|
2773
3029
|
return _mergeUserCustom(
|
|
2774
|
-
|
|
3030
|
+
_mergeRulesFromDirs(_l1InjectionKeywordsToRules(), "injection_detection"),
|
|
2775
3031
|
runtime,
|
|
2776
3032
|
"L1_rules",
|
|
2777
3033
|
"keyword"
|
|
@@ -2783,7 +3039,7 @@ function getEnabledL2SensitivePathsOrDefault(runtime, osType) {
|
|
|
2783
3039
|
return getEnabledL2SensitivePaths(runtime, osType);
|
|
2784
3040
|
}
|
|
2785
3041
|
return _mergeUserCustom(
|
|
2786
|
-
|
|
3042
|
+
_mergeRulesFromDirs(_filterDefaultsByOs(_l2SensitivePathsToRules(), osType), "sensitive_path_access"),
|
|
2787
3043
|
runtime,
|
|
2788
3044
|
["L1_rules", "L2_rules"],
|
|
2789
3045
|
"path"
|
|
@@ -2795,7 +3051,7 @@ function getEnabledL2IntentPatternsOrDefault(runtime, osType) {
|
|
|
2795
3051
|
return getEnabledL2IntentPatterns(runtime, osType);
|
|
2796
3052
|
}
|
|
2797
3053
|
return _mergeUserCustom(
|
|
2798
|
-
|
|
3054
|
+
_mergeRulesFromDirs(_filterDefaultsByOs(_l2IntentPatternsToRules(), osType), "intent_consistency"),
|
|
2799
3055
|
runtime,
|
|
2800
3056
|
"L2_rules",
|
|
2801
3057
|
"intent_keywords"
|
|
@@ -2971,6 +3227,11 @@ function deleteCustomRule(layer, category, ruleId) {
|
|
|
2971
3227
|
}
|
|
2972
3228
|
function addCustomSkill(skillId, category, keywords, content) {
|
|
2973
3229
|
_validateSkillId(skillId);
|
|
3230
|
+
if (isRuntimeConfigDisabled()) {
|
|
3231
|
+
throw new Error(
|
|
3232
|
+
`\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF0C\u4E0D\u5141\u8BB8\u65B0\u589E\u81EA\u5B9A\u4E49 skill\u3002`
|
|
3233
|
+
);
|
|
3234
|
+
}
|
|
2974
3235
|
const runtime = loadRuntimeConfig();
|
|
2975
3236
|
const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
|
|
2976
3237
|
try {
|
|
@@ -3186,7 +3447,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
|
|
|
3186
3447
|
saveRuntimeConfig(runtime);
|
|
3187
3448
|
return runtime;
|
|
3188
3449
|
}
|
|
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;
|
|
3450
|
+
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
3451
|
var init_runtime_config = __esm({
|
|
3191
3452
|
"src/infra/runtime-config.ts"() {
|
|
3192
3453
|
"use strict";
|
|
@@ -3197,6 +3458,7 @@ var init_runtime_config = __esm({
|
|
|
3197
3458
|
init_skill_loader();
|
|
3198
3459
|
init_rules_loader();
|
|
3199
3460
|
init_runtime_config_bridge();
|
|
3461
|
+
init_rules_mode();
|
|
3200
3462
|
__dirname4 = dirname3(fileURLToPath4(import.meta.url));
|
|
3201
3463
|
ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
|
|
3202
3464
|
RUNTIME_CONFIG_DIR = runtimeDir();
|
|
@@ -3219,6 +3481,14 @@ var init_runtime_config = __esm({
|
|
|
3219
3481
|
deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
|
|
3220
3482
|
};
|
|
3221
3483
|
runtimeConfigCache = null;
|
|
3484
|
+
_DEDUP_KEYS = {
|
|
3485
|
+
dangerous_commands: ["pattern"],
|
|
3486
|
+
password_modify_consent: ["pattern"],
|
|
3487
|
+
user_deletion_consent: ["pattern"],
|
|
3488
|
+
sensitive_path_access: ["path"],
|
|
3489
|
+
injection_detection: ["keyword", "lang"],
|
|
3490
|
+
intent_consistency: ["intent_keywords", "reason"]
|
|
3491
|
+
};
|
|
3222
3492
|
hasRuntimeConfigCache = /* @__PURE__ */ new Map();
|
|
3223
3493
|
_AGENTS_DIR_PROD = join3(__dirname4, "agents");
|
|
3224
3494
|
_AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
|
|
@@ -3244,6 +3514,8 @@ var init_runtime_config = __esm({
|
|
|
3244
3514
|
var config_exports = {};
|
|
3245
3515
|
__export(config_exports, {
|
|
3246
3516
|
detectProvider: () => detectProvider,
|
|
3517
|
+
getAgentMossSetting: () => getAgentMossSetting,
|
|
3518
|
+
getCacheOptions: () => getCacheOptions,
|
|
3247
3519
|
getCustomRules: () => getCustomRules,
|
|
3248
3520
|
getLLMConfig: () => getLLMConfig,
|
|
3249
3521
|
getLLMFailMode: () => getLLMFailMode,
|
|
@@ -3255,7 +3527,10 @@ __export(config_exports, {
|
|
|
3255
3527
|
isHeuristicEnabled: () => isHeuristicEnabled,
|
|
3256
3528
|
isLLMAvailable: () => isLLMAvailable,
|
|
3257
3529
|
isLogicRulesEnabled: () => isLogicRulesEnabled,
|
|
3530
|
+
isPolicyGenEnabled: () => isPolicyGenEnabled,
|
|
3531
|
+
isPolicySaveEnabled: () => isPolicySaveEnabled,
|
|
3258
3532
|
probeLLMConnection: () => probeLLMConnection,
|
|
3533
|
+
reloadCustomRules: () => reloadCustomRules,
|
|
3259
3534
|
updateLLMConfig: () => updateLLMConfig,
|
|
3260
3535
|
userRuleVariantPattern: () => userRuleVariantPattern,
|
|
3261
3536
|
wildcardToRegExp: () => wildcardToRegExp
|
|
@@ -3277,8 +3552,10 @@ function resolveConfigPath() {
|
|
|
3277
3552
|
const userConfig = path2.join(getConfigDir(), "config.json");
|
|
3278
3553
|
if (fs2.existsSync(userConfig)) return userConfig;
|
|
3279
3554
|
const defaultCandidates = [
|
|
3280
|
-
path2.join(
|
|
3281
|
-
path2.join(
|
|
3555
|
+
path2.join(__configModuleDir, "config.json.example"),
|
|
3556
|
+
path2.join(__configModuleDir, "..", "config.json.example"),
|
|
3557
|
+
path2.join(__configModuleDir, "..", "..", "agent_moss", "infra", "config.json.example"),
|
|
3558
|
+
path2.join(__configModuleDir, "..", "..", "..", "agent_moss", "infra", "config.json.example")
|
|
3282
3559
|
];
|
|
3283
3560
|
const defaultConfig = defaultCandidates.find((p) => fs2.existsSync(p));
|
|
3284
3561
|
if (defaultConfig) {
|
|
@@ -3287,8 +3564,8 @@ function resolveConfigPath() {
|
|
|
3287
3564
|
const pkgConfig = loadConfigFile(defaultConfig);
|
|
3288
3565
|
const safeConfig = pkgConfig ? { ...pkgConfig, llm: { ...pkgConfig.llm, api_key: "" } } : {};
|
|
3289
3566
|
fs2.writeFileSync(userConfig, JSON.stringify(safeConfig, null, 4), "utf-8");
|
|
3290
|
-
console.
|
|
3291
|
-
console.
|
|
3567
|
+
console.error(`[AgentMoss] \u5DF2\u521B\u5EFA\u7528\u6237\u914D\u7F6E\u6587\u4EF6: ${userConfig}`);
|
|
3568
|
+
console.error(`[AgentMoss] \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\u914D\u7F6E\u4F60\u7684 LLM API Key`);
|
|
3292
3569
|
}
|
|
3293
3570
|
return userConfig;
|
|
3294
3571
|
}
|
|
@@ -3334,17 +3611,19 @@ function wildcardToRegExp(pattern) {
|
|
|
3334
3611
|
}
|
|
3335
3612
|
function loadCustomRules() {
|
|
3336
3613
|
const rules = [];
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3614
|
+
if (!isBuiltinOnly()) {
|
|
3615
|
+
try {
|
|
3616
|
+
const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
|
|
3617
|
+
if (raw) {
|
|
3618
|
+
const parsed = JSON.parse(raw);
|
|
3619
|
+
if (Array.isArray(parsed)) {
|
|
3620
|
+
for (const r of parsed) {
|
|
3621
|
+
if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
|
|
3622
|
+
}
|
|
3344
3623
|
}
|
|
3345
3624
|
}
|
|
3625
|
+
} catch {
|
|
3346
3626
|
}
|
|
3347
|
-
} catch {
|
|
3348
3627
|
}
|
|
3349
3628
|
for (const rulesPath of USER_RULES_PATHS) {
|
|
3350
3629
|
try {
|
|
@@ -3373,6 +3652,9 @@ function userRuleVariantPattern(pattern) {
|
|
|
3373
3652
|
}
|
|
3374
3653
|
return null;
|
|
3375
3654
|
}
|
|
3655
|
+
function reloadCustomRules() {
|
|
3656
|
+
customRules = loadCustomRules();
|
|
3657
|
+
}
|
|
3376
3658
|
function getLLMConfig() {
|
|
3377
3659
|
return llmConfig;
|
|
3378
3660
|
}
|
|
@@ -3460,7 +3742,33 @@ function layerEnabledOrDefault(key) {
|
|
|
3460
3742
|
}
|
|
3461
3743
|
}
|
|
3462
3744
|
function isBrainEnabled() {
|
|
3463
|
-
|
|
3745
|
+
if (isRuntimeConfigDisabled()) return false;
|
|
3746
|
+
return getAgentMossSetting("AGENT_MOSS_BRAIN_ENABLED", "") === "1";
|
|
3747
|
+
}
|
|
3748
|
+
function getAgentMossSetting(key, def = "") {
|
|
3749
|
+
const envValue = process.env[key];
|
|
3750
|
+
if (envValue !== void 0) return envValue;
|
|
3751
|
+
const cfg = loadConfigFile(resolveConfigPath());
|
|
3752
|
+
if (cfg && key in cfg) return String(cfg[key]);
|
|
3753
|
+
return def;
|
|
3754
|
+
}
|
|
3755
|
+
function isPolicyGenEnabled() {
|
|
3756
|
+
return getAgentMossSetting("AGENT_MOSS_ENABLE_POLICY_GEN", "") === "1";
|
|
3757
|
+
}
|
|
3758
|
+
function getCacheOptions() {
|
|
3759
|
+
let enabled = true;
|
|
3760
|
+
let maxSize = 1e3;
|
|
3761
|
+
const cfg = loadConfigFile(resolveConfigPath());
|
|
3762
|
+
const cache3 = cfg?.cache;
|
|
3763
|
+
if (cache3 && typeof cache3 === "object") {
|
|
3764
|
+
if (typeof cache3.enabled === "boolean") enabled = cache3.enabled;
|
|
3765
|
+
if (typeof cache3.max_size === "number" && cache3.max_size > 0) maxSize = cache3.max_size;
|
|
3766
|
+
}
|
|
3767
|
+
if (process.env.AGENT_MOSS_DISABLE_CACHE === "1") enabled = false;
|
|
3768
|
+
return { enabled, maxSize };
|
|
3769
|
+
}
|
|
3770
|
+
function isPolicySaveEnabled() {
|
|
3771
|
+
return getAgentMossSetting("AGENT_MOSS_SAVE_POLICY", "1") !== "0";
|
|
3464
3772
|
}
|
|
3465
3773
|
function isLogicRulesEnabled() {
|
|
3466
3774
|
if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
|
|
@@ -3469,13 +3777,21 @@ function isLogicRulesEnabled() {
|
|
|
3469
3777
|
function isFormalObserveMode() {
|
|
3470
3778
|
return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
|
|
3471
3779
|
}
|
|
3472
|
-
var timeoutConfig, retryConfig, llmConfig,
|
|
3780
|
+
var __configModuleDir, timeoutConfig, retryConfig, llmConfig, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
|
|
3473
3781
|
var init_config = __esm({
|
|
3474
3782
|
"src/config.ts"() {
|
|
3475
3783
|
"use strict";
|
|
3476
3784
|
init_esm_shims();
|
|
3477
3785
|
init_paths();
|
|
3478
3786
|
init_runtime_config();
|
|
3787
|
+
init_rules_mode();
|
|
3788
|
+
__configModuleDir = (() => {
|
|
3789
|
+
try {
|
|
3790
|
+
return path2.dirname(fileURLToPath5(import.meta.url));
|
|
3791
|
+
} catch {
|
|
3792
|
+
return typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
3793
|
+
}
|
|
3794
|
+
})();
|
|
3479
3795
|
llmConfig = (() => {
|
|
3480
3796
|
const configPath = resolveConfigPath();
|
|
3481
3797
|
const fileConfig = fs2.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
|
|
@@ -3493,13 +3809,6 @@ var init_config = __esm({
|
|
|
3493
3809
|
timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
|
|
3494
3810
|
};
|
|
3495
3811
|
})();
|
|
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
3812
|
USER_RULES_PATHS = [
|
|
3504
3813
|
path2.join(__configModuleDir, "rules", "user_rules.json"),
|
|
3505
3814
|
// 源在 ts/src/rules(一般不存在)
|
|
@@ -3532,7 +3841,9 @@ function resolveAgentsDir() {
|
|
|
3532
3841
|
}
|
|
3533
3842
|
function getAgentExtensions(agentId) {
|
|
3534
3843
|
if (!agentId) return null;
|
|
3535
|
-
|
|
3844
|
+
const fp = getRuleSourceFingerprint();
|
|
3845
|
+
const cached2 = cache.get(agentId);
|
|
3846
|
+
if (cached2 && cached2.fp === fp) return cached2.ext;
|
|
3536
3847
|
let ext = null;
|
|
3537
3848
|
try {
|
|
3538
3849
|
if (hasRuntimeConfig()) {
|
|
@@ -3544,7 +3855,7 @@ function getAgentExtensions(agentId) {
|
|
|
3544
3855
|
const agentsDir = resolveAgentsDir();
|
|
3545
3856
|
const rulesFile = resolve4(agentsDir, agentId, "rules.json");
|
|
3546
3857
|
if (!existsSync6(rulesFile)) {
|
|
3547
|
-
cache.set(agentId, null);
|
|
3858
|
+
cache.set(agentId, { fp, ext: null });
|
|
3548
3859
|
return null;
|
|
3549
3860
|
}
|
|
3550
3861
|
try {
|
|
@@ -3552,16 +3863,16 @@ function getAgentExtensions(agentId) {
|
|
|
3552
3863
|
ext = JSON.parse(raw);
|
|
3553
3864
|
if (!ext.agent_id) {
|
|
3554
3865
|
console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
|
|
3555
|
-
cache.set(agentId, null);
|
|
3866
|
+
cache.set(agentId, { fp, ext: null });
|
|
3556
3867
|
return null;
|
|
3557
3868
|
}
|
|
3558
3869
|
} catch (e) {
|
|
3559
3870
|
console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
|
|
3560
|
-
cache.set(agentId, null);
|
|
3871
|
+
cache.set(agentId, { fp, ext: null });
|
|
3561
3872
|
return null;
|
|
3562
3873
|
}
|
|
3563
3874
|
}
|
|
3564
|
-
cache.set(agentId, ext);
|
|
3875
|
+
cache.set(agentId, { fp, ext });
|
|
3565
3876
|
console.error(
|
|
3566
3877
|
`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills, ${ext.builtin_safe_action_types?.length ?? 0} builtin safe action types`
|
|
3567
3878
|
);
|
|
@@ -3607,7 +3918,9 @@ function resolveScenariosDir() {
|
|
|
3607
3918
|
}
|
|
3608
3919
|
function getScenarioExtensions(scenarioId) {
|
|
3609
3920
|
if (!scenarioId) return null;
|
|
3610
|
-
|
|
3921
|
+
const fp = getRuleSourceFingerprint();
|
|
3922
|
+
const cached2 = cache2.get(scenarioId);
|
|
3923
|
+
if (cached2 && cached2.fp === fp) return cached2.ext;
|
|
3611
3924
|
let ext = null;
|
|
3612
3925
|
let overrideHit = null;
|
|
3613
3926
|
try {
|
|
@@ -3631,7 +3944,7 @@ function getScenarioExtensions(scenarioId) {
|
|
|
3631
3944
|
);
|
|
3632
3945
|
}
|
|
3633
3946
|
if (!existsSync7(rulesFile)) {
|
|
3634
|
-
cache2.set(scenarioId, null);
|
|
3947
|
+
cache2.set(scenarioId, { fp, ext: null });
|
|
3635
3948
|
return null;
|
|
3636
3949
|
}
|
|
3637
3950
|
try {
|
|
@@ -3640,7 +3953,7 @@ function getScenarioExtensions(scenarioId) {
|
|
|
3640
3953
|
const id = data.scenario_id || data.agent_id || "";
|
|
3641
3954
|
if (!id) {
|
|
3642
3955
|
console.warn(`[AgentMoss scenarios] rules.json \u7F3A\u5C11 scenario_id \u5B57\u6BB5: ${rulesFile}`);
|
|
3643
|
-
cache2.set(scenarioId, null);
|
|
3956
|
+
cache2.set(scenarioId, { fp, ext: null });
|
|
3644
3957
|
return null;
|
|
3645
3958
|
}
|
|
3646
3959
|
ext = {
|
|
@@ -3655,11 +3968,11 @@ function getScenarioExtensions(scenarioId) {
|
|
|
3655
3968
|
};
|
|
3656
3969
|
} catch (e) {
|
|
3657
3970
|
console.error(`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} rules.json \u5931\u8D25:`, e.message);
|
|
3658
|
-
cache2.set(scenarioId, null);
|
|
3971
|
+
cache2.set(scenarioId, { fp, ext: null });
|
|
3659
3972
|
return null;
|
|
3660
3973
|
}
|
|
3661
3974
|
}
|
|
3662
|
-
cache2.set(scenarioId, ext);
|
|
3975
|
+
cache2.set(scenarioId, { fp, ext });
|
|
3663
3976
|
console.error(
|
|
3664
3977
|
`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths`
|
|
3665
3978
|
);
|
|
@@ -6980,10 +7293,10 @@ async function runLLMAnalysis(input, previous) {
|
|
|
6980
7293
|
const config = input.llmConfig?.apiKey ? { ...globalConfig, ...input.llmConfig } : globalConfig;
|
|
6981
7294
|
const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
|
|
6982
7295
|
const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
|
|
6983
|
-
const
|
|
6984
|
-
if (
|
|
7296
|
+
const cached2 = llmCache.get(cacheKey);
|
|
7297
|
+
if (cached2) {
|
|
6985
7298
|
console.log("[AgentMoss] LLM cache hit");
|
|
6986
|
-
return
|
|
7299
|
+
return cached2;
|
|
6987
7300
|
}
|
|
6988
7301
|
const matchedSkills = matchSkills(input.aNext.actionType, input.aNext.actionDetail, input.reason);
|
|
6989
7302
|
const templates = loadTemplates();
|
|
@@ -7286,6 +7599,56 @@ var BrainRuleStore = class {
|
|
|
7286
7599
|
getRule(ruleId) {
|
|
7287
7600
|
return this.data.rules.find((r) => r.id === ruleId);
|
|
7288
7601
|
}
|
|
7602
|
+
/** 质疑:active → disputed,并把 disputed_count +1(镜像 Python dispute_rule)。 */
|
|
7603
|
+
disputeRule(ruleId, _reason = "") {
|
|
7604
|
+
const rule = this.getRule(ruleId);
|
|
7605
|
+
if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
|
|
7606
|
+
if (rule.status !== STATUS_ACTIVE) throw new Error(`\u53EA\u6709 active \u72B6\u6001\u53EF\u8D28\u7591\uFF0C\u5F53\u524D: ${rule.status}`);
|
|
7607
|
+
rule.status = STATUS_DISPUTED;
|
|
7608
|
+
rule.disputedCount += 1;
|
|
7609
|
+
this.save();
|
|
7610
|
+
}
|
|
7611
|
+
// ==================== 生命周期(curator 用,镜像 Python rule_store) ====================
|
|
7612
|
+
/** 长期未触发的 active/disputed 规则 → stale。 */
|
|
7613
|
+
markStale(staleDays = 30) {
|
|
7614
|
+
const cutoff = Date.now() - staleDays * 864e5;
|
|
7615
|
+
for (const r of this.data.rules) {
|
|
7616
|
+
if ((r.status === STATUS_ACTIVE || r.status === STATUS_DISPUTED) && r.lastTriggeredAt > 0 && r.lastTriggeredAt < cutoff) {
|
|
7617
|
+
r.status = STATUS_STALE;
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
this.save();
|
|
7621
|
+
}
|
|
7622
|
+
/** 长期 stale 的规则 → archived。 */
|
|
7623
|
+
markArchived(archiveDays = 90) {
|
|
7624
|
+
const cutoff = Date.now() - archiveDays * 864e5;
|
|
7625
|
+
for (const r of this.data.rules) {
|
|
7626
|
+
if (r.status === STATUS_STALE && r.lastTriggeredAt > 0 && r.lastTriggeredAt < cutoff) {
|
|
7627
|
+
r.status = STATUS_ARCHIVED;
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7630
|
+
this.save();
|
|
7631
|
+
}
|
|
7632
|
+
/** disputed 次数达阈值 → 自动降级 stale(自动纠错闭环)。 */
|
|
7633
|
+
autoDemoteDisputed(disputeThreshold = 3) {
|
|
7634
|
+
for (const r of this.data.rules) {
|
|
7635
|
+
if (r.status === STATUS_DISPUTED && r.disputedCount >= disputeThreshold) {
|
|
7636
|
+
r.status = STATUS_STALE;
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7639
|
+
this.save();
|
|
7640
|
+
}
|
|
7641
|
+
// ==================== 中心化预留(镜像 Python) ====================
|
|
7642
|
+
/** 导出本地新增(非 central 来源)的 active 规则。 */
|
|
7643
|
+
getRulesForExport() {
|
|
7644
|
+
return this.data.rules.filter((r) => r.source !== "central" && r.status === STATUS_ACTIVE);
|
|
7645
|
+
}
|
|
7646
|
+
/** 导入中心下发规则(强制标 central 来源,避免被再次回流)。 */
|
|
7647
|
+
importRules(rules) {
|
|
7648
|
+
for (const rule of rules) {
|
|
7649
|
+
this.addRule({ ...rule, source: "central", originNode: "central" });
|
|
7650
|
+
}
|
|
7651
|
+
}
|
|
7289
7652
|
getActiveRules(layer) {
|
|
7290
7653
|
return this.data.rules.filter((r) => r.status === STATUS_ACTIVE && (layer === void 0 || r.layer === layer));
|
|
7291
7654
|
}
|
|
@@ -7386,12 +7749,14 @@ var PatternExtractor = class {
|
|
|
7386
7749
|
minOccurrences;
|
|
7387
7750
|
builtinPatterns;
|
|
7388
7751
|
reviewFn;
|
|
7752
|
+
skillManager;
|
|
7389
7753
|
constructor(ruleStore, opts) {
|
|
7390
7754
|
this.ruleStore = ruleStore;
|
|
7391
7755
|
this.minConfidence = opts?.minConfidence ?? 0.9;
|
|
7392
7756
|
this.minOccurrences = opts?.minOccurrences ?? 3;
|
|
7393
7757
|
this.builtinPatterns = opts?.builtinPatterns ?? [];
|
|
7394
7758
|
this.reviewFn = opts?.review ?? null;
|
|
7759
|
+
this.skillManager = opts?.skillManager ?? null;
|
|
7395
7760
|
}
|
|
7396
7761
|
async extract(event, llmResult) {
|
|
7397
7762
|
const result = { rulesCreated: 0, patternsRecorded: 0, skippedReason: "", deduped: [] };
|
|
@@ -7480,8 +7845,96 @@ var PatternExtractor = class {
|
|
|
7480
7845
|
result.rulesCreated += 1;
|
|
7481
7846
|
}
|
|
7482
7847
|
}
|
|
7848
|
+
if (this.skillManager) {
|
|
7849
|
+
const skill = this.buildSkill(actionDetail, reason, riskType, eventId);
|
|
7850
|
+
if (skill) {
|
|
7851
|
+
const id = this.skillManager.addSkill(skill);
|
|
7852
|
+
if (id) {
|
|
7853
|
+
result.skillsCreated = (result.skillsCreated ?? 0) + 1;
|
|
7854
|
+
}
|
|
7855
|
+
}
|
|
7856
|
+
}
|
|
7483
7857
|
return result;
|
|
7484
7858
|
}
|
|
7859
|
+
/**
|
|
7860
|
+
* 生成候选 Skill;不值得沉淀时返回 null(镜像 Python `_extract_skill` 的三道价值评估)。
|
|
7861
|
+
*
|
|
7862
|
+
* 评估 1:reason 太短(<50 字)说明没有可复用的判断逻辑,不沉淀;
|
|
7863
|
+
* 评估 2:同名 skill 已存在 → 跳过;
|
|
7864
|
+
* 评估 3:与已有 active skill 关键词重叠 >= 2 → 跳过(防同类案例重复沉淀,
|
|
7865
|
+
* 如 cat /etc/shadow 与 cat /etc/sudoers 生成两个近乎重复的 skill)。
|
|
7866
|
+
*/
|
|
7867
|
+
buildSkill(actionDetail, reason, riskType, eventId) {
|
|
7868
|
+
if (!reason || reason.length < 50) return null;
|
|
7869
|
+
if (!riskType) return null;
|
|
7870
|
+
const detailWords = (actionDetail.toLowerCase().match(/[a-z]+/g) || []).slice(0, 3);
|
|
7871
|
+
const name = detailWords.length > 0 ? `brain_${riskType}_${detailWords.join("_")}` : `brain_${riskType}`;
|
|
7872
|
+
if (this.skillManager.getSkillMeta(name)) return null;
|
|
7873
|
+
const keywords = this.extractSkillKeywords(actionDetail, reason, riskType);
|
|
7874
|
+
if (this.isCoveredByExistingSkill(keywords)) return null;
|
|
7875
|
+
return {
|
|
7876
|
+
name,
|
|
7877
|
+
category: this.categorizeRisk(riskType),
|
|
7878
|
+
content: this.generateSkillContent(name, actionDetail, reason, riskType),
|
|
7879
|
+
keywords,
|
|
7880
|
+
sourceEventIds: eventId ? [eventId] : [],
|
|
7881
|
+
status: "active"
|
|
7882
|
+
};
|
|
7883
|
+
}
|
|
7884
|
+
/** 关键词提取(镜像 Python `_extract_keywords`)。 */
|
|
7885
|
+
extractSkillKeywords(actionDetail, reason, riskType) {
|
|
7886
|
+
const keywords = /* @__PURE__ */ new Set();
|
|
7887
|
+
if (riskType) keywords.add(riskType);
|
|
7888
|
+
for (const w of (actionDetail.toLowerCase().match(/[a-z_]{3,}/g) || []).slice(0, 10)) keywords.add(w);
|
|
7889
|
+
for (const w of (reason.toLowerCase().match(/[a-z_]{3,}/g) || []).slice(0, 5)) keywords.add(w);
|
|
7890
|
+
return [...keywords];
|
|
7891
|
+
}
|
|
7892
|
+
/** 与已有 active skill 关键词重叠 >= 2 → 视为已覆盖(镜像 `_is_covered_by_existing_skill`)。 */
|
|
7893
|
+
isCoveredByExistingSkill(keywords) {
|
|
7894
|
+
if (keywords.length === 0) return false;
|
|
7895
|
+
let existing = [];
|
|
7896
|
+
try {
|
|
7897
|
+
existing = this.skillManager.getActiveSkills();
|
|
7898
|
+
} catch {
|
|
7899
|
+
return false;
|
|
7900
|
+
}
|
|
7901
|
+
const cand = new Set(keywords.map((k) => k.toLowerCase()));
|
|
7902
|
+
for (const meta of existing) {
|
|
7903
|
+
const metaKeywords = (meta.keywords || []).map((k) => String(k).toLowerCase());
|
|
7904
|
+
if (metaKeywords.length === 0) continue;
|
|
7905
|
+
const overlap = metaKeywords.filter((k) => cand.has(k)).length;
|
|
7906
|
+
if (overlap >= 2) return true;
|
|
7907
|
+
}
|
|
7908
|
+
return false;
|
|
7909
|
+
}
|
|
7910
|
+
/** 风险类型 → skill 分类(镜像 Python `_categorize_risk`)。 */
|
|
7911
|
+
categorizeRisk(riskType) {
|
|
7912
|
+
if (!riskType) return "edge_case";
|
|
7913
|
+
if (["inject", "backdoor", "persist"].some((k) => riskType.includes(k))) return "attack_pattern";
|
|
7914
|
+
if (["exfil", "leak", "export"].some((k) => riskType.includes(k))) return "attack_pattern";
|
|
7915
|
+
return "defense_strategy";
|
|
7916
|
+
}
|
|
7917
|
+
/** skill 正文(与 Python `_generate_skill_content` 同结构,便于双端对拍)。 */
|
|
7918
|
+
generateSkillContent(name, actionDetail, reason, riskType) {
|
|
7919
|
+
return `# ${name}
|
|
7920
|
+
|
|
7921
|
+
## \u6765\u6E90
|
|
7922
|
+
Brain \u81EA\u52A8\u63D0\u53D6\uFF08LLM \u5206\u6790\u6C89\u6DC0\uFF09
|
|
7923
|
+
|
|
7924
|
+
## \u573A\u666F
|
|
7925
|
+
\u5F53 Agent \u6267\u884C\u4EE5\u4E0B\u7C7B\u578B\u64CD\u4F5C\u65F6\u89E6\u53D1\uFF1A
|
|
7926
|
+
- \u98CE\u9669\u7C7B\u578B: ${riskType}
|
|
7927
|
+
- \u64CD\u4F5C\u7279\u5F81: ${actionDetail.slice(0, 200)}
|
|
7928
|
+
|
|
7929
|
+
## \u5224\u65AD\u4F9D\u636E
|
|
7930
|
+
${reason.slice(0, 500)}
|
|
7931
|
+
|
|
7932
|
+
## \u5904\u7406\u5EFA\u8BAE
|
|
7933
|
+
- \u4F18\u5148\u68C0\u67E5\u64CD\u4F5C\u7684\u4E0A\u4E0B\u6587\u548C\u610F\u56FE
|
|
7934
|
+
- \u7ED3\u5408 action_history \u5224\u65AD\u662F\u5426\u5B58\u5728\u6076\u610F\u94FE
|
|
7935
|
+
- \u5FC5\u8981\u65F6 Deny \u5E76\u8BB0\u5F55\u539F\u56E0
|
|
7936
|
+
`;
|
|
7937
|
+
}
|
|
7485
7938
|
extractPatterns(actionDetail, reason, riskType) {
|
|
7486
7939
|
const patterns = [];
|
|
7487
7940
|
const detailLower = actionDetail.toLowerCase();
|
|
@@ -7669,6 +8122,209 @@ function parseReview(content) {
|
|
|
7669
8122
|
};
|
|
7670
8123
|
}
|
|
7671
8124
|
|
|
8125
|
+
// src/brain/skill-manager.ts
|
|
8126
|
+
init_esm_shims();
|
|
8127
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, renameSync, writeFileSync as writeFileSync4 } from "fs";
|
|
8128
|
+
import { homedir as homedir5 } from "os";
|
|
8129
|
+
import { join as join8 } from "path";
|
|
8130
|
+
import { randomUUID } from "crypto";
|
|
8131
|
+
var DEFAULT_SKILL_META_PATH = join8(homedir5(), ".agentmoss", "brain", "skill_meta.json");
|
|
8132
|
+
var ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
|
|
8133
|
+
function getMetaPath(override) {
|
|
8134
|
+
return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
|
|
8135
|
+
}
|
|
8136
|
+
var BrainSkillManager = class {
|
|
8137
|
+
constructor(skillsDir, metaPath) {
|
|
8138
|
+
this.skillsDir = skillsDir;
|
|
8139
|
+
this.metaPath = getMetaPath(metaPath);
|
|
8140
|
+
try {
|
|
8141
|
+
mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
|
|
8142
|
+
} catch {
|
|
8143
|
+
}
|
|
8144
|
+
this.load();
|
|
8145
|
+
this.cleanupGhostSkills();
|
|
8146
|
+
}
|
|
8147
|
+
skillsDir;
|
|
8148
|
+
skills = [];
|
|
8149
|
+
metaPath;
|
|
8150
|
+
load() {
|
|
8151
|
+
if (!existsSync13(this.metaPath)) return;
|
|
8152
|
+
try {
|
|
8153
|
+
const data = JSON.parse(readFileSync13(this.metaPath, "utf-8"));
|
|
8154
|
+
this.skills = data.skills || [];
|
|
8155
|
+
} catch {
|
|
8156
|
+
this.skills = [];
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
8159
|
+
save() {
|
|
8160
|
+
const out = { version: 1, skills: this.skills };
|
|
8161
|
+
const tmp = `${this.metaPath}.tmp`;
|
|
8162
|
+
try {
|
|
8163
|
+
writeFileSync4(tmp, JSON.stringify(out, null, 2), "utf-8");
|
|
8164
|
+
renameSync(tmp, this.metaPath);
|
|
8165
|
+
} catch {
|
|
8166
|
+
}
|
|
8167
|
+
}
|
|
8168
|
+
/**
|
|
8169
|
+
* 清理"幽灵 skill":meta 记 active 但磁盘 .md 已不存在。
|
|
8170
|
+
* (沿用 Python 的处理:置 stale,避免污染关键词表并阻塞同名新 skill 生成。)
|
|
8171
|
+
*/
|
|
8172
|
+
cleanupGhostSkills() {
|
|
8173
|
+
let changed = false;
|
|
8174
|
+
for (const sk of this.skills) {
|
|
8175
|
+
if (sk.status !== "active") continue;
|
|
8176
|
+
if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
|
|
8177
|
+
sk.status = "stale";
|
|
8178
|
+
changed = true;
|
|
8179
|
+
}
|
|
8180
|
+
}
|
|
8181
|
+
if (changed) this.save();
|
|
8182
|
+
}
|
|
8183
|
+
/** 新增 Skill:写 .md + 记元数据。目录缺失或同名已存在时跳过(返回 "")。 */
|
|
8184
|
+
addSkill(skill) {
|
|
8185
|
+
const id = skill.id || randomUUID();
|
|
8186
|
+
const name = skill.name || `brain_${id.slice(0, 8)}`;
|
|
8187
|
+
if (!existsSync13(this.skillsDir)) return "";
|
|
8188
|
+
const mdPath = join8(this.skillsDir, `${name}.md`);
|
|
8189
|
+
if (existsSync13(mdPath)) return "";
|
|
8190
|
+
try {
|
|
8191
|
+
writeFileSync4(mdPath, skill.content, "utf-8");
|
|
8192
|
+
} catch {
|
|
8193
|
+
return "";
|
|
8194
|
+
}
|
|
8195
|
+
this.skills.push({
|
|
8196
|
+
id,
|
|
8197
|
+
name,
|
|
8198
|
+
category: skill.category || "",
|
|
8199
|
+
keywords: skill.keywords || [],
|
|
8200
|
+
source_event_ids: skill.sourceEventIds || [],
|
|
8201
|
+
status: skill.status || "draft",
|
|
8202
|
+
created_at: skill.createdAt || Date.now() / 1e3,
|
|
8203
|
+
usage_count: skill.usageCount || 0,
|
|
8204
|
+
origin_node: skill.originNode || "local"
|
|
8205
|
+
});
|
|
8206
|
+
this.save();
|
|
8207
|
+
return id;
|
|
8208
|
+
}
|
|
8209
|
+
getSkillMeta(name) {
|
|
8210
|
+
return this.skills.find((s) => s.name === name);
|
|
8211
|
+
}
|
|
8212
|
+
getActiveSkills() {
|
|
8213
|
+
return this.skills.filter((s) => s.status === "active").sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
|
|
8214
|
+
}
|
|
8215
|
+
recordUsage(name) {
|
|
8216
|
+
const sk = this.getSkillMeta(name);
|
|
8217
|
+
if (!sk) return;
|
|
8218
|
+
sk.usage_count = (sk.usage_count || 0) + 1;
|
|
8219
|
+
this.save();
|
|
8220
|
+
}
|
|
8221
|
+
/** 长期未使用(usage_count=0)且创建超过 staleDays 的 active skill → stale。 */
|
|
8222
|
+
markStale(staleDays = 60) {
|
|
8223
|
+
const cutoff = Date.now() / 1e3 - staleDays * 86400;
|
|
8224
|
+
let changed = false;
|
|
8225
|
+
for (const sk of this.skills) {
|
|
8226
|
+
if (sk.status === "active" && (sk.usage_count || 0) === 0 && (sk.created_at || 0) < cutoff) {
|
|
8227
|
+
sk.status = "stale";
|
|
8228
|
+
changed = true;
|
|
8229
|
+
}
|
|
8230
|
+
}
|
|
8231
|
+
if (changed) this.save();
|
|
8232
|
+
}
|
|
8233
|
+
/** 导出本地 active skill(含正文),供回流中心。 */
|
|
8234
|
+
exportSkills() {
|
|
8235
|
+
const out = [];
|
|
8236
|
+
for (const sk of this.skills) {
|
|
8237
|
+
if (sk.origin_node !== "local" || sk.status !== "active") continue;
|
|
8238
|
+
const mdPath = join8(this.skillsDir, `${sk.name}.md`);
|
|
8239
|
+
if (!existsSync13(mdPath)) continue;
|
|
8240
|
+
out.push({
|
|
8241
|
+
name: sk.name,
|
|
8242
|
+
category: sk.category || "",
|
|
8243
|
+
content: readFileSync13(mdPath, "utf-8"),
|
|
8244
|
+
keywords: sk.keywords || []
|
|
8245
|
+
});
|
|
8246
|
+
}
|
|
8247
|
+
return out;
|
|
8248
|
+
}
|
|
8249
|
+
/** 导入中心下发 skill(标 central,避免被再次回流)。 */
|
|
8250
|
+
importSkills(skills) {
|
|
8251
|
+
for (const s of skills) {
|
|
8252
|
+
this.addSkill({
|
|
8253
|
+
name: s.name,
|
|
8254
|
+
category: s.category || "",
|
|
8255
|
+
content: s.content,
|
|
8256
|
+
keywords: s.keywords || [],
|
|
8257
|
+
status: "active",
|
|
8258
|
+
originNode: "central"
|
|
8259
|
+
});
|
|
8260
|
+
}
|
|
8261
|
+
}
|
|
8262
|
+
/** 保留 close 签名(JSON 落盘已逐次持久化)。 */
|
|
8263
|
+
close() {
|
|
8264
|
+
this.save();
|
|
8265
|
+
}
|
|
8266
|
+
};
|
|
8267
|
+
|
|
8268
|
+
// src/brain/curator.ts
|
|
8269
|
+
init_esm_shims();
|
|
8270
|
+
var BrainCurator = class {
|
|
8271
|
+
constructor(ruleStore, skillManager) {
|
|
8272
|
+
this.ruleStore = ruleStore;
|
|
8273
|
+
this.skillManager = skillManager;
|
|
8274
|
+
}
|
|
8275
|
+
ruleStore;
|
|
8276
|
+
skillManager;
|
|
8277
|
+
/**
|
|
8278
|
+
* 执行一次维护,返回报告。
|
|
8279
|
+
*
|
|
8280
|
+
* autoApprove 默认 true:brain 提取的规则达阈值后自动生效(学习闭环);
|
|
8281
|
+
* 需人工审核时设 false(由 Console 手动 approve)。
|
|
8282
|
+
*/
|
|
8283
|
+
runMaintenance(opts = {}) {
|
|
8284
|
+
const {
|
|
8285
|
+
staleRuleDays = 30,
|
|
8286
|
+
archiveRuleDays = 90,
|
|
8287
|
+
staleSkillDays = 60,
|
|
8288
|
+
disputeThreshold = 3,
|
|
8289
|
+
autoApprove = true
|
|
8290
|
+
} = opts;
|
|
8291
|
+
const autoApproved = autoApprove ? this.autoApproveDrafts() : [];
|
|
8292
|
+
this.ruleStore.markStale(staleRuleDays);
|
|
8293
|
+
this.ruleStore.markArchived(archiveRuleDays);
|
|
8294
|
+
this.ruleStore.autoDemoteDisputed(disputeThreshold);
|
|
8295
|
+
this.skillManager.markStale(staleSkillDays);
|
|
8296
|
+
return {
|
|
8297
|
+
rule_stats: this.ruleStore.getStats(),
|
|
8298
|
+
auto_approved: autoApproved
|
|
8299
|
+
};
|
|
8300
|
+
}
|
|
8301
|
+
/** draft → active(仅未 disputed 的 draft)。返回被激活规则的 pattern 前缀。 */
|
|
8302
|
+
autoApproveDrafts() {
|
|
8303
|
+
const approved = [];
|
|
8304
|
+
let drafts;
|
|
8305
|
+
try {
|
|
8306
|
+
drafts = this.ruleStore.getDraftRules();
|
|
8307
|
+
} catch {
|
|
8308
|
+
return approved;
|
|
8309
|
+
}
|
|
8310
|
+
for (const rule of drafts) {
|
|
8311
|
+
try {
|
|
8312
|
+
this.ruleStore.approveRule(rule.id);
|
|
8313
|
+
approved.push(rule.pattern.slice(0, 60));
|
|
8314
|
+
} catch {
|
|
8315
|
+
}
|
|
8316
|
+
}
|
|
8317
|
+
return approved;
|
|
8318
|
+
}
|
|
8319
|
+
};
|
|
8320
|
+
|
|
8321
|
+
// src/brain/contributor.ts
|
|
8322
|
+
init_esm_shims();
|
|
8323
|
+
init_asset_dir();
|
|
8324
|
+
import { dirname as dirname9 } from "path";
|
|
8325
|
+
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
8326
|
+
var __dirname8 = dirname9(fileURLToPath9(import.meta.url));
|
|
8327
|
+
|
|
7672
8328
|
// src/brain/index.ts
|
|
7673
8329
|
function checkBrainRules(store, opts) {
|
|
7674
8330
|
if (!store) return null;
|
|
@@ -7715,6 +8371,10 @@ function checkLayer2Context(pattern, actionHistory, actionDetail) {
|
|
|
7715
8371
|
return true;
|
|
7716
8372
|
}
|
|
7717
8373
|
|
|
8374
|
+
// src/engine/coordinator.ts
|
|
8375
|
+
init_asset_dir();
|
|
8376
|
+
init_runtime_config_bridge();
|
|
8377
|
+
|
|
7718
8378
|
// src/engine/formal-engine/index.ts
|
|
7719
8379
|
init_esm_shims();
|
|
7720
8380
|
|
|
@@ -8684,20 +9344,69 @@ function matchesAny(cmd, patterns) {
|
|
|
8684
9344
|
}
|
|
8685
9345
|
var _brainStore = null;
|
|
8686
9346
|
var _brainExtractor = null;
|
|
9347
|
+
var _brainSkillManager = null;
|
|
9348
|
+
var _brainCurator = null;
|
|
8687
9349
|
var _brainInitialized = false;
|
|
8688
|
-
|
|
9350
|
+
var _brainLastMaintenance = 0;
|
|
9351
|
+
var BRAIN_MAINT_INTERVAL_MS = 3e5;
|
|
9352
|
+
function getBrainStore(osType) {
|
|
8689
9353
|
if (!isBrainEnabled()) return null;
|
|
8690
9354
|
if (!_brainInitialized) {
|
|
8691
9355
|
_brainStore = new BrainRuleStore();
|
|
8692
|
-
|
|
9356
|
+
const skillsDir = resolveAssetDir(__dirname, "skills");
|
|
9357
|
+
_brainSkillManager = skillsDir ? new BrainSkillManager(skillsDir) : null;
|
|
9358
|
+
_brainExtractor = new PatternExtractor(_brainStore, {
|
|
9359
|
+
review: createBrainReviewer(),
|
|
9360
|
+
// 内置规则去重集(对齐 Python coordinator._ensure_brain 收集的 builtin_patterns):
|
|
9361
|
+
// 危险命令正则 + 敏感路径(deny_* 或 credential)。
|
|
9362
|
+
// 历史 bug:TS 这里只传 review,不传 builtinPatterns → 提取器拿不到内置规则,
|
|
9363
|
+
// `isCoveredByBuiltin` 恒 False → 把 L1/L2 本来就能拦的案例重复沉淀成 Brain 规则。
|
|
9364
|
+
builtinPatterns: collectBrainBuiltinPatterns(osType),
|
|
9365
|
+
skillManager: _brainSkillManager
|
|
9366
|
+
});
|
|
9367
|
+
if (_brainSkillManager) {
|
|
9368
|
+
_brainCurator = new BrainCurator(_brainStore, _brainSkillManager);
|
|
9369
|
+
}
|
|
8693
9370
|
_brainInitialized = true;
|
|
8694
9371
|
}
|
|
9372
|
+
const now = Date.now();
|
|
9373
|
+
if (_brainCurator && now - _brainLastMaintenance >= BRAIN_MAINT_INTERVAL_MS) {
|
|
9374
|
+
try {
|
|
9375
|
+
_brainCurator.runMaintenance({ autoApprove: true });
|
|
9376
|
+
} catch {
|
|
9377
|
+
}
|
|
9378
|
+
_brainLastMaintenance = now;
|
|
9379
|
+
}
|
|
8695
9380
|
return _brainStore;
|
|
8696
9381
|
}
|
|
9382
|
+
function collectBrainBuiltinPatterns(osType) {
|
|
9383
|
+
const bridge = runtimeConfigBridge.current;
|
|
9384
|
+
if (!bridge) return [];
|
|
9385
|
+
const out = [];
|
|
9386
|
+
try {
|
|
9387
|
+
for (const cp of bridge.effectiveDangerousPatterns(osType)) {
|
|
9388
|
+
if (cp?.pattern?.source) out.push(cp.pattern.source);
|
|
9389
|
+
}
|
|
9390
|
+
} catch {
|
|
9391
|
+
}
|
|
9392
|
+
try {
|
|
9393
|
+
for (const sp of bridge.effectiveSensitivePaths(osType)) {
|
|
9394
|
+
const denyMode = String(sp?.denyMode || "");
|
|
9395
|
+
if (["deny_both", "deny_write", "deny_read"].includes(denyMode) || sp?.credential) {
|
|
9396
|
+
if (sp?.path) out.push(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
9397
|
+
}
|
|
9398
|
+
}
|
|
9399
|
+
} catch {
|
|
9400
|
+
}
|
|
9401
|
+
return out.filter(Boolean);
|
|
9402
|
+
}
|
|
8697
9403
|
function _resetBrainForTest() {
|
|
8698
9404
|
_brainStore = null;
|
|
8699
9405
|
_brainExtractor = null;
|
|
9406
|
+
_brainSkillManager = null;
|
|
9407
|
+
_brainCurator = null;
|
|
8700
9408
|
_brainInitialized = false;
|
|
9409
|
+
_brainLastMaintenance = 0;
|
|
8701
9410
|
}
|
|
8702
9411
|
var formalEngine = new FormalEngine();
|
|
8703
9412
|
async function runAnalysis(input) {
|
|
@@ -8750,7 +9459,7 @@ async function runAnalysis(input) {
|
|
|
8750
9459
|
}
|
|
8751
9460
|
}
|
|
8752
9461
|
}
|
|
8753
|
-
const brainStore = getBrainStore();
|
|
9462
|
+
const brainStore = getBrainStore(input.osType);
|
|
8754
9463
|
if (brainStore) {
|
|
8755
9464
|
const brainL1 = checkBrainRules(brainStore, {
|
|
8756
9465
|
layer: 1,
|