@kenhkl/agent-moss 0.12.7 → 0.12.8

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
@@ -771,6 +771,53 @@ function runLogicCheck(input) {
771
771
  if (pwdResult) return pwdResult;
772
772
  const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory);
773
773
  if (userDelResult) return userDelResult;
774
+ const comboResult = checkComboChain(actionHistory, aNext.actionDetail);
775
+ if (comboResult) return comboResult;
776
+ return null;
777
+ }
778
+ function checkComboChain(actionHistory, actionDetailRaw) {
779
+ INLINE_SCRIPT_RE.lastIndex = 0;
780
+ if (INLINE_SCRIPT_RE.test(actionDetailRaw)) return null;
781
+ const details = [];
782
+ for (const h of actionHistory) {
783
+ if (h.actionDetail) details.push(h.actionDetail.toLowerCase());
784
+ }
785
+ details.push(actionDetailRaw.toLowerCase());
786
+ const seq = details.map((d) => {
787
+ const hits = /* @__PURE__ */ new Set();
788
+ for (const [stage, pats] of Object.entries(COMBO_STAGES)) {
789
+ if (pats.some((p) => {
790
+ p.lastIndex = 0;
791
+ return p.test(d);
792
+ })) hits.add(stage);
793
+ }
794
+ return hits;
795
+ });
796
+ for (const chain of COMBO_CHAINS) {
797
+ const chainStages = chain.stages;
798
+ if (!chainStages.length || chain.min_stages <= 0) continue;
799
+ let matched = 0;
800
+ let stageIdx = 0;
801
+ for (const hits of seq) {
802
+ if (stageIdx < chainStages.length && hits.has(chainStages[stageIdx])) {
803
+ matched += 1;
804
+ stageIdx += 1;
805
+ if (matched >= chain.min_stages) break;
806
+ }
807
+ }
808
+ if (matched >= chain.min_stages) {
809
+ const riskLevel = chain.risk_level === "critical" || chain.risk_level === "high" || chain.risk_level === "medium" || chain.risk_level === "low" ? chain.risk_level : "high";
810
+ return {
811
+ allowed: false,
812
+ reason: chain.reason,
813
+ riskLevel,
814
+ riskType: "combo_chain",
815
+ confidence: 85,
816
+ source: "logic_rule",
817
+ violatedLayers: ["2"]
818
+ };
819
+ }
820
+ }
774
821
  return null;
775
822
  }
776
823
  function extractFilePaths(text) {
@@ -870,17 +917,17 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
870
917
  }
871
918
  }
872
919
  const unreadPaths = [];
873
- for (const path3 of uniqueTargetPaths) {
920
+ for (const path6 of uniqueTargetPaths) {
874
921
  const hasRead = [...historyReadPaths].some(
875
- (rp) => path3 === rp || path3.startsWith(rp) || rp.startsWith(path3)
922
+ (rp) => path6 === rp || path6.startsWith(rp) || rp.startsWith(path6)
876
923
  );
877
924
  if (!hasRead) {
878
- let isCreate = !fs2.existsSync(path3);
925
+ let isCreate = !fs2.existsSync(path6);
879
926
  if (!isCreate) {
880
927
  isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
881
928
  }
882
929
  if (!isCreate) {
883
- unreadPaths.push(path3);
930
+ unreadPaths.push(path6);
884
931
  }
885
932
  }
886
933
  }
@@ -1023,7 +1070,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1023
1070
  violatedLayers: ["2"]
1024
1071
  };
1025
1072
  }
1026
- var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, WRITE_COMMAND_RES, READ_ONLY_COMMANDS, DEVNULL_REDIRECT_RE, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
1073
+ var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, WRITE_COMMAND_RES, READ_ONLY_COMMANDS, DEVNULL_REDIRECT_RE, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
1027
1074
  var init_logic_rules = __esm({
1028
1075
  "src/engine/logic-rules.ts"() {
1029
1076
  "use strict";
@@ -1032,7 +1079,7 @@ var init_logic_rules = __esm({
1032
1079
  init_rules_loader();
1033
1080
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1034
1081
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1035
- CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path3, riskLevel, desc }) => ({ path: path3, riskLevel, desc, credential: true }));
1082
+ CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc }) => ({ path: path6, riskLevel, desc, credential: true }));
1036
1083
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1037
1084
  "ask_user_question",
1038
1085
  "glob",
@@ -1176,6 +1223,24 @@ var init_logic_rules = __esm({
1176
1223
  reason: p.reason
1177
1224
  }));
1178
1225
  })();
