@kenhkl/agent-moss 0.7.13 → 0.8.0

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 CHANGED
@@ -29,6 +29,20 @@ var init_esm_shims = __esm({
29
29
  });
30
30
 
31
31
  // src/config.ts
32
+ var config_exports = {};
33
+ __export(config_exports, {
34
+ detectProvider: () => detectProvider,
35
+ getCustomRules: () => getCustomRules,
36
+ getLLMConfig: () => getLLMConfig,
37
+ getLLMFailMode: () => getLLMFailMode,
38
+ getProviderHeaders: () => getProviderHeaders,
39
+ getRetryConfig: () => getRetryConfig,
40
+ getTimeoutConfig: () => getTimeoutConfig,
41
+ isHeuristicEnabled: () => isHeuristicEnabled,
42
+ isLLMAvailable: () => isLLMAvailable,
43
+ isLogicRulesEnabled: () => isLogicRulesEnabled,
44
+ updateLLMConfig: () => updateLLMConfig
45
+ });
32
46
  import fs from "fs";
33
47
  import path2 from "path";
34
48
  import os from "os";
@@ -108,6 +122,9 @@ function loadCustomRules() {
108
122
  function getLLMConfig() {
109
123
  return llmConfig;
110
124
  }
125
+ function updateLLMConfig(partial) {
126
+ llmConfig = { ...llmConfig, ...partial };
127
+ }
111
128
  function getCustomRules() {
112
129
  return customRules;
113
130
  }
@@ -115,6 +132,10 @@ function isLLMAvailable() {
115
132
  if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
116
133
  return llmConfig.apiKey.length > 0;
117
134
  }
135
+ function getLLMFailMode() {
136
+ const val = (process.env.AGENT_MOSS_LLM_FAIL_MODE || "fail_open").toLowerCase();
137
+ return val === "fail_closed" ? "fail_closed" : "fail_open";
138
+ }
118
139
  function detectProvider(baseUrl, providerHint = "") {
119
140
  if (providerHint) {
120
141
  const hint = providerHint.toLowerCase();
@@ -189,6 +210,56 @@ var init_config = __esm({
189
210
  }
190
211
  });
191
212
 
213
+ // src/agents/index.ts
214
+ import { readFileSync, existsSync } from "fs";
215
+ import { resolve, dirname } from "path";
216
+ import { fileURLToPath as fileURLToPath2 } from "url";
217
+ function resolveAgentsDir() {
218
+ if (existsSync(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
219
+ if (existsSync(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
220
+ return AGENTS_DIR_PROD;
221
+ }
222
+ function getAgentExtensions(agentId) {
223
+ if (!agentId) return null;
224
+ if (cache.has(agentId)) return cache.get(agentId) ?? null;
225
+ const agentsDir = resolveAgentsDir();
226
+ const rulesFile = resolve(agentsDir, agentId, "rules.json");
227
+ if (!existsSync(rulesFile)) {
228
+ cache.set(agentId, null);
229
+ return null;
230
+ }
231
+ try {
232
+ const raw = readFileSync(rulesFile, "utf-8");
233
+ const ext = JSON.parse(raw);
234
+ if (!ext.agent_id) {
235
+ console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
236
+ cache.set(agentId, null);
237
+ return null;
238
+ }
239
+ cache.set(agentId, ext);
240
+ console.log(
241
+ `[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills`
242
+ );
243
+ return ext;
244
+ } catch (e) {
245
+ console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
246
+ cache.set(agentId, null);
247
+ return null;
248
+ }
249
+ }
250
+ var __filename2, __dirname2, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
251
+ var init_agents = __esm({
252
+ "src/agents/index.ts"() {
253
+ "use strict";
254
+ init_esm_shims();
255
+ __filename2 = fileURLToPath2(import.meta.url);
256
+ __dirname2 = dirname(__filename2);
257
+ AGENTS_DIR_PROD = resolve(__dirname2, "agents");
258
+ AGENTS_DIR_DEV_FALLBACK = resolve(__dirname2, "../../../agent_moss/agents");
259
+ cache = /* @__PURE__ */ new Map();
260
+ }
261
+ });
262
+
192
263
  // src/engine/heuristic.ts
193
264
  function runHeuristicCheck(input) {
194
265
  const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
@@ -222,8 +293,24 @@ function runHeuristicCheck(input) {
222
293
  }
223
294
  }
224
295
  highestRiskType = topRiskType || highestRiskType;
296
+ const agentExt = getAgentExtensions(input.agentId);
297
+ if (agentExt?.extra_command_patterns) {
298
+ for (const rule of agentExt.extra_command_patterns) {
299
+ try {
300
+ const re = new RegExp(rule.pattern, "i");
301
+ if (re.test(command)) {
302
+ matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
303
+ if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
304
+ highestSeverity = rule.risk_level;
305
+ highestRiskType = rule.risk_type;
306
+ }
307
+ }
308
+ } catch {
309
+ }
310
+ }
311
+ }
225
312
  const injectionText = `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
226
- const injectionHits = checkInjectionKeywords(injectionText);
313
+ const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
227
314
  if (injectionHits.length > 0) {
228
315
  const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
229
316
  matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
@@ -240,18 +327,26 @@ function runHeuristicCheck(input) {
240
327
  riskType: highestRiskType,
241
328
  confidence: 95,
242
329
  source: "heuristic",
243
- violatedLayers: ["1"]
330
+ violatedLayers: ["1.1"]
331
+ // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
244
332
  };
245
333
  }
246
334
  return null;
247
335
  }
248
- function checkInjectionKeywords(text) {
336
+ function checkInjectionKeywords(text, extraKeywords) {
249
337
  const hits = [];
250
338
  for (const entry of INJECTION_KEYWORDS) {
251
339
  if (text.includes(entry.keyword.toLowerCase())) {
252
340
  hits.push(entry);
253
341
  }
254
342
  }
343
+ if (extraKeywords) {
344
+ for (const entry of extraKeywords) {
345
+ if (text.includes(entry.keyword.toLowerCase())) {
346
+ hits.push({ keyword: entry.keyword, riskLevel: entry.risk_level, lang: entry.lang });
347
+ }
348
+ }
349
+ }
255
350
  return hits;
256
351
  }
257
352
  function isInlineScriptCommand(actionDetail) {
@@ -268,6 +363,7 @@ var init_heuristic = __esm({
268
363
  "use strict";
269
364
  init_esm_shims();
270
365
  init_config();
366
+ init_agents();
271
367
  INJECTION_KEYWORDS = [
272
368
  // ---- Instruction Override ----
273
369
  { keyword: "ignore previous instructions", riskLevel: "critical", lang: "en" },
@@ -321,14 +417,10 @@ var init_heuristic = __esm({
321
417
  { keyword: "DAN mode", riskLevel: "critical", lang: "en" },
322
418
  { keyword: "jailbreak", riskLevel: "critical", lang: "en" },
323
419
  { keyword: "\u5F00\u53D1\u8005\u6A21\u5F0F", riskLevel: "critical", lang: "zh" },
324
- { keyword: "\u8D8A\u72F1", riskLevel: "critical", lang: "zh" },
325
- // ---- xiaoO-specific bypass ----
326
- { keyword: "bypass audit", riskLevel: "critical", lang: "en" },
327
- { keyword: "skip security check", riskLevel: "critical", lang: "en" },
328
- { keyword: "disable guard", riskLevel: "critical", lang: "en" },
329
- { keyword: "\u7ED5\u8FC7\u5BA1\u8BA1", riskLevel: "critical", lang: "zh" },
330
- { keyword: "\u7981\u7528\u5B89\u5168\u68C0\u67E5", riskLevel: "critical", lang: "zh" },
331
- { keyword: "\u5173\u95ED\u9632\u62A4", riskLevel: "critical", lang: "zh" }
420
+ { keyword: "\u8D8A\u72F1", riskLevel: "critical", lang: "zh" }
421
+ // 注:xiaoO 专属绕过词(绕过审计/禁用安全检查/关闭防护/bypass audit 等)
422
+ // 已迁至 agent_moss/agents/xiaoo/rules.json 的 extra_injection_keywords,
423
+ // 由 agent_ext 机制注入,不再污染通用层。
332
424
  ];
333
425
  DANGEROUS_PATTERNS = [
334
426
  // --- Critical: rm command graded detection (strict to loose, first match wins) ---
@@ -626,7 +718,8 @@ function runLogicCheck(input) {
626
718
  }
627
719
  }
628
720
  if (!isExcludedType) {
629
- const pathResult = checkSensitivePath(actionDetail, resolvedCwd);
721
+ const agentExt = getAgentExtensions(input.agentId || "");
722
+ const pathResult = checkSensitivePath(actionDetail, resolvedCwd, agentExt);
630
723
  if (pathResult) return pathResult;
631
724
  }
632
725
  if (actionType !== "file_write" && actionType !== "file_edit") {
@@ -850,7 +943,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
850
943
  }
851
944
  return null;
852
945
  }
853
- function checkSensitivePath(command, cwd) {
946
+ function checkSensitivePath(command, cwd, agentExt) {
854
947
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
855
948
  const _WRITE_CMD_RES = [/\brm\b/, /\bunlink\b/, /\brmdir\b/, /\bshred\b/, /\bcp\b/, /\bmv\b/, /\binstall\b/, /\btee\b/, /\bdd\b/, /\bmkfs\b/, /\b(fdisk|parted|cfdisk|sfdisk)\b/, /\bchmod\b/, /\bchown\b/, /\btruncate\b/];
856
949
  const _DEVNULL_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/;
@@ -872,7 +965,7 @@ function checkSensitivePath(command, cwd) {
872
965
  violatedLayers: ["2"]
873
966
  };
874
967
  }
875
- if (!isWriteOpFlag) {
968
+ if (!isWriteOpFlag && !sp.credential) {
876
969
  continue;
877
970
  }
878
971
  return {
@@ -889,7 +982,7 @@ function checkSensitivePath(command, cwd) {
889
982
  for (const sp of HOME_SENSITIVE_PATHS) {
890
983
  const expanded = `${expandedCwd}/${sp.path}`;
891
984
  if (command.includes(sp.path.toLowerCase()) || command.includes(expanded.toLowerCase())) {
892
- if (!isWriteOpFlag) {
985
+ if (!isWriteOpFlag && !sp.credential) {
893
986
  continue;
894
987
  }
895
988
  return {
@@ -903,6 +996,29 @@ function checkSensitivePath(command, cwd) {
903
996
  };
904
997
  }
905
998
  }
999
+ if (agentExt?.extra_sensitive_paths && agentExt.extra_sensitive_paths.length > 0) {
1000
+ for (const sp of agentExt.extra_sensitive_paths) {
1001
+ const spPath = sp.path.toLowerCase();
1002
+ const expanded = `${expandedCwd}/${spPath}`;
1003
+ if (command.includes(spPath) || command.includes(expanded.toLowerCase())) {
1004
+ const denyMode = sp.deny_mode || (sp.credential ? "deny_both" : sp.read_only ? "deny_read" : "deny_write");
1005
+ if (denyMode === "deny_read") {
1006
+ if (isWriteOpFlag) continue;
1007
+ } else if (denyMode === "deny_write") {
1008
+ if (!isWriteOpFlag) continue;
1009
+ }
1010
+ return {
1011
+ allowed: false,
1012
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE agent \u4E13\u5C5E\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1013
+ riskLevel: sp.risk_level,
1014
+ riskType: "sensitive_path_access",
1015
+ confidence: 90,
1016
+ source: "logic_rule",
1017
+ violatedLayers: ["2"]
1018
+ };
1019
+ }
1020
+ }
1021
+ }
906
1022
  for (const cp of CREDENTIAL_PATHS) {
907
1023
  const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
908
1024
  const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
@@ -1060,12 +1176,13 @@ var init_logic_rules = __esm({
1060
1176
  "use strict";
1061
1177
  init_esm_shims();
1062
1178
  init_inline_analyzer();
1179
+ init_agents();
1063
1180
  ROOT_SENSITIVE_PATHS = [
1064
- { path: "/etc/shadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6" },
1065
- { path: "/etc/gshadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6" },
1181
+ { path: "/etc/shadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6", credential: true },
1182
+ { path: "/etc/gshadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6", credential: true },
1066
1183
  // 注意:/etc/passwd 仅从 L1 启发式规则移除(公开可读),L2 敏感路径仍保留
1067
1184
  { path: "/etc/passwd", riskLevel: "high", desc: "\u7CFB\u7EDF\u7528\u6237\u6587\u4EF6" },
1068
- { path: "/etc/sudoers", riskLevel: "critical", desc: "sudo \u914D\u7F6E" },
1185
+ { path: "/etc/sudoers", riskLevel: "critical", desc: "sudo \u914D\u7F6E", credential: true },
1069
1186
  // 系统配置(仅拦截写入)
1070
1187
  { path: "/etc/hosts", riskLevel: "medium", desc: "DNS \u89E3\u6790\u914D\u7F6E" },
1071
1188
  { path: "/etc/crontab", riskLevel: "high", desc: "\u7CFB\u7EDF\u5B9A\u65F6\u4EFB\u52A1" },
@@ -1083,19 +1200,19 @@ var init_logic_rules = __esm({
1083
1200
  { path: "/dev/mem", riskLevel: "critical", desc: "\u7269\u7406\u5185\u5B58\u8BBF\u95EE\u8BBE\u5907" },
1084
1201
  { path: "/dev/kmsg", riskLevel: "high", desc: "\u5185\u6838\u6D88\u606F\u7F13\u51B2\u533A" },
1085
1202
  // SSH 主机私钥
1086
- { path: "/etc/ssh/ssh_host_rsa_key", riskLevel: "critical", desc: "SSH \u4E3B\u673A RSA \u79C1\u94A5" },
1087
- { path: "/etc/ssh/ssh_host_ed25519_key", riskLevel: "critical", desc: "SSH \u4E3B\u673A Ed25519 \u79C1\u94A5" },
1088
- { path: "/etc/ssh/ssh_host_ecdsa_key", riskLevel: "critical", desc: "SSH \u4E3B\u673A ECDSA \u79C1\u94A5" }
1203
+ { path: "/etc/ssh/ssh_host_rsa_key", riskLevel: "critical", desc: "SSH \u4E3B\u673A RSA \u79C1\u94A5", credential: true },
1204
+ { path: "/etc/ssh/ssh_host_ed25519_key", riskLevel: "critical", desc: "SSH \u4E3B\u673A Ed25519 \u79C1\u94A5", credential: true },
1205
+ { path: "/etc/ssh/ssh_host_ecdsa_key", riskLevel: "critical", desc: "SSH \u4E3B\u673A ECDSA \u79C1\u94A5", credential: true }
1089
1206
  ];
1090
1207
  HOME_SENSITIVE_PATHS = [
1091
- { path: ".ssh/id_rsa", riskLevel: "critical", desc: "SSH \u79C1\u94A5" },
1092
- { path: ".ssh/id_ed25519", riskLevel: "critical", desc: "SSH \u79C1\u94A5 (ed25519)" },
1208
+ { path: ".ssh/id_rsa", riskLevel: "critical", desc: "SSH \u79C1\u94A5", credential: true },
1209
+ { path: ".ssh/id_ed25519", riskLevel: "critical", desc: "SSH \u79C1\u94A5 (ed25519)", credential: true },
1093
1210
  { path: ".ssh/authorized_keys", riskLevel: "high", desc: "SSH \u6388\u6743\u5BC6\u94A5" },
1094
1211
  // 持久化与命令劫持(仅拦截写入)
1095
1212
  { path: ".bashrc", riskLevel: "high", desc: "\u7528\u6237 Shell \u521D\u59CB\u5316\u811A\u672C" },
1096
1213
  { path: ".bash_profile", riskLevel: "high", desc: "\u7528\u6237 Shell \u767B\u5F55\u811A\u672C" },
1097
1214
  { path: ".profile", riskLevel: "high", desc: "\u7528\u6237\u901A\u7528\u767B\u5F55\u811A\u672C" },
1098
- { path: ".aws/credentials", riskLevel: "critical", desc: "AWS \u51ED\u8BC1" },
1215
+ { path: ".aws/credentials", riskLevel: "critical", desc: "AWS \u51ED\u8BC1", credential: true },
1099
1216
  { path: ".gitconfig", riskLevel: "medium", desc: "Git \u914D\u7F6E" },
1100
1217
  { path: ".gnupg", riskLevel: "high", desc: "GPG \u5BC6\u94A5\u76EE\u5F55" }
1101
1218
  ];
@@ -1156,21 +1273,21 @@ var init_logic_rules = __esm({
1156
1273
  });
1157
1274
 
1158
1275
  // src/engine/skill-loader.ts
1159
- import { readFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
1160
- import { resolve, dirname } from "path";
1161
- import { fileURLToPath as fileURLToPath2 } from "url";
1276
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
1277
+ import { resolve as resolve2, dirname as dirname2 } from "path";
1278
+ import { fileURLToPath as fileURLToPath3 } from "url";
1162
1279
  function loadAllSkills() {
1163
1280
  if (cachedSkills) return cachedSkills;
1164
1281
  cachedSkills = {};
1165
- const dir = existsSync2(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
1166
- if (!existsSync2(dir)) {
1282
+ const dir = existsSync3(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
1283
+ if (!existsSync3(dir)) {
1167
1284
  console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR, "and fallback:", SKILLS_DIR_FALLBACK);
1168
1285
  return cachedSkills;
1169
1286
  }
1170
1287
  const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
1171
1288
  for (const f of files) {
1172
1289
  const name = f.replace(".md", "");
1173
- cachedSkills[name] = readFileSync(resolve(dir, f), "utf-8");
1290
+ cachedSkills[name] = readFileSync2(resolve2(dir, f), "utf-8");
1174
1291
  }
1175
1292
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1176
1293
  return cachedSkills;
@@ -1224,15 +1341,15 @@ function formatSkillsForPrompt(skills) {
1224
1341
  ${s.skillContent}`
1225
1342
  ).join("\n\n---\n\n");
1226
1343
  }
1227
- var __filename2, __dirname2, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
1344
+ var __filename3, __dirname3, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
1228
1345
  var init_skill_loader = __esm({
1229
1346
  "src/engine/skill-loader.ts"() {
1230
1347
  "use strict";
1231
1348
  init_esm_shims();
1232
- __filename2 = fileURLToPath2(import.meta.url);
1233
- __dirname2 = dirname(__filename2);
1234
- SKILLS_DIR = resolve(__dirname2, "../skills");
1235
- SKILLS_DIR_FALLBACK = resolve(__dirname2, "../../agent_moss/skills");
1349
+ __filename3 = fileURLToPath3(import.meta.url);
1350
+ __dirname3 = dirname2(__filename3);
1351
+ SKILLS_DIR = resolve2(__dirname3, "../skills");
1352
+ SKILLS_DIR_FALLBACK = resolve2(__dirname3, "../../../agent_moss/skills");
1236
1353
  SKILL_KEYWORD_MAP = {
1237
1354
  "file_access_guard": ["file", "\u6587\u4EF6", "path", "\u8DEF\u5F84", "read", "write", "cat", "open", "access", "\u8BBF\u95EE", "\u8BFB\u5199", "copy", "cp", "mv", "move"],
1238
1355
  "script_execution_guard": ["command", "shell", "bash", "exec", "execute", "\u6267\u884C", "\u547D\u4EE4", "script", "\u811A\u672C", "run", "\u8FD0\u884C", "subprocess", "system", "eval", "compile", "\u5B89\u88C5", "install", "pip", "npm"],
@@ -1255,44 +1372,44 @@ var init_skill_loader = __esm({
1255
1372
  });
1256
1373
 
1257
1374
  // src/engine/template-loader.ts
1258
- import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
1259
- import { resolve as resolve2, dirname as dirname2 } from "path";
1260
- import { fileURLToPath as fileURLToPath3 } from "url";
1375
+ import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
1376
+ import { resolve as resolve3, dirname as dirname3 } from "path";
1377
+ import { fileURLToPath as fileURLToPath4 } from "url";
1261
1378
  function loadTemplates() {
1262
1379
  if (cachedTemplates) return cachedTemplates;
1263
1380
  cachedTemplates = {};
1264
- const dir = existsSync3(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
1265
- if (!existsSync3(dir)) {
1381
+ const dir = existsSync4(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
1382
+ if (!existsSync4(dir)) {
1266
1383
  console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
1267
1384
  return cachedTemplates;
1268
1385
  }
1269
1386
  for (const file of TEMPLATE_FILES) {
1270
- const filePath = resolve2(dir, file);
1271
- if (existsSync3(filePath)) {
1387
+ const filePath = resolve3(dir, file);
1388
+ if (existsSync4(filePath)) {
1272
1389
  const key = file.replace(".txt", "").replace("_template", "");
1273
- cachedTemplates[key] = readFileSync2(filePath, "utf-8");
1390
+ cachedTemplates[key] = readFileSync3(filePath, "utf-8");
1274
1391
  }
1275
1392
  }
1276
1393
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
1277
1394
  return cachedTemplates;
1278
1395
  }
1279
- var __filename3, __dirname3, TEMPLATES_DIR, TEMPLATES_DIR_FALLBACK, TEMPLATE_FILES, cachedTemplates;
1396
+ var __filename4, __dirname4, TEMPLATES_DIR, TEMPLATES_DIR_FALLBACK, TEMPLATE_FILES, cachedTemplates;
1280
1397
  var init_template_loader = __esm({
1281
1398
  "src/engine/template-loader.ts"() {
1282
1399
  "use strict";
1283
1400
  init_esm_shims();
1284
- __filename3 = fileURLToPath3(import.meta.url);
1285
- __dirname3 = dirname2(__filename3);
1286
- TEMPLATES_DIR = resolve2(__dirname3, "../templates");
1287
- TEMPLATES_DIR_FALLBACK = resolve2(__dirname3, "../../agent_moss/templates");
1401
+ __filename4 = fileURLToPath4(import.meta.url);
1402
+ __dirname4 = dirname3(__filename4);
1403
+ TEMPLATES_DIR = resolve3(__dirname4, "../templates");
1404
+ TEMPLATES_DIR_FALLBACK = resolve3(__dirname4, "../../../agent_moss/templates");
1288
1405
  TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
1289
1406
  cachedTemplates = null;
1290
1407
  }
1291
1408
  });
1292
1409
 
1293
1410
  // src/engine/script-analyzer.ts
1294
- import { readFileSync as readFileSync3 } from "fs";
1295
- import { resolve as resolve3, join } from "path";
1411
+ import { readFileSync as readFileSync4 } from "fs";
1412
+ import { resolve as resolve4, join } from "path";
1296
1413
  function emptyResult() {
1297
1414
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
1298
1415
  }
@@ -1345,8 +1462,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
1345
1462
  }
1346
1463
  function readScriptContent(scriptPath, maxLines = 500) {
1347
1464
  try {
1348
- const expandedPath = scriptPath.startsWith("~") ? join(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve3(scriptPath);
1349
- const raw = readFileSync3(expandedPath, "utf-8");
1465
+ const expandedPath = scriptPath.startsWith("~") ? join(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve4(scriptPath);
1466
+ const raw = readFileSync4(expandedPath, "utf-8");
1350
1467
  const lines = raw.split("\n").slice(0, maxLines);
1351
1468
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
1352
1469
  } catch (e) {
@@ -1483,6 +1600,712 @@ var init_script_analyzer = __esm({
1483
1600
  }
1484
1601
  });
1485
1602
 
1603
+ // src/engine/script-content-analyzer.ts
1604
+ import { readFileSync as readFileSync5, statSync, existsSync as existsSync5 } from "fs";
1605
+ import { resolve as resolve5, join as join2, isAbsolute, dirname as dirname4 } from "path";
1606
+ import { homedir } from "os";
1607
+ function emptyChain() {
1608
+ return {
1609
+ nodes: [],
1610
+ entryPath: "",
1611
+ maxDepthReached: false,
1612
+ maxSizeReached: false,
1613
+ totalSize: 0,
1614
+ totalLines: 0,
1615
+ hasCritical: false,
1616
+ hasHigh: false,
1617
+ firstHitNode: null,
1618
+ error: ""
1619
+ };
1620
+ }
1621
+ function newNode(path3, depth) {
1622
+ return {
1623
+ scriptPath: path3,
1624
+ rawContent: "",
1625
+ preprocessedContent: "",
1626
+ lineCount: 0,
1627
+ fileSize: 0,
1628
+ depth,
1629
+ childPaths: [],
1630
+ error: "",
1631
+ l1Hit: false,
1632
+ l1RiskLevel: "",
1633
+ l1RiskType: "",
1634
+ l1Reason: "",
1635
+ l1MatchedPatterns: [],
1636
+ l2Hit: false,
1637
+ l2ViolatedRule: "",
1638
+ l2RiskLevel: "",
1639
+ l2RiskType: "",
1640
+ l2Reason: "",
1641
+ matchedKeywords: [],
1642
+ isSuspicious: false,
1643
+ keywordRiskLevel: "",
1644
+ keywordRiskReason: ""
1645
+ };
1646
+ }
1647
+ function stripComments(content) {
1648
+ return content.split("\n").filter((line) => !line.trimStart().startsWith("#")).join("\n");
1649
+ }
1650
+ function stripOutputText(content) {
1651
+ let result = content.replace(/echo\s+".*?"/gs, 'echo ""');
1652
+ result = result.replace(/echo\s+'.*?'/gs, "echo ''");
1653
+ result = result.replace(/printf\s+".*?"[^\n]*/gs, 'printf ""');
1654
+ result = result.replace(/printf\s+'.*?'[^\n]*/gs, "printf ''");
1655
+ return result;
1656
+ }
1657
+ function expandSimpleVariables(content) {
1658
+ const varMap = {};
1659
+ const assignmentRe = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.+)$/;
1660
+ const collectAssignments = (text) => {
1661
+ for (const line of text.split("\n")) {
1662
+ const m = line.match(assignmentRe);
1663
+ if (m) {
1664
+ const varName = m[1];
1665
+ let varVal = m[2].trim();
1666
+ if (varVal && !varVal.includes("$") && !varVal.includes("`")) {
1667
+ if (varVal.startsWith('"') && varVal.endsWith('"') || varVal.startsWith("'") && varVal.endsWith("'")) {
1668
+ varVal = varVal.slice(1, -1);
1669
+ }
1670
+ varMap[varName] = varVal;
1671
+ }
1672
+ }
1673
+ }
1674
+ };
1675
+ collectAssignments(content);
1676
+ let expanded = content;
1677
+ for (let round = 0; round < 3; round++) {
1678
+ const sortedVars = Object.keys(varMap).sort((a, b) => b.length - a.length);
1679
+ let newExpanded = expanded;
1680
+ for (const v of sortedVars) {
1681
+ const val = varMap[v];
1682
+ newExpanded = newExpanded.split("${" + v + "}").join(val);
1683
+ newExpanded = newExpanded.split("$" + v).join(val);
1684
+ }
1685
+ if (newExpanded === expanded) break;
1686
+ expanded = newExpanded;
1687
+ collectAssignments(expanded);
1688
+ }
1689
+ return expanded;
1690
+ }
1691
+ function preprocessScriptContent(content) {
1692
+ let result = stripComments(content);
1693
+ result = stripOutputText(result);
1694
+ result = expandSimpleVariables(result);
1695
+ return result;
1696
+ }
1697
+ function scanScriptForKeywords2(content) {
1698
+ const matched = [];
1699
+ for (const { name, pattern } of SUSPICIOUS_PATTERNS2) {
1700
+ if (pattern.test(content)) matched.push(name);
1701
+ }
1702
+ return matched;
1703
+ }
1704
+ function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
1705
+ if (matchedKeywords.length === 0) return { level: "", reason: "" };
1706
+ const keywordSet = new Set(matchedKeywords);
1707
+ for (const rule of KEYWORD_COMBINATION_RISKS2) {
1708
+ const hitGroup1 = [...rule.group1].some((k) => keywordSet.has(k));
1709
+ const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
1710
+ if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
1711
+ }
1712
+ const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
1713
+ const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
1714
+ if (hasNetworkPost) {
1715
+ const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
1716
+ const contentLower = scriptContent.toLowerCase();
1717
+ for (const p of envPatterns) {
1718
+ if (contentLower.includes(p.toLowerCase())) {
1719
+ return {
1720
+ level: "critical",
1721
+ reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF\u6536\u96C6\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u811A\u672C\u53EF\u80FD\u5C06\u7528\u6237\u73AF\u5883\u53D8\u91CF\uFF08\u53EF\u80FD\u5305\u542B API Key\u3001\u5BC6\u94A5\u7B49\u654F\u611F\u4FE1\u606F\uFF09\u53D1\u9001\u5230\u8FDC\u7A0B\u670D\u52A1\u5668\u3002"
1722
+ };
1723
+ }
1724
+ }
1725
+ }
1726
+ let bestLevel = "";
1727
+ let bestReason = "";
1728
+ for (const kw of matchedKeywords) {
1729
+ const risk = SINGLE_KEYWORD_RISKS2[kw];
1730
+ if (risk && (LEVEL_ORDER2[risk.level] || 0) > (LEVEL_ORDER2[bestLevel] || 0)) {
1731
+ bestLevel = risk.level;
1732
+ bestReason = risk.reason;
1733
+ }
1734
+ }
1735
+ return { level: bestLevel, reason: bestReason };
1736
+ }
1737
+ function extractScriptPath2(command) {
1738
+ for (const pattern of SCRIPT_EXEC_PATTERNS2) {
1739
+ const m = command.match(pattern);
1740
+ if (m) return m[1];
1741
+ }
1742
+ return null;
1743
+ }
1744
+ function extractInlineScriptPath(actionDetail) {
1745
+ const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1746
+ let m;
1747
+ while ((m = re.exec(actionDetail)) !== null) {
1748
+ const p = m[1];
1749
+ let resolved;
1750
+ try {
1751
+ resolved = resolvePath(p);
1752
+ if (existsSync5(resolved)) {
1753
+ const stat = statSync(resolved);
1754
+ if (stat.isFile()) return resolved;
1755
+ }
1756
+ } catch {
1757
+ }
1758
+ }
1759
+ return null;
1760
+ }
1761
+ function resolvePath(p) {
1762
+ let expanded = p;
1763
+ if (expanded.startsWith("~/") || expanded === "~") {
1764
+ expanded = join2(homedir(), expanded.slice(1));
1765
+ }
1766
+ return resolve5(expanded);
1767
+ }
1768
+ function readScriptContent2(scriptPath, maxFileSize = 2 * 1024 * 1024) {
1769
+ try {
1770
+ const path3 = resolvePath(scriptPath);
1771
+ if (!existsSync5(path3)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${scriptPath}` };
1772
+ const stat = statSync(path3);
1773
+ if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${scriptPath}` };
1774
+ if (stat.size > maxFileSize) {
1775
+ return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
1776
+ }
1777
+ const content = readFileSync5(path3, "utf-8");
1778
+ const lineCount = content ? content.split("\n").length : 0;
1779
+ return { content, lineCount, fileSize: stat.size, error: "" };
1780
+ } catch (e) {
1781
+ const msg = e instanceof Error ? e.message : String(e);
1782
+ return { content: "", lineCount: 0, fileSize: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
1783
+ }
1784
+ }
1785
+ function extractChildScriptPaths(content, baseDir) {
1786
+ const paths = [];
1787
+ for (const pattern of CHILD_SCRIPT_PATTERNS) {
1788
+ pattern.lastIndex = 0;
1789
+ let m;
1790
+ while ((m = pattern.exec(content)) !== null) {
1791
+ const pathStr = m[1];
1792
+ if (pathStr.startsWith("-") || pathStr.startsWith("$")) continue;
1793
+ if (pathStr.includes("/dev/") || pathStr.startsWith("|") || pathStr.startsWith(";")) continue;
1794
+ const full = isAbsolute(pathStr) ? pathStr : join2(baseDir, pathStr);
1795
+ const resolved = resolvePath(full);
1796
+ if (existsSync5(resolved)) {
1797
+ try {
1798
+ if (statSync(resolved).isFile() && !paths.includes(resolved)) paths.push(resolved);
1799
+ } catch {
1800
+ }
1801
+ }
1802
+ }
1803
+ }
1804
+ return paths;
1805
+ }
1806
+ function resolveScriptChain(entryPath, input, maxDepth = 3, maxFileSize = 2 * 1024 * 1024, maxTotalSize = 6 * 1024 * 1024) {
1807
+ const visited = /* @__PURE__ */ new Set();
1808
+ const result = emptyChain();
1809
+ result.entryPath = entryPath;
1810
+ const entry = resolvePath(entryPath);
1811
+ const baseDir = existsSync5(entry) ? dirname4(entry) : input.cwd || process.cwd();
1812
+ resolveRecursive({
1813
+ path: entry,
1814
+ depth: 0,
1815
+ maxDepth,
1816
+ maxFileSize,
1817
+ maxTotalSize,
1818
+ visited,
1819
+ result,
1820
+ baseDir,
1821
+ input
1822
+ });
1823
+ result.hasCritical = result.nodes.some((n) => n.l1RiskLevel === "critical" || n.l2RiskLevel === "critical" || n.keywordRiskLevel === "critical");
1824
+ result.hasHigh = result.nodes.some((n) => ["high", "critical"].includes(n.l1RiskLevel) || ["high", "critical"].includes(n.l2RiskLevel) || ["high", "critical"].includes(n.keywordRiskLevel));
1825
+ result.firstHitNode = result.nodes.find((n) => n.l1Hit || n.l2Hit || n.isSuspicious) || null;
1826
+ return result;
1827
+ }
1828
+ function resolveRecursive(ctx) {
1829
+ const { path: path3, depth, maxDepth, maxFileSize, maxTotalSize, visited, result, baseDir, input } = ctx;
1830
+ if (visited.has(path3)) return;
1831
+ if (depth > maxDepth) {
1832
+ result.maxDepthReached = true;
1833
+ return;
1834
+ }
1835
+ if (result.totalSize >= maxTotalSize) {
1836
+ result.maxSizeReached = true;
1837
+ return;
1838
+ }
1839
+ visited.add(path3);
1840
+ const { content, lineCount, fileSize, error } = readScriptContent2(path3, maxFileSize);
1841
+ if (error) {
1842
+ const node2 = newNode(path3, depth);
1843
+ node2.error = error;
1844
+ node2.fileSize = fileSize;
1845
+ node2.lineCount = lineCount;
1846
+ result.nodes.push(node2);
1847
+ return;
1848
+ }
1849
+ result.totalSize += fileSize;
1850
+ result.totalLines += lineCount;
1851
+ const preprocessed = preprocessScriptContent(content);
1852
+ const node = newNode(path3, depth);
1853
+ node.rawContent = content;
1854
+ node.preprocessedContent = preprocessed;
1855
+ node.lineCount = lineCount;
1856
+ node.fileSize = fileSize;
1857
+ result.nodes.push(node);
1858
+ const virtualInput = {
1859
+ ...input,
1860
+ aNext: { actionType: "bash", actionDetail: preprocessed }
1861
+ };
1862
+ const l1 = runHeuristicCheck(virtualInput);
1863
+ if (l1 && !l1.allowed) {
1864
+ node.l1Hit = true;
1865
+ node.l1RiskLevel = l1.riskLevel;
1866
+ node.l1RiskType = l1.riskType;
1867
+ node.l1Reason = l1.reason;
1868
+ node.l1MatchedPatterns = [];
1869
+ if (l1.riskLevel === "critical") {
1870
+ result.hasCritical = true;
1871
+ result.firstHitNode = node;
1872
+ return;
1873
+ }
1874
+ }
1875
+ const childPaths = extractChildScriptPaths(preprocessed, baseDir);
1876
+ node.childPaths = childPaths;
1877
+ for (const childPath of childPaths) {
1878
+ resolveRecursive({
1879
+ path: resolvePath(childPath),
1880
+ depth: depth + 1,
1881
+ maxDepth,
1882
+ maxFileSize,
1883
+ maxTotalSize,
1884
+ visited,
1885
+ result,
1886
+ baseDir: dirname4(resolvePath(childPath)),
1887
+ input
1888
+ });
1889
+ if (result.hasCritical) return;
1890
+ }
1891
+ }
1892
+ function scanScriptChainWithL1(chain, input) {
1893
+ for (const node of chain.nodes) {
1894
+ if (node.error || !node.preprocessedContent || node.l1Hit) continue;
1895
+ const virtualInput = {
1896
+ ...input,
1897
+ aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
1898
+ };
1899
+ const l1 = runHeuristicCheck(virtualInput);
1900
+ if (l1 && !l1.allowed) {
1901
+ node.l1Hit = true;
1902
+ node.l1RiskLevel = l1.riskLevel;
1903
+ node.l1RiskType = l1.riskType;
1904
+ node.l1Reason = l1.reason;
1905
+ if (l1.riskLevel === "critical") {
1906
+ chain.hasCritical = true;
1907
+ chain.firstHitNode = node;
1908
+ break;
1909
+ }
1910
+ }
1911
+ }
1912
+ return chain;
1913
+ }
1914
+ function scanScriptChainWithL2(chain, input) {
1915
+ for (const node of chain.nodes) {
1916
+ if (node.error || !node.preprocessedContent) continue;
1917
+ if (node.l1RiskLevel === "critical") break;
1918
+ const virtualInput = {
1919
+ ...input,
1920
+ aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
1921
+ };
1922
+ const l2 = runLogicCheck(virtualInput);
1923
+ if (l2 && !l2.allowed) {
1924
+ node.l2Hit = true;
1925
+ node.l2ViolatedRule = `script_logic:${node.scriptPath}`;
1926
+ node.l2RiskLevel = l2.riskLevel;
1927
+ node.l2RiskType = l2.riskType;
1928
+ node.l2Reason = `\u811A\u672C ${node.scriptPath} (\u6DF1\u5EA6 ${node.depth}) \u4E2D: ${l2.reason}`;
1929
+ if (l2.riskLevel === "critical") {
1930
+ chain.hasCritical = true;
1931
+ chain.firstHitNode = node;
1932
+ break;
1933
+ }
1934
+ }
1935
+ }
1936
+ if (!chain.hasHigh) {
1937
+ chain.hasHigh = chain.nodes.some((n) => ["high", "critical"].includes(n.l2RiskLevel));
1938
+ }
1939
+ return chain;
1940
+ }
1941
+ function scanScriptChainKeywords(chain) {
1942
+ for (const node of chain.nodes) {
1943
+ if (node.error || !node.preprocessedContent) continue;
1944
+ if (node.l1Hit || node.l2Hit) continue;
1945
+ const matched = scanScriptForKeywords2(node.preprocessedContent);
1946
+ node.matchedKeywords = matched;
1947
+ node.isSuspicious = matched.length > 0;
1948
+ if (matched.length > 0) {
1949
+ const risk = assessKeywordCombinationRisk2(matched, node.preprocessedContent);
1950
+ node.keywordRiskLevel = risk.level;
1951
+ node.keywordRiskReason = risk.reason;
1952
+ }
1953
+ }
1954
+ return chain;
1955
+ }
1956
+ function analyzeScriptContent2(input) {
1957
+ const actionType = input.aNext.actionType.toLowerCase();
1958
+ if (actionType !== "bash") return emptyChain();
1959
+ const scriptPath = extractScriptPath2(input.aNext.actionDetail);
1960
+ let resolvedPath = scriptPath;
1961
+ if (!resolvedPath) {
1962
+ const inline = extractInlineScriptPath(input.aNext.actionDetail);
1963
+ if (!inline) return emptyChain();
1964
+ resolvedPath = inline;
1965
+ }
1966
+ const chain = resolveScriptChain(resolvedPath, input);
1967
+ if (chain.nodes.length === 0) return chain;
1968
+ scanScriptChainWithL1(chain, input);
1969
+ scanScriptChainWithL2(chain, input);
1970
+ scanScriptChainKeywords(chain);
1971
+ return chain;
1972
+ }
1973
+ function formatScriptChainForPrompt(chain) {
1974
+ if (chain.nodes.length === 0) return "";
1975
+ const errorNodes = chain.nodes.filter((n) => n.error);
1976
+ if (errorNodes.length > 0 && chain.nodes.length === 1) {
1977
+ return `\u26A0\uFE0F \u811A\u672C\u5185\u5BB9\u5206\u6790\u5931\u8D25: ${errorNodes[0].error}`;
1978
+ }
1979
+ const suspiciousNodes = chain.nodes.filter((n) => n.isSuspicious && !n.l1Hit && !n.l2Hit);
1980
+ if (suspiciousNodes.length === 0) {
1981
+ const safeNodes = chain.nodes.filter((n) => !n.error);
1982
+ if (safeNodes.length === 0) return "";
1983
+ const parts2 = ["\u2713 \u811A\u672C\u5185\u5BB9\u5DF2\u5206\u6790\uFF0C\u672A\u53D1\u73B0\u5371\u9669/\u53EF\u7591\u64CD\u4F5C\uFF08L1/L2/\u5173\u952E\u8BCD\u626B\u63CF\u5747\u672A\u547D\u4E2D\uFF09\uFF1A"];
1984
+ for (const node of safeNodes) {
1985
+ const pre = node.preprocessedContent || "";
1986
+ let preview = pre.slice(0, 600);
1987
+ if (pre.length > 600) preview += `
1988
+ ... (\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9 ${pre.length} \u5B57\u7B26)`;
1989
+ parts2.push(
1990
+ ` \u811A\u672C\u8DEF\u5F84: ${node.scriptPath}\uFF08\u9012\u5F52\u6DF1\u5EA6 ${node.depth}\uFF0C${node.lineCount} \u884C\uFF09
1991
+ \u5185\u5BB9\uFF08\u9884\u5904\u7406\u540E\uFF09:
1992
+ \`\`\`
1993
+ ${preview}
1994
+ \`\`\``
1995
+ );
1996
+ }
1997
+ return parts2.join("\n");
1998
+ }
1999
+ const parts = [];
2000
+ for (const node of suspiciousNodes) {
2001
+ const keywordsStr = node.matchedKeywords.join(", ");
2002
+ let riskHint = "";
2003
+ if (node.keywordRiskLevel && node.keywordRiskReason) {
2004
+ riskHint = `
2005
+
2006
+ \u{1F534} **\u98CE\u9669\u8BC4\u4F30**: ${node.keywordRiskLevel.toUpperCase()} \u7EA7\u522B
2007
+ **\u98CE\u9669\u539F\u56E0**: ${node.keywordRiskReason}
2008
+ **\u5EFA\u8BAE**: \u6B64\u811A\u672C\u5E94\u88AB\u62D2\u7EDD\uFF08Deny\uFF09\uFF0C\u9664\u975E\u6709\u660E\u786E\u7684\u4E1A\u52A1\u9700\u6C42\u4E14\u5DF2\u786E\u8BA4\u76EE\u6807\u670D\u52A1\u5668\u53EF\u4FE1\u3002`;
2009
+ }
2010
+ const pre = node.preprocessedContent;
2011
+ let contentPreview = pre.slice(0, 2e3);
2012
+ if (pre.length > 2e3) contentPreview += `
2013
+ ... (\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9 ${pre.length} \u5B57\u7B26)`;
2014
+ parts.push(
2015
+ `\u26A0\uFE0F \u811A\u672C\u5185\u5BB9\u5305\u542B\u53EF\u7591\u5173\u952E\u8BCD:
2016
+ \u811A\u672C\u8DEF\u5F84: ${node.scriptPath}
2017
+ \u9012\u5F52\u6DF1\u5EA6: ${node.depth}
2018
+ \u53EF\u7591\u5173\u952E\u8BCD: ${keywordsStr}
2019
+ \u811A\u672C\u884C\u6570: ${node.lineCount}${riskHint}
2020
+
2021
+ ### \u811A\u672C\u5185\u5BB9\uFF08\u9884\u5904\u7406\u540E\uFF09###
2022
+ \`\`\`
2023
+ ${contentPreview}
2024
+ \`\`\`
2025
+ `
2026
+ );
2027
+ }
2028
+ let header = `\u26A0\uFE0F \u68C0\u6D4B\u5230\u811A\u672C\u6267\u884C\u94FE\uFF08\u5171 ${chain.nodes.length} \u4E2A\u811A\u672C\uFF09\uFF0C\u5176\u4E2D ${suspiciousNodes.length} \u4E2A\u5305\u542B\u53EF\u7591\u5173\u952E\u8BCD:
2029
+ `;
2030
+ if (chain.maxDepthReached) header += "\u26A0\uFE0F \u6CE8\u610F\uFF1A\u9012\u5F52\u8FFD\u8E2A\u89E6\u8FBE\u6DF1\u5EA6\u4E0A\u9650\uFF0C\u53EF\u80FD\u5B58\u5728\u66F4\u6DF1\u5C42\u7684\u811A\u672C\u8C03\u7528\u672A\u88AB\u626B\u63CF\u3002\n";
2031
+ if (chain.maxSizeReached) header += "\u26A0\uFE0F \u6CE8\u610F\uFF1A\u7D2F\u8BA1\u6587\u4EF6\u5927\u5C0F\u89E6\u8FBE\u4E0A\u9650\uFF0C\u90E8\u5206\u811A\u672C\u53EF\u80FD\u672A\u88AB\u5B8C\u6574\u8BFB\u53D6\u3002\n";
2032
+ return header + parts.join("\n---\n\n");
2033
+ }
2034
+ var SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2, CHILD_SCRIPT_PATTERNS;
2035
+ var init_script_content_analyzer = __esm({
2036
+ "src/engine/script-content-analyzer.ts"() {
2037
+ "use strict";
2038
+ init_esm_shims();
2039
+ init_heuristic();
2040
+ init_logic_rules();
2041
+ SUSPICIOUS_PATTERNS2 = [
2042
+ { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
2043
+ { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
2044
+ { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
2045
+ { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
2046
+ { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
2047
+ { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
2048
+ { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
2049
+ { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
2050
+ { name: "/dev/udp", pattern: /\/dev\/udp\// },
2051
+ { name: "$(base64", pattern: /\$\(\s*base64/ },
2052
+ { name: "`base64", pattern: /`\s*base64/ },
2053
+ { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
2054
+ { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
2055
+ { name: "id_rsa", pattern: /\bid_rsa\b/ },
2056
+ { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
2057
+ { name: "crontab install", pattern: /crontab\s+-/ },
2058
+ { name: "authorized_keys", pattern: /authorized_keys/ },
2059
+ { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
2060
+ ];
2061
+ SCRIPT_EXEC_PATTERNS2 = [
2062
+ /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
2063
+ /(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
2064
+ /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
2065
+ /(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
2066
+ /\.\/(\S+)/,
2067
+ /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
2068
+ /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
2069
+ /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
2070
+ /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
2071
+ /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
2072
+ /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
2073
+ /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
2074
+ /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
2075
+ ];
2076
+ KEYWORD_COMBINATION_RISKS2 = [
2077
+ {
2078
+ group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
2079
+ group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
2080
+ level: "critical",
2081
+ reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + Base64\u7F16\u7801\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u653B\u51FB\u8005\u53EF\u80FD\u5C06\u654F\u611F\u6570\u636E\u7F16\u7801\u540E\u5916\u4F20\u3002"
2082
+ },
2083
+ {
2084
+ group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
2085
+ group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
2086
+ level: "critical",
2087
+ reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u654F\u611F\u6587\u4EF6\u8BBF\u95EE\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u51ED\u8BC1\u5916\u4F20\u98CE\u9669\u3002"
2088
+ },
2089
+ {
2090
+ group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
2091
+ group2: /* @__PURE__ */ new Set(),
2092
+ level: "high",
2093
+ reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
2094
+ },
2095
+ {
2096
+ group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
2097
+ group2: /* @__PURE__ */ new Set(),
2098
+ level: "high",
2099
+ reason: "\u68C0\u6D4B\u5230\u6301\u4E45\u5316\u540E\u95E8\u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5728\u7CFB\u7EDF\u4E2D\u690D\u5165\u540E\u95E8\u3002"
2100
+ }
2101
+ ];
2102
+ SINGLE_KEYWORD_RISKS2 = {
2103
+ "curl POST": { level: "high", reason: "\u68C0\u6D4B\u5230 curl POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
2104
+ "curl --data": { level: "high", reason: "\u68C0\u6D4B\u5230 curl --data \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
2105
+ "curl -d": { level: "high", reason: "\u68C0\u6D4B\u5230 curl -d \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
2106
+ "wget --post-data": { level: "high", reason: "\u68C0\u6D4B\u5230 wget POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
2107
+ "wget --post-file": { level: "high", reason: "\u68C0\u6D4B\u5230 wget \u6587\u4EF6\u4E0A\u4F20\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6587\u4EF6\u3002" },
2108
+ "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
2109
+ "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
2110
+ "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
2111
+ "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
2112
+ "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
2113
+ "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
2114
+ "base64 decode pipe": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u89E3\u7801\u540E\u7BA1\u9053\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
2115
+ "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
2116
+ "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
2117
+ "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
2118
+ "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
2119
+ "authorized_keys": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539 SSH \u6388\u6743\u5BC6\u94A5\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
2120
+ "ssh no host key check": { level: "high", reason: "\u68C0\u6D4B\u5230 SSH \u8DF3\u8FC7\u4E3B\u673A\u5BC6\u94A5\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u662F\u6A2A\u5411\u79FB\u52A8\u3002" }
2121
+ };
2122
+ LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
2123
+ CHILD_SCRIPT_PATTERNS = [
2124
+ /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
2125
+ /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
2126
+ /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
2127
+ /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
2128
+ /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
2129
+ /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
2130
+ /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
2131
+ /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
2132
+ /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
2133
+ /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
2134
+ ];
2135
+ }
2136
+ });
2137
+
2138
+ // src/engine/token-stats.ts
2139
+ var token_stats_exports = {};
2140
+ __export(token_stats_exports, {
2141
+ getRecentRecords: () => getRecentRecords,
2142
+ getTokenStats: () => getTokenStats,
2143
+ getTokenTrend: () => getTokenTrend,
2144
+ recordTokenUsage: () => recordTokenUsage,
2145
+ resetTokenStats: () => resetTokenStats
2146
+ });
2147
+ import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync6, mkdirSync, unlinkSync } from "fs";
2148
+ import { join as join3 } from "path";
2149
+ import { homedir as homedir2 } from "os";
2150
+ function getStatsPath() {
2151
+ const envPath = process.env[ENV_STATS_PATH];
2152
+ return envPath || STATS_FILE;
2153
+ }
2154
+ function loadAllRecords(statsPath) {
2155
+ if (!existsSync6(statsPath)) return [];
2156
+ try {
2157
+ const data = JSON.parse(readFileSync6(statsPath, "utf-8"));
2158
+ if (Array.isArray(data)) return data;
2159
+ if (data && Array.isArray(data.records)) return data.records;
2160
+ return [];
2161
+ } catch (e) {
2162
+ return [];
2163
+ }
2164
+ }
2165
+ function saveRecords(statsPath, records) {
2166
+ try {
2167
+ const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
2168
+ if (dir && !existsSync6(dir)) mkdirSync(dir, { recursive: true });
2169
+ writeFileSync(statsPath, JSON.stringify(records, null, 2), "utf-8");
2170
+ } catch (e) {
2171
+ }
2172
+ }
2173
+ function recordTokenUsage(record) {
2174
+ const statsPath = getStatsPath();
2175
+ const records = loadAllRecords(statsPath);
2176
+ records.push({
2177
+ timestamp: record.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
2178
+ session_id: record.sessionId,
2179
+ step: record.step,
2180
+ model: record.model,
2181
+ prompt_tokens: record.promptTokens,
2182
+ completion_tokens: record.completionTokens,
2183
+ total_tokens: record.totalTokens
2184
+ });
2185
+ if (records.length > MAX_RECORDS) {
2186
+ records.splice(0, records.length - MAX_RECORDS);
2187
+ }
2188
+ saveRecords(statsPath, records);
2189
+ }
2190
+ function filterByDate(records, days = 0, startDate, endDate) {
2191
+ if (startDate || endDate) {
2192
+ let startDt = startDate ? new Date(startDate) : /* @__PURE__ */ new Date(-864e13);
2193
+ let endDt = endDate ? new Date(endDate) : void 0;
2194
+ if (endDate) {
2195
+ const e = new Date(endDate);
2196
+ e.setHours(23, 59, 59, 999);
2197
+ endDt = e;
2198
+ }
2199
+ const endMs = endDt ? endDt.getTime() : Number.MAX_SAFE_INTEGER;
2200
+ return records.filter((r) => {
2201
+ try {
2202
+ const ts = new Date(r.timestamp).getTime();
2203
+ return ts >= startDt.getTime() && ts <= endMs;
2204
+ } catch {
2205
+ return true;
2206
+ }
2207
+ });
2208
+ } else if (days > 0) {
2209
+ const cutoff = Date.now() - days * 864e5;
2210
+ return records.filter((r) => {
2211
+ try {
2212
+ return new Date(r.timestamp).getTime() >= cutoff;
2213
+ } catch {
2214
+ return true;
2215
+ }
2216
+ });
2217
+ }
2218
+ return records;
2219
+ }
2220
+ function aggregate(records) {
2221
+ let totalPrompt = 0, totalCompletion = 0, totalTotal = 0;
2222
+ const byStep = {};
2223
+ const byModel = {};
2224
+ const byDate = {};
2225
+ const bucket = (b, key, r) => {
2226
+ const e = b[key] || (b[key] = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, calls: 0 });
2227
+ e.prompt_tokens += r.prompt_tokens || 0;
2228
+ e.completion_tokens += r.completion_tokens || 0;
2229
+ e.total_tokens += r.total_tokens || 0;
2230
+ e.calls += 1;
2231
+ };
2232
+ for (const r of records) {
2233
+ totalPrompt += r.prompt_tokens || 0;
2234
+ totalCompletion += r.completion_tokens || 0;
2235
+ totalTotal += r.total_tokens || 0;
2236
+ bucket(byStep, r.step || "unknown", r);
2237
+ bucket(byModel, r.model || "unknown", r);
2238
+ let dateKey = "unknown";
2239
+ try {
2240
+ dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
2241
+ } catch {
2242
+ }
2243
+ bucket(byDate, dateKey, r);
2244
+ }
2245
+ const sortedDate = Object.keys(byDate).sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
2246
+ const sortedByDate = {};
2247
+ for (const k of sortedDate) sortedByDate[k] = byDate[k];
2248
+ return { totalPrompt, totalCompletion, totalTotal, byStep, byModel, byDate: sortedByDate };
2249
+ }
2250
+ function getTokenStats(days = 0, startDate, endDate) {
2251
+ const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
2252
+ const a = aggregate(records);
2253
+ return {
2254
+ total_prompt_tokens: a.totalPrompt,
2255
+ total_completion_tokens: a.totalCompletion,
2256
+ total_tokens: a.totalTotal,
2257
+ total_calls: records.length,
2258
+ by_step: a.byStep,
2259
+ by_model: a.byModel,
2260
+ by_date: a.byDate
2261
+ };
2262
+ }
2263
+ function getRecentRecords(limit = 20) {
2264
+ const records = loadAllRecords(getStatsPath());
2265
+ return records.slice(-limit);
2266
+ }
2267
+ function getTokenTrend(days = 0, startDate, endDate) {
2268
+ const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
2269
+ const series = {};
2270
+ const datesSet = /* @__PURE__ */ new Set();
2271
+ for (const r of records) {
2272
+ let dateKey;
2273
+ try {
2274
+ dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
2275
+ } catch {
2276
+ continue;
2277
+ }
2278
+ const model = r.model || "unknown";
2279
+ const tokens = r.total_tokens || 0;
2280
+ datesSet.add(dateKey);
2281
+ series[model] = series[model] || {};
2282
+ series[model][dateKey] = (series[model][dateKey] || 0) + tokens;
2283
+ }
2284
+ const dates = Array.from(datesSet).sort();
2285
+ const models = Object.keys(series).sort();
2286
+ return { dates, models, series };
2287
+ }
2288
+ function resetTokenStats() {
2289
+ const statsPath = getStatsPath();
2290
+ if (existsSync6(statsPath)) {
2291
+ try {
2292
+ unlinkSync(statsPath);
2293
+ } catch {
2294
+ }
2295
+ }
2296
+ }
2297
+ var STATS_DIR, STATS_FILE, ENV_STATS_PATH, MAX_RECORDS;
2298
+ var init_token_stats = __esm({
2299
+ "src/engine/token-stats.ts"() {
2300
+ "use strict";
2301
+ init_esm_shims();
2302
+ STATS_DIR = join3(homedir2(), ".config", "agentmoss");
2303
+ STATS_FILE = join3(STATS_DIR, "agent_moss_token_stats.json");
2304
+ ENV_STATS_PATH = "AGENT_MOSS_TOKEN_STATS_PATH";
2305
+ MAX_RECORDS = 1e4;
2306
+ }
2307
+ });
2308
+
1486
2309
  // src/engine/llm-analyzer.ts
1487
2310
  import OpenAI from "openai";
1488
2311
  async function runLLMAnalysis(input, previous) {
@@ -1509,8 +2332,14 @@ async function runLLMAnalysis(input, previous) {
1509
2332
  }
1510
2333
  const matchedSkills = matchSkills(input.aNext.actionType, input.aNext.actionDetail, input.reason);
1511
2334
  const templates = loadTemplates();
1512
- const scriptAnalysis = analyzeScriptContent(input.aNext.actionType, input.aNext.actionDetail);
1513
- const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis);
2335
+ let scriptAnalysis;
2336
+ let scriptChainText = "";
2337
+ if (previous.scriptChain) {
2338
+ scriptChainText = formatScriptChainForPrompt(previous.scriptChain);
2339
+ } else {
2340
+ scriptAnalysis = analyzeScriptContent(input.aNext.actionType, input.aNext.actionDetail);
2341
+ }
2342
+ const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, previous.skipL3Hints ?? "", scriptChainText);
1514
2343
  const result = await analyzeWithLLM(prompt, config);
1515
2344
  if (llmCache.size >= LLM_CACHE_MAX) {
1516
2345
  const firstKey = llmCache.keys().next().value;
@@ -1519,14 +2348,14 @@ async function runLLMAnalysis(input, previous) {
1519
2348
  llmCache.set(cacheKey, result);
1520
2349
  return result;
1521
2350
  }
1522
- function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis) {
2351
+ function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, skipL3Hints = "", scriptChainText = "") {
1523
2352
  const skillsText = formatSkillsForPrompt(matchedSkills);
1524
2353
  const historyStr = input.actionHistory.map((h, i) => `${i + 1}. ${h.actionType}: ${h.actionDetail}`).join("\n") || "(\u65E0\u5386\u53F2\u64CD\u4F5C)";
1525
- const scriptAnalysisText = scriptAnalysis ? formatScriptAnalysisForPrompt(scriptAnalysis) : "";
2354
+ const scriptAnalysisText = scriptChainText ? scriptChainText : scriptAnalysis ? formatScriptAnalysisForPrompt(scriptAnalysis) : "";
1526
2355
  const hintsText = formatHints(previous);
1527
2356
  const securityJudgeTemplate = templates["security_judge"];
1528
2357
  if (securityJudgeTemplate) {
1529
- return securityJudgeTemplate.replaceAll("{prompt_session}", input.promptSession || "\uFF08\u672A\u63D0\u4F9B\uFF09").replaceAll("{action_history}", historyStr).replaceAll("{action_type}", input.aNext.actionType).replaceAll("{action_detail}", input.aNext.actionDetail).replaceAll("{reason}", input.reason || "\u672A\u8BF4\u660E").replaceAll("{skills_text}", skillsText).replaceAll("{hints_text}", hintsText).replaceAll("{script_analysis_text}", scriptAnalysisText).replaceAll("{{", "{").replaceAll("}}", "}");
2358
+ return securityJudgeTemplate.replaceAll("{prompt_session}", input.promptSession || "\uFF08\u672A\u63D0\u4F9B\uFF09").replaceAll("{action_history}", historyStr).replaceAll("{action_type}", input.aNext.actionType).replaceAll("{action_detail}", input.aNext.actionDetail).replaceAll("{reason}", input.reason || "\u672A\u8BF4\u660E").replaceAll("{skills_text}", skillsText).replaceAll("{hints_text}", hintsText).replaceAll("{script_analysis_text}", scriptAnalysisText).replaceAll("{skip_l3_hints}", skipL3Hints).replaceAll("{{", "{").replaceAll("}}", "}");
1530
2359
  }
1531
2360
  console.warn("[AgentMoss] security_judge template missing, using sparse fallback prompt");
1532
2361
  return `## \u5F53\u524D\u64CD\u4F5C\u4E0A\u4E0B\u6587
@@ -1619,6 +2448,19 @@ async function analyzeWithLLM(prompt, config) {
1619
2448
  jsonStr = jsonStr.replace(/^```[\w]*\n?/, "").replace(/\n?```$/, "");
1620
2449
  }
1621
2450
  const parsed = JSON.parse(jsonStr);
2451
+ try {
2452
+ const usage = response.usage;
2453
+ const { recordTokenUsage: recordTokenUsage2 } = await Promise.resolve().then(() => (init_token_stats(), token_stats_exports));
2454
+ recordTokenUsage2({
2455
+ sessionId: "audit",
2456
+ step: "L3_security_judge",
2457
+ model: response.model || config.model,
2458
+ promptTokens: usage?.prompt_tokens || 0,
2459
+ completionTokens: usage?.completion_tokens || 0,
2460
+ totalTokens: usage?.total_tokens || 0
2461
+ });
2462
+ } catch {
2463
+ }
1622
2464
  clearTimeout(totalTimeoutTimer);
1623
2465
  return {
1624
2466
  allowed: parsed.allowed !== false,
@@ -1636,32 +2478,16 @@ async function analyzeWithLLM(prompt, config) {
1636
2478
  }
1637
2479
  if (attempt < maxRetries) {
1638
2480
  console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
1639
- await new Promise((resolve4) => setTimeout(resolve4, retryCfg.retryInterval * 1e3));
2481
+ await new Promise((resolve6) => setTimeout(resolve6, retryCfg.retryInterval * 1e3));
1640
2482
  } else {
1641
2483
  clearTimeout(totalTimeoutTimer);
1642
2484
  console.error("[AgentMoss] LLM call failed after all retries:", err.message);
1643
- return {
1644
- allowed: false,
1645
- reason: `LLM \u5206\u6790\u5931\u8D25\uFF0C\u56E0\u65E0\u6CD5\u5B8C\u6210\u6DF1\u5EA6\u8BED\u4E49\u5206\u6790\uFF0C\u6309 fail-closed \u539F\u5219\u62D2\u7EDD (${maxRetries + 1} \u6B21\u5C1D\u8BD5): ${err.message}`,
1646
- riskLevel: "medium",
1647
- riskType: "",
1648
- confidence: 60,
1649
- source: "llm_failure",
1650
- violatedLayers: ["3"]
1651
- };
2485
+ throw new Error(`LLM \u5206\u6790\u5931\u8D25 (${maxRetries + 1} \u6B21\u5C1D\u8BD5): ${err.message}`);
1652
2486
  }
1653
2487
  }
1654
2488
  }
1655
2489
  clearTimeout(totalTimeoutTimer);
1656
- return {
1657
- allowed: false,
1658
- reason: `LLM \u5206\u6790\u8D85\u65F6\uFF08totalTimeout ${timeoutCfg.totalTimeout}s\uFF09\uFF0C\u6309 fail-closed \u539F\u5219\u62D2\u7EDD`,
1659
- riskLevel: "medium",
1660
- riskType: "analysis_timeout",
1661
- confidence: 60,
1662
- source: "llm_timeout",
1663
- violatedLayers: ["3"]
1664
- };
2490
+ throw new Error(`LLM \u5206\u6790\u8D85\u65F6\uFF08totalTimeout ${timeoutCfg.totalTimeout}s\uFF09`);
1665
2491
  }
1666
2492
  var llmCache, LLM_CACHE_MAX;
1667
2493
  var init_llm_analyzer = __esm({
@@ -1672,14 +2498,177 @@ var init_llm_analyzer = __esm({
1672
2498
  init_skill_loader();
1673
2499
  init_template_loader();
1674
2500
  init_script_analyzer();
2501
+ init_script_content_analyzer();
1675
2502
  llmCache = /* @__PURE__ */ new Map();
1676
2503
  LLM_CACHE_MAX = 200;
1677
2504
  }
1678
2505
  });
1679
2506
 
2507
+ // src/infra/runtime-config.ts
2508
+ var runtime_config_exports = {};
2509
+ __export(runtime_config_exports, {
2510
+ DENY_MODE_LABELS: () => DENY_MODE_LABELS,
2511
+ filterEnabledRules: () => filterEnabledRules,
2512
+ getDisabledL2RulesWithSkipL3: () => getDisabledL2RulesWithSkipL3,
2513
+ getEnabledL1CommandPatterns: () => getEnabledL1CommandPatterns,
2514
+ getEnabledL2SensitivePaths: () => getEnabledL2SensitivePaths,
2515
+ getEnvOverrides: () => getEnvOverrides,
2516
+ getRuntimeConfigPath: () => getRuntimeConfigPath,
2517
+ hasRuntimeConfig: () => hasRuntimeConfig,
2518
+ isLayerEnabled: () => isLayerEnabled,
2519
+ loadRuntimeConfig: () => loadRuntimeConfig,
2520
+ saveRuntimeConfig: () => saveRuntimeConfig
2521
+ });
2522
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2 } from "fs";
2523
+ import { join as join4 } from "path";
2524
+ import { homedir as homedir3 } from "os";
2525
+ function getRuntimeConfigPath() {
2526
+ const envPath = process.env[ENV_RUNTIME_CONFIG_PATH];
2527
+ if (envPath) return envPath;
2528
+ return RUNTIME_CONFIG_PATH;
2529
+ }
2530
+ function saveRuntimeConfig(config) {
2531
+ const p = getRuntimeConfigPath();
2532
+ const dir = p.substring(0, p.lastIndexOf("/"));
2533
+ if (dir && !existsSync7(dir)) mkdirSync2(dir, { recursive: true });
2534
+ writeFileSync2(p, JSON.stringify(config, null, 2), "utf-8");
2535
+ }
2536
+ function emptyRuntimeConfig() {
2537
+ return {
2538
+ version: 1,
2539
+ layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
2540
+ L1_rules: {},
2541
+ L2_rules: {},
2542
+ L3_skills: {}
2543
+ };
2544
+ }
2545
+ function loadRuntimeConfig() {
2546
+ const p = getRuntimeConfigPath();
2547
+ if (!existsSync7(p)) return emptyRuntimeConfig();
2548
+ try {
2549
+ const data = JSON.parse(readFileSync7(p, "utf-8"));
2550
+ return {
2551
+ version: data.version || 1,
2552
+ layers: {
2553
+ L1_heuristic: data.layers?.L1_heuristic ?? true,
2554
+ L2_logic_rules: data.layers?.L2_logic_rules ?? true,
2555
+ L3_llm_analysis: data.layers?.L3_llm_analysis ?? true
2556
+ },
2557
+ L1_rules: data.L1_rules || {},
2558
+ L2_rules: data.L2_rules || {},
2559
+ L3_skills: data.L3_skills || {}
2560
+ };
2561
+ } catch {
2562
+ return emptyRuntimeConfig();
2563
+ }
2564
+ }
2565
+ function isLayerEnabled(layerKey) {
2566
+ const disableKey = LAYER_DISABLE_ENV[layerKey];
2567
+ if (disableKey && process.env[disableKey] === "1") return false;
2568
+ const runtime = loadRuntimeConfig();
2569
+ const layers = runtime.layers || {};
2570
+ if (layerKey in layers) return !!layers[layerKey];
2571
+ return true;
2572
+ }
2573
+ function getEnvOverrides() {
2574
+ const overrides = {};
2575
+ for (const [layerKey, envKey] of Object.entries(LAYER_DISABLE_ENV)) {
2576
+ const val = process.env[envKey];
2577
+ if (val !== void 0) {
2578
+ overrides[layerKey] = {
2579
+ env_var: envKey,
2580
+ env_value: val,
2581
+ overridden: val === "1",
2582
+ effect: val === "1" ? "disabled" : "no_override"
2583
+ };
2584
+ }
2585
+ }
2586
+ return overrides;
2587
+ }
2588
+ function filterEnabledRules(rules, categoryEnabled = true) {
2589
+ if (!categoryEnabled) return [];
2590
+ return rules.filter((r) => r.enabled !== false);
2591
+ }
2592
+ function getEnabledL1CommandPatterns(runtime) {
2593
+ const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
2594
+ return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
2595
+ pattern: r.pattern || "",
2596
+ risk_level: r.risk_level || "",
2597
+ risk_type: r.risk_type || "",
2598
+ reason: r.reason || ""
2599
+ }));
2600
+ }
2601
+ function getEnabledL2SensitivePaths(runtime) {
2602
+ const cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
2603
+ return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => {
2604
+ const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
2605
+ if (r.credential) out.credential = true;
2606
+ if (r.read_only) out.read_only = true;
2607
+ return out;
2608
+ });
2609
+ }
2610
+ function hasRuntimeConfig() {
2611
+ const p = getRuntimeConfigPath();
2612
+ if (!existsSync7(p)) return false;
2613
+ try {
2614
+ JSON.parse(readFileSync7(p, "utf-8"));
2615
+ return true;
2616
+ } catch {
2617
+ return false;
2618
+ }
2619
+ }
2620
+ function getDisabledL2RulesWithSkipL3() {
2621
+ let runtime;
2622
+ try {
2623
+ runtime = loadRuntimeConfig();
2624
+ } catch {
2625
+ return {};
2626
+ }
2627
+ const result = {};
2628
+ const l2 = runtime.L2_rules || {};
2629
+ for (const [catName, cat] of Object.entries(l2)) {
2630
+ if (!cat.category_enabled) continue;
2631
+ const items = [];
2632
+ for (const r of cat.rules || []) {
2633
+ if (r.enabled === false && r.skip_l3_on_disabled) {
2634
+ if (catName === "sensitive_path_access") {
2635
+ if (r.path) items.push(r.path);
2636
+ } else if (catName === "intent_consistency") {
2637
+ items.push(...r.intent_keywords || []);
2638
+ } else if (r.pattern) {
2639
+ items.push(r.pattern);
2640
+ }
2641
+ }
2642
+ }
2643
+ if (items.length > 0) result[catName] = items;
2644
+ }
2645
+ return result;
2646
+ }
2647
+ var RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, ENV_RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS;
2648
+ var init_runtime_config = __esm({
2649
+ "src/infra/runtime-config.ts"() {
2650
+ "use strict";
2651
+ init_esm_shims();
2652
+ RUNTIME_CONFIG_DIR = join4(homedir3(), ".config", "agentmoss");
2653
+ RUNTIME_CONFIG_PATH = join4(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2654
+ ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2655
+ LAYER_DISABLE_ENV = {
2656
+ L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2657
+ L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2658
+ L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2659
+ };
2660
+ DENY_MODE_LABELS = {
2661
+ deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2662
+ deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2663
+ deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2664
+ };
2665
+ }
2666
+ });
2667
+
1680
2668
  // src/engine/coordinator.ts
1681
2669
  var coordinator_exports = {};
1682
2670
  __export(coordinator_exports, {
2671
+ TRUSTED_SKILLS_BASE: () => TRUSTED_SKILLS_BASE,
1683
2672
  runAnalysis: () => runAnalysis
1684
2673
  });
1685
2674
  function extractFirstCommand(command) {
@@ -1710,6 +2699,25 @@ async function runAnalysis(input) {
1710
2699
  return heuristicResult;
1711
2700
  }
1712
2701
  }
2702
+ let scriptChain = null;
2703
+ if (input.aNext.actionType.toLowerCase() === "bash") {
2704
+ scriptChain = analyzeScriptContent2(input);
2705
+ if (scriptChain.nodes.length > 0) {
2706
+ for (const node of scriptChain.nodes) {
2707
+ if (node.l1Hit && (node.l1RiskLevel === "high" || node.l1RiskLevel === "critical")) {
2708
+ return {
2709
+ allowed: false,
2710
+ reason: `\u811A\u672C\u5185\u5BB9\u5305\u542B\u5371\u9669\u64CD\u4F5C: ${node.l1Reason} (\u6587\u4EF6: ${node.scriptPath}, \u6DF1\u5EA6: ${node.depth})`,
2711
+ riskLevel: node.l1RiskLevel,
2712
+ riskType: node.l1RiskType,
2713
+ confidence: 90,
2714
+ source: "heuristic_script_scan",
2715
+ violatedLayers: ["1.1-script"]
2716
+ };
2717
+ }
2718
+ }
2719
+ }
2720
+ }
1713
2721
  const actionType = input.aNext.actionType.toLowerCase();
1714
2722
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
1715
2723
  const lowerDetail = rawDetail.toLowerCase();
@@ -1724,7 +2732,12 @@ async function runAnalysis(input) {
1724
2732
  violatedLayers: []
1725
2733
  };
1726
2734
  }
1727
- if (actionType === "skill" && TRUSTED_SKILLS.has(lowerDetail)) {
2735
+ const agentExt = getAgentExtensions(input.agentId);
2736
+ const trustedSkills = new Set(TRUSTED_SKILLS_BASE);
2737
+ if (agentExt?.builtin_safe_skills) {
2738
+ for (const s of agentExt.builtin_safe_skills) trustedSkills.add(s);
2739
+ }
2740
+ if (actionType === "skill" && trustedSkills.has(lowerDetail)) {
1728
2741
  return {
1729
2742
  allowed: true,
1730
2743
  reason: `\u5185\u7F6E\u5B89\u5168 Skill\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${lowerDetail}`,
@@ -1764,7 +2777,7 @@ async function runAnalysis(input) {
1764
2777
  if (logicResult && !logicResult.allowed) {
1765
2778
  const isCredentialPath = !!logicResult.isCredential;
1766
2779
  const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && logicResult.riskType === "sensitive_path_access";
1767
- if (skipInlineFileAccess && !isCredentialPath) {
2780
+ if (skipInlineFileAccess) {
1768
2781
  const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
1769
2782
  if (inlineJudgment.actionable) {
1770
2783
  if (!inlineJudgment.allowed) {
@@ -1793,6 +2806,23 @@ async function runAnalysis(input) {
1793
2806
  return logicResult;
1794
2807
  }
1795
2808
  }
2809
+ if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
2810
+ for (const node of scriptChain.nodes) {
2811
+ if (node.error || !node.preprocessedContent) continue;
2812
+ if (node.l1RiskLevel === "critical") break;
2813
+ if (node.l2Hit && (node.l2RiskLevel === "high" || node.l2RiskLevel === "critical")) {
2814
+ return {
2815
+ allowed: false,
2816
+ reason: node.l2Reason,
2817
+ riskLevel: node.l2RiskLevel,
2818
+ riskType: node.l2RiskType,
2819
+ confidence: 85,
2820
+ source: "logic_rule_script_scan",
2821
+ violatedLayers: ["1.2-script"]
2822
+ };
2823
+ }
2824
+ }
2825
+ }
1796
2826
  if (skipLLM || !isLLMAvailable()) {
1797
2827
  return {
1798
2828
  allowed: true,
@@ -1804,13 +2834,63 @@ async function runAnalysis(input) {
1804
2834
  violatedLayers: []
1805
2835
  };
1806
2836
  }
1807
- const llmResult = await runLLMAnalysis(input, {
1808
- heuristic: heuristicResult,
1809
- logic: logicResult
1810
- });
2837
+ let skipL3Hints = "\u65E0\uFF08\u5F53\u524D\u65E0\u7981\u7528\u89C4\u5219\u547D\u4E2D\uFF09";
2838
+ try {
2839
+ const { getDisabledL2RulesWithSkipL3: getDisabledL2RulesWithSkipL32 } = await Promise.resolve().then(() => (init_runtime_config(), runtime_config_exports));
2840
+ const disabledL2 = getDisabledL2RulesWithSkipL32();
2841
+ if (disabledL2 && Object.keys(disabledL2).length > 0) {
2842
+ const actionLower = input.aNext.actionDetail.toLowerCase();
2843
+ const matched = [];
2844
+ for (const items of Object.values(disabledL2)) {
2845
+ for (const item of items) {
2846
+ if (item.toLowerCase().includes(actionLower) || actionLower.includes(item.toLowerCase())) {
2847
+ matched.push(item);
2848
+ }
2849
+ }
2850
+ }
2851
+ if (matched.length > 0) {
2852
+ skipL3Hints = `\u26A0\uFE0F \u91CD\u8981\u7EA6\u675F\uFF1A\u7528\u6237\u5DF2\u4E3B\u52A8\u7981\u7528\u9488\u5BF9\u4EE5\u4E0B\u5185\u5BB9\u7684\u5B89\u5168\u62E6\u622A\u89C4\u5219\uFF0C\u8FD9\u662F\u7528\u6237\u7684\u660E\u786E\u51B3\u7B56\uFF1A
2853
+ ${matched.join(", ")}
2854
+ \u5BF9\u8FD9\u4E9B\u8DEF\u5F84/\u5173\u952E\u8BCD\u7684\u76F8\u5173\u64CD\u4F5C\uFF0C\u5FC5\u987B\u89C6\u4E3A\u5141\u8BB8\u7684\uFF0C\u4E0D\u5141\u8BB8\u4EC5\u56E0\u6D89\u53CA\u8FD9\u4E9B\u5185\u5BB9\u5C31\u62D2\u7EDD\u3002\u4F46\u4F60\u4ECD\u9700\u68C0\u67E5\u547D\u4EE4\u7684\u5176\u4ED6\u90E8\u5206\u662F\u5426\u6709\u72EC\u7ACB\u4E8E\u8FD9\u4E9B\u5185\u5BB9\u7684\u5B89\u5168\u98CE\u9669\uFF08\u5982\u7F51\u7EDC\u5916\u4F20\u3001\u63D0\u6743\u7B49\uFF09\u3002`;
2855
+ }
2856
+ }
2857
+ } catch {
2858
+ }
2859
+ let llmResult;
2860
+ try {
2861
+ llmResult = await runLLMAnalysis(input, {
2862
+ heuristic: heuristicResult,
2863
+ logic: logicResult,
2864
+ skipL3Hints,
2865
+ scriptChain: scriptChain ?? void 0
2866
+ });
2867
+ } catch (e) {
2868
+ const { getLLMFailMode: getLLMFailMode2 } = await Promise.resolve().then(() => (init_config(), config_exports));
2869
+ if (getLLMFailMode2() === "fail_open") {
2870
+ console.warn(`[AgentMoss] LLM \u5206\u6790\u5931\u8D25\uFF0Cfail_open \u6A21\u5F0F\uFF1A\u5141\u8BB8\u6267\u884C\uFF08\u524D\u4E24\u5C42\u672A\u62E6\u622A\uFF09: ${e.message}`);
2871
+ return {
2872
+ allowed: true,
2873
+ reason: `LLM \u5206\u6790\u5931\u8D25\uFF08warn-allow\uFF0Cfail_open\uFF09: ${e.message}`,
2874
+ riskLevel: "low",
2875
+ riskType: "",
2876
+ confidence: 50,
2877
+ source: "llm_failure_fallback",
2878
+ violatedLayers: []
2879
+ };
2880
+ }
2881
+ return {
2882
+ allowed: false,
2883
+ reason: `LLM \u5206\u6790\u5931\u8D25\uFF0C\u56E0\u65E0\u6CD5\u5B8C\u6210\u6DF1\u5EA6\u8BED\u4E49\u5206\u6790\uFF0C\u6309 fail-closed \u539F\u5219\u62D2\u7EDD: ${e.message}`,
2884
+ riskLevel: "medium",
2885
+ riskType: "",
2886
+ confidence: 60,
2887
+ source: "llm_failure_fallback",
2888
+ violatedLayers: ["3"]
2889
+ };
2890
+ }
1811
2891
  return llmResult;
1812
2892
  }
1813
- var FULLY_SAFE_TOOLS, TRUSTED_SKILLS, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS;
2893
+ var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS;
1814
2894
  var init_coordinator = __esm({
1815
2895
  "src/engine/coordinator.ts"() {
1816
2896
  "use strict";
@@ -1819,7 +2899,9 @@ var init_coordinator = __esm({
1819
2899
  init_logic_rules();
1820
2900
  init_inline_analyzer();
1821
2901
  init_llm_analyzer();
2902
+ init_script_content_analyzer();
1822
2903
  init_config();
2904
+ init_agents();
1823
2905
  FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
1824
2906
  "ask_user_question",
1825
2907
  "glob",
@@ -1828,7 +2910,7 @@ var init_coordinator = __esm({
1828
2910
  "count_text_length",
1829
2911
  "filemgr-globfiles"
1830
2912
  ]);
1831
- TRUSTED_SKILLS = /* @__PURE__ */ new Set(["xiaoo-guardian"]);
2913
+ TRUSTED_SKILLS_BASE = /* @__PURE__ */ new Set([]);
1832
2914
  READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
1833
2915
  "read",
1834
2916
  "file_read",
@@ -1925,6 +3007,7 @@ var AnalyzeRequestSchema = z.object({
1925
3007
  reason: z.string().default("").describe("Reason for executing this action"),
1926
3008
  os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
1927
3009
  cwd: z.string().default("").describe("Current working directory"),
3010
+ agent_id: z.string().default("").describe("Caller agent identifier, e.g. 'xiaoo' / 'opencode' / 'opendesk'. Empty = generic mode (no agent-specific rules)."),
1928
3011
  metadata: z.record(z.unknown()).optional().describe("Extended metadata")
1929
3012
  });
1930
3013
  var AnalyzeResponseSchema = z.object({
@@ -2074,6 +3157,7 @@ async function analyzeRoute(c) {
2074
3157
  reason: req.reason,
2075
3158
  osType,
2076
3159
  cwd: req.cwd,
3160
+ agentId: req.agent_id,
2077
3161
  llmConfig: req.metadata?.llm_config
2078
3162
  });
2079
3163
  const durationMs = performance.now() - t0;
@@ -2190,7 +3274,8 @@ async function main() {
2190
3274
  },
2191
3275
  reason: data.reason || "",
2192
3276
  osType: data.os_type || (process.platform === "win32" ? "windows" : "linux"),
2193
- cwd: data.cwd || process.cwd()
3277
+ cwd: data.cwd || process.cwd(),
3278
+ agentId: data.agent_id || ""
2194
3279
  });
2195
3280
  console.log(JSON.stringify(result, null, 2));
2196
3281
  process.exit(result.allowed ? 0 : 1);