@kenhkl/agent-moss 0.16.9 → 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/package.json +2 -1
package/dist/server.js
CHANGED
|
@@ -406,71 +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(path6);
|
|
415
|
-
console.warn(
|
|
416
|
-
`[AgentMoss] \u89C4\u5219\u6587\u4EF6\u88AB\u8986\u76D6\u76EE\u5F55\u8986\u76D6: ${path6}\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 ${path6} \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
|
-
}
|
|
424
|
-
}
|
|
425
|
-
function overriddenRuleFiles() {
|
|
426
|
-
const out = [];
|
|
427
|
-
for (const name of Object.keys(EXPECTED_KEYS)) {
|
|
428
|
-
for (const dir of overrideRulesDirs()) {
|
|
429
|
-
const p = resolve(dir, name);
|
|
430
|
-
if (existsSync(p)) {
|
|
431
|
-
out.push(p);
|
|
432
|
-
break;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
return out;
|
|
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)];
|
|
437
414
|
}
|
|
438
415
|
function findRulesFile(name) {
|
|
439
|
-
for (const dir of overrideRulesDirs()) {
|
|
440
|
-
const p = resolve(dir, name);
|
|
441
|
-
if (existsSync(p)) return p;
|
|
442
|
-
}
|
|
443
416
|
for (const dir of RULES_DIR_CANDS) {
|
|
444
417
|
const p = resolve(dir, name);
|
|
445
418
|
if (existsSync(p)) return p;
|
|
446
419
|
}
|
|
447
420
|
return null;
|
|
448
421
|
}
|
|
449
|
-
function isOverridePath(name, p) {
|
|
450
|
-
return overrideRulesDirs().some((dir) => resolve(dir, name) === p);
|
|
451
|
-
}
|
|
452
422
|
function loadJson(name) {
|
|
453
423
|
const p = findRulesFile(name);
|
|
454
424
|
if (!p) return {};
|
|
455
|
-
const isOverride = isOverridePath(name, p);
|
|
456
|
-
let data;
|
|
457
425
|
try {
|
|
458
|
-
|
|
426
|
+
return JSON.parse(readFileSync2(p, "utf-8")) || {};
|
|
459
427
|
} catch {
|
|
460
|
-
if (isOverride) warnOverride(name, p, {});
|
|
461
428
|
return {};
|
|
462
429
|
}
|
|
463
|
-
if (isOverride) warnOverride(name, p, data);
|
|
464
|
-
return data;
|
|
465
430
|
}
|
|
466
431
|
function resolveScenarioRulesFile(scenarioId) {
|
|
467
432
|
const rel = join2("scenarios", scenarioId, "rules.json");
|
|
468
|
-
for (const dir of
|
|
433
|
+
for (const dir of rulesDirs()) {
|
|
469
434
|
const p = resolve(dir, rel);
|
|
470
|
-
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;
|
|
471
451
|
}
|
|
472
452
|
return null;
|
|
473
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
|
+
}
|
|
474
460
|
function loadL1() {
|
|
475
461
|
return loadJson("l1_rules.json");
|
|
476
462
|
}
|
|
@@ -478,10 +464,40 @@ function loadL2() {
|
|
|
478
464
|
return loadJson("l2_rules.json");
|
|
479
465
|
}
|
|
480
466
|
function loadL3() {
|
|
481
|
-
|
|
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;
|
|
482
498
|
}
|
|
483
499
|
function loadMC() {
|
|
484
|
-
const mc =
|
|
500
|
+
const mc = loadMCRaw();
|
|
485
501
|
const rules = mc.rules || [];
|
|
486
502
|
try {
|
|
487
503
|
const rt = runtimeConfigBridge.current;
|
|
@@ -501,144 +517,284 @@ function loadMC() {
|
|
|
501
517
|
return mc;
|
|
502
518
|
}
|
|
503
519
|
function loadToolRegistry() {
|
|
504
|
-
|
|
505
|
-
}
|
|
506
|
-
function extraRulesDirs() {
|
|
507
|
-
return parseDirEnv("AGENT_MOSS_EXTRA_RULES_DIR");
|
|
508
|
-
}
|
|
509
|
-
function listJsonFilesDeep(dir) {
|
|
510
|
-
const out = [];
|
|
511
|
-
let entries;
|
|
520
|
+
const data = { ...loadJson("tool_registry.json") };
|
|
521
|
+
let tools = { ...data.tools || {} };
|
|
512
522
|
try {
|
|
513
|
-
|
|
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 };
|
|
514
527
|
} catch {
|
|
515
|
-
return out;
|
|
516
528
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
529
|
+
data.tools = tools;
|
|
530
|
+
return data;
|
|
531
|
+
}
|
|
532
|
+
function scanStructEntry(cat, value, path6) {
|
|
533
|
+
if (cat === "skills") {
|
|
534
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
535
|
+
console.error(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path6} \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 ${path6} \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 ${path6} \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;
|
|
524
550
|
}
|
|
525
|
-
|
|
526
|
-
else if (name.toLowerCase().endsWith(".json")) out.push(p);
|
|
551
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
527
552
|
}
|
|
528
|
-
|
|
553
|
+
if (cat === "ltl_rules") {
|
|
554
|
+
if (!Array.isArray(value)) {
|
|
555
|
+
console.error(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path6} \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 ${path6} \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 ${path6} \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 ${path6} \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;
|
|
529
577
|
}
|
|
530
|
-
function
|
|
531
|
-
const
|
|
532
|
-
const
|
|
533
|
-
|
|
578
|
+
function scanRulesFile(path6, kind) {
|
|
579
|
+
const out = {};
|
|
580
|
+
const label = kind === "override" ? "\u8986\u76D6" : "\u8FFD\u52A0";
|
|
581
|
+
let parsed;
|
|
582
|
+
try {
|
|
583
|
+
parsed = JSON.parse(readFileSync2(path6, "utf-8"));
|
|
584
|
+
} catch (e) {
|
|
585
|
+
console.error(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path6} \u635F\u574F/\u4E0D\u53EF\u8BFB\uFF0C\u5DF2\u8DF3\u8FC7\uFF08${kind} \u89C4\u5219\u4E0D\u751F\u6548\uFF09: ${e.message}`);
|
|
586
|
+
return out;
|
|
587
|
+
}
|
|
534
588
|
const classifyFlat = (r) => {
|
|
535
589
|
for (const [keys, cat] of EXTRA_FLAT_FIELD) {
|
|
536
590
|
if (r && typeof r === "object" && keys.every((k) => r[k])) return cat;
|
|
537
591
|
}
|
|
538
592
|
return null;
|
|
539
593
|
};
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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 ${path6} \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 ${path6} \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, path6);
|
|
620
|
+
if (got !== null) out[cat] = got;
|
|
546
621
|
continue;
|
|
547
622
|
}
|
|
548
|
-
|
|
549
|
-
if (
|
|
550
|
-
pairs = Object.entries(parsed);
|
|
551
|
-
} else if (Array.isArray(parsed)) {
|
|
552
|
-
let flatBad = 0;
|
|
553
|
-
for (const r of parsed) {
|
|
554
|
-
const cat = classifyFlat(r);
|
|
555
|
-
if (cat === null) {
|
|
556
|
-
flatBad += 1;
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
559
|
-
pairs.push([cat, [r]]);
|
|
560
|
-
}
|
|
561
|
-
if (flatBad > 0) {
|
|
562
|
-
console.warn(
|
|
563
|
-
`[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`
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
} else {
|
|
623
|
+
const required = EXTRA_CATEGORIES[cat];
|
|
624
|
+
if (!required) {
|
|
567
625
|
console.warn(
|
|
568
|
-
`[AgentMoss] \
|
|
626
|
+
`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path6} \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`
|
|
569
627
|
);
|
|
570
628
|
continue;
|
|
571
629
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
630
|
+
if (rules && typeof rules === "object" && !Array.isArray(rules)) {
|
|
631
|
+
console.error(
|
|
632
|
+
`[AgentMoss] \u274C \u89C4\u5219\u6587\u4EF6 ${path6} \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`
|
|
635
|
+
);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (!Array.isArray(rules)) {
|
|
639
|
+
console.warn(`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path6} \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])) {
|
|
576
645
|
console.warn(
|
|
577
|
-
`[AgentMoss] \
|
|
646
|
+
`[AgentMoss] \u89C4\u5219\u6587\u4EF6 ${path6} \u7C7B\u522B ${cat} \u6709\u6761\u76EE\u7F3A\u5FC5\u9700\u5B57\u6BB5 [${required.join(", ")}] \u2014\u2014 \u5DF2\u8DF3\u8FC7\u8BE5\u6761\u3002`
|
|
578
647
|
);
|
|
579
648
|
continue;
|
|
580
649
|
}
|
|
581
|
-
|
|
582
|
-
|
|
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 ${path6}\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 {
|
|
583
679
|
continue;
|
|
584
680
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
)
|
|
590
|
-
|
|
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
|
+
}
|
|
591
690
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
);
|
|
598
|
-
|
|
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 };
|
|
599
699
|
}
|
|
600
|
-
seenIds.set(rid, p);
|
|
601
700
|
}
|
|
602
|
-
|
|
603
|
-
|
|
701
|
+
} else if (name.toLowerCase().endsWith(".json")) {
|
|
702
|
+
ignored.push(f);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
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
|
+
);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
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`);
|
|
604
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`);
|
|
605
736
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
+
);
|
|
609
746
|
}
|
|
610
747
|
}
|
|
611
|
-
return {
|
|
748
|
+
return { override, extra, loaded, ignored };
|
|
612
749
|
}
|
|
613
|
-
function
|
|
614
|
-
const
|
|
615
|
-
const
|
|
616
|
-
|
|
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 = [];
|
|
617
754
|
for (const d of dirs) {
|
|
618
|
-
if (existsSync(d))
|
|
619
|
-
|
|
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
|
+
}
|
|
620
762
|
}
|
|
621
|
-
|
|
622
|
-
const fp = files.map((p) => {
|
|
763
|
+
const fp = JSON.stringify(files.map((p) => {
|
|
623
764
|
try {
|
|
624
765
|
const st = statSync(p);
|
|
625
766
|
return [p, st.mtimeMs, st.size];
|
|
626
767
|
} catch {
|
|
627
768
|
return [p, -1, -1];
|
|
628
769
|
}
|
|
629
|
-
});
|
|
630
|
-
if (
|
|
631
|
-
|
|
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;
|
|
632
782
|
}
|
|
633
|
-
|
|
634
|
-
extraCache = { raw, fp, data, loaded };
|
|
635
|
-
return data;
|
|
783
|
+
return Object.keys(override).length > 0;
|
|
636
784
|
}
|
|
637
|
-
function
|
|
638
|
-
|
|
639
|
-
return extraCache ? [...extraCache.loaded] : [];
|
|
785
|
+
function loadOverrideRules() {
|
|
786
|
+
return rulesCached().override;
|
|
640
787
|
}
|
|
641
|
-
|
|
788
|
+
function loadExtraRules() {
|
|
789
|
+
return rulesCached().extra;
|
|
790
|
+
}
|
|
791
|
+
function ruleFilesLoaded() {
|
|
792
|
+
return rulesCached().loaded;
|
|
793
|
+
}
|
|
794
|
+
function ignoredRuleFiles() {
|
|
795
|
+
return rulesCached().ignored;
|
|
796
|
+
}
|
|
797
|
+
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;
|
|
642
798
|
var init_rules_loader = __esm({
|
|
643
799
|
"src/infra/rules-loader.ts"() {
|
|
644
800
|
"use strict";
|
|
@@ -647,20 +803,13 @@ var init_rules_loader = __esm({
|
|
|
647
803
|
init_runtime_config_bridge();
|
|
648
804
|
__filename2 = fileURLToPath2(import.meta.url);
|
|
649
805
|
__dirname2 = dirname(__filename2);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
"l3_rules.json": [],
|
|
654
|
-
"mc_rules.json": ["rules"],
|
|
655
|
-
"tool_registry.json": ["tools"]
|
|
656
|
-
};
|
|
657
|
-
warnedOverride = /* @__PURE__ */ new Set();
|
|
806
|
+
OVERRIDE_FILENAME = "override.json";
|
|
807
|
+
EXTRA_FILENAME = "extra.json";
|
|
808
|
+
RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
|
|
658
809
|
RULES_DIR_CANDS = (() => {
|
|
659
810
|
const cands = [
|
|
660
|
-
//
|
|
661
|
-
//
|
|
662
|
-
// mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
|
|
663
|
-
resolve(dataRootDir(), "rules"),
|
|
811
|
+
// 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
|
|
812
|
+
// 规则目录只认 override.json / extra.json(见 rulesDirs())。
|
|
664
813
|
// dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
|
|
665
814
|
resolve(__dirname2, "../rules"),
|
|
666
815
|
resolve(__dirname2, "rules"),
|
|
@@ -673,6 +822,7 @@ var init_rules_loader = __esm({
|
|
|
673
822
|
];
|
|
674
823
|
return cands.filter((c) => existsSync(c));
|
|
675
824
|
})();
|
|
825
|
+
SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
|
|
676
826
|
EXTRA_CATEGORIES = {
|
|
677
827
|
dangerous_commands: ["pattern"],
|
|
678
828
|
injection_detection: ["keyword", "lang"],
|
|
@@ -681,6 +831,7 @@ var init_rules_loader = __esm({
|
|
|
681
831
|
password_modify_consent: ["pattern"],
|
|
682
832
|
user_deletion_consent: ["pattern"]
|
|
683
833
|
};
|
|
834
|
+
STRUCT_KEYS = ["skills", "ltl_rules", "tools"];
|
|
684
835
|
EXTRA_FLAT_FIELD = [
|
|
685
836
|
[["path"], "sensitive_path_access"],
|
|
686
837
|
[["keyword", "lang"], "injection_detection"],
|
|
@@ -688,7 +839,8 @@ var init_rules_loader = __esm({
|
|
|
688
839
|
[["pattern", "risk_level"], "dangerous_commands"],
|
|
689
840
|
[["pattern", "risk_type"], "dangerous_commands"]
|
|
690
841
|
];
|
|
691
|
-
|
|
842
|
+
_RULES_CACHE = null;
|
|
843
|
+
warnedIgnored = /* @__PURE__ */ new Set();
|
|
692
844
|
}
|
|
693
845
|
});
|
|
694
846
|
|
|
@@ -2046,18 +2198,34 @@ function _mergeUserCustom(primary, runtime, layers, key) {
|
|
|
2046
2198
|
const extra = _userCustomRules(runtime, layers, key);
|
|
2047
2199
|
return extra.length > 0 ? [...primary, ...extra] : [...primary];
|
|
2048
2200
|
}
|
|
2049
|
-
function
|
|
2201
|
+
function _dedup(rules, category) {
|
|
2202
|
+
const keys = _DEDUP_KEYS[category];
|
|
2203
|
+
if (!keys) return rules;
|
|
2204
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2205
|
+
const out = [];
|
|
2206
|
+
for (const r of rules) {
|
|
2207
|
+
const k = keys.map((x) => String(r?.[x] ?? "")).join("\0");
|
|
2208
|
+
if (seen.has(k)) continue;
|
|
2209
|
+
seen.add(k);
|
|
2210
|
+
out.push(r);
|
|
2211
|
+
}
|
|
2212
|
+
return out;
|
|
2213
|
+
}
|
|
2214
|
+
function _mergeRulesFromDirs(primary, category) {
|
|
2215
|
+
let base = [...primary];
|
|
2050
2216
|
try {
|
|
2217
|
+
const override = loadOverrideRules()[category];
|
|
2218
|
+
if (override !== void 0 && override !== null) base = [...override];
|
|
2051
2219
|
const extra = loadExtraRules()[category] || [];
|
|
2052
|
-
return
|
|
2220
|
+
return _dedup([...base, ...extra], category);
|
|
2053
2221
|
} catch {
|
|
2054
|
-
return
|
|
2222
|
+
return base;
|
|
2055
2223
|
}
|
|
2056
2224
|
}
|
|
2057
2225
|
function getEnabledL1CommandPatterns(runtime, osType) {
|
|
2058
2226
|
const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
|
|
2059
2227
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2060
|
-
rules =
|
|
2228
|
+
rules = _mergeRulesFromDirs(rules, "dangerous_commands");
|
|
2061
2229
|
rules = _mergeUserCustom(rules, runtime, "L1_rules", "pattern");
|
|
2062
2230
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2063
2231
|
return rules.map((r) => ({
|
|
@@ -2073,7 +2241,7 @@ function getEnabledL2SensitivePaths(runtime, osType) {
|
|
|
2073
2241
|
cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
|
|
2074
2242
|
}
|
|
2075
2243
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2076
|
-
rules =
|
|
2244
|
+
rules = _mergeRulesFromDirs(rules, "sensitive_path_access");
|
|
2077
2245
|
rules = _mergeUserCustom(rules, runtime, ["L1_rules", "L2_rules"], "path");
|
|
2078
2246
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2079
2247
|
return rules.map((r) => {
|
|
@@ -2458,25 +2626,11 @@ function generateSourceDefaults() {
|
|
|
2458
2626
|
};
|
|
2459
2627
|
}
|
|
2460
2628
|
function _ltlRulesToRuntime() {
|
|
2461
|
-
const cands = [
|
|
2462
|
-
getRuntimeConfigDir() && join3(getRuntimeConfigDir(), "rules"),
|
|
2463
|
-
join3(__dirname4, "../rules"),
|
|
2464
|
-
join3(__dirname4, "rules"),
|
|
2465
|
-
join3(__dirname4, "../../../agent_moss/rules"),
|
|
2466
|
-
join3(__dirname4, "../../../../agent_moss/rules"),
|
|
2467
|
-
join3(process.cwd(), "agent_moss/rules")
|
|
2468
|
-
].filter(Boolean);
|
|
2469
2629
|
let mc = {};
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
|
|
2475
|
-
} catch {
|
|
2476
|
-
mc = {};
|
|
2477
|
-
}
|
|
2478
|
-
break;
|
|
2479
|
-
}
|
|
2630
|
+
try {
|
|
2631
|
+
mc = loadMCRaw();
|
|
2632
|
+
} catch {
|
|
2633
|
+
mc = {};
|
|
2480
2634
|
}
|
|
2481
2635
|
const rules = mc.rules || [];
|
|
2482
2636
|
return {
|
|
@@ -2705,7 +2859,7 @@ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
|
|
|
2705
2859
|
function getEnabledL1InjectionKeywords(runtime, osType) {
|
|
2706
2860
|
const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
|
|
2707
2861
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2708
|
-
rules =
|
|
2862
|
+
rules = _mergeRulesFromDirs(rules, "injection_detection");
|
|
2709
2863
|
rules = _mergeUserCustom(rules, runtime, "L1_rules", "keyword");
|
|
2710
2864
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2711
2865
|
return rules.map((r) => ({
|
|
@@ -2717,7 +2871,7 @@ function getEnabledL1InjectionKeywords(runtime, osType) {
|
|
|
2717
2871
|
function getEnabledL2IntentPatterns(runtime, osType) {
|
|
2718
2872
|
const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
|
|
2719
2873
|
let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
|
|
2720
|
-
rules =
|
|
2874
|
+
rules = _mergeRulesFromDirs(rules, "intent_consistency");
|
|
2721
2875
|
rules = _mergeUserCustom(rules, runtime, "L2_rules", "intent_keywords");
|
|
2722
2876
|
if (osType) rules = _filterRulesByOs(rules, osType);
|
|
2723
2877
|
return rules.map((r) => ({
|
|
@@ -2729,7 +2883,7 @@ function getEnabledL2IntentPatterns(runtime, osType) {
|
|
|
2729
2883
|
function getEnabledL2PasswordPatterns(runtime) {
|
|
2730
2884
|
const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
|
|
2731
2885
|
const rules = _mergeUserCustom(
|
|
2732
|
-
|
|
2886
|
+
_mergeRulesFromDirs(filterEnabledRules(cat.rules || [], cat.category_enabled), "password_modify_consent"),
|
|
2733
2887
|
runtime,
|
|
2734
2888
|
"L2_rules",
|
|
2735
2889
|
"pattern"
|
|
@@ -2739,7 +2893,7 @@ function getEnabledL2PasswordPatterns(runtime) {
|
|
|
2739
2893
|
function getEnabledL2UserDeletionPatterns(runtime) {
|
|
2740
2894
|
const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
|
|
2741
2895
|
const rules = _mergeUserCustom(
|
|
2742
|
-
|
|
2896
|
+
_mergeRulesFromDirs(filterEnabledRules(cat.rules || [], cat.category_enabled), "user_deletion_consent"),
|
|
2743
2897
|
runtime,
|
|
2744
2898
|
"L2_rules",
|
|
2745
2899
|
"pattern"
|
|
@@ -2776,7 +2930,7 @@ function getEnabledL1CommandPatternsOrDefault(runtime, osType) {
|
|
|
2776
2930
|
return getEnabledL1CommandPatterns(runtime, osType);
|
|
2777
2931
|
}
|
|
2778
2932
|
return _mergeUserCustom(
|
|
2779
|
-
|
|
2933
|
+
_mergeRulesFromDirs(_filterDefaultsByOs(_l1CommandPatternsToRules(), osType), "dangerous_commands"),
|
|
2780
2934
|
runtime,
|
|
2781
2935
|
"L1_rules",
|
|
2782
2936
|
"pattern"
|
|
@@ -2788,7 +2942,7 @@ function getEnabledL1InjectionKeywordsOrDefault(runtime, osType) {
|
|
|
2788
2942
|
return getEnabledL1InjectionKeywords(runtime, osType);
|
|
2789
2943
|
}
|
|
2790
2944
|
return _mergeUserCustom(
|
|
2791
|
-
|
|
2945
|
+
_mergeRulesFromDirs(_l1InjectionKeywordsToRules(), "injection_detection"),
|
|
2792
2946
|
runtime,
|
|
2793
2947
|
"L1_rules",
|
|
2794
2948
|
"keyword"
|
|
@@ -2800,7 +2954,7 @@ function getEnabledL2SensitivePathsOrDefault(runtime, osType) {
|
|
|
2800
2954
|
return getEnabledL2SensitivePaths(runtime, osType);
|
|
2801
2955
|
}
|
|
2802
2956
|
return _mergeUserCustom(
|
|
2803
|
-
|
|
2957
|
+
_mergeRulesFromDirs(_filterDefaultsByOs(_l2SensitivePathsToRules(), osType), "sensitive_path_access"),
|
|
2804
2958
|
runtime,
|
|
2805
2959
|
["L1_rules", "L2_rules"],
|
|
2806
2960
|
"path"
|
|
@@ -2812,7 +2966,7 @@ function getEnabledL2IntentPatternsOrDefault(runtime, osType) {
|
|
|
2812
2966
|
return getEnabledL2IntentPatterns(runtime, osType);
|
|
2813
2967
|
}
|
|
2814
2968
|
return _mergeUserCustom(
|
|
2815
|
-
|
|
2969
|
+
_mergeRulesFromDirs(_filterDefaultsByOs(_l2IntentPatternsToRules(), osType), "intent_consistency"),
|
|
2816
2970
|
runtime,
|
|
2817
2971
|
"L2_rules",
|
|
2818
2972
|
"intent_keywords"
|
|
@@ -3203,7 +3357,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
|
|
|
3203
3357
|
saveRuntimeConfig(runtime);
|
|
3204
3358
|
return runtime;
|
|
3205
3359
|
}
|
|
3206
|
-
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;
|
|
3360
|
+
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;
|
|
3207
3361
|
var init_runtime_config = __esm({
|
|
3208
3362
|
"src/infra/runtime-config.ts"() {
|
|
3209
3363
|
"use strict";
|
|
@@ -3236,6 +3390,14 @@ var init_runtime_config = __esm({
|
|
|
3236
3390
|
deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
|
|
3237
3391
|
};
|
|
3238
3392
|
runtimeConfigCache = null;
|
|
3393
|
+
_DEDUP_KEYS = {
|
|
3394
|
+
dangerous_commands: ["pattern"],
|
|
3395
|
+
password_modify_consent: ["pattern"],
|
|
3396
|
+
user_deletion_consent: ["pattern"],
|
|
3397
|
+
sensitive_path_access: ["path"],
|
|
3398
|
+
injection_detection: ["keyword", "lang"],
|
|
3399
|
+
intent_consistency: ["intent_keywords", "reason"]
|
|
3400
|
+
};
|
|
3239
3401
|
hasRuntimeConfigCache = /* @__PURE__ */ new Map();
|
|
3240
3402
|
_AGENTS_DIR_PROD = join3(__dirname4, "agents");
|
|
3241
3403
|
_AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
|
|
@@ -3261,6 +3423,8 @@ var init_runtime_config = __esm({
|
|
|
3261
3423
|
var config_exports = {};
|
|
3262
3424
|
__export(config_exports, {
|
|
3263
3425
|
detectProvider: () => detectProvider,
|
|
3426
|
+
getAgentMossSetting: () => getAgentMossSetting,
|
|
3427
|
+
getCacheOptions: () => getCacheOptions,
|
|
3264
3428
|
getCustomRules: () => getCustomRules,
|
|
3265
3429
|
getLLMConfig: () => getLLMConfig,
|
|
3266
3430
|
getLLMFailMode: () => getLLMFailMode,
|
|
@@ -3272,6 +3436,8 @@ __export(config_exports, {
|
|
|
3272
3436
|
isHeuristicEnabled: () => isHeuristicEnabled,
|
|
3273
3437
|
isLLMAvailable: () => isLLMAvailable,
|
|
3274
3438
|
isLogicRulesEnabled: () => isLogicRulesEnabled,
|
|
3439
|
+
isPolicyGenEnabled: () => isPolicyGenEnabled,
|
|
3440
|
+
isPolicySaveEnabled: () => isPolicySaveEnabled,
|
|
3275
3441
|
probeLLMConnection: () => probeLLMConnection,
|
|
3276
3442
|
updateLLMConfig: () => updateLLMConfig,
|
|
3277
3443
|
userRuleVariantPattern: () => userRuleVariantPattern,
|
|
@@ -3294,8 +3460,10 @@ function resolveConfigPath() {
|
|
|
3294
3460
|
const userConfig = path2.join(getConfigDir(), "config.json");
|
|
3295
3461
|
if (fs2.existsSync(userConfig)) return userConfig;
|
|
3296
3462
|
const defaultCandidates = [
|
|
3297
|
-
path2.join(
|
|
3298
|
-
path2.join(
|
|
3463
|
+
path2.join(__configModuleDir, "config.json.example"),
|
|
3464
|
+
path2.join(__configModuleDir, "..", "config.json.example"),
|
|
3465
|
+
path2.join(__configModuleDir, "..", "..", "agent_moss", "infra", "config.json.example"),
|
|
3466
|
+
path2.join(__configModuleDir, "..", "..", "..", "agent_moss", "infra", "config.json.example")
|
|
3299
3467
|
];
|
|
3300
3468
|
const defaultConfig = defaultCandidates.find((p) => fs2.existsSync(p));
|
|
3301
3469
|
if (defaultConfig) {
|
|
@@ -3304,8 +3472,8 @@ function resolveConfigPath() {
|
|
|
3304
3472
|
const pkgConfig = loadConfigFile(defaultConfig);
|
|
3305
3473
|
const safeConfig = pkgConfig ? { ...pkgConfig, llm: { ...pkgConfig.llm, api_key: "" } } : {};
|
|
3306
3474
|
fs2.writeFileSync(userConfig, JSON.stringify(safeConfig, null, 4), "utf-8");
|
|
3307
|
-
console.
|
|
3308
|
-
console.
|
|
3475
|
+
console.error(`[AgentMoss] \u5DF2\u521B\u5EFA\u7528\u6237\u914D\u7F6E\u6587\u4EF6: ${userConfig}`);
|
|
3476
|
+
console.error(`[AgentMoss] \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\u914D\u7F6E\u4F60\u7684 LLM API Key`);
|
|
3309
3477
|
}
|
|
3310
3478
|
return userConfig;
|
|
3311
3479
|
}
|
|
@@ -3477,7 +3645,32 @@ function layerEnabledOrDefault(key) {
|
|
|
3477
3645
|
}
|
|
3478
3646
|
}
|
|
3479
3647
|
function isBrainEnabled() {
|
|
3480
|
-
return
|
|
3648
|
+
return getAgentMossSetting("AGENT_MOSS_BRAIN_ENABLED", "") === "1";
|
|
3649
|
+
}
|
|
3650
|
+
function getAgentMossSetting(key, def = "") {
|
|
3651
|
+
const envValue = process.env[key];
|
|
3652
|
+
if (envValue !== void 0) return envValue;
|
|
3653
|
+
const cfg = loadConfigFile(resolveConfigPath());
|
|
3654
|
+
if (cfg && key in cfg) return String(cfg[key]);
|
|
3655
|
+
return def;
|
|
3656
|
+
}
|
|
3657
|
+
function isPolicyGenEnabled() {
|
|
3658
|
+
return getAgentMossSetting("AGENT_MOSS_ENABLE_POLICY_GEN", "") === "1";
|
|
3659
|
+
}
|
|
3660
|
+
function getCacheOptions() {
|
|
3661
|
+
let enabled = true;
|
|
3662
|
+
let maxSize = 1e3;
|
|
3663
|
+
const cfg = loadConfigFile(resolveConfigPath());
|
|
3664
|
+
const cache3 = cfg?.cache;
|
|
3665
|
+
if (cache3 && typeof cache3 === "object") {
|
|
3666
|
+
if (typeof cache3.enabled === "boolean") enabled = cache3.enabled;
|
|
3667
|
+
if (typeof cache3.max_size === "number" && cache3.max_size > 0) maxSize = cache3.max_size;
|
|
3668
|
+
}
|
|
3669
|
+
if (process.env.AGENT_MOSS_DISABLE_CACHE === "1") enabled = false;
|
|
3670
|
+
return { enabled, maxSize };
|
|
3671
|
+
}
|
|
3672
|
+
function isPolicySaveEnabled() {
|
|
3673
|
+
return getAgentMossSetting("AGENT_MOSS_SAVE_POLICY", "1") !== "0";
|
|
3481
3674
|
}
|
|
3482
3675
|
function isLogicRulesEnabled() {
|
|
3483
3676
|
if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
|
|
@@ -3486,13 +3679,20 @@ function isLogicRulesEnabled() {
|
|
|
3486
3679
|
function isFormalObserveMode() {
|
|
3487
3680
|
return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
|
|
3488
3681
|
}
|
|
3489
|
-
var timeoutConfig, retryConfig, llmConfig,
|
|
3682
|
+
var __configModuleDir, timeoutConfig, retryConfig, llmConfig, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
|
|
3490
3683
|
var init_config = __esm({
|
|
3491
3684
|
"src/config.ts"() {
|
|
3492
3685
|
"use strict";
|
|
3493
3686
|
init_esm_shims();
|
|
3494
3687
|
init_paths();
|
|
3495
3688
|
init_runtime_config();
|
|
3689
|
+
__configModuleDir = (() => {
|
|
3690
|
+
try {
|
|
3691
|
+
return path2.dirname(fileURLToPath5(import.meta.url));
|
|
3692
|
+
} catch {
|
|
3693
|
+
return typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
3694
|
+
}
|
|
3695
|
+
})();
|
|
3496
3696
|
llmConfig = (() => {
|
|
3497
3697
|
const configPath = resolveConfigPath();
|
|
3498
3698
|
const fileConfig = fs2.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
|
|
@@ -3510,13 +3710,6 @@ var init_config = __esm({
|
|
|
3510
3710
|
timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
|
|
3511
3711
|
};
|
|
3512
3712
|
})();
|
|
3513
|
-
__configModuleDir = (() => {
|
|
3514
|
-
try {
|
|
3515
|
-
return path2.dirname(fileURLToPath5(import.meta.url));
|
|
3516
|
-
} catch {
|
|
3517
|
-
return typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
3518
|
-
}
|
|
3519
|
-
})();
|
|
3520
3713
|
USER_RULES_PATHS = [
|
|
3521
3714
|
path2.join(__configModuleDir, "rules", "user_rules.json"),
|
|
3522
3715
|
// 源在 ts/src/rules(一般不存在)
|
|
@@ -7222,7 +7415,7 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
7222
7415
|
}
|
|
7223
7416
|
if (attempt < maxRetries) {
|
|
7224
7417
|
console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
|
|
7225
|
-
await new Promise((
|
|
7418
|
+
await new Promise((resolve11) => setTimeout(resolve11, retryCfg.retryInterval * 1e3));
|
|
7226
7419
|
} else {
|
|
7227
7420
|
clearTimeout(totalTimeoutTimer);
|
|
7228
7421
|
console.error("[AgentMoss] LLM call failed after all retries:", err.message);
|
|
@@ -7345,6 +7538,56 @@ var init_rule_store = __esm({
|
|
|
7345
7538
|
getRule(ruleId) {
|
|
7346
7539
|
return this.data.rules.find((r) => r.id === ruleId);
|
|
7347
7540
|
}
|
|
7541
|
+
/** 质疑:active → disputed,并把 disputed_count +1(镜像 Python dispute_rule)。 */
|
|
7542
|
+
disputeRule(ruleId, _reason = "") {
|
|
7543
|
+
const rule = this.getRule(ruleId);
|
|
7544
|
+
if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
|
|
7545
|
+
if (rule.status !== STATUS_ACTIVE) throw new Error(`\u53EA\u6709 active \u72B6\u6001\u53EF\u8D28\u7591\uFF0C\u5F53\u524D: ${rule.status}`);
|
|
7546
|
+
rule.status = STATUS_DISPUTED;
|
|
7547
|
+
rule.disputedCount += 1;
|
|
7548
|
+
this.save();
|
|
7549
|
+
}
|
|
7550
|
+
// ==================== 生命周期(curator 用,镜像 Python rule_store) ====================
|
|
7551
|
+
/** 长期未触发的 active/disputed 规则 → stale。 */
|
|
7552
|
+
markStale(staleDays = 30) {
|
|
7553
|
+
const cutoff = Date.now() - staleDays * 864e5;
|
|
7554
|
+
for (const r of this.data.rules) {
|
|
7555
|
+
if ((r.status === STATUS_ACTIVE || r.status === STATUS_DISPUTED) && r.lastTriggeredAt > 0 && r.lastTriggeredAt < cutoff) {
|
|
7556
|
+
r.status = STATUS_STALE;
|
|
7557
|
+
}
|
|
7558
|
+
}
|
|
7559
|
+
this.save();
|
|
7560
|
+
}
|
|
7561
|
+
/** 长期 stale 的规则 → archived。 */
|
|
7562
|
+
markArchived(archiveDays = 90) {
|
|
7563
|
+
const cutoff = Date.now() - archiveDays * 864e5;
|
|
7564
|
+
for (const r of this.data.rules) {
|
|
7565
|
+
if (r.status === STATUS_STALE && r.lastTriggeredAt > 0 && r.lastTriggeredAt < cutoff) {
|
|
7566
|
+
r.status = STATUS_ARCHIVED;
|
|
7567
|
+
}
|
|
7568
|
+
}
|
|
7569
|
+
this.save();
|
|
7570
|
+
}
|
|
7571
|
+
/** disputed 次数达阈值 → 自动降级 stale(自动纠错闭环)。 */
|
|
7572
|
+
autoDemoteDisputed(disputeThreshold = 3) {
|
|
7573
|
+
for (const r of this.data.rules) {
|
|
7574
|
+
if (r.status === STATUS_DISPUTED && r.disputedCount >= disputeThreshold) {
|
|
7575
|
+
r.status = STATUS_STALE;
|
|
7576
|
+
}
|
|
7577
|
+
}
|
|
7578
|
+
this.save();
|
|
7579
|
+
}
|
|
7580
|
+
// ==================== 中心化预留(镜像 Python) ====================
|
|
7581
|
+
/** 导出本地新增(非 central 来源)的 active 规则。 */
|
|
7582
|
+
getRulesForExport() {
|
|
7583
|
+
return this.data.rules.filter((r) => r.source !== "central" && r.status === STATUS_ACTIVE);
|
|
7584
|
+
}
|
|
7585
|
+
/** 导入中心下发规则(强制标 central 来源,避免被再次回流)。 */
|
|
7586
|
+
importRules(rules) {
|
|
7587
|
+
for (const rule of rules) {
|
|
7588
|
+
this.addRule({ ...rule, source: "central", originNode: "central" });
|
|
7589
|
+
}
|
|
7590
|
+
}
|
|
7348
7591
|
getActiveRules(layer) {
|
|
7349
7592
|
return this.data.rules.filter((r) => r.status === STATUS_ACTIVE && (layer === void 0 || r.layer === layer));
|
|
7350
7593
|
}
|
|
@@ -7452,12 +7695,14 @@ var init_pattern_extractor = __esm({
|
|
|
7452
7695
|
minOccurrences;
|
|
7453
7696
|
builtinPatterns;
|
|
7454
7697
|
reviewFn;
|
|
7698
|
+
skillManager;
|
|
7455
7699
|
constructor(ruleStore, opts) {
|
|
7456
7700
|
this.ruleStore = ruleStore;
|
|
7457
7701
|
this.minConfidence = opts?.minConfidence ?? 0.9;
|
|
7458
7702
|
this.minOccurrences = opts?.minOccurrences ?? 3;
|
|
7459
7703
|
this.builtinPatterns = opts?.builtinPatterns ?? [];
|
|
7460
7704
|
this.reviewFn = opts?.review ?? null;
|
|
7705
|
+
this.skillManager = opts?.skillManager ?? null;
|
|
7461
7706
|
}
|
|
7462
7707
|
async extract(event, llmResult) {
|
|
7463
7708
|
const result = { rulesCreated: 0, patternsRecorded: 0, skippedReason: "", deduped: [] };
|
|
@@ -7546,8 +7791,96 @@ var init_pattern_extractor = __esm({
|
|
|
7546
7791
|
result.rulesCreated += 1;
|
|
7547
7792
|
}
|
|
7548
7793
|
}
|
|
7794
|
+
if (this.skillManager) {
|
|
7795
|
+
const skill = this.buildSkill(actionDetail, reason, riskType, eventId);
|
|
7796
|
+
if (skill) {
|
|
7797
|
+
const id = this.skillManager.addSkill(skill);
|
|
7798
|
+
if (id) {
|
|
7799
|
+
result.skillsCreated = (result.skillsCreated ?? 0) + 1;
|
|
7800
|
+
}
|
|
7801
|
+
}
|
|
7802
|
+
}
|
|
7549
7803
|
return result;
|
|
7550
7804
|
}
|
|
7805
|
+
/**
|
|
7806
|
+
* 生成候选 Skill;不值得沉淀时返回 null(镜像 Python `_extract_skill` 的三道价值评估)。
|
|
7807
|
+
*
|
|
7808
|
+
* 评估 1:reason 太短(<50 字)说明没有可复用的判断逻辑,不沉淀;
|
|
7809
|
+
* 评估 2:同名 skill 已存在 → 跳过;
|
|
7810
|
+
* 评估 3:与已有 active skill 关键词重叠 >= 2 → 跳过(防同类案例重复沉淀,
|
|
7811
|
+
* 如 cat /etc/shadow 与 cat /etc/sudoers 生成两个近乎重复的 skill)。
|
|
7812
|
+
*/
|
|
7813
|
+
buildSkill(actionDetail, reason, riskType, eventId) {
|
|
7814
|
+
if (!reason || reason.length < 50) return null;
|
|
7815
|
+
if (!riskType) return null;
|
|
7816
|
+
const detailWords = (actionDetail.toLowerCase().match(/[a-z]+/g) || []).slice(0, 3);
|
|
7817
|
+
const name = detailWords.length > 0 ? `brain_${riskType}_${detailWords.join("_")}` : `brain_${riskType}`;
|
|
7818
|
+
if (this.skillManager.getSkillMeta(name)) return null;
|
|
7819
|
+
const keywords = this.extractSkillKeywords(actionDetail, reason, riskType);
|
|
7820
|
+
if (this.isCoveredByExistingSkill(keywords)) return null;
|
|
7821
|
+
return {
|
|
7822
|
+
name,
|
|
7823
|
+
category: this.categorizeRisk(riskType),
|
|
7824
|
+
content: this.generateSkillContent(name, actionDetail, reason, riskType),
|
|
7825
|
+
keywords,
|
|
7826
|
+
sourceEventIds: eventId ? [eventId] : [],
|
|
7827
|
+
status: "active"
|
|
7828
|
+
};
|
|
7829
|
+
}
|
|
7830
|
+
/** 关键词提取(镜像 Python `_extract_keywords`)。 */
|
|
7831
|
+
extractSkillKeywords(actionDetail, reason, riskType) {
|
|
7832
|
+
const keywords = /* @__PURE__ */ new Set();
|
|
7833
|
+
if (riskType) keywords.add(riskType);
|
|
7834
|
+
for (const w of (actionDetail.toLowerCase().match(/[a-z_]{3,}/g) || []).slice(0, 10)) keywords.add(w);
|
|
7835
|
+
for (const w of (reason.toLowerCase().match(/[a-z_]{3,}/g) || []).slice(0, 5)) keywords.add(w);
|
|
7836
|
+
return [...keywords];
|
|
7837
|
+
}
|
|
7838
|
+
/** 与已有 active skill 关键词重叠 >= 2 → 视为已覆盖(镜像 `_is_covered_by_existing_skill`)。 */
|
|
7839
|
+
isCoveredByExistingSkill(keywords) {
|
|
7840
|
+
if (keywords.length === 0) return false;
|
|
7841
|
+
let existing = [];
|
|
7842
|
+
try {
|
|
7843
|
+
existing = this.skillManager.getActiveSkills();
|
|
7844
|
+
} catch {
|
|
7845
|
+
return false;
|
|
7846
|
+
}
|
|
7847
|
+
const cand = new Set(keywords.map((k) => k.toLowerCase()));
|
|
7848
|
+
for (const meta of existing) {
|
|
7849
|
+
const metaKeywords = (meta.keywords || []).map((k) => String(k).toLowerCase());
|
|
7850
|
+
if (metaKeywords.length === 0) continue;
|
|
7851
|
+
const overlap = metaKeywords.filter((k) => cand.has(k)).length;
|
|
7852
|
+
if (overlap >= 2) return true;
|
|
7853
|
+
}
|
|
7854
|
+
return false;
|
|
7855
|
+
}
|
|
7856
|
+
/** 风险类型 → skill 分类(镜像 Python `_categorize_risk`)。 */
|
|
7857
|
+
categorizeRisk(riskType) {
|
|
7858
|
+
if (!riskType) return "edge_case";
|
|
7859
|
+
if (["inject", "backdoor", "persist"].some((k) => riskType.includes(k))) return "attack_pattern";
|
|
7860
|
+
if (["exfil", "leak", "export"].some((k) => riskType.includes(k))) return "attack_pattern";
|
|
7861
|
+
return "defense_strategy";
|
|
7862
|
+
}
|
|
7863
|
+
/** skill 正文(与 Python `_generate_skill_content` 同结构,便于双端对拍)。 */
|
|
7864
|
+
generateSkillContent(name, actionDetail, reason, riskType) {
|
|
7865
|
+
return `# ${name}
|
|
7866
|
+
|
|
7867
|
+
## \u6765\u6E90
|
|
7868
|
+
Brain \u81EA\u52A8\u63D0\u53D6\uFF08LLM \u5206\u6790\u6C89\u6DC0\uFF09
|
|
7869
|
+
|
|
7870
|
+
## \u573A\u666F
|
|
7871
|
+
\u5F53 Agent \u6267\u884C\u4EE5\u4E0B\u7C7B\u578B\u64CD\u4F5C\u65F6\u89E6\u53D1\uFF1A
|
|
7872
|
+
- \u98CE\u9669\u7C7B\u578B: ${riskType}
|
|
7873
|
+
- \u64CD\u4F5C\u7279\u5F81: ${actionDetail.slice(0, 200)}
|
|
7874
|
+
|
|
7875
|
+
## \u5224\u65AD\u4F9D\u636E
|
|
7876
|
+
${reason.slice(0, 500)}
|
|
7877
|
+
|
|
7878
|
+
## \u5904\u7406\u5EFA\u8BAE
|
|
7879
|
+
- \u4F18\u5148\u68C0\u67E5\u64CD\u4F5C\u7684\u4E0A\u4E0B\u6587\u548C\u610F\u56FE
|
|
7880
|
+
- \u7ED3\u5408 action_history \u5224\u65AD\u662F\u5426\u5B58\u5728\u6076\u610F\u94FE
|
|
7881
|
+
- \u5FC5\u8981\u65F6 Deny \u5E76\u8BB0\u5F55\u539F\u56E0
|
|
7882
|
+
`;
|
|
7883
|
+
}
|
|
7551
7884
|
extractPatterns(actionDetail, reason, riskType) {
|
|
7552
7885
|
const patterns = [];
|
|
7553
7886
|
const detailLower = actionDetail.toLowerCase();
|
|
@@ -7743,6 +8076,229 @@ var init_reviewer = __esm({
|
|
|
7743
8076
|
}
|
|
7744
8077
|
});
|
|
7745
8078
|
|
|
8079
|
+
// src/brain/skill-manager.ts
|
|
8080
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, renameSync, writeFileSync as writeFileSync4 } from "fs";
|
|
8081
|
+
import { homedir as homedir5 } from "os";
|
|
8082
|
+
import { join as join8 } from "path";
|
|
8083
|
+
import { randomUUID } from "crypto";
|
|
8084
|
+
function getMetaPath(override) {
|
|
8085
|
+
return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
|
|
8086
|
+
}
|
|
8087
|
+
var DEFAULT_SKILL_META_PATH, ENV_SKILL_META_PATH, BrainSkillManager;
|
|
8088
|
+
var init_skill_manager = __esm({
|
|
8089
|
+
"src/brain/skill-manager.ts"() {
|
|
8090
|
+
"use strict";
|
|
8091
|
+
init_esm_shims();
|
|
8092
|
+
DEFAULT_SKILL_META_PATH = join8(homedir5(), ".agentmoss", "brain", "skill_meta.json");
|
|
8093
|
+
ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
|
|
8094
|
+
BrainSkillManager = class {
|
|
8095
|
+
constructor(skillsDir, metaPath) {
|
|
8096
|
+
this.skillsDir = skillsDir;
|
|
8097
|
+
this.metaPath = getMetaPath(metaPath);
|
|
8098
|
+
try {
|
|
8099
|
+
mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
|
|
8100
|
+
} catch {
|
|
8101
|
+
}
|
|
8102
|
+
this.load();
|
|
8103
|
+
this.cleanupGhostSkills();
|
|
8104
|
+
}
|
|
8105
|
+
skillsDir;
|
|
8106
|
+
skills = [];
|
|
8107
|
+
metaPath;
|
|
8108
|
+
load() {
|
|
8109
|
+
if (!existsSync13(this.metaPath)) return;
|
|
8110
|
+
try {
|
|
8111
|
+
const data = JSON.parse(readFileSync13(this.metaPath, "utf-8"));
|
|
8112
|
+
this.skills = data.skills || [];
|
|
8113
|
+
} catch {
|
|
8114
|
+
this.skills = [];
|
|
8115
|
+
}
|
|
8116
|
+
}
|
|
8117
|
+
save() {
|
|
8118
|
+
const out = { version: 1, skills: this.skills };
|
|
8119
|
+
const tmp = `${this.metaPath}.tmp`;
|
|
8120
|
+
try {
|
|
8121
|
+
writeFileSync4(tmp, JSON.stringify(out, null, 2), "utf-8");
|
|
8122
|
+
renameSync(tmp, this.metaPath);
|
|
8123
|
+
} catch {
|
|
8124
|
+
}
|
|
8125
|
+
}
|
|
8126
|
+
/**
|
|
8127
|
+
* 清理"幽灵 skill":meta 记 active 但磁盘 .md 已不存在。
|
|
8128
|
+
* (沿用 Python 的处理:置 stale,避免污染关键词表并阻塞同名新 skill 生成。)
|
|
8129
|
+
*/
|
|
8130
|
+
cleanupGhostSkills() {
|
|
8131
|
+
let changed = false;
|
|
8132
|
+
for (const sk of this.skills) {
|
|
8133
|
+
if (sk.status !== "active") continue;
|
|
8134
|
+
if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
|
|
8135
|
+
sk.status = "stale";
|
|
8136
|
+
changed = true;
|
|
8137
|
+
}
|
|
8138
|
+
}
|
|
8139
|
+
if (changed) this.save();
|
|
8140
|
+
}
|
|
8141
|
+
/** 新增 Skill:写 .md + 记元数据。目录缺失或同名已存在时跳过(返回 "")。 */
|
|
8142
|
+
addSkill(skill) {
|
|
8143
|
+
const id = skill.id || randomUUID();
|
|
8144
|
+
const name = skill.name || `brain_${id.slice(0, 8)}`;
|
|
8145
|
+
if (!existsSync13(this.skillsDir)) return "";
|
|
8146
|
+
const mdPath = join8(this.skillsDir, `${name}.md`);
|
|
8147
|
+
if (existsSync13(mdPath)) return "";
|
|
8148
|
+
try {
|
|
8149
|
+
writeFileSync4(mdPath, skill.content, "utf-8");
|
|
8150
|
+
} catch {
|
|
8151
|
+
return "";
|
|
8152
|
+
}
|
|
8153
|
+
this.skills.push({
|
|
8154
|
+
id,
|
|
8155
|
+
name,
|
|
8156
|
+
category: skill.category || "",
|
|
8157
|
+
keywords: skill.keywords || [],
|
|
8158
|
+
source_event_ids: skill.sourceEventIds || [],
|
|
8159
|
+
status: skill.status || "draft",
|
|
8160
|
+
created_at: skill.createdAt || Date.now() / 1e3,
|
|
8161
|
+
usage_count: skill.usageCount || 0,
|
|
8162
|
+
origin_node: skill.originNode || "local"
|
|
8163
|
+
});
|
|
8164
|
+
this.save();
|
|
8165
|
+
return id;
|
|
8166
|
+
}
|
|
8167
|
+
getSkillMeta(name) {
|
|
8168
|
+
return this.skills.find((s) => s.name === name);
|
|
8169
|
+
}
|
|
8170
|
+
getActiveSkills() {
|
|
8171
|
+
return this.skills.filter((s) => s.status === "active").sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
|
|
8172
|
+
}
|
|
8173
|
+
recordUsage(name) {
|
|
8174
|
+
const sk = this.getSkillMeta(name);
|
|
8175
|
+
if (!sk) return;
|
|
8176
|
+
sk.usage_count = (sk.usage_count || 0) + 1;
|
|
8177
|
+
this.save();
|
|
8178
|
+
}
|
|
8179
|
+
/** 长期未使用(usage_count=0)且创建超过 staleDays 的 active skill → stale。 */
|
|
8180
|
+
markStale(staleDays = 60) {
|
|
8181
|
+
const cutoff = Date.now() / 1e3 - staleDays * 86400;
|
|
8182
|
+
let changed = false;
|
|
8183
|
+
for (const sk of this.skills) {
|
|
8184
|
+
if (sk.status === "active" && (sk.usage_count || 0) === 0 && (sk.created_at || 0) < cutoff) {
|
|
8185
|
+
sk.status = "stale";
|
|
8186
|
+
changed = true;
|
|
8187
|
+
}
|
|
8188
|
+
}
|
|
8189
|
+
if (changed) this.save();
|
|
8190
|
+
}
|
|
8191
|
+
/** 导出本地 active skill(含正文),供回流中心。 */
|
|
8192
|
+
exportSkills() {
|
|
8193
|
+
const out = [];
|
|
8194
|
+
for (const sk of this.skills) {
|
|
8195
|
+
if (sk.origin_node !== "local" || sk.status !== "active") continue;
|
|
8196
|
+
const mdPath = join8(this.skillsDir, `${sk.name}.md`);
|
|
8197
|
+
if (!existsSync13(mdPath)) continue;
|
|
8198
|
+
out.push({
|
|
8199
|
+
name: sk.name,
|
|
8200
|
+
category: sk.category || "",
|
|
8201
|
+
content: readFileSync13(mdPath, "utf-8"),
|
|
8202
|
+
keywords: sk.keywords || []
|
|
8203
|
+
});
|
|
8204
|
+
}
|
|
8205
|
+
return out;
|
|
8206
|
+
}
|
|
8207
|
+
/** 导入中心下发 skill(标 central,避免被再次回流)。 */
|
|
8208
|
+
importSkills(skills) {
|
|
8209
|
+
for (const s of skills) {
|
|
8210
|
+
this.addSkill({
|
|
8211
|
+
name: s.name,
|
|
8212
|
+
category: s.category || "",
|
|
8213
|
+
content: s.content,
|
|
8214
|
+
keywords: s.keywords || [],
|
|
8215
|
+
status: "active",
|
|
8216
|
+
originNode: "central"
|
|
8217
|
+
});
|
|
8218
|
+
}
|
|
8219
|
+
}
|
|
8220
|
+
/** 保留 close 签名(JSON 落盘已逐次持久化)。 */
|
|
8221
|
+
close() {
|
|
8222
|
+
this.save();
|
|
8223
|
+
}
|
|
8224
|
+
};
|
|
8225
|
+
}
|
|
8226
|
+
});
|
|
8227
|
+
|
|
8228
|
+
// src/brain/curator.ts
|
|
8229
|
+
var BrainCurator;
|
|
8230
|
+
var init_curator = __esm({
|
|
8231
|
+
"src/brain/curator.ts"() {
|
|
8232
|
+
"use strict";
|
|
8233
|
+
init_esm_shims();
|
|
8234
|
+
BrainCurator = class {
|
|
8235
|
+
constructor(ruleStore, skillManager) {
|
|
8236
|
+
this.ruleStore = ruleStore;
|
|
8237
|
+
this.skillManager = skillManager;
|
|
8238
|
+
}
|
|
8239
|
+
ruleStore;
|
|
8240
|
+
skillManager;
|
|
8241
|
+
/**
|
|
8242
|
+
* 执行一次维护,返回报告。
|
|
8243
|
+
*
|
|
8244
|
+
* autoApprove 默认 true:brain 提取的规则达阈值后自动生效(学习闭环);
|
|
8245
|
+
* 需人工审核时设 false(由 Console 手动 approve)。
|
|
8246
|
+
*/
|
|
8247
|
+
runMaintenance(opts = {}) {
|
|
8248
|
+
const {
|
|
8249
|
+
staleRuleDays = 30,
|
|
8250
|
+
archiveRuleDays = 90,
|
|
8251
|
+
staleSkillDays = 60,
|
|
8252
|
+
disputeThreshold = 3,
|
|
8253
|
+
autoApprove = true
|
|
8254
|
+
} = opts;
|
|
8255
|
+
const autoApproved = autoApprove ? this.autoApproveDrafts() : [];
|
|
8256
|
+
this.ruleStore.markStale(staleRuleDays);
|
|
8257
|
+
this.ruleStore.markArchived(archiveRuleDays);
|
|
8258
|
+
this.ruleStore.autoDemoteDisputed(disputeThreshold);
|
|
8259
|
+
this.skillManager.markStale(staleSkillDays);
|
|
8260
|
+
return {
|
|
8261
|
+
rule_stats: this.ruleStore.getStats(),
|
|
8262
|
+
auto_approved: autoApproved
|
|
8263
|
+
};
|
|
8264
|
+
}
|
|
8265
|
+
/** draft → active(仅未 disputed 的 draft)。返回被激活规则的 pattern 前缀。 */
|
|
8266
|
+
autoApproveDrafts() {
|
|
8267
|
+
const approved = [];
|
|
8268
|
+
let drafts;
|
|
8269
|
+
try {
|
|
8270
|
+
drafts = this.ruleStore.getDraftRules();
|
|
8271
|
+
} catch {
|
|
8272
|
+
return approved;
|
|
8273
|
+
}
|
|
8274
|
+
for (const rule of drafts) {
|
|
8275
|
+
try {
|
|
8276
|
+
this.ruleStore.approveRule(rule.id);
|
|
8277
|
+
approved.push(rule.pattern.slice(0, 60));
|
|
8278
|
+
} catch {
|
|
8279
|
+
}
|
|
8280
|
+
}
|
|
8281
|
+
return approved;
|
|
8282
|
+
}
|
|
8283
|
+
};
|
|
8284
|
+
}
|
|
8285
|
+
});
|
|
8286
|
+
|
|
8287
|
+
// src/brain/contributor.ts
|
|
8288
|
+
import { dirname as dirname9 } from "path";
|
|
8289
|
+
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
8290
|
+
var __dirname8;
|
|
8291
|
+
var init_contributor = __esm({
|
|
8292
|
+
"src/brain/contributor.ts"() {
|
|
8293
|
+
"use strict";
|
|
8294
|
+
init_esm_shims();
|
|
8295
|
+
init_rule_store();
|
|
8296
|
+
init_skill_manager();
|
|
8297
|
+
init_asset_dir();
|
|
8298
|
+
__dirname8 = dirname9(fileURLToPath9(import.meta.url));
|
|
8299
|
+
}
|
|
8300
|
+
});
|
|
8301
|
+
|
|
7746
8302
|
// src/brain/index.ts
|
|
7747
8303
|
function checkBrainRules(store, opts) {
|
|
7748
8304
|
if (!store) return null;
|
|
@@ -7795,6 +8351,9 @@ var init_brain = __esm({
|
|
|
7795
8351
|
init_rule_store();
|
|
7796
8352
|
init_pattern_extractor();
|
|
7797
8353
|
init_reviewer();
|
|
8354
|
+
init_skill_manager();
|
|
8355
|
+
init_curator();
|
|
8356
|
+
init_contributor();
|
|
7798
8357
|
}
|
|
7799
8358
|
});
|
|
7800
8359
|
|
|
@@ -9324,19 +9883,64 @@ function extractRawCommand(detail) {
|
|
|
9324
9883
|
function matchesAny(cmd, patterns) {
|
|
9325
9884
|
return patterns.some((p) => p.test(cmd));
|
|
9326
9885
|
}
|
|
9327
|
-
function getBrainStore() {
|
|
9886
|
+
function getBrainStore(osType) {
|
|
9328
9887
|
if (!isBrainEnabled()) return null;
|
|
9329
9888
|
if (!_brainInitialized) {
|
|
9330
9889
|
_brainStore = new BrainRuleStore();
|
|
9331
|
-
|
|
9890
|
+
const skillsDir = resolveAssetDir(__dirname, "skills");
|
|
9891
|
+
_brainSkillManager = skillsDir ? new BrainSkillManager(skillsDir) : null;
|
|
9892
|
+
_brainExtractor = new PatternExtractor(_brainStore, {
|
|
9893
|
+
review: createBrainReviewer(),
|
|
9894
|
+
// 内置规则去重集(对齐 Python coordinator._ensure_brain 收集的 builtin_patterns):
|
|
9895
|
+
// 危险命令正则 + 敏感路径(deny_* 或 credential)。
|
|
9896
|
+
// 历史 bug:TS 这里只传 review,不传 builtinPatterns → 提取器拿不到内置规则,
|
|
9897
|
+
// `isCoveredByBuiltin` 恒 False → 把 L1/L2 本来就能拦的案例重复沉淀成 Brain 规则。
|
|
9898
|
+
builtinPatterns: collectBrainBuiltinPatterns(osType),
|
|
9899
|
+
skillManager: _brainSkillManager
|
|
9900
|
+
});
|
|
9901
|
+
if (_brainSkillManager) {
|
|
9902
|
+
_brainCurator = new BrainCurator(_brainStore, _brainSkillManager);
|
|
9903
|
+
}
|
|
9332
9904
|
_brainInitialized = true;
|
|
9333
9905
|
}
|
|
9906
|
+
const now = Date.now();
|
|
9907
|
+
if (_brainCurator && now - _brainLastMaintenance >= BRAIN_MAINT_INTERVAL_MS) {
|
|
9908
|
+
try {
|
|
9909
|
+
_brainCurator.runMaintenance({ autoApprove: true });
|
|
9910
|
+
} catch {
|
|
9911
|
+
}
|
|
9912
|
+
_brainLastMaintenance = now;
|
|
9913
|
+
}
|
|
9334
9914
|
return _brainStore;
|
|
9335
9915
|
}
|
|
9916
|
+
function collectBrainBuiltinPatterns(osType) {
|
|
9917
|
+
const bridge = runtimeConfigBridge.current;
|
|
9918
|
+
if (!bridge) return [];
|
|
9919
|
+
const out = [];
|
|
9920
|
+
try {
|
|
9921
|
+
for (const cp of bridge.effectiveDangerousPatterns(osType)) {
|
|
9922
|
+
if (cp?.pattern?.source) out.push(cp.pattern.source);
|
|
9923
|
+
}
|
|
9924
|
+
} catch {
|
|
9925
|
+
}
|
|
9926
|
+
try {
|
|
9927
|
+
for (const sp of bridge.effectiveSensitivePaths(osType)) {
|
|
9928
|
+
const denyMode = String(sp?.denyMode || "");
|
|
9929
|
+
if (["deny_both", "deny_write", "deny_read"].includes(denyMode) || sp?.credential) {
|
|
9930
|
+
if (sp?.path) out.push(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
9931
|
+
}
|
|
9932
|
+
}
|
|
9933
|
+
} catch {
|
|
9934
|
+
}
|
|
9935
|
+
return out.filter(Boolean);
|
|
9936
|
+
}
|
|
9336
9937
|
function _resetBrainForTest() {
|
|
9337
9938
|
_brainStore = null;
|
|
9338
9939
|
_brainExtractor = null;
|
|
9940
|
+
_brainSkillManager = null;
|
|
9941
|
+
_brainCurator = null;
|
|
9339
9942
|
_brainInitialized = false;
|
|
9943
|
+
_brainLastMaintenance = 0;
|
|
9340
9944
|
}
|
|
9341
9945
|
async function runAnalysis(input) {
|
|
9342
9946
|
const observedFormalLayers = [];
|
|
@@ -9388,7 +9992,7 @@ async function runAnalysis(input) {
|
|
|
9388
9992
|
}
|
|
9389
9993
|
}
|
|
9390
9994
|
}
|
|
9391
|
-
const brainStore = getBrainStore();
|
|
9995
|
+
const brainStore = getBrainStore(input.osType);
|
|
9392
9996
|
if (brainStore) {
|
|
9393
9997
|
const brainL1 = checkBrainRules(brainStore, {
|
|
9394
9998
|
layer: 1,
|
|
@@ -9685,7 +10289,7 @@ async function runAnalysis(input) {
|
|
|
9685
10289
|
}
|
|
9686
10290
|
return llmResult;
|
|
9687
10291
|
}
|
|
9688
|
-
var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, _brainStore, _brainExtractor, _brainInitialized, formalEngine;
|
|
10292
|
+
var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, _brainStore, _brainExtractor, _brainSkillManager, _brainCurator, _brainInitialized, _brainLastMaintenance, BRAIN_MAINT_INTERVAL_MS, formalEngine;
|
|
9689
10293
|
var init_coordinator = __esm({
|
|
9690
10294
|
"src/engine/coordinator.ts"() {
|
|
9691
10295
|
"use strict";
|
|
@@ -9702,7 +10306,11 @@ var init_coordinator = __esm({
|
|
|
9702
10306
|
init_agents();
|
|
9703
10307
|
init_scenarios();
|
|
9704
10308
|
init_brain();
|
|
10309
|
+
init_skill_manager();
|
|
10310
|
+
init_curator();
|
|
9705
10311
|
init_reviewer();
|
|
10312
|
+
init_asset_dir();
|
|
10313
|
+
init_runtime_config_bridge();
|
|
9706
10314
|
init_formal_engine();
|
|
9707
10315
|
FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
9708
10316
|
"ask_user_question",
|
|
@@ -9725,7 +10333,11 @@ var init_coordinator = __esm({
|
|
|
9725
10333
|
]);
|
|
9726
10334
|
_brainStore = null;
|
|
9727
10335
|
_brainExtractor = null;
|
|
10336
|
+
_brainSkillManager = null;
|
|
10337
|
+
_brainCurator = null;
|
|
9728
10338
|
_brainInitialized = false;
|
|
10339
|
+
_brainLastMaintenance = 0;
|
|
10340
|
+
BRAIN_MAINT_INTERVAL_MS = 3e5;
|
|
9729
10341
|
formalEngine = new FormalEngine();
|
|
9730
10342
|
}
|
|
9731
10343
|
});
|
|
@@ -9736,6 +10348,7 @@ import { Hono as Hono2 } from "hono";
|
|
|
9736
10348
|
import { cors } from "hono/cors";
|
|
9737
10349
|
import { logger } from "hono/logger";
|
|
9738
10350
|
import net from "net";
|
|
10351
|
+
import { existsSync as existsSync19 } from "fs";
|
|
9739
10352
|
|
|
9740
10353
|
// src/routes.ts
|
|
9741
10354
|
init_esm_shims();
|
|
@@ -9822,8 +10435,438 @@ var ReviewResponseSchema = z.object({
|
|
|
9822
10435
|
discovered_patterns: z.array(z.record(z.unknown())).default([]).describe("\u6570\u636E\u9A71\u52A8\u53D1\u73B0\uFF1A\u8DE8 session \u9891\u7E41\u5371\u9669\u7EC4\u5408\uFF08\u9884\u7F6E\u94FE\u76F2\u533A\uFF0CPatternMiner\uFF09")
|
|
9823
10436
|
});
|
|
9824
10437
|
|
|
9825
|
-
// src/
|
|
10438
|
+
// src/engine/analyzer.ts
|
|
10439
|
+
init_esm_shims();
|
|
9826
10440
|
init_coordinator();
|
|
10441
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
|
|
10442
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
10443
|
+
import { join as join11, resolve as resolve10 } from "path";
|
|
10444
|
+
|
|
10445
|
+
// src/infra/policy-cache.ts
|
|
10446
|
+
init_esm_shims();
|
|
10447
|
+
init_config();
|
|
10448
|
+
import { createHash as createHash2 } from "crypto";
|
|
10449
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync15, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
10450
|
+
import { tmpdir } from "os";
|
|
10451
|
+
import { join as join10 } from "path";
|
|
10452
|
+
var CACHE_DIR = join10(tmpdir(), "agent_moss_policy_cache");
|
|
10453
|
+
var cacheDirOverride = null;
|
|
10454
|
+
function cacheDir() {
|
|
10455
|
+
return cacheDirOverride ?? CACHE_DIR;
|
|
10456
|
+
}
|
|
10457
|
+
var PolicyCache = class {
|
|
10458
|
+
// Map 的插入序即 LRU 序(get 时重新 set 到末尾)
|
|
10459
|
+
constructor(options) {
|
|
10460
|
+
this.options = options;
|
|
10461
|
+
}
|
|
10462
|
+
options;
|
|
10463
|
+
memory = /* @__PURE__ */ new Map();
|
|
10464
|
+
cacheKey(sessionId, promptSession) {
|
|
10465
|
+
return `${sessionId}::${promptSession}`;
|
|
10466
|
+
}
|
|
10467
|
+
cacheFilePath(sessionId, promptSession) {
|
|
10468
|
+
const promptHash = createHash2("md5").update(promptSession).digest("hex").slice(0, 12);
|
|
10469
|
+
const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
|
|
10470
|
+
return join10(cacheDir(), safeSessionId, `${promptHash}.toml`);
|
|
10471
|
+
}
|
|
10472
|
+
/** 取缓存:内存 → 文件;未命中返回 null。 */
|
|
10473
|
+
get(sessionId, promptSession) {
|
|
10474
|
+
if (!this.options.enabled) return null;
|
|
10475
|
+
const key = this.cacheKey(sessionId, promptSession);
|
|
10476
|
+
const hit = this.memory.get(key);
|
|
10477
|
+
if (hit !== void 0) {
|
|
10478
|
+
this.memory.delete(key);
|
|
10479
|
+
this.memory.set(key, hit);
|
|
10480
|
+
return hit;
|
|
10481
|
+
}
|
|
10482
|
+
const file = this.cacheFilePath(sessionId, promptSession);
|
|
10483
|
+
if (existsSync15(file)) {
|
|
10484
|
+
try {
|
|
10485
|
+
const policy = readFileSync15(file, "utf-8");
|
|
10486
|
+
this.remember(key, policy);
|
|
10487
|
+
return policy;
|
|
10488
|
+
} catch {
|
|
10489
|
+
}
|
|
10490
|
+
}
|
|
10491
|
+
return null;
|
|
10492
|
+
}
|
|
10493
|
+
/** 写缓存(内存 + 文件)。落盘失败不影响判定(缓存是旁路产物)。 */
|
|
10494
|
+
put(sessionId, promptSession, policy) {
|
|
10495
|
+
if (!this.options.enabled) return;
|
|
10496
|
+
this.remember(this.cacheKey(sessionId, promptSession), policy);
|
|
10497
|
+
const file = this.cacheFilePath(sessionId, promptSession);
|
|
10498
|
+
try {
|
|
10499
|
+
mkdirSync6(join10(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
|
|
10500
|
+
writeFileSync6(file, policy, "utf-8");
|
|
10501
|
+
} catch {
|
|
10502
|
+
}
|
|
10503
|
+
}
|
|
10504
|
+
remember(key, policy) {
|
|
10505
|
+
if (this.memory.has(key)) this.memory.delete(key);
|
|
10506
|
+
this.memory.set(key, policy);
|
|
10507
|
+
while (this.memory.size > this.options.maxSize) {
|
|
10508
|
+
const oldest = this.memory.keys().next().value;
|
|
10509
|
+
if (oldest === void 0) break;
|
|
10510
|
+
this.memory.delete(oldest);
|
|
10511
|
+
}
|
|
10512
|
+
}
|
|
10513
|
+
invalidate(sessionId, promptSession) {
|
|
10514
|
+
this.memory.delete(this.cacheKey(sessionId, promptSession));
|
|
10515
|
+
try {
|
|
10516
|
+
unlinkSync3(this.cacheFilePath(sessionId, promptSession));
|
|
10517
|
+
} catch {
|
|
10518
|
+
}
|
|
10519
|
+
}
|
|
10520
|
+
invalidateSession(sessionId) {
|
|
10521
|
+
for (const k of [...this.memory.keys()]) {
|
|
10522
|
+
if (k.startsWith(`${sessionId}::`)) this.memory.delete(k);
|
|
10523
|
+
}
|
|
10524
|
+
const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
|
|
10525
|
+
const dir = join10(cacheDir(), safeSessionId);
|
|
10526
|
+
try {
|
|
10527
|
+
if (existsSync15(dir)) {
|
|
10528
|
+
for (const f of readdirSync5(dir)) {
|
|
10529
|
+
try {
|
|
10530
|
+
unlinkSync3(join10(dir, f));
|
|
10531
|
+
} catch {
|
|
10532
|
+
}
|
|
10533
|
+
}
|
|
10534
|
+
rmdirSync(dir);
|
|
10535
|
+
}
|
|
10536
|
+
} catch {
|
|
10537
|
+
}
|
|
10538
|
+
}
|
|
10539
|
+
clear() {
|
|
10540
|
+
this.memory.clear();
|
|
10541
|
+
try {
|
|
10542
|
+
if (existsSync15(cacheDir())) {
|
|
10543
|
+
for (const sessionDir of readdirSync5(cacheDir())) {
|
|
10544
|
+
const dir = join10(cacheDir(), sessionDir);
|
|
10545
|
+
try {
|
|
10546
|
+
for (const f of readdirSync5(dir)) unlinkSync3(join10(dir, f));
|
|
10547
|
+
rmdirSync(dir);
|
|
10548
|
+
} catch {
|
|
10549
|
+
}
|
|
10550
|
+
}
|
|
10551
|
+
}
|
|
10552
|
+
} catch {
|
|
10553
|
+
}
|
|
10554
|
+
}
|
|
10555
|
+
/** 当前内存缓存条目数。 */
|
|
10556
|
+
size() {
|
|
10557
|
+
return this.memory.size;
|
|
10558
|
+
}
|
|
10559
|
+
};
|
|
10560
|
+
var defaultCache = null;
|
|
10561
|
+
function getDefaultCache() {
|
|
10562
|
+
if (!defaultCache) defaultCache = new PolicyCache(getCacheOptions());
|
|
10563
|
+
return defaultCache;
|
|
10564
|
+
}
|
|
10565
|
+
|
|
10566
|
+
// src/engine/analyzer.ts
|
|
10567
|
+
init_config();
|
|
10568
|
+
init_template_loader();
|
|
10569
|
+
init_asset_dir();
|
|
10570
|
+
init_token_stats();
|
|
10571
|
+
|
|
10572
|
+
// src/infra/logging.ts
|
|
10573
|
+
init_esm_shims();
|
|
10574
|
+
function safe(value, limit = 200) {
|
|
10575
|
+
const s = String(value ?? "");
|
|
10576
|
+
return s.length > limit ? `${s.slice(0, limit)}\u2026` : s;
|
|
10577
|
+
}
|
|
10578
|
+
var MossLogger = class {
|
|
10579
|
+
constructor(sessionId) {
|
|
10580
|
+
this.sessionId = sessionId;
|
|
10581
|
+
}
|
|
10582
|
+
sessionId;
|
|
10583
|
+
stepTimes = {};
|
|
10584
|
+
stepStart = null;
|
|
10585
|
+
startTime = Date.now();
|
|
10586
|
+
prefix() {
|
|
10587
|
+
return `[${safe(this.sessionId, 64)}]`;
|
|
10588
|
+
}
|
|
10589
|
+
logInput(promptSession, actionHistory, aNext, reason) {
|
|
10590
|
+
console.error(
|
|
10591
|
+
`${this.prefix()} agent_moss \u8F93\u5165 | prompt=${safe(promptSession, 100)}\u2026 | \u5386\u53F2\u52A8\u4F5C\u6570=${actionHistory.length} | \u4E0B\u4E00\u6B65\u52A8\u4F5C=${safe(aNext?.action_type ?? "?", 32)}:${safe(aNext?.action_detail ?? "?", 80)} | \u7406\u7531=${safe(reason, 80)}`
|
|
10592
|
+
);
|
|
10593
|
+
}
|
|
10594
|
+
startStep(stepName) {
|
|
10595
|
+
this.stepStart = Date.now();
|
|
10596
|
+
}
|
|
10597
|
+
endStep(stepName, detail = "") {
|
|
10598
|
+
if (this.stepStart === null) return;
|
|
10599
|
+
const elapsed = (Date.now() - this.stepStart) / 1e3;
|
|
10600
|
+
this.stepTimes[stepName] = elapsed;
|
|
10601
|
+
const detailStr = detail ? ` | ${safe(detail)}` : "";
|
|
10602
|
+
console.error(
|
|
10603
|
+
`${this.prefix()} \u6B65\u9AA4\u5B8C\u6210: ${safe(stepName, 48)} | \u8017\u65F6=${elapsed.toFixed(3)}s${detailStr}`
|
|
10604
|
+
);
|
|
10605
|
+
this.stepStart = null;
|
|
10606
|
+
}
|
|
10607
|
+
logLlmInteraction(step, promptSummary, responseSummary) {
|
|
10608
|
+
console.error(
|
|
10609
|
+
`${this.prefix()} LLM\u4EA4\u4E92 | \u6B65\u9AA4=${safe(step, 48)} | PROMPT\u6458\u8981=${safe(promptSummary, 200)} | \u54CD\u5E94\u6458\u8981=${safe(responseSummary, 200)}`
|
|
10610
|
+
);
|
|
10611
|
+
}
|
|
10612
|
+
logResult(decision, reason, policy = "") {
|
|
10613
|
+
const total = (Date.now() - this.startTime) / 1e3;
|
|
10614
|
+
const steps = Object.entries(this.stepTimes).map(([k, v]) => `${safe(k, 48)}=${v.toFixed(3)}s`).join(", ");
|
|
10615
|
+
console.error(
|
|
10616
|
+
`${this.prefix()} agent_moss \u7ED3\u679C | \u51B3\u7B56=${safe(decision, 16)} | \u7406\u7531=${safe(reason, 150)} | \u6B65\u9AA4\u8017\u65F6=[${steps}] | \u603B\u8017\u65F6=${total.toFixed(3)}s`
|
|
10617
|
+
);
|
|
10618
|
+
if (policy) {
|
|
10619
|
+
console.error(`${this.prefix()} \u751F\u6210\u7684Policy:
|
|
10620
|
+
${safe(policy, 2e3)}`);
|
|
10621
|
+
}
|
|
10622
|
+
}
|
|
10623
|
+
logError(step, error) {
|
|
10624
|
+
const name = error?.name ?? typeof error;
|
|
10625
|
+
console.error(
|
|
10626
|
+
`${this.prefix()} \u6B65\u9AA4\u5F02\u5E38: ${safe(step, 48)} | \u9519\u8BEF=${name}: ${safe(error?.message ?? error)}`
|
|
10627
|
+
);
|
|
10628
|
+
}
|
|
10629
|
+
};
|
|
10630
|
+
|
|
10631
|
+
// src/engine/analyzer.ts
|
|
10632
|
+
import { parse as parseToml } from "smol-toml";
|
|
10633
|
+
var POLICY_OUTPUT_DIR = join11(tmpdir2(), "agent_moss");
|
|
10634
|
+
var policyOutputDirOverride = null;
|
|
10635
|
+
function policyOutputDir() {
|
|
10636
|
+
return policyOutputDirOverride ?? POLICY_OUTPUT_DIR;
|
|
10637
|
+
}
|
|
10638
|
+
var DEFAULT_READONLY_POLICY = `landlock_optional = false
|
|
10639
|
+
mount_isolation_fallback = false
|
|
10640
|
+
|
|
10641
|
+
[path_groups]
|
|
10642
|
+
system_binaries = true
|
|
10643
|
+
system_libraries = true
|
|
10644
|
+
temp_directories = true
|
|
10645
|
+
device_files = false
|
|
10646
|
+
proc_filesystem = false
|
|
10647
|
+
network_config = false
|
|
10648
|
+
wsl_paths = false
|
|
10649
|
+
|
|
10650
|
+
[namespaces]
|
|
10651
|
+
mount = true
|
|
10652
|
+
pid = false
|
|
10653
|
+
network = true
|
|
10654
|
+
user = false
|
|
10655
|
+
|
|
10656
|
+
[resources]
|
|
10657
|
+
timeout_secs = 30
|
|
10658
|
+
max_memory_bytes = 268435456
|
|
10659
|
+
|
|
10660
|
+
[environment]
|
|
10661
|
+
whitelist = ["PATH", "LANG", "HOME", "USER", "HTTP_PROXY", "HTTPS_PROXY", "http_proxy", "https_proxy", "ALL_PROXY", "all_proxy", "NO_PROXY", "no_proxy"]
|
|
10662
|
+
`;
|
|
10663
|
+
function savePolicyToFile(sessionId, policyToml) {
|
|
10664
|
+
if (!isPolicySaveEnabled()) return null;
|
|
10665
|
+
try {
|
|
10666
|
+
const outDir = policyOutputDir();
|
|
10667
|
+
mkdirSync7(outDir, { recursive: true });
|
|
10668
|
+
const safeName = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
|
|
10669
|
+
const filepath = join11(outDir, `${safeName}.toml`);
|
|
10670
|
+
writeFileSync7(filepath, policyToml, "utf-8");
|
|
10671
|
+
return filepath;
|
|
10672
|
+
} catch (err) {
|
|
10673
|
+
console.warn(`[AgentMoss] \u7B56\u7565\u6587\u4EF6\u843D\u76D8\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u5224\u5B9A\uFF09: ${policyOutputDir()}: ${String(err)}`);
|
|
10674
|
+
return null;
|
|
10675
|
+
}
|
|
10676
|
+
}
|
|
10677
|
+
function parsePolicyFromLlm(response) {
|
|
10678
|
+
const tomlBlock = response.match(/```toml\s*\n([\s\S]*?)```/);
|
|
10679
|
+
if (tomlBlock) {
|
|
10680
|
+
const s = tomlBlock[1].trim();
|
|
10681
|
+
validateToml(s);
|
|
10682
|
+
return s;
|
|
10683
|
+
}
|
|
10684
|
+
const anyBlock = response.match(/```\s*\n([\s\S]*?)```/);
|
|
10685
|
+
if (anyBlock) {
|
|
10686
|
+
const s = anyBlock[1].trim();
|
|
10687
|
+
try {
|
|
10688
|
+
validateToml(s);
|
|
10689
|
+
return s;
|
|
10690
|
+
} catch {
|
|
10691
|
+
}
|
|
10692
|
+
}
|
|
10693
|
+
const whole = response.trim();
|
|
10694
|
+
validateToml(whole);
|
|
10695
|
+
return whole;
|
|
10696
|
+
}
|
|
10697
|
+
function validateToml(tomlStr) {
|
|
10698
|
+
try {
|
|
10699
|
+
parseToml(tomlStr);
|
|
10700
|
+
} catch (err) {
|
|
10701
|
+
throw new Error(`TOML \u89E3\u6790\u5931\u8D25: ${err.message}`);
|
|
10702
|
+
}
|
|
10703
|
+
}
|
|
10704
|
+
function loadPolicyMappingDoc() {
|
|
10705
|
+
const dir = resolveAssetDir(__dirname, "templates");
|
|
10706
|
+
if (dir) {
|
|
10707
|
+
const p = resolve10(dir, "policy_mapping.md");
|
|
10708
|
+
if (existsSync16(p)) return readFileSync16(p, "utf-8");
|
|
10709
|
+
}
|
|
10710
|
+
for (const cand of assetDirCandidates(__dirname, "templates")) {
|
|
10711
|
+
const p = resolve10(cand, "policy_mapping.md");
|
|
10712
|
+
if (existsSync16(p)) return readFileSync16(p, "utf-8");
|
|
10713
|
+
}
|
|
10714
|
+
return "";
|
|
10715
|
+
}
|
|
10716
|
+
function buildPolicyPrompt(promptSession) {
|
|
10717
|
+
const template = loadTemplates()["prompt1"] || "";
|
|
10718
|
+
return template.replaceAll("{prompt_policy_mapping_doc}", loadPolicyMappingDoc()).replaceAll("{prompt_session}", promptSession);
|
|
10719
|
+
}
|
|
10720
|
+
async function generatePolicyWithLLM(sessionId, promptSession) {
|
|
10721
|
+
const { default: OpenAI3 } = await import("openai");
|
|
10722
|
+
const cfg = getLLMConfig();
|
|
10723
|
+
const timeoutCfg = getTimeoutConfig();
|
|
10724
|
+
const retryCfg = getRetryConfig();
|
|
10725
|
+
const client = new OpenAI3({
|
|
10726
|
+
baseURL: cfg.baseUrl,
|
|
10727
|
+
apiKey: cfg.apiKey,
|
|
10728
|
+
timeout: (cfg.timeout || timeoutCfg.prompt1Timeout) * 1e3
|
|
10729
|
+
});
|
|
10730
|
+
const prompt1 = buildPolicyPrompt(promptSession);
|
|
10731
|
+
let lastErr = null;
|
|
10732
|
+
for (let attempt = 1; attempt <= Math.max(1, retryCfg.maxRetries); attempt++) {
|
|
10733
|
+
try {
|
|
10734
|
+
const resp = await client.chat.completions.create({
|
|
10735
|
+
model: cfg.model,
|
|
10736
|
+
temperature: 0.1,
|
|
10737
|
+
messages: [{ role: "user", content: prompt1 }]
|
|
10738
|
+
});
|
|
10739
|
+
try {
|
|
10740
|
+
const usage = resp.usage || {};
|
|
10741
|
+
recordTokenUsage({
|
|
10742
|
+
sessionId,
|
|
10743
|
+
step: "step2_policy_gen",
|
|
10744
|
+
model: cfg.model,
|
|
10745
|
+
promptTokens: usage.prompt_tokens || 0,
|
|
10746
|
+
completionTokens: usage.completion_tokens || 0,
|
|
10747
|
+
totalTokens: usage.total_tokens || 0
|
|
10748
|
+
});
|
|
10749
|
+
} catch {
|
|
10750
|
+
}
|
|
10751
|
+
return parsePolicyFromLlm(resp.choices?.[0]?.message?.content || "");
|
|
10752
|
+
} catch (err) {
|
|
10753
|
+
lastErr = err;
|
|
10754
|
+
if (attempt < Math.max(1, retryCfg.maxRetries)) {
|
|
10755
|
+
await new Promise((r) => setTimeout(r, retryCfg.retryInterval * 1e3));
|
|
10756
|
+
}
|
|
10757
|
+
}
|
|
10758
|
+
}
|
|
10759
|
+
throw lastErr ?? new Error("\u7B56\u7565\u751F\u6210\u5931\u8D25");
|
|
10760
|
+
}
|
|
10761
|
+
function allowResult(policy, judgment) {
|
|
10762
|
+
return {
|
|
10763
|
+
decision: "Allow",
|
|
10764
|
+
reason: judgment.reason,
|
|
10765
|
+
risk_level: judgment.riskLevel,
|
|
10766
|
+
risk_type: judgment.riskType,
|
|
10767
|
+
violated_layers: judgment.violatedLayers || [],
|
|
10768
|
+
policy,
|
|
10769
|
+
violated_policy: "",
|
|
10770
|
+
confidence: judgment.confidence
|
|
10771
|
+
};
|
|
10772
|
+
}
|
|
10773
|
+
async function analyze(input) {
|
|
10774
|
+
const mossLog = new MossLogger(input.sessionId);
|
|
10775
|
+
try {
|
|
10776
|
+
return await analyzeInner(input, mossLog);
|
|
10777
|
+
} catch (err) {
|
|
10778
|
+
mossLog.logError("unknown", err);
|
|
10779
|
+
const result = {
|
|
10780
|
+
decision: "Deny",
|
|
10781
|
+
reason: `\u5206\u6790\u8FC7\u7A0B\u53D1\u751F\u672A\u9884\u671F\u5F02\u5E38\uFF1A${String(err)}`,
|
|
10782
|
+
risk_level: "high",
|
|
10783
|
+
risk_type: "",
|
|
10784
|
+
violated_layers: [],
|
|
10785
|
+
policy: "",
|
|
10786
|
+
violated_policy: "\u672A\u77E5\u5F02\u5E38",
|
|
10787
|
+
confidence: 100
|
|
10788
|
+
};
|
|
10789
|
+
mossLog.logResult("Deny", result.reason);
|
|
10790
|
+
return result;
|
|
10791
|
+
}
|
|
10792
|
+
}
|
|
10793
|
+
async function analyzeInner(input, mossLog) {
|
|
10794
|
+
mossLog.logInput(input.promptSession, input.actionHistory || [], input.aNext, input.reason || "");
|
|
10795
|
+
mossLog.startStep("step1_security_judge");
|
|
10796
|
+
const judgment = await runAnalysis(input);
|
|
10797
|
+
mossLog.endStep(
|
|
10798
|
+
"step1_security_judge",
|
|
10799
|
+
`allowed=${judgment.allowed}, risk_level=${judgment.riskLevel}, source=${judgment.source}`
|
|
10800
|
+
);
|
|
10801
|
+
if (!judgment.allowed) {
|
|
10802
|
+
const denied = {
|
|
10803
|
+
decision: "Deny",
|
|
10804
|
+
reason: judgment.reason,
|
|
10805
|
+
risk_level: judgment.riskLevel,
|
|
10806
|
+
risk_type: judgment.riskType,
|
|
10807
|
+
violated_layers: judgment.violatedLayers || [],
|
|
10808
|
+
policy: "",
|
|
10809
|
+
violated_policy: `[${judgment.riskType}] ${judgment.reason}`,
|
|
10810
|
+
confidence: judgment.confidence
|
|
10811
|
+
};
|
|
10812
|
+
mossLog.logResult("Deny", denied.reason);
|
|
10813
|
+
return denied;
|
|
10814
|
+
}
|
|
10815
|
+
const cache3 = getDefaultCache();
|
|
10816
|
+
mossLog.startStep("step2_cache_lookup");
|
|
10817
|
+
const cached = cache3.get(input.sessionId, input.promptSession);
|
|
10818
|
+
if (cached !== null) {
|
|
10819
|
+
mossLog.endStep("step2_cache_lookup", "\u7F13\u5B58\u547D\u4E2D");
|
|
10820
|
+
savePolicyToFile(input.sessionId, cached);
|
|
10821
|
+
const res = allowResult(cached, judgment);
|
|
10822
|
+
mossLog.logResult("Allow", res.reason, cached);
|
|
10823
|
+
return res;
|
|
10824
|
+
}
|
|
10825
|
+
if (!isPolicyGenEnabled()) {
|
|
10826
|
+
mossLog.endStep("step2_cache_lookup", "\u7F13\u5B58\u672A\u547D\u4E2D\uFF0CPolicy \u751F\u6210\u5DF2\u7981\u7528");
|
|
10827
|
+
mossLog.startStep("step2_default_policy");
|
|
10828
|
+
cache3.put(input.sessionId, input.promptSession, DEFAULT_READONLY_POLICY);
|
|
10829
|
+
savePolicyToFile(input.sessionId, DEFAULT_READONLY_POLICY);
|
|
10830
|
+
const res = allowResult(DEFAULT_READONLY_POLICY, judgment);
|
|
10831
|
+
mossLog.endStep("step2_default_policy", "\u4F7F\u7528\u9ED8\u8BA4\u6700\u5C0F\u6743\u9650 Policy");
|
|
10832
|
+
mossLog.logResult("Allow", res.reason, DEFAULT_READONLY_POLICY);
|
|
10833
|
+
return res;
|
|
10834
|
+
}
|
|
10835
|
+
if (judgment.source === "whitelist_bypass") {
|
|
10836
|
+
mossLog.endStep("step2_cache_lookup", "\u7F13\u5B58\u672A\u547D\u4E2D\uFF0C\u767D\u540D\u5355\u5DE5\u5177");
|
|
10837
|
+
cache3.put(input.sessionId, input.promptSession, DEFAULT_READONLY_POLICY);
|
|
10838
|
+
savePolicyToFile(input.sessionId, DEFAULT_READONLY_POLICY);
|
|
10839
|
+
const res = allowResult(DEFAULT_READONLY_POLICY, judgment);
|
|
10840
|
+
mossLog.logResult("Allow", res.reason, DEFAULT_READONLY_POLICY);
|
|
10841
|
+
return res;
|
|
10842
|
+
}
|
|
10843
|
+
mossLog.endStep("step2_cache_lookup", "\u7F13\u5B58\u672A\u547D\u4E2D\uFF0C\u542F\u7528 LLM \u751F\u6210");
|
|
10844
|
+
mossLog.startStep("step2_generate_policy");
|
|
10845
|
+
try {
|
|
10846
|
+
const policy = await generatePolicyWithLLM(input.sessionId, input.promptSession);
|
|
10847
|
+
cache3.put(input.sessionId, input.promptSession, policy);
|
|
10848
|
+
savePolicyToFile(input.sessionId, policy);
|
|
10849
|
+
mossLog.endStep("step2_generate_policy", "Policy \u751F\u6210\u5E76\u7F13\u5B58\u6210\u529F");
|
|
10850
|
+
const res = allowResult(policy, judgment);
|
|
10851
|
+
mossLog.logResult("Allow", res.reason, policy);
|
|
10852
|
+
return res;
|
|
10853
|
+
} catch (err) {
|
|
10854
|
+
mossLog.endStep("step2_generate_policy", `\u5931\u8D25: ${String(err)}`);
|
|
10855
|
+
mossLog.logError("step2_generate_policy", err);
|
|
10856
|
+
const failed = {
|
|
10857
|
+
decision: "Deny",
|
|
10858
|
+
reason: `\u5B89\u5168\u68C0\u6D4B\u901A\u8FC7\uFF0C\u4F46\u7B56\u7565\u751F\u6210\u5931\u8D25\uFF1A${String(err)}`,
|
|
10859
|
+
risk_level: judgment.riskLevel,
|
|
10860
|
+
risk_type: judgment.riskType,
|
|
10861
|
+
violated_layers: judgment.violatedLayers || [],
|
|
10862
|
+
policy: "",
|
|
10863
|
+
violated_policy: "\u65E0\u6CD5\u751F\u6210\u7B56\u7565\uFF1ALLM \u670D\u52A1\u5F02\u5E38\u6216\u8FD4\u56DE\u683C\u5F0F\u65E0\u6548",
|
|
10864
|
+
confidence: judgment.confidence
|
|
10865
|
+
};
|
|
10866
|
+
mossLog.logResult("Deny", failed.reason);
|
|
10867
|
+
return failed;
|
|
10868
|
+
}
|
|
10869
|
+
}
|
|
9827
10870
|
|
|
9828
10871
|
// src/actrail.ts
|
|
9829
10872
|
init_esm_shims();
|
|
@@ -10112,7 +11155,80 @@ function loadAgentGateSessions(dbPath = "") {
|
|
|
10112
11155
|
return readJsonDir(jsonDefault);
|
|
10113
11156
|
}
|
|
10114
11157
|
|
|
11158
|
+
// src/adapters/observable.ts
|
|
11159
|
+
init_esm_shims();
|
|
11160
|
+
function isRawFormat(data) {
|
|
11161
|
+
if (!data || typeof data !== "object") return false;
|
|
11162
|
+
const d = data;
|
|
11163
|
+
if ("current_action" in d) return true;
|
|
11164
|
+
if (!("a_next" in d)) return true;
|
|
11165
|
+
if (!("session_id" in d) && "agent_id" in d) return true;
|
|
11166
|
+
return false;
|
|
11167
|
+
}
|
|
11168
|
+
function adaptObservable(rawData) {
|
|
11169
|
+
const sessionId = rawData.session_id || rawData.agent_id || "unknown";
|
|
11170
|
+
const actionRaw = rawData.a_next || rawData.current_action || {};
|
|
11171
|
+
const aNext = actionRaw && typeof actionRaw === "object" ? {
|
|
11172
|
+
action_type: actionRaw.action_type || actionRaw.tool || "unknown",
|
|
11173
|
+
action_detail: actionRaw.action_detail || actionRaw.command || ""
|
|
11174
|
+
} : { action_type: "unknown", action_detail: "" };
|
|
11175
|
+
const historyRaw = Array.isArray(rawData.action_history) ? rawData.action_history : [];
|
|
11176
|
+
const actionHistory = historyRaw.filter((h) => h && typeof h === "object").map((h) => {
|
|
11177
|
+
const item = {
|
|
11178
|
+
action_type: String(h.action_type || h.name || h.tool || ""),
|
|
11179
|
+
action_detail: String(h.action_detail || h.command || "")
|
|
11180
|
+
};
|
|
11181
|
+
if ("output" in h) item.output = h.output ?? "";
|
|
11182
|
+
return item;
|
|
11183
|
+
});
|
|
11184
|
+
const result = {
|
|
11185
|
+
session_id: sessionId,
|
|
11186
|
+
prompt_session: rawData.prompt_session || rawData.prompt || "",
|
|
11187
|
+
action_history: actionHistory,
|
|
11188
|
+
a_next: aNext,
|
|
11189
|
+
reason: rawData.reason || (actionRaw && typeof actionRaw === "object" ? actionRaw.description : "") || "",
|
|
11190
|
+
os_type: rawData.os_type || "",
|
|
11191
|
+
cwd: rawData.cwd || "",
|
|
11192
|
+
agent_id: rawData.agent_id || ""
|
|
11193
|
+
};
|
|
11194
|
+
if (!result.reason && actionRaw && typeof actionRaw === "object") {
|
|
11195
|
+
result.reason = actionRaw.description || "";
|
|
11196
|
+
}
|
|
11197
|
+
let metadata = rawData.metadata;
|
|
11198
|
+
if (!metadata || typeof metadata !== "object" || Object.keys(metadata).length === 0) {
|
|
11199
|
+
metadata = {};
|
|
11200
|
+
for (const k of ["agent_id", "sandbox_id", "trace_id"]) {
|
|
11201
|
+
if (k in rawData && !(k in result)) metadata[k] = rawData[k];
|
|
11202
|
+
}
|
|
11203
|
+
}
|
|
11204
|
+
if (metadata && Object.keys(metadata).length > 0) result.metadata = metadata;
|
|
11205
|
+
return result;
|
|
11206
|
+
}
|
|
11207
|
+
function normalizeObservablePayload(body) {
|
|
11208
|
+
if (!isRawFormat(body)) return body;
|
|
11209
|
+
const adapted = adaptObservable(body);
|
|
11210
|
+
const actionType = String(adapted.a_next?.action_type || "").trim().toLowerCase();
|
|
11211
|
+
const actionDetail = String(adapted.a_next?.action_detail || "").trim();
|
|
11212
|
+
if (!actionDetail && (actionType === "" || actionType === "unknown")) {
|
|
11213
|
+
const err = new Error(
|
|
11214
|
+
"\u65E0\u6CD5\u8BC6\u522B\u672C\u6B21\u52A8\u4F5C\uFF1A\u65E2\u6CA1\u6709 a_next\uFF08\u6807\u51C6\u683C\u5F0F\uFF09\uFF0C\u4E5F\u6CA1\u6709 current_action\uFF08\u53EF\u89C2\u6D4B\u670D\u52A1\u539F\u59CB\u683C\u5F0F\uFF09"
|
|
11215
|
+
);
|
|
11216
|
+
err.issues = [{
|
|
11217
|
+
path: ["a_next"],
|
|
11218
|
+
message: err.message
|
|
11219
|
+
}];
|
|
11220
|
+
throw err;
|
|
11221
|
+
}
|
|
11222
|
+
return adapted;
|
|
11223
|
+
}
|
|
11224
|
+
|
|
10115
11225
|
// src/routes.ts
|
|
11226
|
+
init_rule_store();
|
|
11227
|
+
init_skill_manager();
|
|
11228
|
+
init_asset_dir();
|
|
11229
|
+
import { join as join13, dirname as dirname11 } from "path";
|
|
11230
|
+
import { fileURLToPath as fileURLToPath10 } from "url";
|
|
11231
|
+
var __dirname9 = dirname11(fileURLToPath10(import.meta.url));
|
|
10116
11232
|
async function healthRoute(c) {
|
|
10117
11233
|
return c.json(HealthResponseSchema.parse({
|
|
10118
11234
|
instance: process.env.AGENT_MOSS_INSTANCE || ""
|
|
@@ -10122,13 +11238,13 @@ async function analyzeRoute(c) {
|
|
|
10122
11238
|
const t0 = performance.now();
|
|
10123
11239
|
try {
|
|
10124
11240
|
const body = await c.req.json();
|
|
10125
|
-
const req = AnalyzeRequestSchema.parse(body);
|
|
11241
|
+
const req = AnalyzeRequestSchema.parse(normalizeObservablePayload(body));
|
|
10126
11242
|
let osType = req.os_type;
|
|
10127
11243
|
if (!osType) {
|
|
10128
11244
|
osType = process.platform === "win32" ? "windows" : "linux";
|
|
10129
11245
|
}
|
|
10130
11246
|
const agentId = req.agent_id || req.metadata?.agent_type || "";
|
|
10131
|
-
const result = await
|
|
11247
|
+
const result = await analyze({
|
|
10132
11248
|
sessionId: req.session_id,
|
|
10133
11249
|
promptSession: req.prompt_session,
|
|
10134
11250
|
// output 必须透传:L2 的「授权(consent)」判定看的是**用户对提问的回答**,
|
|
@@ -10148,6 +11264,20 @@ async function analyzeRoute(c) {
|
|
|
10148
11264
|
output: h.output
|
|
10149
11265
|
}))
|
|
10150
11266
|
})),
|
|
11267
|
+
// prompt_history 必须透传:L2 意图一致性(logic-rules.ts 的
|
|
11268
|
+
// checkIntentConsistency)与 L3 语义分析(llm-analyzer.ts 的
|
|
11269
|
+
// formatPromptHistory)都靠它看**完整的交错历史**;只传 prompt_session
|
|
11270
|
+
// (最后一条用户消息)会让这两处在压缩/续跑场景下失明。
|
|
11271
|
+
// 历史 bug:请求模型里有这个字段,但路由的 runAnalysis 调用没映射它 →
|
|
11272
|
+
// 经 TS HTTP 入口的判定与 Python HTTP(routes.py 有透传)、与库内直调
|
|
11273
|
+
// runAnalysis 的结果都不一致(2026-10 修正,映射口径对齐 Python)。
|
|
11274
|
+
promptHistory: (req.prompt_history || []).map((t) => ({
|
|
11275
|
+
text: t.text,
|
|
11276
|
+
actions: t.actions.map((a) => ({
|
|
11277
|
+
actionType: a.action_type,
|
|
11278
|
+
actionDetail: a.action_detail
|
|
11279
|
+
}))
|
|
11280
|
+
})),
|
|
10151
11281
|
aNext: {
|
|
10152
11282
|
actionType: req.a_next.action_type,
|
|
10153
11283
|
actionDetail: req.a_next.action_detail
|
|
@@ -10165,15 +11295,15 @@ async function analyzeRoute(c) {
|
|
|
10165
11295
|
try {
|
|
10166
11296
|
reportEnforcementEvent({
|
|
10167
11297
|
traceId: req.session_id,
|
|
10168
|
-
decision: result.
|
|
11298
|
+
decision: result.decision === "Allow" ? "allow" : "deny",
|
|
10169
11299
|
toolName: req.a_next.action_type,
|
|
10170
11300
|
toolCommand: req.a_next.action_detail,
|
|
10171
11301
|
sessionId: req.session_id,
|
|
10172
11302
|
prompt: req.prompt_session,
|
|
10173
|
-
riskLevel: result.
|
|
10174
|
-
riskType: result.
|
|
11303
|
+
riskLevel: result.risk_level,
|
|
11304
|
+
riskType: result.risk_type,
|
|
10175
11305
|
reason: result.reason,
|
|
10176
|
-
violatedLayers: result.
|
|
11306
|
+
violatedLayers: result.violated_layers,
|
|
10177
11307
|
confidence: result.confidence,
|
|
10178
11308
|
durationMs: Math.round(durationMs * 10) / 10
|
|
10179
11309
|
});
|
|
@@ -10190,7 +11320,7 @@ async function analyzeRoute(c) {
|
|
|
10190
11320
|
}, req.session_id, Boolean(meta.is_error));
|
|
10191
11321
|
const judgment = incMatchedToDeny(inc);
|
|
10192
11322
|
if (judgment) {
|
|
10193
|
-
const layers = [...result.
|
|
11323
|
+
const layers = [...result.violated_layers];
|
|
10194
11324
|
if (!layers.includes("L2.2-stateful")) layers.push("L2.2-stateful");
|
|
10195
11325
|
console.error(
|
|
10196
11326
|
`[AgentMoss] stateful \u589E\u91CF\u62E6\u622A: agent=${agentId || "-"} property=${judgment.property_id} risk=${judgment.risk_level}`
|
|
@@ -10216,13 +11346,15 @@ async function analyzeRoute(c) {
|
|
|
10216
11346
|
}
|
|
10217
11347
|
return c.json(
|
|
10218
11348
|
AnalyzeResponseSchema.parse({
|
|
10219
|
-
decision: result.
|
|
11349
|
+
decision: result.decision,
|
|
10220
11350
|
reason: result.reason,
|
|
10221
|
-
risk_level: result.
|
|
10222
|
-
risk_type: result.
|
|
10223
|
-
violated_layers: result.
|
|
10224
|
-
policy
|
|
10225
|
-
violated_policy
|
|
11351
|
+
risk_level: result.risk_level,
|
|
11352
|
+
risk_type: result.risk_type,
|
|
11353
|
+
violated_layers: result.violated_layers,
|
|
11354
|
+
// policy / violated_policy 现在真的有值(镜像 Python analyze()):
|
|
11355
|
+
// Allow → 最小权限 TOML;Deny → 空 policy + violated_policy 描述
|
|
11356
|
+
policy: result.policy,
|
|
11357
|
+
violated_policy: result.violated_policy,
|
|
10226
11358
|
confidence: result.confidence,
|
|
10227
11359
|
analysis_duration_ms: Math.round(durationMs * 10) / 10
|
|
10228
11360
|
})
|
|
@@ -10230,12 +11362,12 @@ async function analyzeRoute(c) {
|
|
|
10230
11362
|
} catch (err) {
|
|
10231
11363
|
console.error("[AgentMoss] Analysis error:", err);
|
|
10232
11364
|
if (err instanceof SyntaxError) {
|
|
10233
|
-
return c.json({ error: "Invalid JSON"
|
|
11365
|
+
return c.json({ detail: err.message, error: "Invalid JSON" }, 422);
|
|
10234
11366
|
}
|
|
10235
11367
|
if (err?.issues) {
|
|
10236
|
-
return c.json({ error: "Validation error"
|
|
11368
|
+
return c.json({ detail: err.issues, error: "Validation error" }, 422);
|
|
10237
11369
|
}
|
|
10238
|
-
return c.json({ error: "Internal error"
|
|
11370
|
+
return c.json({ detail: String(err), error: "Internal error" }, 500);
|
|
10239
11371
|
}
|
|
10240
11372
|
}
|
|
10241
11373
|
var reviewStore = null;
|
|
@@ -10293,13 +11425,25 @@ async function reviewRoute(c) {
|
|
|
10293
11425
|
const violations = [];
|
|
10294
11426
|
if (sessions.length > 0 || tracker.nodes.length > 0) {
|
|
10295
11427
|
const eng = new FormalEngine();
|
|
11428
|
+
const verifySessions = sessions.length > 0 ? sessions.map((s) => ({ sessionId: s.sessionId, actionHistory: s.actionHistory })) : tracker.nodes.length > 0 ? [{
|
|
11429
|
+
sessionId: tracker.agentId,
|
|
11430
|
+
actionHistory: tracker.nodes.map((n) => ({
|
|
11431
|
+
actionType: n.actionType,
|
|
11432
|
+
actionDetail: n.actionDetail
|
|
11433
|
+
}))
|
|
11434
|
+
}] : [];
|
|
10296
11435
|
const full = eng.verify(
|
|
10297
|
-
|
|
11436
|
+
verifySessions,
|
|
10298
11437
|
req.a_next ? { actionType: req.a_next.action_type, actionDetail: req.a_next.action_detail } : void 0
|
|
10299
11438
|
);
|
|
10300
11439
|
verified = full.verified;
|
|
10301
11440
|
for (const v of full.violations) {
|
|
10302
|
-
violations.push({
|
|
11441
|
+
violations.push({
|
|
11442
|
+
property_id: v.propertyId,
|
|
11443
|
+
reason: v.reason,
|
|
11444
|
+
risk_level: v.riskLevel,
|
|
11445
|
+
path: v.path
|
|
11446
|
+
});
|
|
10303
11447
|
}
|
|
10304
11448
|
}
|
|
10305
11449
|
const pending = tracker.pendingPrefixes.map((p) => ({
|
|
@@ -10329,6 +11473,101 @@ async function reviewRoute(c) {
|
|
|
10329
11473
|
return c.json({ error: "Internal error", detail: String(err) }, 500);
|
|
10330
11474
|
}
|
|
10331
11475
|
}
|
|
11476
|
+
function getBrainRuleStore() {
|
|
11477
|
+
return new BrainRuleStore();
|
|
11478
|
+
}
|
|
11479
|
+
function getBrainSkillManager() {
|
|
11480
|
+
const dir = resolveAssetDir(__dirname9, "skills") ?? join13(__dirname9, "..", "..", "agent_moss", "skills");
|
|
11481
|
+
return new BrainSkillManager(dir);
|
|
11482
|
+
}
|
|
11483
|
+
async function brainRulesRoute(c) {
|
|
11484
|
+
const store = getBrainRuleStore();
|
|
11485
|
+
const layerRaw = c.req.query("layer");
|
|
11486
|
+
const layer = layerRaw ? parseInt(layerRaw, 10) : void 0;
|
|
11487
|
+
const rules = store.getActiveRules(layer);
|
|
11488
|
+
return c.json({
|
|
11489
|
+
rules: rules.map((r) => ({
|
|
11490
|
+
id: r.id,
|
|
11491
|
+
layer: r.layer,
|
|
11492
|
+
rule_type: r.ruleType,
|
|
11493
|
+
pattern: r.pattern,
|
|
11494
|
+
scope: r.scope,
|
|
11495
|
+
confidence: r.confidence,
|
|
11496
|
+
explanation: r.explanation
|
|
11497
|
+
}))
|
|
11498
|
+
});
|
|
11499
|
+
}
|
|
11500
|
+
async function brainDraftsRoute(c) {
|
|
11501
|
+
const store = getBrainRuleStore();
|
|
11502
|
+
return c.json({
|
|
11503
|
+
drafts: store.getDraftRules().map((r) => ({
|
|
11504
|
+
id: r.id,
|
|
11505
|
+
layer: r.layer,
|
|
11506
|
+
rule_type: r.ruleType,
|
|
11507
|
+
pattern: r.pattern,
|
|
11508
|
+
scope: r.scope,
|
|
11509
|
+
confidence: r.confidence,
|
|
11510
|
+
explanation: r.explanation,
|
|
11511
|
+
source_event_ids: r.sourceEventIds
|
|
11512
|
+
}))
|
|
11513
|
+
});
|
|
11514
|
+
}
|
|
11515
|
+
async function brainApproveRoute(c) {
|
|
11516
|
+
const ruleId = c.req.param("rule_id") ?? "";
|
|
11517
|
+
try {
|
|
11518
|
+
getBrainRuleStore().approveRule(ruleId);
|
|
11519
|
+
return c.json({ status: "approved" });
|
|
11520
|
+
} catch (err) {
|
|
11521
|
+
return c.json({ detail: String(err.message) }, 400);
|
|
11522
|
+
}
|
|
11523
|
+
}
|
|
11524
|
+
async function brainRejectRoute(c) {
|
|
11525
|
+
const ruleId = c.req.param("rule_id") ?? "";
|
|
11526
|
+
const reason = c.req.query("reason") || "";
|
|
11527
|
+
try {
|
|
11528
|
+
getBrainRuleStore().rejectRule(ruleId);
|
|
11529
|
+
return c.json({ status: "rejected" });
|
|
11530
|
+
} catch (err) {
|
|
11531
|
+
return c.json({ detail: String(err.message) }, 400);
|
|
11532
|
+
}
|
|
11533
|
+
}
|
|
11534
|
+
async function brainStatsRoute(c) {
|
|
11535
|
+
return c.json(getBrainRuleStore().getStats());
|
|
11536
|
+
}
|
|
11537
|
+
async function brainSkillsRoute(c) {
|
|
11538
|
+
const mgr = getBrainSkillManager();
|
|
11539
|
+
const skills = mgr.getActiveSkills();
|
|
11540
|
+
mgr.close();
|
|
11541
|
+
return c.json({ skills });
|
|
11542
|
+
}
|
|
11543
|
+
async function brainExportRoute(c) {
|
|
11544
|
+
const store = getBrainRuleStore();
|
|
11545
|
+
return c.json({
|
|
11546
|
+
rules: store.getRulesForExport().map((r) => ({
|
|
11547
|
+
id: r.id,
|
|
11548
|
+
layer: r.layer,
|
|
11549
|
+
rule_type: r.ruleType,
|
|
11550
|
+
pattern: r.pattern,
|
|
11551
|
+
scope: r.scope,
|
|
11552
|
+
confidence: r.confidence,
|
|
11553
|
+
explanation: r.explanation,
|
|
11554
|
+
origin_node: r.originNode
|
|
11555
|
+
}))
|
|
11556
|
+
});
|
|
11557
|
+
}
|
|
11558
|
+
async function brainImportRoute(c) {
|
|
11559
|
+
const body = await c.req.json();
|
|
11560
|
+
const rules = Array.isArray(body) ? body : [];
|
|
11561
|
+
getBrainRuleStore().importRules(rules.map((r) => ({
|
|
11562
|
+
layer: r.layer ?? 1,
|
|
11563
|
+
ruleType: r.rule_type ?? "",
|
|
11564
|
+
pattern: r.pattern ?? "",
|
|
11565
|
+
scope: r.scope ?? "all",
|
|
11566
|
+
confidence: r.confidence ?? 0.9,
|
|
11567
|
+
explanation: r.explanation ?? ""
|
|
11568
|
+
})));
|
|
11569
|
+
return c.json({ imported: rules.length });
|
|
11570
|
+
}
|
|
10332
11571
|
|
|
10333
11572
|
// src/console/index.ts
|
|
10334
11573
|
init_esm_shims();
|
|
@@ -10338,12 +11577,13 @@ init_esm_shims();
|
|
|
10338
11577
|
init_runtime_config();
|
|
10339
11578
|
init_token_stats();
|
|
10340
11579
|
init_rules_loader();
|
|
11580
|
+
init_asset_dir();
|
|
10341
11581
|
import { Hono } from "hono";
|
|
10342
|
-
import { readFileSync as
|
|
10343
|
-
import { join as
|
|
10344
|
-
import { fileURLToPath as
|
|
10345
|
-
var
|
|
10346
|
-
var STATIC_DIR =
|
|
11582
|
+
import { readFileSync as readFileSync18, existsSync as existsSync18, unlinkSync as unlinkSync4 } from "fs";
|
|
11583
|
+
import { join as join14, dirname as dirname12 } from "path";
|
|
11584
|
+
import { fileURLToPath as fileURLToPath11 } from "url";
|
|
11585
|
+
var __dirname10 = dirname12(fileURLToPath11(import.meta.url));
|
|
11586
|
+
var STATIC_DIR = join14(__dirname10, "static");
|
|
10347
11587
|
var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
|
|
10348
11588
|
function getConsoleToken() {
|
|
10349
11589
|
return process.env[CONSOLE_TOKEN_ENV] || "";
|
|
@@ -10358,7 +11598,7 @@ function verifyAuth(req) {
|
|
|
10358
11598
|
function authMiddleware() {
|
|
10359
11599
|
return async (c, next) => {
|
|
10360
11600
|
if (!verifyAuth(c.req.raw)) {
|
|
10361
|
-
return c.json({
|
|
11601
|
+
return c.json({ detail: "Unauthorized: invalid or missing Bearer token" }, 401);
|
|
10362
11602
|
}
|
|
10363
11603
|
await next();
|
|
10364
11604
|
};
|
|
@@ -10367,9 +11607,9 @@ function createConsoleRouter() {
|
|
|
10367
11607
|
const console2 = new Hono();
|
|
10368
11608
|
console2.use("/api/*", authMiddleware());
|
|
10369
11609
|
const serveIndex = (c) => {
|
|
10370
|
-
const indexFile =
|
|
10371
|
-
if (
|
|
10372
|
-
return c.html(
|
|
11610
|
+
const indexFile = join14(STATIC_DIR, "index.html");
|
|
11611
|
+
if (existsSync18(indexFile)) {
|
|
11612
|
+
return c.html(readFileSync18(indexFile, "utf-8"));
|
|
10373
11613
|
}
|
|
10374
11614
|
return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
|
|
10375
11615
|
};
|
|
@@ -10381,8 +11621,18 @@ function createConsoleRouter() {
|
|
|
10381
11621
|
});
|
|
10382
11622
|
console2.put("/api/layers", async (c) => {
|
|
10383
11623
|
const body = await c.req.json();
|
|
11624
|
+
const validKeys = /* @__PURE__ */ new Set(["L1_heuristic", "L2_logic_rules", "L3_llm_analysis"]);
|
|
11625
|
+
if (!validKeys.has(body.layer_key)) {
|
|
11626
|
+
return c.json({ detail: `Invalid layer_key: ${body.layer_key}` }, 400);
|
|
11627
|
+
}
|
|
11628
|
+
const envOverrides = getEnvOverrides();
|
|
11629
|
+
if (envOverrides[body.layer_key]?.overridden) {
|
|
11630
|
+
return c.json({
|
|
11631
|
+
detail: `\u5C42\u7EA7 ${body.layer_key} \u88AB\u73AF\u5883\u53D8\u91CF ${envOverrides[body.layer_key].env_var}=1 \u5F3A\u5236\u8986\u76D6\uFF0CWeb \u8BBE\u7F6E\u65E0\u6548`
|
|
11632
|
+
}, 409);
|
|
11633
|
+
}
|
|
10384
11634
|
const rc = updateLayerEnabled(body.layer_key, body.enabled);
|
|
10385
|
-
return c.json({ layers: rc.layers, env_overrides:
|
|
11635
|
+
return c.json({ layers: rc.layers, env_overrides: envOverrides });
|
|
10386
11636
|
});
|
|
10387
11637
|
function getL22View() {
|
|
10388
11638
|
const ltlHuman = {
|
|
@@ -10459,7 +11709,21 @@ function createConsoleRouter() {
|
|
|
10459
11709
|
}
|
|
10460
11710
|
console2.get("/api/rules", (c) => {
|
|
10461
11711
|
const rc = loadRuntimeConfig();
|
|
10462
|
-
|
|
11712
|
+
const layer = c.req.query("layer");
|
|
11713
|
+
const category = c.req.query("category");
|
|
11714
|
+
const out = {};
|
|
11715
|
+
for (const layerKey of ["L1_rules", "L2_rules"]) {
|
|
11716
|
+
if (layer && layer !== layerKey) continue;
|
|
11717
|
+
const layerData = rc[layerKey] || {};
|
|
11718
|
+
const picked = {};
|
|
11719
|
+
for (const [catName, catData] of Object.entries(layerData)) {
|
|
11720
|
+
if (category && category !== catName) continue;
|
|
11721
|
+
picked[catName] = catData;
|
|
11722
|
+
}
|
|
11723
|
+
out[layerKey] = picked;
|
|
11724
|
+
}
|
|
11725
|
+
if (!category) out.L2_2 = getL22View();
|
|
11726
|
+
return c.json(out);
|
|
10463
11727
|
});
|
|
10464
11728
|
console2.put("/api/rules/enabled", async (c) => {
|
|
10465
11729
|
const b = await c.req.json();
|
|
@@ -10468,6 +11732,9 @@ function createConsoleRouter() {
|
|
|
10468
11732
|
});
|
|
10469
11733
|
console2.put("/api/rules/deny_mode", async (c) => {
|
|
10470
11734
|
const b = await c.req.json();
|
|
11735
|
+
if (!["deny_write", "deny_read", "deny_both"].includes(b.deny_mode)) {
|
|
11736
|
+
return c.json({ detail: `\u65E0\u6548\u7684 deny_mode: ${b.deny_mode}` }, 400);
|
|
11737
|
+
}
|
|
10471
11738
|
const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
|
|
10472
11739
|
return c.json(withL22View(rc));
|
|
10473
11740
|
});
|
|
@@ -10499,8 +11766,10 @@ function createConsoleRouter() {
|
|
|
10499
11766
|
});
|
|
10500
11767
|
console2.delete("/api/rules", async (c) => {
|
|
10501
11768
|
const b = await c.req.json();
|
|
10502
|
-
deleteCustomRule(b.layer, b.category, b.rule_id);
|
|
10503
|
-
|
|
11769
|
+
const rc = deleteCustomRule(b.layer, b.category, b.rule_id);
|
|
11770
|
+
if (rc === null) {
|
|
11771
|
+
return c.json({ detail: "\u5185\u7F6E\u89C4\u5219\u4E0D\u53EF\u5220\u9664\uFF0C\u53EA\u80FD\u7981\u7528" }, 403);
|
|
11772
|
+
}
|
|
10504
11773
|
return c.json(withL22View(rc));
|
|
10505
11774
|
});
|
|
10506
11775
|
console2.get("/api/skills", (c) => {
|
|
@@ -10524,22 +11793,26 @@ function createConsoleRouter() {
|
|
|
10524
11793
|
});
|
|
10525
11794
|
console2.delete("/api/skills", async (c) => {
|
|
10526
11795
|
const b = await c.req.json();
|
|
10527
|
-
deleteCustomSkill(b.skill_id, b.category);
|
|
10528
|
-
|
|
11796
|
+
const rc = deleteCustomSkill(b.skill_id, b.category);
|
|
11797
|
+
if (rc === null) {
|
|
11798
|
+
return c.json({ detail: "\u5185\u7F6E skill \u4E0D\u53EF\u5220\u9664\uFF0C\u53EA\u80FD\u7981\u7528" }, 403);
|
|
11799
|
+
}
|
|
10529
11800
|
return c.json(withL22View(rc));
|
|
10530
11801
|
});
|
|
10531
11802
|
console2.get("/api/skills/:skill_id/content", async (c) => {
|
|
10532
11803
|
const skillId = c.req.param("skill_id");
|
|
10533
11804
|
if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
|
|
10534
|
-
return c.json({
|
|
11805
|
+
return c.json({ detail: "\u975E\u6CD5 skill id" }, 400);
|
|
10535
11806
|
}
|
|
10536
|
-
const
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
|
|
11807
|
+
const userPath = join14(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
|
|
11808
|
+
const builtinDir = resolveAssetDir(__dirname10, "skills");
|
|
11809
|
+
const candidates = [userPath, builtinDir ? join14(builtinDir, `${skillId}.md`) : ""].filter(Boolean);
|
|
11810
|
+
for (const p of candidates) {
|
|
11811
|
+
if (existsSync18(p)) {
|
|
11812
|
+
return c.json({ skill_id: skillId, path: p, content: readFileSync18(p, "utf-8") });
|
|
11813
|
+
}
|
|
10541
11814
|
}
|
|
10542
|
-
return c.
|
|
11815
|
+
return c.json({ detail: `skill ${skillId} \u4E0D\u5B58\u5728` }, 404);
|
|
10543
11816
|
});
|
|
10544
11817
|
console2.get("/api/agents", (c) => {
|
|
10545
11818
|
const rc = loadRuntimeConfig();
|
|
@@ -10631,12 +11904,19 @@ function createConsoleRouter() {
|
|
|
10631
11904
|
});
|
|
10632
11905
|
console2.post("/api/reset", (c) => {
|
|
10633
11906
|
const defaults = generateSourceDefaults();
|
|
10634
|
-
|
|
11907
|
+
try {
|
|
11908
|
+
const p = getRuntimeConfigPath();
|
|
11909
|
+
if (existsSync18(p)) unlinkSync4(p);
|
|
11910
|
+
} catch {
|
|
11911
|
+
}
|
|
10635
11912
|
return c.json(withL22View(defaults));
|
|
10636
11913
|
});
|
|
11914
|
+
const parseDays = (v) => {
|
|
11915
|
+
const n = parseInt(v || "0", 10);
|
|
11916
|
+
return Number.isFinite(n) ? n : 0;
|
|
11917
|
+
};
|
|
10637
11918
|
console2.get("/api/token-stats", (c) => {
|
|
10638
|
-
|
|
10639
|
-
return c.json(getTokenStats(days));
|
|
11919
|
+
return c.json(getTokenStats(parseDays(c.req.query("days"))));
|
|
10640
11920
|
});
|
|
10641
11921
|
console2.get("/api/token-stats/recent", (c) => {
|
|
10642
11922
|
const limit = parseInt(c.req.query("limit") || "20", 10);
|
|
@@ -10644,12 +11924,11 @@ function createConsoleRouter() {
|
|
|
10644
11924
|
return c.json({ records: getRecentRecords(limit, offset), total: getRecentRecordsCount() });
|
|
10645
11925
|
});
|
|
10646
11926
|
console2.get("/api/token-stats/trend", (c) => {
|
|
10647
|
-
|
|
10648
|
-
return c.json(getTokenTrend(days));
|
|
11927
|
+
return c.json(getTokenTrend(parseDays(c.req.query("days"))));
|
|
10649
11928
|
});
|
|
10650
11929
|
console2.post("/api/token-stats/reset", (c) => {
|
|
10651
11930
|
resetTokenStats();
|
|
10652
|
-
return c.json({
|
|
11931
|
+
return c.json({ detail: "Token stats reset successfully" });
|
|
10653
11932
|
});
|
|
10654
11933
|
return console2;
|
|
10655
11934
|
}
|
|
@@ -10664,25 +11943,27 @@ function logDeploymentSelfCheck() {
|
|
|
10664
11943
|
const layerKeys = ["L1_heuristic", "L2_logic_rules", "L3_llm_analysis", "agent_rules_enabled", "scenario_rules_enabled"];
|
|
10665
11944
|
console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
|
|
10666
11945
|
try {
|
|
10667
|
-
const
|
|
10668
|
-
if (
|
|
10669
|
-
console.
|
|
10670
|
-
|
|
11946
|
+
const dirs = rulesDirs().filter((d) => existsSync19(d));
|
|
11947
|
+
if (dirs.length > 0) {
|
|
11948
|
+
console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
|
|
11949
|
+
}
|
|
11950
|
+
const loaded = ruleFilesLoaded();
|
|
11951
|
+
if (loaded.length > 0) {
|
|
11952
|
+
const ov = loadOverrideRules();
|
|
11953
|
+
const ex = loadExtraRules();
|
|
11954
|
+
console.error(`\u26A0\uFE0F \u5DF2\u4ECE\u89C4\u5219\u76EE\u5F55\u52A0\u8F7D ${loaded.length} \u4E2A\u6587\u4EF6\uFF08\u6574\u4EFD\u66FF\u6362 / \u8FFD\u52A0\uFF0C\u5185\u7F6E\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4EEC\u4E0A\u9762\uFF09:`);
|
|
11955
|
+
for (const f of loaded) console.error(` ${f}`);
|
|
11956
|
+
const ovs = Object.entries(ov).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
|
|
11957
|
+
if (ovs) console.error(` \u8986\u76D6\u7C7B\u522B: ${ovs}\uFF08**\u6574\u4EFD\u66FF\u6362**\uFF1A\u672A\u5217\u51FA\u7684\u5185\u7F6E\u89C4\u5219\u4E0D\u518D\u751F\u6548\uFF09`);
|
|
11958
|
+
const exs = Object.entries(ex).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
|
|
11959
|
+
if (exs) console.log(` \u8FFD\u52A0\u7C7B\u522B: ${exs}\uFF08\u5185\u7F6E\u4FDD\u7559\uFF0C\u8FFD\u52A0\u5728\u5176\u540E\u4E00\u8D77\u5224\u5B9A\uFF09`);
|
|
11960
|
+
}
|
|
11961
|
+
const ignored = ignoredRuleFiles();
|
|
11962
|
+
if (ignored.length > 0) {
|
|
10671
11963
|
console.error(
|
|
10672
|
-
|
|
10673
|
-
);
|
|
10674
|
-
}
|
|
10675
|
-
} catch {
|
|
10676
|
-
}
|
|
10677
|
-
try {
|
|
10678
|
-
const extras = extraRuleFiles();
|
|
10679
|
-
if (extras.length > 0) {
|
|
10680
|
-
const cats = loadExtraRules();
|
|
10681
|
-
const total = Object.values(cats).reduce((n, v) => n + v.length, 0);
|
|
10682
|
-
const catSummary = Object.entries(cats).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
|
|
10683
|
-
console.log(
|
|
10684
|
-
`\u5DF2\u4ECE\u8FFD\u52A0\u76EE\u5F55 ${extraRulesDirs().join(", ")} \u52A0\u8F7D ${extras.length} \u4E2A\u89C4\u5219\u6587\u4EF6\uFF08\u5171 ${total} \u6761${catSummary ? `\uFF0C\u7C7B\u522B\uFF1A${catSummary}` : ""}\uFF09\u3002\u8FFD\u52A0\u8BED\u4E49\uFF1A\u5185\u7F6E\u89C4\u5219\u5168\u90E8\u4FDD\u7559\uFF0C\u8FD9\u4E9B\u89C4\u5219\u6302\u5728\u5176\u540E\u4E00\u8D77\u5224\u5B9A\u3002`
|
|
11964
|
+
`\u26A0\uFE0F \u89C4\u5219\u76EE\u5F55\u91CC\u6709 ${ignored.length} \u4E2A json **\u4E0D\u4F1A\u88AB\u8BFB\u53D6**\uFF08\u53EA\u8BA4 ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09:`
|
|
10685
11965
|
);
|
|
11966
|
+
for (const f of ignored) console.error(` ${f}`);
|
|
10686
11967
|
}
|
|
10687
11968
|
} catch {
|
|
10688
11969
|
}
|
|
@@ -10706,18 +11987,26 @@ function createApp() {
|
|
|
10706
11987
|
app.get("/api/v1/health", healthRoute);
|
|
10707
11988
|
app.post("/api/v1/analyze", analyzeRoute);
|
|
10708
11989
|
app.post("/api/v1/review", reviewRoute);
|
|
11990
|
+
app.get("/api/v1/brain/rules", brainRulesRoute);
|
|
11991
|
+
app.get("/api/v1/brain/rules/drafts", brainDraftsRoute);
|
|
11992
|
+
app.post("/api/v1/brain/rules/:rule_id/approve", brainApproveRoute);
|
|
11993
|
+
app.post("/api/v1/brain/rules/:rule_id/reject", brainRejectRoute);
|
|
11994
|
+
app.get("/api/v1/brain/stats", brainStatsRoute);
|
|
11995
|
+
app.get("/api/v1/brain/skills", brainSkillsRoute);
|
|
11996
|
+
app.get("/api/v1/brain/rules/export", brainExportRoute);
|
|
11997
|
+
app.post("/api/v1/brain/rules/import", brainImportRoute);
|
|
10709
11998
|
app.route("/console", createConsoleRouter());
|
|
10710
11999
|
app.get("/console/", (c) => c.redirect("/console"));
|
|
10711
12000
|
return app;
|
|
10712
12001
|
}
|
|
10713
12002
|
async function findFreePort(startPort, host = "127.0.0.1", scan = 100) {
|
|
10714
12003
|
for (let port = startPort; port < startPort + scan; port++) {
|
|
10715
|
-
const free = await new Promise((
|
|
12004
|
+
const free = await new Promise((resolve11) => {
|
|
10716
12005
|
const server = net.createServer();
|
|
10717
12006
|
server.listen(port, host, () => {
|
|
10718
|
-
server.close(() =>
|
|
12007
|
+
server.close(() => resolve11(true));
|
|
10719
12008
|
});
|
|
10720
|
-
server.on("error", () =>
|
|
12009
|
+
server.on("error", () => resolve11(false));
|
|
10721
12010
|
});
|
|
10722
12011
|
if (free) {
|
|
10723
12012
|
if (port !== startPort) {
|
|
@@ -10773,11 +12062,71 @@ async function startServer(host = "127.0.0.1", port = 9090) {
|
|
|
10773
12062
|
}
|
|
10774
12063
|
};
|
|
10775
12064
|
}
|
|
12065
|
+
async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_moss.sock") {
|
|
12066
|
+
const app = createApp();
|
|
12067
|
+
let server = null;
|
|
12068
|
+
return {
|
|
12069
|
+
socketPath,
|
|
12070
|
+
close: async () => {
|
|
12071
|
+
if (!server) return;
|
|
12072
|
+
await new Promise((resolve11) => {
|
|
12073
|
+
server.close(() => resolve11());
|
|
12074
|
+
});
|
|
12075
|
+
server = null;
|
|
12076
|
+
},
|
|
12077
|
+
serve: async () => {
|
|
12078
|
+
const { mkdirSync: mkdirSync8, existsSync: existsSync20, unlinkSync: unlinkSync5 } = await import("fs");
|
|
12079
|
+
const { dirname: dirname13 } = await import("path");
|
|
12080
|
+
const dir = dirname13(socketPath);
|
|
12081
|
+
if (dir) {
|
|
12082
|
+
try {
|
|
12083
|
+
mkdirSync8(dir, { recursive: true });
|
|
12084
|
+
} catch {
|
|
12085
|
+
}
|
|
12086
|
+
}
|
|
12087
|
+
if (existsSync20(socketPath)) {
|
|
12088
|
+
const { connect } = await import("net");
|
|
12089
|
+
const inUse = await new Promise((resolve11) => {
|
|
12090
|
+
const probe = connect(socketPath);
|
|
12091
|
+
const done = (v) => {
|
|
12092
|
+
probe.destroy();
|
|
12093
|
+
resolve11(v);
|
|
12094
|
+
};
|
|
12095
|
+
probe.once("connect", () => done(true));
|
|
12096
|
+
probe.once("error", () => done(false));
|
|
12097
|
+
probe.setTimeout(500, () => done(false));
|
|
12098
|
+
});
|
|
12099
|
+
if (inUse) {
|
|
12100
|
+
console.warn(
|
|
12101
|
+
`[AgentMoss] socket ${socketPath} \u5DF2\u6709\u670D\u52A1\u5728\u76D1\u542C\uFF0C\u4E0D\u6E05\u7406\uFF08bind \u4F1A\u56E0\u5360\u7528\u800C\u5931\u8D25\uFF09`
|
|
12102
|
+
);
|
|
12103
|
+
} else {
|
|
12104
|
+
try {
|
|
12105
|
+
unlinkSync5(socketPath);
|
|
12106
|
+
console.log(`[AgentMoss] \u6E05\u7406\u6B8B\u7559 socket \u6587\u4EF6: ${socketPath}`);
|
|
12107
|
+
} catch {
|
|
12108
|
+
}
|
|
12109
|
+
}
|
|
12110
|
+
}
|
|
12111
|
+
logDeploymentSelfCheck();
|
|
12112
|
+
await warmUpEngine();
|
|
12113
|
+
const { getRequestListener } = await import("@hono/node-server");
|
|
12114
|
+
const { createServer } = await import("http");
|
|
12115
|
+
server = createServer(getRequestListener(app.fetch));
|
|
12116
|
+
await new Promise((resolve11, reject) => {
|
|
12117
|
+
server.once("error", reject);
|
|
12118
|
+
server.listen(socketPath, () => resolve11());
|
|
12119
|
+
});
|
|
12120
|
+
console.log(`AgentMoss HTTP server on unix socket: ${socketPath}`);
|
|
12121
|
+
}
|
|
12122
|
+
};
|
|
12123
|
+
}
|
|
10776
12124
|
export {
|
|
10777
12125
|
createApp,
|
|
10778
12126
|
findFreePort,
|
|
10779
12127
|
logDeploymentSelfCheck,
|
|
10780
|
-
startServer
|
|
12128
|
+
startServer,
|
|
12129
|
+
startUnixSocketServer
|
|
10781
12130
|
};
|
|
10782
12131
|
//! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
|
|
10783
12132
|
//! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
|