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