@kenhkl/agent-moss 0.16.14 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,6 +25,10 @@ var init_esm_shims = __esm({
25
25
  import { join } from "path";
26
26
  import { homedir } from "os";
27
27
  import { readFileSync } from "fs";
28
+ function isSafeId(value) {
29
+ if (!value || value.length > ID_MAX_LEN) return false;
30
+ return ID_PATTERN.test(value);
31
+ }
28
32
  function getRulesRoot() {
29
33
  const raw = (process.env[ENV_RULES_ROOT] ?? "").trim();
30
34
  return raw ? raw : null;
@@ -98,7 +102,7 @@ function getTokenStatsPath() {
98
102
  }
99
103
  return join(getDataRoot(), "agent_moss_token_stats.json");
100
104
  }
101
- var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT, ENV_RULES_ROOT;
105
+ var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT, ENV_RULES_ROOT, ID_MAX_LEN, ID_PATTERN;
102
106
  var init_paths = __esm({
103
107
  "src/infra/paths.ts"() {
104
108
  "use strict";
@@ -107,6 +111,8 @@ var init_paths = __esm({
107
111
  ENV_INSTANCE = "AGENT_MOSS_INSTANCE";
108
112
  DEFAULT_DATA_ROOT = join(homedir(), ".config", "agentmoss");
109
113
  ENV_RULES_ROOT = "AGENT_MOSS_RULES_ROOT";
114
+ ID_MAX_LEN = 64;
115
+ ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
110
116
  }
111
117
  });
112
118
 
@@ -228,7 +234,7 @@ function parseDirEnv(envName) {
228
234
  function rulesDirs() {
229
235
  if (isBuiltinOnly()) return [];
230
236
  const dirs = [];
231
- for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
237
+ for (const env2 of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env2));
232
238
  dirs.push(resolve(dataRootDir(), "rules"));
233
239
  return [...new Set(dirs)];
234
240
  }
@@ -273,6 +279,7 @@ function loadJson(name) {
273
279
  }
274
280
  }
275
281
  function resolveScenarioRulesFile(scenarioId) {
282
+ if (!isSafeId(scenarioId)) return null;
276
283
  const rel = join2("scenarios", scenarioId, "rules.json");
277
284
  for (const dir of rulesDirs()) {
278
285
  const p = resolve(dir, rel);
@@ -1282,7 +1289,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1282
1289
  targetPaths.push(...segPathsFiltered);
1283
1290
  }
1284
1291
  }
1285
- const uniqueTargetPaths = [...new Set(targetPaths)];
1292
+ const uniqueTargetPaths = [...new Set(targetPaths)].filter((p) => !PSEUDO_FS_PREFIXES.some((pfx) => p.startsWith(pfx)));
1286
1293
  if (uniqueTargetPaths.length === 0) return null;
1287
1294
  const historyReadPaths = /* @__PURE__ */ new Set();
1288
1295
  for (const action of actionHistory) {
@@ -1461,7 +1468,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1461
1468
  violatedLayers: ["L2.1"]
1462
1469
  };
1463
1470
  }
1464
- var _LOGIC, _HEURISTIC, EXCLUDED_PREFIXES, TEMP_DIRS, ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, ROOT_SENSITIVE_PATHS_WINDOWS, HOME_SENSITIVE_PATHS_WINDOWS, CREDENTIAL_PATHS_WINDOWS, ROOT_SENSITIVE_PATHS_MACOS, HOME_SENSITIVE_PATHS_MACOS, CREDENTIAL_PATHS_MACOS, ROOT_SENSITIVE_PATHS_HARMONY, HOME_SENSITIVE_PATHS_HARMONY, CREDENTIAL_PATHS_HARMONY, _scopeIndex, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, SED_INPLACE_RE, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW, _IFA, IFA_READ_KEYWORDS, IFA_WRITE_KEYWORDS, IFA_EXEC_KEYWORDS, IFA_OPEN_KEYWORDS;
1471
+ var _LOGIC, _HEURISTIC, EXCLUDED_PREFIXES, TEMP_DIRS, ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, ROOT_SENSITIVE_PATHS_WINDOWS, HOME_SENSITIVE_PATHS_WINDOWS, CREDENTIAL_PATHS_WINDOWS, ROOT_SENSITIVE_PATHS_MACOS, HOME_SENSITIVE_PATHS_MACOS, CREDENTIAL_PATHS_MACOS, ROOT_SENSITIVE_PATHS_HARMONY, HOME_SENSITIVE_PATHS_HARMONY, CREDENTIAL_PATHS_HARMONY, _scopeIndex, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, PSEUDO_FS_PREFIXES, WRITE_KEYWORDS, SED_INPLACE_RE, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW, _IFA, IFA_READ_KEYWORDS, IFA_WRITE_KEYWORDS, IFA_EXEC_KEYWORDS, IFA_OPEN_KEYWORDS;
1465
1472
  var init_logic_rules = __esm({
1466
1473
  "src/engine/logic-rules.ts"() {
1467
1474
  "use strict";
@@ -1508,6 +1515,7 @@ var init_logic_rules = __esm({
1508
1515
  "cat"
1509
1516
  ]);
1510
1517
  NON_WRITE_TOOL_PREFIXES = _LOGIC.non_write_tool_prefixes ?? [];
1518
+ PSEUDO_FS_PREFIXES = ["/sys/", "/proc/", "/dev/", "/run/"];
1511
1519
  WRITE_KEYWORDS = ["write", "\u5199\u5165", "save", "\u4FDD\u5B58", "modify", "\u4FEE\u6539", "update", "\u66F4\u65B0", "delete", "\u5220\u9664", "remove", "\u79FB\u9664", "create", "\u521B\u5EFA", "append", "\u8FFD\u52A0", "truncate", "\u622A\u65AD", "overwrite", "\u8986\u76D6", "replace", "\u66FF\u6362", "edit"];
1512
1520
  SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
1513
1521
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
@@ -2122,7 +2130,7 @@ function _l2SensitivePathsToRules() {
2122
2130
  const all = [...root, ...home, ...credential];
2123
2131
  for (const sp of all) {
2124
2132
  const os3 = String(sp.os || "linux").toLowerCase();
2125
- const denyMode = _deriveDenyMode({
2133
+ const denyMode = sp.denyMode || _deriveDenyMode({
2126
2134
  deny_mode: sp.deny_mode,
2127
2135
  credential: sp.credential,
2128
2136
  read_only: sp.read_only
@@ -3509,6 +3517,11 @@ function resolveAgentsDir() {
3509
3517
  }
3510
3518
  function getAgentExtensions(agentId) {
3511
3519
  if (!agentId) return null;
3520
+ if (!isSafeId(agentId)) {
3521
+ console.warn(`[AgentMoss agents] \u975E\u6CD5 agent_id \u5DF2\u5FFD\u7565\uFF08\u542B\u8DEF\u5F84\u5B57\u7B26\u6216\u8D85\u957F\uFF09: ${agentId.slice(0, 120)}`);
3522
+ return null;
3523
+ }
3524
+ if (cache.size >= CACHE_MAX) cache.clear();
3512
3525
  const fp = getRuleSourceFingerprint();
3513
3526
  const cached2 = cache.get(agentId);
3514
3527
  if (cached2 && cached2.fp === fp) return cached2.ext;
@@ -3561,7 +3574,7 @@ function mergeAgentExtensions(...exts) {
3561
3574
  extra_skills: nonNull.flatMap((e) => e.extra_skills || [])
3562
3575
  };
3563
3576
  }
3564
- var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
3577
+ var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache, CACHE_MAX;
3565
3578
  var init_agents = __esm({
3566
3579
  "src/agents/index.ts"() {
3567
3580
  "use strict";
@@ -3573,6 +3586,7 @@ var init_agents = __esm({
3573
3586
  AGENTS_DIR_PROD = resolve4(__dirname5, "agents");
3574
3587
  AGENTS_DIR_DEV_FALLBACK = resolve4(__dirname5, "../../../agent_moss/agents");
3575
3588
  cache = /* @__PURE__ */ new Map();
3589
+ CACHE_MAX = 512;
3576
3590
  }
3577
3591
  });
3578
3592
 
@@ -3589,6 +3603,11 @@ function resolveScenariosDir() {
3589
3603
  }
3590
3604
  function getScenarioExtensions(scenarioId) {
3591
3605
  if (!scenarioId) return null;
3606
+ if (!isSafeId(scenarioId)) {
3607
+ console.warn(`[AgentMoss scenarios] \u975E\u6CD5 scenario_id \u5DF2\u5FFD\u7565\uFF08\u542B\u8DEF\u5F84\u5B57\u7B26\u6216\u8D85\u957F\uFF09: ${scenarioId.slice(0, 120)}`);
3608
+ return null;
3609
+ }
3610
+ if (cache2.size >= CACHE_MAX2) cache2.clear();
3592
3611
  const fp = getRuleSourceFingerprint();
3593
3612
  const cached2 = cache2.get(scenarioId);
3594
3613
  if (cached2 && cached2.fp === fp) return cached2.ext;
@@ -3658,7 +3677,7 @@ function getScenarioExtensionsMulti(scenarioSpec) {
3658
3677
  if (nonNull.length === 0) return null;
3659
3678
  return mergeAgentExtensions(...nonNull);
3660
3679
  }
3661
- var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
3680
+ var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2, CACHE_MAX2;
3662
3681
  var init_scenarios = __esm({
3663
3682
  "src/scenarios/index.ts"() {
3664
3683
  "use strict";
@@ -3672,6 +3691,7 @@ var init_scenarios = __esm({
3672
3691
  SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
3673
3692
  SCENARIOS_DIR_DEV_FALLBACK = resolve5(__dirname6, "../../../agent_moss/scenarios");
3674
3693
  cache2 = /* @__PURE__ */ new Map();
3694
+ CACHE_MAX2 = 512;
3675
3695
  }
3676
3696
  });
3677
3697
 
@@ -4560,16 +4580,16 @@ __export(token_stats_exports, {
4560
4580
  recordTokenUsage: () => recordTokenUsage,
4561
4581
  resetTokenStats: () => resetTokenStats
4562
4582
  });
4563
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
4583
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
4564
4584
  function getStatsPath() {
4565
4585
  const envPath = process.env[ENV_STATS_PATH];
4566
4586
  if (envPath) return envPath;
4567
4587
  return getTokenStatsPath();
4568
4588
  }
4569
4589
  function loadAllRecords(statsPath) {
4570
- if (!existsSync11(statsPath)) return [];
4590
+ if (!existsSync12(statsPath)) return [];
4571
4591
  try {
4572
- const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
4592
+ const data = JSON.parse(readFileSync12(statsPath, "utf-8"));
4573
4593
  if (Array.isArray(data)) return data;
4574
4594
  if (data && Array.isArray(data.records)) return data.records;
4575
4595
  return [];
@@ -4580,7 +4600,7 @@ function loadAllRecords(statsPath) {
4580
4600
  function saveRecords(statsPath, records) {
4581
4601
  try {
4582
4602
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
4583
- if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
4603
+ if (dir && !existsSync12(dir)) mkdirSync3(dir, { recursive: true });
4584
4604
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
4585
4605
  } catch (e) {
4586
4606
  }
@@ -4707,7 +4727,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
4707
4727
  }
4708
4728
  function resetTokenStats() {
4709
4729
  const statsPath = getStatsPath();
4710
- if (existsSync11(statsPath)) {
4730
+ if (existsSync12(statsPath)) {
4711
4731
  try {
4712
4732
  unlinkSync2(statsPath);
4713
4733
  } catch {
@@ -4735,17 +4755,113 @@ init_inline_analyzer();
4735
4755
 
4736
4756
  // src/engine/llm-analyzer.ts
4737
4757
  init_esm_shims();
4758
+
4759
+ // src/infra/security-policy.ts
4760
+ init_esm_shims();
4761
+ init_paths();
4762
+ import { chmodSync, closeSync, existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync7, readSync, realpathSync, writeSync } from "fs";
4763
+ import { homedir as homedir2, tmpdir } from "os";
4764
+ import { basename, isAbsolute, join as join7, relative, resolve as resolve6, dirname as dirname6 } from "path";
4765
+ var TRUTHY2 = ["1", "true", "yes", "on"];
4766
+ function env(name, dflt = "") {
4767
+ return (process.env[name] ?? dflt).trim();
4768
+ }
4769
+ function splitList(raw) {
4770
+ return raw.replace(/;/g, ",").split(",").map((x) => x.trim()).filter(Boolean);
4771
+ }
4772
+ function homeDir() {
4773
+ try {
4774
+ return homedir2();
4775
+ } catch {
4776
+ return tmpdir();
4777
+ }
4778
+ }
4779
+ function logRoots() {
4780
+ const raw = env("AGENT_MOSS_LOG_ROOTS");
4781
+ if (raw) return splitList(raw);
4782
+ return [.../* @__PURE__ */ new Set([homeDir(), "/tmp", tmpdir()])];
4783
+ }
4784
+ function realpathLoose(p) {
4785
+ const abs = resolve6(p.replace(/^~(?=$|\/)/, homeDir()));
4786
+ try {
4787
+ return realpathSync(abs);
4788
+ } catch {
4789
+ }
4790
+ const parts = [];
4791
+ let cur = abs;
4792
+ for (; ; ) {
4793
+ const parent = dirname6(cur);
4794
+ if (parent === cur) return null;
4795
+ parts.unshift(basename(cur));
4796
+ cur = parent;
4797
+ try {
4798
+ return join7(realpathSync(cur), ...parts);
4799
+ } catch {
4800
+ }
4801
+ }
4802
+ }
4803
+ function pathWithin(p, roots) {
4804
+ if (!p) return true;
4805
+ const target = realpathLoose(p);
4806
+ if (target === null) return false;
4807
+ for (const root of roots) {
4808
+ const rootReal = realpathLoose(root);
4809
+ if (rootReal === null) continue;
4810
+ const rel = relative(rootReal, target);
4811
+ if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) return true;
4812
+ }
4813
+ return false;
4814
+ }
4815
+ var BLOCKED_HOSTS = ["169.254.169.254", "metadata.google.internal", "metadata.tencentyun.com"];
4816
+ function validateLlmBaseUrl(url, allowPrivate) {
4817
+ if (!url) return [true, ""];
4818
+ let u;
4819
+ try {
4820
+ u = new URL(url);
4821
+ } catch {
4822
+ return [false, "baseUrl \u4E0D\u662F\u5408\u6CD5 URL"];
4823
+ }
4824
+ if (u.protocol !== "http:" && u.protocol !== "https:") {
4825
+ return [false, `baseUrl \u534F\u8BAE\u4E0D\u53D7\u652F\u6301: ${u.protocol}`];
4826
+ }
4827
+ const host = (u.hostname || "").toLowerCase();
4828
+ if (!host) return [false, "baseUrl \u7F3A\u5C11\u4E3B\u673A\u540D"];
4829
+ if (BLOCKED_HOSTS.includes(host)) return [false, `baseUrl \u6307\u5411\u4E91\u5143\u6570\u636E\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
4830
+ if (/^169\.254\.\d{1,3}\.\d{1,3}$/.test(host)) {
4831
+ return [false, `baseUrl \u6307\u5411\u94FE\u8DEF\u672C\u5730\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
4832
+ }
4833
+ const allowlist = splitList(env("AGENT_MOSS_LLM_HOST_ALLOWLIST")).map((h) => h.toLowerCase());
4834
+ if (allowlist.length && !allowlist.includes(host)) {
4835
+ return [false, `baseUrl \u4E3B\u673A\u4E0D\u5728\u5141\u8BB8\u5217\u8868: ${host}`];
4836
+ }
4837
+ if (allowPrivate === void 0) {
4838
+ allowPrivate = TRUTHY2.includes(env("AGENT_MOSS_TRUST_PRIVATE_LLM", "1").toLowerCase());
4839
+ }
4840
+ if (!allowPrivate) {
4841
+ if (host === "localhost" || host === "::1") return [false, "baseUrl \u6307\u5411\u672C\u673A\uFF0C\u5DF2\u62D2\u7EDD"];
4842
+ const m = host.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
4843
+ if (m) {
4844
+ const [a, b] = [Number(m[1]), Number(m[2])];
4845
+ if (a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 127 || a === 169 && b === 254) {
4846
+ return [false, `baseUrl \u6307\u5411\u5185\u7F51\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
4847
+ }
4848
+ }
4849
+ }
4850
+ return [true, ""];
4851
+ }
4852
+
4853
+ // src/engine/llm-analyzer.ts
4738
4854
  init_config();
4739
4855
  init_skill_loader();
4740
4856
 
4741
4857
  // src/engine/template-loader.ts
4742
4858
  init_esm_shims();
4743
4859
  init_asset_dir();
4744
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4745
- import { resolve as resolve6, dirname as dirname6 } from "path";
4860
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4861
+ import { resolve as resolve7, dirname as dirname7 } from "path";
4746
4862
  import { fileURLToPath as fileURLToPath8 } from "url";
4747
4863
  var __filename6 = fileURLToPath8(import.meta.url);
4748
- var __dirname7 = dirname6(__filename6);
4864
+ var __dirname7 = dirname7(__filename6);
4749
4865
  var TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
4750
4866
  var cachedTemplates = null;
4751
4867
  function loadTemplates() {
@@ -4758,10 +4874,10 @@ function loadTemplates() {
4758
4874
  return cachedTemplates;
4759
4875
  }
4760
4876
  for (const file of TEMPLATE_FILES) {
4761
- const filePath = resolve6(dir, file);
4762
- if (existsSync8(filePath)) {
4877
+ const filePath = resolve7(dir, file);
4878
+ if (existsSync9(filePath)) {
4763
4879
  const key = file.replace(".txt", "").replace("_template", "");
4764
- cachedTemplates[key] = readFileSync7(filePath, "utf-8");
4880
+ cachedTemplates[key] = readFileSync8(filePath, "utf-8");
4765
4881
  }
4766
4882
  }
4767
4883
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -4771,8 +4887,8 @@ function loadTemplates() {
4771
4887
  // src/engine/script-analyzer.ts
4772
4888
  init_esm_shims();
4773
4889
  init_rules_loader();
4774
- import { readFileSync as readFileSync8 } from "fs";
4775
- import { resolve as resolve7, join as join7 } from "path";
4890
+ import { readFileSync as readFileSync9 } from "fs";
4891
+ import { resolve as resolve8, join as join8 } from "path";
4776
4892
  var SCRIPT_RULES = loadScriptRules();
4777
4893
  if (!SCRIPT_RULES || Object.keys(SCRIPT_RULES).length === 0) {
4778
4894
  console.error(
@@ -4842,8 +4958,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4842
4958
  }
4843
4959
  function readScriptContent(scriptPath, maxLines = 500) {
4844
4960
  try {
4845
- const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join7(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
4846
- const raw = readFileSync8(expandedPath, "utf-8");
4961
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join8(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve8(scriptPath);
4962
+ const raw = readFileSync9(expandedPath, "utf-8");
4847
4963
  const lines = raw.split("\n").slice(0, maxLines);
4848
4964
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
4849
4965
  } catch (e) {
@@ -4899,9 +5015,9 @@ init_heuristic();
4899
5015
  init_config();
4900
5016
  init_logic_rules();
4901
5017
  init_inline_analyzer();
4902
- import { statSync as statSync4, existsSync as existsSync10 } from "fs";
4903
- import { resolve as resolve9, join as join9, isAbsolute as isAbsolute2 } from "path";
4904
- import { homedir as homedir3 } from "os";
5018
+ import { statSync as statSync4, existsSync as existsSync11 } from "fs";
5019
+ import { resolve as resolve10, join as join10, isAbsolute as isAbsolute3 } from "path";
5020
+ import { homedir as homedir4 } from "os";
4905
5021
 
4906
5022
  // src/engine/script-engine/tracker.ts
4907
5023
  init_esm_shims();
@@ -5011,14 +5127,14 @@ function isSysArgv(node) {
5011
5127
  const prop = node.getChild("PropertyName");
5012
5128
  return prop !== null && srcSlice(prop) === "argv";
5013
5129
  }
5014
- function evalExpr(node, env) {
5130
+ function evalExpr(node, env2) {
5015
5131
  if (!node) return null;
5016
5132
  const name = node.name;
5017
5133
  if (name === "String") return stringValue(srcSlice(node));
5018
5134
  if (name === "Number") return srcSlice(node);
5019
5135
  if (name === "VariableName") {
5020
5136
  const id = srcSlice(node);
5021
- return id in env.vars ? env.vars[id] : null;
5137
+ return id in env2.vars ? env2.vars[id] : null;
5022
5138
  }
5023
5139
  if (name === "MemberExpression") {
5024
5140
  const bracket = node.getChild("[");
@@ -5028,13 +5144,13 @@ function evalExpr(node, env) {
5028
5144
  const idxNode = bracket.nextSibling;
5029
5145
  if (idxNode && idxNode.name === "Number") {
5030
5146
  const idx = parseInt(srcSlice(idxNode), 10);
5031
- if (idx >= 1 && idx <= env.argv.length) return env.argv[idx - 1];
5147
+ if (idx >= 1 && idx <= env2.argv.length) return env2.argv[idx - 1];
5032
5148
  return null;
5033
5149
  }
5034
5150
  }
5035
5151
  return null;
5036
5152
  }
5037
- if (isSysArgv(node)) return env.argv[0] ?? null;
5153
+ if (isSysArgv(node)) return env2.argv[0] ?? null;
5038
5154
  return null;
5039
5155
  }
5040
5156
  if (name === "CallExpression") {
@@ -5042,7 +5158,7 @@ function evalExpr(node, env) {
5042
5158
  if (func && func.name === "VariableName" && srcSlice(func) === "len") {
5043
5159
  const argList = node.getChild("ArgList");
5044
5160
  const inner = argList ? firstNamedChild(argList) : null;
5045
- if (inner && isSysArgv(inner)) return String(env.argv.length + 1);
5161
+ if (inner && isSysArgv(inner)) return String(env2.argv.length + 1);
5046
5162
  }
5047
5163
  return null;
5048
5164
  }
@@ -5054,16 +5170,16 @@ function evalExpr(node, env) {
5054
5170
  const condNode = ifKw.nextSibling;
5055
5171
  const orelseNode = elseKw.nextSibling;
5056
5172
  if (!thenNode || !condNode || !orelseNode) return null;
5057
- const cond = evalCondition(condNode, env);
5058
- if (cond === true) return evalExpr(thenNode, env);
5059
- if (cond === false) return evalExpr(orelseNode, env);
5173
+ const cond = evalCondition(condNode, env2);
5174
+ if (cond === true) return evalExpr(thenNode, env2);
5175
+ if (cond === false) return evalExpr(orelseNode, env2);
5060
5176
  return null;
5061
5177
  }
5062
5178
  if (name === "BinaryExpression") {
5063
5179
  const op = findOp(node);
5064
5180
  if (op === "+") {
5065
- const left = evalExpr(node.firstChild, env);
5066
- const right = evalExpr(lastNamedChild(node), env);
5181
+ const left = evalExpr(node.firstChild, env2);
5182
+ const right = evalExpr(lastNamedChild(node), env2);
5067
5183
  if (left !== null && right !== null) return left + right;
5068
5184
  }
5069
5185
  return null;
@@ -5096,7 +5212,7 @@ function findOp(node) {
5096
5212
  }
5097
5213
  return null;
5098
5214
  }
5099
- function evalCondition(node, env) {
5215
+ function evalCondition(node, env2) {
5100
5216
  if (!node) return null;
5101
5217
  const name = node.name;
5102
5218
  if (name === "Boolean") {
@@ -5107,7 +5223,7 @@ function evalCondition(node, env) {
5107
5223
  const op = findOp(node);
5108
5224
  if (op === "and" || op === "or") {
5109
5225
  const parts2 = namedChildren(node);
5110
- const vals = parts2.map((p) => evalCondition(p, env));
5226
+ const vals = parts2.map((p) => evalCondition(p, env2));
5111
5227
  if (op === "and") {
5112
5228
  if (vals.some((v) => v === false)) return false;
5113
5229
  if (vals.every((v) => v === true)) return true;
@@ -5121,13 +5237,13 @@ function evalCondition(node, env) {
5121
5237
  if (parts.length !== 3) return null;
5122
5238
  const [left, opNode, right] = parts;
5123
5239
  const opText = srcSlice(opNode).trim();
5124
- return evalCompare(left, opText, right, env);
5240
+ return evalCompare(left, opText, right, env2);
5125
5241
  }
5126
5242
  if (name === "UnaryExpression") {
5127
5243
  const kw = node.getChild("not");
5128
5244
  if (kw) {
5129
5245
  const operand = kw.nextSibling;
5130
- const v = evalCondition(operand, env);
5246
+ const v = evalCondition(operand, env2);
5131
5247
  return v === null ? null : !v;
5132
5248
  }
5133
5249
  return null;
@@ -5148,39 +5264,39 @@ function namedChildren(node) {
5148
5264
  }
5149
5265
  return out;
5150
5266
  }
5151
- function evalCompare(left, opText, right, env) {
5267
+ function evalCompare(left, opText, right, env2) {
5152
5268
  if (opText === "==" && left.name === "VariableName" && srcSlice(left) === "__name__") {
5153
- const rv = evalExpr(right, env);
5269
+ const rv = evalExpr(right, env2);
5154
5270
  if (rv !== null) return rv === "__main__";
5155
5271
  return null;
5156
5272
  }
5157
5273
  if (opText === "==" || opText === "!=") {
5158
- const lv = evalExpr(left, env);
5159
- const rv = evalExpr(right, env);
5274
+ const lv = evalExpr(left, env2);
5275
+ const rv = evalExpr(right, env2);
5160
5276
  if (lv !== null && rv !== null) {
5161
5277
  return opText === "==" ? lv === rv : lv !== rv;
5162
5278
  }
5163
5279
  return null;
5164
5280
  }
5165
5281
  if (opText === "in") {
5166
- const lv = evalExpr(left, env);
5282
+ const lv = evalExpr(left, env2);
5167
5283
  if (lv === null) return null;
5168
5284
  const rightName = right.name;
5169
5285
  if (rightName === "TupleExpression" || rightName === "ArrayExpression" || rightName === "SetExpression" || rightName === "ParenthesizedExpression") {
5170
5286
  const container = rightName === "ParenthesizedExpression" && right.firstChild && right.firstChild.name === "TupleExpression" ? right.firstChild : right;
5171
5287
  for (const elt of namedChildren(container)) {
5172
- const ev = evalExpr(elt, env);
5288
+ const ev = evalExpr(elt, env2);
5173
5289
  if (ev !== null && ev === lv) return true;
5174
5290
  }
5175
5291
  return false;
5176
5292
  }
5177
- const rv = evalExpr(right, env);
5293
+ const rv = evalExpr(right, env2);
5178
5294
  if (rv !== null) return rv.includes(lv);
5179
5295
  return null;
5180
5296
  }
5181
5297
  if (opText === ">" || opText === ">=" || opText === "<" || opText === "<=") {
5182
- const lv = evalExpr(left, env);
5183
- const rv = evalExpr(right, env);
5298
+ const lv = evalExpr(left, env2);
5299
+ const rv = evalExpr(right, env2);
5184
5300
  if (lv === null || rv === null) return null;
5185
5301
  const ln = parseInt(lv, 10);
5186
5302
  const rn = parseInt(rv, 10);
@@ -5192,71 +5308,71 @@ function evalCompare(left, opText, right, env) {
5192
5308
  }
5193
5309
  return null;
5194
5310
  }
5195
- function processStmts(stmts, env, lineStarts, lines, branches) {
5196
- for (const s of stmts) processStmt(s, env, lineStarts, lines, branches);
5311
+ function processStmts(stmts, env2, lineStarts, lines, branches) {
5312
+ for (const s of stmts) processStmt(s, env2, lineStarts, lines, branches);
5197
5313
  }
5198
- function invalidateEnvOnCalls(node, env) {
5314
+ function invalidateEnvOnCalls(node, env2) {
5199
5315
  const cursor = node.cursor();
5200
5316
  cursor.iterate((n) => {
5201
5317
  const nd = n.node;
5202
5318
  if (nd.name !== "CallExpression") return true;
5203
5319
  const func = nd.firstChild;
5204
5320
  if (func && func.name === "VariableName" && !PURE_BUILTINS.has(srcSlice(func))) {
5205
- for (const k of Object.keys(env.vars)) env.vars[k] = null;
5321
+ for (const k of Object.keys(env2.vars)) env2.vars[k] = null;
5206
5322
  return false;
5207
5323
  }
5208
5324
  return true;
5209
5325
  });
5210
5326
  }
5211
- function processStmt(stmt, env, lineStarts, lines, branches) {
5327
+ function processStmt(stmt, env2, lineStarts, lines, branches) {
5212
5328
  const name = stmt.name;
5213
5329
  if (name === "AssignStatement") {
5214
5330
  const target = stmt.firstChild;
5215
5331
  if (target && target.name === "VariableName") {
5216
5332
  const valNode = target.nextSibling ? target.nextSibling.nextSibling : null;
5217
- invalidateEnvOnCalls(stmt, env);
5218
- const val = evalExpr(valNode, env);
5219
- env.vars[srcSlice(target)] = val;
5333
+ invalidateEnvOnCalls(stmt, env2);
5334
+ const val = evalExpr(valNode, env2);
5335
+ env2.vars[srcSlice(target)] = val;
5220
5336
  }
5221
5337
  markNodeLines(stmt, lines, lineStarts);
5222
5338
  return;
5223
5339
  }
5224
5340
  if (name === "UpdateStatement") {
5225
5341
  const target = stmt.firstChild;
5226
- if (target && target.name === "VariableName") env.vars[srcSlice(target)] = null;
5227
- invalidateEnvOnCalls(stmt, env);
5342
+ if (target && target.name === "VariableName") env2.vars[srcSlice(target)] = null;
5343
+ invalidateEnvOnCalls(stmt, env2);
5228
5344
  markNodeLines(stmt, lines, lineStarts);
5229
5345
  return;
5230
5346
  }
5231
5347
  if (name === "IfStatement") {
5232
- processIf(stmt, env, lineStarts, lines, branches);
5348
+ processIf(stmt, env2, lineStarts, lines, branches);
5233
5349
  return;
5234
5350
  }
5235
5351
  if (name === "ForStatement" || name === "WhileStatement") {
5236
5352
  markNodeLines(stmt, lines, lineStarts);
5237
5353
  for (const body of stmt.getChildren("Body")) {
5238
- processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5354
+ processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5239
5355
  }
5240
5356
  return;
5241
5357
  }
5242
5358
  if (name === "TryStatement") {
5243
5359
  markNodeLines(stmt, lines, lineStarts);
5244
5360
  for (const body of stmt.getChildren("Body")) {
5245
- processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5361
+ processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5246
5362
  }
5247
5363
  return;
5248
5364
  }
5249
5365
  if (name === "WithStatement") {
5250
5366
  markNodeLines(stmt, lines, lineStarts);
5251
5367
  const body = stmt.getChild("Body");
5252
- if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5368
+ if (body) processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5253
5369
  return;
5254
5370
  }
5255
5371
  if (name === "MatchStatement") {
5256
5372
  markNodeLines(stmt, lines, lineStarts);
5257
5373
  for (const clause of stmt.getChildren("MatchClause")) {
5258
5374
  const body = clause.getChild("Body");
5259
- if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5375
+ if (body) processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5260
5376
  }
5261
5377
  return;
5262
5378
  }
@@ -5265,7 +5381,7 @@ function processStmt(stmt, env, lineStarts, lines, branches) {
5265
5381
  return;
5266
5382
  }
5267
5383
  if (name === "ExpressionStatement") {
5268
- invalidateEnvOnCalls(stmt, env);
5384
+ invalidateEnvOnCalls(stmt, env2);
5269
5385
  }
5270
5386
  markNodeLines(stmt, lines, lineStarts);
5271
5387
  }
@@ -5285,7 +5401,7 @@ function bodyStmts(body) {
5285
5401
  }
5286
5402
  return out;
5287
5403
  }
5288
- function processIf(stmt, env, lineStarts, lines, branches) {
5404
+ function processIf(stmt, env2, lineStarts, lines, branches) {
5289
5405
  const sections = [];
5290
5406
  let c = stmt.firstChild;
5291
5407
  while (c) {
@@ -5300,17 +5416,17 @@ function processIf(stmt, env, lineStarts, lines, branches) {
5300
5416
  }
5301
5417
  c = c.nextSibling;
5302
5418
  }
5303
- processIfSections(stmt, sections, 0, env, lineStarts, lines, branches);
5419
+ processIfSections(stmt, sections, 0, env2, lineStarts, lines, branches);
5304
5420
  }
5305
- function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branches) {
5421
+ function processIfSections(_stmt, sections, idx, env2, lineStarts, lines, branches) {
5306
5422
  if (idx >= sections.length) return;
5307
5423
  const s = sections[idx];
5308
5424
  if (s.cond) markNodeLines(s.cond, lines, lineStarts);
5309
5425
  if (s.isElse) {
5310
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5426
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5311
5427
  return;
5312
5428
  }
5313
- const cond = s.cond ? evalCondition(s.cond, env) : null;
5429
+ const cond = s.cond ? evalCondition(s.cond, env2) : null;
5314
5430
  branches.push({
5315
5431
  kind: "if",
5316
5432
  label: s.cond ? srcSlice(s.cond) : "",
@@ -5318,18 +5434,18 @@ function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branche
5318
5434
  reachable: cond !== false
5319
5435
  });
5320
5436
  if (cond === true) {
5321
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5437
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5322
5438
  return;
5323
5439
  }
5324
5440
  if (cond === false) {
5325
- processIfSections(_stmt, sections, idx + 1, env, lineStarts, lines, branches);
5441
+ processIfSections(_stmt, sections, idx + 1, env2, lineStarts, lines, branches);
5326
5442
  return;
5327
5443
  }
5328
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5444
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5329
5445
  for (let j = idx + 1; j < sections.length; j++) {
5330
5446
  const sj = sections[j];
5331
5447
  if (sj.cond) markNodeLines(sj.cond, lines, lineStarts);
5332
- if (sj.body) processStmts(bodyStmts(sj.body), env, lineStarts, lines, branches);
5448
+ if (sj.body) processStmts(bodyStmts(sj.body), env2, lineStarts, lines, branches);
5333
5449
  }
5334
5450
  }
5335
5451
  function parseShellString(s) {
@@ -5452,7 +5568,7 @@ var PythonBackend = class {
5452
5568
  SOURCE = c;
5453
5569
  const sourceLines = c.split("\n");
5454
5570
  const lineStarts = buildLineStarts(c);
5455
- const env = makeEnv(entryArgs || []);
5571
+ const env2 = makeEnv(entryArgs || []);
5456
5572
  const lines = /* @__PURE__ */ new Set();
5457
5573
  const branches = [];
5458
5574
  if (sourceLines.length > MAX_LINES) {
@@ -5479,7 +5595,7 @@ var PythonBackend = class {
5479
5595
  };
5480
5596
  }
5481
5597
  const stmts = bodyStmts(tree);
5482
- processStmts(stmts, env, lineStarts, lines, branches);
5598
+ processStmts(stmts, env2, lineStarts, lines, branches);
5483
5599
  const reachable = reconstruct(sourceLines, lines);
5484
5600
  let calls = extractCallsFromTree(tree, lines);
5485
5601
  if (calls.length === 0) calls = extractCallsRegex(c);
@@ -5729,7 +5845,7 @@ function tokenize(content) {
5729
5845
  return stmts;
5730
5846
  }
5731
5847
  var EXPR_SPLIT_RE = /(\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$\d+)/;
5732
- function evalExpr2(text, env) {
5848
+ function evalExpr2(text, env2) {
5733
5849
  const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
5734
5850
  if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
5735
5851
  text = text.slice(1, -1);
@@ -5745,19 +5861,19 @@ function evalExpr2(text, env) {
5745
5861
  const dm = inner.match(/^([A-Za-z_][A-Za-z0-9_]*|\d+):?-(.*)$/s);
5746
5862
  if (dm) {
5747
5863
  const key = /^\d+$/.test(dm[1]) ? `$${dm[1]}` : dm[1];
5748
- const bound = env.get(key);
5864
+ const bound = env2.get(key);
5749
5865
  if (bound && !(bound.kind === "concrete" && !bound.value)) {
5750
5866
  result.push(bound);
5751
5867
  } else {
5752
- result.push(evalExpr2(dm[2], env));
5868
+ result.push(evalExpr2(dm[2], env2));
5753
5869
  }
5754
5870
  continue;
5755
5871
  }
5756
5872
  if (/^\d+$/.test(inner)) {
5757
- const v = env.get(`$${inner}`);
5873
+ const v = env2.get(`$${inner}`);
5758
5874
  result.push(v || svalSym(`$${inner}`));
5759
5875
  } else {
5760
- const v = env.get(inner);
5876
+ const v = env2.get(inner);
5761
5877
  result.push(v || svalSym(inner));
5762
5878
  }
5763
5879
  continue;
@@ -5766,7 +5882,7 @@ function evalExpr2(text, env) {
5766
5882
  if (bare) {
5767
5883
  const varName = bare[1];
5768
5884
  const key = /^\d+$/.test(varName) ? `$${varName}` : varName;
5769
- const v = env.get(key);
5885
+ const v = env2.get(key);
5770
5886
  result.push(v || svalSym(p));
5771
5887
  continue;
5772
5888
  }
@@ -5776,13 +5892,13 @@ function evalExpr2(text, env) {
5776
5892
  return fold(svalExpr(result));
5777
5893
  }
5778
5894
  function initEnv(entryArgs) {
5779
- const env = /* @__PURE__ */ new Map();
5895
+ const env2 = /* @__PURE__ */ new Map();
5780
5896
  if (entryArgs && entryArgs.length) {
5781
- entryArgs.forEach((a, idx) => env.set(`$${idx + 1}`, svalConc(a)));
5782
- env.set("$@", svalConc(entryArgs.join(" ")));
5783
- env.set("$*", svalConc(entryArgs.join(" ")));
5897
+ entryArgs.forEach((a, idx) => env2.set(`$${idx + 1}`, svalConc(a)));
5898
+ env2.set("$@", svalConc(entryArgs.join(" ")));
5899
+ env2.set("$*", svalConc(entryArgs.join(" ")));
5784
5900
  }
5785
- return env;
5901
+ return env2;
5786
5902
  }
5787
5903
  var HAS_GLOB = /[*?[]/;
5788
5904
  function globToRegex(pattern) {
@@ -5841,12 +5957,12 @@ function evalNumericCompare(op, left, right) {
5841
5957
  return null;
5842
5958
  }
5843
5959
  }
5844
- function resolveVarSide(tok, env) {
5960
+ function resolveVarSide(tok, env2) {
5845
5961
  if (!tok.startsWith("$")) return tok;
5846
- const ev = evalExpr2(tok, env);
5962
+ const ev = evalExpr2(tok, env2);
5847
5963
  return ev.kind === "concrete" ? ev.value : null;
5848
5964
  }
5849
- function evalCondition2(cond, env) {
5965
+ function evalCondition2(cond, env2) {
5850
5966
  let inner = cond.trim().replace(/^\[\[?|\]\]?$/g, "").trim();
5851
5967
  if (/^-[A-Za-z]\s/.test(inner) || inner.startsWith("-d") || inner.startsWith("-f")) return null;
5852
5968
  const innerQ = inner.replace(/"/g, "").replace(/'/g, "");
@@ -5856,8 +5972,8 @@ function evalCondition2(cond, env) {
5856
5972
  if (!opm) continue;
5857
5973
  const left2 = innerQ.slice(0, opm.index).trim();
5858
5974
  const right2 = innerQ.slice(opm.index + opm[0].length).trim();
5859
- const lv2 = resolveVarSide(left2, env);
5860
- const rv2 = resolveVarSide(right2, env);
5975
+ const lv2 = resolveVarSide(left2, env2);
5976
+ const rv2 = resolveVarSide(right2, env2);
5861
5977
  if (lv2 === null || rv2 === null) return null;
5862
5978
  return evalNumericCompare(op2, lv2, rv2);
5863
5979
  }
@@ -5868,7 +5984,7 @@ function evalCondition2(cond, env) {
5868
5984
  let right = sm[3].trim();
5869
5985
  if (op === "=~") {
5870
5986
  if (right.startsWith("/") && right.endsWith("/")) right = right.slice(1, -1);
5871
- const lv2 = resolveVarSide(left, env);
5987
+ const lv2 = resolveVarSide(left, env2);
5872
5988
  if (lv2 === null) return null;
5873
5989
  try {
5874
5990
  return new RegExp(right).test(lv2);
@@ -5876,8 +5992,8 @@ function evalCondition2(cond, env) {
5876
5992
  return null;
5877
5993
  }
5878
5994
  }
5879
- const lv = resolveVarSide(left, env);
5880
- const rv = resolveVarSide(right, env);
5995
+ const lv = resolveVarSide(left, env2);
5996
+ const rv = resolveVarSide(right, env2);
5881
5997
  if (lv === null || rv === null) return null;
5882
5998
  if (op === "!=") return lv !== rv;
5883
5999
  return lv === rv;
@@ -5887,7 +6003,7 @@ function symbolicProcess(content, entryArgs) {
5887
6003
  const stmts = tokenize(content);
5888
6004
  if (!stmts.some((s) => s.kind === "case" || s.kind === "if" || s.kind === "assign")) return null;
5889
6005
  if (stmts.some((s) => !s.modelable)) return null;
5890
- const env = initEnv(entryArgs);
6006
+ const env2 = initEnv(entryArgs);
5891
6007
  let linesBudget = 0;
5892
6008
  let branchesBudget = 0;
5893
6009
  let modelable = false;
@@ -5897,9 +6013,9 @@ function symbolicProcess(content, entryArgs) {
5897
6013
  if (linesBudget > MAX_LINES2) return null;
5898
6014
  if (s.kind === "assign") {
5899
6015
  modelable = true;
5900
- if (env.size >= MAX_VARS) return null;
5901
- const v = evalExpr2(s.rhs, env);
5902
- env.set(s.name, fold(v));
6016
+ if (env2.size >= MAX_VARS) return null;
6017
+ const v = evalExpr2(s.rhs, env2);
6018
+ env2.set(s.name, fold(v));
5903
6019
  out.push(s.line);
5904
6020
  continue;
5905
6021
  }
@@ -5907,7 +6023,7 @@ function symbolicProcess(content, entryArgs) {
5907
6023
  modelable = true;
5908
6024
  branchesBudget += s.branches.length;
5909
6025
  if (branchesBudget > MAX_BRANCHES) return null;
5910
- const selVal = fold(evalExpr2(s.selector, env));
6026
+ const selVal = fold(evalExpr2(s.selector, env2));
5911
6027
  if (selVal.kind !== "concrete") return null;
5912
6028
  const concrete = selVal.value || "";
5913
6029
  let selected = null;
@@ -5934,7 +6050,7 @@ esac`);
5934
6050
  modelable = true;
5935
6051
  branchesBudget++;
5936
6052
  if (branchesBudget > MAX_BRANCHES) return null;
5937
- const truth = evalCondition2(s.cond, env);
6053
+ const truth = evalCondition2(s.cond, env2);
5938
6054
  if (truth === null) return null;
5939
6055
  const chosen = truth ? s.thenBody : s.elseBody;
5940
6056
  if (chosen === null) return null;
@@ -6539,7 +6655,7 @@ function extractInlineScriptPath(actionDetail) {
6539
6655
  if (looksLikeScriptPath(tok)) {
6540
6656
  try {
6541
6657
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6542
- if (existsSync10(resolved)) {
6658
+ if (existsSync11(resolved)) {
6543
6659
  const stat = statSync4(resolved);
6544
6660
  if (stat.isFile()) return resolved;
6545
6661
  }
@@ -6553,7 +6669,7 @@ function extractInlineScriptPath(actionDetail) {
6553
6669
  const p = m[1];
6554
6670
  try {
6555
6671
  const resolved = resolvePath2(p);
6556
- if (existsSync10(resolved)) {
6672
+ if (existsSync11(resolved)) {
6557
6673
  const stat = statSync4(resolved);
6558
6674
  if (stat.isFile()) return resolved;
6559
6675
  }
@@ -6569,7 +6685,7 @@ function extractInlineScriptPath(actionDetail) {
6569
6685
  let resolved;
6570
6686
  try {
6571
6687
  resolved = resolvePath2(p);
6572
- if (existsSync10(resolved)) {
6688
+ if (existsSync11(resolved)) {
6573
6689
  const stat = statSync4(resolved);
6574
6690
  if (stat.isFile()) return resolved;
6575
6691
  }
@@ -6610,9 +6726,9 @@ function shlexSplit(s) {
6610
6726
  function resolvePath2(p) {
6611
6727
  let expanded = p;
6612
6728
  if (expanded.startsWith("~/") || expanded === "~") {
6613
- expanded = join9(homedir3(), expanded.slice(1));
6729
+ expanded = join10(homedir4(), expanded.slice(1));
6614
6730
  }
6615
- return resolve9(expanded);
6731
+ return resolve10(expanded);
6616
6732
  }
6617
6733
  function scanExtensionPatterns(ext, content) {
6618
6734
  const pats = ext?.extra_command_patterns;
@@ -6733,8 +6849,8 @@ function analyzeScriptContent2(input, ext) {
6733
6849
  resolvedPath = inline;
6734
6850
  isInline = true;
6735
6851
  }
6736
- if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
6737
- resolvedPath = join9(input.cwd || process.cwd(), resolvedPath);
6852
+ if (resolvedPath && !isAbsolute3(resolvedPath) && !resolvedPath.startsWith("~/")) {
6853
+ resolvedPath = join10(input.cwd || process.cwd(), resolvedPath);
6738
6854
  }
6739
6855
  const l1Adapter = (content) => {
6740
6856
  const j = scanDangerousCommandPatterns("bash", content, input.osType);
@@ -6821,7 +6937,7 @@ ${contentPreview}
6821
6937
  }
6822
6938
 
6823
6939
  // src/engine/llm-analyzer.ts
6824
- import { appendFileSync } from "fs";
6940
+ import { openSync as openSync2, writeSync as writeSync2, closeSync as closeSync2, constants as fsConstants } from "fs";
6825
6941
  import OpenAI from "openai";
6826
6942
  var llmCache = /* @__PURE__ */ new Map();
6827
6943
  var LLM_CACHE_MAX = 200;
@@ -6839,7 +6955,15 @@ async function runLLMAnalysis(input, previous) {
6839
6955
  };
6840
6956
  }
6841
6957
  const globalConfig = getLLMConfig();
6842
- const config = input.llmConfig?.apiKey ? { ...globalConfig, ...input.llmConfig } : globalConfig;
6958
+ let perRequest = input.llmConfig;
6959
+ if (perRequest?.apiKey) {
6960
+ const [okUrl, why] = validateLlmBaseUrl(String(perRequest.baseUrl || ""));
6961
+ if (!okUrl) {
6962
+ console.warn("[AgentMoss] \u5FFD\u7565\u4E0D\u88AB\u5141\u8BB8\u7684 per-request llmConfig.baseUrl:", why);
6963
+ perRequest = { ...perRequest, baseUrl: void 0 };
6964
+ }
6965
+ }
6966
+ const config = perRequest?.apiKey ? { ...globalConfig, ...perRequest } : globalConfig;
6843
6967
  const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
6844
6968
  const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
6845
6969
  const cached2 = llmCache.get(cacheKey);
@@ -6868,6 +6992,10 @@ async function runLLMAnalysis(input, previous) {
6868
6992
  }
6869
6993
  function logPrompt(prompt, aNext, logPath) {
6870
6994
  if (!logPath) return;
6995
+ if (!pathWithin(logPath, logRoots())) {
6996
+ console.warn("[AgentMoss] \u5FFD\u7565\u8D8A\u754C\u7684 llm_log_path\uFF08\u4E0D\u5728\u5141\u8BB8\u6839\u76EE\u5F55\u5185\uFF09:", logPath);
6997
+ return;
6998
+ }
6871
6999
  try {
6872
7000
  const actionType = aNext.actionType || "unknown";
6873
7001
  const actionDetail = (aNext.actionDetail || "").slice(0, 100);
@@ -6875,7 +7003,14 @@ function logPrompt(prompt, aNext, logPath) {
6875
7003
  ${prompt}
6876
7004
  --- END PROMPT ---
6877
7005
  `;
6878
- appendFileSync(logPath, line, "utf-8");
7006
+ let flags = fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_APPEND;
7007
+ if (fsConstants.O_NOFOLLOW) flags |= fsConstants.O_NOFOLLOW;
7008
+ const fd = openSync2(logPath, flags, 384);
7009
+ try {
7010
+ writeSync2(fd, line);
7011
+ } finally {
7012
+ closeSync2(fd);
7013
+ }
6879
7014
  } catch (e) {
6880
7015
  console.warn("[AgentMoss] Failed to write LLM prompt to log:", e);
6881
7016
  }
@@ -7034,7 +7169,7 @@ async function analyzeWithLLM(prompt, config) {
7034
7169
  }
7035
7170
  if (attempt < maxRetries) {
7036
7171
  console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
7037
- await new Promise((resolve10) => setTimeout(resolve10, retryCfg.retryInterval * 1e3));
7172
+ await new Promise((resolve11) => setTimeout(resolve11, retryCfg.retryInterval * 1e3));
7038
7173
  } else {
7039
7174
  clearTimeout(totalTimeoutTimer);
7040
7175
  console.error("[AgentMoss] LLM call failed after all retries:", err.message);
@@ -7057,10 +7192,10 @@ init_esm_shims();
7057
7192
 
7058
7193
  // src/brain/rule-store.ts
7059
7194
  init_esm_shims();
7060
- import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7061
- import { join as join10 } from "path";
7062
- import { homedir as homedir4 } from "os";
7063
- var DEFAULT_RULES_PATH = join10(homedir4(), ".agentmoss", "brain", "rules.json");
7195
+ import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
7196
+ import { join as join11 } from "path";
7197
+ import { homedir as homedir5 } from "os";
7198
+ var DEFAULT_RULES_PATH = join11(homedir5(), ".agentmoss", "brain", "rules.json");
7064
7199
  var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
7065
7200
  var STATUS_DRAFT = "draft";
7066
7201
  var STATUS_APPROVED = "approved";
@@ -7089,11 +7224,11 @@ var BrainRuleStore = class {
7089
7224
  this.data = this.load();
7090
7225
  }
7091
7226
  load() {
7092
- if (!existsSync12(this.path)) {
7227
+ if (!existsSync13(this.path)) {
7093
7228
  return { version: 1, rules: [], pattern_candidates: [] };
7094
7229
  }
7095
7230
  try {
7096
- const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
7231
+ const raw = JSON.parse(readFileSync13(this.path, "utf-8"));
7097
7232
  return {
7098
7233
  version: raw.version || 1,
7099
7234
  rules: raw.rules || [],
@@ -7105,7 +7240,7 @@ var BrainRuleStore = class {
7105
7240
  }
7106
7241
  save() {
7107
7242
  const dir = this.path.substring(0, this.path.lastIndexOf("/"));
7108
- if (dir) mkdirSync3(dir, { recursive: true });
7243
+ if (dir) mkdirSync4(dir, { recursive: true });
7109
7244
  writeFileSync3(this.path, JSON.stringify(this.data, null, 2), "utf-8");
7110
7245
  }
7111
7246
  addRule(rule) {
@@ -7673,11 +7808,11 @@ function parseReview(content) {
7673
7808
 
7674
7809
  // src/brain/skill-manager.ts
7675
7810
  init_esm_shims();
7676
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, renameSync, writeFileSync as writeFileSync4 } from "fs";
7677
- import { homedir as homedir5 } from "os";
7678
- import { join as join11 } from "path";
7811
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync14, renameSync, writeFileSync as writeFileSync4 } from "fs";
7812
+ import { homedir as homedir6 } from "os";
7813
+ import { join as join12 } from "path";
7679
7814
  import { randomUUID } from "crypto";
7680
- var DEFAULT_SKILL_META_PATH = join11(homedir5(), ".agentmoss", "brain", "skill_meta.json");
7815
+ var DEFAULT_SKILL_META_PATH = join12(homedir6(), ".agentmoss", "brain", "skill_meta.json");
7681
7816
  var ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
7682
7817
  function getMetaPath(override) {
7683
7818
  return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
@@ -7687,7 +7822,7 @@ var BrainSkillManager = class {
7687
7822
  this.skillsDir = skillsDir;
7688
7823
  this.metaPath = getMetaPath(metaPath);
7689
7824
  try {
7690
- mkdirSync4(join11(this.metaPath, ".."), { recursive: true });
7825
+ mkdirSync5(join12(this.metaPath, ".."), { recursive: true });
7691
7826
  } catch {
7692
7827
  }
7693
7828
  this.load();
@@ -7697,9 +7832,9 @@ var BrainSkillManager = class {
7697
7832
  skills = [];
7698
7833
  metaPath;
7699
7834
  load() {
7700
- if (!existsSync13(this.metaPath)) return;
7835
+ if (!existsSync14(this.metaPath)) return;
7701
7836
  try {
7702
- const data = JSON.parse(readFileSync13(this.metaPath, "utf-8"));
7837
+ const data = JSON.parse(readFileSync14(this.metaPath, "utf-8"));
7703
7838
  this.skills = data.skills || [];
7704
7839
  } catch {
7705
7840
  this.skills = [];
@@ -7722,7 +7857,7 @@ var BrainSkillManager = class {
7722
7857
  let changed = false;
7723
7858
  for (const sk of this.skills) {
7724
7859
  if (sk.status !== "active") continue;
7725
- if (!existsSync13(join11(this.skillsDir, `${sk.name}.md`))) {
7860
+ if (!existsSync14(join12(this.skillsDir, `${sk.name}.md`))) {
7726
7861
  sk.status = "stale";
7727
7862
  changed = true;
7728
7863
  }
@@ -7733,9 +7868,9 @@ var BrainSkillManager = class {
7733
7868
  addSkill(skill) {
7734
7869
  const id = skill.id || randomUUID();
7735
7870
  const name = skill.name || `brain_${id.slice(0, 8)}`;
7736
- if (!existsSync13(this.skillsDir)) return "";
7737
- const mdPath = join11(this.skillsDir, `${name}.md`);
7738
- if (existsSync13(mdPath)) return "";
7871
+ if (!existsSync14(this.skillsDir)) return "";
7872
+ const mdPath = join12(this.skillsDir, `${name}.md`);
7873
+ if (existsSync14(mdPath)) return "";
7739
7874
  try {
7740
7875
  writeFileSync4(mdPath, skill.content, "utf-8");
7741
7876
  } catch {
@@ -7784,12 +7919,12 @@ var BrainSkillManager = class {
7784
7919
  const out = [];
7785
7920
  for (const sk of this.skills) {
7786
7921
  if (sk.origin_node !== "local" || sk.status !== "active") continue;
7787
- const mdPath = join11(this.skillsDir, `${sk.name}.md`);
7788
- if (!existsSync13(mdPath)) continue;
7922
+ const mdPath = join12(this.skillsDir, `${sk.name}.md`);
7923
+ if (!existsSync14(mdPath)) continue;
7789
7924
  out.push({
7790
7925
  name: sk.name,
7791
7926
  category: sk.category || "",
7792
- content: readFileSync13(mdPath, "utf-8"),
7927
+ content: readFileSync14(mdPath, "utf-8"),
7793
7928
  keywords: sk.keywords || []
7794
7929
  });
7795
7930
  }
@@ -7870,9 +8005,9 @@ var BrainCurator = class {
7870
8005
  // src/brain/contributor.ts
7871
8006
  init_esm_shims();
7872
8007
  init_asset_dir();
7873
- import { dirname as dirname9 } from "path";
8008
+ import { dirname as dirname10 } from "path";
7874
8009
  import { fileURLToPath as fileURLToPath9 } from "url";
7875
- var __dirname8 = dirname9(fileURLToPath9(import.meta.url));
8010
+ var __dirname8 = dirname10(fileURLToPath9(import.meta.url));
7876
8011
 
7877
8012
  // src/brain/index.ts
7878
8013
  function checkBrainRules(store, opts) {
@@ -8225,7 +8360,7 @@ function markIntegrity(actionDetail) {
8225
8360
  }
8226
8361
  return { integrity: "medium", isLowSource: false };
8227
8362
  }
8228
- function initNode(sessionId, seq, actionType, actionDetail) {
8363
+ function initNode(sessionId, seq, actionType, actionDetail, timestamp) {
8229
8364
  const { integrity, isLowSource } = markIntegrity(actionDetail);
8230
8365
  return {
8231
8366
  sessionId,
@@ -8235,7 +8370,8 @@ function initNode(sessionId, seq, actionType, actionDetail) {
8235
8370
  stage: markStage(actionDetail),
8236
8371
  resources: extractResources(actionDetail),
8237
8372
  integrity,
8238
- isLowSource
8373
+ isLowSource,
8374
+ timestamp: timestamp || void 0
8239
8375
  };
8240
8376
  }
8241
8377
  function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
@@ -8245,13 +8381,21 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
8245
8381
  const details = [];
8246
8382
  for (const a of actionHistory) {
8247
8383
  const ad = String(a.actionDetail || "");
8248
- if (ad) details.push({ actionType: String(a.actionType || ""), actionDetail: ad });
8384
+ if (ad) details.push({
8385
+ actionType: String(a.actionType || ""),
8386
+ actionDetail: ad,
8387
+ calledAt: a.calledAt || void 0
8388
+ });
8249
8389
  }
8250
8390
  if (aNext) {
8251
- details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
8391
+ details.push({
8392
+ actionType: String(aNext.actionType || ""),
8393
+ actionDetail: String(aNext.actionDetail || ""),
8394
+ calledAt: aNext.calledAt || void 0
8395
+ });
8252
8396
  }
8253
8397
  details.forEach((d, seq) => {
8254
- const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
8398
+ const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt);
8255
8399
  nodes.push(node);
8256
8400
  if (node.stage) stageFlags.add(node.stage);
8257
8401
  });
@@ -8277,7 +8421,8 @@ function buildMultiSessionGraph(sessions, aNext) {
8277
8421
  last.sessionId,
8278
8422
  last.nodes.length,
8279
8423
  String(aNext.actionType || ""),
8280
- String(aNext.actionDetail || "")
8424
+ String(aNext.actionDetail || ""),
8425
+ aNext.calledAt || void 0
8281
8426
  ));
8282
8427
  if (last.nodes.length > 1) {
8283
8428
  last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
@@ -8325,6 +8470,27 @@ function allNodesFlat(mg) {
8325
8470
  // src/engine/formal-engine/properties.ts
8326
8471
  init_esm_shims();
8327
8472
  init_rules_loader();
8473
+ function parseTs(ts) {
8474
+ if (!ts) return null;
8475
+ const t = Date.parse(String(ts).trim());
8476
+ return Number.isFinite(t) ? t : null;
8477
+ }
8478
+ function privescExfilWindowSec() {
8479
+ const raw = (process.env.AGENT_MOSS_PRIVESC_EXFIL_WINDOW_SEC || "").trim();
8480
+ if (!raw) return 1800;
8481
+ const n = parseInt(raw, 10);
8482
+ return Number.isFinite(n) ? Math.max(0, n) : 1800;
8483
+ }
8484
+ function privescInWindow(nodes, indexes, exfilIdx, windowSec) {
8485
+ const before = indexes.filter((i) => i < exfilIdx);
8486
+ if (windowSec <= 0) return before;
8487
+ const exfilTs = parseTs(nodes[exfilIdx].timestamp);
8488
+ if (exfilTs === null) return before;
8489
+ return before.filter((i) => {
8490
+ const ts = parseTs(nodes[i].timestamp);
8491
+ return ts === null || exfilTs - ts <= windowSec * 1e3;
8492
+ });
8493
+ }
8328
8494
  var CROSS_SESSION_CHAINS = null;
8329
8495
  function loadCrossSessionChains() {
8330
8496
  if (CROSS_SESSION_CHAINS) return CROSS_SESSION_CHAINS;
@@ -8527,6 +8693,7 @@ function checkNeverExfilAfterPrivesc(graph) {
8527
8693
  const nodes = allNodes(graph);
8528
8694
  if (nodes.length < 2) return violations;
8529
8695
  const witness = graph.witnessIdx;
8696
+ const windowSec = privescExfilWindowSec();
8530
8697
  if (witness !== void 0) {
8531
8698
  const evIdx = [];
8532
8699
  nodes.forEach((n, i) => {
@@ -8534,15 +8701,17 @@ function checkNeverExfilAfterPrivesc(graph) {
8534
8701
  });
8535
8702
  if (evIdx.length === 0) return violations;
8536
8703
  const firstEv = Math.min(...evIdx);
8537
- let pre = null;
8538
- for (let i = 0; i < firstEv; i++) {
8539
- if (isPrivescNode(nodes[i])) pre = nodes[i];
8540
- }
8541
- if (pre) {
8704
+ const privescIdx = [];
8705
+ nodes.forEach((n, i) => {
8706
+ if (isPrivescNode(n)) privescIdx.push(i);
8707
+ });
8708
+ const candidates = privescInWindow(nodes, privescIdx, firstEv, windowSec);
8709
+ if (candidates.length > 0) {
8710
+ const pre = nodes[candidates[candidates.length - 1]];
8542
8711
  const post = nodes[firstEv];
8543
8712
  return [{
8544
8713
  propertyId: "no-exfil-after-privesc",
8545
- reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
8714
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\u25C7EXFIL) \u7A97\u53E3\u7EA6\u675F",
8546
8715
  riskLevel: "critical",
8547
8716
  path: [
8548
8717
  { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
@@ -8555,18 +8724,18 @@ function checkNeverExfilAfterPrivesc(graph) {
8555
8724
  for (let i = 0; i < nodes.length; i++) {
8556
8725
  if (!isPrivescNode(nodes[i])) continue;
8557
8726
  for (let j = i + 1; j < nodes.length; j++) {
8558
- if (isExfilNode(nodes[j])) {
8559
- violations.push({
8560
- propertyId: "no-exfil-after-privesc",
8561
- reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
8562
- riskLevel: "critical",
8563
- path: [
8564
- { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
8565
- { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
8566
- ]
8567
- });
8568
- return violations;
8569
- }
8727
+ if (!isExfilNode(nodes[j])) continue;
8728
+ if (privescInWindow(nodes, [i], j, windowSec).length === 0) continue;
8729
+ violations.push({
8730
+ propertyId: "no-exfil-after-privesc",
8731
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\u25C7EXFIL) \u7A97\u53E3\u7EA6\u675F",
8732
+ riskLevel: "critical",
8733
+ path: [
8734
+ { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
8735
+ { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
8736
+ ]
8737
+ });
8738
+ return violations;
8570
8739
  }
8571
8740
  }
8572
8741
  return violations;
@@ -8733,6 +8902,11 @@ function checkCrossSessionReachability(graph) {
8733
8902
  }
8734
8903
  return violations;
8735
8904
  }
8905
+ var DATA_SOURCE_READ_RE = /\b(?:cat|head|tail|less|more|sed|awk|grep|strings|base64|xxd|od|dd)\b|\b(?:open|read_text|read_bytes)\s*\(/i;
8906
+ function isDataSourceNode(n) {
8907
+ if (n.stage === "CRED" || n.isLowSource) return true;
8908
+ return DATA_SOURCE_READ_RE.test(n.actionDetail);
8909
+ }
8736
8910
  function checkSensitiveDataAggregation(graph) {
8737
8911
  const violations = [];
8738
8912
  const nodes = allNodes(graph);
@@ -8752,6 +8926,7 @@ function checkSensitiveDataAggregation(graph) {
8752
8926
  for (const r of n.resources) {
8753
8927
  for (const j of resIndex.get(r) || []) {
8754
8928
  if (j === exfilIdx) continue;
8929
+ if (!isDataSourceNode(nodes[j])) continue;
8755
8930
  srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
8756
8931
  }
8757
8932
  }