@kenhkl/agent-moss 0.15.7 → 0.16.3

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.
@@ -1,13 +1,5 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
- }) : x)(function(x) {
8
- if (typeof require !== "undefined") return require.apply(this, arguments);
9
- throw Error('Dynamic require of "' + x + '" is not supported');
10
- });
11
3
  var __esm = (fn, res) => function __init() {
12
4
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
5
  };
@@ -15,15 +7,6 @@ var __export = (target, all) => {
15
7
  for (var name in all)
16
8
  __defProp(target, name, { get: all[name], enumerable: true });
17
9
  };
18
- var __copyProps = (to, from, except, desc) => {
19
- if (from && typeof from === "object" || typeof from === "function") {
20
- for (let key of __getOwnPropNames(from))
21
- if (!__hasOwnProp.call(to, key) && key !== except)
22
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
- }
24
- return to;
25
- };
26
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
10
 
28
11
  // node_modules/tsup/assets/esm_shims.js
29
12
  import path from "path";
@@ -122,278 +105,31 @@ var init_paths = __esm({
122
105
  }
123
106
  });
124
107
 
125
- // src/config.ts
126
- var config_exports = {};
127
- __export(config_exports, {
128
- detectProvider: () => detectProvider,
129
- getCustomRules: () => getCustomRules,
130
- getLLMConfig: () => getLLMConfig,
131
- getLLMFailMode: () => getLLMFailMode,
132
- getProviderHeaders: () => getProviderHeaders,
133
- getRetryConfig: () => getRetryConfig,
134
- getTimeoutConfig: () => getTimeoutConfig,
135
- isBrainEnabled: () => isBrainEnabled,
136
- isFormalObserveMode: () => isFormalObserveMode,
137
- isHeuristicEnabled: () => isHeuristicEnabled,
138
- isLLMAvailable: () => isLLMAvailable,
139
- isLogicRulesEnabled: () => isLogicRulesEnabled,
140
- probeLLMConnection: () => probeLLMConnection,
141
- updateLLMConfig: () => updateLLMConfig
142
- });
143
- import fs from "fs";
144
- import path2 from "path";
145
- import { fileURLToPath as fileURLToPath2 } from "url";
146
- function loadConfigFile(filePath) {
147
- try {
148
- const raw = fs.readFileSync(filePath, "utf-8");
149
- return JSON.parse(raw);
150
- } catch {
151
- return null;
152
- }
153
- }
154
- function resolveConfigPath() {
155
- const envPath = process.env.AGENT_MOSS_CONFIG_PATH;
156
- if (envPath) return envPath;
157
- const userConfig = path2.join(getConfigDir(), "config.json");
158
- if (fs.existsSync(userConfig)) return userConfig;
159
- const defaultCandidates = [
160
- path2.join(__dirname, "config.json.example"),
161
- path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example")
162
- ];
163
- const defaultConfig = defaultCandidates.find((p) => fs.existsSync(p));
164
- if (defaultConfig) {
165
- const configDir = path2.dirname(userConfig);
166
- fs.mkdirSync(configDir, { recursive: true });
167
- const pkgConfig = loadConfigFile(defaultConfig);
168
- const safeConfig = pkgConfig ? { ...pkgConfig, llm: { ...pkgConfig.llm, api_key: "" } } : {};
169
- fs.writeFileSync(userConfig, JSON.stringify(safeConfig, null, 4), "utf-8");
170
- console.log(`[AgentMoss] \u5DF2\u521B\u5EFA\u7528\u6237\u914D\u7F6E\u6587\u4EF6: ${userConfig}`);
171
- console.log(`[AgentMoss] \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\u914D\u7F6E\u4F60\u7684 LLM API Key`);
172
- }
173
- return userConfig;
174
- }
175
- function loadLLMConfigFromFile(configPath) {
176
- const data = loadConfigFile(configPath);
177
- if (!data?.llm) return {};
178
- const llm = data.llm;
179
- return {
180
- provider: llm.provider || "",
181
- baseUrl: llm.base_url || "",
182
- apiKey: llm.api_key || "",
183
- model: llm.model || "",
184
- temperature: llm.temperature ?? 0.1,
185
- maxTokens: llm.max_tokens ?? 4096,
186
- timeout: llm.timeout ?? 40
187
- };
188
- }
189
- function loadTimeoutRetryFromFile(configPath) {
190
- const data = loadConfigFile(configPath);
191
- const t = data?.timeout ?? {};
192
- const r = data?.retry ?? {};
193
- return {
194
- timeout: {
195
- totalTimeout: Number(t.total_timeout ?? 90),
196
- prompt1Timeout: Number(t.prompt1_timeout ?? 40),
197
- prompt2Timeout: Number(t.prompt2_timeout ?? 20),
198
- stepInterval: Number(t.step_interval ?? 0)
199
- },
200
- retry: {
201
- maxRetries: Number(r.max_retries ?? 2),
202
- retryInterval: Number(r.retry_interval ?? 2)
203
- }
204
- };
205
- }
206
- function getTimeoutConfig() {
207
- return timeoutConfig;
208
- }
209
- function getRetryConfig() {
210
- return retryConfig;
211
- }
212
- function wildcardToRegExp(pattern) {
213
- return pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
214
- }
215
- function loadCustomRules() {
216
- const rules = [];
217
- try {
218
- const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
219
- if (raw) {
220
- const parsed = JSON.parse(raw);
221
- if (Array.isArray(parsed)) {
222
- for (const r of parsed) {
223
- if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
224
- }
225
- }
226
- }
227
- } catch {
228
- }
229
- for (const rulesPath of USER_RULES_PATHS) {
230
- try {
231
- if (!fs.existsSync(rulesPath)) continue;
232
- const data = JSON.parse(fs.readFileSync(rulesPath, "utf-8"));
233
- const actions = Array.isArray(data?.sensitive_actions) ? data.sensitive_actions : [];
234
- for (const r of actions) {
235
- const p = r?.pattern;
236
- if (!p) continue;
237
- rules.push({ pattern: wildcardToRegExp(p), action: "Deny", severity: "high" });
238
- }
239
- break;
240
- } catch {
241
- }
242
- }
243
- return rules;
244
- }
245
- function getLLMConfig() {
246
- return llmConfig;
247
- }
248
- function updateLLMConfig(partial) {
249
- llmConfig = { ...llmConfig, ...partial };
250
- }
251
- function getCustomRules() {
252
- return customRules;
253
- }
254
- function isLLMAvailable() {
255
- if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
256
- if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
257
- return llmConfig.apiKey.length > 0;
108
+ // src/engine/shell-split.ts
109
+ function hasCommandSubstitution(text) {
110
+ return CMD_SUBSTITUTION.test(text);
258
111
  }
259
- async function probeLLMConnection(timeoutMs = 8e3) {
260
- if (!isLLMAvailable()) return false;
261
- const OpenAI3 = (await import("openai")).default;
262
- const config = getLLMConfig();
263
- const provider = detectProvider(config.baseUrl, config.provider);
264
- const extraHeaders = getProviderHeaders(provider);
265
- const client = new OpenAI3({
266
- baseURL: config.baseUrl,
267
- apiKey: config.apiKey,
268
- timeout: timeoutMs,
269
- maxRetries: 0,
270
- defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
271
- });
272
- try {
273
- const resp = await client.chat.completions.create({
274
- model: config.model,
275
- messages: [{ role: "user", content: "ping" }],
276
- max_tokens: 1
277
- });
278
- return !!(resp && resp.choices && resp.choices[0]);
279
- } catch {
280
- return false;
281
- }
282
- }
283
- function getLLMFailMode() {
284
- const val = (process.env.AGENT_MOSS_LLM_FAIL_MODE || "fail_open").toLowerCase();
285
- return val === "fail_closed" ? "fail_closed" : "fail_open";
286
- }
287
- function detectProvider(baseUrl, providerHint = "") {
288
- if (providerHint) {
289
- const hint = providerHint.toLowerCase();
290
- if (["claude", "anthropic"].includes(hint)) return "anthropic";
291
- if (["google", "gemini"].includes(hint)) return "gemini";
292
- if (["zai-coding-plan", "zhipu-coding-plan", "zhipuai-coding-plan"].includes(hint)) return "zai-coding-plan";
293
- if (["zai", "z-ai", "z.ai", "zai-cn", "zai-china", "zhipu", "glm-cn", "bigmodel"].includes(hint)) return "zhipu";
294
- if (["xai", "xai-grok"].includes(hint)) return "xai";
295
- if (["minimax", "minimax-openai"].includes(hint)) return "minimax";
296
- if (["minimax-anthropic"].includes(hint)) return "minimax-anthropic";
297
- if (["other", "custom", "local"].includes(hint)) return "openai-compatible";
298
- return hint;
299
- }
300
- const url = baseUrl.toLowerCase();
301
- if (url.includes("openrouter.ai")) return "openrouter";
302
- if (url.includes("api.openai.com")) return "openai";
303
- if (url.includes("anthropic.com") || url.includes("api.anthropic.com")) return "anthropic";
304
- if (url.includes("generativelanguage.googleapis.com") || url.includes("gemini")) return "gemini";
305
- if (url.includes("api.deepseek.com")) return "deepseek";
306
- if (url.includes("api.groq.com")) return "groq";
307
- if (url.includes("api.mistral.ai")) return "mistral";
308
- if (url.includes("api.together.xyz")) return "together";
309
- if (url.includes("api.x.ai")) return "xai";
310
- if (url.includes("api.minimaxi.com")) return "minimax";
311
- if (url.includes("api-ai.gitcode.com")) return "gitcode";
312
- if (url.includes("api.z.ai")) return "zai-coding-plan";
313
- if (url.includes("open.bigmodel.cn") || url.includes("bigmodel")) return "zhipu";
314
- if (url.includes(":11434")) return "ollama";
315
- return "openai-compatible";
316
- }
317
- function getProviderHeaders(provider) {
318
- return PROVIDER_HEADERS[provider] || {};
319
- }
320
- function isHeuristicEnabled() {
321
- if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
322
- return layerEnabledOrDefault("L1_heuristic");
323
- }
324
- function layerEnabledOrDefault(key) {
325
- try {
326
- const { isLayerEnabled: isLayerEnabled2 } = __require("../infra/runtime-config");
327
- return isLayerEnabled2(key);
328
- } catch {
329
- return true;
330
- }
331
- }
332
- function isBrainEnabled() {
333
- return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
112
+ function splitCommandSegments(command) {
113
+ return command.split(CMD_SEPARATORS).map((s) => s.trim()).filter((s) => s.length > 0);
334
114
  }
335
- function isLogicRulesEnabled() {
336
- if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
337
- return layerEnabledOrDefault("L2_logic_rules");
338
- }
339
- function isFormalObserveMode() {
340
- return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
115
+ function extractFirstCommand(command) {
116
+ const segments = splitCommandSegments(command);
117
+ return segments.length ? segments[0] : "";
341
118
  }
342
- var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
343
- var init_config = __esm({
344
- "src/config.ts"() {
119
+ var CMD_SEPARATORS, CMD_SUBSTITUTION;
120
+ var init_shell_split = __esm({
121
+ "src/engine/shell-split.ts"() {
345
122
  "use strict";
346
123
  init_esm_shims();
347
- init_paths();
348
- llmConfig = (() => {
349
- const configPath = resolveConfigPath();
350
- const fileConfig = fs.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
351
- const tr = loadTimeoutRetryFromFile(configPath);
352
- timeoutConfig = tr.timeout;
353
- retryConfig = tr.retry;
354
- return {
355
- provider: process.env.AGENT_MOSS_LLM_PROVIDER || fileConfig.provider || "openai",
356
- baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || fileConfig.baseUrl || "https://api.openai.com/v1",
357
- apiKey: process.env.AGENT_MOSS_LLM_API_KEY || process.env.OPENAI_API_KEY || fileConfig.apiKey || "",
358
- model: process.env.AGENT_MOSS_LLM_MODEL || fileConfig.model || "gpt-4o",
359
- temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || String(fileConfig.temperature ?? "0.1")),
360
- maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || String(fileConfig.maxTokens ?? "4096"), 10),
361
- // 优先用 env,否则用 timeout 块的 prompt1Timeout(对齐 py)
362
- timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
363
- };
364
- })();
365
- __configModuleDir = (() => {
366
- try {
367
- return path2.dirname(fileURLToPath2(import.meta.url));
368
- } catch {
369
- return typeof __dirname !== "undefined" ? __dirname : process.cwd();
370
- }
371
- })();
372
- USER_RULES_PATHS = [
373
- path2.join(__configModuleDir, "rules", "user_rules.json"),
374
- // 源在 ts/src/rules(一般不存在)
375
- path2.join(__configModuleDir, "..", "rules", "user_rules.json"),
376
- // dist: dist/engine/../rules = dist/rules ✅
377
- path2.join(__configModuleDir, "..", "..", "agent_moss", "rules", "user_rules.json")
378
- // dev src: ts/src/../../agent_moss
379
- ];
380
- customRules = loadCustomRules();
381
- PROVIDER_HEADERS = {
382
- "openrouter": {
383
- "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM",
384
- "X-OpenRouter-Title": "AgentMoss"
385
- },
386
- "xai": {
387
- "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM"
388
- }
389
- };
124
+ CMD_SEPARATORS = /\|\||&&|[|;&\n]/;
125
+ CMD_SUBSTITUTION = /\$\(|`|</;
390
126
  }
391
127
  });
392
128
 
393
129
  // src/engine/inline-analyzer.ts
394
130
  function isLiteralTextCommand(command) {
395
- ECHO_CMD_RE.lastIndex = 0;
396
- if (!ECHO_CMD_RE.test(command)) return false;
131
+ const segments = splitCommandSegments(command);
132
+ if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
397
133
  if (command.includes(">")) {
398
134
  let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
399
135
  d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
@@ -483,14 +219,15 @@ function analyzeInlineScript(command) {
483
219
  riskType: "file_access"
484
220
  };
485
221
  }
486
- var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_CMD_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SHELL_RC_FRAGMENTS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
222
+ var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_SEG_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SHELL_RC_FRAGMENTS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
487
223
  var init_inline_analyzer = __esm({
488
224
  "src/engine/inline-analyzer.ts"() {
489
225
  "use strict";
490
226
  init_esm_shims();
227
+ init_shell_split();
491
228
  INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
492
229
  ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
493
- ECHO_CMD_RE = /(?:^|\s|;|&&|\|\|)(?:echo|printf)\s/gi;
230
+ ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
494
231
  READ_WRITE_KEYWORDS = [
495
232
  // 函数调用形态(关键词后跟 '(')
496
233
  "open(",
@@ -539,9 +276,34 @@ var init_inline_analyzer = __esm({
539
276
  "passthru",
540
277
  "shell_exec",
541
278
  "proc_open",
542
- // Perl-Ruby
279
+ // Perl/Ruby
543
280
  "<stdin>",
544
- "open my"
281
+ "open my",
282
+ // Node/JS(2026-09 补,与 Python _READ_WRITE_KEYWORDS 同步):历史缺口 ——
283
+ // 识别表里一条 Node API 都没有,于是 node -e "require('fs').readFileSync('/etc/shadow')"
284
+ // 判为纯文本字面量 → 放行(真漏报)。
285
+ "readfilesync(",
286
+ "writefilesync(",
287
+ "appendfilesync(",
288
+ "unlinksync(",
289
+ "rmsync(",
290
+ "mkdirsync(",
291
+ "execsync(",
292
+ "spawnsync(",
293
+ "execfile",
294
+ "createreadstream",
295
+ "createwritestream",
296
+ "fs.read",
297
+ "fs.write",
298
+ "fs.promises",
299
+ "require('fs')",
300
+ 'require("fs")',
301
+ "require('node:fs')",
302
+ 'require("node:fs")',
303
+ "require('child_process')",
304
+ 'require("child_process")',
305
+ "require('https')",
306
+ 'require("https")'
545
307
  ];
546
308
  WRITE_EXEC_CHAIN_PATTERNS = [
547
309
  // echo/cat/printf "..." > file && bash/sh/python file
@@ -596,15 +358,45 @@ var init_inline_analyzer = __esm({
596
358
  }
597
359
  });
598
360
 
599
- // src/infra/rules-loader.ts
600
- import { readFileSync as readFileSync2, existsSync } from "fs";
601
- import { resolve, dirname, join as join2 } from "path";
602
- import { fileURLToPath as fileURLToPath3 } from "url";
603
- import { homedir as homedir2 } from "os";
604
- function dataRootDir() {
605
- return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
361
+ // src/infra/runtime-config-bridge.ts
362
+ function registerRuntimeConfigBridge(bridge) {
363
+ runtimeConfigBridge.current = bridge;
606
364
  }
607
- function findRulesFile(name) {
365
+ var runtimeConfigBridge;
366
+ var init_runtime_config_bridge = __esm({
367
+ "src/infra/runtime-config-bridge.ts"() {
368
+ "use strict";
369
+ init_esm_shims();
370
+ runtimeConfigBridge = { current: null };
371
+ }
372
+ });
373
+
374
+ // src/infra/rules-loader.ts
375
+ import { readFileSync as readFileSync2, existsSync } from "fs";
376
+ import { resolve, dirname } from "path";
377
+ import { fileURLToPath as fileURLToPath2 } from "url";
378
+ function dataRootDir() {
379
+ return getDataRoot();
380
+ }
381
+ function overrideRulesDir() {
382
+ return resolve(dataRootDir(), "rules");
383
+ }
384
+ function warnOverride(name, path5, data) {
385
+ if (warnedOverride.has(path5)) return;
386
+ warnedOverride.add(path5);
387
+ console.warn(
388
+ `[AgentMoss] \u89C4\u5219\u6587\u4EF6\u88AB\u7528\u6237\u6570\u636E\u76EE\u5F55\u8986\u76D6: ${path5}\uFF08\u5185\u7F6E\u9ED8\u8BA4\u88AB\u5FFD\u7565\uFF09\u3002\u8BE5\u6587\u4EF6\u7531\u90E8\u7F72\u4FA7\u81EA\u884C\u7EF4\u62A4 \u2014\u2014 **\u5185\u7F6E\u89C4\u5219\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4E0A\u9762**\uFF1B\u5220\u9664\u8BE5\u6587\u4EF6\u5373\u6062\u590D\u5185\u7F6E\u89C4\u5219\u3002\u82E5\u53EA\u60F3\u9010\u6761\u542F\u505C/\u589E\u5220\uFF0C\u8BF7\u6539\u7528 runtime config\uFF08Console\uFF09\u3002`
389
+ );
390
+ const missing = (EXPECTED_KEYS[name] || []).filter((k) => !(k in data));
391
+ if (missing.length > 0) {
392
+ console.warn(
393
+ `\u26A0\uFE0F [AgentMoss] \u8986\u76D6\u6587\u4EF6 ${path5} \u7F3A\u5C11\u9876\u5C42\u952E [${missing.join(", ")}] \u2014\u2014 \u5BF9\u5E94\u7C7B\u522B\u4F1A**\u4E00\u6761\u89C4\u5219\u90FD\u4E0D\u8DD1**\uFF08\u9759\u9ED8\u5931\u6548\uFF09\u3002\u82E5\u53EA\u60F3\u6539\u5176\u4E2D\u4E00\u7C7B\uFF0C\u8BF7\u5148\u62F7\u8D1D\u5B8C\u6574\u7684\u5185\u7F6E\u6587\u4EF6\u518D\u6539\u3002`
394
+ );
395
+ }
396
+ }
397
+ function findRulesFile(name) {
398
+ const override = resolve(overrideRulesDir(), name);
399
+ if (existsSync(override)) return override;
608
400
  for (const dir of RULES_DIR_CANDS) {
609
401
  const p = resolve(dir, name);
610
402
  if (existsSync(p)) return p;
@@ -614,11 +406,16 @@ function findRulesFile(name) {
614
406
  function loadJson(name) {
615
407
  const p = findRulesFile(name);
616
408
  if (!p) return {};
409
+ const isOverride = p === resolve(overrideRulesDir(), name);
410
+ let data;
617
411
  try {
618
- return JSON.parse(readFileSync2(p, "utf-8")) || {};
412
+ data = JSON.parse(readFileSync2(p, "utf-8")) || {};
619
413
  } catch {
414
+ if (isOverride) warnOverride(name, p, {});
620
415
  return {};
621
416
  }
417
+ if (isOverride) warnOverride(name, p, data);
418
+ return data;
622
419
  }
623
420
  function loadL1() {
624
421
  return loadJson("l1_rules.json");
@@ -633,8 +430,8 @@ function loadMC() {
633
430
  const mc = loadJson("mc_rules.json");
634
431
  const rules = mc.rules || [];
635
432
  try {
636
- const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
637
- if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
433
+ const rt = runtimeConfigBridge.current;
434
+ if (rules.length > 0 && rt?.hasRuntimeConfig()) {
638
435
  const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
639
436
  const rtRules = ltl.rules || [];
640
437
  const enabledById = {};
@@ -652,16 +449,26 @@ function loadMC() {
652
449
  function loadToolRegistry() {
653
450
  return loadJson("tool_registry.json");
654
451
  }
655
- var __filename2, __dirname2, RULES_DIR_CANDS;
452
+ var __filename2, __dirname2, EXPECTED_KEYS, warnedOverride, RULES_DIR_CANDS;
656
453
  var init_rules_loader = __esm({
657
454
  "src/infra/rules-loader.ts"() {
658
455
  "use strict";
659
456
  init_esm_shims();
660
- __filename2 = fileURLToPath3(import.meta.url);
457
+ init_paths();
458
+ init_runtime_config_bridge();
459
+ __filename2 = fileURLToPath2(import.meta.url);
661
460
  __dirname2 = dirname(__filename2);
461
+ EXPECTED_KEYS = {
462
+ "l1_rules.json": ["dangerous_commands", "injection_detection", "sensitive_path_access"],
463
+ "l2_rules.json": ["intent_consistency", "password_modify_consent", "user_deletion_consent"],
464
+ "l3_rules.json": [],
465
+ "mc_rules.json": ["rules"],
466
+ "tool_registry.json": ["tools"]
467
+ };
468
+ warnedOverride = /* @__PURE__ */ new Set();
662
469
  RULES_DIR_CANDS = (() => {
663
470
  const cands = [
664
- // 用户数据目录覆盖优先(与 Python rules_loader._load_json 的
471
+ // 用户数据目录覆盖优先(与 Python rules_loader.resolve_rules_file 的
665
472
  // <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
666
473
  // mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
667
474
  resolve(dataRootDir(), "rules"),
@@ -680,21 +487,455 @@ var init_rules_loader = __esm({
680
487
  }
681
488
  });
682
489
 
490
+ // src/engine/platform-traits.ts
491
+ function getPlatformTraits(osType) {
492
+ const accepted = acceptedOsValues(osType);
493
+ if (accepted.includes("windows")) return WINDOWS_TRAITS;
494
+ if (accepted.includes("macos")) return MACOS_TRAITS;
495
+ if (accepted.includes("harmony")) return HARMONY_TRAITS;
496
+ return LINUX_TRAITS;
497
+ }
498
+ var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
499
+ var init_platform_traits = __esm({
500
+ "src/engine/platform-traits.ts"() {
501
+ "use strict";
502
+ init_esm_shims();
503
+ init_logic_rules();
504
+ LINUX_TRAITS = {
505
+ writeKeywords: [
506
+ "write",
507
+ "\u5199\u5165",
508
+ "save",
509
+ "\u4FDD\u5B58",
510
+ "modify",
511
+ "\u4FEE\u6539",
512
+ "update",
513
+ "\u66F4\u65B0",
514
+ "delete",
515
+ "\u5220\u9664",
516
+ "remove",
517
+ "\u79FB\u9664",
518
+ "create",
519
+ "\u521B\u5EFA",
520
+ "append",
521
+ "\u8FFD\u52A0",
522
+ "truncate",
523
+ "\u622A\u65AD",
524
+ "overwrite",
525
+ "\u8986\u76D6",
526
+ "replace",
527
+ "\u66FF\u6362",
528
+ "edit"
529
+ ],
530
+ readKeywords: [
531
+ "read",
532
+ "\u8BFB\u53D6",
533
+ "cat",
534
+ "head",
535
+ "tail",
536
+ "less",
537
+ "more",
538
+ "view",
539
+ "\u67E5\u770B",
540
+ "open",
541
+ "\u6253\u5F00",
542
+ "load",
543
+ "\u52A0\u8F7D",
544
+ "grep",
545
+ "search",
546
+ "\u641C\u7D22",
547
+ "find",
548
+ "\u67E5\u627E"
549
+ ],
550
+ writeCommandRes: [
551
+ /\brm\b/,
552
+ /\bunlink\b/,
553
+ /\brmdir\b/,
554
+ /\bshred\b/,
555
+ /\bcp\b/,
556
+ /\bmv\b/,
557
+ /\binstall\b/,
558
+ /\btee\b/,
559
+ /\bdd\b/,
560
+ /\bmkfs\b/,
561
+ /\b(fdisk|parted|cfdisk|sfdisk)\b/,
562
+ /\bchmod\b/,
563
+ /\bchown\b/,
564
+ /\btruncate\b/
565
+ ],
566
+ readOnlyCommands: /* @__PURE__ */ new Set([
567
+ "cat",
568
+ "head",
569
+ "tail",
570
+ "less",
571
+ "more",
572
+ "grep",
573
+ "find",
574
+ "awk",
575
+ "sed",
576
+ "sort",
577
+ "uniq",
578
+ "wc",
579
+ "cut",
580
+ "strings",
581
+ "od",
582
+ "xxd",
583
+ "hexdump",
584
+ "tr",
585
+ "file",
586
+ "stat",
587
+ "du",
588
+ "df",
589
+ "ls",
590
+ "dir",
591
+ "tree",
592
+ "nl",
593
+ "tac",
594
+ "rev",
595
+ "echo",
596
+ "printf",
597
+ "lsblk",
598
+ "blockdev",
599
+ "smartctl",
600
+ "udevadm",
601
+ "dmidecode",
602
+ "lscpu",
603
+ "lspci",
604
+ "lsusb",
605
+ "lsmem",
606
+ "lsns",
607
+ "lsof",
608
+ "hwinfo",
609
+ "inxi",
610
+ "fdisk",
611
+ "parted",
612
+ "dmesg",
613
+ "journalctl",
614
+ "systemctl",
615
+ "hostnamectl",
616
+ "localectl",
617
+ "timedatectl",
618
+ "ps",
619
+ "top",
620
+ "free",
621
+ "vmstat",
622
+ "iostat",
623
+ "mpstat",
624
+ "pidof",
625
+ "pgrep",
626
+ "ip",
627
+ "ifconfig",
628
+ "route",
629
+ "ss",
630
+ "netstat",
631
+ "arp",
632
+ "ethtool",
633
+ "nmcli",
634
+ "uname",
635
+ "arch",
636
+ "nproc",
637
+ "getconf",
638
+ "getent",
639
+ "id",
640
+ "whoami",
641
+ "who",
642
+ "w",
643
+ "last",
644
+ "uptime",
645
+ "lsmod",
646
+ "modinfo",
647
+ "sysctl"
648
+ ]),
649
+ fullySafeCommands: [
650
+ /^ls(?:\s|$)/i,
651
+ /^dir(?:\s|$)/i,
652
+ /^pwd(?:\s|$)/i,
653
+ /^which\s+/i,
654
+ /^whereis\s+/i,
655
+ /^realpath\s+/i,
656
+ /^basename\s+/i,
657
+ /^dirname\s+/i,
658
+ /^file\s+/i,
659
+ /^stat\s+/i,
660
+ /^du(?:\s|$)/i,
661
+ /^echo\s+/i,
662
+ /^printf\s+/i,
663
+ /^type\s+/i,
664
+ /^command\s+/i,
665
+ // 系统信息查询(只读,不涉及文件/网络/权限)
666
+ /^whoami(?:\s|$)/i,
667
+ /^id(?:\s|$)/i,
668
+ /^hostname(?:\s|$)/i,
669
+ /^uname(?:\s|$)/i,
670
+ /^date(?:\s|$)/i,
671
+ /^env(?:\s|$)/i,
672
+ /^printenv(?:\s|$)/i,
673
+ /^tty(?:\s|$)/i,
674
+ /^arch(?:\s|$)/i,
675
+ /^uptime(?:\s|$)/i,
676
+ /^groups(?:\s|$)/i,
677
+ /^logname(?:\s|$)/i,
678
+ // 系统资源/硬件信息(只读,不读文件内容、无状态变更选项)
679
+ // 与上面 uptime/uname/id 同性质;缺了它们时开 L3 的部署每查一次磁盘/内存
680
+ // 都要走 LLM(实测 df -h 从 5ms 变成十几秒)。
681
+ /^df(?:\s|$)/i,
682
+ /^free(?:\s|$)/i,
683
+ /^vmstat(?:\s|$)/i,
684
+ /^nproc(?:\s|$)/i,
685
+ /^lscpu(?:\s|$)/i,
686
+ /^lsblk(?:\s|$)/i,
687
+ /^lspci(?:\s|$)/i,
688
+ /^lsusb(?:\s|$)/i,
689
+ /^lsmod(?:\s|$)/i
690
+ ],
691
+ readonlySensitiveCommands: [
692
+ /^cat\s+/i,
693
+ /^head(?:\s|$)/i,
694
+ /^tail(?:\s|$)/i,
695
+ /^less(?:\s|$)/i,
696
+ /^more(?:\s|$)/i,
697
+ /^wc(?:\s|$)/i,
698
+ /^grep(?:\s|$)/i,
699
+ /^find\s+/i,
700
+ /^ag(?:\s|$)/i,
701
+ /^rg(?:\s|$)/i,
702
+ /^awk\s+/i,
703
+ /^sed\s+/i,
704
+ /^cut\s+/i,
705
+ /^sort(?:\s|$)/i,
706
+ /^uniq(?:\s|$)/i,
707
+ // 进程/网络/打开文件信息:只读,但会暴露进程命令行、连接对端、打开的文件
708
+ // 路径,故只跳 L3、保留 L2(L1 敏感路径检测照常生效)。
709
+ /^ps(?:\s|$)/i,
710
+ /^ss(?:\s|$)/i,
711
+ /^netstat(?:\s|$)/i,
712
+ /^lsof(?:\s|$)/i,
713
+ // 只读子命令级白名单:匹配的是「第一段命令」而非首词(extractFirstCommand
714
+ // 按 | && || 切分),所以能精确到子命令。systemctl stop/disable 等不在此列。
715
+ /^systemctl\s+(?:status|is-active|is-enabled|is-failed|list-units|list-unit-files|list-dependencies|show|cat)\b/i,
716
+ /^service\s+\S+\s+status\b/i,
717
+ // journalctl 整族是只读的,但 --vacuum-*/--rotate/--flush/--sync 会删改日志,
718
+ // 用否定前瞻挡掉后再放行。注意前瞻里必须用 .* 扫描**整段**:只写 (?!\s+--...)
719
+ // 的话只看紧跟 journalctl 的第一个参数,
720
+ // `journalctl -u sshd --vacuum-size=100M` 就漏过去了。
721
+ /^journalctl\b(?!.*--(?:vacuum-size|vacuum-time|vacuum-files|rotate|flush|sync)\b)/i
722
+ ]
723
+ };
724
+ WINDOWS_TRAITS = {
725
+ writeKeywords: [
726
+ "write",
727
+ "\u5199\u5165",
728
+ "save",
729
+ "\u4FDD\u5B58",
730
+ "modify",
731
+ "\u4FEE\u6539",
732
+ "update",
733
+ "\u66F4\u65B0",
734
+ "delete",
735
+ "\u5220\u9664",
736
+ "remove",
737
+ "\u79FB\u9664",
738
+ "create",
739
+ "\u521B\u5EFA",
740
+ "append",
741
+ "\u8FFD\u52A0",
742
+ "overwrite",
743
+ "\u8986\u76D6",
744
+ "replace",
745
+ "\u66FF\u6362",
746
+ "set",
747
+ "\u8BBE\u7F6E"
748
+ // 注意:这里**不能放 `reg` / `注册表`**。关键词是子串包含匹配,放进去等于
749
+ // 「只要命令里出现 reg 字样就算写操作」——`reg query`(读)会被判成写,
750
+ // 进而误拦对 deny_write 路径的只读查询。注册表的写判定交给下面的
751
+ // writeCommandRes(只认写子命令)。两端语义必须一致。
752
+ ],
753
+ readKeywords: [
754
+ "read",
755
+ "\u8BFB\u53D6",
756
+ "type",
757
+ "findstr",
758
+ "dir",
759
+ "view",
760
+ "\u67E5\u770B",
761
+ "open",
762
+ "\u6253\u5F00",
763
+ "load",
764
+ "\u52A0\u8F7D",
765
+ "search",
766
+ "\u641C\u7D22",
767
+ "find",
768
+ "\u67E5\u627E",
769
+ "get",
770
+ "\u83B7\u53D6"
771
+ ],
772
+ // Windows 写/删命令:del/copy/move/xcopy/replace/rmdir/icacls/reg delete 等
773
+ writeCommandRes: [
774
+ /\b(del|erase)\b/,
775
+ /\brmdir\b/,
776
+ /\b(rd)\b/,
777
+ /\bcopy\b/,
778
+ /\b(xcopy|robocopy)\b/,
779
+ /\b(move|movefile)\b/,
780
+ /\breplace\b/,
781
+ /\bicacls\b/,
782
+ /\battrib\b/,
783
+ // 注册表:**只认写子命令**(对齐 Python WindowsProfile.write_command_res)。
784
+ // 此前是 /\breg\b/,把 reg query(读)也判成写 → 误拦对 deny_write 路径的只读查询。
785
+ // 写子命令 add/delete/copy/save/restore/import/load/unload;
786
+ // 读子命令 query/compare/export/flags/help 不在此列。
787
+ /\breg(?:\.exe)?\s+(?:add|delete|copy|save|restore|import|load|unload)\b/,
788
+ /\bformat\b/,
789
+ /\bdiskpart\b/,
790
+ /\bmkdir\b/,
791
+ /\bmd\b/,
792
+ /\bsetx\b/,
793
+ /\bwmic\b/
794
+ ],
795
+ // Windows 只读命令(重定向写判定用):type/findstr/more/tree/dir 等
796
+ readOnlyCommands: /* @__PURE__ */ new Set([
797
+ "type",
798
+ "findstr",
799
+ "more",
800
+ "tree",
801
+ "dir",
802
+ "where",
803
+ "ver",
804
+ "cls",
805
+ "echo",
806
+ "help",
807
+ "fc",
808
+ "comp",
809
+ "find",
810
+ "sort",
811
+ "powershell",
812
+ "cmd",
813
+ "netstat",
814
+ "ipconfig",
815
+ "tasklist",
816
+ "systeminfo",
817
+ "driverquery",
818
+ "reg",
819
+ "wmic",
820
+ "whoami",
821
+ "hostname",
822
+ "getmac",
823
+ "query",
824
+ "w32tm",
825
+ "diskpart"
826
+ ]),
827
+ // Windows 完全安全命令(对齐 WindowsProfile.fully_safe_commands)
828
+ fullySafeCommands: [
829
+ /^dir(?:\s|$)/,
830
+ /^where\s+/,
831
+ /^echo\s+/,
832
+ /^cd(?:\s|$)/,
833
+ /^tree(?:\s|$)/,
834
+ /^help(?:\s|$)/,
835
+ /^cls(?:\s|$)/,
836
+ /^ver(?:\s|$)/,
837
+ // 系统信息查询(只读,不读文件内容、无状态变更选项),与 Linux 侧 df/free 对齐
838
+ /^whoami(?:\s|$)/,
839
+ /^hostname(?:\s|$)/,
840
+ /^systeminfo(?:\s|$)/,
841
+ /^tasklist(?:\s|$)/,
842
+ /^getmac(?:\s|$)/,
843
+ /^driverquery(?:\s|$)/,
844
+ /^netstat(?:\s|$)/
845
+ ],
846
+ // Windows 安全但可能访问敏感路径的命令(对齐 WindowsProfile.readonly_sensitive_commands)
847
+ readonlySensitiveCommands: [
848
+ /^type\s+/,
849
+ /^findstr\s+/,
850
+ // 只读子命令级白名单(匹配「第一段命令」而非首词);
851
+ // sc create/delete/start/stop、reg add/delete/set 均不在此列。
852
+ /^sc\s+query\b/,
853
+ /^reg\s+query\b/
854
+ ]
855
+ };
856
+ MACOS_TRAITS = LINUX_TRAITS;
857
+ HARMONY_TRAITS = LINUX_TRAITS;
858
+ }
859
+ });
860
+
683
861
  // src/engine/logic-rules.ts
684
- import * as fs2 from "fs";
685
- function _loadSensitivePathScope(scope) {
862
+ import * as fs from "fs";
863
+ function acceptedOsValues(osType) {
864
+ const t = String(osType || "").toLowerCase();
865
+ if (t === "windows") return ["windows"];
866
+ if (t === "macos" || t === "darwin" || t === "osx" || t === "mac") return ["linux", "macos"];
867
+ if (t === "harmony" || t === "hmos" || t === "openharmony" || t === "hongmeng") return ["linux", "harmony"];
868
+ return ["linux"];
869
+ }
870
+ function _loadSensitivePathScope(scope, osType = "linux") {
871
+ const accepted = acceptedOsValues(osType);
686
872
  const sps = loadL1().sensitive_path_access || [];
687
- return sps.filter((sp) => sp.scope === scope).map((sp) => ({
873
+ return sps.filter((sp) => sp.scope === scope).filter((sp) => accepted.includes(String(sp.os || "linux").toLowerCase())).map((sp) => ({
688
874
  path: sp.path,
689
875
  riskLevel: sp.risk_level,
690
876
  desc: sp.desc,
691
877
  credential: sp.deny_mode === "deny_both" ? true : void 0,
692
- dirDelOnly: sp.dir_del_only === true ? true : void 0
878
+ readOnly: sp.read_only === true || sp.deny_mode === "deny_read" ? true : void 0,
879
+ denyMode: sp.deny_mode || void 0,
880
+ dirDelOnly: sp.dir_del_only === true ? true : void 0,
881
+ // 保留条目的原始平台归属(非查询用的 osType)—— seed 分组去重用
882
+ os: String(sp.os || "linux").toLowerCase()
693
883
  }));
694
884
  }
695
- function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
885
+ function _scopeOf(os3, path5) {
886
+ if (!_scopeIndex) {
887
+ _scopeIndex = /* @__PURE__ */ new Map();
888
+ for (const sp of loadL1().sensitive_path_access || []) {
889
+ const key = `${String(sp.os || "linux").toLowerCase()}|${sp.path}`;
890
+ if (!_scopeIndex.has(key)) _scopeIndex.set(key, sp.scope);
891
+ }
892
+ }
893
+ return _scopeIndex.get(`${String(os3 || "linux").toLowerCase()}|${path5}`) || "root";
894
+ }
895
+ function getSensitivePathsForOs(osType) {
896
+ const effective = runtimeConfigBridge.current?.effectiveSensitivePaths(osType);
897
+ if (effective) {
898
+ const root = [];
899
+ const home = [];
900
+ const credential = [];
901
+ for (const r of effective) {
902
+ const entry = {
903
+ path: r.path,
904
+ riskLevel: r.riskLevel,
905
+ desc: r.desc,
906
+ // ⚠️ 不要强制 credential:用户在 Console 把某条从 deny_both 改成 deny_write
907
+ // 之后,强制 credential 会让读操作仍被拦(改了不生效)。一律以 runtime 为准。
908
+ credential: r.credential === true ? true : void 0,
909
+ readOnly: r.readOnly === true ? true : void 0,
910
+ denyMode: r.denyMode || void 0,
911
+ dirDelOnly: r.dirDelOnly === true ? true : void 0
912
+ };
913
+ const scope = _scopeOf(r.os, r.path);
914
+ if (scope === "home") home.push(entry);
915
+ else if (scope === "credential") credential.push(entry);
916
+ else root.push(entry);
917
+ }
918
+ return { root, home, credential };
919
+ }
920
+ return getSensitivePathsForOsBuiltin(osType);
921
+ }
922
+ function getSensitivePathsForOsBuiltin(osType) {
923
+ const t = String(osType || "").toLowerCase();
924
+ if (t === "windows") {
925
+ return { root: ROOT_SENSITIVE_PATHS_WINDOWS, home: HOME_SENSITIVE_PATHS_WINDOWS, credential: CREDENTIAL_PATHS_WINDOWS };
926
+ }
927
+ if (t === "macos" || t === "darwin" || t === "osx" || t === "mac") {
928
+ return { root: ROOT_SENSITIVE_PATHS_MACOS, home: HOME_SENSITIVE_PATHS_MACOS, credential: CREDENTIAL_PATHS_MACOS };
929
+ }
930
+ if (t === "harmony" || t === "hmos" || t === "openharmony" || t === "hongmeng") {
931
+ return { root: ROOT_SENSITIVE_PATHS_HARMONY, home: HOME_SENSITIVE_PATHS_HARMONY, credential: CREDENTIAL_PATHS_HARMONY };
932
+ }
933
+ return { root: ROOT_SENSITIVE_PATHS, home: HOME_SENSITIVE_PATHS, credential: CREDENTIAL_PATHS };
934
+ }
935
+ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS, osType) {
696
936
  if (writeKeywords.some((kw) => actionType.includes(kw) || actionDetail.includes(kw))) return true;
697
- if (WRITE_COMMAND_RES.some((rx) => rx.test(actionDetail))) return true;
937
+ const { writeCommandRes } = getPlatformTraits(osType);
938
+ if (writeCommandRes.some((rx) => rx.test(actionDetail))) return true;
698
939
  if (/\bsed\s+-[a-zA-Z]*i/i.test(actionDetail)) return true;
699
940
  for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
700
941
  const trimmed = seg.trim();
@@ -728,6 +969,12 @@ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWOR
728
969
  }
729
970
  return false;
730
971
  }
972
+ function getIntentPatternsForOs(osType) {
973
+ const effective = runtimeConfigBridge.current?.effectiveIntentPatterns(osType);
974
+ if (effective) return effective;
975
+ const accepted = acceptedOsValues(osType);
976
+ return INTENT_DEVIATION_PATTERNS.filter((p) => accepted.includes(p.os));
977
+ }
731
978
  function matchDangerousAction(actionDetail, action) {
732
979
  if (/[^\x00-\x7f]/.test(action)) {
733
980
  return actionDetail.includes(action);
@@ -762,38 +1009,47 @@ function collectIntentTexts(promptSession, promptHistory) {
762
1009
  }
763
1010
  return deduped;
764
1011
  }
765
- function runLogicCheck(input) {
1012
+ function checkIntentConsistency(input) {
1013
+ const { aNext, promptSession, promptHistory } = input;
1014
+ const actionDetail = aNext.actionDetail.toLowerCase();
1015
+ const actionType = aNext.actionType.toLowerCase();
1016
+ const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1017
+ if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1018
+ if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1019
+ const intentTexts = collectIntentTexts(promptSession, promptHistory);
1020
+ for (const pattern of getIntentPatternsForOs(input.osType)) {
1021
+ const intentMatched = intentTexts.some(
1022
+ (text) => pattern.intentKeywords.some((kw) => text.includes(kw))
1023
+ );
1024
+ const dangerousMatched = pattern.dangerousActions.some((kw) => matchDangerousAction(actionDetail, kw));
1025
+ if (intentMatched && dangerousMatched) {
1026
+ return {
1027
+ allowed: false,
1028
+ reason: pattern.reason,
1029
+ riskLevel: "high",
1030
+ riskType: "intent_deviation",
1031
+ confidence: 85,
1032
+ source: "logic_rule",
1033
+ violatedLayers: ["L2.1"]
1034
+ };
1035
+ }
1036
+ }
1037
+ return null;
1038
+ }
1039
+ function runLogicCheck(input, opts) {
766
1040
  const { actionHistory, aNext, cwd } = input;
767
1041
  const actionDetail = aNext.actionDetail.toLowerCase();
768
1042
  const actionType = aNext.actionType.toLowerCase();
769
1043
  const resolvedCwd = cwd || "/";
770
1044
  const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
771
1045
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
772
- const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd);
773
- if (rbwResult) return rbwResult;
774
- if (!isExcludedType) {
775
- if (!INLINE_SCRIPT_RE.test(actionDetail)) {
776
- const intentTexts = collectIntentTexts(input.promptSession, input.promptHistory);
777
- for (const pattern of INTENT_DEVIATION_PATTERNS) {
778
- const intentMatched = intentTexts.some(
779
- (text) => pattern.intentKeywords.some((kw) => text.includes(kw))
780
- );
781
- const dangerousMatched = pattern.dangerousActions.some((kw) => matchDangerousAction(actionDetail, kw));
782
- if (intentMatched && dangerousMatched) {
783
- return {
784
- allowed: false,
785
- reason: pattern.reason,
786
- riskLevel: "high",
787
- riskType: "intent_deviation",
788
- confidence: 85,
789
- source: "logic_rule",
790
- violatedLayers: ["L2.1"]
791
- };
792
- }
793
- }
794
- }
1046
+ if (!opts?.skipReadBeforeWrite) {
1047
+ const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
1048
+ if (rbwResult) return rbwResult;
795
1049
  }
796
- const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd);
1050
+ const intentResult = checkIntentConsistency(input);
1051
+ if (intentResult) return intentResult;
1052
+ const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd, input.osType);
797
1053
  if (indirectResult) return indirectResult;
798
1054
  if (INLINE_SCRIPT_RE.test(actionDetail)) {
799
1055
  } else if (containsPrivilegeEscalation(actionDetail)) {
@@ -816,9 +1072,9 @@ function runLogicCheck(input) {
816
1072
  }
817
1073
  }
818
1074
  }
819
- const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory);
1075
+ const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory, input.osType);
820
1076
  if (pwdResult) return pwdResult;
821
- const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory);
1077
+ const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory, input.osType);
822
1078
  if (userDelResult) return userDelResult;
823
1079
  const comboResult = checkComboChain(actionHistory, aNext.actionDetail);
824
1080
  if (comboResult) return comboResult;
@@ -869,22 +1125,33 @@ function checkComboChain(actionHistory, actionDetailRaw) {
869
1125
  }
870
1126
  return null;
871
1127
  }
872
- function extractFilePaths(text) {
1128
+ function extractFilePaths(text, osType) {
873
1129
  const paths = [];
874
1130
  const unixPaths = text.match(/(?:^|\s)(\/[^\s;|&><'"]+)/g);
875
1131
  if (unixPaths) paths.push(...unixPaths.map((p) => p.trim()));
1132
+ const isWindows = String(osType || "").toLowerCase() === "windows";
1133
+ if (isWindows) {
1134
+ const winPaths = text.match(/(?:^|\s)([A-Za-z]:[\\\/][^\s;|&><'"]+)/g);
1135
+ if (winPaths) paths.push(...winPaths.map((p) => p.trim()));
1136
+ const uncPaths = text.match(/(?:^|\s)(\\{2}[^\\\s]+(?:\\[^\s;|&><'"]*))/g);
1137
+ if (uncPaths) paths.push(...uncPaths.map((p) => p.trim()));
1138
+ }
876
1139
  const textNoQuotes = text.replace(/'[^']*'|"[^"]*"/g, "");
877
1140
  const kvPaths = textNoQuotes.match(/(?:=)(\/[^\s;|&><'"]+)/g);
878
1141
  if (kvPaths) paths.push(...kvPaths.map((p) => p.replace(/^=/, "")));
879
1142
  const quotedPaths = text.match(/["']\/([^"']+)["']/g);
880
1143
  if (quotedPaths) paths.push(...quotedPaths.map((p) => p.replace(/["']/g, "")));
1144
+ if (isWindows) {
1145
+ const quotedWinPaths = text.match(/["']([A-Za-z]:[\\\/][^"']+)["']/g);
1146
+ if (quotedWinPaths) paths.push(...quotedWinPaths.map((p) => p.replace(/["']/g, "")));
1147
+ }
881
1148
  return [...new Set(paths)];
882
1149
  }
883
- function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd) {
1150
+ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd, osType) {
884
1151
  if (NON_WRITE_TOOL_PREFIXES.some((p) => actionType.startsWith(p))) return null;
885
1152
  if (NON_WRITE_TOOLS.has(actionType)) return null;
886
1153
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
887
- const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail) : WRITE_KEYWORDS.some((kw) => actionType.includes(kw));
1154
+ const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
888
1155
  if (!isWrite) return null;
889
1156
  const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
890
1157
  if (isBashLike) {
@@ -928,7 +1195,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
928
1195
  if (firstWord === "cd") {
929
1196
  continue;
930
1197
  }
931
- if (READ_ONLY_COMMANDS.has(firstWord)) {
1198
+ if (getPlatformTraits(osType).readOnlyCommands.has(firstWord)) {
932
1199
  const segUnquoted = trimmed.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
933
1200
  const segNoDevnull = segUnquoted.replace(DEVNULL_REDIRECT_RE, "");
934
1201
  const segNoQuotes = segNoDevnull.replace(/'[^']*'|"[^"]*"/g, "");
@@ -936,7 +1203,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
936
1203
  if (m) targetPaths.push(m[1] || m[2] || m[3]);
937
1204
  continue;
938
1205
  }
939
- const segPaths = extractFilePaths(trimmed);
1206
+ const segPaths = extractFilePaths(trimmed, osType);
940
1207
  const segPathsFiltered = firstWord === "wget" || firstWord === "curl" ? segPaths.filter((p) => p !== "/dev/null") : segPaths;
941
1208
  if (SOURCE_ARG_CMDS.has(firstWord)) {
942
1209
  if (segPathsFiltered.length > 1) targetPaths.push(...segPathsFiltered.slice(1));
@@ -958,20 +1225,30 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
958
1225
  for (const action of actionHistory) {
959
1226
  const histDetail = action.actionDetail.toLowerCase();
960
1227
  const histType = action.actionType.toLowerCase();
961
- const isRead = READ_KEYWORDS.some((kw) => histType.includes(kw) || histDetail.includes(kw));
1228
+ const isRead = getPlatformTraits(osType).readKeywords.some((kw) => histType.includes(kw) || histDetail.includes(kw));
962
1229
  if (isRead) {
963
- for (const p of extractFilePaths(histDetail)) {
1230
+ for (const p of extractFilePaths(histDetail, osType)) {
964
1231
  historyReadPaths.add(p);
965
1232
  }
966
1233
  }
967
1234
  }
1235
+ const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1236
+ let filteredPaths = uniqueTargetPaths;
1237
+ if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1238
+ filteredPaths = uniqueTargetPaths.filter(
1239
+ (p) => !TEMP_DIRS.some((td) => p.startsWith(td))
1240
+ );
1241
+ }
1242
+ if (filteredPaths.length === 0) {
1243
+ return null;
1244
+ }
968
1245
  const unreadPaths = [];
969
- for (const path5 of uniqueTargetPaths) {
1246
+ for (const path5 of filteredPaths) {
970
1247
  const hasRead = [...historyReadPaths].some(
971
1248
  (rp) => path5 === rp || path5.startsWith(rp) || rp.startsWith(path5)
972
1249
  );
973
1250
  if (!hasRead) {
974
- let isCreate = !fs2.existsSync(path5);
1251
+ let isCreate = !fs.existsSync(path5);
975
1252
  if (!isCreate) {
976
1253
  isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
977
1254
  }
@@ -993,14 +1270,18 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
993
1270
  }
994
1271
  return null;
995
1272
  }
996
- function checkPasswordConsent(actionType, actionDetail, actionHistory) {
1273
+ function hasUserConsent(actionHistory) {
1274
+ return actionHistory.some((h) => {
1275
+ const t = String(h.actionType || "").toLowerCase();
1276
+ return t === "ask_user_question" && String(h.output || "").trim().length > 0;
1277
+ });
1278
+ }
1279
+ function checkPasswordConsent(actionType, actionDetail, actionHistory, osType) {
997
1280
  if (actionType !== "bash") return null;
998
- const isPasswordModify = PASSWORD_MODIFY_PATTERNS.some((p) => p.test(actionDetail));
1281
+ const pwdPatterns = runtimeConfigBridge.current?.effectivePasswordPatterns(osType) ?? PASSWORD_MODIFY_PATTERNS;
1282
+ const isPasswordModify = pwdPatterns.some((p) => p.test(actionDetail));
999
1283
  if (!isPasswordModify) return null;
1000
- const hasConsent = actionHistory.some(
1001
- (h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
1002
- );
1003
- if (hasConsent) return null;
1284
+ if (hasUserConsent(actionHistory)) return null;
1004
1285
  return {
1005
1286
  allowed: false,
1006
1287
  reason: "\u6267\u884C\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u547D\u4EE4\uFF0C\u4F46\u5386\u53F2\u6267\u884C\u5E8F\u5217\u4E2D\u672A\u53D1\u73B0\u5411\u7528\u6237\u786E\u8BA4\u5BC6\u7801\u7684 ask_user_question \u64CD\u4F5C\uFF0C\u53EF\u80FD\u5B58\u5728\u672A\u6388\u6743\u7684\u5BC6\u7801\u4FEE\u6539\u98CE\u9669",
@@ -1011,14 +1292,12 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
1011
1292
  violatedLayers: ["L2.1"]
1012
1293
  };
1013
1294
  }
1014
- function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
1295
+ function checkUserDeletionConsent(actionType, actionDetail, actionHistory, osType) {
1015
1296
  if (actionType !== "bash") return null;
1016
- const isUserDeletion = USER_DELETION_PATTERNS.some((p) => p.test(actionDetail));
1297
+ const udPatterns = runtimeConfigBridge.current?.effectiveUserDeletionPatterns(osType) ?? USER_DELETION_PATTERNS;
1298
+ const isUserDeletion = udPatterns.some((p) => p.test(actionDetail));
1017
1299
  if (!isUserDeletion) return null;
1018
- const hasConsent = actionHistory.some(
1019
- (h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
1020
- );
1021
- if (hasConsent) return null;
1300
+ if (hasUserConsent(actionHistory)) return null;
1022
1301
  return {
1023
1302
  allowed: false,
1024
1303
  reason: "\u6267\u884C\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u547D\u4EE4\uFF0C\u4F46\u5386\u53F2\u6267\u884C\u5E8F\u5217\u4E2D\u672A\u53D1\u73B0\u5411\u7528\u6237\u786E\u8BA4\u7684 ask_user_question \u64CD\u4F5C\uFF0C\u53EF\u80FD\u5B58\u5728\u672A\u6388\u6743\u7684\u7528\u6237\u5220\u9664\u98CE\u9669",
@@ -1041,7 +1320,7 @@ function containsPrivilegeEscalation(actionDetail) {
1041
1320
  }
1042
1321
  return false;
1043
1322
  }
1044
- function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1323
+ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1045
1324
  if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") {
1046
1325
  return null;
1047
1326
  }
@@ -1054,14 +1333,14 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1054
1333
  if (!hasTraversal) {
1055
1334
  return null;
1056
1335
  }
1057
- const READ_KEYWORDS2 = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1336
+ const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1058
1337
  const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1059
1338
  const EXEC_KEYWORDS = ["system(", "exec(", "eval(", "popen(", "os.system", "os.popen", "subprocess", "os.exec", "os.spawn", "shell=true", "check_output", "check_call", "run(", "passthru", "shell_exec", "proc_open"];
1060
1339
  const OPEN_KEYWORDS = ["open(", "fopen("];
1061
1340
  const code = extractInlineCode(actionDetail);
1062
1341
  if (!code) return null;
1063
1342
  const codeLower = code.toLowerCase();
1064
- const hasAnyOp = READ_KEYWORDS2.some((kw) => codeLower.includes(kw)) || WRITE_KEYWORDS2.some((kw) => codeLower.includes(kw)) || EXEC_KEYWORDS.some((kw) => codeLower.includes(kw)) || OPEN_KEYWORDS.some((kw) => codeLower.includes(kw));
1343
+ const hasAnyOp = READ_KEYWORDS.some((kw) => codeLower.includes(kw)) || WRITE_KEYWORDS2.some((kw) => codeLower.includes(kw)) || EXEC_KEYWORDS.some((kw) => codeLower.includes(kw)) || OPEN_KEYWORDS.some((kw) => codeLower.includes(kw));
1065
1344
  if (!hasAnyOp) {
1066
1345
  return null;
1067
1346
  }
@@ -1069,15 +1348,16 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1069
1348
  const hasExec = EXEC_KEYWORDS.some((kw) => codeLower.includes(kw));
1070
1349
  const blockAllSensitive = hasWrite || hasExec;
1071
1350
  let actualFiles = [];
1072
- if (cwd && cwd !== "/") {
1351
+ if (cwd) {
1073
1352
  try {
1074
- actualFiles = fs2.readdirSync(cwd);
1353
+ actualFiles = fs.readdirSync(cwd);
1075
1354
  } catch {
1076
1355
  }
1077
1356
  }
1078
1357
  if (actualFiles.length > 0) {
1079
- for (const cp of CREDENTIAL_PATHS) {
1080
- const fname = cp.path.split("/").pop() || cp.path;
1358
+ const { root: rootPaths, home: homePaths, credential: credentialPaths } = getSensitivePathsForOs(osType);
1359
+ for (const cp of credentialPaths) {
1360
+ const fname = cp.path.split(/[/\\]/).pop() || cp.path;
1081
1361
  if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
1082
1362
  return {
1083
1363
  allowed: false,
@@ -1092,8 +1372,8 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1092
1372
  }
1093
1373
  }
1094
1374
  if (blockAllSensitive) {
1095
- for (const sp of [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS]) {
1096
- const fname = sp.path.split("/").pop() || sp.path;
1375
+ for (const sp of [...rootPaths, ...homePaths]) {
1376
+ const fname = sp.path.split(/[/\\]/).pop() || sp.path;
1097
1377
  if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
1098
1378
  return {
1099
1379
  allowed: false,
@@ -1119,16 +1399,28 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1119
1399
  violatedLayers: ["L2.1"]
1120
1400
  };
1121
1401
  }
1122
- 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;
1402
+ var 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, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
1123
1403
  var init_logic_rules = __esm({
1124
1404
  "src/engine/logic-rules.ts"() {
1125
1405
  "use strict";
1126
1406
  init_esm_shims();
1127
1407
  init_inline_analyzer();
1408
+ init_runtime_config_bridge();
1128
1409
  init_rules_loader();
1410
+ init_platform_traits();
1129
1411
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1130
1412
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1131
1413
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
1414
+ ROOT_SENSITIVE_PATHS_WINDOWS = _loadSensitivePathScope("root", "windows");
1415
+ HOME_SENSITIVE_PATHS_WINDOWS = _loadSensitivePathScope("home", "windows");
1416
+ CREDENTIAL_PATHS_WINDOWS = _loadSensitivePathScope("credential", "windows").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
1417
+ ROOT_SENSITIVE_PATHS_MACOS = _loadSensitivePathScope("root", "macos");
1418
+ HOME_SENSITIVE_PATHS_MACOS = _loadSensitivePathScope("home", "macos");
1419
+ CREDENTIAL_PATHS_MACOS = _loadSensitivePathScope("credential", "macos").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
1420
+ ROOT_SENSITIVE_PATHS_HARMONY = _loadSensitivePathScope("root", "harmony");
1421
+ HOME_SENSITIVE_PATHS_HARMONY = _loadSensitivePathScope("home", "harmony");
1422
+ CREDENTIAL_PATHS_HARMONY = _loadSensitivePathScope("credential", "harmony").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
1423
+ _scopeIndex = null;
1132
1424
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1133
1425
  "ask_user_question",
1134
1426
  "glob",
@@ -1150,111 +1442,7 @@ var init_logic_rules = __esm({
1150
1442
  ]);
1151
1443
  NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1152
1444
  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"];
1153
- WRITE_COMMAND_RES = [
1154
- /\brm\b/,
1155
- /\bunlink\b/,
1156
- /\brmdir\b/,
1157
- /\bshred\b/,
1158
- /\bcp\b/,
1159
- /\bmv\b/,
1160
- /\binstall\b/,
1161
- /\btee\b/,
1162
- /\bdd\b/,
1163
- /\bmkfs\b/,
1164
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
1165
- /\bchmod\b/,
1166
- /\bchown\b/,
1167
- /\btruncate\b/
1168
- ];
1169
- READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
1170
- "cat",
1171
- "head",
1172
- "tail",
1173
- "less",
1174
- "more",
1175
- "grep",
1176
- "find",
1177
- "awk",
1178
- "sed",
1179
- "sort",
1180
- "uniq",
1181
- "wc",
1182
- "cut",
1183
- "strings",
1184
- "od",
1185
- "xxd",
1186
- "hexdump",
1187
- "tr",
1188
- "file",
1189
- "stat",
1190
- "du",
1191
- "df",
1192
- "ls",
1193
- "dir",
1194
- "tree",
1195
- "nl",
1196
- "tac",
1197
- "rev",
1198
- // NEW-FP-I: echo/printf 是文本输出命令,参数是字面量非写目标。加入此集让
1199
- // read_before_write 的 echo 段走只读命令分支(只提取重定向目标,不提取参数路径),
1200
- // 避免 echo "cat /etc/shadow" > /tmp/x.sh 的引号内 /etc/shadow 被当写目标误报。
1201
- // echo "x" > /tmp/x.sh 的重定向目标 /tmp/x.sh 仍被提取(真实写不漏报)。
1202
- "echo",
1203
- "printf",
1204
- "lsblk",
1205
- "blockdev",
1206
- "smartctl",
1207
- "udevadm",
1208
- "dmidecode",
1209
- "lscpu",
1210
- "lspci",
1211
- "lsusb",
1212
- "lsmem",
1213
- "lsns",
1214
- "lsof",
1215
- "hwinfo",
1216
- "inxi",
1217
- "fdisk",
1218
- "parted",
1219
- "dmesg",
1220
- "journalctl",
1221
- "systemctl",
1222
- "hostnamectl",
1223
- "localectl",
1224
- "timedatectl",
1225
- "ps",
1226
- "top",
1227
- "free",
1228
- "vmstat",
1229
- "iostat",
1230
- "mpstat",
1231
- "pidof",
1232
- "pgrep",
1233
- "ip",
1234
- "ifconfig",
1235
- "route",
1236
- "ss",
1237
- "netstat",
1238
- "arp",
1239
- "ethtool",
1240
- "nmcli",
1241
- "uname",
1242
- "arch",
1243
- "nproc",
1244
- "getconf",
1245
- "getent",
1246
- "id",
1247
- "whoami",
1248
- "who",
1249
- "w",
1250
- "last",
1251
- "uptime",
1252
- "lsmod",
1253
- "modinfo",
1254
- "sysctl"
1255
- ]);
1256
1445
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
1257
- READ_KEYWORDS = ["read", "\u8BFB\u53D6", "cat", "head", "tail", "less", "more", "view", "\u67E5\u770B", "open", "\u6253\u5F00", "load", "\u52A0\u8F7D", "grep", "search", "\u641C\u7D22", "find", "\u67E5\u627E"];
1258
1446
  CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
1259
1447
  PASSWORD_MODIFY_PATTERNS = (() => {
1260
1448
  const pats = loadL2().password_modify_consent || [];
@@ -1269,7 +1457,8 @@ var init_logic_rules = __esm({
1269
1457
  return pats.map((p) => ({
1270
1458
  intentKeywords: p.intent_keywords,
1271
1459
  dangerousActions: p.dangerous_actions,
1272
- reason: p.reason
1460
+ reason: p.reason,
1461
+ os: String(p.os || "linux").toLowerCase()
1273
1462
  }));
1274
1463
  })();
1275
1464
  COMBO_STAGES = (() => {
@@ -1329,9 +1518,9 @@ var init_asset_dir = __esm({
1329
1518
  });
1330
1519
 
1331
1520
  // src/engine/skill-loader.ts
1332
- import { readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
1521
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2, existsSync as existsSync4 } from "fs";
1333
1522
  import { resolve as resolve3, dirname as dirname2 } from "path";
1334
- import { fileURLToPath as fileURLToPath4 } from "url";
1523
+ import { fileURLToPath as fileURLToPath3 } from "url";
1335
1524
  function loadAllSkills() {
1336
1525
  if (cachedSkills) return cachedSkills;
1337
1526
  cachedSkills = {};
@@ -1349,14 +1538,47 @@ function loadAllSkills() {
1349
1538
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1350
1539
  return cachedSkills;
1351
1540
  }
1541
+ function userSkillsDir() {
1542
+ return resolve3(getDataRoot(), "agent_moss_skills");
1543
+ }
1544
+ function runtimeCustomSkillKeywords() {
1545
+ try {
1546
+ const rt = runtimeConfigBridge.current?.loadRuntimeConfig?.();
1547
+ if (!rt) return {};
1548
+ const out = {};
1549
+ for (const cat of Object.values(rt.L3_skills || {})) {
1550
+ if (!cat || typeof cat !== "object" || cat.category_enabled === false) continue;
1551
+ for (const sk of cat.skills || []) {
1552
+ if (!sk || sk.builtin !== false || sk.enabled === false || !sk.id) continue;
1553
+ out[sk.id] = sk.keywords || [];
1554
+ }
1555
+ }
1556
+ return out;
1557
+ } catch {
1558
+ return {};
1559
+ }
1560
+ }
1561
+ function loadUserSkill(name) {
1562
+ if (userSkillCache[name] !== void 0) return userSkillCache[name];
1563
+ try {
1564
+ const p = resolve3(userSkillsDir(), `${name}.md`);
1565
+ userSkillCache[name] = existsSync4(p) ? readFileSync3(p, "utf-8") : "";
1566
+ } catch {
1567
+ userSkillCache[name] = "";
1568
+ }
1569
+ return userSkillCache[name];
1570
+ }
1352
1571
  function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
1353
1572
  const skills = loadAllSkills();
1354
1573
  const combined = `${actionType} ${actionDetail} ${reason}`.toLowerCase();
1355
1574
  const actionDetailLower = actionDetail.toLowerCase();
1356
1575
  const actionTypeLower = actionType.toLowerCase();
1357
1576
  const reasonLower = reason.toLowerCase();
1577
+ const disabledSkillIds = runtimeConfigBridge.current?.disabledSkillIds() ?? /* @__PURE__ */ new Set();
1578
+ const skillMap = { ...SKILL_KEYWORD_MAP, ...runtimeCustomSkillKeywords() };
1358
1579
  const scores = {};
1359
- for (const [skillName, keywords] of Object.entries(SKILL_KEYWORD_MAP)) {
1580
+ for (const [skillName, keywords] of Object.entries(skillMap)) {
1581
+ if (disabledSkillIds.has(skillName)) continue;
1360
1582
  let score = 0;
1361
1583
  for (const kw of keywords) {
1362
1584
  if (combined.includes(kw)) {
@@ -1373,7 +1595,7 @@ function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
1373
1595
  const maxScore = top[0]?.[1] || 1;
1374
1596
  const results = [];
1375
1597
  for (const [name, score] of top) {
1376
- const content = skills[name];
1598
+ const content = skills[name] || loadUserSkill(name);
1377
1599
  if (content) {
1378
1600
  results.push({
1379
1601
  skillName: name,
@@ -1398,14 +1620,16 @@ function formatSkillsForPrompt(skills) {
1398
1620
  ${s.skillContent}`
1399
1621
  ).join("\n\n---\n\n");
1400
1622
  }
1401
- var __filename3, __dirname3, SKILL_KEYWORD_MAP, cachedSkills;
1623
+ var __filename3, __dirname3, SKILL_KEYWORD_MAP, cachedSkills, userSkillCache;
1402
1624
  var init_skill_loader = __esm({
1403
1625
  "src/engine/skill-loader.ts"() {
1404
1626
  "use strict";
1405
1627
  init_esm_shims();
1406
1628
  init_rules_loader();
1629
+ init_paths();
1407
1630
  init_asset_dir();
1408
- __filename3 = fileURLToPath4(import.meta.url);
1631
+ init_runtime_config_bridge();
1632
+ __filename3 = fileURLToPath3(import.meta.url);
1409
1633
  __dirname3 = dirname2(__filename3);
1410
1634
  SKILL_KEYWORD_MAP = (() => {
1411
1635
  const map = {};
@@ -1419,6 +1643,7 @@ var init_skill_loader = __esm({
1419
1643
  return map;
1420
1644
  })();
1421
1645
  cachedSkills = null;
1646
+ userSkillCache = {};
1422
1647
  }
1423
1648
  });
1424
1649
 
@@ -1435,6 +1660,12 @@ __export(runtime_config_exports, {
1435
1660
  deleteCustomRule: () => deleteCustomRule,
1436
1661
  deleteCustomSkill: () => deleteCustomSkill,
1437
1662
  deleteScenarioRule: () => deleteScenarioRule,
1663
+ effectiveDangerousPatterns: () => effectiveDangerousPatterns,
1664
+ effectiveInjectionKeywords: () => effectiveInjectionKeywords,
1665
+ effectiveIntentPatterns: () => effectiveIntentPatterns,
1666
+ effectivePasswordPatterns: () => effectivePasswordPatterns,
1667
+ effectiveSensitivePaths: () => effectiveSensitivePaths,
1668
+ effectiveUserDeletionPatterns: () => effectiveUserDeletionPatterns,
1438
1669
  filterEnabledRules: () => filterEnabledRules,
1439
1670
  generateSourceDefaults: () => generateSourceDefaults,
1440
1671
  getAgentRules: () => getAgentRules,
@@ -1478,9 +1709,9 @@ __export(runtime_config_exports, {
1478
1709
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1479
1710
  updateSkillEnabled: () => updateSkillEnabled
1480
1711
  });
1481
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3, statSync } from "fs";
1712
+ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync3, statSync } from "fs";
1482
1713
  import { join as join3 } from "path";
1483
- import { fileURLToPath as fileURLToPath5 } from "url";
1714
+ import { fileURLToPath as fileURLToPath4 } from "url";
1484
1715
  import { dirname as dirname3 } from "path";
1485
1716
  function runtimeDir() {
1486
1717
  return getRuntimeConfigDir();
@@ -1493,7 +1724,7 @@ function getRuntimeConfigPath() {
1493
1724
  function saveRuntimeConfig(config) {
1494
1725
  const p = getRuntimeConfigPath();
1495
1726
  const dir = p.substring(0, p.lastIndexOf("/"));
1496
- if (dir && !existsSync4(dir)) mkdirSync(dir, { recursive: true });
1727
+ if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
1497
1728
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
1498
1729
  invalidateRuntimeConfigCache();
1499
1730
  }
@@ -1511,10 +1742,11 @@ function emptyRuntimeConfig() {
1511
1742
  }
1512
1743
  function invalidateRuntimeConfigCache() {
1513
1744
  runtimeConfigCache = null;
1745
+ hasRuntimeConfigCache.clear();
1514
1746
  }
1515
1747
  function loadRuntimeConfig() {
1516
1748
  const p = getRuntimeConfigPath();
1517
- if (!existsSync4(p)) {
1749
+ if (!existsSync5(p)) {
1518
1750
  const seeded = mergeConfig({}, generateSourceDefaults());
1519
1751
  try {
1520
1752
  saveRuntimeConfig(seeded);
@@ -1535,7 +1767,13 @@ function loadRuntimeConfig() {
1535
1767
  layers: {
1536
1768
  L1_heuristic: data.layers?.L1_heuristic ?? true,
1537
1769
  L2_logic_rules: data.layers?.L2_logic_rules ?? true,
1538
- L3_llm_analysis: data.layers?.L3_llm_analysis ?? true
1770
+ L3_llm_analysis: data.layers?.L3_llm_analysis ?? true,
1771
+ // ⚠️ agent_rules_enabled / scenario_rules_enabled 必须一并带上:
1772
+ // isLayerEnabled 的判据是 `layerKey in layers`,键丢了就恒真,
1773
+ // 于是控制台的「agent 规则 / 场景规则」总开关**永远是开**(点了没反应)。
1774
+ // 这两个键本来就写在 seed 里(DEFAULT_LAYERS),只是这里没透传。
1775
+ agent_rules_enabled: data.layers?.agent_rules_enabled ?? true,
1776
+ scenario_rules_enabled: data.layers?.scenario_rules_enabled ?? true
1539
1777
  },
1540
1778
  L1_rules: merged.L1_rules || {},
1541
1779
  L2_rules: merged.L2_rules || {},
@@ -1577,21 +1815,48 @@ function filterEnabledRules(rules, categoryEnabled = true) {
1577
1815
  if (!categoryEnabled) return [];
1578
1816
  return rules.filter((r) => r.enabled !== false);
1579
1817
  }
1580
- function getEnabledL1CommandPatterns(runtime) {
1818
+ function _filterRulesByOs(rules, osType) {
1819
+ const accepted = acceptedOsValues(osType);
1820
+ return rules.filter((r) => accepted.includes(String(r.os || "linux").toLowerCase()));
1821
+ }
1822
+ function _userCustomRules(runtime, layers, requiredKey) {
1823
+ const list = Array.isArray(layers) ? layers : [layers];
1824
+ const out = [];
1825
+ for (const layer of list) {
1826
+ const cat = runtime?.[layer]?.user_custom;
1827
+ const rules = filterEnabledRules(cat?.rules || [], cat?.category_enabled);
1828
+ for (const r of rules) {
1829
+ if (!r[requiredKey]) continue;
1830
+ out.push(r.risk_level ? r : { ...r, risk_level: "high" });
1831
+ }
1832
+ }
1833
+ return out;
1834
+ }
1835
+ function _mergeUserCustom(primary, runtime, layers, key) {
1836
+ const extra = _userCustomRules(runtime, layers, key);
1837
+ return extra.length > 0 ? [...primary, ...extra] : [...primary];
1838
+ }
1839
+ function getEnabledL1CommandPatterns(runtime, osType) {
1581
1840
  const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
1582
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
1841
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
1842
+ rules = _mergeUserCustom(rules, runtime, "L1_rules", "pattern");
1843
+ if (osType) rules = _filterRulesByOs(rules, osType);
1844
+ return rules.map((r) => ({
1583
1845
  pattern: r.pattern || "",
1584
1846
  risk_level: r.risk_level || "",
1585
1847
  risk_type: r.risk_type || "",
1586
1848
  reason: r.reason || ""
1587
1849
  }));
1588
1850
  }
1589
- function getEnabledL2SensitivePaths(runtime) {
1851
+ function getEnabledL2SensitivePaths(runtime, osType) {
1590
1852
  let cat = runtime.L1_rules?.sensitive_path_access;
1591
1853
  if (!cat || !cat.rules || cat.rules.length === 0) {
1592
1854
  cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
1593
1855
  }
1594
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => {
1856
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
1857
+ rules = _mergeUserCustom(rules, runtime, ["L1_rules", "L2_rules"], "path");
1858
+ if (osType) rules = _filterRulesByOs(rules, osType);
1859
+ return rules.map((r) => {
1595
1860
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1596
1861
  if (r.credential) out.credential = true;
1597
1862
  if (r.read_only) out.read_only = true;
@@ -1601,13 +1866,24 @@ function getEnabledL2SensitivePaths(runtime) {
1601
1866
  }
1602
1867
  function hasRuntimeConfig() {
1603
1868
  const p = getRuntimeConfigPath();
1604
- if (!existsSync4(p)) return false;
1869
+ let key;
1605
1870
  try {
1606
- JSON.parse(readFileSync4(p, "utf-8"));
1607
- return true;
1871
+ const st = statSync(p);
1872
+ key = `${p}|${st.mtimeMs}|${st.size}`;
1608
1873
  } catch {
1609
1874
  return false;
1610
1875
  }
1876
+ const cached = hasRuntimeConfigCache.get(key);
1877
+ if (cached !== void 0) return cached;
1878
+ let ok = false;
1879
+ try {
1880
+ JSON.parse(readFileSync4(p, "utf-8"));
1881
+ ok = true;
1882
+ } catch {
1883
+ ok = false;
1884
+ }
1885
+ hasRuntimeConfigCache.set(key, ok);
1886
+ return ok;
1611
1887
  }
1612
1888
  function getDisabledL2RulesWithSkipL3() {
1613
1889
  let runtime;
@@ -1672,15 +1948,28 @@ function _syncDenyModeFields(rule) {
1672
1948
  }
1673
1949
  }
1674
1950
  function _l1CommandPatternsToRules() {
1675
- return DANGEROUS_PATTERNS.map((p) => ({
1676
- id: _generatePatternId(p.pattern instanceof RegExp ? p.pattern.source : p.pattern),
1677
- pattern: p.pattern instanceof RegExp ? p.pattern.source : p.pattern,
1678
- risk_level: p.severity,
1679
- risk_type: p.riskType,
1680
- reason: p.description,
1681
- enabled: true,
1682
- builtin: true
1683
- }));
1951
+ const out = [];
1952
+ const seen = /* @__PURE__ */ new Set();
1953
+ for (const osName of ["linux", "windows", "macos", "harmony"]) {
1954
+ for (const p of getDangerousPatternsForOs(osName)) {
1955
+ const src = p.pattern instanceof RegExp ? p.pattern.source : String(p.pattern);
1956
+ const rule = {
1957
+ id: _generatePatternId(src),
1958
+ pattern: src,
1959
+ risk_level: p.severity,
1960
+ risk_type: p.riskType,
1961
+ reason: p.description,
1962
+ enabled: true,
1963
+ builtin: true,
1964
+ os: p.os || "linux"
1965
+ };
1966
+ const fp = _ruleFingerprint(rule);
1967
+ if (seen.has(fp)) continue;
1968
+ seen.add(fp);
1969
+ out.push(rule);
1970
+ }
1971
+ }
1972
+ return out;
1684
1973
  }
1685
1974
  function _l1InjectionKeywordsToRules() {
1686
1975
  return INJECTION_KEYWORDS.map((kw) => ({
@@ -1693,28 +1982,42 @@ function _l1InjectionKeywordsToRules() {
1693
1982
  }));
1694
1983
  }
1695
1984
  function _l2SensitivePathsToRules() {
1696
- const all = [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS, ...CREDENTIAL_PATHS];
1697
- return all.map((sp) => {
1698
- const denyMode = _deriveDenyMode({
1699
- deny_mode: sp.deny_mode,
1700
- credential: sp.credential,
1701
- read_only: sp.read_only
1702
- });
1703
- const rule = {
1704
- id: `path_${_slugify(sp.path)}`,
1705
- path: sp.path,
1706
- risk_level: sp.riskLevel,
1707
- desc: sp.desc,
1708
- deny_mode: denyMode,
1709
- source_deny_mode: denyMode,
1710
- skip_l3_on_disabled: true,
1711
- enabled: true,
1712
- builtin: true
1713
- };
1714
- _syncDenyModeFields(rule);
1715
- if (sp.dirDelOnly) rule.dir_del_only = true;
1716
- return rule;
1717
- });
1985
+ const out = [];
1986
+ const seen = /* @__PURE__ */ new Set();
1987
+ for (const osName of ["linux", "windows", "macos", "harmony"]) {
1988
+ const { root, home, credential } = getSensitivePathsForOsBuiltin(osName);
1989
+ const all = [...root, ...home, ...credential];
1990
+ for (const sp of all) {
1991
+ const os3 = String(sp.os || "linux").toLowerCase();
1992
+ const denyMode = _deriveDenyMode({
1993
+ deny_mode: sp.deny_mode,
1994
+ credential: sp.credential,
1995
+ read_only: sp.read_only
1996
+ });
1997
+ const rule = {
1998
+ // id = path_<平台>_<slug>_<hash8>:平台段消除跨平台同名(.ssh/.aws 各一条),
1999
+ // hash 段消除同平台 slug 撞车(macOS 的 /Library/LaunchAgents 与
2000
+ // Library/LaunchAgents)—— Console 按 id 操作,撞 id 会误改别的规则。
2001
+ id: `path_${os3}_${_slugify(sp.path)}_${_fnv1a32(sp.path)}`,
2002
+ path: sp.path,
2003
+ risk_level: sp.riskLevel,
2004
+ desc: sp.desc,
2005
+ deny_mode: denyMode,
2006
+ source_deny_mode: denyMode,
2007
+ skip_l3_on_disabled: true,
2008
+ enabled: true,
2009
+ builtin: true,
2010
+ os: os3
2011
+ };
2012
+ _syncDenyModeFields(rule);
2013
+ if (sp.dirDelOnly) rule.dir_del_only = true;
2014
+ const fp = _ruleFingerprint(rule);
2015
+ if (seen.has(fp)) continue;
2016
+ seen.add(fp);
2017
+ out.push(rule);
2018
+ }
2019
+ }
2020
+ return out;
1718
2021
  }
1719
2022
  function _l2IntentPatternsToRules() {
1720
2023
  return INTENT_DEVIATION_PATTERNS.map((pat, i) => ({
@@ -1791,8 +2094,8 @@ function _l3SkillsToRules() {
1791
2094
  }
1792
2095
  function _scenarioRulesToDefaults() {
1793
2096
  const out = {};
1794
- const scenDir = existsSync4(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync4(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
1795
- if (!existsSync4(scenDir)) return out;
2097
+ const scenDir = existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2098
+ if (!existsSync5(scenDir)) return out;
1796
2099
  let entries;
1797
2100
  try {
1798
2101
  entries = readdirSync3(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -1801,7 +2104,7 @@ function _scenarioRulesToDefaults() {
1801
2104
  }
1802
2105
  for (const scenarioId of entries) {
1803
2106
  const rulesFile = join3(scenDir, scenarioId, "rules.json");
1804
- if (!existsSync4(rulesFile)) continue;
2107
+ if (!existsSync5(rulesFile)) continue;
1805
2108
  let data;
1806
2109
  try {
1807
2110
  data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
@@ -1840,8 +2143,8 @@ function _scenarioRulesToDefaults() {
1840
2143
  }
1841
2144
  function _agentRulesToDefaults() {
1842
2145
  const out = {};
1843
- const agentsDir = existsSync4(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync4(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
1844
- if (!existsSync4(agentsDir)) return out;
2146
+ const agentsDir = existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2147
+ if (!existsSync5(agentsDir)) return out;
1845
2148
  let entries;
1846
2149
  try {
1847
2150
  entries = readdirSync3(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -1850,7 +2153,7 @@ function _agentRulesToDefaults() {
1850
2153
  }
1851
2154
  for (const agentId of entries) {
1852
2155
  const rulesFile = join3(agentsDir, agentId, "rules.json");
1853
- if (!existsSync4(rulesFile)) continue;
2156
+ if (!existsSync5(rulesFile)) continue;
1854
2157
  let data;
1855
2158
  try {
1856
2159
  data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
@@ -1946,7 +2249,7 @@ function _ltlRulesToRuntime() {
1946
2249
  let mc = {};
1947
2250
  for (const dir of cands) {
1948
2251
  const p = join3(dir, "mc_rules.json");
1949
- if (existsSync4(p)) {
2252
+ if (existsSync5(p)) {
1950
2253
  try {
1951
2254
  mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
1952
2255
  } catch {
@@ -1975,17 +2278,27 @@ function normalizePatternForFingerprint(pattern) {
1975
2278
  return pattern.replace(/\\\//g, "/");
1976
2279
  }
1977
2280
  function _ruleFingerprint(rule) {
2281
+ const osTag = String(rule.os || "linux").toLowerCase();
1978
2282
  if (rule.intent_keywords && rule.intent_keywords.length || rule.dangerous_actions && rule.dangerous_actions.length) {
1979
2283
  return JSON.stringify([
2284
+ osTag,
1980
2285
  [...rule.intent_keywords || []].sort(),
1981
2286
  [...rule.dangerous_actions || []].sort()
1982
2287
  ]);
1983
2288
  }
1984
- if (rule.path) return JSON.stringify([normalizePatternForFingerprint(rule.path)]);
1985
- if (rule.pattern) return JSON.stringify([normalizePatternForFingerprint(rule.pattern)]);
1986
- if (rule.keyword) return JSON.stringify([rule.keyword, rule.lang || ""]);
1987
- if (rule.value) return JSON.stringify([rule.value]);
1988
- return JSON.stringify([rule.id || ""]);
2289
+ if (rule.path) return JSON.stringify([osTag, normalizePatternForFingerprint(rule.path)]);
2290
+ if (rule.pattern) return JSON.stringify([osTag, normalizePatternForFingerprint(rule.pattern)]);
2291
+ if (rule.keyword) return JSON.stringify([osTag, rule.keyword, rule.lang || ""]);
2292
+ if (rule.value) return JSON.stringify([osTag, rule.value]);
2293
+ return JSON.stringify([osTag, rule.id || ""]);
2294
+ }
2295
+ function _applyRuleEnabled(rule, enabled) {
2296
+ rule.enabled = enabled;
2297
+ if (enabled) {
2298
+ delete rule.user_disabled;
2299
+ } else {
2300
+ rule.user_disabled = true;
2301
+ }
1989
2302
  }
1990
2303
  function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
1991
2304
  const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
@@ -2008,13 +2321,14 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
2008
2321
  srcFps.add(fp);
2009
2322
  if (userByFp.has(fp)) {
2010
2323
  const existing = userByFp.get(fp);
2011
- const userEnabled = existing.enabled;
2324
+ const userDisabled = existing.user_disabled === true;
2012
2325
  const userSkipL3 = existing.skip_l3_on_disabled;
2013
2326
  const userDenyMode = existing.deny_mode;
2014
2327
  const userSourceDeny = existing.source_deny_mode;
2015
2328
  Object.keys(existing).forEach((k) => delete existing[k]);
2016
2329
  Object.assign(existing, srcR);
2017
- existing.enabled = userEnabled;
2330
+ existing.enabled = !userDisabled;
2331
+ if (userDisabled) existing.user_disabled = true;
2018
2332
  if (userSkipL3 !== void 0) existing.skip_l3_on_disabled = userSkipL3;
2019
2333
  if (userDenyMode && userSourceDeny && userDenyMode !== userSourceDeny) {
2020
2334
  existing.deny_mode = userDenyMode;
@@ -2040,7 +2354,19 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
2040
2354
  }
2041
2355
  for (const catName of Object.keys(userLayer)) {
2042
2356
  if (catName === "user_custom") continue;
2043
- if (!(catName in srcLayer)) delete userLayer[catName];
2357
+ if (!(catName in srcLayer)) {
2358
+ const kept = (userLayer[catName].rules || []).filter((r) => r.builtin !== true);
2359
+ if (kept.length) {
2360
+ const uc = userLayer.user_custom || (userLayer.user_custom = {
2361
+ category_enabled: true,
2362
+ category_desc: "",
2363
+ rules: []
2364
+ });
2365
+ const existing = new Set((uc.rules || []).map((r) => r.id));
2366
+ uc.rules = [...uc.rules || [], ...kept.filter((r) => !existing.has(r.id))];
2367
+ }
2368
+ delete userLayer[catName];
2369
+ }
2044
2370
  }
2045
2371
  }
2046
2372
  function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
@@ -2088,10 +2414,11 @@ function _mergeAgentRules(userCfg, srcDefaults, partition = "agent_rules") {
2088
2414
  srcFps.add(fp);
2089
2415
  if (userByFp.has(fp)) {
2090
2416
  const existing = userByFp.get(fp);
2091
- const userEnabled = existing.enabled;
2417
+ const userDisabled = existing.user_disabled === true;
2092
2418
  Object.keys(existing).forEach((k) => delete existing[k]);
2093
2419
  Object.assign(existing, srcR);
2094
- existing.enabled = userEnabled;
2420
+ existing.enabled = !userDisabled;
2421
+ if (userDisabled) existing.user_disabled = true;
2095
2422
  } else {
2096
2423
  userRules.push({ ...srcR, enabled: true });
2097
2424
  }
@@ -2155,17 +2482,23 @@ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
2155
2482
  delete src[catName];
2156
2483
  }
2157
2484
  }
2158
- function getEnabledL1InjectionKeywords(runtime) {
2485
+ function getEnabledL1InjectionKeywords(runtime, osType) {
2159
2486
  const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
2160
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
2487
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
2488
+ rules = _mergeUserCustom(rules, runtime, "L1_rules", "keyword");
2489
+ if (osType) rules = _filterRulesByOs(rules, osType);
2490
+ return rules.map((r) => ({
2161
2491
  keyword: r.keyword || "",
2162
2492
  risk_level: r.risk_level || "",
2163
2493
  lang: r.lang || "en"
2164
2494
  }));
2165
2495
  }
2166
- function getEnabledL2IntentPatterns(runtime) {
2496
+ function getEnabledL2IntentPatterns(runtime, osType) {
2167
2497
  const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
2168
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
2498
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
2499
+ rules = _mergeUserCustom(rules, runtime, "L2_rules", "intent_keywords");
2500
+ if (osType) rules = _filterRulesByOs(rules, osType);
2501
+ return rules.map((r) => ({
2169
2502
  intent_keywords: r.intent_keywords || [],
2170
2503
  dangerous_actions: r.dangerous_actions || [],
2171
2504
  reason: r.reason || ""
@@ -2173,11 +2506,13 @@ function getEnabledL2IntentPatterns(runtime) {
2173
2506
  }
2174
2507
  function getEnabledL2PasswordPatterns(runtime) {
2175
2508
  const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
2176
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => r.pattern || "");
2509
+ const rules = _mergeUserCustom(filterEnabledRules(cat.rules || [], cat.category_enabled), runtime, "L2_rules", "pattern");
2510
+ return rules.map((r) => r.pattern || "");
2177
2511
  }
2178
2512
  function getEnabledL2UserDeletionPatterns(runtime) {
2179
2513
  const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
2180
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => r.pattern || "");
2514
+ const rules = _mergeUserCustom(filterEnabledRules(cat.rules || [], cat.category_enabled), runtime, "L2_rules", "pattern");
2515
+ return rules.map((r) => r.pattern || "");
2181
2516
  }
2182
2517
  function isL2CategoryEnabled(runtime, categoryKey) {
2183
2518
  const cat = runtime.L2_rules?.[categoryKey];
@@ -2196,45 +2531,126 @@ function getDisabledSkillIds(runtime) {
2196
2531
  }
2197
2532
  return disabled;
2198
2533
  }
2199
- function getEnabledL1CommandPatternsOrDefault(runtime) {
2200
- const enabled = getEnabledL1CommandPatterns(runtime);
2201
- if (enabled.length) return enabled;
2202
- if (!hasRuntimeConfig()) return _l1CommandPatternsToRules();
2203
- return [];
2204
- }
2205
- function getEnabledL1InjectionKeywordsOrDefault(runtime) {
2206
- const enabled = getEnabledL1InjectionKeywords(runtime);
2207
- if (enabled.length) return enabled;
2208
- if (!hasRuntimeConfig()) return _l1InjectionKeywordsToRules();
2209
- return [];
2210
- }
2211
- function getEnabledL2SensitivePathsOrDefault(runtime) {
2212
- const enabled = getEnabledL2SensitivePaths(runtime);
2213
- if (enabled.length) return enabled;
2214
- if (!hasRuntimeConfig()) return _l2SensitivePathsToRules();
2215
- return [];
2216
- }
2217
- function getEnabledL2IntentPatternsOrDefault(runtime) {
2218
- const enabled = getEnabledL2IntentPatterns(runtime);
2219
- if (enabled.length) return enabled;
2220
- if (!hasRuntimeConfig()) return _l2IntentPatternsToRules();
2221
- return [];
2222
- }
2223
- function getEnabledL2PasswordPatternsOrDefault(runtime) {
2224
- const enabled = getEnabledL2PasswordPatterns(runtime);
2225
- if (enabled.length) return enabled;
2226
- if (!hasRuntimeConfig()) {
2227
- return PASSWORD_MODIFY_PATTERNS.map((p) => p.source);
2228
- }
2229
- return [];
2230
- }
2231
- function getEnabledL2UserDeletionPatternsOrDefault(runtime) {
2232
- const enabled = getEnabledL2UserDeletionPatterns(runtime);
2233
- if (enabled.length) return enabled;
2234
- if (!hasRuntimeConfig()) {
2235
- return USER_DELETION_PATTERNS.map((p) => p.source);
2236
- }
2237
- return [];
2534
+ function _filterDefaultsByOs(rules, osType) {
2535
+ const accepted = acceptedOsValues(osType);
2536
+ return rules.filter((r) => !r.os || accepted.includes(String(r.os).toLowerCase()));
2537
+ }
2538
+ function _hasRulesForOs(rules, osType) {
2539
+ return _filterRulesByOs(rules || [], osType || "linux").length > 0;
2540
+ }
2541
+ function getEnabledL1CommandPatternsOrDefault(runtime, osType) {
2542
+ const cat = runtime.L1_rules?.dangerous_commands;
2543
+ if (_hasRulesForOs(cat?.rules, osType)) {
2544
+ return getEnabledL1CommandPatterns(runtime, osType);
2545
+ }
2546
+ return _mergeUserCustom(_filterDefaultsByOs(_l1CommandPatternsToRules(), osType), runtime, "L1_rules", "pattern");
2547
+ }
2548
+ function getEnabledL1InjectionKeywordsOrDefault(runtime, osType) {
2549
+ const cat = runtime.L1_rules?.injection_detection;
2550
+ if (_hasRulesForOs(cat?.rules, osType)) {
2551
+ return getEnabledL1InjectionKeywords(runtime, osType);
2552
+ }
2553
+ return _mergeUserCustom(_l1InjectionKeywordsToRules(), runtime, "L1_rules", "keyword");
2554
+ }
2555
+ function getEnabledL2SensitivePathsOrDefault(runtime, osType) {
2556
+ const spCat = _sensitivePathCategoryOf(runtime);
2557
+ if (_hasRulesForOs(spCat?.rules, osType)) {
2558
+ return getEnabledL2SensitivePaths(runtime, osType);
2559
+ }
2560
+ return _mergeUserCustom(_filterDefaultsByOs(_l2SensitivePathsToRules(), osType), runtime, ["L1_rules", "L2_rules"], "path");
2561
+ }
2562
+ function getEnabledL2IntentPatternsOrDefault(runtime, osType) {
2563
+ const cat = runtime.L2_rules?.intent_consistency;
2564
+ if (_hasRulesForOs(cat?.rules, osType)) {
2565
+ return getEnabledL2IntentPatterns(runtime, osType);
2566
+ }
2567
+ return _mergeUserCustom(_filterDefaultsByOs(_l2IntentPatternsToRules(), osType), runtime, "L2_rules", "intent_keywords");
2568
+ }
2569
+ function getEnabledL2PasswordPatternsOrDefault(runtime, osType) {
2570
+ const cat = runtime.L2_rules?.password_modify_consent;
2571
+ if (_hasRulesForOs(cat?.rules, osType)) {
2572
+ return getEnabledL2PasswordPatterns(runtime);
2573
+ }
2574
+ return PASSWORD_MODIFY_PATTERNS.map((p) => p.source);
2575
+ }
2576
+ function getEnabledL2UserDeletionPatternsOrDefault(runtime, osType) {
2577
+ const cat = runtime.L2_rules?.user_deletion_consent;
2578
+ if (_hasRulesForOs(cat?.rules, osType)) {
2579
+ return getEnabledL2UserDeletionPatterns(runtime);
2580
+ }
2581
+ return USER_DELETION_PATTERNS.map((p) => p.source);
2582
+ }
2583
+ function _effective(osType, key, compute) {
2584
+ const runtime = loadRuntimeConfig();
2585
+ let bucket = _effectiveCache.get(runtime);
2586
+ if (!bucket) {
2587
+ bucket = /* @__PURE__ */ new Map();
2588
+ _effectiveCache.set(runtime, bucket);
2589
+ }
2590
+ const k = `${key}|${osType || ""}`;
2591
+ if (bucket.has(k)) return bucket.get(k);
2592
+ const value = compute(runtime);
2593
+ bucket.set(k, value);
2594
+ return value;
2595
+ }
2596
+ function effectiveDangerousPatterns(osType) {
2597
+ return _effective(osType, "danger", (rt) => {
2598
+ const rules = getEnabledL1CommandPatternsOrDefault(rt, osType);
2599
+ return rules.filter((r) => r.pattern).map((r) => ({
2600
+ pattern: new RegExp(String(r.pattern), "i"),
2601
+ severity: String(r.risk_level || "medium"),
2602
+ riskType: String(r.risk_type || ""),
2603
+ description: String(r.reason || ""),
2604
+ os: String(r.os || "linux").toLowerCase()
2605
+ }));
2606
+ });
2607
+ }
2608
+ function effectiveInjectionKeywords(osType) {
2609
+ return _effective(osType, "injection", (rt) => {
2610
+ const rules = getEnabledL1InjectionKeywordsOrDefault(rt, osType);
2611
+ return rules.map((r) => ({
2612
+ keyword: String(r.keyword || ""),
2613
+ riskLevel: String(r.risk_level || ""),
2614
+ lang: String(r.lang || "en")
2615
+ })).filter((r) => r.keyword);
2616
+ });
2617
+ }
2618
+ function effectiveSensitivePaths(osType) {
2619
+ return _effective(osType, "sensitive", (rt) => {
2620
+ const rules = getEnabledL2SensitivePathsOrDefault(rt, osType);
2621
+ return rules.filter((r) => r.path).map((r) => ({
2622
+ path: String(r.path),
2623
+ riskLevel: String(r.risk_level || "high"),
2624
+ desc: String(r.desc || ""),
2625
+ credential: r.credential === true ? true : void 0,
2626
+ readOnly: r.read_only === true ? true : void 0,
2627
+ dirDelOnly: r.dir_del_only === true ? true : void 0,
2628
+ denyMode: String(r.deny_mode || ""),
2629
+ os: String(r.os || "linux").toLowerCase()
2630
+ }));
2631
+ });
2632
+ }
2633
+ function effectiveIntentPatterns(osType) {
2634
+ return _effective(osType, "intent", (rt) => {
2635
+ const rules = getEnabledL2IntentPatternsOrDefault(rt, osType);
2636
+ return rules.map((r) => ({
2637
+ intentKeywords: r.intent_keywords || [],
2638
+ dangerousActions: r.dangerous_actions || [],
2639
+ reason: String(r.reason || ""),
2640
+ os: String(r.os || "linux").toLowerCase()
2641
+ }));
2642
+ });
2643
+ }
2644
+ function effectivePasswordPatterns(osType) {
2645
+ return _effective(osType, "password", (rt) => getEnabledL2PasswordPatternsOrDefault(rt, osType).map((p) => new RegExp(p, "i")));
2646
+ }
2647
+ function effectiveUserDeletionPatterns(osType) {
2648
+ return _effective(osType, "userdel", (rt) => getEnabledL2UserDeletionPatternsOrDefault(rt, osType).map((p) => new RegExp(p, "i")));
2649
+ }
2650
+ function _sensitivePathCategoryOf(runtime) {
2651
+ const l1 = runtime.L1_rules?.sensitive_path_access;
2652
+ if (l1 && (l1.rules || []).length > 0) return l1;
2653
+ return runtime.L2_rules?.sensitive_path_access;
2238
2654
  }
2239
2655
  function _validateSkillId(skillId) {
2240
2656
  if (!skillId || !SKILL_ID_RE.test(skillId)) {
@@ -2251,7 +2667,7 @@ function updateRuleEnabled(layer, category, ruleId, enabled) {
2251
2667
  const runtime = loadRuntimeConfig();
2252
2668
  for (const r of runtime[layer]?.[category]?.rules || []) {
2253
2669
  if (r.id === ruleId) {
2254
- r.enabled = enabled;
2670
+ _applyRuleEnabled(r, enabled);
2255
2671
  break;
2256
2672
  }
2257
2673
  }
@@ -2321,7 +2737,7 @@ function deleteCustomRule(layer, category, ruleId) {
2321
2737
  function addCustomSkill(skillId, category, keywords, content) {
2322
2738
  _validateSkillId(skillId);
2323
2739
  const runtime = loadRuntimeConfig();
2324
- const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2740
+ const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
2325
2741
  try {
2326
2742
  mkdirSync(skillsDir, { recursive: true });
2327
2743
  } catch {
@@ -2341,9 +2757,9 @@ function deleteCustomSkill(skillId, category) {
2341
2757
  if (idx >= 0) {
2342
2758
  if (skills[idx].builtin) return null;
2343
2759
  skills.splice(idx, 1);
2344
- const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2760
+ const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
2345
2761
  try {
2346
- if (existsSync4(skillPath)) unlinkSync(skillPath);
2762
+ if (existsSync5(skillPath)) unlinkSync(skillPath);
2347
2763
  } catch {
2348
2764
  }
2349
2765
  saveRuntimeConfig(runtime);
@@ -2422,7 +2838,7 @@ function updateAgentRuleEnabled(agentId, category, ruleFp, enabled) {
2422
2838
  const targetFp = _ruleFingerprint(ruleFp);
2423
2839
  for (const r of cat.rules || []) {
2424
2840
  if (_ruleFingerprint(r) === targetFp) {
2425
- r.enabled = enabled;
2841
+ _applyRuleEnabled(r, enabled);
2426
2842
  break;
2427
2843
  }
2428
2844
  }
@@ -2457,130 +2873,412 @@ function updateAgentCategoryEnabled(agentId, category, enabled) {
2457
2873
  saveRuntimeConfig(runtime);
2458
2874
  return runtime;
2459
2875
  }
2460
- function getScenarioRules(runtime) {
2461
- return runtime.scenario_rules || {};
2876
+ function getScenarioRules(runtime) {
2877
+ return runtime.scenario_rules || {};
2878
+ }
2879
+ function getEnabledScenarioExtensions(runtime, scenarioId) {
2880
+ const scenData = runtime.scenario_rules?.[scenarioId];
2881
+ if (!scenData) return null;
2882
+ const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
2883
+ pattern: r.pattern,
2884
+ risk_level: r.risk_level || "",
2885
+ risk_type: r.risk_type || "",
2886
+ reason: r.reason || ""
2887
+ }));
2888
+ const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
2889
+ keyword: r.keyword,
2890
+ risk_level: r.risk_level || "",
2891
+ lang: r.lang || "en"
2892
+ }));
2893
+ const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
2894
+ path: r.path,
2895
+ risk_level: r.risk_level || "",
2896
+ desc: r.desc || "",
2897
+ credential: r.credential,
2898
+ read_only: r.read_only,
2899
+ deny_mode: r.deny_mode
2900
+ }));
2901
+ return {
2902
+ agent_id: scenarioId,
2903
+ description: "",
2904
+ extra_command_patterns: cmdPatterns,
2905
+ extra_injection_keywords: injKeywords,
2906
+ extra_sensitive_paths: sensitivePaths,
2907
+ builtin_safe_skills: [],
2908
+ builtin_safe_action_types: [],
2909
+ extra_skills: []
2910
+ };
2911
+ }
2912
+ function updateScenarioRuleEnabled(scenarioId, category, ruleFp, enabled) {
2913
+ const runtime = loadRuntimeConfig();
2914
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2915
+ if (cat) {
2916
+ const targetFp = _ruleFingerprint(ruleFp);
2917
+ for (const r of cat.rules || []) {
2918
+ if (_ruleFingerprint(r) === targetFp) {
2919
+ _applyRuleEnabled(r, enabled);
2920
+ break;
2921
+ }
2922
+ }
2923
+ }
2924
+ saveRuntimeConfig(runtime);
2925
+ return runtime;
2926
+ }
2927
+ function addScenarioRule(scenarioId, category, rule) {
2928
+ const runtime = loadRuntimeConfig();
2929
+ if (!runtime.scenario_rules) runtime.scenario_rules = {};
2930
+ if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
2931
+ const cats = runtime.scenario_rules[scenarioId];
2932
+ if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
2933
+ (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
2934
+ saveRuntimeConfig(runtime);
2935
+ return runtime;
2936
+ }
2937
+ function deleteScenarioRule(scenarioId, category, ruleFp) {
2938
+ const runtime = loadRuntimeConfig();
2939
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2940
+ if (cat) {
2941
+ const targetFp = _ruleFingerprint(ruleFp);
2942
+ cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
2943
+ }
2944
+ saveRuntimeConfig(runtime);
2945
+ return runtime;
2946
+ }
2947
+ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2948
+ const runtime = loadRuntimeConfig();
2949
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2950
+ if (cat) cat.category_enabled = enabled;
2951
+ saveRuntimeConfig(runtime);
2952
+ return runtime;
2953
+ }
2954
+ var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, runtimeConfigCache, hasRuntimeConfigCache, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, _effectiveCache, SKILL_ID_RE;
2955
+ var init_runtime_config = __esm({
2956
+ "src/infra/runtime-config.ts"() {
2957
+ "use strict";
2958
+ init_esm_shims();
2959
+ init_paths();
2960
+ init_heuristic();
2961
+ init_logic_rules();
2962
+ init_skill_loader();
2963
+ init_runtime_config_bridge();
2964
+ __dirname4 = dirname3(fileURLToPath4(import.meta.url));
2965
+ ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2966
+ RUNTIME_CONFIG_DIR = runtimeDir();
2967
+ RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2968
+ LAYER_DISABLE_ENV = {
2969
+ L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2970
+ L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2971
+ L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2972
+ };
2973
+ DEFAULT_LAYERS = {
2974
+ L1_heuristic: true,
2975
+ L2_logic_rules: true,
2976
+ L3_llm_analysis: true,
2977
+ agent_rules_enabled: true,
2978
+ scenario_rules_enabled: true
2979
+ };
2980
+ DENY_MODE_LABELS = {
2981
+ deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2982
+ deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2983
+ deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2984
+ };
2985
+ runtimeConfigCache = null;
2986
+ hasRuntimeConfigCache = /* @__PURE__ */ new Map();
2987
+ _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2988
+ _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2989
+ _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2990
+ _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2991
+ _effectiveCache = /* @__PURE__ */ new WeakMap();
2992
+ SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2993
+ registerRuntimeConfigBridge({
2994
+ hasRuntimeConfig,
2995
+ loadRuntimeConfig,
2996
+ effectiveDangerousPatterns,
2997
+ effectiveInjectionKeywords,
2998
+ effectiveSensitivePaths,
2999
+ effectiveIntentPatterns,
3000
+ effectivePasswordPatterns,
3001
+ effectiveUserDeletionPatterns,
3002
+ disabledSkillIds: () => getDisabledSkillIds(loadRuntimeConfig())
3003
+ });
3004
+ }
3005
+ });
3006
+
3007
+ // src/config.ts
3008
+ var config_exports = {};
3009
+ __export(config_exports, {
3010
+ detectProvider: () => detectProvider,
3011
+ getCustomRules: () => getCustomRules,
3012
+ getLLMConfig: () => getLLMConfig,
3013
+ getLLMFailMode: () => getLLMFailMode,
3014
+ getProviderHeaders: () => getProviderHeaders,
3015
+ getRetryConfig: () => getRetryConfig,
3016
+ getTimeoutConfig: () => getTimeoutConfig,
3017
+ isBrainEnabled: () => isBrainEnabled,
3018
+ isFormalObserveMode: () => isFormalObserveMode,
3019
+ isHeuristicEnabled: () => isHeuristicEnabled,
3020
+ isLLMAvailable: () => isLLMAvailable,
3021
+ isLogicRulesEnabled: () => isLogicRulesEnabled,
3022
+ probeLLMConnection: () => probeLLMConnection,
3023
+ updateLLMConfig: () => updateLLMConfig
3024
+ });
3025
+ import fs2 from "fs";
3026
+ import path2 from "path";
3027
+ import { fileURLToPath as fileURLToPath5 } from "url";
3028
+ function loadConfigFile(filePath) {
3029
+ try {
3030
+ const raw = fs2.readFileSync(filePath, "utf-8");
3031
+ return JSON.parse(raw);
3032
+ } catch {
3033
+ return null;
3034
+ }
3035
+ }
3036
+ function resolveConfigPath() {
3037
+ const envPath = process.env.AGENT_MOSS_CONFIG_PATH;
3038
+ if (envPath) return envPath;
3039
+ const userConfig = path2.join(getConfigDir(), "config.json");
3040
+ if (fs2.existsSync(userConfig)) return userConfig;
3041
+ const defaultCandidates = [
3042
+ path2.join(__dirname, "config.json.example"),
3043
+ path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example")
3044
+ ];
3045
+ const defaultConfig = defaultCandidates.find((p) => fs2.existsSync(p));
3046
+ if (defaultConfig) {
3047
+ const configDir = path2.dirname(userConfig);
3048
+ fs2.mkdirSync(configDir, { recursive: true });
3049
+ const pkgConfig = loadConfigFile(defaultConfig);
3050
+ const safeConfig = pkgConfig ? { ...pkgConfig, llm: { ...pkgConfig.llm, api_key: "" } } : {};
3051
+ fs2.writeFileSync(userConfig, JSON.stringify(safeConfig, null, 4), "utf-8");
3052
+ console.log(`[AgentMoss] \u5DF2\u521B\u5EFA\u7528\u6237\u914D\u7F6E\u6587\u4EF6: ${userConfig}`);
3053
+ console.log(`[AgentMoss] \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\u914D\u7F6E\u4F60\u7684 LLM API Key`);
3054
+ }
3055
+ return userConfig;
3056
+ }
3057
+ function loadLLMConfigFromFile(configPath) {
3058
+ const data = loadConfigFile(configPath);
3059
+ if (!data?.llm) return {};
3060
+ const llm = data.llm;
3061
+ return {
3062
+ provider: llm.provider || "",
3063
+ baseUrl: llm.base_url || "",
3064
+ apiKey: llm.api_key || "",
3065
+ model: llm.model || "",
3066
+ temperature: llm.temperature ?? 0.1,
3067
+ maxTokens: llm.max_tokens ?? 4096,
3068
+ timeout: llm.timeout ?? 40
3069
+ };
3070
+ }
3071
+ function loadTimeoutRetryFromFile(configPath) {
3072
+ const data = loadConfigFile(configPath);
3073
+ const t = data?.timeout ?? {};
3074
+ const r = data?.retry ?? {};
3075
+ return {
3076
+ timeout: {
3077
+ totalTimeout: Number(t.total_timeout ?? 90),
3078
+ prompt1Timeout: Number(t.prompt1_timeout ?? 40),
3079
+ prompt2Timeout: Number(t.prompt2_timeout ?? 20),
3080
+ stepInterval: Number(t.step_interval ?? 0)
3081
+ },
3082
+ retry: {
3083
+ maxRetries: Number(r.max_retries ?? 2),
3084
+ retryInterval: Number(r.retry_interval ?? 2)
3085
+ }
3086
+ };
3087
+ }
3088
+ function getTimeoutConfig() {
3089
+ return timeoutConfig;
3090
+ }
3091
+ function getRetryConfig() {
3092
+ return retryConfig;
3093
+ }
3094
+ function wildcardToRegExp(pattern) {
3095
+ return pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
3096
+ }
3097
+ function loadCustomRules() {
3098
+ const rules = [];
3099
+ try {
3100
+ const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3101
+ if (raw) {
3102
+ const parsed = JSON.parse(raw);
3103
+ if (Array.isArray(parsed)) {
3104
+ for (const r of parsed) {
3105
+ if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3106
+ }
3107
+ }
3108
+ }
3109
+ } catch {
3110
+ }
3111
+ for (const rulesPath of USER_RULES_PATHS) {
3112
+ try {
3113
+ if (!fs2.existsSync(rulesPath)) continue;
3114
+ const data = JSON.parse(fs2.readFileSync(rulesPath, "utf-8"));
3115
+ const actions = Array.isArray(data?.sensitive_actions) ? data.sensitive_actions : [];
3116
+ for (const r of actions) {
3117
+ const p = r?.pattern;
3118
+ if (!p) continue;
3119
+ rules.push({ pattern: wildcardToRegExp(p), action: "Deny", severity: "high" });
3120
+ }
3121
+ break;
3122
+ } catch {
3123
+ }
3124
+ }
3125
+ return rules;
3126
+ }
3127
+ function getLLMConfig() {
3128
+ return llmConfig;
3129
+ }
3130
+ function updateLLMConfig(partial) {
3131
+ llmConfig = { ...llmConfig, ...partial };
3132
+ }
3133
+ function getCustomRules() {
3134
+ return customRules;
3135
+ }
3136
+ function isLLMAvailable() {
3137
+ if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
3138
+ if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
3139
+ return llmConfig.apiKey.length > 0;
3140
+ }
3141
+ async function probeLLMConnection(timeoutMs = 3e4) {
3142
+ if (!isLLMAvailable()) return false;
3143
+ const OpenAI3 = (await import("openai")).default;
3144
+ const config = getLLMConfig();
3145
+ const provider = detectProvider(config.baseUrl, config.provider);
3146
+ const extraHeaders = getProviderHeaders(provider);
3147
+ const client = new OpenAI3({
3148
+ baseURL: config.baseUrl,
3149
+ apiKey: config.apiKey,
3150
+ timeout: timeoutMs,
3151
+ maxRetries: 0,
3152
+ defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
3153
+ });
3154
+ try {
3155
+ const resp = await client.chat.completions.create({
3156
+ model: config.model,
3157
+ messages: [{ role: "user", content: "ping" }],
3158
+ max_tokens: 1
3159
+ });
3160
+ return !!(resp && resp.choices && resp.choices[0]);
3161
+ } catch {
3162
+ return false;
3163
+ }
3164
+ }
3165
+ function getLLMFailMode() {
3166
+ const val = (process.env.AGENT_MOSS_LLM_FAIL_MODE || "fail_open").toLowerCase();
3167
+ return val === "fail_closed" ? "fail_closed" : "fail_open";
3168
+ }
3169
+ function detectProvider(baseUrl, providerHint = "") {
3170
+ if (providerHint) {
3171
+ const hint = providerHint.toLowerCase();
3172
+ if (["claude", "anthropic"].includes(hint)) return "anthropic";
3173
+ if (["google", "gemini"].includes(hint)) return "gemini";
3174
+ if (["zai-coding-plan", "zhipu-coding-plan", "zhipuai-coding-plan"].includes(hint)) return "zai-coding-plan";
3175
+ if (["zai", "z-ai", "z.ai", "zai-cn", "zai-china", "zhipu", "glm-cn", "bigmodel"].includes(hint)) return "zhipu";
3176
+ if (["xai", "xai-grok"].includes(hint)) return "xai";
3177
+ if (["minimax", "minimax-openai"].includes(hint)) return "minimax";
3178
+ if (["minimax-anthropic"].includes(hint)) return "minimax-anthropic";
3179
+ if (["other", "custom", "local"].includes(hint)) return "openai-compatible";
3180
+ return hint;
3181
+ }
3182
+ const url = baseUrl.toLowerCase();
3183
+ if (url.includes("openrouter.ai")) return "openrouter";
3184
+ if (url.includes("api.openai.com")) return "openai";
3185
+ if (url.includes("anthropic.com") || url.includes("api.anthropic.com")) return "anthropic";
3186
+ if (url.includes("generativelanguage.googleapis.com") || url.includes("gemini")) return "gemini";
3187
+ if (url.includes("api.deepseek.com")) return "deepseek";
3188
+ if (url.includes("api.groq.com")) return "groq";
3189
+ if (url.includes("api.mistral.ai")) return "mistral";
3190
+ if (url.includes("api.together.xyz")) return "together";
3191
+ if (url.includes("api.x.ai")) return "xai";
3192
+ if (url.includes("api.minimaxi.com")) return "minimax";
3193
+ if (url.includes("api-ai.gitcode.com")) return "gitcode";
3194
+ if (url.includes("api.z.ai")) return "zai-coding-plan";
3195
+ if (url.includes("open.bigmodel.cn") || url.includes("bigmodel")) return "zhipu";
3196
+ if (url.includes(":11434")) return "ollama";
3197
+ return "openai-compatible";
3198
+ }
3199
+ function getProviderHeaders(provider) {
3200
+ return PROVIDER_HEADERS[provider] || {};
2462
3201
  }
2463
- function getEnabledScenarioExtensions(runtime, scenarioId) {
2464
- const scenData = runtime.scenario_rules?.[scenarioId];
2465
- if (!scenData) return null;
2466
- const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
2467
- pattern: r.pattern,
2468
- risk_level: r.risk_level || "",
2469
- risk_type: r.risk_type || "",
2470
- reason: r.reason || ""
2471
- }));
2472
- const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
2473
- keyword: r.keyword,
2474
- risk_level: r.risk_level || "",
2475
- lang: r.lang || "en"
2476
- }));
2477
- const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
2478
- path: r.path,
2479
- risk_level: r.risk_level || "",
2480
- desc: r.desc || "",
2481
- credential: r.credential,
2482
- read_only: r.read_only,
2483
- deny_mode: r.deny_mode
2484
- }));
2485
- return {
2486
- agent_id: scenarioId,
2487
- description: "",
2488
- extra_command_patterns: cmdPatterns,
2489
- extra_injection_keywords: injKeywords,
2490
- extra_sensitive_paths: sensitivePaths,
2491
- builtin_safe_skills: [],
2492
- builtin_safe_action_types: [],
2493
- extra_skills: []
2494
- };
3202
+ function isHeuristicEnabled() {
3203
+ if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
3204
+ return layerEnabledOrDefault("L1_heuristic");
2495
3205
  }
2496
- function updateScenarioRuleEnabled(scenarioId, category, ruleFp, enabled) {
2497
- const runtime = loadRuntimeConfig();
2498
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2499
- if (cat) {
2500
- const targetFp = _ruleFingerprint(ruleFp);
2501
- for (const r of cat.rules || []) {
2502
- if (_ruleFingerprint(r) === targetFp) {
2503
- r.enabled = enabled;
2504
- break;
2505
- }
2506
- }
3206
+ function layerEnabledOrDefault(key) {
3207
+ try {
3208
+ return isLayerEnabled(key);
3209
+ } catch {
3210
+ return true;
2507
3211
  }
2508
- saveRuntimeConfig(runtime);
2509
- return runtime;
2510
3212
  }
2511
- function addScenarioRule(scenarioId, category, rule) {
2512
- const runtime = loadRuntimeConfig();
2513
- if (!runtime.scenario_rules) runtime.scenario_rules = {};
2514
- if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
2515
- const cats = runtime.scenario_rules[scenarioId];
2516
- if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
2517
- (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
2518
- saveRuntimeConfig(runtime);
2519
- return runtime;
3213
+ function isBrainEnabled() {
3214
+ return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
2520
3215
  }
2521
- function deleteScenarioRule(scenarioId, category, ruleFp) {
2522
- const runtime = loadRuntimeConfig();
2523
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2524
- if (cat) {
2525
- const targetFp = _ruleFingerprint(ruleFp);
2526
- cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
2527
- }
2528
- saveRuntimeConfig(runtime);
2529
- return runtime;
3216
+ function isLogicRulesEnabled() {
3217
+ if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
3218
+ return layerEnabledOrDefault("L2_logic_rules");
2530
3219
  }
2531
- function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2532
- const runtime = loadRuntimeConfig();
2533
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2534
- if (cat) cat.category_enabled = enabled;
2535
- saveRuntimeConfig(runtime);
2536
- return runtime;
3220
+ function isFormalObserveMode() {
3221
+ return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
2537
3222
  }
2538
- var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, runtimeConfigCache, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2539
- var init_runtime_config = __esm({
2540
- "src/infra/runtime-config.ts"() {
3223
+ var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
3224
+ var init_config = __esm({
3225
+ "src/config.ts"() {
2541
3226
  "use strict";
2542
3227
  init_esm_shims();
2543
3228
  init_paths();
2544
- init_heuristic();
2545
- init_logic_rules();
2546
- init_skill_loader();
2547
- __dirname4 = dirname3(fileURLToPath5(import.meta.url));
2548
- ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2549
- RUNTIME_CONFIG_DIR = runtimeDir();
2550
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2551
- LAYER_DISABLE_ENV = {
2552
- L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2553
- L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2554
- L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2555
- };
2556
- DEFAULT_LAYERS = {
2557
- L1_heuristic: true,
2558
- L2_logic_rules: true,
2559
- L3_llm_analysis: true,
2560
- agent_rules_enabled: true,
2561
- scenario_rules_enabled: true
2562
- };
2563
- DENY_MODE_LABELS = {
2564
- deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2565
- deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2566
- deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
3229
+ init_runtime_config();
3230
+ llmConfig = (() => {
3231
+ const configPath = resolveConfigPath();
3232
+ const fileConfig = fs2.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
3233
+ const tr = loadTimeoutRetryFromFile(configPath);
3234
+ timeoutConfig = tr.timeout;
3235
+ retryConfig = tr.retry;
3236
+ return {
3237
+ provider: process.env.AGENT_MOSS_LLM_PROVIDER || fileConfig.provider || "openai",
3238
+ baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || fileConfig.baseUrl || "https://api.openai.com/v1",
3239
+ apiKey: process.env.AGENT_MOSS_LLM_API_KEY || process.env.OPENAI_API_KEY || fileConfig.apiKey || "",
3240
+ model: process.env.AGENT_MOSS_LLM_MODEL || fileConfig.model || "gpt-4o",
3241
+ temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || String(fileConfig.temperature ?? "0.1")),
3242
+ maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || String(fileConfig.maxTokens ?? "4096"), 10),
3243
+ // 优先用 env,否则用 timeout 块的 prompt1Timeout(对齐 py)
3244
+ timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
3245
+ };
3246
+ })();
3247
+ __configModuleDir = (() => {
3248
+ try {
3249
+ return path2.dirname(fileURLToPath5(import.meta.url));
3250
+ } catch {
3251
+ return typeof __dirname !== "undefined" ? __dirname : process.cwd();
3252
+ }
3253
+ })();
3254
+ USER_RULES_PATHS = [
3255
+ path2.join(__configModuleDir, "rules", "user_rules.json"),
3256
+ // 源在 ts/src/rules(一般不存在)
3257
+ path2.join(__configModuleDir, "..", "rules", "user_rules.json"),
3258
+ // dist: dist/engine/../rules = dist/rules ✅
3259
+ path2.join(__configModuleDir, "..", "..", "agent_moss", "rules", "user_rules.json")
3260
+ // dev src: ts/src/../../agent_moss
3261
+ ];
3262
+ customRules = loadCustomRules();
3263
+ PROVIDER_HEADERS = {
3264
+ "openrouter": {
3265
+ "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM",
3266
+ "X-OpenRouter-Title": "AgentMoss"
3267
+ },
3268
+ "xai": {
3269
+ "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM"
3270
+ }
2567
3271
  };
2568
- runtimeConfigCache = null;
2569
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2570
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2571
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2572
- _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2573
- SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2574
3272
  }
2575
3273
  });
2576
3274
 
2577
3275
  // src/agents/index.ts
2578
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
3276
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
2579
3277
  import { resolve as resolve4, dirname as dirname4 } from "path";
2580
3278
  import { fileURLToPath as fileURLToPath6 } from "url";
2581
3279
  function resolveAgentsDir() {
2582
- if (existsSync5(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
2583
- if (existsSync5(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3280
+ if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3281
+ if (existsSync6(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
2584
3282
  return AGENTS_DIR_PROD;
2585
3283
  }
2586
3284
  function getAgentExtensions(agentId) {
@@ -2588,16 +3286,15 @@ function getAgentExtensions(agentId) {
2588
3286
  if (cache.has(agentId)) return cache.get(agentId) ?? null;
2589
3287
  let ext = null;
2590
3288
  try {
2591
- const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledAgentExtensions: getEnabledAgentExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
2592
- if (hasRuntimeConfig2()) {
2593
- ext = getEnabledAgentExtensions2(loadRuntimeConfig2(), agentId);
3289
+ if (hasRuntimeConfig()) {
3290
+ ext = getEnabledAgentExtensions(loadRuntimeConfig(), agentId);
2594
3291
  }
2595
3292
  } catch {
2596
3293
  }
2597
3294
  if (ext === null) {
2598
3295
  const agentsDir = resolveAgentsDir();
2599
3296
  const rulesFile = resolve4(agentsDir, agentId, "rules.json");
2600
- if (!existsSync5(rulesFile)) {
3297
+ if (!existsSync6(rulesFile)) {
2601
3298
  cache.set(agentId, null);
2602
3299
  return null;
2603
3300
  }
@@ -2616,7 +3313,7 @@ function getAgentExtensions(agentId) {
2616
3313
  }
2617
3314
  }
2618
3315
  cache.set(agentId, ext);
2619
- console.log(
3316
+ console.error(
2620
3317
  `[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills, ${ext.builtin_safe_action_types?.length ?? 0} builtin safe action types`
2621
3318
  );
2622
3319
  return ext;
@@ -2641,6 +3338,7 @@ var init_agents = __esm({
2641
3338
  "src/agents/index.ts"() {
2642
3339
  "use strict";
2643
3340
  init_esm_shims();
3341
+ init_runtime_config();
2644
3342
  __filename4 = fileURLToPath6(import.meta.url);
2645
3343
  __dirname5 = dirname4(__filename4);
2646
3344
  AGENTS_DIR_PROD = resolve4(__dirname5, "agents");
@@ -2650,12 +3348,12 @@ var init_agents = __esm({
2650
3348
  });
2651
3349
 
2652
3350
  // src/scenarios/index.ts
2653
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
3351
+ import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
2654
3352
  import { resolve as resolve5, dirname as dirname5 } from "path";
2655
3353
  import { fileURLToPath as fileURLToPath7 } from "url";
2656
3354
  function resolveScenariosDir() {
2657
- if (existsSync6(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
2658
- if (existsSync6(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3355
+ if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3356
+ if (existsSync7(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
2659
3357
  return SCENARIOS_DIR_PROD;
2660
3358
  }
2661
3359
  function getScenarioExtensions(scenarioId) {
@@ -2663,16 +3361,15 @@ function getScenarioExtensions(scenarioId) {
2663
3361
  if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
2664
3362
  let ext = null;
2665
3363
  try {
2666
- const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledScenarioExtensions: getEnabledScenarioExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
2667
- if (hasRuntimeConfig2()) {
2668
- ext = getEnabledScenarioExtensions2(loadRuntimeConfig2(), scenarioId);
3364
+ if (hasRuntimeConfig()) {
3365
+ ext = getEnabledScenarioExtensions(loadRuntimeConfig(), scenarioId);
2669
3366
  }
2670
3367
  } catch {
2671
3368
  }
2672
3369
  if (ext === null) {
2673
3370
  const scenariosDir = resolveScenariosDir();
2674
3371
  const rulesFile = resolve5(scenariosDir, scenarioId, "rules.json");
2675
- if (!existsSync6(rulesFile)) {
3372
+ if (!existsSync7(rulesFile)) {
2676
3373
  cache2.set(scenarioId, null);
2677
3374
  return null;
2678
3375
  }
@@ -2702,7 +3399,7 @@ function getScenarioExtensions(scenarioId) {
2702
3399
  }
2703
3400
  }
2704
3401
  cache2.set(scenarioId, ext);
2705
- console.log(
3402
+ console.error(
2706
3403
  `[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`
2707
3404
  );
2708
3405
  return ext;
@@ -2722,6 +3419,7 @@ var init_scenarios = __esm({
2722
3419
  "use strict";
2723
3420
  init_esm_shims();
2724
3421
  init_agents();
3422
+ init_runtime_config();
2725
3423
  __filename5 = fileURLToPath7(import.meta.url);
2726
3424
  __dirname6 = dirname5(__filename5);
2727
3425
  SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
@@ -2731,6 +3429,62 @@ var init_scenarios = __esm({
2731
3429
  });
2732
3430
 
2733
3431
  // src/engine/heuristic.ts
3432
+ function _effectiveDangerousPatterns(osType) {
3433
+ return runtimeConfigBridge.current?.effectiveDangerousPatterns(osType) ?? getDangerousPatternsForOs(osType);
3434
+ }
3435
+ function _loadDangerousPatterns(osType) {
3436
+ const accepted = acceptedOsValues(osType);
3437
+ const cmds = loadL1().dangerous_commands || [];
3438
+ return cmds.filter((c) => accepted.includes(String(c.os || "linux").toLowerCase())).map((c) => ({
3439
+ pattern: new RegExp(c.pattern, "i"),
3440
+ severity: c.risk_level,
3441
+ riskType: c.risk_type,
3442
+ description: c.reason,
3443
+ // 保留条目的原始平台归属(非查询用的 osType)—— seed 生成据此分组去重
3444
+ os: String(c.os || "linux").toLowerCase()
3445
+ }));
3446
+ }
3447
+ function getDangerousPatternsForOs(osType) {
3448
+ const t = String(osType || "").toLowerCase();
3449
+ if (t === "windows") return DANGEROUS_PATTERNS_WINDOWS;
3450
+ if (t === "macos") return DANGEROUS_PATTERNS_MACOS;
3451
+ if (t === "harmony" || t === "hmos" || t === "openharmony" || t === "hongmeng") return DANGEROUS_PATTERNS_HARMONY;
3452
+ return DANGEROUS_PATTERNS2;
3453
+ }
3454
+ function normalizeShellObfuscation(text) {
3455
+ let t = text.replace(/(?<=\w)(?:''|"")(?=\w)/g, "");
3456
+ t = t.replace(/\$\{IFS\}|\$IFS/gi, " ");
3457
+ return t;
3458
+ }
3459
+ function scanDangerousCommandPatterns(actionType, actionDetail, osType) {
3460
+ const scanDetail = normalizePathSegments(stripLiteralTextSegments(actionDetail));
3461
+ const scanCommands = String(osType || "").toLowerCase() === "windows" ? [
3462
+ `${actionType}: ${scanDetail}`,
3463
+ scanDetail,
3464
+ `${actionType}: ${scanDetail.replace(/\//g, "\\")}`,
3465
+ scanDetail.replace(/\//g, "\\")
3466
+ ] : [`${actionType}: ${scanDetail}`, scanDetail];
3467
+ const deob = normalizeShellObfuscation(scanDetail);
3468
+ if (deob !== scanDetail) scanCommands.push(`${actionType}: ${deob}`, deob);
3469
+ let top = null;
3470
+ for (const rule of _effectiveDangerousPatterns(osType)) {
3471
+ if (scanCommands.some((c) => rule.pattern.test(c))) {
3472
+ if (!top || severityRank(rule.severity) > severityRank(top.sev)) {
3473
+ top = { sev: rule.severity, type: rule.riskType, desc: rule.description };
3474
+ }
3475
+ }
3476
+ }
3477
+ if (!top) return null;
3478
+ return {
3479
+ allowed: false,
3480
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${top.desc}`,
3481
+ riskLevel: top.sev,
3482
+ riskType: top.type,
3483
+ confidence: 95,
3484
+ source: "heuristic",
3485
+ violatedLayers: ["L1.1"]
3486
+ };
3487
+ }
2734
3488
  function runHeuristicCheck(input) {
2735
3489
  const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
2736
3490
  const matched = [];
@@ -2757,11 +3511,23 @@ function runHeuristicCheck(input) {
2757
3511
  }
2758
3512
  }
2759
3513
  const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
2760
- const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
3514
+ const scanCommands = String(input.osType || "").toLowerCase() === "windows" ? [
3515
+ `${input.aNext.actionType}: ${scanDetail}`,
3516
+ scanDetail,
3517
+ `${input.aNext.actionType}: ${scanDetail.replace(/\//g, "\\")}`,
3518
+ scanDetail.replace(/\//g, "\\")
3519
+ ] : [`${input.aNext.actionType}: ${scanDetail}`, scanDetail];
3520
+ const deobfuscated = normalizeShellObfuscation(scanDetail);
3521
+ if (deobfuscated !== scanDetail) {
3522
+ scanCommands.push(
3523
+ `${input.aNext.actionType}: ${deobfuscated}`,
3524
+ deobfuscated
3525
+ );
3526
+ }
2761
3527
  let topRiskType = "";
2762
3528
  const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
2763
- for (const rule of DANGEROUS_PATTERNS) {
2764
- if (rule.pattern.test(scanCommand)) {
3529
+ for (const rule of _effectiveDangerousPatterns(input.osType)) {
3530
+ if (scanCommands.some((c) => rule.pattern.test(c))) {
2765
3531
  if (exemptKeygen && rule.riskType === "file_access") continue;
2766
3532
  matched.push(rule.description);
2767
3533
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
@@ -2779,7 +3545,7 @@ function runHeuristicCheck(input) {
2779
3545
  for (const rule of agentExt.extra_command_patterns) {
2780
3546
  try {
2781
3547
  const re = new RegExp(rule.pattern, "i");
2782
- if (re.test(scanCommand)) {
3548
+ if (scanCommands.some((c) => re.test(c))) {
2783
3549
  matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
2784
3550
  if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
2785
3551
  highestSeverity = rule.risk_level;
@@ -2791,7 +3557,7 @@ function runHeuristicCheck(input) {
2791
3557
  }
2792
3558
  }
2793
3559
  const injectionText = isSearchActionType(input.aNext.actionType) ? `${input.aNext.actionType} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase() : `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
2794
- const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
3560
+ const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords, input.osType);
2795
3561
  if (injectionHits.length > 0) {
2796
3562
  const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
2797
3563
  matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
@@ -2804,7 +3570,7 @@ function runHeuristicCheck(input) {
2804
3570
  const detailL = input.aNext.actionDetail.toLowerCase();
2805
3571
  const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
2806
3572
  const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
2807
- const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
3573
+ const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt, input.osType);
2808
3574
  const homeResult = checkHomeDeletion(actionTypeL, detailL);
2809
3575
  const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
2810
3576
  if (mergeResults.length > 0) {
@@ -2840,9 +3606,10 @@ function runHeuristicCheck(input) {
2840
3606
  if (lateralResult) return lateralResult;
2841
3607
  return null;
2842
3608
  }
2843
- function checkInjectionKeywords(text, extraKeywords) {
3609
+ function checkInjectionKeywords(text, extraKeywords, osType) {
2844
3610
  const hits = [];
2845
- for (const entry of INJECTION_KEYWORDS) {
3611
+ const baseKeywords = runtimeConfigBridge.current?.effectiveInjectionKeywords(osType) ?? INJECTION_KEYWORDS;
3612
+ for (const entry of baseKeywords) {
2846
3613
  if (text.includes(entry.keyword.toLowerCase())) {
2847
3614
  hits.push(entry);
2848
3615
  }
@@ -2902,7 +3669,7 @@ function buildPathContext(actionDetail) {
2902
3669
  if (!PATH_CONTEXT_TRIGGER_RE.test(actionDetail)) return "";
2903
3670
  const stripped = stripLiteralTextSegments(actionDetail);
2904
3671
  const lines = [];
2905
- for (const seg of splitCommandSegments(stripped)) {
3672
+ for (const seg of splitCommandSegments2(stripped)) {
2906
3673
  for (const [raw, lex] of resolvePathTokens(seg)) {
2907
3674
  lines.push(`- \u547D\u4EE4\u6BB5 \`${seg.trim().slice(0, 60)}\`\uFF1A\`${raw}\` \u8BCD\u6CD5\u89E3\u6790\u4E3A \`${lex}\``);
2908
3675
  }
@@ -2917,7 +3684,7 @@ function stripLiteralTextSegments(actionDetail) {
2917
3684
  return actionDetail;
2918
3685
  }
2919
3686
  const resultParts = [];
2920
- for (const seg of splitCommandSegments(actionDetail)) {
3687
+ for (const seg of splitCommandSegments2(actionDetail)) {
2921
3688
  const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
2922
3689
  if (fw === "echo" || fw === "printf") {
2923
3690
  const segUnquotedRedirect = seg.replace(
@@ -2937,7 +3704,7 @@ function stripHeredocContent(actionDetail) {
2937
3704
  "<<heredoc>>\n"
2938
3705
  );
2939
3706
  }
2940
- function normalizePathSegments(text) {
3707
+ function normalizePathSegments(text, foldQuotes = false) {
2941
3708
  const protTokens = [];
2942
3709
  let t = text.replace(/\b(https?|ftp|file):\/\//gi, (m) => {
2943
3710
  const scheme = m.slice(0, m.indexOf(":"));
@@ -2945,10 +3712,12 @@ function normalizePathSegments(text) {
2945
3712
  return `${scheme}__PROT${protTokens.length - 1}__`;
2946
3713
  });
2947
3714
  const quoteTokens = [];
2948
- t = t.replace(/'[^']*'|"[^"]*"/g, (m) => {
2949
- quoteTokens.push(m);
2950
- return `__Q${quoteTokens.length - 1}__`;
2951
- });
3715
+ if (!foldQuotes) {
3716
+ t = t.replace(/'[^']*'|"[^"]*"/g, (m) => {
3717
+ quoteTokens.push(m);
3718
+ return `__Q${quoteTokens.length - 1}__`;
3719
+ });
3720
+ }
2952
3721
  t = t.replace(/\/{2,}/g, "/");
2953
3722
  t = t.replace(/\/\.\//g, "/");
2954
3723
  for (let i = 0; i < quoteTokens.length; i++) {
@@ -2959,7 +3728,12 @@ function normalizePathSegments(text) {
2959
3728
  }
2960
3729
  return t;
2961
3730
  }
2962
- function splitCommandSegments(actionDetail) {
3731
+ function pathScanVariants(actionDetailLower) {
3732
+ const base = stripLiteralTextSegments(actionDetailLower);
3733
+ const deep = normalizePathSegments(base, true);
3734
+ return deep === base ? [base] : [base, deep];
3735
+ }
3736
+ function splitCommandSegments2(actionDetail) {
2963
3737
  const segments = [];
2964
3738
  let cur = "";
2965
3739
  let quote = null;
@@ -3008,11 +3782,15 @@ function isSearchActionType(actionType) {
3008
3782
  const at = actionType.toLowerCase();
3009
3783
  return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || ["websearch", "web_search", "search", "query"].includes(at);
3010
3784
  }
3011
- function checkSensitivePath(command, cwd, agentExt) {
3785
+ function checkSensitivePath(command, cwd, agentExt, osType) {
3012
3786
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
3013
- const isWriteOpFlag = isWriteOperation("", command);
3787
+ const { root: rootPaths, home: homePaths, credential: credentialPaths } = getSensitivePathsForOs(osType);
3788
+ const isWriteOpFlag = isWriteOperation("", command, void 0, osType);
3014
3789
  const keygenGenerate = isSshKeygenGenerate(command);
3015
- const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
3790
+ const pathScanTexts = pathScanVariants(command);
3791
+ if (String(osType || "").toLowerCase() === "windows") {
3792
+ pathScanTexts.push(...pathScanTexts.map((t) => t.replace(/\//g, "\\")));
3793
+ }
3016
3794
  const pathMatches = (text, path5) => {
3017
3795
  const parts = path5.split(/(\(\?!.*?\))/);
3018
3796
  const escaped = parts.map((part) => {
@@ -3022,8 +3800,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3022
3800
  const re = path5.startsWith(".") || path5.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
3023
3801
  return re.test(text);
3024
3802
  };
3025
- for (const sp of ROOT_SENSITIVE_PATHS) {
3026
- if (pathMatches(pathScanText, sp.path.toLowerCase())) {
3803
+ const pathMatchesAny = (path5) => pathScanTexts.some((t) => pathMatches(t, path5));
3804
+ for (const sp of rootPaths) {
3805
+ if (pathMatchesAny(sp.path.toLowerCase())) {
3027
3806
  if (keygenGenerate) continue;
3028
3807
  if (sp.path === "/dev/random") {
3029
3808
  if (isWriteOpFlag) {
@@ -3039,9 +3818,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3039
3818
  violatedLayers: ["L1.1"]
3040
3819
  };
3041
3820
  }
3042
- if (!isWriteOpFlag && !sp.credential) {
3043
- continue;
3044
- }
3821
+ const denyMode = sp.denyMode || (sp.credential ? "deny_both" : sp.readOnly ? "deny_read" : "deny_write");
3822
+ if (denyMode === "deny_write" && !isWriteOpFlag) continue;
3823
+ if (denyMode === "deny_read" && isWriteOpFlag) continue;
3045
3824
  return {
3046
3825
  allowed: false,
3047
3826
  reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
@@ -3054,17 +3833,19 @@ function checkSensitivePath(command, cwd, agentExt) {
3054
3833
  };
3055
3834
  }
3056
3835
  }
3057
- for (const sp of HOME_SENSITIVE_PATHS) {
3836
+ for (const sp of homePaths) {
3058
3837
  const expanded = `${expandedCwd}/${sp.path}`;
3059
3838
  let pPath = sp.path;
3060
3839
  if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
3061
3840
  let dirPrefixMatch = null;
3062
3841
  if (sp.dirDelOnly) {
3063
3842
  const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
3064
- dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
3843
+ dirPrefixMatch = pathScanTexts.some(
3844
+ (t) => dirRe(sp.path).test(t) || dirRe(expanded.toLowerCase()).test(t)
3845
+ );
3065
3846
  }
3066
- const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
3067
- const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
3847
+ const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatchesAny(pPath);
3848
+ const expandedReMatch = sp.dirDelOnly ? false : pathMatchesAny(expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
3068
3849
  if (pathReMatch || expandedReMatch) {
3069
3850
  if (keygenGenerate) continue;
3070
3851
  if (sp.dirDelOnly) {
@@ -3094,9 +3875,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3094
3875
  }
3095
3876
  continue;
3096
3877
  }
3097
- if (!isWriteOpFlag && !sp.credential) {
3098
- continue;
3099
- }
3878
+ const denyMode = sp.denyMode || (sp.credential ? "deny_both" : sp.readOnly ? "deny_read" : "deny_write");
3879
+ if (denyMode === "deny_write" && !isWriteOpFlag) continue;
3880
+ if (denyMode === "deny_read" && isWriteOpFlag) continue;
3100
3881
  return {
3101
3882
  allowed: false,
3102
3883
  reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
@@ -3133,7 +3914,7 @@ function checkSensitivePath(command, cwd, agentExt) {
3133
3914
  }
3134
3915
  }
3135
3916
  }
3136
- for (const cp of CREDENTIAL_PATHS) {
3917
+ for (const cp of credentialPaths) {
3137
3918
  const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3138
3919
  const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
3139
3920
  if (re.test(command)) {
@@ -3253,7 +4034,7 @@ function isDeleteActionForDir(command, dirPath) {
3253
4034
  }
3254
4035
  for (const seg of low.split(/&&|\|\||;|\n/)) {
3255
4036
  const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
3256
- if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
4037
+ if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && seg.includes(dpath)) {
3257
4038
  return true;
3258
4039
  }
3259
4040
  }
@@ -3436,7 +4217,7 @@ function checkLateralMovement(actionDetail) {
3436
4217
  }
3437
4218
  return null;
3438
4219
  }
3439
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
4220
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS2, DANGEROUS_PATTERNS_WINDOWS, DANGEROUS_PATTERNS_MACOS, DANGEROUS_PATTERNS_HARMONY, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3440
4221
  var init_heuristic = __esm({
3441
4222
  "src/engine/heuristic.ts"() {
3442
4223
  "use strict";
@@ -3445,6 +4226,7 @@ var init_heuristic = __esm({
3445
4226
  init_agents();
3446
4227
  init_scenarios();
3447
4228
  init_runtime_config();
4229
+ init_runtime_config_bridge();
3448
4230
  init_rules_loader();
3449
4231
  init_logic_rules();
3450
4232
  INJECTION_KEYWORDS = (() => {
@@ -3455,15 +4237,10 @@ var init_heuristic = __esm({
3455
4237
  lang: k.lang || "en"
3456
4238
  }));
3457
4239
  })();
3458
- DANGEROUS_PATTERNS = (() => {
3459
- const cmds = loadL1().dangerous_commands || [];
3460
- return cmds.map((c) => ({
3461
- pattern: new RegExp(c.pattern, "i"),
3462
- severity: c.risk_level,
3463
- riskType: c.risk_type,
3464
- description: c.reason
3465
- }));
3466
- })();
4240
+ DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
4241
+ DANGEROUS_PATTERNS_WINDOWS = _loadDangerousPatterns("windows");
4242
+ DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4243
+ DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
3467
4244
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
3468
4245
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
3469
4246
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
@@ -3483,14 +4260,14 @@ __export(token_stats_exports, {
3483
4260
  recordTokenUsage: () => recordTokenUsage,
3484
4261
  resetTokenStats: () => resetTokenStats
3485
4262
  });
3486
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
4263
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
3487
4264
  function getStatsPath() {
3488
4265
  const envPath = process.env[ENV_STATS_PATH];
3489
4266
  if (envPath) return envPath;
3490
4267
  return getTokenStatsPath();
3491
4268
  }
3492
4269
  function loadAllRecords(statsPath) {
3493
- if (!existsSync10(statsPath)) return [];
4270
+ if (!existsSync11(statsPath)) return [];
3494
4271
  try {
3495
4272
  const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
3496
4273
  if (Array.isArray(data)) return data;
@@ -3503,7 +4280,7 @@ function loadAllRecords(statsPath) {
3503
4280
  function saveRecords(statsPath, records) {
3504
4281
  try {
3505
4282
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
3506
- if (dir && !existsSync10(dir)) mkdirSync2(dir, { recursive: true });
4283
+ if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
3507
4284
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
3508
4285
  } catch (e) {
3509
4286
  }
@@ -3630,7 +4407,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
3630
4407
  }
3631
4408
  function resetTokenStats() {
3632
4409
  const statsPath = getStatsPath();
3633
- if (existsSync10(statsPath)) {
4410
+ if (existsSync11(statsPath)) {
3634
4411
  try {
3635
4412
  unlinkSync2(statsPath);
3636
4413
  } catch {
@@ -3652,6 +4429,8 @@ var init_token_stats = __esm({
3652
4429
  init_esm_shims();
3653
4430
  init_heuristic();
3654
4431
  init_logic_rules();
4432
+ init_platform_traits();
4433
+ init_shell_split();
3655
4434
  init_inline_analyzer();
3656
4435
 
3657
4436
  // src/engine/llm-analyzer.ts
@@ -3662,7 +4441,7 @@ init_skill_loader();
3662
4441
  // src/engine/template-loader.ts
3663
4442
  init_esm_shims();
3664
4443
  init_asset_dir();
3665
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
4444
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
3666
4445
  import { resolve as resolve6, dirname as dirname6 } from "path";
3667
4446
  import { fileURLToPath as fileURLToPath8 } from "url";
3668
4447
  var __filename6 = fileURLToPath8(import.meta.url);
@@ -3680,7 +4459,7 @@ function loadTemplates() {
3680
4459
  }
3681
4460
  for (const file of TEMPLATE_FILES) {
3682
4461
  const filePath = resolve6(dir, file);
3683
- if (existsSync7(filePath)) {
4462
+ if (existsSync8(filePath)) {
3684
4463
  const key = file.replace(".txt", "").replace("_template", "");
3685
4464
  cachedTemplates[key] = readFileSync7(filePath, "utf-8");
3686
4465
  }
@@ -3825,7 +4604,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3825
4604
  }
3826
4605
  function readScriptContent(scriptPath, maxLines = 500) {
3827
4606
  try {
3828
- const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve7(scriptPath);
4607
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
3829
4608
  const raw = readFileSync8(expandedPath, "utf-8");
3830
4609
  const lines = raw.split("\n").slice(0, maxLines);
3831
4610
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -3879,11 +4658,12 @@ ${result.scriptContent}
3879
4658
  // src/engine/script-content-analyzer.ts
3880
4659
  init_esm_shims();
3881
4660
  init_heuristic();
4661
+ init_config();
3882
4662
  init_logic_rules();
3883
4663
  init_inline_analyzer();
3884
- import { statSync as statSync3, existsSync as existsSync9 } from "fs";
4664
+ import { statSync as statSync3, existsSync as existsSync10 } from "fs";
3885
4665
  import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
3886
- import { homedir as homedir4 } from "os";
4666
+ import { homedir as homedir3 } from "os";
3887
4667
 
3888
4668
  // src/engine/script-engine/tracker.ts
3889
4669
  init_esm_shims();
@@ -4710,7 +5490,7 @@ function tokenize(content) {
4710
5490
  }
4711
5491
  return stmts;
4712
5492
  }
4713
- var EXPR_SPLIT_RE = /(\$\{?\d+\}?|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)/;
5493
+ var EXPR_SPLIT_RE = /(\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$\d+)/;
4714
5494
  function evalExpr2(text, env) {
4715
5495
  const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
4716
5496
  if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
@@ -4721,9 +5501,20 @@ function evalExpr2(text, env) {
4721
5501
  const result = [];
4722
5502
  for (const p of parts) {
4723
5503
  if (!p) continue;
4724
- const brace = p.match(/^\$\{([^}]+)\}$/);
5504
+ const brace = p.match(/^\$\{([^}]*)\}$/);
4725
5505
  if (brace) {
4726
5506
  const inner = brace[1];
5507
+ const dm = inner.match(/^([A-Za-z_][A-Za-z0-9_]*|\d+):?-(.*)$/s);
5508
+ if (dm) {
5509
+ const key = /^\d+$/.test(dm[1]) ? `$${dm[1]}` : dm[1];
5510
+ const bound = env.get(key);
5511
+ if (bound && !(bound.kind === "concrete" && !bound.value)) {
5512
+ result.push(bound);
5513
+ } else {
5514
+ result.push(evalExpr2(dm[2], env));
5515
+ }
5516
+ continue;
5517
+ }
4727
5518
  if (/^\d+$/.test(inner)) {
4728
5519
  const v = env.get(`$${inner}`);
4729
5520
  result.push(v || svalSym(`$${inner}`));
@@ -5176,7 +5967,15 @@ var ShellBackend = class {
5176
5967
  // src/engine/script-engine/tracker.ts
5177
5968
  var DEFAULT_MAX_DEPTH = 14;
5178
5969
  var DEFAULT_MAX_NODES = 64;
5179
- var DEFAULT_MAX_FILE_SIZE = 2 * 1024 * 1024;
5970
+ function maxFileSizeFromEnv() {
5971
+ const raw = (process.env.AGENT_MOSS_SCRIPT_MAX_FILE_SIZE || "").trim();
5972
+ if (raw) {
5973
+ const v = Number(raw);
5974
+ if (Number.isFinite(v) && v > 0) return v;
5975
+ }
5976
+ return 2 * 1024 * 1024;
5977
+ }
5978
+ var DEFAULT_MAX_FILE_SIZE = maxFileSizeFromEnv();
5180
5979
  var DEFAULT_MAX_TOTAL_SIZE = 8 * 1024 * 1024;
5181
5980
  var BACKENDS = {
5182
5981
  ".py": new PythonBackend()
@@ -5224,6 +6023,7 @@ function emptyNode(pathStr, depth) {
5224
6023
  depth,
5225
6024
  childPaths: [],
5226
6025
  error: "",
6026
+ unanalyzable: false,
5227
6027
  l1Hit: false,
5228
6028
  l1RiskLevel: "",
5229
6029
  l1RiskType: "",
@@ -5305,6 +6105,7 @@ var RecursiveTracker = class {
5305
6105
  node2.error = error;
5306
6106
  node2.fileSize = fileSize;
5307
6107
  node2.lineCount = lineCount;
6108
+ node2.unanalyzable = error.startsWith("\u811A\u672C\u6587\u4EF6\u8FC7\u5927");
5308
6109
  this.result.nodes.push(node2);
5309
6110
  this.visiting.delete(pathStr);
5310
6111
  return;
@@ -5536,12 +6337,12 @@ function extractInlineScriptPath(actionDetail) {
5536
6337
  const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
5537
6338
  while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
5538
6339
  for (const cand of candidates) {
5539
- const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
6340
+ const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : shlexSplit(cand);
5540
6341
  for (const tok of tokens) {
5541
6342
  if (looksLikeScriptPath(tok)) {
5542
6343
  try {
5543
6344
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
5544
- if (existsSync9(resolved)) {
6345
+ if (existsSync10(resolved)) {
5545
6346
  const stat = statSync3(resolved);
5546
6347
  if (stat.isFile()) return resolved;
5547
6348
  }
@@ -5555,7 +6356,7 @@ function extractInlineScriptPath(actionDetail) {
5555
6356
  const p = m[1];
5556
6357
  try {
5557
6358
  const resolved = resolvePath2(p);
5558
- if (existsSync9(resolved)) {
6359
+ if (existsSync10(resolved)) {
5559
6360
  const stat = statSync3(resolved);
5560
6361
  if (stat.isFile()) return resolved;
5561
6362
  }
@@ -5571,7 +6372,7 @@ function extractInlineScriptPath(actionDetail) {
5571
6372
  let resolved;
5572
6373
  try {
5573
6374
  resolved = resolvePath2(p);
5574
- if (existsSync9(resolved)) {
6375
+ if (existsSync10(resolved)) {
5575
6376
  const stat = statSync3(resolved);
5576
6377
  if (stat.isFile()) return resolved;
5577
6378
  }
@@ -5580,10 +6381,39 @@ function extractInlineScriptPath(actionDetail) {
5580
6381
  }
5581
6382
  return null;
5582
6383
  }
6384
+ function shlexSplit(s) {
6385
+ const out = [];
6386
+ let cur = "";
6387
+ let quote = null;
6388
+ let quoted = false;
6389
+ for (let i = 0; i < s.length; i++) {
6390
+ const ch = s[i];
6391
+ if (quote) {
6392
+ if (ch === quote) quote = null;
6393
+ else if (ch === "\\" && quote === '"' && i + 1 < s.length) cur += s[++i];
6394
+ else cur += ch;
6395
+ } else if (ch === '"' || ch === "'") {
6396
+ quote = ch;
6397
+ quoted = true;
6398
+ } else if (ch === "\\" && i + 1 < s.length) {
6399
+ cur += s[++i];
6400
+ } else if (/\s/.test(ch)) {
6401
+ if (cur || quoted) {
6402
+ out.push(cur);
6403
+ cur = "";
6404
+ quoted = false;
6405
+ }
6406
+ } else {
6407
+ cur += ch;
6408
+ }
6409
+ }
6410
+ if (cur || quoted) out.push(cur);
6411
+ return out;
6412
+ }
5583
6413
  function resolvePath2(p) {
5584
6414
  let expanded = p;
5585
6415
  if (expanded.startsWith("~/") || expanded === "~") {
5586
- expanded = join6(homedir4(), expanded.slice(1));
6416
+ expanded = join6(homedir3(), expanded.slice(1));
5587
6417
  }
5588
6418
  return resolve9(expanded);
5589
6419
  }
@@ -5594,7 +6424,7 @@ function scanScriptChainWithL1(chain, input) {
5594
6424
  ...input,
5595
6425
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
5596
6426
  };
5597
- const l1 = runHeuristicCheck(virtualInput);
6427
+ const l1 = scanDangerousCommandPatterns("bash", node.preprocessedContent, input.osType);
5598
6428
  if (l1 && !l1.allowed) {
5599
6429
  node.l1Hit = true;
5600
6430
  node.l1RiskLevel = l1.riskLevel;
@@ -5617,18 +6447,25 @@ function scanScriptChainWithL2(chain, input) {
5617
6447
  ...input,
5618
6448
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
5619
6449
  };
5620
- const l2 = runLogicCheck(virtualInput);
5621
- if (l2 && !l2.allowed) {
5622
- node.l2Hit = true;
5623
- node.l2ViolatedRule = `script_logic:${node.scriptPath}`;
5624
- node.l2RiskLevel = l2.riskLevel;
5625
- node.l2RiskType = l2.riskType;
5626
- node.l2Reason = `\u811A\u672C ${node.scriptPath} (\u6DF1\u5EA6 ${node.depth}) \u4E2D: ${l2.reason}`;
5627
- if (l2.riskLevel === "critical") {
5628
- chain.hasCritical = true;
5629
- chain.firstHitNode = node;
5630
- break;
5631
- }
6450
+ let l2 = null;
6451
+ if (isLogicRulesEnabled()) {
6452
+ l2 = checkSensitivePath(
6453
+ node.preprocessedContent.toLowerCase(),
6454
+ input.cwd || "/",
6455
+ null,
6456
+ input.osType
6457
+ ) || checkIntentConsistency(virtualInput);
6458
+ }
6459
+ if (!l2 || l2.allowed) continue;
6460
+ node.l2Hit = true;
6461
+ node.l2ViolatedRule = `script_logic:${node.scriptPath}`;
6462
+ node.l2RiskLevel = l2.riskLevel;
6463
+ node.l2RiskType = l2.riskType;
6464
+ node.l2Reason = `\u811A\u672C ${node.scriptPath} (\u6DF1\u5EA6 ${node.depth}) \u4E2D: ${l2.reason}`;
6465
+ if (l2.riskLevel === "critical") {
6466
+ chain.hasCritical = true;
6467
+ chain.firstHitNode = node;
6468
+ break;
5632
6469
  }
5633
6470
  }
5634
6471
  if (!chain.hasHigh) {
@@ -5667,13 +6504,16 @@ function analyzeScriptContent2(input) {
5667
6504
  resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
5668
6505
  }
5669
6506
  const l1Adapter = (content) => {
5670
- const virtualInput = {
5671
- ...input,
5672
- aNext: { actionType: "bash", actionDetail: content }
5673
- };
5674
- const j = runHeuristicCheck(virtualInput);
6507
+ const j = scanDangerousCommandPatterns("bash", content, input.osType);
5675
6508
  if (j && !j.allowed) {
5676
- return { hit: true, riskLevel: j.riskLevel, riskType: j.riskType, reason: j.reason, matchedPatterns: [] };
6509
+ return {
6510
+ hit: true,
6511
+ riskLevel: j.riskLevel,
6512
+ riskType: j.riskType,
6513
+ reason: j.reason,
6514
+ matchedPatterns: []
6515
+ // SecurityJudgment 不带模式列表(见 types.ts);留空即可
6516
+ };
5677
6517
  }
5678
6518
  return { hit: false, riskLevel: "", riskType: "", reason: "", matchedPatterns: [] };
5679
6519
  };
@@ -5984,10 +6824,10 @@ init_esm_shims();
5984
6824
 
5985
6825
  // src/brain/rule-store.ts
5986
6826
  init_esm_shims();
5987
- import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
6827
+ import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
5988
6828
  import { join as join7 } from "path";
5989
- import { homedir as homedir5 } from "os";
5990
- var DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
6829
+ import { homedir as homedir4 } from "os";
6830
+ var DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
5991
6831
  var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
5992
6832
  var STATUS_DRAFT = "draft";
5993
6833
  var STATUS_APPROVED = "approved";
@@ -6016,7 +6856,7 @@ var BrainRuleStore = class {
6016
6856
  this.data = this.load();
6017
6857
  }
6018
6858
  load() {
6019
- if (!existsSync11(this.path)) {
6859
+ if (!existsSync12(this.path)) {
6020
6860
  return { version: 1, rules: [], pattern_candidates: [] };
6021
6861
  }
6022
6862
  try {
@@ -7454,63 +8294,15 @@ var READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
7454
8294
  "filemgr-readfile",
7455
8295
  "filemgr-grepfiles"
7456
8296
  ]);
7457
- var FULLY_SAFE_BASH_PATTERNS = [
7458
- /^ls(?:\s|$)/i,
7459
- /^dir(?:\s|$)/i,
7460
- /^pwd(?:\s|$)/i,
7461
- /^which\s+/i,
7462
- /^whereis\s+/i,
7463
- /^realpath\s+/i,
7464
- /^basename\s+/i,
7465
- /^dirname\s+/i,
7466
- /^file\s+/i,
7467
- /^stat\s+/i,
7468
- /^du(?:\s|$)/i,
7469
- /^echo\s+/i,
7470
- /^printf\s+/i,
7471
- /^type\s+/i,
7472
- /^command\s+/i,
7473
- // 系统信息查询(只读,不涉及文件/网络/权限)
7474
- /^whoami(?:\s|$)/i,
7475
- /^id(?:\s|$)/i,
7476
- /^hostname(?:\s|$)/i,
7477
- /^uname(?:\s|$)/i,
7478
- /^date(?:\s|$)/i,
7479
- /^env(?:\s|$)/i,
7480
- /^printenv(?:\s|$)/i,
7481
- /^tty(?:\s|$)/i,
7482
- /^arch(?:\s|$)/i,
7483
- /^uptime(?:\s|$)/i,
7484
- /^groups(?:\s|$)/i,
7485
- /^logname(?:\s|$)/i
7486
- ];
7487
- var READONLY_SENSITIVE_BASH_PATTERNS = [
7488
- /^cat\s+/i,
7489
- /^head(?:\s|$)/i,
7490
- /^tail(?:\s|$)/i,
7491
- /^less(?:\s|$)/i,
7492
- /^more(?:\s|$)/i,
7493
- /^wc(?:\s|$)/i,
7494
- /^grep(?:\s|$)/i,
7495
- /^find\s+/i,
7496
- /^ag(?:\s|$)/i,
7497
- /^rg(?:\s|$)/i,
7498
- /^awk\s+/i,
7499
- /^sed\s+/i,
7500
- /^cut\s+/i,
7501
- /^sort(?:\s|$)/i,
7502
- /^uniq(?:\s|$)/i
7503
- ];
7504
- function extractFirstCommand(command) {
7505
- let main = command.split("|")[0].trim();
7506
- for (const sep of ["&&", "||"]) {
7507
- const idx = main.indexOf(sep);
7508
- if (idx !== -1) {
7509
- main = main.substring(0, idx).trim();
7510
- break;
7511
- }
7512
- }
7513
- return main;
8297
+ function whitelistTier(command, fullySafe, readonlySensitive) {
8298
+ const segments = splitCommandSegments(command);
8299
+ if (segments.length === 0) return "";
8300
+ if (segments.some((seg) => hasCommandSubstitution(seg))) return "";
8301
+ const hitFull = (seg) => matchesAny(seg, fullySafe);
8302
+ const hitAny = (seg) => hitFull(seg) || matchesAny(seg, readonlySensitive);
8303
+ if (segments.every(hitFull)) return "full";
8304
+ if (segments.every(hitAny)) return "readonly";
8305
+ return "";
7514
8306
  }
7515
8307
  function extractRawCommand(detail) {
7516
8308
  const m = detail.match(/command=([^,]+)/);
@@ -7545,7 +8337,7 @@ async function runAnalysis(input) {
7545
8337
  if (heuristicResult && !heuristicResult.allowed) {
7546
8338
  const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && heuristicResult.riskType === "file_access";
7547
8339
  if (!skipInlineFileAccess) {
7548
- const shouldDenyL1 = heuristicResult.riskLevel === "high" || heuristicResult.riskLevel === "critical" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "file_access";
8340
+ const shouldDenyL1 = heuristicResult.riskLevel === "high" || heuristicResult.riskLevel === "critical" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "file_access" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "resource_exhaustion";
7549
8341
  if (shouldDenyL1) {
7550
8342
  return heuristicResult;
7551
8343
  }
@@ -7555,6 +8347,19 @@ async function runAnalysis(input) {
7555
8347
  if (input.aNext.actionType.toLowerCase() === "bash") {
7556
8348
  scriptChain = analyzeScriptContent2(input);
7557
8349
  if (scriptChain.nodes.length > 0) {
8350
+ for (const node of scriptChain.nodes) {
8351
+ if (node.unanalyzable) {
8352
+ return {
8353
+ allowed: false,
8354
+ reason: `\u811A\u672C\u5B58\u5728\u4F46\u5185\u5BB9\u65E0\u6CD5\u9759\u6001\u5206\u6790\uFF08${node.error}\uFF09\u2014\u2014 \u65E0\u6CD5\u786E\u8BA4\u5176\u5B89\u5168\u6027\uFF0C\u6309\u4E0D\u53EF\u4FE1\u5904\u7406\u3002\u6587\u4EF6: ${node.scriptPath}\u3002\u5982\u786E\u5C5E\u6B63\u5E38\u5927\u811A\u672C\uFF0C\u53EF\u8BBE AGENT_MOSS_SCRIPT_MAX_FILE_SIZE \u653E\u5BBD\u4E0A\u9650\u3002`,
8355
+ riskLevel: "high",
8356
+ riskType: "script_execution",
8357
+ confidence: 80,
8358
+ source: "heuristic_script_scan",
8359
+ violatedLayers: ["L1.2"]
8360
+ };
8361
+ }
8362
+ }
7558
8363
  for (const node of scriptChain.nodes) {
7559
8364
  if (node.l1Hit && (node.l1RiskLevel === "high" || node.l1RiskLevel === "critical")) {
7560
8365
  return {
@@ -7642,11 +8447,15 @@ async function runAnalysis(input) {
7642
8447
  let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
7643
8448
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
7644
8449
  const firstCmd = extractFirstCommand(lowerDetail);
7645
- if (matchesAny(firstCmd, FULLY_SAFE_BASH_PATTERNS)) {
7646
- const fullCmdHit = DANGEROUS_PATTERNS.some(
8450
+ const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);
8451
+ const tier = whitelistTier(lowerDetail, fullySafeCommands, readonlySensitiveCommands);
8452
+ if (tier === "full") {
8453
+ const fastPassScanText = normalizePathSegments(stripLiteralTextSegments(lowerDetail));
8454
+ const osPatterns = getDangerousPatternsForOs(input.osType);
8455
+ const fullCmdHit = osPatterns.some(
7647
8456
  (p) => p.severity === "critical" || p.severity === "high"
7648
- ) && DANGEROUS_PATTERNS.some(
7649
- (p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(lowerDetail)
8457
+ ) && osPatterns.some(
8458
+ (p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(fastPassScanText)
7650
8459
  );
7651
8460
  const hasRedirectWrite = isWriteOperation(firstCmd, lowerDetail);
7652
8461
  if (!fullCmdHit && !hasRedirectWrite) {
@@ -7661,7 +8470,7 @@ async function runAnalysis(input) {
7661
8470
  };
7662
8471
  }
7663
8472
  }
7664
- if (matchesAny(firstCmd, READONLY_SENSITIVE_BASH_PATTERNS)) {
8473
+ if (tier !== "") {
7665
8474
  skipLLM = true;
7666
8475
  }
7667
8476
  }
@@ -7670,8 +8479,8 @@ async function runAnalysis(input) {
7670
8479
  const effectiveResult = logicResult && !logicResult.allowed ? logicResult : l1FileAccessHit ? heuristicResult : null;
7671
8480
  if (effectiveResult) {
7672
8481
  const isCredentialPath = !!effectiveResult.isCredential;
7673
- const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && effectiveResult.riskType === "file_access";
7674
- if (skipInlineFileAccess) {
8482
+ const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && l1FileAccessHit;
8483
+ if (isInlineScriptCmd || isLiteralTextCmd) {
7675
8484
  const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
7676
8485
  if (inlineJudgment.actionable) {
7677
8486
  if (!inlineJudgment.allowed) {
@@ -7870,7 +8679,11 @@ async function runAnalysis(input) {
7870
8679
  export {
7871
8680
  TRUSTED_SKILLS_BASE,
7872
8681
  _resetBrainForTest,
7873
- runAnalysis
8682
+ extractFirstCommand,
8683
+ matchesAny,
8684
+ runAnalysis,
8685
+ splitCommandSegments,
8686
+ whitelistTier
7874
8687
  };
7875
8688
  //! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
7876
8689
  //! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python