1226
+ COMBO_STAGES = (() => {
1227
+ const combo = loadL2().combo_chains || {};
1228
+ const stages = {};
1229
+ for (const [name, pats] of Object.entries(combo.stages || {})) {
1230
+ stages[name] = pats.map((p) => new RegExp(p, "i"));
1231
+ }
1232
+ return stages;
1233
+ })();
1234
+ COMBO_CHAINS = (() => {
1235
+ const combo = loadL2().combo_chains || {};
1236
+ return (combo.chains || []).map((c) => ({
1237
+ id: String(c.id || "combo_chain"),
1238
+ stages: c.stages || [],
1239
+ min_stages: Number(c.min_stages) || (c.stages || []).length,
1240
+ risk_level: String(c.risk_level || "high"),
1241
+ reason: String(c.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE")
1242
+ }));
1243
+ })();
1179
1244
  INLINE_SCRIPT_RE = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
1180
1245
  DIR_TRAVERSAL_KW = [
1181
1246
  "os.listdir",
@@ -1293,9 +1358,11 @@ __export(runtime_config_exports, {
1293
1358
  addAgentRule: () => addAgentRule,
1294
1359
  addCustomRule: () => addCustomRule,
1295
1360
  addCustomSkill: () => addCustomSkill,
1361
+ addScenarioRule: () => addScenarioRule,
1296
1362
  deleteAgentRule: () => deleteAgentRule,
1297
1363
  deleteCustomRule: () => deleteCustomRule,
1298
1364
  deleteCustomSkill: () => deleteCustomSkill,
1365
+ deleteScenarioRule: () => deleteScenarioRule,
1299
1366
  filterEnabledRules: () => filterEnabledRules,
1300
1367
  generateSourceDefaults: () => generateSourceDefaults,
1301
1368
  getAgentRules: () => getAgentRules,
@@ -1314,9 +1381,11 @@ __export(runtime_config_exports, {
1314
1381
  getEnabledL2SensitivePathsOrDefault: () => getEnabledL2SensitivePathsOrDefault,
1315
1382
  getEnabledL2UserDeletionPatterns: () => getEnabledL2UserDeletionPatterns,
1316
1383
  getEnabledL2UserDeletionPatternsOrDefault: () => getEnabledL2UserDeletionPatternsOrDefault,
1384
+ getEnabledScenarioExtensions: () => getEnabledScenarioExtensions,
1317
1385
  getEnvOverrides: () => getEnvOverrides,
1318
1386
  getProfileOrDetect: () => getProfileOrDetect,
1319
1387
  getRuntimeConfigPath: () => getRuntimeConfigPath,
1388
+ getScenarioRules: () => getScenarioRules,
1320
1389
  hasRuntimeConfig: () => hasRuntimeConfig,
1321
1390
  isL2CategoryEnabled: () => isL2CategoryEnabled,
1322
1391
  isLayerEnabled: () => isLayerEnabled,
@@ -1330,6 +1399,8 @@ __export(runtime_config_exports, {
1330
1399
  updateRuleDenyMode: () => updateRuleDenyMode,
1331
1400
  updateRuleEnabled: () => updateRuleEnabled,
1332
1401
  updateRuleSkipL3: () => updateRuleSkipL3,
1402
+ updateScenarioCategoryEnabled: () => updateScenarioCategoryEnabled,
1403
+ updateScenarioRuleEnabled: () => updateScenarioRuleEnabled,
1333
1404
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1334
1405
  updateSkillEnabled: () => updateSkillEnabled
1335
1406
  });
@@ -1358,7 +1429,8 @@ function emptyRuntimeConfig() {
1358
1429
  L1_rules: {},
1359
1430
  L2_rules: {},
1360
1431
  L3_skills: {},
1361
- agent_rules: {}
1432
+ agent_rules: {},
1433
+ scenario_rules: {}
1362
1434
  };
1363
1435
  }
1364
1436
  function loadRuntimeConfig() {
@@ -1384,7 +1456,8 @@ function loadRuntimeConfig() {
1384
1456
  L1_rules: merged.L1_rules || {},
1385
1457
  L2_rules: merged.L2_rules || {},
1386
1458
  L3_skills: merged.L3_skills || {},
1387
- agent_rules: merged.agent_rules || {}
1459
+ agent_rules: merged.agent_rules || {},
1460
+ scenario_rules: merged.scenario_rules || {}
1388
1461
  };
1389
1462
  } catch {
1390
1463
  return emptyRuntimeConfig();
@@ -1619,6 +1692,55 @@ function _l3SkillsToRules() {
1619
1692
  out.user_custom = { category_enabled: true, category_desc: "", skills: [] };
1620
1693
  return out;
1621
1694
  }
1695
+ function _scenarioRulesToDefaults() {
1696
+ const out = {};
1697
+ const scenDir = existsSync4(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync4(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
1698
+ if (!existsSync4(scenDir)) return out;
1699
+ let entries;
1700
+ try {
1701
+ entries = readdirSync3(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1702
+ } catch {
1703
+ return out;
1704
+ }
1705
+ for (const scenarioId of entries) {
1706
+ const rulesFile = join2(scenDir, scenarioId, "rules.json");
1707
+ if (!existsSync4(rulesFile)) continue;
1708
+ let data;
1709
+ try {
1710
+ data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
1711
+ } catch {
1712
+ continue;
1713
+ }
1714
+ out[scenarioId] = {
1715
+ builtin_safe_action_types: {
1716
+ category_enabled: true,
1717
+ category_desc: "\u573A\u666F\u5B8C\u5168\u5B89\u5168\u64CD\u4F5C\u767D\u540D\u5355\uFF08\u573A\u666F\u901A\u5E38\u4E0D\u4F7F\u7528\uFF09",
1718
+ rules: []
1719
+ },
1720
+ builtin_safe_skills: {
1721
+ category_enabled: true,
1722
+ category_desc: "\u573A\u666F\u4FE1\u4EFB\u7684\u5185\u7F6E Skill\uFF08\u573A\u666F\u901A\u5E38\u4E0D\u4F7F\u7528\uFF09",
1723
+ rules: []
1724
+ },
1725
+ extra_command_patterns: {
1726
+ category_enabled: true,
1727
+ category_desc: "\u573A\u666F\u4E13\u5C5E\u5371\u9669\u547D\u4EE4\u6B63\u5219\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1728
+ rules: (data.extra_command_patterns || []).map((p) => ({ ...p, enabled: true, builtin: true }))
1729
+ },
1730
+ extra_injection_keywords: {
1731
+ category_enabled: true,
1732
+ category_desc: "\u573A\u666F\u4E13\u5C5E\u6CE8\u5165\u5173\u952E\u8BCD\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1733
+ rules: (data.extra_injection_keywords || []).map((k) => ({ ...k, enabled: true, builtin: true }))
1734
+ },
1735
+ extra_sensitive_paths: {
1736
+ category_enabled: true,
1737
+ category_desc: "\u573A\u666F\u4E13\u5C5E\u654F\u611F\u8DEF\u5F84\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1738
+ rules: (data.extra_sensitive_paths || []).map((sp) => ({ ...sp, enabled: true, builtin: true }))
1739
+ }
1740
+ };
1741
+ }
1742
+ return out;
1743
+ }
1622
1744
  function _agentRulesToDefaults() {
1623
1745
  const out = {};
1624
1746
  const agentsDir = existsSync4(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync4(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
@@ -1710,7 +1832,8 @@ function generateSourceDefaults() {
1710
1832
  user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1711
1833
  },
1712
1834
  L3_skills: _l3SkillsToRules(),
1713
- agent_rules: _agentRulesToDefaults()
1835
+ agent_rules: _agentRulesToDefaults(),
1836
+ scenario_rules: _scenarioRulesToDefaults()
1714
1837
  };
1715
1838
  }
1716
1839
  function normalizePatternForFingerprint(pattern) {
@@ -1803,9 +1926,9 @@ function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
1803
1926
  }
1804
1927
  }
1805
1928
  }
1806
- function _mergeAgentRules(userCfg, srcDefaults) {
1807
- const userAgent = userCfg.agent_rules || (userCfg.agent_rules = {});
1808
- const srcAgent = srcDefaults.agent_rules || {};
1929
+ function _mergeAgentRules(userCfg, srcDefaults, partition = "agent_rules") {
1930
+ const userAgent = userCfg[partition] || (userCfg[partition] = {});
1931
+ const srcAgent = srcDefaults[partition] || {};
1809
1932
  for (const [agentId, srcCats] of Object.entries(srcAgent)) {
1810
1933
  if (!(agentId in userAgent)) {
1811
1934
  userAgent[agentId] = JSON.parse(JSON.stringify(srcCats));
@@ -1864,6 +1987,7 @@ function mergeConfig(userCfg, srcDefaults) {
1864
1987
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
1865
1988
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1866
1989
  _mergeAgentRules(cfg, srcDefaults);
1990
+ _mergeAgentRules(cfg, srcDefaults, "scenario_rules");
1867
1991
  return cfg;
1868
1992
  }
1869
1993
  function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
@@ -2189,7 +2313,85 @@ function updateAgentCategoryEnabled(agentId, category, enabled) {
2189
2313
  saveRuntimeConfig(runtime);
2190
2314
  return runtime;
2191
2315
  }
2192
- var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, SKILL_ID_RE;
2316
+ function getScenarioRules(runtime) {
2317
+ return runtime.scenario_rules || {};
2318
+ }
2319
+ function getEnabledScenarioExtensions(runtime, scenarioId) {
2320
+ const scenData = runtime.scenario_rules?.[scenarioId];
2321
+ if (!scenData) return null;
2322
+ const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
2323
+ pattern: r.pattern,
2324
+ risk_level: r.risk_level || "",
2325
+ risk_type: r.risk_type || "",
2326
+ reason: r.reason || ""
2327
+ }));
2328
+ const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
2329
+ keyword: r.keyword,
2330
+ risk_level: r.risk_level || "",
2331
+ lang: r.lang || "en"
2332
+ }));
2333
+ const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
2334
+ path: r.path,
2335
+ risk_level: r.risk_level || "",
2336
+ desc: r.desc || "",
2337
+ credential: r.credential,
2338
+ read_only: r.read_only,
2339
+ deny_mode: r.deny_mode
2340
+ }));
2341
+ return {
2342
+ agent_id: scenarioId,
2343
+ description: "",
2344
+ extra_command_patterns: cmdPatterns,
2345
+ extra_injection_keywords: injKeywords,
2346
+ extra_sensitive_paths: sensitivePaths,
2347
+ builtin_safe_skills: [],
2348
+ builtin_safe_action_types: [],
2349
+ extra_skills: []
2350
+ };
2351
+ }
2352
+ function updateScenarioRuleEnabled(scenarioId, category, ruleFp, enabled) {
2353
+ const runtime = loadRuntimeConfig();
2354
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2355
+ if (cat) {
2356
+ const targetFp = _ruleFingerprint(ruleFp);
2357
+ for (const r of cat.rules || []) {
2358
+ if (_ruleFingerprint(r) === targetFp) {
2359
+ r.enabled = enabled;
2360
+ break;
2361
+ }
2362
+ }
2363
+ }
2364
+ saveRuntimeConfig(runtime);
2365
+ return runtime;
2366
+ }
2367
+ function addScenarioRule(scenarioId, category, rule) {
2368
+ const runtime = loadRuntimeConfig();
2369
+ if (!runtime.scenario_rules) runtime.scenario_rules = {};
2370
+ if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
2371
+ const cats = runtime.scenario_rules[scenarioId];
2372
+ if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
2373
+ (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
2374
+ saveRuntimeConfig(runtime);
2375
+ return runtime;
2376
+ }
2377
+ function deleteScenarioRule(scenarioId, category, ruleFp) {
2378
+ const runtime = loadRuntimeConfig();
2379
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2380
+ if (cat) {
2381
+ const targetFp = _ruleFingerprint(ruleFp);
2382
+ cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
2383
+ }
2384
+ saveRuntimeConfig(runtime);
2385
+ return runtime;
2386
+ }
2387
+ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2388
+ const runtime = loadRuntimeConfig();
2389
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2390
+ if (cat) cat.category_enabled = enabled;
2391
+ saveRuntimeConfig(runtime);
2392
+ return runtime;
2393
+ }
2394
+ var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2193
2395
  var init_runtime_config = __esm({
2194
2396
  "src/infra/runtime-config.ts"() {
2195
2397
  "use strict";
@@ -2214,6 +2416,8 @@ var init_runtime_config = __esm({
2214
2416
  };
2215
2417
  _AGENTS_DIR_PROD = join2(__dirname4, "agents");
2216
2418
  _AGENTS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "agents");
2419
+ _SCENARIOS_DIR_PROD = join2(__dirname4, "scenarios");
2420
+ _SCENARIOS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2217
2421
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2218
2422
  }
2219
2423
  });
@@ -2265,6 +2469,21 @@ function getAgentExtensions(agentId) {
2265
2469
  );
2266
2470
  return ext;
2267
2471
  }
2472
+ function mergeAgentExtensions(...exts) {
2473
+ const nonNull = exts.filter((e) => !!e);
2474
+ if (nonNull.length === 0) return null;
2475
+ const base = nonNull[0];
2476
+ return {
2477
+ agent_id: base.agent_id,
2478
+ description: base.description,
2479
+ extra_command_patterns: nonNull.flatMap((e) => e.extra_command_patterns || []),
2480
+ extra_injection_keywords: nonNull.flatMap((e) => e.extra_injection_keywords || []),
2481
+ extra_sensitive_paths: nonNull.flatMap((e) => e.extra_sensitive_paths || []),
2482
+ builtin_safe_skills: nonNull.flatMap((e) => e.builtin_safe_skills || []),
2483
+ builtin_safe_action_types: nonNull.flatMap((e) => e.builtin_safe_action_types || []),
2484
+ extra_skills: nonNull.flatMap((e) => e.extra_skills || [])
2485
+ };
2486
+ }
2268
2487
  var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
2269
2488
  var init_agents = __esm({
2270
2489
  "src/agents/index.ts"() {
@@ -2278,6 +2497,77 @@ var init_agents = __esm({
2278
2497
  }
2279
2498
  });
2280
2499
 
2500
+ // src/scenarios/index.ts
2501
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
2502
+ import { resolve as resolve4, dirname as dirname5 } from "path";
2503
+ import { fileURLToPath as fileURLToPath7 } from "url";
2504
+ function resolveScenariosDir() {
2505
+ if (existsSync6(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
2506
+ if (existsSync6(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
2507
+ return SCENARIOS_DIR_PROD;
2508
+ }
2509
+ function getScenarioExtensions(scenarioId) {
2510
+ if (!scenarioId) return null;
2511
+ if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
2512
+ let ext = null;
2513
+ try {
2514
+ const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledScenarioExtensions: getEnabledScenarioExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
2515
+ if (hasRuntimeConfig2()) {
2516
+ ext = getEnabledScenarioExtensions2(loadRuntimeConfig2(), scenarioId);
2517
+ }
2518
+ } catch {
2519
+ }
2520
+ if (ext === null) {
2521
+ const scenariosDir = resolveScenariosDir();
2522
+ const rulesFile = resolve4(scenariosDir, scenarioId, "rules.json");
2523
+ if (!existsSync6(rulesFile)) {
2524
+ cache2.set(scenarioId, null);
2525
+ return null;
2526
+ }
2527
+ try {
2528
+ const raw = readFileSync6(rulesFile, "utf-8");
2529
+ const data = JSON.parse(raw);
2530
+ const id = data.scenario_id || data.agent_id || "";
2531
+ if (!id) {
2532
+ console.warn(`[AgentMoss scenarios] rules.json \u7F3A\u5C11 scenario_id \u5B57\u6BB5: ${rulesFile}`);
2533
+ cache2.set(scenarioId, null);
2534
+ return null;
2535
+ }
2536
+ ext = {
2537
+ agent_id: id,
2538
+ description: data.description || "",
2539
+ extra_command_patterns: data.extra_command_patterns || [],
2540
+ extra_injection_keywords: data.extra_injection_keywords || [],
2541
+ extra_sensitive_paths: data.extra_sensitive_paths || [],
2542
+ builtin_safe_skills: data.builtin_safe_skills || [],
2543
+ builtin_safe_action_types: data.builtin_safe_action_types || [],
2544
+ extra_skills: data.extra_skills || []
2545
+ };
2546
+ } catch (e) {
2547
+ console.error(`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} rules.json \u5931\u8D25:`, e.message);
2548
+ cache2.set(scenarioId, null);
2549
+ return null;
2550
+ }
2551
+ }
2552
+ cache2.set(scenarioId, ext);
2553
+ console.log(
2554
+ `[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths`
2555
+ );
2556
+ return ext;
2557
+ }
2558
+ var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
2559
+ var init_scenarios = __esm({
2560
+ "src/scenarios/index.ts"() {
2561
+ "use strict";
2562
+ init_esm_shims();
2563
+ __filename5 = fileURLToPath7(import.meta.url);
2564
+ __dirname6 = dirname5(__filename5);
2565
+ SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
2566
+ SCENARIOS_DIR_DEV_FALLBACK = resolve4(__dirname6, "../../../agent_moss/scenarios");
2567
+ cache2 = /* @__PURE__ */ new Map();
2568
+ }
2569
+ });
2570
+
2281
2571
  // src/engine/heuristic.ts
2282
2572
  function runHeuristicCheck(input) {
2283
2573
  const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
@@ -2317,7 +2607,10 @@ function runHeuristicCheck(input) {
2317
2607
  }
2318
2608
  }
2319
2609
  highestRiskType = topRiskType || highestRiskType;
2320
- const agentExt = getAgentExtensions(input.agentId);
2610
+ const agentExt = mergeAgentExtensions(
2611
+ getAgentExtensions(input.agentId),
2612
+ getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
2613
+ );
2321
2614
  if (agentExt?.extra_command_patterns) {
2322
2615
  for (const rule of agentExt.extra_command_patterns) {
2323
2616
  try {
@@ -2490,13 +2783,13 @@ function checkSensitivePath(command, cwd, agentExt) {
2490
2783
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
2491
2784
  const isWriteOpFlag = isWriteOperation("", command);
2492
2785
  const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
2493
- const pathMatches = (text, path3) => {
2494
- const parts = path3.split(/(\(\?!.*?\))/);
2786
+ const pathMatches = (text, path6) => {
2787
+ const parts = path6.split(/(\(\?!.*?\))/);
2495
2788
  const escaped = parts.map((part) => {
2496
2789
  if (part.startsWith("(?!")) return part;
2497
2790
  return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2498
2791
  }).join("");
2499
- const re = path3.startsWith(".") || path3.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
2792
+ const re = path6.startsWith(".") || path6.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
2500
2793
  return re.test(text);
2501
2794
  };
2502
2795
  for (const sp of ROOT_SENSITIVE_PATHS) {
@@ -2760,6 +3053,7 @@ var init_heuristic = __esm({
2760
3053
  init_esm_shims();
2761
3054
  init_config();
2762
3055
  init_agents();
3056
+ init_scenarios();
2763
3057
  init_rules_loader();
2764
3058
  init_logic_rules();
2765
3059
  INJECTION_KEYWORDS = (() => {
@@ -2785,44 +3079,44 @@ var init_heuristic = __esm({
2785
3079
  });
2786
3080
 
2787
3081
  // src/engine/template-loader.ts
2788
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
2789
- import { resolve as resolve4, dirname as dirname5 } from "path";
2790
- import { fileURLToPath as fileURLToPath7 } from "url";
3082
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
3083
+ import { resolve as resolve5, dirname as dirname6 } from "path";
3084
+ import { fileURLToPath as fileURLToPath8 } from "url";
2791
3085
  function loadTemplates() {
2792
3086
  if (cachedTemplates) return cachedTemplates;
2793
3087
  cachedTemplates = {};
2794
- const dir = existsSync6(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
2795
- if (!existsSync6(dir)) {
3088
+ const dir = existsSync7(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
3089
+ if (!existsSync7(dir)) {
2796
3090
  console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
2797
3091
  return cachedTemplates;
2798
3092
  }
2799
3093
  for (const file of TEMPLATE_FILES) {
2800
- const filePath = resolve4(dir, file);
2801
- if (existsSync6(filePath)) {
3094
+ const filePath = resolve5(dir, file);
3095
+ if (existsSync7(filePath)) {
2802
3096
  const key = file.replace(".txt", "").replace("_template", "");
2803
- cachedTemplates[key] = readFileSync6(filePath, "utf-8");
3097
+ cachedTemplates[key] = readFileSync7(filePath, "utf-8");
2804
3098
  }
2805
3099
  }
2806
3100
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
2807
3101
  return cachedTemplates;
2808
3102
  }
2809
- var __filename5, __dirname6, TEMPLATES_DIR, TEMPLATES_DIR_FALLBACK, TEMPLATE_FILES, cachedTemplates;
3103
+ var __filename6, __dirname7, TEMPLATES_DIR, TEMPLATES_DIR_FALLBACK, TEMPLATE_FILES, cachedTemplates;
2810
3104
  var init_template_loader = __esm({
2811
3105
  "src/engine/template-loader.ts"() {
2812
3106
  "use strict";
2813
3107
  init_esm_shims();
2814
- __filename5 = fileURLToPath7(import.meta.url);
2815
- __dirname6 = dirname5(__filename5);
2816
- TEMPLATES_DIR = resolve4(__dirname6, "../templates");
2817
- TEMPLATES_DIR_FALLBACK = resolve4(__dirname6, "../../../agent_moss/templates");
3108
+ __filename6 = fileURLToPath8(import.meta.url);
3109
+ __dirname7 = dirname6(__filename6);
3110
+ TEMPLATES_DIR = resolve5(__dirname7, "../templates");
3111
+ TEMPLATES_DIR_FALLBACK = resolve5(__dirname7, "../../../agent_moss/templates");
2818
3112
  TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
2819
3113
  cachedTemplates = null;
2820
3114
  }
2821
3115
  });
2822
3116
 
2823
3117
  // src/engine/script-analyzer.ts
2824
- import { readFileSync as readFileSync7 } from "fs";
2825
- import { resolve as resolve5, join as join3 } from "path";
3118
+ import { readFileSync as readFileSync8 } from "fs";
3119
+ import { resolve as resolve6, join as join3 } from "path";
2826
3120
  function emptyResult() {
2827
3121
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
2828
3122
  }
@@ -2875,8 +3169,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
2875
3169
  }
2876
3170
  function readScriptContent(scriptPath, maxLines = 500) {
2877
3171
  try {
2878
- const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve5(scriptPath);
2879
- const raw = readFileSync7(expandedPath, "utf-8");
3172
+ const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
3173
+ const raw = readFileSync8(expandedPath, "utf-8");
2880
3174
  const lines = raw.split("\n").slice(0, maxLines);
2881
3175
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
2882
3176
  } catch (e) {
@@ -3013,294 +3307,1604 @@ var init_script_analyzer = __esm({
3013
3307
  }
3014
3308
  });
3015
3309
 
3016
- // src/engine/script-content-analyzer.ts
3017
- import { readFileSync as readFileSync8, statSync, existsSync as existsSync7 } from "fs";
3018
- import { resolve as resolve6, join as join4, isAbsolute, dirname as dirname6 } from "path";
3019
- import { homedir as homedir2 } from "os";
3020
- function emptyChain() {
3021
- return {
3022
- nodes: [],
3023
- entryPath: "",
3024
- maxDepthReached: false,
3025
- maxSizeReached: false,
3026
- totalSize: 0,
3027
- totalLines: 0,
3028
- hasCritical: false,
3029
- hasHigh: false,
3030
- firstHitNode: null,
3031
- error: ""
3032
- };
3033
- }
3034
- function newNode(path3, depth) {
3035
- return {
3036
- scriptPath: path3,
3037
- rawContent: "",
3038
- preprocessedContent: "",
3039
- lineCount: 0,
3040
- fileSize: 0,
3041
- depth,
3042
- childPaths: [],
3043
- error: "",
3044
- l1Hit: false,
3045
- l1RiskLevel: "",
3046
- l1RiskType: "",
3047
- l1Reason: "",
3048
- l1MatchedPatterns: [],
3049
- l2Hit: false,
3050
- l2ViolatedRule: "",
3051
- l2RiskLevel: "",
3052
- l2RiskType: "",
3053
- l2Reason: "",
3054
- matchedKeywords: [],
3055
- isSuspicious: false,
3056
- keywordRiskLevel: "",
3057
- keywordRiskReason: ""
3058
- };
3059
- }
3060
- function stripComments(content) {
3061
- return content.split("\n").filter((line) => !line.trimStart().startsWith("#")).join("\n");
3062
- }
3063
- function stripOutputText(content) {
3064
- let result = content.replace(/echo\s+".*?"/gs, 'echo ""');
3065
- result = result.replace(/echo\s+'.*?'/gs, "echo ''");
3066
- result = result.replace(/printf\s+".*?"[^\n]*/gs, 'printf ""');
3067
- result = result.replace(/printf\s+'.*?'[^\n]*/gs, "printf ''");
3068
- return result;
3069
- }
3070
- function expandSimpleVariables(content) {
3071
- const varMap = {};
3072
- const assignmentRe = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.+)$/;
3073
- const collectAssignments = (text) => {
3074
- for (const line of text.split("\n")) {
3075
- const m = line.match(assignmentRe);
3076
- if (m) {
3077
- const varName = m[1];
3078
- let varVal = m[2].trim();
3079
- if (varVal && !varVal.includes("$") && !varVal.includes("`")) {
3080
- if (varVal.startsWith('"') && varVal.endsWith('"') || varVal.startsWith("'") && varVal.endsWith("'")) {
3081
- varVal = varVal.slice(1, -1);
3082
- }
3083
- varMap[varName] = varVal;
3310
+ // src/engine/script-engine/backends/python-backend.ts
3311
+ import { parser } from "@lezer/python";
3312
+ function buildLineStarts(content) {
3313
+ const starts = [0];
3314
+ for (let i = 0; i < content.length; i++) {
3315
+ if (content[i] === "\n") starts.push(i + 1);
3316
+ }
3317
+ return starts;
3318
+ }
3319
+ function lineAt(lineStarts, pos) {
3320
+ let lo = 0;
3321
+ let hi = lineStarts.length - 1;
3322
+ while (lo < hi) {
3323
+ const mid = lo + hi + 1 >> 1;
3324
+ if (lineStarts[mid] <= pos) lo = mid;
3325
+ else hi = mid - 1;
3326
+ }
3327
+ return lo + 1;
3328
+ }
3329
+ function markNodeLines(node, lines, lineStarts) {
3330
+ const start = lineAt(lineStarts, node.from);
3331
+ const end = node.to > node.from ? lineAt(lineStarts, node.to - 1) : start;
3332
+ for (let i = start; i <= end; i++) lines.add(i);
3333
+ }
3334
+ function reconstruct(sourceLines, lines) {
3335
+ const out = [];
3336
+ for (const i of [...lines].sort((a, b) => a - b)) {
3337
+ if (i >= 1 && i <= sourceLines.length) out.push(sourceLines[i - 1]);
3338
+ }
3339
+ return out.join("\n");
3340
+ }
3341
+ function makeEnv(argv) {
3342
+ return { argv, vars: {} };
3343
+ }
3344
+ function srcSlice(node) {
3345
+ return SOURCE.slice(node.from, node.to);
3346
+ }
3347
+ function stringValue(tokenText) {
3348
+ let s = tokenText;
3349
+ if (s.length >= 2) {
3350
+ const q = s[0];
3351
+ if ((q === '"' || q === "'") && s.endsWith(q)) s = s.slice(1, -1);
3352
+ }
3353
+ s = s.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\\\/g, "\\").replace(/\\"/g, '"').replace(/\\'/g, "'");
3354
+ return s;
3355
+ }
3356
+ function isSysArgv(node) {
3357
+ if (!node || node.name !== "MemberExpression") return false;
3358
+ const first = node.firstChild;
3359
+ if (!first || first.name !== "VariableName" || srcSlice(first) !== "sys") return false;
3360
+ const prop = node.getChild("PropertyName");
3361
+ return prop !== null && srcSlice(prop) === "argv";
3362
+ }
3363
+ function evalExpr(node, env) {
3364
+ if (!node) return null;
3365
+ const name = node.name;
3366
+ if (name === "String") return stringValue(srcSlice(node));
3367
+ if (name === "Number") return srcSlice(node);
3368
+ if (name === "VariableName") {
3369
+ const id = srcSlice(node);
3370
+ return id in env.vars ? env.vars[id] : null;
3371
+ }
3372
+ if (name === "MemberExpression") {
3373
+ const bracket = node.getChild("[");
3374
+ if (bracket) {
3375
+ const inner = node.firstChild;
3376
+ if (inner && isSysArgv(inner)) {
3377
+ const idxNode = bracket.nextSibling;
3378
+ if (idxNode && idxNode.name === "Number") {
3379
+ const idx = parseInt(srcSlice(idxNode), 10);
3380
+ if (idx >= 1 && idx <= env.argv.length) return env.argv[idx - 1];
3381
+ return null;
3084
3382
  }
3085
3383
  }
3384
+ return null;
3086
3385
  }
3087
- };
3088
- collectAssignments(content);
3089
- let expanded = content;
3090
- for (let round = 0; round < 3; round++) {
3091
- const sortedVars = Object.keys(varMap).sort((a, b) => b.length - a.length);
3092
- let newExpanded = expanded;
3093
- for (const v of sortedVars) {
3094
- const val = varMap[v];
3095
- newExpanded = newExpanded.split("${" + v + "}").join(val);
3096
- newExpanded = newExpanded.split("$" + v).join(val);
3097
- }
3098
- if (newExpanded === expanded) break;
3099
- expanded = newExpanded;
3100
- collectAssignments(expanded);
3101
- }
3102
- return expanded;
3103
- }
3104
- function preprocessScriptContent(content) {
3105
- let result = stripComments(content);
3106
- result = stripOutputText(result);
3107
- result = expandSimpleVariables(result);
3108
- return result;
3109
- }
3110
- function scanScriptForKeywords2(content) {
3111
- const matched = [];
3112
- for (const { name, pattern } of SUSPICIOUS_PATTERNS2) {
3113
- if (pattern.test(content)) matched.push(name);
3114
- }
3115
- return matched;
3116
- }
3117
- function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
3118
- if (matchedKeywords.length === 0) return { level: "", reason: "" };
3119
- const keywordSet = new Set(matchedKeywords);
3120
- for (const rule of KEYWORD_COMBINATION_RISKS2) {
3121
- const hitGroup1 = [...rule.group1].some((k) => keywordSet.has(k));
3122
- const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
3123
- if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
3386
+ if (isSysArgv(node)) return env.argv[0] ?? null;
3387
+ return null;
3124
3388
  }
3125
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
3126
- const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
3127
- if (hasNetworkPost) {
3128
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
3129
- const contentLower = scriptContent.toLowerCase();
3130
- for (const p of envPatterns) {
3131
- if (contentLower.includes(p.toLowerCase())) {
3132
- return {
3133
- level: "critical",
3134
- 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"
3135
- };
3136
- }
3389
+ if (name === "CallExpression") {
3390
+ const func = node.firstChild;
3391
+ if (func && func.name === "VariableName" && srcSlice(func) === "len") {
3392
+ const argList = node.getChild("ArgList");
3393
+ const inner = argList ? firstNamedChild(argList) : null;
3394
+ if (inner && isSysArgv(inner)) return String(env.argv.length + 1);
3137
3395
  }
3396
+ return null;
3138
3397
  }
3139
- let bestLevel = "";
3140
- let bestReason = "";
3141
- for (const kw of matchedKeywords) {
3142
- const risk = SINGLE_KEYWORD_RISKS2[kw];
3143
- if (risk && (LEVEL_ORDER2[risk.level] || 0) > (LEVEL_ORDER2[bestLevel] || 0)) {
3144
- bestLevel = risk.level;
3145
- bestReason = risk.reason;
3398
+ if (name === "ConditionalExpression") {
3399
+ const ifKw = node.getChild("if");
3400
+ const elseKw = node.getChild("else");
3401
+ if (!ifKw || !elseKw) return null;
3402
+ const thenNode = node.firstChild;
3403
+ const condNode = ifKw.nextSibling;
3404
+ const orelseNode = elseKw.nextSibling;
3405
+ if (!thenNode || !condNode || !orelseNode) return null;
3406
+ const cond = evalCondition(condNode, env);
3407
+ if (cond === true) return evalExpr(thenNode, env);
3408
+ if (cond === false) return evalExpr(orelseNode, env);
3409
+ return null;
3410
+ }
3411
+ if (name === "BinaryExpression") {
3412
+ const op = findOp(node);
3413
+ if (op === "+") {
3414
+ const left = evalExpr(node.firstChild, env);
3415
+ const right = evalExpr(lastNamedChild(node), env);
3416
+ if (left !== null && right !== null) return left + right;
3146
3417
  }
3418
+ return null;
3147
3419
  }
3148
- return { level: bestLevel, reason: bestReason };
3420
+ return null;
3149
3421
  }
3150
- function extractScriptPath2(command) {
3151
- for (const pattern of SCRIPT_EXEC_PATTERNS2) {
3152
- const m = command.match(pattern);
3153
- if (m) return m[1];
3422
+ function firstNamedChild(node) {
3423
+ let c = node.firstChild;
3424
+ while (c) {
3425
+ if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":") return c;
3426
+ c = c.nextSibling;
3154
3427
  }
3155
3428
  return null;
3156
3429
  }
3157
- function extractInlineScriptPath(actionDetail) {
3158
- const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
3159
- let m;
3160
- while ((m = re.exec(actionDetail)) !== null) {
3161
- const p = m[1];
3162
- let resolved;
3163
- try {
3164
- resolved = resolvePath(p);
3165
- if (existsSync7(resolved)) {
3166
- const stat = statSync(resolved);
3167
- if (stat.isFile()) return resolved;
3168
- }
3169
- } catch {
3170
- }
3430
+ function lastNamedChild(node) {
3431
+ let c = node.lastChild;
3432
+ while (c) {
3433
+ if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":") return c;
3434
+ c = c.prevSibling;
3171
3435
  }
3172
3436
  return null;
3173
3437
  }
3174
- function resolvePath(p) {
3175
- let expanded = p;
3176
- if (expanded.startsWith("~/") || expanded === "~") {
3177
- expanded = join4(homedir2(), expanded.slice(1));
3438
+ function findOp(node) {
3439
+ let c = node.firstChild;
3440
+ while (c) {
3441
+ if (c.name === "CompareOp" || c.name === "and" || c.name === "or" || c.name === "UpdateOp") {
3442
+ return srcSlice(c).trim();
3443
+ }
3444
+ c = c.nextSibling;
3178
3445
  }
3179
- return resolve6(expanded);
3446
+ return null;
3180
3447
  }
3181
- function readScriptContent2(scriptPath, maxFileSize = 2 * 1024 * 1024) {
3182
- try {
3183
- const path3 = resolvePath(scriptPath);
3184
- if (!existsSync7(path3)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${scriptPath}` };
3185
- const stat = statSync(path3);
3186
- if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${scriptPath}` };
3187
- if (stat.size > maxFileSize) {
3188
- return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
3448
+ function evalCondition(node, env) {
3449
+ if (!node) return null;
3450
+ const name = node.name;
3451
+ if (name === "Boolean") {
3452
+ const t = srcSlice(node).trim();
3453
+ return t === "True" ? true : t === "False" ? false : null;
3454
+ }
3455
+ if (name === "BinaryExpression") {
3456
+ const op = findOp(node);
3457
+ if (op === "and" || op === "or") {
3458
+ const parts2 = namedChildren(node);
3459
+ const vals = parts2.map((p) => evalCondition(p, env));
3460
+ if (op === "and") {
3461
+ if (vals.some((v) => v === false)) return false;
3462
+ if (vals.every((v) => v === true)) return true;
3463
+ return null;
3464
+ }
3465
+ if (vals.some((v) => v === true)) return true;
3466
+ if (vals.every((v) => v === false)) return false;
3467
+ return null;
3468
+ }
3469
+ const parts = namedChildren(node);
3470
+ if (parts.length !== 3) return null;
3471
+ const [left, opNode, right] = parts;
3472
+ const opText = srcSlice(opNode).trim();
3473
+ return evalCompare(left, opText, right, env);
3474
+ }
3475
+ if (name === "UnaryExpression") {
3476
+ const kw = node.getChild("not");
3477
+ if (kw) {
3478
+ const operand = kw.nextSibling;
3479
+ const v = evalCondition(operand, env);
3480
+ return v === null ? null : !v;
3481
+ }
3482
+ return null;
3483
+ }
3484
+ if (name === "CallExpression") {
3485
+ return null;
3486
+ }
3487
+ return null;
3488
+ }
3489
+ function namedChildren(node) {
3490
+ const out = [];
3491
+ let c = node.firstChild;
3492
+ while (c) {
3493
+ if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":" && c.name !== "[" && c.name !== "]") {
3494
+ out.push(c);
3495
+ }
3496
+ c = c.nextSibling;
3497
+ }
3498
+ return out;
3499
+ }
3500
+ function evalCompare(left, opText, right, env) {
3501
+ if (opText === "==" && left.name === "VariableName" && srcSlice(left) === "__name__") {
3502
+ const rv = evalExpr(right, env);
3503
+ if (rv !== null) return rv === "__main__";
3504
+ return null;
3505
+ }
3506
+ if (opText === "==" || opText === "!=") {
3507
+ const lv = evalExpr(left, env);
3508
+ const rv = evalExpr(right, env);
3509
+ if (lv !== null && rv !== null) {
3510
+ return opText === "==" ? lv === rv : lv !== rv;
3511
+ }
3512
+ return null;
3513
+ }
3514
+ if (opText === "in") {
3515
+ const lv = evalExpr(left, env);
3516
+ if (lv === null) return null;
3517
+ const rightName = right.name;
3518
+ if (rightName === "TupleExpression" || rightName === "ArrayExpression" || rightName === "SetExpression" || rightName === "ParenthesizedExpression") {
3519
+ const container = rightName === "ParenthesizedExpression" && right.firstChild && right.firstChild.name === "TupleExpression" ? right.firstChild : right;
3520
+ for (const elt of namedChildren(container)) {
3521
+ const ev = evalExpr(elt, env);
3522
+ if (ev !== null && ev === lv) return true;
3523
+ }
3524
+ return false;
3525
+ }
3526
+ const rv = evalExpr(right, env);
3527
+ if (rv !== null) return rv.includes(lv);
3528
+ return null;
3529
+ }
3530
+ if (opText === ">" || opText === ">=" || opText === "<" || opText === "<=") {
3531
+ const lv = evalExpr(left, env);
3532
+ const rv = evalExpr(right, env);
3533
+ if (lv === null || rv === null) return null;
3534
+ const ln = parseInt(lv, 10);
3535
+ const rn = parseInt(rv, 10);
3536
+ if (Number.isNaN(ln) || Number.isNaN(rn)) return null;
3537
+ if (opText === ">") return ln > rn;
3538
+ if (opText === ">=") return ln >= rn;
3539
+ if (opText === "<") return ln < rn;
3540
+ return ln <= rn;
3541
+ }
3542
+ return null;
3543
+ }
3544
+ function processStmts(stmts, env, lineStarts, lines, branches) {
3545
+ for (const s of stmts) processStmt(s, env, lineStarts, lines, branches);
3546
+ }
3547
+ function invalidateEnvOnCalls(node, env) {
3548
+ const cursor = node.cursor();
3549
+ cursor.iterate((n) => {
3550
+ const nd = n.node;
3551
+ if (nd.name !== "CallExpression") return true;
3552
+ const func = nd.firstChild;
3553
+ if (func && func.name === "VariableName" && !PURE_BUILTINS.has(srcSlice(func))) {
3554
+ for (const k of Object.keys(env.vars)) env.vars[k] = null;
3555
+ return false;
3556
+ }
3557
+ return true;
3558
+ });
3559
+ }
3560
+ function processStmt(stmt, env, lineStarts, lines, branches) {
3561
+ const name = stmt.name;
3562
+ if (name === "AssignStatement") {
3563
+ const target = stmt.firstChild;
3564
+ if (target && target.name === "VariableName") {
3565
+ const valNode = target.nextSibling ? target.nextSibling.nextSibling : null;
3566
+ invalidateEnvOnCalls(stmt, env);
3567
+ const val = evalExpr(valNode, env);
3568
+ env.vars[srcSlice(target)] = val;
3569
+ }
3570
+ markNodeLines(stmt, lines, lineStarts);
3571
+ return;
3572
+ }
3573
+ if (name === "UpdateStatement") {
3574
+ const target = stmt.firstChild;
3575
+ if (target && target.name === "VariableName") env.vars[srcSlice(target)] = null;
3576
+ invalidateEnvOnCalls(stmt, env);
3577
+ markNodeLines(stmt, lines, lineStarts);
3578
+ return;
3579
+ }
3580
+ if (name === "IfStatement") {
3581
+ processIf(stmt, env, lineStarts, lines, branches);
3582
+ return;
3583
+ }
3584
+ if (name === "ForStatement" || name === "WhileStatement") {
3585
+ markNodeLines(stmt, lines, lineStarts);
3586
+ for (const body of stmt.getChildren("Body")) {
3587
+ processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
3588
+ }
3589
+ return;
3590
+ }
3591
+ if (name === "TryStatement") {
3592
+ markNodeLines(stmt, lines, lineStarts);
3593
+ for (const body of stmt.getChildren("Body")) {
3594
+ processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
3595
+ }
3596
+ return;
3597
+ }
3598
+ if (name === "WithStatement") {
3599
+ markNodeLines(stmt, lines, lineStarts);
3600
+ const body = stmt.getChild("Body");
3601
+ if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
3602
+ return;
3603
+ }
3604
+ if (name === "MatchStatement") {
3605
+ markNodeLines(stmt, lines, lineStarts);
3606
+ for (const clause of stmt.getChildren("MatchClause")) {
3607
+ const body = clause.getChild("Body");
3608
+ if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
3189
3609
  }
3190
- const content = readFileSync8(path3, "utf-8");
3191
- const lineCount = content ? content.split("\n").length : 0;
3192
- return { content, lineCount, fileSize: stat.size, error: "" };
3610
+ return;
3611
+ }
3612
+ if (name === "FunctionDefinition" || name === "ClassDefinition") {
3613
+ markDefTree(stmt, lines, lineStarts);
3614
+ return;
3615
+ }
3616
+ if (name === "ExpressionStatement") {
3617
+ invalidateEnvOnCalls(stmt, env);
3618
+ }
3619
+ markNodeLines(stmt, lines, lineStarts);
3620
+ }
3621
+ function markDefTree(stmt, lines, lineStarts) {
3622
+ const cursor = stmt.cursor();
3623
+ cursor.iterate((n) => {
3624
+ markNodeLines(n.node, lines, lineStarts);
3625
+ return true;
3626
+ });
3627
+ }
3628
+ function bodyStmts(body) {
3629
+ const out = [];
3630
+ let c = body.firstChild;
3631
+ while (c) {
3632
+ if (c.name !== ":" && c.name !== ";") out.push(c);
3633
+ c = c.nextSibling;
3634
+ }
3635
+ return out;
3636
+ }
3637
+ function processIf(stmt, env, lineStarts, lines, branches) {
3638
+ const sections = [];
3639
+ let c = stmt.firstChild;
3640
+ while (c) {
3641
+ if (c.name === "if" || c.name === "elif") {
3642
+ sections.push({ cond: c.nextSibling ?? null, body: null, isElse: false });
3643
+ } else if (c.name === "else") {
3644
+ sections.push({ cond: null, body: null, isElse: true });
3645
+ } else if (c.name === "Body") {
3646
+ if (sections.length > 0 && sections[sections.length - 1].body === null) {
3647
+ sections[sections.length - 1].body = c;
3648
+ }
3649
+ }
3650
+ c = c.nextSibling;
3651
+ }
3652
+ processIfSections(stmt, sections, 0, env, lineStarts, lines, branches);
3653
+ }
3654
+ function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branches) {
3655
+ if (idx >= sections.length) return;
3656
+ const s = sections[idx];
3657
+ if (s.cond) markNodeLines(s.cond, lines, lineStarts);
3658
+ if (s.isElse) {
3659
+ if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
3660
+ return;
3661
+ }
3662
+ const cond = s.cond ? evalCondition(s.cond, env) : null;
3663
+ branches.push({
3664
+ kind: "if",
3665
+ label: s.cond ? srcSlice(s.cond) : "",
3666
+ body: "",
3667
+ reachable: cond !== false
3668
+ });
3669
+ if (cond === true) {
3670
+ if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
3671
+ return;
3672
+ }
3673
+ if (cond === false) {
3674
+ processIfSections(_stmt, sections, idx + 1, env, lineStarts, lines, branches);
3675
+ return;
3676
+ }
3677
+ if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
3678
+ for (let j = idx + 1; j < sections.length; j++) {
3679
+ const sj = sections[j];
3680
+ if (sj.cond) markNodeLines(sj.cond, lines, lineStarts);
3681
+ if (sj.body) processStmts(bodyStmts(sj.body), env, lineStarts, lines, branches);
3682
+ }
3683
+ }
3684
+ function parseShellString(s) {
3685
+ CHILD_CALL_RE.lastIndex = 0;
3686
+ let m;
3687
+ while ((m = CHILD_CALL_RE.exec(s)) !== null) {
3688
+ const interp = m[1];
3689
+ const path6 = m[2].trim();
3690
+ let rest = m[3].trim();
3691
+ if (path6.startsWith("-") || path6.startsWith("$") || path6.includes("/dev/")) continue;
3692
+ rest = rest.split(/[;|&<>]/)[0].trim();
3693
+ const args = [];
3694
+ for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
3695
+ args.push(tm[1] ? tm[2] : tm[3]);
3696
+ }
3697
+ return { path: path6, args, interpreter: interp };
3698
+ }
3699
+ return null;
3700
+ }
3701
+ function extractCallsFromTree(tree, reachableLines) {
3702
+ const calls = [];
3703
+ const seen = /* @__PURE__ */ new Set();
3704
+ const add = (c) => {
3705
+ const key = `${c.path}\0${c.args.join("\0")}\0${c.interpreter}`;
3706
+ if (!seen.has(key)) {
3707
+ seen.add(key);
3708
+ calls.push(c);
3709
+ }
3710
+ };
3711
+ const lineReachable = (node) => {
3712
+ if (reachableLines === null) return true;
3713
+ return reachableLines.has(lineAt(buildLineStarts(SOURCE), node.from));
3714
+ };
3715
+ const cursor = tree.cursor();
3716
+ cursor.iterate((n) => {
3717
+ const node = n.node;
3718
+ if (node.name !== "CallExpression") return true;
3719
+ if (!lineReachable(node)) return true;
3720
+ const func = node.firstChild;
3721
+ if (!func || func.name !== "MemberExpression") return true;
3722
+ const modName = func.firstChild;
3723
+ if (!modName || modName.name !== "VariableName") return true;
3724
+ const funcNameNode = func.getChild("PropertyName");
3725
+ if (!funcNameNode) return true;
3726
+ const mod = srcSlice(modName);
3727
+ const funcName = srcSlice(funcNameNode);
3728
+ const argList = node.getChild("ArgList");
3729
+ const firstArg = argList ? firstNamedChild(argList) : null;
3730
+ if (mod === "subprocess" && SUBPROCESS_FUNCS.has(funcName)) {
3731
+ if (firstArg && (firstArg.name === "ArrayExpression" || firstArg.name === "TupleExpression")) {
3732
+ const elts = [];
3733
+ let allStr = true;
3734
+ for (const elt of namedChildren(firstArg)) {
3735
+ if (elt.name === "String") elts.push(stringValue(srcSlice(elt)));
3736
+ else {
3737
+ allStr = false;
3738
+ break;
3739
+ }
3740
+ }
3741
+ if (allStr && elts.length >= 2) {
3742
+ const interp = elts[0];
3743
+ const path6 = elts[1];
3744
+ if (!path6.startsWith("-") && !path6.startsWith("$") && !path6.includes("/dev/")) {
3745
+ add({ path: path6, args: elts.slice(2), interpreter: interp });
3746
+ return true;
3747
+ }
3748
+ }
3749
+ }
3750
+ if (firstArg && firstArg.name === "String") {
3751
+ const sc = parseShellString(stringValue(srcSlice(firstArg)));
3752
+ if (sc) add(sc);
3753
+ }
3754
+ } else if (mod === "os" && OS_FUNCS.has(funcName)) {
3755
+ if (firstArg && firstArg.name === "String") {
3756
+ const sc = parseShellString(stringValue(srcSlice(firstArg)));
3757
+ if (sc) add(sc);
3758
+ }
3759
+ }
3760
+ return true;
3761
+ });
3762
+ const cursor2 = tree.cursor();
3763
+ cursor2.iterate((n) => {
3764
+ const node = n.node;
3765
+ if (node.name !== "ImportStatement") return true;
3766
+ const txt = srcSlice(node);
3767
+ if (!/^from\s+\./m.test(txt)) return true;
3768
+ const m = txt.match(/\bimport\s+(.+)$/m);
3769
+ if (m) {
3770
+ for (const part of m[1].split(",")) {
3771
+ const name = part.trim().replace(/\s+as\s+\w+$/, "");
3772
+ if (name && name !== "*") add({ path: name.replace(/\./g, "/") + ".py", args: [], interpreter: "python3" });
3773
+ }
3774
+ }
3775
+ return true;
3776
+ });
3777
+ return calls;
3778
+ }
3779
+ function extractCallsRegex(content) {
3780
+ const calls = [];
3781
+ CHILD_CALL_RE.lastIndex = 0;
3782
+ let m;
3783
+ while ((m = CHILD_CALL_RE.exec(content)) !== null) {
3784
+ const interp = m[1];
3785
+ const path6 = m[2].trim();
3786
+ let rest = m[3].trim();
3787
+ if (path6.startsWith("-") || path6.startsWith("$") || path6.includes("/dev/")) continue;
3788
+ rest = rest.split(/[;|&<>]/)[0].trim();
3789
+ const args = [];
3790
+ for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
3791
+ args.push(tm[1] ? tm[2] : tm[3]);
3792
+ }
3793
+ calls.push({ path: path6, args, interpreter: interp });
3794
+ }
3795
+ return calls;
3796
+ }
3797
+ var MAX_LINES, PURE_BUILTINS, CHILD_CALL_RE, SUBPROCESS_FUNCS, OS_FUNCS, SOURCE, PythonBackend;
3798
+ var init_python_backend = __esm({
3799
+ "src/engine/script-engine/backends/python-backend.ts"() {
3800
+ "use strict";
3801
+ init_esm_shims();
3802
+ MAX_LINES = 500;
3803
+ PURE_BUILTINS = /* @__PURE__ */ new Set([
3804
+ "len",
3805
+ "print",
3806
+ "str",
3807
+ "int",
3808
+ "float",
3809
+ "bool",
3810
+ "list",
3811
+ "dict",
3812
+ "set",
3813
+ "tuple",
3814
+ "range",
3815
+ "sorted",
3816
+ "enumerate",
3817
+ "zip",
3818
+ "map",
3819
+ "filter",
3820
+ "sum",
3821
+ "min",
3822
+ "max",
3823
+ "abs",
3824
+ "round",
3825
+ "isinstance",
3826
+ "type",
3827
+ "open",
3828
+ "input",
3829
+ "repr",
3830
+ "ord",
3831
+ "chr",
3832
+ "hex",
3833
+ "oct",
3834
+ "bin",
3835
+ "format",
3836
+ "id",
3837
+ "hash",
3838
+ "any",
3839
+ "all",
3840
+ "next",
3841
+ "iter",
3842
+ "reversed",
3843
+ "pow",
3844
+ "divmod"
3845
+ ]);
3846
+ CHILD_CALL_RE = /(?:^|\s|&&|\|\|)\s*((?:\/bin\/)?bash|sh|zsh|(?:python|python3|node|ruby|perl|php|lua))\s+(\S+(?:\.(?:sh|shrc|py|js|rb|pl|php|lua))?)([^\n;|&]*?)$/gim;
3847
+ SUBPROCESS_FUNCS = /* @__PURE__ */ new Set(["run", "call", "Popen", "check_call", "check_output"]);
3848
+ OS_FUNCS = /* @__PURE__ */ new Set(["system", "popen", "popen2", "popen3", "popen4"]);
3849
+ SOURCE = "";
3850
+ PythonBackend = class {
3851
+ language = "python";
3852
+ parse(content, entryArgs) {
3853
+ const c = content || "";
3854
+ SOURCE = c;
3855
+ const sourceLines = c.split("\n");
3856
+ const lineStarts = buildLineStarts(c);
3857
+ const env = makeEnv(entryArgs || []);
3858
+ const lines = /* @__PURE__ */ new Set();
3859
+ const branches = [];
3860
+ if (sourceLines.length > MAX_LINES) {
3861
+ return {
3862
+ reachableContent: c,
3863
+ controlFlowResolved: false,
3864
+ calls: extractCallsRegex(c),
3865
+ branches: [],
3866
+ unresolvedSnippets: [],
3867
+ language: this.language
3868
+ };
3869
+ }
3870
+ let tree;
3871
+ try {
3872
+ tree = parser.parse(c).topNode;
3873
+ } catch {
3874
+ return {
3875
+ reachableContent: c,
3876
+ controlFlowResolved: false,
3877
+ calls: extractCallsRegex(c),
3878
+ branches: [],
3879
+ unresolvedSnippets: [],
3880
+ language: this.language
3881
+ };
3882
+ }
3883
+ const stmts = bodyStmts(tree);
3884
+ processStmts(stmts, env, lineStarts, lines, branches);
3885
+ const reachable = reconstruct(sourceLines, lines);
3886
+ let calls = extractCallsFromTree(tree, lines);
3887
+ if (calls.length === 0) calls = extractCallsRegex(c);
3888
+ const srcNonblank = sourceLines.filter((l) => l.trim().length > 0);
3889
+ const rcNonblank = reachable.split("\n").filter((l) => l.trim().length > 0);
3890
+ const resolved = rcNonblank.length !== srcNonblank.length || rcNonblank.some((l, i) => l !== srcNonblank[i]);
3891
+ return {
3892
+ reachableContent: reachable,
3893
+ controlFlowResolved: resolved,
3894
+ calls,
3895
+ branches,
3896
+ unresolvedSnippets: [],
3897
+ language: this.language
3898
+ };
3899
+ }
3900
+ };
3901
+ }
3902
+ });
3903
+
3904
+ // src/engine/script-engine/symbolic.ts
3905
+ function reescape(s) {
3906
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3907
+ }
3908
+ function svalConc(v) {
3909
+ return { kind: "concrete", value: v, children: [], op: "" };
3910
+ }
3911
+ function svalSym(name) {
3912
+ return { kind: "symbol", value: name, children: [], op: "" };
3913
+ }
3914
+ function svalExpr(children) {
3915
+ return { kind: "expr", value: null, children, op: "+" };
3916
+ }
3917
+ function fold(val) {
3918
+ if (val.kind !== "expr") return val;
3919
+ const parts = [];
3920
+ for (const c of val.children) {
3921
+ const cc = fold(c);
3922
+ if (cc.kind === "concrete") {
3923
+ parts.push(cc.value || "");
3924
+ } else {
3925
+ return cc;
3926
+ }
3927
+ }
3928
+ return svalConc(parts.join(""));
3929
+ }
3930
+ function countMatches(re, s) {
3931
+ const m = s.match(re);
3932
+ return m ? m.length : 0;
3933
+ }
3934
+ function findBlockEnd(lines, startLine, openRe, closeRe) {
3935
+ let depth = 0;
3936
+ for (let idx = startLine; idx < lines.length; idx++) {
3937
+ depth += countMatches(openRe, lines[idx]) - countMatches(closeRe, lines[idx]);
3938
+ if (depth <= 0 && idx > startLine) return idx;
3939
+ }
3940
+ return -1;
3941
+ }
3942
+ function findCaseEnd(lines, start) {
3943
+ return findBlockEnd(lines, start, CASE_OPEN_RE, CASE_CLOSE_RE);
3944
+ }
3945
+ function findIfEnd(lines, start) {
3946
+ return findBlockEnd(lines, start, IF_OPEN_RE, IF_CLOSE_RE);
3947
+ }
3948
+ function findLoopEnd(lines, start) {
3949
+ let depth = 0;
3950
+ for (let idx = start; idx < lines.length; idx++) {
3951
+ depth += countMatches(LOOP_DO_RE, lines[idx]) - countMatches(LOOP_DONE_RE, lines[idx]);
3952
+ if (depth <= 0 && idx > start) return idx;
3953
+ }
3954
+ return -1;
3955
+ }
3956
+ function findFnEnd(lines, start) {
3957
+ let depth = 0;
3958
+ for (let idx = start; idx < lines.length; idx++) {
3959
+ depth += (lines[idx].match(/\{/g) || []).length - (lines[idx].match(/\}/g) || []).length;
3960
+ if (depth <= 0 && idx > start) return idx;
3961
+ }
3962
+ return -1;
3963
+ }
3964
+ function stripComments(content) {
3965
+ return content.split("\n").filter((l) => !l.trimStart().startsWith("#")).join("\n");
3966
+ }
3967
+ function parseCaseStmt(block) {
3968
+ const header = block.match(/\s*case\s+(\S[^;]*?)\s*\bin\b[\s\S]*/);
3969
+ if (!header) return null;
3970
+ const sel = header[1].trim();
3971
+ const lines = block.split("\n");
3972
+ let headerLine = 0;
3973
+ for (let i = 0; i < lines.length; i++) {
3974
+ if (/\bin\b/.test(lines[i]) && /\bcase\b/.test(lines[i])) {
3975
+ headerLine = i;
3976
+ break;
3977
+ }
3978
+ }
3979
+ const bodyStartLine = headerLine + 1;
3980
+ const endLine = findCaseEnd(lines, 0);
3981
+ if (endLine === -1) return null;
3982
+ const branches = splitCaseBranchesDepthAware(lines.slice(bodyStartLine, endLine));
3983
+ if (!branches.length) return null;
3984
+ return { kind: "case", line: block, modelable: true, selector: sel, branches };
3985
+ }
3986
+ function splitCaseBranchesDepthAware(bodyLines) {
3987
+ let depth = 0;
3988
+ let curPat = null;
3989
+ let curBody = [];
3990
+ const branches = [];
3991
+ const flush = () => {
3992
+ if (curPat !== null) {
3993
+ branches.push({ pat: curPat, body: curBody.join("\n") });
3994
+ }
3995
+ curPat = null;
3996
+ curBody = [];
3997
+ };
3998
+ for (const ln of bodyLines) {
3999
+ const stripped = ln.trim();
4000
+ if (depth === 0) {
4001
+ const bm = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
4002
+ if (bm) {
4003
+ if (curPat !== null) flush();
4004
+ curPat = bm[1].trim().replace(/^["']|["']$/g, "");
4005
+ curBody = bm[2].trim() ? [bm[2].trim()] : [];
4006
+ continue;
4007
+ }
4008
+ }
4009
+ if (/\bcase\b/.test(stripped)) depth += 1;
4010
+ else if (/\besac\b/.test(stripped)) depth = Math.max(0, depth - 1);
4011
+ else if (curPat !== null) {
4012
+ curBody.push(ln);
4013
+ if (depth === 0 && stripped.endsWith(";;")) flush();
4014
+ }
4015
+ }
4016
+ if (curPat !== null && curBody.length) flush();
4017
+ return branches;
4018
+ }
4019
+ function parseIfStmt(block) {
4020
+ const m = block.match(/\s*if\s+([^\n;]+?)\s*;\s*then/);
4021
+ if (!m) return null;
4022
+ const cond = m[1].trim();
4023
+ const lines = block.split("\n");
4024
+ const fiLine = findIfEnd(lines, 0);
4025
+ if (fiLine === -1) return null;
4026
+ const body = lines.slice(1, fiLine).join("\n");
4027
+ const elseM = /\belse\b/.exec(body);
4028
+ let thenBody;
4029
+ let elseBody;
4030
+ if (elseM) {
4031
+ thenBody = body.slice(0, elseM.index);
4032
+ elseBody = body.slice(elseM.index + 4);
4033
+ } else {
4034
+ thenBody = body;
4035
+ elseBody = null;
4036
+ }
4037
+ return { kind: "if", line: block, modelable: true, cond, thenBody, elseBody };
4038
+ }
4039
+ function tokenize(content) {
4040
+ const lines = stripComments(content).split("\n");
4041
+ const stmts = [];
4042
+ let i = 0;
4043
+ while (i < lines.length) {
4044
+ const ln = lines[i];
4045
+ const stripped = ln.trim();
4046
+ if (!stripped) {
4047
+ i++;
4048
+ continue;
4049
+ }
4050
+ if (/\b(?:while|for|until)\b/.test(stripped) && !CASE_START_RE.test(stripped) && !IF_START_RE.test(stripped)) {
4051
+ const endLine = findLoopEnd(lines, i);
4052
+ if (endLine !== -1) {
4053
+ for (let k = i; k <= endLine; k++) stmts.push({ kind: "plain", line: lines[k], modelable: false });
4054
+ i = endLine + 1;
4055
+ continue;
4056
+ }
4057
+ stmts.push({ kind: "plain", line: ln, modelable: false });
4058
+ i++;
4059
+ continue;
4060
+ }
4061
+ if (FN_DEF_RE.test(stripped) || FN_DEF_KW_RE.test(stripped)) {
4062
+ const endLine = findFnEnd(lines, i);
4063
+ if (endLine !== -1) {
4064
+ for (let k = i; k <= endLine; k++) stmts.push({ kind: "plain", line: lines[k], modelable: false });
4065
+ i = endLine + 1;
4066
+ continue;
4067
+ }
4068
+ stmts.push({ kind: "plain", line: ln, modelable: false });
4069
+ i++;
4070
+ continue;
4071
+ }
4072
+ if (CASE_START_RE.test(stripped)) {
4073
+ const endLine = findCaseEnd(lines, i);
4074
+ if (endLine === -1) {
4075
+ stmts.push({ kind: "plain", line: ln, modelable: false });
4076
+ i++;
4077
+ continue;
4078
+ }
4079
+ const parsed = parseCaseStmt(lines.slice(i, endLine + 1).join("\n"));
4080
+ stmts.push(parsed || { kind: "plain", line: ln, modelable: false });
4081
+ i = endLine + 1;
4082
+ continue;
4083
+ }
4084
+ if (IF_START_RE.test(stripped)) {
4085
+ const endLine = findIfEnd(lines, i);
4086
+ if (endLine === -1) {
4087
+ stmts.push({ kind: "plain", line: ln, modelable: false });
4088
+ i++;
4089
+ continue;
4090
+ }
4091
+ const parsed = parseIfStmt(lines.slice(i, endLine + 1).join("\n"));
4092
+ stmts.push(parsed || { kind: "plain", line: ln, modelable: false });
4093
+ i = endLine + 1;
4094
+ continue;
4095
+ }
4096
+ if (/\bshift\b|\bset\s+--(?:\s|$)|[\s;|&]read\b|^\s*read\b/.test(stripped)) {
4097
+ stmts.push({ kind: "plain", line: ln, modelable: false });
4098
+ i++;
4099
+ continue;
4100
+ }
4101
+ const am = stripped.match(ASGN_RE);
4102
+ if (am && !NOT_MODELED_RE.test(stripped)) {
4103
+ stmts.push({ kind: "assign", line: stripped, modelable: true, name: am[1], rhs: am[2].trim() });
4104
+ i++;
4105
+ continue;
4106
+ }
4107
+ stmts.push(NOT_MODELED_RE.test(stripped) ? { kind: "plain", line: ln, modelable: false } : { kind: "plain", line: ln, modelable: true });
4108
+ i++;
4109
+ }
4110
+ return stmts;
4111
+ }
4112
+ function evalExpr2(text, env) {
4113
+ const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
4114
+ if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
4115
+ text = text.slice(1, -1);
4116
+ }
4117
+ if (isSingleQuoted) return svalConc(text);
4118
+ const parts = text.split(EXPR_SPLIT_RE);
4119
+ const result = [];
4120
+ for (const p of parts) {
4121
+ if (!p) continue;
4122
+ const brace = p.match(/^\$\{([^}]+)\}$/);
4123
+ if (brace) {
4124
+ const inner = brace[1];
4125
+ if (/^\d+$/.test(inner)) {
4126
+ const v = env.get(`$${inner}`);
4127
+ result.push(v || svalSym(`$${inner}`));
4128
+ } else {
4129
+ const v = env.get(inner);
4130
+ result.push(v || svalSym(inner));
4131
+ }
4132
+ continue;
4133
+ }
4134
+ const bare = p.match(/^\$([A-Za-z_][A-Za-z0-9_]*|\d+)$/);
4135
+ if (bare) {
4136
+ const varName = bare[1];
4137
+ const key = /^\d+$/.test(varName) ? `$${varName}` : varName;
4138
+ const v = env.get(key);
4139
+ result.push(v || svalSym(p));
4140
+ continue;
4141
+ }
4142
+ result.push(svalConc(p));
4143
+ }
4144
+ if (result.length === 1) return fold(result[0]);
4145
+ return fold(svalExpr(result));
4146
+ }
4147
+ function initEnv(entryArgs) {
4148
+ const env = /* @__PURE__ */ new Map();
4149
+ if (entryArgs && entryArgs.length) {
4150
+ entryArgs.forEach((a, idx) => env.set(`$${idx + 1}`, svalConc(a)));
4151
+ env.set("$@", svalConc(entryArgs.join(" ")));
4152
+ env.set("$*", svalConc(entryArgs.join(" ")));
4153
+ }
4154
+ return env;
4155
+ }
4156
+ function globToRegex(pattern) {
4157
+ let out = "^";
4158
+ let i = 0;
4159
+ while (i < pattern.length) {
4160
+ const c = pattern[i];
4161
+ if (c === "*") out += ".*";
4162
+ else if (c === "?") out += ".";
4163
+ else if (c === "[") {
4164
+ let j = i + 1;
4165
+ let neg = false;
4166
+ if (j < pattern.length && (pattern[j] === "!" || pattern[j] === "^")) {
4167
+ neg = true;
4168
+ j++;
4169
+ }
4170
+ let cls = "";
4171
+ while (j < pattern.length && pattern[j] !== "]") {
4172
+ cls += pattern[j];
4173
+ j++;
4174
+ }
4175
+ if (j >= pattern.length) {
4176
+ out += reescape(pattern.slice(i));
4177
+ break;
4178
+ }
4179
+ out += "[" + (neg ? "^" : "") + reescape(cls) + "]";
4180
+ i = j;
4181
+ } else {
4182
+ out += reescape(c);
4183
+ }
4184
+ i++;
4185
+ }
4186
+ out += "$";
4187
+ return new RegExp(out, RE_DOTALL);
4188
+ }
4189
+ function matchGlob(pattern, value) {
4190
+ if (!HAS_GLOB.test(pattern)) return pattern === value;
4191
+ return globToRegex(pattern).test(value);
4192
+ }
4193
+ function evalNumericCompare(op, left, right) {
4194
+ const li = parseInt(left, 10);
4195
+ const ri = parseInt(right, 10);
4196
+ if (isNaN(li) || isNaN(ri)) return null;
4197
+ switch (op) {
4198
+ case "-gt":
4199
+ return li > ri;
4200
+ case "-lt":
4201
+ return li < ri;
4202
+ case "-eq":
4203
+ return li === ri;
4204
+ case "-ge":
4205
+ return li >= ri;
4206
+ case "-le":
4207
+ return li <= ri;
4208
+ default:
4209
+ return null;
4210
+ }
4211
+ }
4212
+ function resolveVarSide(tok, env) {
4213
+ if (!tok.startsWith("$")) return tok;
4214
+ const ev = evalExpr2(tok, env);
4215
+ return ev.kind === "concrete" ? ev.value : null;
4216
+ }
4217
+ function evalCondition2(cond, env) {
4218
+ let inner = cond.trim().replace(/^\[\[?|\]\]?$/g, "").trim();
4219
+ if (/^-[A-Za-z]\s/.test(inner) || inner.startsWith("-d") || inner.startsWith("-f")) return null;
4220
+ const innerQ = inner.replace(/"/g, "").replace(/'/g, "");
4221
+ for (const op2 of ["-gt", "-lt", "-eq", "-ge", "-le"]) {
4222
+ const re = new RegExp(`\\s${op2}\\s`);
4223
+ const opm = re.exec(innerQ);
4224
+ if (!opm) continue;
4225
+ const left2 = innerQ.slice(0, opm.index).trim();
4226
+ const right2 = innerQ.slice(opm.index + opm[0].length).trim();
4227
+ const lv2 = resolveVarSide(left2, env);
4228
+ const rv2 = resolveVarSide(right2, env);
4229
+ if (lv2 === null || rv2 === null) return null;
4230
+ return evalNumericCompare(op2, lv2, rv2);
4231
+ }
4232
+ const sm = innerQ.match(/(.*?)\s*(==|=|!=|=~)\s*(.*)/);
4233
+ if (!sm) return null;
4234
+ const op = sm[2];
4235
+ let left = sm[1].trim();
4236
+ let right = sm[3].trim();
4237
+ if (op === "=~") {
4238
+ if (right.startsWith("/") && right.endsWith("/")) right = right.slice(1, -1);
4239
+ const lv2 = resolveVarSide(left, env);
4240
+ if (lv2 === null) return null;
4241
+ try {
4242
+ return new RegExp(right).test(lv2);
4243
+ } catch {
4244
+ return null;
4245
+ }
4246
+ }
4247
+ const lv = resolveVarSide(left, env);
4248
+ const rv = resolveVarSide(right, env);
4249
+ if (lv === null || rv === null) return null;
4250
+ if (op === "!=") return lv !== rv;
4251
+ return lv === rv;
4252
+ }
4253
+ function symbolicProcess(content, entryArgs) {
4254
+ if (!content) return null;
4255
+ const stmts = tokenize(content);
4256
+ if (!stmts.some((s) => s.kind === "case" || s.kind === "if" || s.kind === "assign")) return null;
4257
+ if (stmts.some((s) => !s.modelable)) return null;
4258
+ const env = initEnv(entryArgs);
4259
+ let linesBudget = 0;
4260
+ let branchesBudget = 0;
4261
+ let modelable = false;
4262
+ const out = [];
4263
+ for (const s of stmts) {
4264
+ linesBudget++;
4265
+ if (linesBudget > MAX_LINES2) return null;
4266
+ if (s.kind === "assign") {
4267
+ modelable = true;
4268
+ if (env.size >= MAX_VARS) return null;
4269
+ const v = evalExpr2(s.rhs, env);
4270
+ env.set(s.name, fold(v));
4271
+ out.push(s.line);
4272
+ continue;
4273
+ }
4274
+ if (s.kind === "case") {
4275
+ modelable = true;
4276
+ branchesBudget += s.branches.length;
4277
+ if (branchesBudget > MAX_BRANCHES) return null;
4278
+ const selVal = fold(evalExpr2(s.selector, env));
4279
+ if (selVal.kind !== "concrete") return null;
4280
+ const concrete = selVal.value || "";
4281
+ let selected = null;
4282
+ let fallback = "";
4283
+ for (const b of s.branches) {
4284
+ const patStripped = b.pat.trim();
4285
+ if (patStripped === "*") {
4286
+ fallback = b.body;
4287
+ continue;
4288
+ }
4289
+ if (matchGlob(patStripped, concrete)) {
4290
+ selected = b.body;
4291
+ break;
4292
+ }
4293
+ }
4294
+ const final = selected !== null ? selected : fallback || null;
4295
+ if (final === null) return null;
4296
+ out.push(`case ${s.selector} in
4297
+ ${final}
4298
+ esac`);
4299
+ continue;
4300
+ }
4301
+ if (s.kind === "if") {
4302
+ modelable = true;
4303
+ branchesBudget++;
4304
+ if (branchesBudget > MAX_BRANCHES) return null;
4305
+ const truth = evalCondition2(s.cond, env);
4306
+ if (truth === null) return null;
4307
+ const chosen = truth ? s.thenBody : s.elseBody;
4308
+ if (chosen === null) return null;
4309
+ out.push(`if ${s.cond}; then
4310
+ ${chosen}
4311
+ fi`);
4312
+ continue;
4313
+ }
4314
+ out.push(s.line);
4315
+ }
4316
+ if (!modelable) return null;
4317
+ return out.join("\n");
4318
+ }
4319
+ var RE_DOTALL, MAX_LINES2, MAX_BRANCHES, MAX_VARS, ASGN_RE, CASE_START_RE, IF_START_RE, NOT_MODELED_RE, CASE_OPEN_RE, CASE_CLOSE_RE, IF_OPEN_RE, IF_CLOSE_RE, LOOP_DO_RE, LOOP_DONE_RE, FN_DEF_RE, FN_DEF_KW_RE, EXPR_SPLIT_RE, HAS_GLOB;
4320
+ var init_symbolic = __esm({
4321
+ "src/engine/script-engine/symbolic.ts"() {
4322
+ "use strict";
4323
+ init_esm_shims();
4324
+ RE_DOTALL = "s";
4325
+ MAX_LINES2 = 500;
4326
+ MAX_BRANCHES = 50;
4327
+ MAX_VARS = 200;
4328
+ ASGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
4329
+ CASE_START_RE = /^\s*case\s+/;
4330
+ IF_START_RE = /^\s*if\s+/;
4331
+ NOT_MODELED_RE = /\$\(|`[^`]*`/;
4332
+ CASE_OPEN_RE = /\bcase\b/;
4333
+ CASE_CLOSE_RE = /\besac\b/;
4334
+ IF_OPEN_RE = /\bif\b/;
4335
+ IF_CLOSE_RE = /\bfi\b/;
4336
+ LOOP_DO_RE = /\bdo\b/g;
4337
+ LOOP_DONE_RE = /\bdone\b/g;
4338
+ FN_DEF_RE = /^\s*(?:function\s+)?[A-Za-z_][A-Za-z0-9_]*\s*\(\s*\)\s*\{/;
4339
+ FN_DEF_KW_RE = /^\s*function\s+[A-Za-z_][A-Za-z0-9_]*\s*\{?/;
4340
+ EXPR_SPLIT_RE = /(\$\{?\d+\}?|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)/;
4341
+ HAS_GLOB = /[*?[]/;
4342
+ }
4343
+ });
4344
+
4345
+ // src/engine/script-engine/backends/shell-backend.ts
4346
+ function bindPositionalArgs(content, args) {
4347
+ if (!args.length) return content;
4348
+ const allArgs = args.join(" ");
4349
+ content = content.replace(/\$\{(\d+|@|\*)(?::?[-]?([^}]*))?\}/g, (full, kind, defv) => {
4350
+ if (kind === "@" || kind === "*") return allArgs;
4351
+ if (!/^\d+$/.test(kind)) return full;
4352
+ const idx = parseInt(kind, 10);
4353
+ if (1 <= idx && idx <= args.length) return args[idx - 1];
4354
+ return defv !== void 0 ? defv : "";
4355
+ });
4356
+ content = content.replace(/\$\{(\d+)\}/g, (full, kind) => {
4357
+ const idx = parseInt(kind, 10);
4358
+ if (1 <= idx && idx <= args.length) return args[idx - 1];
4359
+ return "";
4360
+ });
4361
+ content = content.replace(/\$(\d+)/g, (full, kind) => {
4362
+ const idx = parseInt(kind, 10);
4363
+ if (1 <= idx && idx <= args.length) return args[idx - 1];
4364
+ return "";
4365
+ });
4366
+ content = content.replace(/\$[@*]/g, allArgs);
4367
+ return content;
4368
+ }
4369
+ function findSelectorValue(selector, args) {
4370
+ if (!args || !args.length) return null;
4371
+ const m = selector.match(/^\$\{?(\d+)\}?$/);
4372
+ if (m) {
4373
+ const idx = parseInt(m[1], 10);
4374
+ if (1 <= idx && idx <= args.length) return args[idx - 1];
4375
+ return null;
4376
+ }
4377
+ return null;
4378
+ }
4379
+ function splitCaseBody(body) {
4380
+ const lines = body.split("\n");
4381
+ const branches = [];
4382
+ let cur = null;
4383
+ for (const ln of lines) {
4384
+ const stripped = ln.trim();
4385
+ if (/^[\w:-]+\s*\(\)\s*\{/.test(stripped)) continue;
4386
+ const m = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
4387
+ if (m) {
4388
+ const pat = m[1].trim().replace(/^["']|["']$/g, "");
4389
+ const inline = m[2].trim();
4390
+ if (pat) {
4391
+ if (cur) branches.push(cur);
4392
+ cur = { label: pat, body: inline };
4393
+ }
4394
+ continue;
4395
+ }
4396
+ if (cur) {
4397
+ cur.body += cur.body ? "\n" + ln : ln;
4398
+ if (stripped.endsWith(";;")) {
4399
+ branches.push(cur);
4400
+ cur = null;
4401
+ }
4402
+ }
4403
+ }
4404
+ if (cur && cur.body.trim()) {
4405
+ if (!branches.includes(cur)) branches.push(cur);
4406
+ }
4407
+ return branches;
4408
+ }
4409
+ function parseCase(content, args) {
4410
+ const m = content.match(/\bcase\s+(\$\{?\d+\}?|[A-Za-z_]\w*|\$[A-Za-z_]\w*)\s+in/i);
4411
+ if (!m) return null;
4412
+ const selector = m[1].trim();
4413
+ const val = findSelectorValue(selector, args);
4414
+ if (val === null) return null;
4415
+ const lines = content.split("\n");
4416
+ const endMatch = findCaseEndInLines(lines, 0);
4417
+ if (endMatch === -1) return null;
4418
+ const bodyLines = lines.slice(1, endMatch);
4419
+ const body = bodyLines.join("\n");
4420
+ const hasNested = /\bcase\s+/.test(body) || /\bif\s+/.test(body);
4421
+ let final;
4422
+ if (hasNested) {
4423
+ final = splitNestedCaseBranches(bodyLines, val);
4424
+ if (final === null) return null;
4425
+ } else {
4426
+ const branches = splitCaseBody(body);
4427
+ let selected = null;
4428
+ let fallback = "";
4429
+ for (const b of branches) {
4430
+ const values = b.label.split("|").map((v) => v.trim()).filter(Boolean);
4431
+ if (values.includes("*")) fallback = b.body;
4432
+ else if (values.includes(val)) selected = b.body;
4433
+ }
4434
+ final = selected !== null ? selected : fallback || null;
4435
+ if (final === null) return null;
4436
+ }
4437
+ const prefix = content.slice(0, m.index ?? 0);
4438
+ const suffix = lines.slice(endMatch + 1).join("\n");
4439
+ return `${prefix}
4440
+ ${final}
4441
+ ${suffix}`;
4442
+ }
4443
+ function findCaseEndInLines(lines, headerLine) {
4444
+ let depth = 0;
4445
+ for (let idx = headerLine; idx < lines.length; idx++) {
4446
+ const ln = lines[idx];
4447
+ const opens = (ln.match(/\bcase\b/g) || []).length;
4448
+ const closes = (ln.match(/\besac\b/g) || []).length;
4449
+ depth += opens - closes;
4450
+ if (depth <= 0 && idx > headerLine) return idx;
4451
+ }
4452
+ return -1;
4453
+ }
4454
+ function splitNestedCaseBranches(bodyLines, selectorVal) {
4455
+ let depth = 0;
4456
+ let curPat = null;
4457
+ let curBody = [];
4458
+ let selected = null;
4459
+ let fallback = null;
4460
+ const flush = () => {
4461
+ if (curPat === null) return;
4462
+ const values = curPat.split("|").map((v) => v.trim()).filter(Boolean);
4463
+ const body = curBody.join("\n");
4464
+ if (values.includes("*")) {
4465
+ if (fallback === null) fallback = body;
4466
+ } else if (values.includes(selectorVal)) {
4467
+ selected = body;
4468
+ }
4469
+ curPat = null;
4470
+ curBody = [];
4471
+ };
4472
+ for (const ln of bodyLines) {
4473
+ const stripped = ln.trim();
4474
+ if (depth === 0) {
4475
+ const bm = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
4476
+ if (bm) {
4477
+ if (curPat !== null) flush();
4478
+ curPat = bm[1].trim().replace(/^["']|["']$/g, "");
4479
+ curBody = bm[2].trim() ? [bm[2].trim()] : [];
4480
+ continue;
4481
+ }
4482
+ }
4483
+ if (/\bcase\b/.test(stripped)) depth += 1;
4484
+ else if (/\besac\b/.test(stripped)) depth = Math.max(0, depth - 1);
4485
+ else if (curPat !== null) {
4486
+ curBody.push(ln);
4487
+ if (depth === 0 && stripped.endsWith(";;")) flush();
4488
+ }
4489
+ }
4490
+ if (curPat !== null && curBody.length) flush();
4491
+ return selected !== null ? selected : fallback;
4492
+ }
4493
+ function evalIfCondition(cond, args) {
4494
+ if (!args || !args.length) return null;
4495
+ let inner = cond.replace(/^\[\[?|\]\]?$/g, "").replace(/"/g, "").replace(/'/g, "").trim();
4496
+ const m = inner.match(/(.*?)\s*(==|=)\s*(.*)/);
4497
+ if (!m) return null;
4498
+ let left = m[1].trim();
4499
+ let right = m[3].trim();
4500
+ const lm = left.match(/^\$(\d+)$/);
4501
+ if (lm) {
4502
+ const idx = parseInt(lm[1], 10);
4503
+ if (1 <= idx && idx <= args.length) left = args[idx - 1];
4504
+ else return null;
4505
+ } else if (left.startsWith("$")) return null;
4506
+ const rm = right.match(/^\$(\d+)$/);
4507
+ if (rm) {
4508
+ const idx = parseInt(rm[1], 10);
4509
+ if (1 <= idx && idx <= args.length) right = args[idx - 1];
4510
+ else return null;
4511
+ } else if (right.startsWith("$")) return null;
4512
+ return left === right;
4513
+ }
4514
+ function parseIf(content, args) {
4515
+ const m = content.match(/\bif\s+([^\n;]+?)\s*;\s*then/i);
4516
+ if (!m) return null;
4517
+ const truth = evalIfCondition(m[1].trim(), args);
4518
+ if (truth === null) return null;
4519
+ const start = m.index ?? 0;
4520
+ const thenBodyStart = start + m[0].length;
4521
+ const fiPos = content.indexOf("fi", thenBodyStart);
4522
+ if (fiPos === -1) return null;
4523
+ const segment = content.slice(thenBodyStart, fiPos);
4524
+ const elseM = segment.match(/\belse\b/);
4525
+ let chosen;
4526
+ if (truth) chosen = elseM ? segment.slice(0, elseM.index) : segment;
4527
+ else {
4528
+ if (!elseM) return null;
4529
+ chosen = segment.slice((elseM.index ?? 0) + 4);
4530
+ }
4531
+ return `${content.slice(0, start)}
4532
+ ${chosen}
4533
+ ${content.slice(fiPos + 2)}`;
4534
+ }
4535
+ function markLoopsReachable(content) {
4536
+ return content;
4537
+ }
4538
+ function extractCallsFromContent(content) {
4539
+ const calls = [];
4540
+ let m;
4541
+ CHILD_CALL_RE2.lastIndex = 0;
4542
+ while ((m = CHILD_CALL_RE2.exec(content)) !== null) {
4543
+ const interp = m[1];
4544
+ const path6 = m[2].trim();
4545
+ let rest = m[3].trim();
4546
+ if (path6.startsWith("-") || path6.startsWith("$") || path6.includes("/dev/")) continue;
4547
+ rest = rest.split(/[;|&<>]/)[0].trim();
4548
+ const args = [];
4549
+ for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
4550
+ args.push(tm[1] ? tm[2] : tm[3]);
4551
+ }
4552
+ calls.push({ path: path6, args, interpreter: interp });
4553
+ }
4554
+ return calls;
4555
+ }
4556
+ var CHILD_CALL_RE2, ShellBackend;
4557
+ var init_shell_backend = __esm({
4558
+ "src/engine/script-engine/backends/shell-backend.ts"() {
4559
+ "use strict";
4560
+ init_esm_shims();
4561
+ init_symbolic();
4562
+ CHILD_CALL_RE2 = /(?:^|\s|&&|\|\|)\s*((?:\/bin\/)?bash|sh|zsh|(?:python|python3|node|ruby|perl|php|lua))\s+(\S+(?:\.(?:sh|shrc|py|js|rb|pl|php|lua))?)([^\n;|&]*?)$/gmi;
4563
+ ShellBackend = class {
4564
+ language = "shell";
4565
+ parse(content, entryArgs) {
4566
+ const c = content || "";
4567
+ const orig = c;
4568
+ let candidate = c;
4569
+ let resolved = false;
4570
+ const p1Safe = !/(?:\bshift\b|\bset\s+--(?:\s|$)|[\s;|&]read\b|^\s*read\b|\(\)\s*\{|function\s+\w+)/.test(c);
4571
+ if (p1Safe) {
4572
+ const caseOut = parseCase(candidate, entryArgs);
4573
+ if (caseOut !== null && caseOut !== candidate) {
4574
+ resolved = true;
4575
+ candidate = caseOut;
4576
+ }
4577
+ const ifOut = parseIf(candidate, entryArgs);
4578
+ if (ifOut !== null && ifOut !== candidate) {
4579
+ resolved = true;
4580
+ candidate = ifOut;
4581
+ }
4582
+ }
4583
+ let bound = p1Safe ? bindPositionalArgs(candidate, entryArgs || []) : candidate;
4584
+ if (!resolved) {
4585
+ const symOut = symbolicProcess(c, entryArgs);
4586
+ if (symOut !== null && symOut !== c) {
4587
+ resolved = true;
4588
+ bound = symOut;
4589
+ }
4590
+ }
4591
+ const loopMarked = markLoopsReachable(bound);
4592
+ const calls = extractCallsFromContent(loopMarked);
4593
+ return {
4594
+ reachableContent: loopMarked,
4595
+ controlFlowResolved: resolved,
4596
+ calls,
4597
+ branches: [],
4598
+ unresolvedSnippets: [],
4599
+ language: this.language
4600
+ };
4601
+ }
4602
+ };
4603
+ }
4604
+ });
4605
+
4606
+ // src/engine/script-engine/backends/index.ts
4607
+ var init_backends = __esm({
4608
+ "src/engine/script-engine/backends/index.ts"() {
4609
+ "use strict";
4610
+ init_esm_shims();
4611
+ init_shell_backend();
4612
+ init_shell_backend();
4613
+ init_python_backend();
4614
+ }
4615
+ });
4616
+
4617
+ // src/engine/script-engine/tracker.ts
4618
+ import * as fs3 from "fs";
4619
+ import * as path3 from "path";
4620
+ import * as os from "os";
4621
+ function pickBackend(pathStr) {
4622
+ const lower = pathStr.toLowerCase();
4623
+ for (const ext of Object.keys(BACKENDS)) {
4624
+ if (lower.endsWith(ext)) return BACKENDS[ext];
4625
+ }
4626
+ return DEFAULT_BACKEND;
4627
+ }
4628
+ function resolvePath(p, baseDir) {
4629
+ let expanded = p;
4630
+ if (expanded.startsWith("~/") || expanded === "~") {
4631
+ expanded = path3.join(os.homedir(), expanded.slice(1));
4632
+ } else if (!path3.isAbsolute(expanded) && baseDir) {
4633
+ expanded = path3.join(baseDir, expanded);
4634
+ }
4635
+ return path3.resolve(expanded);
4636
+ }
4637
+ function readScript(pathStr, maxFileSize) {
4638
+ try {
4639
+ const p = resolvePath(pathStr);
4640
+ if (!fs3.existsSync(p)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${pathStr}` };
4641
+ const stat = fs3.statSync(p);
4642
+ if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${pathStr}` };
4643
+ if (stat.size > maxFileSize) {
4644
+ return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
4645
+ }
4646
+ const content = fs3.readFileSync(p, "utf-8");
4647
+ return { content, lineCount: content ? content.split("\n").length : 0, fileSize: stat.size, error: "" };
3193
4648
  } catch (e) {
3194
4649
  const msg = e instanceof Error ? e.message : String(e);
3195
4650
  return { content: "", lineCount: 0, fileSize: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
3196
4651
  }
3197
4652
  }
3198
- function extractChildScriptPaths(content, baseDir) {
3199
- const paths = [];
3200
- for (const pattern of CHILD_SCRIPT_PATTERNS) {
3201
- pattern.lastIndex = 0;
3202
- let m;
3203
- while ((m = pattern.exec(content)) !== null) {
3204
- const pathStr = m[1];
3205
- if (pathStr.startsWith("-") || pathStr.startsWith("$")) continue;
3206
- if (pathStr.includes("/dev/") || pathStr.startsWith("|") || pathStr.startsWith(";")) continue;
3207
- const full = isAbsolute(pathStr) ? pathStr : join4(baseDir, pathStr);
3208
- const resolved = resolvePath(full);
3209
- if (existsSync7(resolved)) {
3210
- try {
3211
- if (statSync(resolved).isFile() && !paths.includes(resolved)) paths.push(resolved);
3212
- } catch {
3213
- }
4653
+ function emptyNode(pathStr, depth) {
4654
+ return {
4655
+ scriptPath: pathStr,
4656
+ rawContent: "",
4657
+ preprocessedContent: "",
4658
+ lineCount: 0,
4659
+ fileSize: 0,
4660
+ depth,
4661
+ childPaths: [],
4662
+ error: "",
4663
+ l1Hit: false,
4664
+ l1RiskLevel: "",
4665
+ l1RiskType: "",
4666
+ l1Reason: "",
4667
+ l1MatchedPatterns: [],
4668
+ l2Hit: false,
4669
+ l2ViolatedRule: "",
4670
+ l2RiskLevel: "",
4671
+ l2RiskType: "",
4672
+ l2Reason: "",
4673
+ matchedKeywords: [],
4674
+ isSuspicious: false,
4675
+ keywordRiskLevel: "",
4676
+ keywordRiskReason: ""
4677
+ };
4678
+ }
4679
+ var DEFAULT_MAX_DEPTH, DEFAULT_MAX_NODES, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_TOTAL_SIZE, BACKENDS, DEFAULT_BACKEND, RecursiveTracker;
4680
+ var init_tracker = __esm({
4681
+ "src/engine/script-engine/tracker.ts"() {
4682
+ "use strict";
4683
+ init_esm_shims();
4684
+ init_python_backend();
4685
+ init_backends();
4686
+ DEFAULT_MAX_DEPTH = 14;
4687
+ DEFAULT_MAX_NODES = 64;
4688
+ DEFAULT_MAX_FILE_SIZE = 2 * 1024 * 1024;
4689
+ DEFAULT_MAX_TOTAL_SIZE = 8 * 1024 * 1024;
4690
+ BACKENDS = {
4691
+ ".py": new PythonBackend()
4692
+ };
4693
+ DEFAULT_BACKEND = new ShellBackend();
4694
+ RecursiveTracker = class {
4695
+ constructor(commandScanner, maxDepth = DEFAULT_MAX_DEPTH, maxNodes = DEFAULT_MAX_NODES, maxFileSize = DEFAULT_MAX_FILE_SIZE, maxTotalSize = DEFAULT_MAX_TOTAL_SIZE) {
4696
+ this.maxDepth = maxDepth;
4697
+ this.maxNodes = maxNodes;
4698
+ this.maxFileSize = maxFileSize;
4699
+ this.maxTotalSize = maxTotalSize;
4700
+ this._scanner = commandScanner;
4701
+ this.result = {
4702
+ nodes: [],
4703
+ entryPath: "",
4704
+ maxDepthReached: false,
4705
+ maxSizeReached: false,
4706
+ maxNodesReached: false,
4707
+ totalSize: 0,
4708
+ totalLines: 0,
4709
+ totalNodes: 0,
4710
+ hasCritical: false,
4711
+ hasHigh: false,
4712
+ firstHitNode: null,
4713
+ error: ""
4714
+ };
4715
+ }
4716
+ maxDepth;
4717
+ maxNodes;
4718
+ maxFileSize;
4719
+ maxTotalSize;
4720
+ result;
4721
+ visiting = /* @__PURE__ */ new Set();
4722
+ visitedPaths = /* @__PURE__ */ new Set();
4723
+ _scanner;
4724
+ track(entryPath, entryArgs, cwd = "") {
4725
+ const baseDir = cwd || process.cwd();
4726
+ const resolved = resolvePath(entryPath);
4727
+ this.result.entryPath = resolved;
4728
+ this.visitScript(resolved, 0, path3.dirname(resolved), entryArgs);
4729
+ const nodes = this.result.nodes;
4730
+ this.result.hasCritical = nodes.some((n) => n.l1RiskLevel === "critical" || n.l2RiskLevel === "critical" || n.keywordRiskLevel === "critical") || this.result.hasCritical;
4731
+ this.result.hasHigh = nodes.some((n) => ["high", "critical"].includes(n.l1RiskLevel) || ["high", "critical"].includes(n.l2RiskLevel) || ["high", "critical"].includes(n.keywordRiskLevel));
4732
+ this.result.firstHitNode = nodes.find((n) => n.l1Hit || n.l2Hit || n.isSuspicious) || null;
4733
+ return this.result;
4734
+ }
4735
+ visitScript(pathStr, depth, baseDir, args) {
4736
+ pathStr = resolvePath(pathStr);
4737
+ if (this.visiting.has(pathStr)) return;
4738
+ if (this.visitedPaths.has(pathStr)) return;
4739
+ if (depth > this.maxDepth) {
4740
+ this.result.maxDepthReached = true;
4741
+ return;
4742
+ }
4743
+ if (this.result.nodes.length >= this.maxNodes) {
4744
+ this.result.maxNodesReached = true;
4745
+ return;
4746
+ }
4747
+ if (this.result.totalSize >= this.maxTotalSize) {
4748
+ this.result.maxSizeReached = true;
4749
+ return;
4750
+ }
4751
+ this.visiting.add(pathStr);
4752
+ this.visitedPaths.add(pathStr);
4753
+ const { content, lineCount, fileSize, error } = readScript(pathStr, this.maxFileSize);
4754
+ if (error) {
4755
+ const node2 = emptyNode(pathStr, depth);
4756
+ node2.error = error;
4757
+ node2.fileSize = fileSize;
4758
+ node2.lineCount = lineCount;
4759
+ this.result.nodes.push(node2);
4760
+ this.visiting.delete(pathStr);
4761
+ return;
4762
+ }
4763
+ this.result.totalSize += fileSize;
4764
+ this.result.totalLines += lineCount;
4765
+ const ir = pickBackend(pathStr).parse(content, args);
4766
+ const node = emptyNode(pathStr, depth);
4767
+ node.rawContent = content;
4768
+ node.preprocessedContent = ir.reachableContent;
4769
+ node.lineCount = lineCount;
4770
+ node.fileSize = fileSize;
4771
+ this.result.nodes.push(node);
4772
+ this.result.totalNodes = this.result.nodes.length;
4773
+ if (this._scanner && ir.reachableContent) {
4774
+ const l1 = this._scanner(ir.reachableContent);
4775
+ if (l1.hit) {
4776
+ node.l1Hit = true;
4777
+ node.l1RiskLevel = l1.riskLevel;
4778
+ node.l1RiskType = l1.riskType;
4779
+ node.l1Reason = l1.reason;
4780
+ node.l1MatchedPatterns = l1.matchedPatterns || [];
4781
+ if (l1.riskLevel === "critical") {
4782
+ this.result.hasCritical = true;
4783
+ this.result.firstHitNode = node;
4784
+ this.visiting.delete(pathStr);
4785
+ return;
4786
+ }
4787
+ }
4788
+ }
4789
+ const parentDir = path3.dirname(pathStr);
4790
+ const childPaths = [];
4791
+ for (const call of ir.calls) {
4792
+ const child = resolvePath(call.path, parentDir);
4793
+ childPaths.push(child);
4794
+ this.visitScript(child, depth + 1, path3.dirname(child), call.args);
4795
+ if (this.result.hasCritical) break;
4796
+ }
4797
+ node.childPaths = childPaths;
4798
+ this.visiting.delete(pathStr);
4799
+ }
4800
+ };
4801
+ }
4802
+ });
4803
+
4804
+ // src/engine/script-content-analyzer.ts
4805
+ import { statSync as statSync2, existsSync as existsSync9 } from "fs";
4806
+ import { resolve as resolve8, join as join5, isAbsolute as isAbsolute2 } from "path";
4807
+ import { homedir as homedir3 } from "os";
4808
+ function emptyChain() {
4809
+ return {
4810
+ nodes: [],
4811
+ entryPath: "",
4812
+ maxDepthReached: false,
4813
+ maxSizeReached: false,
4814
+ maxNodesReached: false,
4815
+ totalSize: 0,
4816
+ totalLines: 0,
4817
+ totalNodes: 0,
4818
+ hasCritical: false,
4819
+ hasHigh: false,
4820
+ firstHitNode: null,
4821
+ error: ""
4822
+ };
4823
+ }
4824
+ function scanScriptForKeywords2(content) {
4825
+ const matched = [];
4826
+ for (const { name, pattern } of SUSPICIOUS_PATTERNS2) {
4827
+ if (pattern.test(content)) matched.push(name);
4828
+ }
4829
+ return matched;
4830
+ }
4831
+ function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
4832
+ if (matchedKeywords.length === 0) return { level: "", reason: "" };
4833
+ const keywordSet = new Set(matchedKeywords);
4834
+ for (const rule of KEYWORD_COMBINATION_RISKS2) {
4835
+ const hitGroup1 = [...rule.group1].some((k) => keywordSet.has(k));
4836
+ const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
4837
+ if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
4838
+ }
4839
+ const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
4840
+ const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
4841
+ if (hasNetworkPost) {
4842
+ const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
4843
+ const contentLower = scriptContent.toLowerCase();
4844
+ for (const p of envPatterns) {
4845
+ if (contentLower.includes(p.toLowerCase())) {
4846
+ return {
4847
+ level: "critical",
4848
+ 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"
4849
+ };
3214
4850
  }
3215
4851
  }
3216
4852
  }
3217
- return paths;
3218
- }
3219
- function resolveScriptChain(entryPath, input, maxDepth = 3, maxFileSize = 2 * 1024 * 1024, maxTotalSize = 6 * 1024 * 1024) {
3220
- const visited = /* @__PURE__ */ new Set();
3221
- const result = emptyChain();
3222
- result.entryPath = entryPath;
3223
- const entry = resolvePath(entryPath);
3224
- const baseDir = existsSync7(entry) ? dirname6(entry) : input.cwd || process.cwd();
3225
- resolveRecursive({
3226
- path: entry,
3227
- depth: 0,
3228
- maxDepth,
3229
- maxFileSize,
3230
- maxTotalSize,
3231
- visited,
3232
- result,
3233
- baseDir,
3234
- input
3235
- });
3236
- result.hasCritical = result.nodes.some((n) => n.l1RiskLevel === "critical" || n.l2RiskLevel === "critical" || n.keywordRiskLevel === "critical");
3237
- result.hasHigh = result.nodes.some((n) => ["high", "critical"].includes(n.l1RiskLevel) || ["high", "critical"].includes(n.l2RiskLevel) || ["high", "critical"].includes(n.keywordRiskLevel));
3238
- result.firstHitNode = result.nodes.find((n) => n.l1Hit || n.l2Hit || n.isSuspicious) || null;
3239
- return result;
4853
+ let bestLevel = "";
4854
+ let bestReason = "";
4855
+ for (const kw of matchedKeywords) {
4856
+ const risk = SINGLE_KEYWORD_RISKS2[kw];
4857
+ if (risk && (LEVEL_ORDER2[risk.level] || 0) > (LEVEL_ORDER2[bestLevel] || 0)) {
4858
+ bestLevel = risk.level;
4859
+ bestReason = risk.reason;
4860
+ }
4861
+ }
4862
+ return { level: bestLevel, reason: bestReason };
3240
4863
  }
3241
- function resolveRecursive(ctx) {
3242
- const { path: path3, depth, maxDepth, maxFileSize, maxTotalSize, visited, result, baseDir, input } = ctx;
3243
- if (visited.has(path3)) return;
3244
- if (depth > maxDepth) {
3245
- result.maxDepthReached = true;
3246
- return;
4864
+ function extractScriptPath2(command) {
4865
+ for (const pattern of SCRIPT_EXEC_PATTERNS2) {
4866
+ const m = command.match(pattern);
4867
+ if (m) return m[1];
3247
4868
  }
3248
- if (result.totalSize >= maxTotalSize) {
3249
- result.maxSizeReached = true;
3250
- return;
4869
+ return null;
4870
+ }
4871
+ function extractScriptPathWithArgs(command) {
4872
+ const scriptPath = extractScriptPath2(command);
4873
+ if (!scriptPath) return { path: null, args: [] };
4874
+ const idx = command.indexOf(scriptPath);
4875
+ if (idx < 0) return { path: scriptPath, args: [] };
4876
+ let rest = command.slice(idx + scriptPath.length);
4877
+ rest = rest.split(/[;|&<>]/)[0].trim();
4878
+ const tokens = [...rest.matchAll(/(["'])(.*?)\1|(\S+)/g)];
4879
+ const args = [];
4880
+ for (const m of tokens) {
4881
+ args.push(m[1] ? m[2] : m[3]);
3251
4882
  }
3252
- visited.add(path3);
3253
- const { content, lineCount, fileSize, error } = readScriptContent2(path3, maxFileSize);
3254
- if (error) {
3255
- const node2 = newNode(path3, depth);
3256
- node2.error = error;
3257
- node2.fileSize = fileSize;
3258
- node2.lineCount = lineCount;
3259
- result.nodes.push(node2);
3260
- return;
4883
+ return { path: scriptPath, args };
4884
+ }
4885
+ function extractInlineScriptPath(actionDetail) {
4886
+ const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
4887
+ let m;
4888
+ while ((m = re.exec(actionDetail)) !== null) {
4889
+ const p = m[1];
4890
+ let resolved;
4891
+ try {
4892
+ resolved = resolvePath2(p);
4893
+ if (existsSync9(resolved)) {
4894
+ const stat = statSync2(resolved);
4895
+ if (stat.isFile()) return resolved;
4896
+ }
4897
+ } catch {
4898
+ }
3261
4899
  }
3262
- result.totalSize += fileSize;
3263
- result.totalLines += lineCount;
3264
- const preprocessed = preprocessScriptContent(content);
3265
- const node = newNode(path3, depth);
3266
- node.rawContent = content;
3267
- node.preprocessedContent = preprocessed;
3268
- node.lineCount = lineCount;
3269
- node.fileSize = fileSize;
3270
- result.nodes.push(node);
3271
- const virtualInput = {
3272
- ...input,
3273
- aNext: { actionType: "bash", actionDetail: preprocessed }
3274
- };
3275
- const l1 = runHeuristicCheck(virtualInput);
3276
- if (l1 && !l1.allowed) {
3277
- node.l1Hit = true;
3278
- node.l1RiskLevel = l1.riskLevel;
3279
- node.l1RiskType = l1.riskType;
3280
- node.l1Reason = l1.reason;
3281
- node.l1MatchedPatterns = [];
3282
- if (l1.riskLevel === "critical") {
3283
- result.hasCritical = true;
3284
- result.firstHitNode = node;
3285
- return;
3286
- }
3287
- }
3288
- const childPaths = extractChildScriptPaths(preprocessed, baseDir);
3289
- node.childPaths = childPaths;
3290
- for (const childPath of childPaths) {
3291
- resolveRecursive({
3292
- path: resolvePath(childPath),
3293
- depth: depth + 1,
3294
- maxDepth,
3295
- maxFileSize,
3296
- maxTotalSize,
3297
- visited,
3298
- result,
3299
- baseDir: dirname6(resolvePath(childPath)),
3300
- input
3301
- });
3302
- if (result.hasCritical) return;
4900
+ return null;
4901
+ }
4902
+ function resolvePath2(p) {
4903
+ let expanded = p;
4904
+ if (expanded.startsWith("~/") || expanded === "~") {
4905
+ expanded = join5(homedir3(), expanded.slice(1));
3303
4906
  }
4907
+ return resolve8(expanded);
3304
4908
  }
3305
4909
  function scanScriptChainWithL1(chain, input) {
3306
4910
  for (const node of chain.nodes) {
@@ -3369,14 +4973,31 @@ function scanScriptChainKeywords(chain) {
3369
4973
  function analyzeScriptContent2(input) {
3370
4974
  const actionType = input.aNext.actionType.toLowerCase();
3371
4975
  if (actionType !== "bash") return emptyChain();
3372
- const scriptPath = extractScriptPath2(input.aNext.actionDetail);
4976
+ const { path: scriptPath, args: scriptArgs } = extractScriptPathWithArgs(input.aNext.actionDetail);
3373
4977
  let resolvedPath = scriptPath;
4978
+ let isInline = false;
3374
4979
  if (!resolvedPath) {
3375
4980
  const inline = extractInlineScriptPath(input.aNext.actionDetail);
3376
4981
  if (!inline) return emptyChain();
3377
4982
  resolvedPath = inline;
4983
+ isInline = true;
3378
4984
  }
3379
- const chain = resolveScriptChain(resolvedPath, input);
4985
+ if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
4986
+ resolvedPath = join5(input.cwd || process.cwd(), resolvedPath);
4987
+ }
4988
+ const l1Adapter = (content) => {
4989
+ const virtualInput = {
4990
+ ...input,
4991
+ aNext: { actionType: "bash", actionDetail: content }
4992
+ };
4993
+ const j = runHeuristicCheck(virtualInput);
4994
+ if (j && !j.allowed) {
4995
+ return { hit: true, riskLevel: j.riskLevel, riskType: j.riskType, reason: j.reason, matchedPatterns: [] };
4996
+ }
4997
+ return { hit: false, riskLevel: "", riskType: "", reason: "", matchedPatterns: [] };
4998
+ };
4999
+ const tracker = new RecursiveTracker(l1Adapter);
5000
+ const chain = tracker.track(resolvedPath, isInline ? null : scriptArgs, input.cwd || process.cwd());
3380
5001
  if (chain.nodes.length === 0) return chain;
3381
5002
  scanScriptChainWithL1(chain, input);
3382
5003
  scanScriptChainWithL2(chain, input);
@@ -3444,13 +5065,14 @@ ${contentPreview}
3444
5065
  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";
3445
5066
  return header + parts.join("\n---\n\n");
3446
5067
  }
3447
- var SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2, CHILD_SCRIPT_PATTERNS;
5068
+ var SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2;
3448
5069
  var init_script_content_analyzer = __esm({
3449
5070
  "src/engine/script-content-analyzer.ts"() {
3450
5071
  "use strict";
3451
5072
  init_esm_shims();
3452
5073
  init_heuristic();
3453
5074
  init_logic_rules();
5075
+ init_tracker();
3454
5076
  SUSPICIOUS_PATTERNS2 = [
3455
5077
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
3456
5078
  { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
@@ -3533,18 +5155,6 @@ var init_script_content_analyzer = __esm({
3533
5155
  "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" }
3534
5156
  };
3535
5157
  LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
3536
- CHILD_SCRIPT_PATTERNS = [
3537
- /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
3538
- /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
3539
- /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
3540
- /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
3541
- /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
3542
- /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
3543
- /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
3544
- /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
3545
- /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
3546
- /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
3547
- ];
3548
5158
  }
3549
5159
  });
3550
5160
 
@@ -3558,16 +5168,16 @@ __export(token_stats_exports, {
3558
5168
  recordTokenUsage: () => recordTokenUsage,
3559
5169
  resetTokenStats: () => resetTokenStats
3560
5170
  });
3561
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
5171
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
3562
5172
  function getStatsPath() {
3563
5173
  const envPath = process.env[ENV_STATS_PATH];
3564
5174
  if (envPath) return envPath;
3565
5175
  return getTokenStatsPath();
3566
5176
  }
3567
5177
  function loadAllRecords(statsPath) {
3568
- if (!existsSync8(statsPath)) return [];
5178
+ if (!existsSync10(statsPath)) return [];
3569
5179
  try {
3570
- const data = JSON.parse(readFileSync9(statsPath, "utf-8"));
5180
+ const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
3571
5181
  if (Array.isArray(data)) return data;
3572
5182
  if (data && Array.isArray(data.records)) return data.records;
3573
5183
  return [];
@@ -3578,7 +5188,7 @@ function loadAllRecords(statsPath) {
3578
5188
  function saveRecords(statsPath, records) {
3579
5189
  try {
3580
5190
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
3581
- if (dir && !existsSync8(dir)) mkdirSync2(dir, { recursive: true });
5191
+ if (dir && !existsSync10(dir)) mkdirSync2(dir, { recursive: true });
3582
5192
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
3583
5193
  } catch (e) {
3584
5194
  }
@@ -3705,7 +5315,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
3705
5315
  }
3706
5316
  function resetTokenStats() {
3707
5317
  const statsPath = getStatsPath();
3708
- if (existsSync8(statsPath)) {
5318
+ if (existsSync10(statsPath)) {
3709
5319
  try {
3710
5320
  unlinkSync2(statsPath);
3711
5321
  } catch {
@@ -3935,7 +5545,7 @@ async function analyzeWithLLM(prompt, config) {
3935
5545
  }
3936
5546
  if (attempt < maxRetries) {
3937
5547
  console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
3938
- await new Promise((resolve7) => setTimeout(resolve7, retryCfg.retryInterval * 1e3));
5548
+ await new Promise((resolve9) => setTimeout(resolve9, retryCfg.retryInterval * 1e3));
3939
5549
  } else {
3940
5550
  clearTimeout(totalTimeoutTimer);
3941
5551
  console.error("[AgentMoss] LLM call failed after all retries:", err.message);
@@ -3962,9 +5572,9 @@ var init_llm_analyzer = __esm({
3962
5572
  });
3963
5573
 
3964
5574
  // src/brain/rule-store.ts
3965
- import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
3966
- import { join as join5 } from "path";
3967
- import { homedir as homedir3 } from "os";
5575
+ import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
5576
+ import { join as join6 } from "path";
5577
+ import { homedir as homedir4 } from "os";
3968
5578
  function getRulesPath() {
3969
5579
  const envPath = process.env[ENV_RULES_PATH];
3970
5580
  if (envPath) return envPath;
@@ -3982,7 +5592,7 @@ var init_rule_store = __esm({
3982
5592
  "src/brain/rule-store.ts"() {
3983
5593
  "use strict";
3984
5594
  init_esm_shims();
3985
- DEFAULT_RULES_PATH = join5(homedir3(), ".agentmoss", "brain", "rules.json");
5595
+ DEFAULT_RULES_PATH = join6(homedir4(), ".agentmoss", "brain", "rules.json");
3986
5596
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
3987
5597
  STATUS_DRAFT = "draft";
3988
5598
  STATUS_APPROVED = "approved";
@@ -3994,16 +5604,16 @@ var init_rule_store = __esm({
3994
5604
  BrainRuleStore = class {
3995
5605
  path;
3996
5606
  data;
3997
- constructor(path3) {
3998
- this.path = path3 || getRulesPath();
5607
+ constructor(path6) {
5608
+ this.path = path6 || getRulesPath();
3999
5609
  this.data = this.load();
4000
5610
  }
4001
5611
  load() {
4002
- if (!existsSync9(this.path)) {
5612
+ if (!existsSync11(this.path)) {
4003
5613
  return { version: 1, rules: [], pattern_candidates: [] };
4004
5614
  }
4005
5615
  try {
4006
- const raw = JSON.parse(readFileSync10(this.path, "utf-8"));
5616
+ const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
4007
5617
  return {
4008
5618
  version: raw.version || 1,
4009
5619
  rules: raw.rules || [],
@@ -4308,10 +5918,10 @@ var init_pattern_extractor = __esm({
4308
5918
  const pathRe = new RegExp(pathPrefix + pathBody + pathSuffix, "g");
4309
5919
  let mm;
4310
5920
  while ((mm = pathRe.exec(src)) !== null) {
4311
- const path3 = mm[1];
4312
- if (path3.split("/").length < 3) continue;
4313
- if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(path3)) continue;
4314
- const gen = this.generalizePath(path3);
5921
+ const path6 = mm[1];
5922
+ if (path6.split("/").length < 3) continue;
5923
+ if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(path6)) continue;
5924
+ const gen = this.generalizePath(path6);
4315
5925
  if (seen.has(gen)) continue;
4316
5926
  seen.add(gen);
4317
5927
  patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
@@ -4330,8 +5940,8 @@ var init_pattern_extractor = __esm({
4330
5940
  }
4331
5941
  return false;
4332
5942
  }
4333
- generalizePath(path3) {
4334
- const p = path3.toLowerCase();
5943
+ generalizePath(path6) {
5944
+ const p = path6.toLowerCase();
4335
5945
  if (/\/etc\/(shadow|passwd|sudoers|security|ssh)/.test(p)) {
4336
5946
  return "(?:^|[^a-z0-9_-])etc/(?:shadow|passwd|sudoers|security/|ssh/)";
4337
5947
  }
@@ -4456,58 +6066,980 @@ var init_reviewer = __esm({
4456
6066
  }
4457
6067
  });
4458
6068
 
4459
- // src/brain/index.ts
4460
- function checkBrainRules(store, opts) {
4461
- if (!store) return null;
4462
- const rules = store.getActiveRules(opts.layer);
4463
- if (rules.length === 0) return null;
4464
- const combined = `${opts.actionType} ${opts.actionDetail}`;
4465
- for (const rule of rules) {
4466
- try {
4467
- if (new RegExp(rule.pattern, "i").test(combined)) {
4468
- if (opts.layer === 2 && opts.actionHistory) {
4469
- if (!checkLayer2Context(rule.pattern, opts.actionHistory, opts.actionDetail)) {
4470
- continue;
6069
+ // src/brain/index.ts
6070
+ function checkBrainRules(store, opts) {
6071
+ if (!store) return null;
6072
+ const rules = store.getActiveRules(opts.layer);
6073
+ if (rules.length === 0) return null;
6074
+ const combined = `${opts.actionType} ${opts.actionDetail}`;
6075
+ for (const rule of rules) {
6076
+ try {
6077
+ if (new RegExp(rule.pattern, "i").test(combined)) {
6078
+ if (opts.layer === 2 && opts.actionHistory) {
6079
+ if (!checkLayer2Context(rule.pattern, opts.actionHistory, opts.actionDetail)) {
6080
+ continue;
6081
+ }
6082
+ }
6083
+ store.recordTrigger(rule.id);
6084
+ return {
6085
+ ruleId: rule.id,
6086
+ pattern: rule.pattern,
6087
+ explanation: rule.explanation,
6088
+ confidence: rule.confidence
6089
+ };
6090
+ }
6091
+ } catch {
6092
+ continue;
6093
+ }
6094
+ }
6095
+ return null;
6096
+ }
6097
+ function checkLayer2Context(pattern, actionHistory, actionDetail) {
6098
+ if (pattern.startsWith("BEHAVIOR_CHAIN:")) {
6099
+ const chainType = pattern.replace("BEHAVIOR_CHAIN:", "");
6100
+ if (chainType === "external_read\u2192sensitive_write") {
6101
+ const hasExternalRead = actionHistory.slice(-5).some((h) => /curl|wget|http/i.test(h.actionDetail ?? ""));
6102
+ const isSensitiveWrite = /\.ssh|\.gnupg|authorized_keys|bashrc|profile/i.test(actionDetail);
6103
+ return hasExternalRead && isSensitiveWrite;
6104
+ }
6105
+ if (chainType === "multi_config_read\u2192privilege_action") {
6106
+ const configReads = actionHistory.slice(-5).filter((h) => /\/etc\/|config|\.conf|\.yaml|\.json/i.test(h.actionDetail ?? "")).length;
6107
+ const isPrivilege = /sudo|su |chmod|chown/i.test(actionDetail);
6108
+ return configReads >= 2 && isPrivilege;
6109
+ }
6110
+ return false;
6111
+ }
6112
+ return true;
6113
+ }
6114
+ var init_brain = __esm({
6115
+ "src/brain/index.ts"() {
6116
+ "use strict";
6117
+ init_esm_shims();
6118
+ init_rule_store();
6119
+ init_pattern_extractor();
6120
+ init_reviewer();
6121
+ }
6122
+ });
6123
+
6124
+ // src/engine/formal-engine/decode.ts
6125
+ function isValidBase64Token(tok) {
6126
+ if (tok.length < 8) return false;
6127
+ if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
6128
+ return true;
6129
+ }
6130
+ function isPrintable(s) {
6131
+ for (let i = 0; i < s.length; i++) {
6132
+ const c = s.charCodeAt(i);
6133
+ if (c < 32 || c > 126) return false;
6134
+ }
6135
+ return true;
6136
+ }
6137
+ function decodeBase64Literals(text) {
6138
+ const out = [];
6139
+ const seen = /* @__PURE__ */ new Set();
6140
+ for (const m of text.matchAll(B64_TOKEN_RE)) {
6141
+ const tok = m[0];
6142
+ if (!isValidBase64Token(tok)) continue;
6143
+ if (!/[A-Z]/.test(tok) || !/[a-z]/.test(tok)) continue;
6144
+ try {
6145
+ const padded = tok + "=".repeat((4 - tok.length % 4) % 4);
6146
+ const bin = Buffer.from(padded, "base64");
6147
+ const s = bin.toString("utf8");
6148
+ if (s && isPrintable(s) && !seen.has(s)) {
6149
+ seen.add(s);
6150
+ out.push(s);
6151
+ }
6152
+ } catch {
6153
+ }
6154
+ }
6155
+ return out;
6156
+ }
6157
+ function decodeUrlAndNumeric(text) {
6158
+ const out = [];
6159
+ let s = text;
6160
+ for (let i = 0; i < 3; i++) {
6161
+ let nxt = s;
6162
+ try {
6163
+ nxt = decodeURIComponent(s);
6164
+ } catch {
6165
+ break;
6166
+ }
6167
+ if (nxt === s) break;
6168
+ s = nxt;
6169
+ }
6170
+ if (s !== text) out.push(s);
6171
+ if (/\\[0-7]{3}/.test(text)) {
6172
+ let expanded = "";
6173
+ try {
6174
+ expanded = text.replace(/\\[0-7]{3}/g, (m) => String.fromCharCode(parseInt(m.slice(1), 8)));
6175
+ } catch {
6176
+ }
6177
+ if (expanded && expanded !== text) out.push(expanded);
6178
+ }
6179
+ if (/\\x[0-9a-fA-F]{2}/.test(text)) {
6180
+ let expanded = "";
6181
+ try {
6182
+ expanded = text.replace(/\\x[0-9a-fA-F]{2}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
6183
+ } catch {
6184
+ }
6185
+ if (expanded && expanded !== text) out.push(expanded);
6186
+ }
6187
+ return out;
6188
+ }
6189
+ function allCandidates(detail, max = 8) {
6190
+ const cands = [detail];
6191
+ cands.push(...decodeBase64Literals(detail));
6192
+ cands.push(...decodeUrlAndNumeric(detail));
6193
+ const seen = /* @__PURE__ */ new Set();
6194
+ const out = [];
6195
+ for (const c of cands) {
6196
+ if (seen.has(c) || c.length > 4096) continue;
6197
+ seen.add(c);
6198
+ out.push(c);
6199
+ if (out.length >= max) break;
6200
+ }
6201
+ return out;
6202
+ }
6203
+ function matchStageAny(detail, stages) {
6204
+ for (const cand of allCandidates(detail)) {
6205
+ for (const [name, pats] of Object.entries(stages)) {
6206
+ for (const p of pats) {
6207
+ p.lastIndex = 0;
6208
+ if (p.test(cand)) return name;
6209
+ }
6210
+ }
6211
+ }
6212
+ return "";
6213
+ }
6214
+ var B64_TOKEN_RE;
6215
+ var init_decode = __esm({
6216
+ "src/engine/formal-engine/decode.ts"() {
6217
+ "use strict";
6218
+ init_esm_shims();
6219
+ B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
6220
+ }
6221
+ });
6222
+
6223
+ // src/engine/formal-engine/builder.ts
6224
+ function loadStages() {
6225
+ if (COMBO_STAGES2) return COMBO_STAGES2;
6226
+ const combo = loadL2().combo_chains || {};
6227
+ const stages = {};
6228
+ for (const [name, pats] of Object.entries(combo.stages || {})) {
6229
+ stages[name] = pats.map((p) => new RegExp(p, "i"));
6230
+ }
6231
+ COMBO_STAGES2 = stages;
6232
+ return stages;
6233
+ }
6234
+ function extractResources(actionDetail) {
6235
+ const resources = /* @__PURE__ */ new Set();
6236
+ const text = actionDetail.replace(/'[^']*'|"[^"]*"/g, "");
6237
+ PATH_RE.lastIndex = 0;
6238
+ let m;
6239
+ while ((m = PATH_RE.exec(text)) !== null) {
6240
+ const p = m[1];
6241
+ if (p.startsWith("/dev/") || p.includes("://")) continue;
6242
+ resources.add(p);
6243
+ }
6244
+ return resources;
6245
+ }
6246
+ function markStage(actionDetail) {
6247
+ return matchStageAny(actionDetail, loadStages());
6248
+ }
6249
+ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
6250
+ const nodes = [];
6251
+ const edges = [];
6252
+ const stageFlags = /* @__PURE__ */ new Set();
6253
+ const details = [];
6254
+ for (const a of actionHistory) {
6255
+ const ad = String(a.actionDetail || "");
6256
+ if (ad) details.push({ actionType: String(a.actionType || ""), actionDetail: ad });
6257
+ }
6258
+ if (aNext) {
6259
+ details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
6260
+ }
6261
+ details.forEach((d, seq) => {
6262
+ const stage = markStage(d.actionDetail);
6263
+ const node = {
6264
+ sessionId,
6265
+ seq,
6266
+ actionType: d.actionType,
6267
+ actionDetail: d.actionDetail,
6268
+ stage,
6269
+ resources: extractResources(d.actionDetail)
6270
+ };
6271
+ nodes.push(node);
6272
+ if (stage) stageFlags.add(stage);
6273
+ });
6274
+ for (let i = 0; i < nodes.length - 1; i++) {
6275
+ edges.push({ src: i, dst: i + 1, kind: "next" });
6276
+ }
6277
+ return { sessionId, nodes, edges, stageFlags };
6278
+ }
6279
+ function buildMultiSessionGraph(sessions, aNext) {
6280
+ const mg = { sessions: [], crossEdges: [] };
6281
+ if (!sessions || sessions.length === 0) {
6282
+ mg.sessions.push(buildSessionGraph([], aNext, "current"));
6283
+ return mg;
6284
+ }
6285
+ for (const s of sessions) {
6286
+ const sid = String(s.sessionId || "s");
6287
+ mg.sessions.push(buildSessionGraph(s.actionHistory || [], void 0, sid));
6288
+ }
6289
+ if (aNext) {
6290
+ const last = mg.sessions[mg.sessions.length - 1];
6291
+ if (last.sessionId === "current") {
6292
+ const stage = markStage(String(aNext.actionDetail || ""));
6293
+ last.nodes.push({
6294
+ sessionId: last.sessionId,
6295
+ seq: last.nodes.length,
6296
+ actionType: String(aNext.actionType || ""),
6297
+ actionDetail: String(aNext.actionDetail || ""),
6298
+ stage,
6299
+ resources: extractResources(String(aNext.actionDetail || ""))
6300
+ });
6301
+ if (last.nodes.length > 1) {
6302
+ last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
6303
+ }
6304
+ } else {
6305
+ mg.sessions.push(buildSessionGraph([], aNext, "current"));
6306
+ }
6307
+ }
6308
+ addCrossDataEdges(mg);
6309
+ return mg;
6310
+ }
6311
+ function addCrossDataEdges(mg) {
6312
+ const nodes = allNodesFlat(mg);
6313
+ if (nodes.length < 2) return;
6314
+ for (let i = 0; i < nodes.length; i++) {
6315
+ for (let j = i + 1; j < nodes.length; j++) {
6316
+ if (nodes[i].sessionId === nodes[j].sessionId) continue;
6317
+ const shared = intersect(nodes[i].resources, nodes[j].resources);
6318
+ if (shared.size > 0) {
6319
+ mg.crossEdges.push({ src: i, dst: j, kind: "data" });
6320
+ }
6321
+ }
6322
+ }
6323
+ }
6324
+ function allNodesFlat(mg) {
6325
+ const out = [];
6326
+ for (const s of mg.sessions) out.push(...s.nodes);
6327
+ return out;
6328
+ }
6329
+ function intersect(a, b) {
6330
+ const out = /* @__PURE__ */ new Set();
6331
+ for (const v of a) if (b.has(v)) out.add(v);
6332
+ return out;
6333
+ }
6334
+ var COMBO_STAGES2, PATH_RE;
6335
+ var init_builder = __esm({
6336
+ "src/engine/formal-engine/builder.ts"() {
6337
+ "use strict";
6338
+ init_esm_shims();
6339
+ init_rules_loader();
6340
+ init_decode();
6341
+ COMBO_STAGES2 = null;
6342
+ PATH_RE = /(?:^|\s|[@=])((?:\/|~\/)[\w./~-]+)/g;
6343
+ }
6344
+ });
6345
+
6346
+ // src/engine/formal-engine/ir.ts
6347
+ function allNodes(mg) {
6348
+ const out = [];
6349
+ for (const s of mg.sessions) out.push(...s.nodes);
6350
+ return out;
6351
+ }
6352
+ function allEdges(mg) {
6353
+ const out = [];
6354
+ for (const s of mg.sessions) out.push(...s.edges);
6355
+ out.push(...mg.crossEdges);
6356
+ return out;
6357
+ }
6358
+ function emptyVerificationResult() {
6359
+ return { verified: true, violations: [] };
6360
+ }
6361
+ var init_ir = __esm({
6362
+ "src/engine/formal-engine/ir.ts"() {
6363
+ "use strict";
6364
+ init_esm_shims();
6365
+ }
6366
+ });
6367
+
6368
+ // src/engine/formal-engine/properties.ts
6369
+ function loadCrossSessionChains() {
6370
+ if (CROSS_SESSION_CHAINS) return CROSS_SESSION_CHAINS;
6371
+ const combo = loadL2().combo_chains || {};
6372
+ CROSS_SESSION_CHAINS = (combo.chains || []).map((c) => ({
6373
+ id: String(c.id || "combo_chain"),
6374
+ stages: c.stages || [],
6375
+ riskLevel: String(c.riskLevel || c.risk_level || "high"),
6376
+ reason: "\u8DE8\u4F1A\u8BDD" + String(c.reason || "\u7EC4\u5408\u653B\u51FB\u94FE")
6377
+ }));
6378
+ return CROSS_SESSION_CHAINS;
6379
+ }
6380
+ function checkCrossSessionChain(graph) {
6381
+ const violations = [];
6382
+ const nodes = allNodes(graph);
6383
+ if (nodes.length === 0) return violations;
6384
+ for (const chain of loadCrossSessionChains()) {
6385
+ const stages = chain.stages;
6386
+ if (!stages.length) continue;
6387
+ let matched = 0;
6388
+ let stageIdx = 0;
6389
+ const path6 = [];
6390
+ for (const n of nodes) {
6391
+ if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
6392
+ matched += 1;
6393
+ path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6394
+ stageIdx += 1;
6395
+ if (matched >= stages.length) break;
6396
+ }
6397
+ }
6398
+ if (matched >= stages.length) {
6399
+ violations.push({
6400
+ propertyId: "cross-" + chain.id,
6401
+ reason: chain.reason,
6402
+ riskLevel: chain.riskLevel,
6403
+ path: path6
6404
+ });
6405
+ }
6406
+ }
6407
+ return violations;
6408
+ }
6409
+ function checkSensitiveDataFlow(graph) {
6410
+ const violations = [];
6411
+ const nodes = allNodes(graph);
6412
+ if (nodes.length === 0) return violations;
6413
+ const tainted = /* @__PURE__ */ new Map();
6414
+ for (let i = 0; i < nodes.length; i++) {
6415
+ const n = nodes[i];
6416
+ if (n.stage === "CRED" && [...n.resources].some((r) => CRED_FILE_RE.test(r))) {
6417
+ tainted.set(i, `${n.sessionId}#${n.seq} \u8BBF\u95EE ${[...n.resources][0]}`);
6418
+ }
6419
+ }
6420
+ if (tainted.size === 0) return violations;
6421
+ const edges = allEdges(graph);
6422
+ let changed = true;
6423
+ while (changed) {
6424
+ changed = false;
6425
+ for (const e of edges) {
6426
+ if (tainted.has(e.src) && !tainted.has(e.dst)) {
6427
+ const srcRes = nodes[e.src].resources;
6428
+ const dstRes = nodes[e.dst].resources;
6429
+ for (const r of srcRes) {
6430
+ if (dstRes.has(r)) {
6431
+ tainted.set(e.dst, tainted.get(e.src));
6432
+ changed = true;
6433
+ break;
6434
+ }
6435
+ }
6436
+ }
6437
+ }
6438
+ }
6439
+ for (const [i, n] of nodes.entries()) {
6440
+ if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
6441
+ violations.push({
6442
+ propertyId: "sensitive-data-exfil",
6443
+ reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${tainted.get(i)} \u2192 ${n.sessionId}#${n.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
6444
+ riskLevel: "critical",
6445
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
6446
+ });
6447
+ }
6448
+ }
6449
+ return violations;
6450
+ }
6451
+ function isExfilNode(n) {
6452
+ if (n.stage === "EXFIL") return true;
6453
+ if (EXFIL_RE.test(n.actionDetail) && EXFIL_ACTION_RE.test(n.actionDetail)) return true;
6454
+ if (EBPF_NET_RE.test(n.actionDetail) && REMOTE_RE.test(n.actionDetail)) return true;
6455
+ return false;
6456
+ }
6457
+ function isCredNode(n) {
6458
+ if (n.stage !== "CRED") return false;
6459
+ for (const r of n.resources) {
6460
+ if (CRED_STRICT_RE.test(r)) return true;
6461
+ }
6462
+ return CRED_STRICT_RE.test(n.actionDetail);
6463
+ }
6464
+ function isPrivescNode(n) {
6465
+ if (n.stage === "PRIVESC") return true;
6466
+ return PRIVESC_RE.test(n.actionDetail);
6467
+ }
6468
+ function checkNeverExfilAfterPrivesc(graph) {
6469
+ const violations = [];
6470
+ const nodes = allNodes(graph);
6471
+ if (nodes.length < 2) return violations;
6472
+ for (let i = 0; i < nodes.length; i++) {
6473
+ if (!isPrivescNode(nodes[i])) continue;
6474
+ for (let j = i + 1; j < nodes.length; j++) {
6475
+ if (isExfilNode(nodes[j])) {
6476
+ violations.push({
6477
+ propertyId: "no-privesc-then-exfil",
6478
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6479
+ riskLevel: "critical",
6480
+ path: [
6481
+ { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
6482
+ { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
6483
+ ]
6484
+ });
6485
+ return violations;
6486
+ }
6487
+ }
6488
+ }
6489
+ return violations;
6490
+ }
6491
+ function checkNeverExfilAfterCred(graph) {
6492
+ const violations = [];
6493
+ const nodes = allNodes(graph);
6494
+ if (nodes.length < 2) return violations;
6495
+ for (let i = 0; i < nodes.length; i++) {
6496
+ if (!isCredNode(nodes[i])) continue;
6497
+ for (let j = i + 1; j < nodes.length; j++) {
6498
+ if (isExfilNode(nodes[j])) {
6499
+ violations.push({
6500
+ propertyId: "no-cred-then-exfil",
6501
+ reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6502
+ riskLevel: "critical",
6503
+ path: [
6504
+ { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
6505
+ { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
6506
+ ]
6507
+ });
6508
+ return violations;
6509
+ }
6510
+ }
6511
+ }
6512
+ return violations;
6513
+ }
6514
+ var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, PRIVESC_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, ALL_PROPERTIES;
6515
+ var init_properties = __esm({
6516
+ "src/engine/formal-engine/properties.ts"() {
6517
+ "use strict";
6518
+ init_esm_shims();
6519
+ init_rules_loader();
6520
+ init_ir();
6521
+ CROSS_SESSION_CHAINS = null;
6522
+ CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
6523
+ EXFIL_RE = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
6524
+ EBPF_NET_RE = /connect|sendto|network_connect/i;
6525
+ REMOTE_RE = /to\s+\d+\.\d+\.\d+\.\d+|remote_addr/i;
6526
+ PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
6527
+ EXFIL_ACTION_RE = /\bcurl\b|\bwget\b|\bscp\b|\brsync\b|\brclone\b|\bftp\b|connect\s+.*\d+\.\d+\.\d+\.\d+|open\s+.*tcp|base64\s+.*\-o/i;
6528
+ CRED_STRICT_RE = /(known_hosts|\.ssh|id_rsa|\.pem|\b\.key\b|\/etc\/shadow|\/etc\/passwd|\.aws\/credentials|credentials\.json|netrc|password|secrets?\.ya?ml|credentials?\s*(file|path)?\s*(=|\:)|\bconfig.*(password|token))/i;
6529
+ ALL_PROPERTIES = [
6530
+ { id: "cross-session-chain", check: checkCrossSessionChain },
6531
+ { id: "sensitive-data-flow", check: checkSensitiveDataFlow },
6532
+ { id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
6533
+ { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred }
6534
+ ];
6535
+ }
6536
+ });
6537
+
6538
+ // src/engine/formal-engine/engine.ts
6539
+ function ebpfEventToDetail(e) {
6540
+ const parts = [];
6541
+ const syscall = String(e.syscall || e.op || "");
6542
+ const argv = String(e.argv || e.pathname || "");
6543
+ const remote = String(e.remote_addr || "");
6544
+ const port = e.remote_port;
6545
+ if (syscall) parts.push(syscall);
6546
+ if (argv && argv !== "" && argv !== "None") parts.push(argv);
6547
+ if (remote) parts.push("to " + remote + (port ? ":" + String(port) : ""));
6548
+ return parts.join(" ");
6549
+ }
6550
+ var FormalEngine;
6551
+ var init_engine = __esm({
6552
+ "src/engine/formal-engine/engine.ts"() {
6553
+ "use strict";
6554
+ init_esm_shims();
6555
+ init_builder();
6556
+ init_ir();
6557
+ init_properties();
6558
+ FormalEngine = class {
6559
+ properties;
6560
+ constructor(properties) {
6561
+ this.properties = properties && properties.length > 0 ? properties : ALL_PROPERTIES;
6562
+ }
6563
+ verify(sessions, aNext, ebpfEvents) {
6564
+ const all = sessions ? [...sessions] : [];
6565
+ if (ebpfEvents && ebpfEvents.length > 0) {
6566
+ all.push({
6567
+ sessionId: "ebpf-evidence",
6568
+ actionHistory: ebpfEvents.map((e) => ({
6569
+ actionType: "syscall",
6570
+ actionDetail: ebpfEventToDetail(e)
6571
+ }))
6572
+ });
6573
+ }
6574
+ const graph = buildMultiSessionGraph(all, aNext);
6575
+ const result = emptyVerificationResult();
6576
+ for (const prop of this.properties) {
6577
+ let violations;
6578
+ try {
6579
+ violations = prop.check(graph);
6580
+ } catch (e) {
6581
+ result.verified = false;
6582
+ result.violations.push({
6583
+ propertyId: prop.id,
6584
+ reason: `\u5F62\u5F0F\u5316\u6027\u8D28\u9A8C\u8BC1\u5F02\u5E38\uFF0C\u4FDD\u5B88\u62E6\u622A: ${e}`,
6585
+ riskLevel: "medium",
6586
+ path: []
6587
+ });
6588
+ return result;
6589
+ }
6590
+ if (violations.length > 0) {
6591
+ result.verified = false;
6592
+ result.violations.push(...violations);
6593
+ break;
6594
+ }
6595
+ }
6596
+ return result;
6597
+ }
6598
+ };
6599
+ }
6600
+ });
6601
+
6602
+ // src/engine/formal-engine/stateful.ts
6603
+ function loadRules() {
6604
+ if (STAGES_PATTERNS !== null && CHAINS !== null) {
6605
+ return { stages: STAGES_PATTERNS, chains: CHAINS };
6606
+ }
6607
+ const combo = loadL2().combo_chains || {};
6608
+ const stages = {};
6609
+ for (const [name, pats] of Object.entries(combo.stages || {})) {
6610
+ stages[name] = pats.map((p) => new RegExp(p, "i"));
6611
+ }
6612
+ const chains = combo.chains || [];
6613
+ STAGES_PATTERNS = stages;
6614
+ CHAINS = chains;
6615
+ return { stages, chains };
6616
+ }
6617
+ function markStage2(actionDetail) {
6618
+ const { stages } = loadRules();
6619
+ return matchStageAny(actionDetail, stages);
6620
+ }
6621
+ function extractResources2(actionDetail) {
6622
+ const text = actionDetail.replace(/'[^']*'|"[^"]*"/g, "");
6623
+ const out = [];
6624
+ const re = /(?:^|\s|[@=])((?:\/|~\/)[\w./~-]+)/g;
6625
+ let m;
6626
+ while ((m = re.exec(text)) !== null) {
6627
+ const p = m[1];
6628
+ if (p.startsWith("/dev/") || p.includes("://")) continue;
6629
+ if (!out.includes(p)) out.push(p);
6630
+ }
6631
+ return out;
6632
+ }
6633
+ function lifecycleFor(detail, stage, isError) {
6634
+ if (isError) {
6635
+ if (stage === "LISTEN" || stage === "PERSIST" || NEVER_ACTIVATED_RE.test(detail)) return "never_activated";
6636
+ }
6637
+ if (DESTROY_RE.test(detail)) return "dead";
6638
+ if (stage === "LISTEN" || stage === "PERSIST") return "alive";
6639
+ if (detail.includes(">>") || detail.includes(">")) return "alive";
6640
+ return "";
6641
+ }
6642
+ var DESTROY_RE, NEVER_ACTIVATED_RE, CRED_FILE_RE2, EXFIL_RE2, STAGES_PATTERNS, CHAINS, MAX_NODES, StatefulChainTracker;
6643
+ var init_stateful = __esm({
6644
+ "src/engine/formal-engine/stateful.ts"() {
6645
+ "use strict";
6646
+ init_esm_shims();
6647
+ init_rules_loader();
6648
+ init_decode();
6649
+ DESTROY_RE = /\b(?:pkill|kill)\b|systemctl\s+stop|service\s+stop|crontab\s+-r|rm\s+-[rf]?\s+/i;
6650
+ NEVER_ACTIVATED_RE = /\b(?:not\s+found|no\s+such\s+file|command\s+not\s+found|refused|failed\s+to\s+(?:start|bind|listen)|address\s+already\s+in\s+use)\b/i;
6651
+ CRED_FILE_RE2 = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
6652
+ EXFIL_RE2 = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
6653
+ STAGES_PATTERNS = null;
6654
+ CHAINS = null;
6655
+ MAX_NODES = 1e5;
6656
+ StatefulChainTracker = class {
6657
+ agentId;
6658
+ nodes_ = [];
6659
+ ptr = {};
6660
+ tainted = /* @__PURE__ */ new Set();
6661
+ /** taint 反索引:资源路径 → 已染毒(O(1) 命中)。 */
6662
+ taintedResources = /* @__PURE__ */ new Set();
6663
+ aliveResources = {};
6664
+ seqNext = 0;
6665
+ constructor(agentId = "") {
6666
+ this.agentId = agentId;
6667
+ loadRules();
6668
+ }
6669
+ get nodes() {
6670
+ return this.nodes_;
6671
+ }
6672
+ get pendingPrefixes() {
6673
+ const { chains } = loadRules();
6674
+ const out = [];
6675
+ for (const chain of chains) {
6676
+ const stages = chain.stages || [];
6677
+ if (stages.length === 0) continue;
6678
+ const matched = this.ptr[String(chain.id || "")] || 0;
6679
+ if (matched > 0 && matched < stages.length) {
6680
+ const pf = {
6681
+ chainId: String(chain.id || ""),
6682
+ stages,
6683
+ matched,
6684
+ riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
6685
+ get nextStage() {
6686
+ return this.matched < this.stages.length ? this.stages[this.matched] : "";
6687
+ }
6688
+ };
6689
+ out.push(pf);
4471
6690
  }
4472
6691
  }
4473
- store.recordTrigger(rule.id);
6692
+ return out;
6693
+ }
6694
+ get aliveResourcesSnapshot() {
6695
+ return { ...this.aliveResources };
6696
+ }
6697
+ /**
6698
+ * 全量/定期灌入历史 session(多 session 各动作追加到时间线)。
6699
+ * 历史节点同样推进链指针 / 生命周期 / taint,否则后续 advance 无法在历史基础上
6700
+ * 补全链。历史阶段推进若补全链,属于历史事实,不阻断当前动作。
6701
+ */
6702
+ seed(sessions) {
6703
+ let added = 0;
6704
+ for (const s of sessions || []) {
6705
+ const sid = String(s.sessionId || "s");
6706
+ for (const a of s.actionHistory || []) {
6707
+ const node = this.appendNode(a, sid);
6708
+ if (!node) {
6709
+ continue;
6710
+ }
6711
+ added += 1;
6712
+ this.recordLifecycle(node);
6713
+ this.advanceTaint(node);
6714
+ this.advanceChains(node);
6715
+ }
6716
+ }
6717
+ return added;
6718
+ }
6719
+ /** 增量推进:把当前动作追加到时间线 → 链指针推进 → 判定。 */
6720
+ advance(args) {
6721
+ const { aNext, sessionId = "current", isError = false } = args;
6722
+ const node = this.appendNode(aNext, sessionId, isError);
6723
+ if (!node) {
6724
+ return { seq: this.seqNext, node: null, matched: null, pendingPrefixes: this.pendingPrefixes, exfil: [], shouldBlock: false };
6725
+ }
6726
+ const seq = this.seqNext;
6727
+ this.recordLifecycle(node);
6728
+ const matched = this.advanceChains(node);
6729
+ this.advanceTaint(node);
6730
+ const exfil = this.checkExfilAfterTaint(node);
4474
6731
  return {
4475
- ruleId: rule.id,
4476
- pattern: rule.pattern,
4477
- explanation: rule.explanation,
4478
- confidence: rule.confidence
6732
+ seq,
6733
+ node,
6734
+ matched,
6735
+ pendingPrefixes: this.pendingPrefixes,
6736
+ exfil,
6737
+ get shouldBlock() {
6738
+ return this.matched !== null || this.exfil.length > 0;
6739
+ }
4479
6740
  };
4480
6741
  }
4481
- } catch {
4482
- continue;
4483
- }
6742
+ appendNode(action, sessionId, isError = false) {
6743
+ const ad = String(action.actionDetail || "");
6744
+ const at = String(action.actionType || "");
6745
+ if (!ad) return null;
6746
+ if (this.nodes_.length >= MAX_NODES) {
6747
+ return null;
6748
+ }
6749
+ const stage = markStage2(ad);
6750
+ const node = {
6751
+ seq: this.seqNext,
6752
+ sessionId,
6753
+ actionType: at,
6754
+ actionDetail: ad,
6755
+ stage,
6756
+ resources: extractResources2(ad),
6757
+ isError,
6758
+ lifecycle: lifecycleFor(ad, stage, isError)
6759
+ };
6760
+ this.nodes_.push(node);
6761
+ this.seqNext += 1;
6762
+ return node;
6763
+ }
6764
+ recordLifecycle(node) {
6765
+ if (node.lifecycle === "dead") {
6766
+ let hit = false;
6767
+ for (const r of node.resources) {
6768
+ if (r in this.aliveResources) {
6769
+ delete this.aliveResources[r];
6770
+ hit = true;
6771
+ }
6772
+ }
6773
+ const detail = node.actionDetail;
6774
+ for (const r of Object.keys(this.aliveResources)) {
6775
+ const base = r.split("/").pop() || "";
6776
+ if (base && detail.includes(base)) {
6777
+ delete this.aliveResources[r];
6778
+ hit = true;
6779
+ }
6780
+ }
6781
+ if (hit) this.resetChainsOnDestroy();
6782
+ return;
6783
+ }
6784
+ if (node.lifecycle === "alive") {
6785
+ for (const r of node.resources) {
6786
+ this.aliveResources[r] = node.seq;
6787
+ }
6788
+ }
6789
+ }
6790
+ resetChainsOnDestroy() {
6791
+ for (const cid of Object.keys(this.ptr)) {
6792
+ this.ptr[cid] = 0;
6793
+ }
6794
+ }
6795
+ advanceChains(node) {
6796
+ if (node.lifecycle === "dead") return null;
6797
+ if (node.lifecycle === "never_activated") {
6798
+ this.invalidateChainOnFailure(node);
6799
+ return null;
6800
+ }
6801
+ const stage = node.stage;
6802
+ if (!stage) return null;
6803
+ const { chains } = loadRules();
6804
+ for (const chain of chains) {
6805
+ const stages = chain.stages || [];
6806
+ if (stages.length === 0) continue;
6807
+ const cid = String(chain.id || "");
6808
+ const cur = this.ptr[cid] || 0;
6809
+ if (cur < stages.length && stages[cur] === stage) {
6810
+ this.ptr[cid] = cur + 1;
6811
+ if (cur + 1 >= stages.length) {
6812
+ return {
6813
+ chainId: cid,
6814
+ stages,
6815
+ riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
6816
+ reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
6817
+ path: this.chainPath(stages)
6818
+ };
6819
+ }
6820
+ }
6821
+ }
6822
+ return null;
6823
+ }
6824
+ invalidateChainOnFailure(node) {
6825
+ const failed = new Set(node.resources);
6826
+ if (failed.size === 0) return;
6827
+ const { chains } = loadRules();
6828
+ for (const chain of chains) {
6829
+ const cid = String(chain.id || "");
6830
+ if ((this.ptr[cid] || 0) > 0) {
6831
+ this.ptr[cid] = 0;
6832
+ }
6833
+ }
6834
+ }
6835
+ chainPath(stages) {
6836
+ const path6 = [];
6837
+ let want = stages[0];
6838
+ for (const n of this.nodes_) {
6839
+ if (n.stage === want) {
6840
+ path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage });
6841
+ const idx = stages.indexOf(n.stage);
6842
+ if (idx + 1 < stages.length) {
6843
+ want = stages[idx + 1];
6844
+ } else {
6845
+ break;
6846
+ }
6847
+ }
6848
+ }
6849
+ return path6;
6850
+ }
6851
+ // ---------------- 敏感数据 taint(P2) ----------------
6852
+ advanceTaint(node) {
6853
+ if (node.lifecycle === "dead") return;
6854
+ const idx = node.seq;
6855
+ const taintedRes = /* @__PURE__ */ new Set();
6856
+ for (const r of node.resources) {
6857
+ if (this.resourceTainted(r)) taintedRes.add(r);
6858
+ }
6859
+ if (node.stage === "CRED" && node.resources.some((r) => CRED_FILE_RE2.test(r))) {
6860
+ this.tainted.add(idx);
6861
+ for (const r of node.resources) taintedRes.add(r);
6862
+ }
6863
+ if (taintedRes.size > 0) {
6864
+ this.tainted.add(idx);
6865
+ for (const r of taintedRes) this.taintedResources.add(r);
6866
+ }
6867
+ }
6868
+ resourceTainted(resource) {
6869
+ return this.taintedResources.has(resource);
6870
+ return false;
6871
+ }
6872
+ checkExfilAfterTaint(node) {
6873
+ if (!this.tainted.has(node.seq)) return [];
6874
+ if (EXFIL_RE2.test(node.actionDetail)) {
6875
+ return [{
6876
+ property_id: "sensitive-data-exfil",
6877
+ reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${this.taintSource(node)} \u2192 ${node.sessionId}#${node.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
6878
+ risk_level: "critical",
6879
+ path: [[node.sessionId, node.seq, node.actionDetail.slice(0, 60)]]
6880
+ }];
6881
+ }
6882
+ return [];
6883
+ }
6884
+ taintSource(node) {
6885
+ const sorted = [...this.tainted].sort((a, b) => a - b);
6886
+ for (const i of sorted) {
6887
+ if (i === node.seq) continue;
6888
+ const n = this.nodes_[i];
6889
+ if (!n) continue;
6890
+ for (const r of node.resources) {
6891
+ if (n.resources.includes(r)) return `${n.sessionId}#${n.seq} \u8BBF\u95EE ${r}`;
6892
+ }
6893
+ }
6894
+ return "\u672A\u77E5\u51ED\u636E\u6E90";
6895
+ }
6896
+ // ---------------- 序列化/状态 ----------------
6897
+ toSnapshot() {
6898
+ return {
6899
+ agent_id: this.agentId,
6900
+ nodes: this.nodes_.map((n) => ({ ...n, resources: [...n.resources] })),
6901
+ ptrs: { ...this.ptr },
6902
+ tainted: [...this.tainted].sort((a, b) => a - b),
6903
+ alive: { ...this.aliveResources },
6904
+ seq_next: this.seqNext
6905
+ };
6906
+ }
6907
+ restore(snapshot) {
6908
+ if (!snapshot) return;
6909
+ this.agentId = String(snapshot.agent_id || this.agentId);
6910
+ for (const raw of snapshot.nodes || []) {
6911
+ this.nodes_.push({
6912
+ seq: Number(raw.seq || 0),
6913
+ sessionId: String(raw.sessionId || ""),
6914
+ actionType: String(raw.actionType || ""),
6915
+ actionDetail: String(raw.actionDetail || ""),
6916
+ stage: String(raw.stage || ""),
6917
+ resources: [...raw.resources || []],
6918
+ isError: Boolean(raw.isError),
6919
+ lifecycle: String(raw.lifecycle || "")
6920
+ });
6921
+ }
6922
+ this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
6923
+ this.ptr = Object.fromEntries(Object.entries(snapshot.ptrs || {}).map(([k, v]) => [k, Number(v)]));
6924
+ this.tainted = new Set((snapshot.tainted || []).map(Number));
6925
+ this.taintedResources = /* @__PURE__ */ new Set();
6926
+ for (const i of this.tainted) {
6927
+ const n = this.nodes_[i];
6928
+ if (n) for (const r of n.resources) this.taintedResources.add(r);
6929
+ }
6930
+ this.aliveResources = Object.fromEntries(
6931
+ Object.entries(snapshot.alive || {}).map(([k, v]) => [k, Number(v)])
6932
+ );
6933
+ }
6934
+ };
4484
6935
  }
4485
- return null;
4486
- }
4487
- function checkLayer2Context(pattern, actionHistory, actionDetail) {
4488
- if (pattern.startsWith("BEHAVIOR_CHAIN:")) {
4489
- const chainType = pattern.replace("BEHAVIOR_CHAIN:", "");
4490
- if (chainType === "external_read\u2192sensitive_write") {
4491
- const hasExternalRead = actionHistory.slice(-5).some((h) => /curl|wget|http/i.test(h.actionDetail ?? ""));
4492
- const isSensitiveWrite = /\.ssh|\.gnupg|authorized_keys|bashrc|profile/i.test(actionDetail);
4493
- return hasExternalRead && isSensitiveWrite;
4494
- }
4495
- if (chainType === "multi_config_read\u2192privilege_action") {
4496
- const configReads = actionHistory.slice(-5).filter((h) => /\/etc\/|config|\.conf|\.yaml|\.json/i.test(h.actionDetail ?? "")).length;
4497
- const isPrivilege = /sudo|su |chmod|chown/i.test(actionDetail);
4498
- return configReads >= 2 && isPrivilege;
4499
- }
4500
- return false;
6936
+ });
6937
+
6938
+ // src/engine/formal-engine/chain_state.ts
6939
+ import * as fs4 from "fs";
6940
+ import * as os2 from "os";
6941
+ import * as path4 from "path";
6942
+ import { createHash } from "crypto";
6943
+ function defaultStateDbPath() {
6944
+ const dir = process.env.AGENT_MOSS_STATE_DIR || path4.join(os2.homedir(), ".agentmoss");
6945
+ const tag = createHash("sha1").update("agentmoss-stateful").digest("hex").slice(0, 8);
6946
+ return path4.join(dir, `stateful_chains_${tag}.json`);
6947
+ }
6948
+ var ChainStateStore;
6949
+ var init_chain_state = __esm({
6950
+ "src/engine/formal-engine/chain_state.ts"() {
6951
+ "use strict";
6952
+ init_esm_shims();
6953
+ init_stateful();
6954
+ ChainStateStore = class {
6955
+ filePath;
6956
+ data;
6957
+ _timer = null;
6958
+ _flushDelayMs = 50;
6959
+ constructor(filePath = "") {
6960
+ this.filePath = filePath || defaultStateDbPath();
6961
+ this.data = this._read();
6962
+ }
6963
+ _read() {
6964
+ try {
6965
+ if (fs4.existsSync(this.filePath)) {
6966
+ const raw = fs4.readFileSync(this.filePath, "utf-8");
6967
+ return JSON.parse(raw);
6968
+ }
6969
+ } catch {
6970
+ }
6971
+ return {};
6972
+ }
6973
+ /** 防抖写入:save() 只改内存,后台批量落盘(避免每次 tool call 全量 writeFileSync)。 */
6974
+ _scheduleWrite() {
6975
+ if (this._timer !== null) return;
6976
+ this._timer = setTimeout(() => {
6977
+ this._timer = null;
6978
+ try {
6979
+ fs4.mkdirSync(path4.dirname(this.filePath), { recursive: true });
6980
+ fs4.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
6981
+ } catch {
6982
+ }
6983
+ }, this._flushDelayMs);
6984
+ }
6985
+ /** 立即落盘(测试/关停前调用,保证状态不丢)。 */
6986
+ flush() {
6987
+ if (this._timer !== null) {
6988
+ clearTimeout(this._timer);
6989
+ this._timer = null;
6990
+ }
6991
+ try {
6992
+ fs4.mkdirSync(path4.dirname(this.filePath), { recursive: true });
6993
+ fs4.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
6994
+ } catch {
6995
+ }
6996
+ }
6997
+ load(agentId) {
6998
+ const entry = this.data[agentId];
6999
+ if (!entry) return null;
7000
+ const t = new StatefulChainTracker(agentId);
7001
+ t.restore(entry.state);
7002
+ return t;
7003
+ }
7004
+ save(tracker) {
7005
+ this.data[tracker.agentId] = {
7006
+ state: tracker.toSnapshot(),
7007
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
7008
+ };
7009
+ this._scheduleWrite();
7010
+ }
7011
+ delete(agentId) {
7012
+ delete this.data[agentId];
7013
+ this._scheduleWrite();
7014
+ }
7015
+ applyAction(agentId, aNext, sessionId, isError = false) {
7016
+ const t = this.load(agentId) || new StatefulChainTracker(agentId);
7017
+ const r = t.advance({ aNext, sessionId, isError });
7018
+ this.save(t);
7019
+ return r;
7020
+ }
7021
+ seedAndApply(agentId, sessions, aNext, sessionId = "current", isError = false) {
7022
+ const t = this.load(agentId) || new StatefulChainTracker(agentId);
7023
+ t.seed(sessions);
7024
+ const r = t.advance({ aNext, sessionId, isError });
7025
+ this.save(t);
7026
+ return r;
7027
+ }
7028
+ };
4501
7029
  }
4502
- return true;
4503
- }
4504
- var init_brain = __esm({
4505
- "src/brain/index.ts"() {
7030
+ });
7031
+
7032
+ // src/engine/formal-engine/index.ts
7033
+ var init_formal_engine = __esm({
7034
+ "src/engine/formal-engine/index.ts"() {
4506
7035
  "use strict";
4507
7036
  init_esm_shims();
4508
- init_rule_store();
4509
- init_pattern_extractor();
4510
- init_reviewer();
7037
+ init_engine();
7038
+ init_ir();
7039
+ init_builder();
7040
+ init_properties();
7041
+ init_stateful();
7042
+ init_chain_state();
4511
7043
  }
4512
7044
  });
4513
7045
 
@@ -4604,7 +7136,10 @@ async function runAnalysis(input) {
4604
7136
  const actionType = input.aNext.actionType.toLowerCase();
4605
7137
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
4606
7138
  const lowerDetail = rawDetail.toLowerCase();
4607
- const agentExt = getAgentExtensions(input.agentId);
7139
+ const agentExt = mergeAgentExtensions(
7140
+ getAgentExtensions(input.agentId),
7141
+ getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
7142
+ );
4608
7143
  if (FULLY_SAFE_TOOLS.has(actionType)) {
4609
7144
  return {
4610
7145
  allowed: true,
@@ -4709,6 +7244,28 @@ async function runAnalysis(input) {
4709
7244
  return effectiveResult;
4710
7245
  }
4711
7246
  }
7247
+ if (isLogicRulesEnabled()) {
7248
+ const formalSessions = [];
7249
+ if (input.sessionHistory) {
7250
+ for (const s of input.sessionHistory) {
7251
+ formalSessions.push({ sessionId: s.sessionId || "s", actionHistory: s.actionHistory || [] });
7252
+ }
7253
+ }
7254
+ formalSessions.push({ sessionId: "current", actionHistory: input.actionHistory });
7255
+ const formalResult = formalEngine.verify(formalSessions, input.aNext);
7256
+ if (!formalResult.verified) {
7257
+ const v = formalResult.violations[0];
7258
+ return {
7259
+ allowed: false,
7260
+ reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
7261
+ riskLevel: v ? v.riskLevel : "high",
7262
+ riskType: "formal_verification",
7263
+ confidence: 90,
7264
+ source: "formal_engine",
7265
+ violatedLayers: ["2.4"]
7266
+ };
7267
+ }
7268
+ }
4712
7269
  if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
4713
7270
  for (const node of scriptChain.nodes) {
4714
7271
  if (node.error || !node.preprocessedContent) continue;
@@ -4827,7 +7384,7 @@ async function runAnalysis(input) {
4827
7384
  }
4828
7385
  return llmResult;
4829
7386
  }
4830
- var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS, _brainStore, _brainExtractor, _brainInitialized;
7387
+ var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS, _brainStore, _brainExtractor, _brainInitialized, formalEngine;
4831
7388
  var init_coordinator = __esm({
4832
7389
  "src/engine/coordinator.ts"() {
4833
7390
  "use strict";
@@ -4839,8 +7396,10 @@ var init_coordinator = __esm({
4839
7396
  init_script_content_analyzer();
4840
7397
  init_config();
4841
7398
  init_agents();
7399
+ init_scenarios();
4842
7400
  init_brain();
4843
7401
  init_reviewer();
7402
+ init_formal_engine();
4844
7403
  FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
4845
7404
  "ask_user_question",
4846
7405
  "glob",
@@ -4910,6 +7469,7 @@ var init_coordinator = __esm({
4910
7469
  _brainStore = null;
4911
7470
  _brainExtractor = null;
4912
7471
  _brainInitialized = false;
7472
+ formalEngine = new FormalEngine();
4913
7473
  }
4914
7474
  });
4915
7475
 
@@ -4970,20 +7530,20 @@ var require_file_uri_to_path = __commonJS({
4970
7530
  var rest = decodeURI(uri.substring(7));
4971
7531
  var firstSlash = rest.indexOf("/");
4972
7532
  var host = rest.substring(0, firstSlash);
4973
- var path3 = rest.substring(firstSlash + 1);
7533
+ var path6 = rest.substring(firstSlash + 1);
4974
7534
  if ("localhost" == host) host = "";
4975
7535
  if (host) {
4976
7536
  host = sep + sep + host;
4977
7537
  }
4978
- path3 = path3.replace(/^(.+)\|/, "$1:");
7538
+ path6 = path6.replace(/^(.+)\|/, "$1:");
4979
7539
  if (sep == "\\") {
4980
- path3 = path3.replace(/\//g, "\\");
7540
+ path6 = path6.replace(/\//g, "\\");
4981
7541
  }
4982
- if (/^.+\:/.test(path3)) {
7542
+ if (/^.+\:/.test(path6)) {
4983
7543
  } else {
4984
- path3 = sep + path3;
7544
+ path6 = sep + path6;
4985
7545
  }
4986
- return host + path3;
7546
+ return host + path6;
4987
7547
  }
4988
7548
  }
4989
7549
  });
@@ -4993,19 +7553,19 @@ var require_bindings = __commonJS({
4993
7553
  "node_modules/bindings/bindings.js"(exports, module) {
4994
7554
  "use strict";
4995
7555
  init_esm_shims();
4996
- var fs4 = __require("fs");
4997
- var path3 = __require("path");
4998
- var fileURLToPath10 = require_file_uri_to_path();
4999
- var join7 = path3.join;
5000
- var dirname8 = path3.dirname;
5001
- var exists = fs4.accessSync && function(path4) {
7556
+ var fs7 = __require("fs");
7557
+ var path6 = __require("path");
7558
+ var fileURLToPath11 = require_file_uri_to_path();
7559
+ var join10 = path6.join;
7560
+ var dirname11 = path6.dirname;
7561
+ var exists = fs7.accessSync && function(path7) {
5002
7562
  try {
5003
- fs4.accessSync(path4);
7563
+ fs7.accessSync(path7);
5004
7564
  } catch (e) {
5005
7565
  return false;
5006
7566
  }
5007
7567
  return true;
5008
- } || fs4.existsSync || path3.existsSync;
7568
+ } || fs7.existsSync || path6.existsSync;
5009
7569
  var defaults = {
5010
7570
  arrow: process.env.NODE_BINDINGS_ARROW || " \u2192 ",
5011
7571
  compiled: process.env.NODE_BINDINGS_COMPILED_DIR || "compiled",
@@ -5050,13 +7610,13 @@ var require_bindings = __commonJS({
5050
7610
  if (!opts.module_root) {
5051
7611
  opts.module_root = exports.getRoot(exports.getFileName());
5052
7612
  }
5053
- if (path3.extname(opts.bindings) != ".node") {
7613
+ if (path6.extname(opts.bindings) != ".node") {
5054
7614
  opts.bindings += ".node";
5055
7615
  }
5056
7616
  var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
5057
7617
  var tries = [], i = 0, l = opts.try.length, n, b, err;
5058
7618
  for (; i < l; i++) {
5059
- n = join7.apply(
7619
+ n = join10.apply(
5060
7620
  null,
5061
7621
  opts.try[i].map(function(p) {
5062
7622
  return opts[p] || p;
@@ -5107,17 +7667,17 @@ var require_bindings = __commonJS({
5107
7667
  Error.stackTraceLimit = origSTL;
5108
7668
  var fileSchema = "file://";
5109
7669
  if (fileName.indexOf(fileSchema) === 0) {
5110
- fileName = fileURLToPath10(fileName);
7670
+ fileName = fileURLToPath11(fileName);
5111
7671
  }
5112
7672
  return fileName;
5113
7673
  };
5114
7674
  exports.getRoot = function getRoot(file) {
5115
- var dir = dirname8(file), prev;
7675
+ var dir = dirname11(file), prev;
5116
7676
  while (true) {
5117
7677
  if (dir === ".") {
5118
7678
  dir = process.cwd();
5119
7679
  }
5120
- if (exists(join7(dir, "package.json")) || exists(join7(dir, "node_modules"))) {
7680
+ if (exists(join10(dir, "package.json")) || exists(join10(dir, "node_modules"))) {
5121
7681
  return dir;
5122
7682
  }
5123
7683
  if (prev === dir) {
@@ -5126,7 +7686,7 @@ var require_bindings = __commonJS({
5126
7686
  );
5127
7687
  }
5128
7688
  prev = dir;
5129
- dir = join7(dir, "..");
7689
+ dir = join10(dir, "..");
5130
7690
  }
5131
7691
  };
5132
7692
  }
@@ -5292,11 +7852,11 @@ var require_backup = __commonJS({
5292
7852
  "node_modules/better-sqlite3/lib/methods/backup.js"(exports, module) {
5293
7853
  "use strict";
5294
7854
  init_esm_shims();
5295
- var fs4 = __require("fs");
5296
- var path3 = __require("path");
7855
+ var fs7 = __require("fs");
7856
+ var path6 = __require("path");
5297
7857
  var { promisify } = __require("util");
5298
7858
  var { cppdb } = require_util();
5299
- var fsAccess = promisify(fs4.access);
7859
+ var fsAccess = promisify(fs7.access);
5300
7860
  module.exports = async function backup(filename, options) {
5301
7861
  if (options == null) options = {};
5302
7862
  if (typeof filename !== "string") throw new TypeError("Expected first argument to be a string");
@@ -5309,7 +7869,7 @@ var require_backup = __commonJS({
5309
7869
  if (typeof attachedName !== "string") throw new TypeError('Expected the "attached" option to be a string');
5310
7870
  if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
5311
7871
  if (handler != null && typeof handler !== "function") throw new TypeError('Expected the "progress" option to be a function');
5312
- await fsAccess(path3.dirname(filename)).catch(() => {
7872
+ await fsAccess(path6.dirname(filename)).catch(() => {
5313
7873
  throw new TypeError("Cannot save backup because the directory does not exist");
5314
7874
  });
5315
7875
  const isNewFile = await fsAccess(filename).then(() => false, () => true);
@@ -5318,13 +7878,13 @@ var require_backup = __commonJS({
5318
7878
  var runBackup = (backup, handler) => {
5319
7879
  let rate = 0;
5320
7880
  let useDefault = true;
5321
- return new Promise((resolve7, reject) => {
7881
+ return new Promise((resolve9, reject) => {
5322
7882
  setImmediate(function step() {
5323
7883
  try {
5324
7884
  const progress = backup.transfer(rate);
5325
7885
  if (!progress.remainingPages) {
5326
7886
  backup.close();
5327
- resolve7(progress);
7887
+ resolve9(progress);
5328
7888
  return;
5329
7889
  }
5330
7890
  if (useDefault) {
@@ -5620,8 +8180,8 @@ var require_database = __commonJS({
5620
8180
  "node_modules/better-sqlite3/lib/database.js"(exports, module) {
5621
8181
  "use strict";
5622
8182
  init_esm_shims();
5623
- var fs4 = __require("fs");
5624
- var path3 = __require("path");
8183
+ var fs7 = __require("fs");
8184
+ var path6 = __require("path");
5625
8185
  var util = require_util();
5626
8186
  var SqliteError = require_sqlite_error();
5627
8187
  var DEFAULT_ADDON;
@@ -5657,7 +8217,7 @@ var require_database = __commonJS({
5657
8217
  addon = DEFAULT_ADDON || (DEFAULT_ADDON = require_bindings()("better_sqlite3.node"));
5658
8218
  } else if (typeof nativeBinding === "string") {
5659
8219
  const requireFunc = typeof __non_webpack_require__ === "function" ? __non_webpack_require__ : __require;
5660
- addon = requireFunc(path3.resolve(nativeBinding).replace(/(\.node)?$/, ".node"));
8220
+ addon = requireFunc(path6.resolve(nativeBinding).replace(/(\.node)?$/, ".node"));
5661
8221
  } else {
5662
8222
  addon = nativeBinding;
5663
8223
  }
@@ -5665,7 +8225,7 @@ var require_database = __commonJS({
5665
8225
  addon.setErrorConstructor(SqliteError);
5666
8226
  addon.isInitialized = true;
5667
8227
  }
5668
- if (!anonymous && !fs4.existsSync(path3.dirname(filename))) {
8228
+ if (!anonymous && !fs7.existsSync(path6.dirname(filename))) {
5669
8229
  throw new TypeError("Cannot open database because the directory does not exist");
5670
8230
  }
5671
8231
  Object.defineProperties(this, {
@@ -5740,6 +8300,10 @@ var AnalyzeRequestSchema = z.object({
5740
8300
  prompt_session: z.string().default("").describe("User original prompt (most recent user message; backward compat)"),
5741
8301
  prompt_history: z.array(PromptTurnSchema).default([]).describe("Full interleaved prompt history (ordered turns, each with its triggered actions). Optional; empty = fall back to prompt_session only."),
5742
8302
  action_history: z.array(ActionItemSchema).default([]).describe("Historical action sequence"),
8303
+ session_history: z.array(z.object({
8304
+ session_id: z.string(),
8305
+ action_history: z.array(ActionItemSchema).default([])
8306
+ })).default([]).describe("Multi-session historical actions (optional): for L2.4 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
5743
8307
  a_next: ActionItemSchema.describe("Next action to be executed"),
5744
8308
  reason: z.string().default("").describe("Reason for executing this action"),
5745
8309
  os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
@@ -5766,13 +8330,33 @@ var HealthResponseSchema = z.object({
5766
8330
  // 空=未配置(独立部署或旧版),调用方可选择兼容或严格过滤。
5767
8331
  instance: z.string().default("")
5768
8332
  });
8333
+ var ReviewRequestSchema = z.object({
8334
+ agent_id: z.string().default("").describe("\u76EE\u6807 agent \u6807\u8BC6\uFF08\u51B3\u5B9A\u72B6\u6001\u5E93\u5206\u6876\uFF09"),
8335
+ db_path: z.string().default("").describe("AgentGate \u672C\u5730\u5B58\u50A8\u8DEF\u5F84\uFF08SQLite \u6587\u4EF6 \u6216 tool_calls JSON \u76EE\u5F55\uFF09\uFF1B\u4E3A\u7A7A\u5219\u7528 sessions"),
8336
+ sessions: z.array(z.object({
8337
+ session_id: z.string(),
8338
+ action_history: z.array(ActionItemSchema).default([])
8339
+ })).default([]).describe("\u591A session \u5386\u53F2\u52A8\u4F5C\uFF08\u66FF\u4EE3/\u515C\u5E95 db_path\uFF09"),
8340
+ a_next: ActionItemSchema.optional().describe("\u53EF\u9009\uFF1Aseed \u540E\u8FDE\u540C\u5F53\u524D\u52A8\u4F5C\u4E00\u8D77\u5224\u5B9A"),
8341
+ is_error: z.boolean().default(false).describe("\u5F53\u524D\u52A8\u4F5C\u6267\u884C\u662F\u5426\u5931\u8D25"),
8342
+ output_pending: z.boolean().default(false).describe("\u8FD4\u56DE\u524D\u5E8F\u94FE\u6E05\u5355 pending_prefixes")
8343
+ });
8344
+ var ReviewResponseSchema = z.object({
8345
+ agent_id: z.string().default(""),
8346
+ seeded_nodes: z.number().default(0),
8347
+ verified: z.boolean().default(true),
8348
+ violations: z.array(z.record(z.unknown())).default([]),
8349
+ pending_prefixes: z.array(z.record(z.unknown())).default([]),
8350
+ state_nodes: z.number().default(0),
8351
+ discovered_patterns: z.array(z.record(z.unknown())).default([]).describe("\u6570\u636E\u9A71\u52A8\u53D1\u73B0\uFF1A\u8DE8 session \u9891\u7E41\u5371\u9669\u7EC4\u5408\uFF08\u9884\u7F6E\u94FE\u76F2\u533A\uFF0CPatternMiner\uFF09")
8352
+ });
5769
8353
 
5770
8354
  // src/routes.ts
5771
8355
  init_coordinator();
5772
8356
 
5773
8357
  // src/actrail.ts
5774
8358
  init_esm_shims();
5775
- import * as fs3 from "fs";
8359
+ import * as fs5 from "fs";
5776
8360
  function escape(value) {
5777
8361
  return value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/=/g, "\\e");
5778
8362
  }
@@ -5793,7 +8377,7 @@ function getStoragePath() {
5793
8377
  function reportEnforcementEvent(opts) {
5794
8378
  if (!isEnabled()) return false;
5795
8379
  const storagePath = getStoragePath();
5796
- if (!fs3.existsSync(storagePath)) return false;
8380
+ if (!fs5.existsSync(storagePath)) return false;
5797
8381
  try {
5798
8382
  let Database;
5799
8383
  try {
@@ -5871,6 +8455,182 @@ function reportEnforcementEvent(opts) {
5871
8455
  }
5872
8456
  }
5873
8457
 
8458
+ // src/routes.ts
8459
+ init_formal_engine();
8460
+
8461
+ // src/engine/formal-engine/pattern_miner.ts
8462
+ init_esm_shims();
8463
+ init_builder();
8464
+ var DANGER_STAGES = /* @__PURE__ */ new Set(["PRIVESC", "EXFIL", "LISTEN"]);
8465
+ var SENSITIVE_PAIRS = /* @__PURE__ */ new Set(["CRED|LISTEN", "LISTEN|CRED"]);
8466
+ function projectStages(actionHistory) {
8467
+ const stages = [];
8468
+ let prev = null;
8469
+ for (const act of actionHistory) {
8470
+ const s = markStage(String(act.actionDetail || ""));
8471
+ if (s && s !== prev) {
8472
+ stages.push(s);
8473
+ prev = s;
8474
+ }
8475
+ }
8476
+ return stages;
8477
+ }
8478
+ function isDangerous(pat) {
8479
+ if (pat.some((s) => DANGER_STAGES.has(s))) return true;
8480
+ for (let i = 0; i < pat.length - 1; i++) {
8481
+ if (SENSITIVE_PAIRS.has(pat[i] + "|" + pat[i + 1])) return true;
8482
+ }
8483
+ return false;
8484
+ }
8485
+ function mineDangerousPatterns(sessions, minSup = 2, maxLen = 4) {
8486
+ const seqs = [];
8487
+ for (const s of sessions) {
8488
+ const sid = String(s.sessionId || "");
8489
+ const stages = projectStages(s.actionHistory || []);
8490
+ if (stages.length > 0) seqs.push([sid, stages]);
8491
+ }
8492
+ if (seqs.length === 0) return [];
8493
+ const results = /* @__PURE__ */ new Map();
8494
+ const mine = (prefix, projected) => {
8495
+ if (prefix.length > 0) {
8496
+ results.set(prefix.join("\u2192"), projected.length);
8497
+ }
8498
+ if (prefix.length >= maxLen) return;
8499
+ const itemSids = /* @__PURE__ */ new Map();
8500
+ for (const [sid, suffix] of projected) {
8501
+ const seen = /* @__PURE__ */ new Set();
8502
+ for (const it of suffix) {
8503
+ if (!seen.has(it)) {
8504
+ seen.add(it);
8505
+ if (!itemSids.has(it)) itemSids.set(it, /* @__PURE__ */ new Set());
8506
+ itemSids.get(it).add(sid);
8507
+ }
8508
+ }
8509
+ }
8510
+ for (const [it, sids] of [...itemSids.entries()].sort()) {
8511
+ if (sids.size >= minSup) {
8512
+ const next = [];
8513
+ for (const [sid, suffix] of projected) {
8514
+ const idx = suffix.indexOf(it);
8515
+ if (idx >= 0) next.push([sid, suffix.slice(idx + 1)]);
8516
+ }
8517
+ mine([...prefix, it], next);
8518
+ }
8519
+ }
8520
+ };
8521
+ mine([], seqs);
8522
+ const out = [];
8523
+ for (const [key, support] of results) {
8524
+ const pat = key.split("\u2192");
8525
+ if (pat.length < 2 || !isDangerous(pat)) continue;
8526
+ const risk = pat.some((s) => s === "PRIVESC" || s === "EXFIL") ? "high" : "medium";
8527
+ out.push({
8528
+ pattern: pat,
8529
+ support,
8530
+ risk,
8531
+ reason: `\u8DE8 session \u9891\u7E41\u51FA\u73B0\u7EC4\u5408 ${pat.join("\u2192")}\uFF08${support} \u4E2A session\uFF09\uFF0C\u9884\u7F6E\u94FE\u672A\u76F4\u63A5\u8986\u76D6\uFF0C\u7591\u4F3C\u653B\u51FB\u524D\u5E8F`
8532
+ });
8533
+ }
8534
+ out.sort((a, b) => b.support - a.support || b.pattern.length - a.pattern.length);
8535
+ return out;
8536
+ }
8537
+
8538
+ // src/agent-gate-reader.ts
8539
+ init_esm_shims();
8540
+ import * as fs6 from "fs";
8541
+ import * as os3 from "os";
8542
+ import * as path5 from "path";
8543
+ var DEFAULT_AGENTGATE_SQLITE = path5.join(
8544
+ os3.homedir(),
8545
+ ".local",
8546
+ "share",
8547
+ "agent_gate",
8548
+ "agent_gate_ts.db"
8549
+ );
8550
+ function normalizeAction(row) {
8551
+ return {
8552
+ actionType: String(row.action_type || row.tool_name || row.name || ""),
8553
+ actionDetail: String(row.action_detail || row.command || row.input || "")
8554
+ };
8555
+ }
8556
+ function readJsonDir(dirPath, limit = 1e5) {
8557
+ const sessions = /* @__PURE__ */ new Map();
8558
+ try {
8559
+ const files = fs6.readdirSync(dirPath).filter((f) => f.endsWith(".json")).sort();
8560
+ for (const f of files.slice(-200)) {
8561
+ const sid = f.replace(/\.json$/, "");
8562
+ const raw = fs6.readFileSync(path5.join(dirPath, f), "utf-8");
8563
+ const arr = JSON.parse(raw);
8564
+ if (!Array.isArray(arr)) continue;
8565
+ for (const item of arr) {
8566
+ if (!item || typeof item !== "object") continue;
8567
+ const act = normalizeAction(item);
8568
+ if (act.actionDetail) {
8569
+ const list = sessions.get(sid) || [];
8570
+ list.push(act);
8571
+ sessions.set(sid, list);
8572
+ }
8573
+ }
8574
+ let total = 0;
8575
+ for (const v of sessions.values()) total += v.length;
8576
+ if (total > limit) break;
8577
+ }
8578
+ } catch {
8579
+ }
8580
+ return [...sessions.entries()].map(([sessionId, actionHistory]) => ({ sessionId, actionHistory }));
8581
+ }
8582
+ function readSqlite(dbPath, limit = 1e5) {
8583
+ let Database;
8584
+ try {
8585
+ Database = require_lib();
8586
+ } catch {
8587
+ return [];
8588
+ }
8589
+ let db;
8590
+ try {
8591
+ db = new Database(dbPath, { readonly: true });
8592
+ const rows = db.prepare(
8593
+ `SELECT session_id, action_type, tool_name, action_detail, command, called_at
8594
+ FROM tool_calls ORDER BY called_at, id LIMIT ?`
8595
+ ).all(limit);
8596
+ const sessions = /* @__PURE__ */ new Map();
8597
+ for (const r of rows) {
8598
+ const sid = String(r.session_id || "");
8599
+ if (!sid) continue;
8600
+ const act = normalizeAction(r);
8601
+ if (!act.actionDetail) continue;
8602
+ const list = sessions.get(sid) || [];
8603
+ list.push(act);
8604
+ sessions.set(sid, list);
8605
+ }
8606
+ return [...sessions.entries()].map(([sessionId, actionHistory]) => ({ sessionId, actionHistory }));
8607
+ } catch {
8608
+ return [];
8609
+ } finally {
8610
+ try {
8611
+ db?.close();
8612
+ } catch {
8613
+ }
8614
+ }
8615
+ }
8616
+ function loadAgentGateSessions(dbPath = "") {
8617
+ if (dbPath) {
8618
+ try {
8619
+ if (fs6.statSync(dbPath).isDirectory()) {
8620
+ return readJsonDir(dbPath);
8621
+ }
8622
+ } catch {
8623
+ }
8624
+ const sql2 = readSqlite(dbPath);
8625
+ if (sql2.length > 0) return sql2;
8626
+ return readJsonDir(dbPath);
8627
+ }
8628
+ const sql = readSqlite(DEFAULT_AGENTGATE_SQLITE);
8629
+ if (sql.length > 0) return sql;
8630
+ const jsonDefault = path5.join(os3.homedir(), ".local", "share", "agent_gate", "tool_calls");
8631
+ return readJsonDir(jsonDefault);
8632
+ }
8633
+
5874
8634
  // src/routes.ts
5875
8635
  async function healthRoute(c) {
5876
8636
  return c.json(HealthResponseSchema.parse({
@@ -5886,6 +8646,7 @@ async function analyzeRoute(c) {
5886
8646
  if (!osType) {
5887
8647
  osType = process.platform === "win32" ? "windows" : "linux";
5888
8648
  }
8649
+ const agentId = req.agent_id || req.metadata?.agent_type || "";
5889
8650
  const result = await runAnalysis({
5890
8651
  sessionId: req.session_id,
5891
8652
  promptSession: req.prompt_session,
@@ -5893,6 +8654,13 @@ async function analyzeRoute(c) {
5893
8654
  actionType: h.action_type,
5894
8655
  actionDetail: h.action_detail
5895
8656
  })),
8657
+ sessionHistory: (req.session_history || []).map((s) => ({
8658
+ sessionId: s.session_id,
8659
+ actionHistory: (s.action_history || []).map((h) => ({
8660
+ actionType: h.action_type,
8661
+ actionDetail: h.action_detail
8662
+ }))
8663
+ })),
5896
8664
  aNext: {
5897
8665
  actionType: req.a_next.action_type,
5898
8666
  actionDetail: req.a_next.action_detail
@@ -5900,10 +8668,9 @@ async function analyzeRoute(c) {
5900
8668
  reason: req.reason,
5901
8669
  osType,
5902
8670
  cwd: req.cwd,
5903
- // agent_id 透传:优先顶层;为空兜底 metadata.agent_type(AgentGate V1
5904
- // normalizer 历史上只把 agent_type 塞进 metadata,顶层 agent_id 为空 →
5905
- // 定制规则不生效。AgentGate 已修,此处兜底防御其他只传 metadata 的调用方)。
5906
- agentId: req.agent_id || req.metadata?.agent_type || "",
8671
+ // agent_id:AgentGate V1 normalizer 历史上把 agent_type 塞 metadata,
8672
+ // 顶层为空时兜底(OpenDesk 的 agent-moss 需 agent_id 加载专属规则)。
8673
+ agentId,
5907
8674
  llmConfig: req.metadata?.llm_config,
5908
8675
  llmLogPath: req.metadata?.llm_log_path
5909
8676
  });
@@ -5925,6 +8692,40 @@ async function analyzeRoute(c) {
5925
8692
  });
5926
8693
  } catch {
5927
8694
  }
8695
+ const meta = req.metadata || {};
8696
+ const incEnabled = String(meta.stateful_incremental || "").toLowerCase();
8697
+ if (incEnabled === "1" || incEnabled === "true" || incEnabled === "yes" || incEnabled === "on") {
8698
+ try {
8699
+ const store = getChainStateStoreFor(agentId);
8700
+ const inc = store.applyAction(agentId, {
8701
+ actionType: req.a_next.action_type,
8702
+ actionDetail: req.a_next.action_detail
8703
+ }, req.session_id, Boolean(meta.is_error));
8704
+ const judgment = incMatchedToDeny(inc);
8705
+ if (judgment) {
8706
+ const layers = [...result.violatedLayers];
8707
+ if (!layers.includes("2.4-stateful")) layers.push("2.4-stateful");
8708
+ console.error(
8709
+ `[AgentMoss] stateful \u589E\u91CF\u62E6\u622A: agent=${agentId || "-"} property=${judgment.property_id} risk=${judgment.risk_level}`
8710
+ );
8711
+ return c.json(
8712
+ AnalyzeResponseSchema.parse({
8713
+ decision: "Deny",
8714
+ reason: judgment.reason,
8715
+ risk_level: judgment.risk_level,
8716
+ risk_type: "formal_verification",
8717
+ violated_layers: layers,
8718
+ policy: "",
8719
+ violated_policy: "",
8720
+ confidence: 90,
8721
+ analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10
8722
+ })
8723
+ );
8724
+ }
8725
+ } catch (err) {
8726
+ console.error("[AgentMoss] stateful incremental error (non-fatal):", err);
8727
+ }
8728
+ }
5928
8729
  return c.json(
5929
8730
  AnalyzeResponseSchema.parse({
5930
8731
  decision: result.allowed ? "Allow" : "Deny",
@@ -5949,6 +8750,94 @@ async function analyzeRoute(c) {
5949
8750
  return c.json({ error: "Internal error", detail: String(err) }, 500);
5950
8751
  }
5951
8752
  }
8753
+ var reviewStore = null;
8754
+ function getChainStateStoreFor(_agentId) {
8755
+ if (!reviewStore) reviewStore = new ChainStateStore();
8756
+ return reviewStore;
8757
+ }
8758
+ function incMatchedToDeny(inc) {
8759
+ if (inc.matched) {
8760
+ return {
8761
+ property_id: "cross-" + inc.matched.chainId,
8762
+ reason: inc.matched.reason,
8763
+ risk_level: inc.matched.riskLevel
8764
+ };
8765
+ }
8766
+ if (inc.exfil && inc.exfil.length > 0) {
8767
+ return {
8768
+ property_id: "sensitive-data-exfil",
8769
+ reason: inc.exfil[0].reason,
8770
+ risk_level: inc.exfil[0].risk_level
8771
+ };
8772
+ }
8773
+ return null;
8774
+ }
8775
+ async function reviewRoute(c) {
8776
+ const t0 = performance.now();
8777
+ try {
8778
+ const body = await c.req.json();
8779
+ const req = ReviewRequestSchema.parse(body);
8780
+ const agentId = req.agent_id || "default";
8781
+ const store = getChainStateStoreFor(agentId);
8782
+ let tracker = store.load(agentId);
8783
+ if (!tracker) {
8784
+ tracker = new StatefulChainTracker(agentId);
8785
+ }
8786
+ let sessions = [];
8787
+ if (req.db_path) {
8788
+ sessions = loadAgentGateSessions(req.db_path);
8789
+ if (sessions.length === 0) sessions = (req.sessions || []).map((s) => ({
8790
+ sessionId: s.session_id,
8791
+ actionHistory: s.action_history.map((a) => ({ actionType: a.action_type, actionDetail: a.action_detail }))
8792
+ }));
8793
+ } else {
8794
+ sessions = (req.sessions || []).map((s) => ({
8795
+ sessionId: s.session_id,
8796
+ actionHistory: s.action_history.map((a) => ({ actionType: a.action_type, actionDetail: a.action_detail }))
8797
+ }));
8798
+ }
8799
+ const added = sessions.length > 0 ? tracker.seed(sessions) : 0;
8800
+ store.save(tracker);
8801
+ let verified = true;
8802
+ const violations = [];
8803
+ if (sessions.length > 0 || tracker.nodes.length > 0) {
8804
+ const eng = new FormalEngine();
8805
+ const full = eng.verify(
8806
+ sessions.map((s) => ({ sessionId: s.sessionId, actionHistory: s.actionHistory })),
8807
+ req.a_next ? { actionType: req.a_next.action_type, actionDetail: req.a_next.action_detail } : void 0
8808
+ );
8809
+ verified = full.verified;
8810
+ for (const v of full.violations) {
8811
+ violations.push({ property_id: v.propertyId, reason: v.reason, risk_level: v.riskLevel });
8812
+ }
8813
+ }
8814
+ const pending = tracker.pendingPrefixes.map((p) => ({
8815
+ chain_id: p.chainId,
8816
+ matched: p.matched,
8817
+ next_stage: p.nextStage,
8818
+ risk_level: p.riskLevel
8819
+ }));
8820
+ let discovered = [];
8821
+ if (sessions.length > 0) {
8822
+ try {
8823
+ discovered = mineDangerousPatterns(sessions, 2);
8824
+ } catch {
8825
+ }
8826
+ }
8827
+ return c.json(ReviewResponseSchema.parse({
8828
+ agent_id: agentId,
8829
+ seeded_nodes: added,
8830
+ verified,
8831
+ violations,
8832
+ pending_prefixes: pending,
8833
+ state_nodes: tracker.nodes.length,
8834
+ discovered_patterns: discovered
8835
+ }));
8836
+ } catch (err) {
8837
+ console.error("[AgentMoss] Review error:", err);
8838
+ return c.json({ error: "Internal error", detail: String(err) }, 500);
8839
+ }
8840
+ }
5952
8841
 
5953
8842
  // src/console/index.ts
5954
8843
  init_esm_shims();
@@ -5958,11 +8847,11 @@ init_esm_shims();
5958
8847
  init_runtime_config();
5959
8848
  init_token_stats();
5960
8849
  import { Hono } from "hono";
5961
- import { readFileSync as readFileSync11, existsSync as existsSync11 } from "fs";
5962
- import { join as join6, dirname as dirname7 } from "path";
5963
- import { fileURLToPath as fileURLToPath8 } from "url";
5964
- var __dirname7 = dirname7(fileURLToPath8(import.meta.url));
5965
- var STATIC_DIR = join6(__dirname7, "static");
8850
+ import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
8851
+ import { join as join9, dirname as dirname10 } from "path";
8852
+ import { fileURLToPath as fileURLToPath9 } from "url";
8853
+ var __dirname8 = dirname10(fileURLToPath9(import.meta.url));
8854
+ var STATIC_DIR = join9(__dirname8, "static");
5966
8855
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
5967
8856
  function getConsoleToken() {
5968
8857
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -5986,9 +8875,9 @@ function createConsoleRouter() {
5986
8875
  const console2 = new Hono();
5987
8876
  console2.use("/api/*", authMiddleware());
5988
8877
  const serveIndex = (c) => {
5989
- const indexFile = join6(STATIC_DIR, "index.html");
5990
- if (existsSync11(indexFile)) {
5991
- return c.html(readFileSync11(indexFile, "utf-8"));
8878
+ const indexFile = join9(STATIC_DIR, "index.html");
8879
+ if (existsSync14(indexFile)) {
8880
+ return c.html(readFileSync15(indexFile, "utf-8"));
5992
8881
  }
5993
8882
  return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
5994
8883
  };
@@ -6079,13 +8968,13 @@ function createConsoleRouter() {
6079
8968
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
6080
8969
  return c.json({ error: "invalid skill_id" }, 400);
6081
8970
  }
6082
- const skillPath = join6(dirname7(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
6083
- if (!existsSync11(skillPath)) {
6084
- const builtinPath = join6(__dirname7, "..", "..", "agent_moss", "skills", `${skillId}.md`);
6085
- if (existsSync11(builtinPath)) return c.text(readFileSync11(builtinPath, "utf-8"));
8971
+ const skillPath = join9(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
8972
+ if (!existsSync14(skillPath)) {
8973
+ const builtinPath = join9(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
8974
+ if (existsSync14(builtinPath)) return c.text(readFileSync15(builtinPath, "utf-8"));
6086
8975
  return c.json({ error: "skill not found" }, 404);
6087
8976
  }
6088
- return c.text(readFileSync11(skillPath, "utf-8"));
8977
+ return c.text(readFileSync15(skillPath, "utf-8"));
6089
8978
  });
6090
8979
  console2.get("/api/agents", (c) => {
6091
8980
  const rc = loadRuntimeConfig();
@@ -6125,6 +9014,49 @@ function createConsoleRouter() {
6125
9014
  const rc = updateAgentCategoryEnabled(b.agent_id, b.category, b.enabled);
6126
9015
  return c.json({ agents: getAgentRules(rc) });
6127
9016
  });
9017
+ const enabledScenarioIds = () => (process.env.AGENT_MOSS_SCENARIO || "").split(",").map((s) => s.trim()).filter(Boolean);
9018
+ console2.get("/api/scenarios", (c) => {
9019
+ const all = getScenarioRules(loadRuntimeConfig());
9020
+ const enabled = enabledScenarioIds();
9021
+ if (!enabled.length) return c.json({ scenarios: {}, enabled: [] });
9022
+ const shown = {};
9023
+ for (const sid of enabled) if (all[sid]) shown[sid] = all[sid];
9024
+ return c.json({ scenarios: shown, enabled });
9025
+ });
9026
+ console2.put("/api/scenarios/rules/enabled", async (c) => {
9027
+ const b = await c.req.json();
9028
+ const fp = {};
9029
+ if (b.value) fp.value = b.value;
9030
+ if (b.pattern) fp.pattern = b.pattern;
9031
+ if (b.keyword) fp.keyword = b.keyword;
9032
+ if (b.path) fp.path = b.path;
9033
+ const rc = updateScenarioRuleEnabled(b.scenario_id, b.category, fp, b.enabled);
9034
+ return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
9035
+ });
9036
+ console2.post("/api/scenarios/rules", async (c) => {
9037
+ const b = await c.req.json();
9038
+ const rule = {};
9039
+ for (const [k, v] of Object.entries(b)) {
9040
+ if (!["scenario_id", "category"].includes(k) && v !== void 0 && v !== null) rule[k] = v;
9041
+ }
9042
+ const rc = addScenarioRule(b.scenario_id, b.category, rule);
9043
+ return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
9044
+ });
9045
+ console2.delete("/api/scenarios/rules", async (c) => {
9046
+ const b = await c.req.json();
9047
+ const fp = {};
9048
+ if (b.value) fp.value = b.value;
9049
+ if (b.pattern) fp.pattern = b.pattern;
9050
+ if (b.keyword) fp.keyword = b.keyword;
9051
+ if (b.path) fp.path = b.path;
9052
+ const rc = deleteScenarioRule(b.scenario_id, b.category, fp);
9053
+ return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
9054
+ });
9055
+ console2.put("/api/scenarios/categories/enabled", async (c) => {
9056
+ const b = await c.req.json();
9057
+ const rc = updateScenarioCategoryEnabled(b.scenario_id, b.category, b.enabled);
9058
+ return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
9059
+ });
6128
9060
  console2.get("/api/config", (c) => {
6129
9061
  const rc = loadRuntimeConfig();
6130
9062
  return c.json(rc);
@@ -6164,18 +9096,19 @@ function createApp() {
6164
9096
  app.use("*", logger());
6165
9097
  app.get("/api/v1/health", healthRoute);
6166
9098
  app.post("/api/v1/analyze", analyzeRoute);
9099
+ app.post("/api/v1/review", reviewRoute);
6167
9100
  app.route("/console", createConsoleRouter());
6168
9101
  app.get("/console/", (c) => c.redirect("/console"));
6169
9102
  return app;
6170
9103
  }
6171
9104
  async function findFreePort(startPort, host = "127.0.0.1", scan = 100) {
6172
9105
  for (let port = startPort; port < startPort + scan; port++) {
6173
- const free = await new Promise((resolve7) => {
9106
+ const free = await new Promise((resolve9) => {
6174
9107
  const server = net.createServer();
6175
9108
  server.listen(port, host, () => {
6176
- server.close(() => resolve7(true));
9109
+ server.close(() => resolve9(true));
6177
9110
  });
6178
- server.on("error", () => resolve7(false));
9111
+ server.on("error", () => resolve9(false));
6179
9112
  });
6180
9113
  if (free) {
6181
9114
  if (port !== startPort) {
@@ -6200,7 +9133,7 @@ async function startServer(host = "127.0.0.1", port = 9090) {
6200
9133
  }
6201
9134
 
6202
9135
  // src/cli.ts
6203
- import { fileURLToPath as fileURLToPath9 } from "url";
9136
+ import { fileURLToPath as fileURLToPath10 } from "url";
6204
9137
  import { realpathSync } from "fs";
6205
9138
  function printHelp() {
6206
9139
  console.log(`AgentMoss security analysis engine (TypeScript v${version})
@@ -6229,7 +9162,7 @@ async function main() {
6229
9162
  switch (command) {
6230
9163
  case "init": {
6231
9164
  const output = args[2] || "input.json";
6232
- const fs4 = await import("fs");
9165
+ const fs7 = await import("fs");
6233
9166
  const template = {
6234
9167
  session_id: "my-session-001",
6235
9168
  prompt_session: "Read /var/log/app.log and analyze errors",
@@ -6239,7 +9172,7 @@ async function main() {
6239
9172
  os_type: "",
6240
9173
  cwd: "/home/user/project"
6241
9174
  };
6242
- fs4.writeFileSync(output, JSON.stringify(template, null, 2), "utf-8");
9175
+ fs7.writeFileSync(output, JSON.stringify(template, null, 2), "utf-8");
6243
9176
  console.log(`Template written to ${output}`);
6244
9177
  break;
6245
9178
  }
@@ -6248,11 +9181,11 @@ async function main() {
6248
9181
  console.error("Usage: agent-moss analyze <input.json>");
6249
9182
  process.exit(1);
6250
9183
  }
6251
- const fs4 = await import("fs");
6252
- const path3 = await import("path");
9184
+ const fs7 = await import("fs");
9185
+ const path6 = await import("path");
6253
9186
  const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_coordinator(), coordinator_exports));
6254
- const filePath = path3.resolve(args[1]);
6255
- const data = JSON.parse(fs4.readFileSync(filePath, "utf-8"));
9187
+ const filePath = path6.resolve(args[1]);
9188
+ const data = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
6256
9189
  const result = await runAnalysis2({
6257
9190
  sessionId: data.session_id || "unknown",
6258
9191
  promptSession: data.prompt_session || "",
@@ -6289,9 +9222,9 @@ async function main() {
6289
9222
  case "install": {
6290
9223
  const enable = args.includes("--enable");
6291
9224
  const { execSync } = await import("child_process");
6292
- const fs4 = await import("fs");
6293
- const path3 = await import("path");
6294
- const { fileURLToPath: fileURLToPath10 } = await import("url");
9225
+ const fs7 = await import("fs");
9226
+ const path6 = await import("path");
9227
+ const { fileURLToPath: fileURLToPath11 } = await import("url");
6295
9228
  if (process.getuid?.() !== 0) {
6296
9229
  try {
6297
9230
  execSync("sudo -n true", { stdio: "ignore", timeout: 5e3 });
@@ -6306,23 +9239,23 @@ async function main() {
6306
9239
  execSync(sudoArgs.join(" "), { stdio: "inherit" });
6307
9240
  process.exit(0);
6308
9241
  }
6309
- const __filename6 = fileURLToPath10(import.meta.url);
6310
- const __dirname8 = path3.dirname(__filename6);
6311
- let srcPath = path3.join(__dirname8, "data", "agent_moss.service");
6312
- if (!fs4.existsSync(srcPath)) {
6313
- srcPath = path3.join(__dirname8, "..", "src", "data", "agent_moss.service");
9242
+ const __filename7 = fileURLToPath11(import.meta.url);
9243
+ const __dirname9 = path6.dirname(__filename7);
9244
+ let srcPath = path6.join(__dirname9, "data", "agent_moss.service");
9245
+ if (!fs7.existsSync(srcPath)) {
9246
+ srcPath = path6.join(__dirname9, "..", "src", "data", "agent_moss.service");
6314
9247
  }
6315
- if (!fs4.existsSync(srcPath)) {
9248
+ if (!fs7.existsSync(srcPath)) {
6316
9249
  console.error("\u9519\u8BEF\uFF1A\u627E\u4E0D\u5230 agent_moss.service \u6A21\u677F\u6587\u4EF6");
6317
9250
  process.exit(1);
6318
9251
  }
6319
9252
  const which = execSync("which agent-moss 2>/dev/null || true", { encoding: "utf-8" }).trim();
6320
- const binPath = which || `${process.execPath} ${path3.join(__dirname8, "cli.js")}`;
6321
- let content = fs4.readFileSync(srcPath, "utf-8");
9253
+ const binPath = which || `${process.execPath} ${path6.join(__dirname9, "cli.js")}`;
9254
+ let content = fs7.readFileSync(srcPath, "utf-8");
6322
9255
  content = content.replace("__AGENT_MOSS_BIN__", binPath);
6323
9256
  const dst = "/etc/systemd/system/agent-moss.service";
6324
- fs4.writeFileSync(dst, content, "utf-8");
6325
- fs4.chmodSync(dst, 420);
9257
+ fs7.writeFileSync(dst, content, "utf-8");
9258
+ fs7.chmodSync(dst, 420);
6326
9259
  console.log(`\u2705 \u5DF2\u5B89\u88C5 systemd service: ${dst}`);
6327
9260
  execSync("systemctl daemon-reload", { stdio: "inherit" });
6328
9261
  console.log("\u2705 systemctl daemon-reload \u5B8C\u6210");
@@ -6361,13 +9294,13 @@ async function main() {
6361
9294
  let version2 = "";
6362
9295
  for (let port = 9090; port <= 9095; port++) {
6363
9296
  try {
6364
- const data = await new Promise((resolve7, reject) => {
9297
+ const data = await new Promise((resolve9, reject) => {
6365
9298
  const req = http.get(`http://127.0.0.1:${port}/api/v1/health`, { timeout: 2e3 }, (res) => {
6366
9299
  let body = "";
6367
9300
  res.on("data", (chunk) => body += chunk);
6368
9301
  res.on("end", () => {
6369
9302
  try {
6370
- resolve7(JSON.parse(body));
9303
+ resolve9(JSON.parse(body));
6371
9304
  } catch {
6372
9305
  reject();
6373
9306
  }
@@ -6395,13 +9328,13 @@ async function main() {
6395
9328
  console.log(` Systemd: \u672A\u6CE8\u518C\uFF08\u8FD0\u884C agent-moss install --enable \u5B89\u88C5\uFF09`);
6396
9329
  }
6397
9330
  try {
6398
- const data = await new Promise((resolve7, reject) => {
9331
+ const data = await new Promise((resolve9, reject) => {
6399
9332
  const req = http.get(`http://127.0.0.1:${foundPort}/console/api/config`, { timeout: 3e3 }, (res) => {
6400
9333
  let body = "";
6401
9334
  res.on("data", (chunk) => body += chunk);
6402
9335
  res.on("end", () => {
6403
9336
  try {
6404
- resolve7(JSON.parse(body));
9337
+ resolve9(JSON.parse(body));
6405
9338
  } catch {
6406
9339
  reject();
6407
9340
  }
@@ -6448,9 +9381,9 @@ async function main() {
6448
9381
  var isMain = (() => {
6449
9382
  try {
6450
9383
  if (!process.argv[1]) return false;
6451
- const thisFile = realpathSync(fileURLToPath9(import.meta.url));
9384
+ const thisFile = realpathSync(fileURLToPath10(import.meta.url));
6452
9385
  const argv1 = realpathSync(
6453
- process.argv[1].startsWith("file://") ? fileURLToPath9(process.argv[1]) : process.argv[1]
9386
+ process.argv[1].startsWith("file://") ? fileURLToPath10(process.argv[1]) : process.argv[1]
6454
9387
  );
6455
9388
  return thisFile === argv1;
6456
9389
  } catch {