@kenhkl/agent-moss 0.15.5 → 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.
package/dist/cli.js CHANGED
@@ -1,45 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
- }) : x)(function(x) {
9
- if (typeof require !== "undefined") return require.apply(this, arguments);
10
- throw Error('Dynamic require of "' + x + '" is not supported');
11
- });
12
4
  var __esm = (fn, res) => function __init() {
13
5
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
14
6
  };
15
- var __commonJS = (cb, mod) => function __require2() {
16
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
- };
18
7
  var __export = (target, all) => {
19
8
  for (var name in all)
20
9
  __defProp(target, name, { get: all[name], enumerable: true });
21
10
  };
22
- var __copyProps = (to, from, except, desc) => {
23
- if (from && typeof from === "object" || typeof from === "function") {
24
- for (let key of __getOwnPropNames(from))
25
- if (!__hasOwnProp.call(to, key) && key !== except)
26
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
27
- }
28
- return to;
29
- };
30
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
31
11
 
32
12
  // node_modules/tsup/assets/esm_shims.js
33
13
  import path from "path";
34
14
  import { fileURLToPath } from "url";
35
- var getFilename, getDirname, __dirname, __filename;
15
+ var getFilename, getDirname, __dirname;
36
16
  var init_esm_shims = __esm({
37
17
  "node_modules/tsup/assets/esm_shims.js"() {
38
18
  "use strict";
39
19
  getFilename = () => fileURLToPath(import.meta.url);
40
20
  getDirname = () => path.dirname(getFilename());
41
21
  __dirname = /* @__PURE__ */ getDirname();
42
- __filename = /* @__PURE__ */ getFilename();
43
22
  }
44
23
  });
45
24
 
@@ -127,278 +106,31 @@ var init_paths = __esm({
127
106
  }
128
107
  });
129
108
 
130
- // src/config.ts
131
- var config_exports = {};
132
- __export(config_exports, {
133
- detectProvider: () => detectProvider,
134
- getCustomRules: () => getCustomRules,
135
- getLLMConfig: () => getLLMConfig,
136
- getLLMFailMode: () => getLLMFailMode,
137
- getProviderHeaders: () => getProviderHeaders,
138
- getRetryConfig: () => getRetryConfig,
139
- getTimeoutConfig: () => getTimeoutConfig,
140
- isBrainEnabled: () => isBrainEnabled,
141
- isFormalObserveMode: () => isFormalObserveMode,
142
- isHeuristicEnabled: () => isHeuristicEnabled,
143
- isLLMAvailable: () => isLLMAvailable,
144
- isLogicRulesEnabled: () => isLogicRulesEnabled,
145
- probeLLMConnection: () => probeLLMConnection,
146
- updateLLMConfig: () => updateLLMConfig
147
- });
148
- import fs from "fs";
149
- import path2 from "path";
150
- import { fileURLToPath as fileURLToPath2 } from "url";
151
- function loadConfigFile(filePath) {
152
- try {
153
- const raw = fs.readFileSync(filePath, "utf-8");
154
- return JSON.parse(raw);
155
- } catch {
156
- return null;
157
- }
158
- }
159
- function resolveConfigPath() {
160
- const envPath = process.env.AGENT_MOSS_CONFIG_PATH;
161
- if (envPath) return envPath;
162
- const userConfig = path2.join(getConfigDir(), "config.json");
163
- if (fs.existsSync(userConfig)) return userConfig;
164
- const defaultCandidates = [
165
- path2.join(__dirname, "config.json.example"),
166
- path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example")
167
- ];
168
- const defaultConfig = defaultCandidates.find((p) => fs.existsSync(p));
169
- if (defaultConfig) {
170
- const configDir = path2.dirname(userConfig);
171
- fs.mkdirSync(configDir, { recursive: true });
172
- const pkgConfig = loadConfigFile(defaultConfig);
173
- const safeConfig = pkgConfig ? { ...pkgConfig, llm: { ...pkgConfig.llm, api_key: "" } } : {};
174
- fs.writeFileSync(userConfig, JSON.stringify(safeConfig, null, 4), "utf-8");
175
- console.log(`[AgentMoss] \u5DF2\u521B\u5EFA\u7528\u6237\u914D\u7F6E\u6587\u4EF6: ${userConfig}`);
176
- console.log(`[AgentMoss] \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\u914D\u7F6E\u4F60\u7684 LLM API Key`);
177
- }
178
- return userConfig;
179
- }
180
- function loadLLMConfigFromFile(configPath) {
181
- const data = loadConfigFile(configPath);
182
- if (!data?.llm) return {};
183
- const llm = data.llm;
184
- return {
185
- provider: llm.provider || "",
186
- baseUrl: llm.base_url || "",
187
- apiKey: llm.api_key || "",
188
- model: llm.model || "",
189
- temperature: llm.temperature ?? 0.1,
190
- maxTokens: llm.max_tokens ?? 4096,
191
- timeout: llm.timeout ?? 40
192
- };
193
- }
194
- function loadTimeoutRetryFromFile(configPath) {
195
- const data = loadConfigFile(configPath);
196
- const t = data?.timeout ?? {};
197
- const r = data?.retry ?? {};
198
- return {
199
- timeout: {
200
- totalTimeout: Number(t.total_timeout ?? 90),
201
- prompt1Timeout: Number(t.prompt1_timeout ?? 40),
202
- prompt2Timeout: Number(t.prompt2_timeout ?? 20),
203
- stepInterval: Number(t.step_interval ?? 0)
204
- },
205
- retry: {
206
- maxRetries: Number(r.max_retries ?? 2),
207
- retryInterval: Number(r.retry_interval ?? 2)
208
- }
209
- };
210
- }
211
- function getTimeoutConfig() {
212
- return timeoutConfig;
213
- }
214
- function getRetryConfig() {
215
- return retryConfig;
216
- }
217
- function wildcardToRegExp(pattern) {
218
- return pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
219
- }
220
- function loadCustomRules() {
221
- const rules = [];
222
- try {
223
- const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
224
- if (raw) {
225
- const parsed = JSON.parse(raw);
226
- if (Array.isArray(parsed)) {
227
- for (const r of parsed) {
228
- if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
229
- }
230
- }
231
- }
232
- } catch {
233
- }
234
- for (const rulesPath of USER_RULES_PATHS) {
235
- try {
236
- if (!fs.existsSync(rulesPath)) continue;
237
- const data = JSON.parse(fs.readFileSync(rulesPath, "utf-8"));
238
- const actions = Array.isArray(data?.sensitive_actions) ? data.sensitive_actions : [];
239
- for (const r of actions) {
240
- const p = r?.pattern;
241
- if (!p) continue;
242
- rules.push({ pattern: wildcardToRegExp(p), action: "Deny", severity: "high" });
243
- }
244
- break;
245
- } catch {
246
- }
247
- }
248
- return rules;
249
- }
250
- function getLLMConfig() {
251
- return llmConfig;
252
- }
253
- function updateLLMConfig(partial) {
254
- llmConfig = { ...llmConfig, ...partial };
255
- }
256
- function getCustomRules() {
257
- return customRules;
258
- }
259
- function isLLMAvailable() {
260
- if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
261
- if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
262
- return llmConfig.apiKey.length > 0;
263
- }
264
- async function probeLLMConnection(timeoutMs = 8e3) {
265
- if (!isLLMAvailable()) return false;
266
- const OpenAI3 = (await import("openai")).default;
267
- const config = getLLMConfig();
268
- const provider = detectProvider(config.baseUrl, config.provider);
269
- const extraHeaders = getProviderHeaders(provider);
270
- const client = new OpenAI3({
271
- baseURL: config.baseUrl,
272
- apiKey: config.apiKey,
273
- timeout: timeoutMs,
274
- maxRetries: 0,
275
- defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
276
- });
277
- try {
278
- const resp = await client.chat.completions.create({
279
- model: config.model,
280
- messages: [{ role: "user", content: "ping" }],
281
- max_tokens: 1
282
- });
283
- return !!(resp && resp.choices && resp.choices[0]);
284
- } catch {
285
- return false;
286
- }
287
- }
288
- function getLLMFailMode() {
289
- const val = (process.env.AGENT_MOSS_LLM_FAIL_MODE || "fail_open").toLowerCase();
290
- return val === "fail_closed" ? "fail_closed" : "fail_open";
291
- }
292
- function detectProvider(baseUrl, providerHint = "") {
293
- if (providerHint) {
294
- const hint = providerHint.toLowerCase();
295
- if (["claude", "anthropic"].includes(hint)) return "anthropic";
296
- if (["google", "gemini"].includes(hint)) return "gemini";
297
- if (["zai-coding-plan", "zhipu-coding-plan", "zhipuai-coding-plan"].includes(hint)) return "zai-coding-plan";
298
- if (["zai", "z-ai", "z.ai", "zai-cn", "zai-china", "zhipu", "glm-cn", "bigmodel"].includes(hint)) return "zhipu";
299
- if (["xai", "xai-grok"].includes(hint)) return "xai";
300
- if (["minimax", "minimax-openai"].includes(hint)) return "minimax";
301
- if (["minimax-anthropic"].includes(hint)) return "minimax-anthropic";
302
- if (["other", "custom", "local"].includes(hint)) return "openai-compatible";
303
- return hint;
304
- }
305
- const url = baseUrl.toLowerCase();
306
- if (url.includes("openrouter.ai")) return "openrouter";
307
- if (url.includes("api.openai.com")) return "openai";
308
- if (url.includes("anthropic.com") || url.includes("api.anthropic.com")) return "anthropic";
309
- if (url.includes("generativelanguage.googleapis.com") || url.includes("gemini")) return "gemini";
310
- if (url.includes("api.deepseek.com")) return "deepseek";
311
- if (url.includes("api.groq.com")) return "groq";
312
- if (url.includes("api.mistral.ai")) return "mistral";
313
- if (url.includes("api.together.xyz")) return "together";
314
- if (url.includes("api.x.ai")) return "xai";
315
- if (url.includes("api.minimaxi.com")) return "minimax";
316
- if (url.includes("api-ai.gitcode.com")) return "gitcode";
317
- if (url.includes("api.z.ai")) return "zai-coding-plan";
318
- if (url.includes("open.bigmodel.cn") || url.includes("bigmodel")) return "zhipu";
319
- if (url.includes(":11434")) return "ollama";
320
- return "openai-compatible";
321
- }
322
- function getProviderHeaders(provider) {
323
- return PROVIDER_HEADERS[provider] || {};
324
- }
325
- function isHeuristicEnabled() {
326
- if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
327
- return layerEnabledOrDefault("L1_heuristic");
328
- }
329
- function layerEnabledOrDefault(key) {
330
- try {
331
- const { isLayerEnabled: isLayerEnabled2 } = __require("../infra/runtime-config");
332
- return isLayerEnabled2(key);
333
- } catch {
334
- return true;
335
- }
336
- }
337
- function isBrainEnabled() {
338
- return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
109
+ // src/engine/shell-split.ts
110
+ function hasCommandSubstitution(text) {
111
+ return CMD_SUBSTITUTION.test(text);
339
112
  }
340
- function isLogicRulesEnabled() {
341
- if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
342
- return layerEnabledOrDefault("L2_logic_rules");
113
+ function splitCommandSegments(command) {
114
+ return command.split(CMD_SEPARATORS).map((s) => s.trim()).filter((s) => s.length > 0);
343
115
  }
344
- function isFormalObserveMode() {
345
- return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
116
+ function extractFirstCommand(command) {
117
+ const segments = splitCommandSegments(command);
118
+ return segments.length ? segments[0] : "";
346
119
  }
347
- var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
348
- var init_config = __esm({
349
- "src/config.ts"() {
120
+ var CMD_SEPARATORS, CMD_SUBSTITUTION;
121
+ var init_shell_split = __esm({
122
+ "src/engine/shell-split.ts"() {
350
123
  "use strict";
351
124
  init_esm_shims();
352
- init_paths();
353
- llmConfig = (() => {
354
- const configPath = resolveConfigPath();
355
- const fileConfig = fs.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
356
- const tr = loadTimeoutRetryFromFile(configPath);
357
- timeoutConfig = tr.timeout;
358
- retryConfig = tr.retry;
359
- return {
360
- provider: process.env.AGENT_MOSS_LLM_PROVIDER || fileConfig.provider || "openai",
361
- baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || fileConfig.baseUrl || "https://api.openai.com/v1",
362
- apiKey: process.env.AGENT_MOSS_LLM_API_KEY || process.env.OPENAI_API_KEY || fileConfig.apiKey || "",
363
- model: process.env.AGENT_MOSS_LLM_MODEL || fileConfig.model || "gpt-4o",
364
- temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || String(fileConfig.temperature ?? "0.1")),
365
- maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || String(fileConfig.maxTokens ?? "4096"), 10),
366
- // 优先用 env,否则用 timeout 块的 prompt1Timeout(对齐 py)
367
- timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
368
- };
369
- })();
370
- __configModuleDir = (() => {
371
- try {
372
- return path2.dirname(fileURLToPath2(import.meta.url));
373
- } catch {
374
- return typeof __dirname !== "undefined" ? __dirname : process.cwd();
375
- }
376
- })();
377
- USER_RULES_PATHS = [
378
- path2.join(__configModuleDir, "rules", "user_rules.json"),
379
- // 源在 ts/src/rules(一般不存在)
380
- path2.join(__configModuleDir, "..", "rules", "user_rules.json"),
381
- // dist: dist/engine/../rules = dist/rules ✅
382
- path2.join(__configModuleDir, "..", "..", "agent_moss", "rules", "user_rules.json")
383
- // dev src: ts/src/../../agent_moss
384
- ];
385
- customRules = loadCustomRules();
386
- PROVIDER_HEADERS = {
387
- "openrouter": {
388
- "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM",
389
- "X-OpenRouter-Title": "AgentMoss"
390
- },
391
- "xai": {
392
- "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM"
393
- }
394
- };
125
+ CMD_SEPARATORS = /\|\||&&|[|;&\n]/;
126
+ CMD_SUBSTITUTION = /\$\(|`|</;
395
127
  }
396
128
  });
397
129
 
398
130
  // src/engine/inline-analyzer.ts
399
131
  function isLiteralTextCommand(command) {
400
- ECHO_CMD_RE.lastIndex = 0;
401
- if (!ECHO_CMD_RE.test(command)) return false;
132
+ const segments = splitCommandSegments(command);
133
+ if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
402
134
  if (command.includes(">")) {
403
135
  let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
404
136
  d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
@@ -408,6 +140,9 @@ function isLiteralTextCommand(command) {
408
140
  return false;
409
141
  }
410
142
  }
143
+ if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
144
+ return false;
145
+ }
411
146
  return true;
412
147
  }
413
148
  function extractInlineCode(command) {
@@ -485,14 +220,15 @@ function analyzeInlineScript(command) {
485
220
  riskType: "file_access"
486
221
  };
487
222
  }
488
- 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;
223
+ 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;
489
224
  var init_inline_analyzer = __esm({
490
225
  "src/engine/inline-analyzer.ts"() {
491
226
  "use strict";
492
227
  init_esm_shims();
228
+ init_shell_split();
493
229
  INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
494
230
  ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
495
- ECHO_CMD_RE = /(?:^|\s|;|&&|\|\|)(?:echo|printf)\s/gi;
231
+ ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
496
232
  READ_WRITE_KEYWORDS = [
497
233
  // 函数调用形态(关键词后跟 '(')
498
234
  "open(",
@@ -541,9 +277,34 @@ var init_inline_analyzer = __esm({
541
277
  "passthru",
542
278
  "shell_exec",
543
279
  "proc_open",
544
- // Perl-Ruby
280
+ // Perl/Ruby
545
281
  "<stdin>",
546
- "open my"
282
+ "open my",
283
+ // Node/JS(2026-09 补,与 Python _READ_WRITE_KEYWORDS 同步):历史缺口 ——
284
+ // 识别表里一条 Node API 都没有,于是 node -e "require('fs').readFileSync('/etc/shadow')"
285
+ // 判为纯文本字面量 → 放行(真漏报)。
286
+ "readfilesync(",
287
+ "writefilesync(",
288
+ "appendfilesync(",
289
+ "unlinksync(",
290
+ "rmsync(",
291
+ "mkdirsync(",
292
+ "execsync(",
293
+ "spawnsync(",
294
+ "execfile",
295
+ "createreadstream",
296
+ "createwritestream",
297
+ "fs.read",
298
+ "fs.write",
299
+ "fs.promises",
300
+ "require('fs')",
301
+ 'require("fs")',
302
+ "require('node:fs')",
303
+ 'require("node:fs")',
304
+ "require('child_process')",
305
+ 'require("child_process")',
306
+ "require('https')",
307
+ 'require("https")'
547
308
  ];
548
309
  WRITE_EXEC_CHAIN_PATTERNS = [
549
310
  // echo/cat/printf "..." > file && bash/sh/python file
@@ -598,15 +359,53 @@ var init_inline_analyzer = __esm({
598
359
  }
599
360
  });
600
361
 
601
- // src/infra/rules-loader.ts
602
- import { readFileSync as readFileSync2, existsSync } from "fs";
603
- import { resolve, dirname, join as join2 } from "path";
604
- import { fileURLToPath as fileURLToPath3 } from "url";
605
- import { homedir as homedir2 } from "os";
362
+ // src/infra/runtime-config-bridge.ts
363
+ function registerRuntimeConfigBridge(bridge) {
364
+ runtimeConfigBridge.current = bridge;
365
+ }
366
+ var runtimeConfigBridge;
367
+ var init_runtime_config_bridge = __esm({
368
+ "src/infra/runtime-config-bridge.ts"() {
369
+ "use strict";
370
+ init_esm_shims();
371
+ runtimeConfigBridge = { current: null };
372
+ }
373
+ });
374
+
375
+ // src/infra/rules-loader.ts
376
+ import { readFileSync as readFileSync2, existsSync } from "fs";
377
+ import { resolve, dirname } from "path";
378
+ import { fileURLToPath as fileURLToPath2 } from "url";
606
379
  function dataRootDir() {
607
- return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
380
+ return getDataRoot();
381
+ }
382
+ function overrideRulesDir() {
383
+ return resolve(dataRootDir(), "rules");
384
+ }
385
+ function warnOverride(name, path6, data) {
386
+ if (warnedOverride.has(path6)) return;
387
+ warnedOverride.add(path6);
388
+ console.warn(
389
+ `[AgentMoss] \u89C4\u5219\u6587\u4EF6\u88AB\u7528\u6237\u6570\u636E\u76EE\u5F55\u8986\u76D6: ${path6}\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`
390
+ );
391
+ const missing = (EXPECTED_KEYS[name] || []).filter((k) => !(k in data));
392
+ if (missing.length > 0) {
393
+ console.warn(
394
+ `\u26A0\uFE0F [AgentMoss] \u8986\u76D6\u6587\u4EF6 ${path6} \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`
395
+ );
396
+ }
397
+ }
398
+ function overriddenRuleFiles() {
399
+ const out = [];
400
+ for (const name of Object.keys(EXPECTED_KEYS)) {
401
+ const p = resolve(overrideRulesDir(), name);
402
+ if (existsSync(p)) out.push(p);
403
+ }
404
+ return out;
608
405
  }
609
406
  function findRulesFile(name) {
407
+ const override = resolve(overrideRulesDir(), name);
408
+ if (existsSync(override)) return override;
610
409
  for (const dir of RULES_DIR_CANDS) {
611
410
  const p = resolve(dir, name);
612
411
  if (existsSync(p)) return p;
@@ -616,11 +415,16 @@ function findRulesFile(name) {
616
415
  function loadJson(name) {
617
416
  const p = findRulesFile(name);
618
417
  if (!p) return {};
418
+ const isOverride = p === resolve(overrideRulesDir(), name);
419
+ let data;
619
420
  try {
620
- return JSON.parse(readFileSync2(p, "utf-8")) || {};
421
+ data = JSON.parse(readFileSync2(p, "utf-8")) || {};
621
422
  } catch {
423
+ if (isOverride) warnOverride(name, p, {});
622
424
  return {};
623
425
  }
426
+ if (isOverride) warnOverride(name, p, data);
427
+ return data;
624
428
  }
625
429
  function loadL1() {
626
430
  return loadJson("l1_rules.json");
@@ -635,8 +439,8 @@ function loadMC() {
635
439
  const mc = loadJson("mc_rules.json");
636
440
  const rules = mc.rules || [];
637
441
  try {
638
- const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
639
- if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
442
+ const rt = runtimeConfigBridge.current;
443
+ if (rules.length > 0 && rt?.hasRuntimeConfig()) {
640
444
  const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
641
445
  const rtRules = ltl.rules || [];
642
446
  const enabledById = {};
@@ -654,16 +458,26 @@ function loadMC() {
654
458
  function loadToolRegistry() {
655
459
  return loadJson("tool_registry.json");
656
460
  }
657
- var __filename2, __dirname2, RULES_DIR_CANDS;
461
+ var __filename2, __dirname2, EXPECTED_KEYS, warnedOverride, RULES_DIR_CANDS;
658
462
  var init_rules_loader = __esm({
659
463
  "src/infra/rules-loader.ts"() {
660
464
  "use strict";
661
465
  init_esm_shims();
662
- __filename2 = fileURLToPath3(import.meta.url);
466
+ init_paths();
467
+ init_runtime_config_bridge();
468
+ __filename2 = fileURLToPath2(import.meta.url);
663
469
  __dirname2 = dirname(__filename2);
470
+ EXPECTED_KEYS = {
471
+ "l1_rules.json": ["dangerous_commands", "injection_detection", "sensitive_path_access"],
472
+ "l2_rules.json": ["intent_consistency", "password_modify_consent", "user_deletion_consent"],
473
+ "l3_rules.json": [],
474
+ "mc_rules.json": ["rules"],
475
+ "tool_registry.json": ["tools"]
476
+ };
477
+ warnedOverride = /* @__PURE__ */ new Set();
664
478
  RULES_DIR_CANDS = (() => {
665
479
  const cands = [
666
- // 用户数据目录覆盖优先(与 Python rules_loader._load_json 的
480
+ // 用户数据目录覆盖优先(与 Python rules_loader.resolve_rules_file 的
667
481
  // <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
668
482
  // mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
669
483
  resolve(dataRootDir(), "rules"),
@@ -682,21 +496,455 @@ var init_rules_loader = __esm({
682
496
  }
683
497
  });
684
498
 
499
+ // src/engine/platform-traits.ts
500
+ function getPlatformTraits(osType) {
501
+ const accepted = acceptedOsValues(osType);
502
+ if (accepted.includes("windows")) return WINDOWS_TRAITS;
503
+ if (accepted.includes("macos")) return MACOS_TRAITS;
504
+ if (accepted.includes("harmony")) return HARMONY_TRAITS;
505
+ return LINUX_TRAITS;
506
+ }
507
+ var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
508
+ var init_platform_traits = __esm({
509
+ "src/engine/platform-traits.ts"() {
510
+ "use strict";
511
+ init_esm_shims();
512
+ init_logic_rules();
513
+ LINUX_TRAITS = {
514
+ writeKeywords: [
515
+ "write",
516
+ "\u5199\u5165",
517
+ "save",
518
+ "\u4FDD\u5B58",
519
+ "modify",
520
+ "\u4FEE\u6539",
521
+ "update",
522
+ "\u66F4\u65B0",
523
+ "delete",
524
+ "\u5220\u9664",
525
+ "remove",
526
+ "\u79FB\u9664",
527
+ "create",
528
+ "\u521B\u5EFA",
529
+ "append",
530
+ "\u8FFD\u52A0",
531
+ "truncate",
532
+ "\u622A\u65AD",
533
+ "overwrite",
534
+ "\u8986\u76D6",
535
+ "replace",
536
+ "\u66FF\u6362",
537
+ "edit"
538
+ ],
539
+ readKeywords: [
540
+ "read",
541
+ "\u8BFB\u53D6",
542
+ "cat",
543
+ "head",
544
+ "tail",
545
+ "less",
546
+ "more",
547
+ "view",
548
+ "\u67E5\u770B",
549
+ "open",
550
+ "\u6253\u5F00",
551
+ "load",
552
+ "\u52A0\u8F7D",
553
+ "grep",
554
+ "search",
555
+ "\u641C\u7D22",
556
+ "find",
557
+ "\u67E5\u627E"
558
+ ],
559
+ writeCommandRes: [
560
+ /\brm\b/,
561
+ /\bunlink\b/,
562
+ /\brmdir\b/,
563
+ /\bshred\b/,
564
+ /\bcp\b/,
565
+ /\bmv\b/,
566
+ /\binstall\b/,
567
+ /\btee\b/,
568
+ /\bdd\b/,
569
+ /\bmkfs\b/,
570
+ /\b(fdisk|parted|cfdisk|sfdisk)\b/,
571
+ /\bchmod\b/,
572
+ /\bchown\b/,
573
+ /\btruncate\b/
574
+ ],
575
+ readOnlyCommands: /* @__PURE__ */ new Set([
576
+ "cat",
577
+ "head",
578
+ "tail",
579
+ "less",
580
+ "more",
581
+ "grep",
582
+ "find",
583
+ "awk",
584
+ "sed",
585
+ "sort",
586
+ "uniq",
587
+ "wc",
588
+ "cut",
589
+ "strings",
590
+ "od",
591
+ "xxd",
592
+ "hexdump",
593
+ "tr",
594
+ "file",
595
+ "stat",
596
+ "du",
597
+ "df",
598
+ "ls",
599
+ "dir",
600
+ "tree",
601
+ "nl",
602
+ "tac",
603
+ "rev",
604
+ "echo",
605
+ "printf",
606
+ "lsblk",
607
+ "blockdev",
608
+ "smartctl",
609
+ "udevadm",
610
+ "dmidecode",
611
+ "lscpu",
612
+ "lspci",
613
+ "lsusb",
614
+ "lsmem",
615
+ "lsns",
616
+ "lsof",
617
+ "hwinfo",
618
+ "inxi",
619
+ "fdisk",
620
+ "parted",
621
+ "dmesg",
622
+ "journalctl",
623
+ "systemctl",
624
+ "hostnamectl",
625
+ "localectl",
626
+ "timedatectl",
627
+ "ps",
628
+ "top",
629
+ "free",
630
+ "vmstat",
631
+ "iostat",
632
+ "mpstat",
633
+ "pidof",
634
+ "pgrep",
635
+ "ip",
636
+ "ifconfig",
637
+ "route",
638
+ "ss",
639
+ "netstat",
640
+ "arp",
641
+ "ethtool",
642
+ "nmcli",
643
+ "uname",
644
+ "arch",
645
+ "nproc",
646
+ "getconf",
647
+ "getent",
648
+ "id",
649
+ "whoami",
650
+ "who",
651
+ "w",
652
+ "last",
653
+ "uptime",
654
+ "lsmod",
655
+ "modinfo",
656
+ "sysctl"
657
+ ]),
658
+ fullySafeCommands: [
659
+ /^ls(?:\s|$)/i,
660
+ /^dir(?:\s|$)/i,
661
+ /^pwd(?:\s|$)/i,
662
+ /^which\s+/i,
663
+ /^whereis\s+/i,
664
+ /^realpath\s+/i,
665
+ /^basename\s+/i,
666
+ /^dirname\s+/i,
667
+ /^file\s+/i,
668
+ /^stat\s+/i,
669
+ /^du(?:\s|$)/i,
670
+ /^echo\s+/i,
671
+ /^printf\s+/i,
672
+ /^type\s+/i,
673
+ /^command\s+/i,
674
+ // 系统信息查询(只读,不涉及文件/网络/权限)
675
+ /^whoami(?:\s|$)/i,
676
+ /^id(?:\s|$)/i,
677
+ /^hostname(?:\s|$)/i,
678
+ /^uname(?:\s|$)/i,
679
+ /^date(?:\s|$)/i,
680
+ /^env(?:\s|$)/i,
681
+ /^printenv(?:\s|$)/i,
682
+ /^tty(?:\s|$)/i,
683
+ /^arch(?:\s|$)/i,
684
+ /^uptime(?:\s|$)/i,
685
+ /^groups(?:\s|$)/i,
686
+ /^logname(?:\s|$)/i,
687
+ // 系统资源/硬件信息(只读,不读文件内容、无状态变更选项)
688
+ // 与上面 uptime/uname/id 同性质;缺了它们时开 L3 的部署每查一次磁盘/内存
689
+ // 都要走 LLM(实测 df -h 从 5ms 变成十几秒)。
690
+ /^df(?:\s|$)/i,
691
+ /^free(?:\s|$)/i,
692
+ /^vmstat(?:\s|$)/i,
693
+ /^nproc(?:\s|$)/i,
694
+ /^lscpu(?:\s|$)/i,
695
+ /^lsblk(?:\s|$)/i,
696
+ /^lspci(?:\s|$)/i,
697
+ /^lsusb(?:\s|$)/i,
698
+ /^lsmod(?:\s|$)/i
699
+ ],
700
+ readonlySensitiveCommands: [
701
+ /^cat\s+/i,
702
+ /^head(?:\s|$)/i,
703
+ /^tail(?:\s|$)/i,
704
+ /^less(?:\s|$)/i,
705
+ /^more(?:\s|$)/i,
706
+ /^wc(?:\s|$)/i,
707
+ /^grep(?:\s|$)/i,
708
+ /^find\s+/i,
709
+ /^ag(?:\s|$)/i,
710
+ /^rg(?:\s|$)/i,
711
+ /^awk\s+/i,
712
+ /^sed\s+/i,
713
+ /^cut\s+/i,
714
+ /^sort(?:\s|$)/i,
715
+ /^uniq(?:\s|$)/i,
716
+ // 进程/网络/打开文件信息:只读,但会暴露进程命令行、连接对端、打开的文件
717
+ // 路径,故只跳 L3、保留 L2(L1 敏感路径检测照常生效)。
718
+ /^ps(?:\s|$)/i,
719
+ /^ss(?:\s|$)/i,
720
+ /^netstat(?:\s|$)/i,
721
+ /^lsof(?:\s|$)/i,
722
+ // 只读子命令级白名单:匹配的是「第一段命令」而非首词(extractFirstCommand
723
+ // 按 | && || 切分),所以能精确到子命令。systemctl stop/disable 等不在此列。
724
+ /^systemctl\s+(?:status|is-active|is-enabled|is-failed|list-units|list-unit-files|list-dependencies|show|cat)\b/i,
725
+ /^service\s+\S+\s+status\b/i,
726
+ // journalctl 整族是只读的,但 --vacuum-*/--rotate/--flush/--sync 会删改日志,
727
+ // 用否定前瞻挡掉后再放行。注意前瞻里必须用 .* 扫描**整段**:只写 (?!\s+--...)
728
+ // 的话只看紧跟 journalctl 的第一个参数,
729
+ // `journalctl -u sshd --vacuum-size=100M` 就漏过去了。
730
+ /^journalctl\b(?!.*--(?:vacuum-size|vacuum-time|vacuum-files|rotate|flush|sync)\b)/i
731
+ ]
732
+ };
733
+ WINDOWS_TRAITS = {
734
+ writeKeywords: [
735
+ "write",
736
+ "\u5199\u5165",
737
+ "save",
738
+ "\u4FDD\u5B58",
739
+ "modify",
740
+ "\u4FEE\u6539",
741
+ "update",
742
+ "\u66F4\u65B0",
743
+ "delete",
744
+ "\u5220\u9664",
745
+ "remove",
746
+ "\u79FB\u9664",
747
+ "create",
748
+ "\u521B\u5EFA",
749
+ "append",
750
+ "\u8FFD\u52A0",
751
+ "overwrite",
752
+ "\u8986\u76D6",
753
+ "replace",
754
+ "\u66FF\u6362",
755
+ "set",
756
+ "\u8BBE\u7F6E"
757
+ // 注意:这里**不能放 `reg` / `注册表`**。关键词是子串包含匹配,放进去等于
758
+ // 「只要命令里出现 reg 字样就算写操作」——`reg query`(读)会被判成写,
759
+ // 进而误拦对 deny_write 路径的只读查询。注册表的写判定交给下面的
760
+ // writeCommandRes(只认写子命令)。两端语义必须一致。
761
+ ],
762
+ readKeywords: [
763
+ "read",
764
+ "\u8BFB\u53D6",
765
+ "type",
766
+ "findstr",
767
+ "dir",
768
+ "view",
769
+ "\u67E5\u770B",
770
+ "open",
771
+ "\u6253\u5F00",
772
+ "load",
773
+ "\u52A0\u8F7D",
774
+ "search",
775
+ "\u641C\u7D22",
776
+ "find",
777
+ "\u67E5\u627E",
778
+ "get",
779
+ "\u83B7\u53D6"
780
+ ],
781
+ // Windows 写/删命令:del/copy/move/xcopy/replace/rmdir/icacls/reg delete 等
782
+ writeCommandRes: [
783
+ /\b(del|erase)\b/,
784
+ /\brmdir\b/,
785
+ /\b(rd)\b/,
786
+ /\bcopy\b/,
787
+ /\b(xcopy|robocopy)\b/,
788
+ /\b(move|movefile)\b/,
789
+ /\breplace\b/,
790
+ /\bicacls\b/,
791
+ /\battrib\b/,
792
+ // 注册表:**只认写子命令**(对齐 Python WindowsProfile.write_command_res)。
793
+ // 此前是 /\breg\b/,把 reg query(读)也判成写 → 误拦对 deny_write 路径的只读查询。
794
+ // 写子命令 add/delete/copy/save/restore/import/load/unload;
795
+ // 读子命令 query/compare/export/flags/help 不在此列。
796
+ /\breg(?:\.exe)?\s+(?:add|delete|copy|save|restore|import|load|unload)\b/,
797
+ /\bformat\b/,
798
+ /\bdiskpart\b/,
799
+ /\bmkdir\b/,
800
+ /\bmd\b/,
801
+ /\bsetx\b/,
802
+ /\bwmic\b/
803
+ ],
804
+ // Windows 只读命令(重定向写判定用):type/findstr/more/tree/dir 等
805
+ readOnlyCommands: /* @__PURE__ */ new Set([
806
+ "type",
807
+ "findstr",
808
+ "more",
809
+ "tree",
810
+ "dir",
811
+ "where",
812
+ "ver",
813
+ "cls",
814
+ "echo",
815
+ "help",
816
+ "fc",
817
+ "comp",
818
+ "find",
819
+ "sort",
820
+ "powershell",
821
+ "cmd",
822
+ "netstat",
823
+ "ipconfig",
824
+ "tasklist",
825
+ "systeminfo",
826
+ "driverquery",
827
+ "reg",
828
+ "wmic",
829
+ "whoami",
830
+ "hostname",
831
+ "getmac",
832
+ "query",
833
+ "w32tm",
834
+ "diskpart"
835
+ ]),
836
+ // Windows 完全安全命令(对齐 WindowsProfile.fully_safe_commands)
837
+ fullySafeCommands: [
838
+ /^dir(?:\s|$)/,
839
+ /^where\s+/,
840
+ /^echo\s+/,
841
+ /^cd(?:\s|$)/,
842
+ /^tree(?:\s|$)/,
843
+ /^help(?:\s|$)/,
844
+ /^cls(?:\s|$)/,
845
+ /^ver(?:\s|$)/,
846
+ // 系统信息查询(只读,不读文件内容、无状态变更选项),与 Linux 侧 df/free 对齐
847
+ /^whoami(?:\s|$)/,
848
+ /^hostname(?:\s|$)/,
849
+ /^systeminfo(?:\s|$)/,
850
+ /^tasklist(?:\s|$)/,
851
+ /^getmac(?:\s|$)/,
852
+ /^driverquery(?:\s|$)/,
853
+ /^netstat(?:\s|$)/
854
+ ],
855
+ // Windows 安全但可能访问敏感路径的命令(对齐 WindowsProfile.readonly_sensitive_commands)
856
+ readonlySensitiveCommands: [
857
+ /^type\s+/,
858
+ /^findstr\s+/,
859
+ // 只读子命令级白名单(匹配「第一段命令」而非首词);
860
+ // sc create/delete/start/stop、reg add/delete/set 均不在此列。
861
+ /^sc\s+query\b/,
862
+ /^reg\s+query\b/
863
+ ]
864
+ };
865
+ MACOS_TRAITS = LINUX_TRAITS;
866
+ HARMONY_TRAITS = LINUX_TRAITS;
867
+ }
868
+ });
869
+
685
870
  // src/engine/logic-rules.ts
686
- import * as fs2 from "fs";
687
- function _loadSensitivePathScope(scope) {
871
+ import * as fs from "fs";
872
+ function acceptedOsValues(osType) {
873
+ const t = String(osType || "").toLowerCase();
874
+ if (t === "windows") return ["windows"];
875
+ if (t === "macos" || t === "darwin" || t === "osx" || t === "mac") return ["linux", "macos"];
876
+ if (t === "harmony" || t === "hmos" || t === "openharmony" || t === "hongmeng") return ["linux", "harmony"];
877
+ return ["linux"];
878
+ }
879
+ function _loadSensitivePathScope(scope, osType = "linux") {
880
+ const accepted = acceptedOsValues(osType);
688
881
  const sps = loadL1().sensitive_path_access || [];
689
- return sps.filter((sp) => sp.scope === scope).map((sp) => ({
882
+ return sps.filter((sp) => sp.scope === scope).filter((sp) => accepted.includes(String(sp.os || "linux").toLowerCase())).map((sp) => ({
690
883
  path: sp.path,
691
884
  riskLevel: sp.risk_level,
692
885
  desc: sp.desc,
693
886
  credential: sp.deny_mode === "deny_both" ? true : void 0,
694
- dirDelOnly: sp.dir_del_only === true ? true : void 0
887
+ readOnly: sp.read_only === true || sp.deny_mode === "deny_read" ? true : void 0,
888
+ denyMode: sp.deny_mode || void 0,
889
+ dirDelOnly: sp.dir_del_only === true ? true : void 0,
890
+ // 保留条目的原始平台归属(非查询用的 osType)—— seed 分组去重用
891
+ os: String(sp.os || "linux").toLowerCase()
695
892
  }));
696
893
  }
697
- function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
894
+ function _scopeOf(os4, path6) {
895
+ if (!_scopeIndex) {
896
+ _scopeIndex = /* @__PURE__ */ new Map();
897
+ for (const sp of loadL1().sensitive_path_access || []) {
898
+ const key = `${String(sp.os || "linux").toLowerCase()}|${sp.path}`;
899
+ if (!_scopeIndex.has(key)) _scopeIndex.set(key, sp.scope);
900
+ }
901
+ }
902
+ return _scopeIndex.get(`${String(os4 || "linux").toLowerCase()}|${path6}`) || "root";
903
+ }
904
+ function getSensitivePathsForOs(osType) {
905
+ const effective = runtimeConfigBridge.current?.effectiveSensitivePaths(osType);
906
+ if (effective) {
907
+ const root = [];
908
+ const home = [];
909
+ const credential = [];
910
+ for (const r of effective) {
911
+ const entry = {
912
+ path: r.path,
913
+ riskLevel: r.riskLevel,
914
+ desc: r.desc,
915
+ // ⚠️ 不要强制 credential:用户在 Console 把某条从 deny_both 改成 deny_write
916
+ // 之后,强制 credential 会让读操作仍被拦(改了不生效)。一律以 runtime 为准。
917
+ credential: r.credential === true ? true : void 0,
918
+ readOnly: r.readOnly === true ? true : void 0,
919
+ denyMode: r.denyMode || void 0,
920
+ dirDelOnly: r.dirDelOnly === true ? true : void 0
921
+ };
922
+ const scope = _scopeOf(r.os, r.path);
923
+ if (scope === "home") home.push(entry);
924
+ else if (scope === "credential") credential.push(entry);
925
+ else root.push(entry);
926
+ }
927
+ return { root, home, credential };
928
+ }
929
+ return getSensitivePathsForOsBuiltin(osType);
930
+ }
931
+ function getSensitivePathsForOsBuiltin(osType) {
932
+ const t = String(osType || "").toLowerCase();
933
+ if (t === "windows") {
934
+ return { root: ROOT_SENSITIVE_PATHS_WINDOWS, home: HOME_SENSITIVE_PATHS_WINDOWS, credential: CREDENTIAL_PATHS_WINDOWS };
935
+ }
936
+ if (t === "macos" || t === "darwin" || t === "osx" || t === "mac") {
937
+ return { root: ROOT_SENSITIVE_PATHS_MACOS, home: HOME_SENSITIVE_PATHS_MACOS, credential: CREDENTIAL_PATHS_MACOS };
938
+ }
939
+ if (t === "harmony" || t === "hmos" || t === "openharmony" || t === "hongmeng") {
940
+ return { root: ROOT_SENSITIVE_PATHS_HARMONY, home: HOME_SENSITIVE_PATHS_HARMONY, credential: CREDENTIAL_PATHS_HARMONY };
941
+ }
942
+ return { root: ROOT_SENSITIVE_PATHS, home: HOME_SENSITIVE_PATHS, credential: CREDENTIAL_PATHS };
943
+ }
944
+ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS, osType) {
698
945
  if (writeKeywords.some((kw) => actionType.includes(kw) || actionDetail.includes(kw))) return true;
699
- if (WRITE_COMMAND_RES.some((rx) => rx.test(actionDetail))) return true;
946
+ const { writeCommandRes } = getPlatformTraits(osType);
947
+ if (writeCommandRes.some((rx) => rx.test(actionDetail))) return true;
700
948
  if (/\bsed\s+-[a-zA-Z]*i/i.test(actionDetail)) return true;
701
949
  for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
702
950
  const trimmed = seg.trim();
@@ -730,6 +978,12 @@ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWOR
730
978
  }
731
979
  return false;
732
980
  }
981
+ function getIntentPatternsForOs(osType) {
982
+ const effective = runtimeConfigBridge.current?.effectiveIntentPatterns(osType);
983
+ if (effective) return effective;
984
+ const accepted = acceptedOsValues(osType);
985
+ return INTENT_DEVIATION_PATTERNS.filter((p) => accepted.includes(p.os));
986
+ }
733
987
  function matchDangerousAction(actionDetail, action) {
734
988
  if (/[^\x00-\x7f]/.test(action)) {
735
989
  return actionDetail.includes(action);
@@ -764,38 +1018,47 @@ function collectIntentTexts(promptSession, promptHistory) {
764
1018
  }
765
1019
  return deduped;
766
1020
  }
767
- function runLogicCheck(input) {
1021
+ function checkIntentConsistency(input) {
1022
+ const { aNext, promptSession, promptHistory } = input;
1023
+ const actionDetail = aNext.actionDetail.toLowerCase();
1024
+ const actionType = aNext.actionType.toLowerCase();
1025
+ const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1026
+ if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1027
+ if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1028
+ const intentTexts = collectIntentTexts(promptSession, promptHistory);
1029
+ for (const pattern of getIntentPatternsForOs(input.osType)) {
1030
+ const intentMatched = intentTexts.some(
1031
+ (text) => pattern.intentKeywords.some((kw) => text.includes(kw))
1032
+ );
1033
+ const dangerousMatched = pattern.dangerousActions.some((kw) => matchDangerousAction(actionDetail, kw));
1034
+ if (intentMatched && dangerousMatched) {
1035
+ return {
1036
+ allowed: false,
1037
+ reason: pattern.reason,
1038
+ riskLevel: "high",
1039
+ riskType: "intent_deviation",
1040
+ confidence: 85,
1041
+ source: "logic_rule",
1042
+ violatedLayers: ["L2.1"]
1043
+ };
1044
+ }
1045
+ }
1046
+ return null;
1047
+ }
1048
+ function runLogicCheck(input, opts) {
768
1049
  const { actionHistory, aNext, cwd } = input;
769
1050
  const actionDetail = aNext.actionDetail.toLowerCase();
770
1051
  const actionType = aNext.actionType.toLowerCase();
771
1052
  const resolvedCwd = cwd || "/";
772
1053
  const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
773
1054
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
774
- const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd);
775
- if (rbwResult) return rbwResult;
776
- if (!isExcludedType) {
777
- if (!INLINE_SCRIPT_RE.test(actionDetail)) {
778
- const intentTexts = collectIntentTexts(input.promptSession, input.promptHistory);
779
- for (const pattern of INTENT_DEVIATION_PATTERNS) {
780
- const intentMatched = intentTexts.some(
781
- (text) => pattern.intentKeywords.some((kw) => text.includes(kw))
782
- );
783
- const dangerousMatched = pattern.dangerousActions.some((kw) => matchDangerousAction(actionDetail, kw));
784
- if (intentMatched && dangerousMatched) {
785
- return {
786
- allowed: false,
787
- reason: pattern.reason,
788
- riskLevel: "high",
789
- riskType: "intent_deviation",
790
- confidence: 85,
791
- source: "logic_rule",
792
- violatedLayers: ["L2.1"]
793
- };
794
- }
795
- }
796
- }
1055
+ if (!opts?.skipReadBeforeWrite) {
1056
+ const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
1057
+ if (rbwResult) return rbwResult;
797
1058
  }
798
- const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd);
1059
+ const intentResult = checkIntentConsistency(input);
1060
+ if (intentResult) return intentResult;
1061
+ const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd, input.osType);
799
1062
  if (indirectResult) return indirectResult;
800
1063
  if (INLINE_SCRIPT_RE.test(actionDetail)) {
801
1064
  } else if (containsPrivilegeEscalation(actionDetail)) {
@@ -818,9 +1081,9 @@ function runLogicCheck(input) {
818
1081
  }
819
1082
  }
820
1083
  }
821
- const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory);
1084
+ const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory, input.osType);
822
1085
  if (pwdResult) return pwdResult;
823
- const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory);
1086
+ const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory, input.osType);
824
1087
  if (userDelResult) return userDelResult;
825
1088
  const comboResult = checkComboChain(actionHistory, aNext.actionDetail);
826
1089
  if (comboResult) return comboResult;
@@ -871,22 +1134,33 @@ function checkComboChain(actionHistory, actionDetailRaw) {
871
1134
  }
872
1135
  return null;
873
1136
  }
874
- function extractFilePaths(text) {
1137
+ function extractFilePaths(text, osType) {
875
1138
  const paths = [];
876
1139
  const unixPaths = text.match(/(?:^|\s)(\/[^\s;|&><'"]+)/g);
877
1140
  if (unixPaths) paths.push(...unixPaths.map((p) => p.trim()));
1141
+ const isWindows = String(osType || "").toLowerCase() === "windows";
1142
+ if (isWindows) {
1143
+ const winPaths = text.match(/(?:^|\s)([A-Za-z]:[\\\/][^\s;|&><'"]+)/g);
1144
+ if (winPaths) paths.push(...winPaths.map((p) => p.trim()));
1145
+ const uncPaths = text.match(/(?:^|\s)(\\{2}[^\\\s]+(?:\\[^\s;|&><'"]*))/g);
1146
+ if (uncPaths) paths.push(...uncPaths.map((p) => p.trim()));
1147
+ }
878
1148
  const textNoQuotes = text.replace(/'[^']*'|"[^"]*"/g, "");
879
1149
  const kvPaths = textNoQuotes.match(/(?:=)(\/[^\s;|&><'"]+)/g);
880
1150
  if (kvPaths) paths.push(...kvPaths.map((p) => p.replace(/^=/, "")));
881
1151
  const quotedPaths = text.match(/["']\/([^"']+)["']/g);
882
1152
  if (quotedPaths) paths.push(...quotedPaths.map((p) => p.replace(/["']/g, "")));
1153
+ if (isWindows) {
1154
+ const quotedWinPaths = text.match(/["']([A-Za-z]:[\\\/][^"']+)["']/g);
1155
+ if (quotedWinPaths) paths.push(...quotedWinPaths.map((p) => p.replace(/["']/g, "")));
1156
+ }
883
1157
  return [...new Set(paths)];
884
1158
  }
885
- function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd) {
1159
+ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd, osType) {
886
1160
  if (NON_WRITE_TOOL_PREFIXES.some((p) => actionType.startsWith(p))) return null;
887
1161
  if (NON_WRITE_TOOLS.has(actionType)) return null;
888
1162
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
889
- const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail) : WRITE_KEYWORDS.some((kw) => actionType.includes(kw));
1163
+ const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
890
1164
  if (!isWrite) return null;
891
1165
  const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
892
1166
  if (isBashLike) {
@@ -930,7 +1204,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
930
1204
  if (firstWord === "cd") {
931
1205
  continue;
932
1206
  }
933
- if (READ_ONLY_COMMANDS.has(firstWord)) {
1207
+ if (getPlatformTraits(osType).readOnlyCommands.has(firstWord)) {
934
1208
  const segUnquoted = trimmed.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
935
1209
  const segNoDevnull = segUnquoted.replace(DEVNULL_REDIRECT_RE, "");
936
1210
  const segNoQuotes = segNoDevnull.replace(/'[^']*'|"[^"]*"/g, "");
@@ -938,7 +1212,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
938
1212
  if (m) targetPaths.push(m[1] || m[2] || m[3]);
939
1213
  continue;
940
1214
  }
941
- const segPaths = extractFilePaths(trimmed);
1215
+ const segPaths = extractFilePaths(trimmed, osType);
942
1216
  const segPathsFiltered = firstWord === "wget" || firstWord === "curl" ? segPaths.filter((p) => p !== "/dev/null") : segPaths;
943
1217
  if (SOURCE_ARG_CMDS.has(firstWord)) {
944
1218
  if (segPathsFiltered.length > 1) targetPaths.push(...segPathsFiltered.slice(1));
@@ -960,20 +1234,30 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
960
1234
  for (const action of actionHistory) {
961
1235
  const histDetail = action.actionDetail.toLowerCase();
962
1236
  const histType = action.actionType.toLowerCase();
963
- const isRead = READ_KEYWORDS.some((kw) => histType.includes(kw) || histDetail.includes(kw));
1237
+ const isRead = getPlatformTraits(osType).readKeywords.some((kw) => histType.includes(kw) || histDetail.includes(kw));
964
1238
  if (isRead) {
965
- for (const p of extractFilePaths(histDetail)) {
1239
+ for (const p of extractFilePaths(histDetail, osType)) {
966
1240
  historyReadPaths.add(p);
967
1241
  }
968
1242
  }
969
1243
  }
1244
+ const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1245
+ let filteredPaths = uniqueTargetPaths;
1246
+ if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1247
+ filteredPaths = uniqueTargetPaths.filter(
1248
+ (p) => !TEMP_DIRS.some((td) => p.startsWith(td))
1249
+ );
1250
+ }
1251
+ if (filteredPaths.length === 0) {
1252
+ return null;
1253
+ }
970
1254
  const unreadPaths = [];
971
- for (const path6 of uniqueTargetPaths) {
1255
+ for (const path6 of filteredPaths) {
972
1256
  const hasRead = [...historyReadPaths].some(
973
1257
  (rp) => path6 === rp || path6.startsWith(rp) || rp.startsWith(path6)
974
1258
  );
975
1259
  if (!hasRead) {
976
- let isCreate = !fs2.existsSync(path6);
1260
+ let isCreate = !fs.existsSync(path6);
977
1261
  if (!isCreate) {
978
1262
  isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
979
1263
  }
@@ -995,14 +1279,18 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
995
1279
  }
996
1280
  return null;
997
1281
  }
998
- function checkPasswordConsent(actionType, actionDetail, actionHistory) {
1282
+ function hasUserConsent(actionHistory) {
1283
+ return actionHistory.some((h) => {
1284
+ const t = String(h.actionType || "").toLowerCase();
1285
+ return t === "ask_user_question" && String(h.output || "").trim().length > 0;
1286
+ });
1287
+ }
1288
+ function checkPasswordConsent(actionType, actionDetail, actionHistory, osType) {
999
1289
  if (actionType !== "bash") return null;
1000
- const isPasswordModify = PASSWORD_MODIFY_PATTERNS.some((p) => p.test(actionDetail));
1290
+ const pwdPatterns = runtimeConfigBridge.current?.effectivePasswordPatterns(osType) ?? PASSWORD_MODIFY_PATTERNS;
1291
+ const isPasswordModify = pwdPatterns.some((p) => p.test(actionDetail));
1001
1292
  if (!isPasswordModify) return null;
1002
- const hasConsent = actionHistory.some(
1003
- (h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
1004
- );
1005
- if (hasConsent) return null;
1293
+ if (hasUserConsent(actionHistory)) return null;
1006
1294
  return {
1007
1295
  allowed: false,
1008
1296
  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",
@@ -1013,14 +1301,12 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
1013
1301
  violatedLayers: ["L2.1"]
1014
1302
  };
1015
1303
  }
1016
- function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
1304
+ function checkUserDeletionConsent(actionType, actionDetail, actionHistory, osType) {
1017
1305
  if (actionType !== "bash") return null;
1018
- const isUserDeletion = USER_DELETION_PATTERNS.some((p) => p.test(actionDetail));
1306
+ const udPatterns = runtimeConfigBridge.current?.effectiveUserDeletionPatterns(osType) ?? USER_DELETION_PATTERNS;
1307
+ const isUserDeletion = udPatterns.some((p) => p.test(actionDetail));
1019
1308
  if (!isUserDeletion) return null;
1020
- const hasConsent = actionHistory.some(
1021
- (h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
1022
- );
1023
- if (hasConsent) return null;
1309
+ if (hasUserConsent(actionHistory)) return null;
1024
1310
  return {
1025
1311
  allowed: false,
1026
1312
  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",
@@ -1043,7 +1329,7 @@ function containsPrivilegeEscalation(actionDetail) {
1043
1329
  }
1044
1330
  return false;
1045
1331
  }
1046
- function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1332
+ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1047
1333
  if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") {
1048
1334
  return null;
1049
1335
  }
@@ -1056,14 +1342,14 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1056
1342
  if (!hasTraversal) {
1057
1343
  return null;
1058
1344
  }
1059
- const READ_KEYWORDS2 = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1345
+ const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1060
1346
  const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1061
1347
  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"];
1062
1348
  const OPEN_KEYWORDS = ["open(", "fopen("];
1063
1349
  const code = extractInlineCode(actionDetail);
1064
1350
  if (!code) return null;
1065
1351
  const codeLower = code.toLowerCase();
1066
- 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));
1352
+ 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));
1067
1353
  if (!hasAnyOp) {
1068
1354
  return null;
1069
1355
  }
@@ -1071,15 +1357,16 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1071
1357
  const hasExec = EXEC_KEYWORDS.some((kw) => codeLower.includes(kw));
1072
1358
  const blockAllSensitive = hasWrite || hasExec;
1073
1359
  let actualFiles = [];
1074
- if (cwd && cwd !== "/") {
1360
+ if (cwd) {
1075
1361
  try {
1076
- actualFiles = fs2.readdirSync(cwd);
1362
+ actualFiles = fs.readdirSync(cwd);
1077
1363
  } catch {
1078
1364
  }
1079
1365
  }
1080
1366
  if (actualFiles.length > 0) {
1081
- for (const cp of CREDENTIAL_PATHS) {
1082
- const fname = cp.path.split("/").pop() || cp.path;
1367
+ const { root: rootPaths, home: homePaths, credential: credentialPaths } = getSensitivePathsForOs(osType);
1368
+ for (const cp of credentialPaths) {
1369
+ const fname = cp.path.split(/[/\\]/).pop() || cp.path;
1083
1370
  if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
1084
1371
  return {
1085
1372
  allowed: false,
@@ -1094,8 +1381,8 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1094
1381
  }
1095
1382
  }
1096
1383
  if (blockAllSensitive) {
1097
- for (const sp of [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS]) {
1098
- const fname = sp.path.split("/").pop() || sp.path;
1384
+ for (const sp of [...rootPaths, ...homePaths]) {
1385
+ const fname = sp.path.split(/[/\\]/).pop() || sp.path;
1099
1386
  if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
1100
1387
  return {
1101
1388
  allowed: false,
@@ -1121,16 +1408,28 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1121
1408
  violatedLayers: ["L2.1"]
1122
1409
  };
1123
1410
  }
1124
- 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;
1411
+ 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;
1125
1412
  var init_logic_rules = __esm({
1126
1413
  "src/engine/logic-rules.ts"() {
1127
1414
  "use strict";
1128
1415
  init_esm_shims();
1129
1416
  init_inline_analyzer();
1417
+ init_runtime_config_bridge();
1130
1418
  init_rules_loader();
1419
+ init_platform_traits();
1131
1420
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1132
1421
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1133
1422
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
1423
+ ROOT_SENSITIVE_PATHS_WINDOWS = _loadSensitivePathScope("root", "windows");
1424
+ HOME_SENSITIVE_PATHS_WINDOWS = _loadSensitivePathScope("home", "windows");
1425
+ CREDENTIAL_PATHS_WINDOWS = _loadSensitivePathScope("credential", "windows").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
1426
+ ROOT_SENSITIVE_PATHS_MACOS = _loadSensitivePathScope("root", "macos");
1427
+ HOME_SENSITIVE_PATHS_MACOS = _loadSensitivePathScope("home", "macos");
1428
+ CREDENTIAL_PATHS_MACOS = _loadSensitivePathScope("credential", "macos").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
1429
+ ROOT_SENSITIVE_PATHS_HARMONY = _loadSensitivePathScope("root", "harmony");
1430
+ HOME_SENSITIVE_PATHS_HARMONY = _loadSensitivePathScope("home", "harmony");
1431
+ CREDENTIAL_PATHS_HARMONY = _loadSensitivePathScope("credential", "harmony").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
1432
+ _scopeIndex = null;
1134
1433
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1135
1434
  "ask_user_question",
1136
1435
  "glob",
@@ -1152,111 +1451,7 @@ var init_logic_rules = __esm({
1152
1451
  ]);
1153
1452
  NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1154
1453
  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"];
1155
- WRITE_COMMAND_RES = [
1156
- /\brm\b/,
1157
- /\bunlink\b/,
1158
- /\brmdir\b/,
1159
- /\bshred\b/,
1160
- /\bcp\b/,
1161
- /\bmv\b/,
1162
- /\binstall\b/,
1163
- /\btee\b/,
1164
- /\bdd\b/,
1165
- /\bmkfs\b/,
1166
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
1167
- /\bchmod\b/,
1168
- /\bchown\b/,
1169
- /\btruncate\b/
1170
- ];
1171
- READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
1172
- "cat",
1173
- "head",
1174
- "tail",
1175
- "less",
1176
- "more",
1177
- "grep",
1178
- "find",
1179
- "awk",
1180
- "sed",
1181
- "sort",
1182
- "uniq",
1183
- "wc",
1184
- "cut",
1185
- "strings",
1186
- "od",
1187
- "xxd",
1188
- "hexdump",
1189
- "tr",
1190
- "file",
1191
- "stat",
1192
- "du",
1193
- "df",
1194
- "ls",
1195
- "dir",
1196
- "tree",
1197
- "nl",
1198
- "tac",
1199
- "rev",
1200
- // NEW-FP-I: echo/printf 是文本输出命令,参数是字面量非写目标。加入此集让
1201
- // read_before_write 的 echo 段走只读命令分支(只提取重定向目标,不提取参数路径),
1202
- // 避免 echo "cat /etc/shadow" > /tmp/x.sh 的引号内 /etc/shadow 被当写目标误报。
1203
- // echo "x" > /tmp/x.sh 的重定向目标 /tmp/x.sh 仍被提取(真实写不漏报)。
1204
- "echo",
1205
- "printf",
1206
- "lsblk",
1207
- "blockdev",
1208
- "smartctl",
1209
- "udevadm",
1210
- "dmidecode",
1211
- "lscpu",
1212
- "lspci",
1213
- "lsusb",
1214
- "lsmem",
1215
- "lsns",
1216
- "lsof",
1217
- "hwinfo",
1218
- "inxi",
1219
- "fdisk",
1220
- "parted",
1221
- "dmesg",
1222
- "journalctl",
1223
- "systemctl",
1224
- "hostnamectl",
1225
- "localectl",
1226
- "timedatectl",
1227
- "ps",
1228
- "top",
1229
- "free",
1230
- "vmstat",
1231
- "iostat",
1232
- "mpstat",
1233
- "pidof",
1234
- "pgrep",
1235
- "ip",
1236
- "ifconfig",
1237
- "route",
1238
- "ss",
1239
- "netstat",
1240
- "arp",
1241
- "ethtool",
1242
- "nmcli",
1243
- "uname",
1244
- "arch",
1245
- "nproc",
1246
- "getconf",
1247
- "getent",
1248
- "id",
1249
- "whoami",
1250
- "who",
1251
- "w",
1252
- "last",
1253
- "uptime",
1254
- "lsmod",
1255
- "modinfo",
1256
- "sysctl"
1257
- ]);
1258
1454
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
1259
- 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"];
1260
1455
  CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
1261
1456
  PASSWORD_MODIFY_PATTERNS = (() => {
1262
1457
  const pats = loadL2().password_modify_consent || [];
@@ -1271,7 +1466,8 @@ var init_logic_rules = __esm({
1271
1466
  return pats.map((p) => ({
1272
1467
  intentKeywords: p.intent_keywords,
1273
1468
  dangerousActions: p.dangerous_actions,
1274
- reason: p.reason
1469
+ reason: p.reason,
1470
+ os: String(p.os || "linux").toLowerCase()
1275
1471
  }));
1276
1472
  })();
1277
1473
  COMBO_STAGES = (() => {
@@ -1308,34 +1504,90 @@ var init_logic_rules = __esm({
1308
1504
  }
1309
1505
  });
1310
1506
 
1507
+ // src/engine/asset-dir.ts
1508
+ import { existsSync as existsSync3 } from "fs";
1509
+ import { resolve as resolve2 } from "path";
1510
+ function assetDirCandidates(moduleDir, name) {
1511
+ return [
1512
+ resolve2(moduleDir, name),
1513
+ resolve2(moduleDir, "../../..", "agent_moss", name)
1514
+ ];
1515
+ }
1516
+ function resolveAssetDir(moduleDir, name) {
1517
+ for (const dir of assetDirCandidates(moduleDir, name)) {
1518
+ if (existsSync3(dir)) return dir;
1519
+ }
1520
+ return null;
1521
+ }
1522
+ var init_asset_dir = __esm({
1523
+ "src/engine/asset-dir.ts"() {
1524
+ "use strict";
1525
+ init_esm_shims();
1526
+ }
1527
+ });
1528
+
1311
1529
  // src/engine/skill-loader.ts
1312
- import { readFileSync as readFileSync3, readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
1313
- import { resolve as resolve2, dirname as dirname2 } from "path";
1314
- import { fileURLToPath as fileURLToPath4 } from "url";
1530
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2, existsSync as existsSync4 } from "fs";
1531
+ import { resolve as resolve3, dirname as dirname2 } from "path";
1532
+ import { fileURLToPath as fileURLToPath3 } from "url";
1315
1533
  function loadAllSkills() {
1316
1534
  if (cachedSkills) return cachedSkills;
1317
1535
  cachedSkills = {};
1318
- const dir = existsSync3(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
1319
- if (!existsSync3(dir)) {
1320
- console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR, "and fallback:", SKILLS_DIR_FALLBACK);
1536
+ const dir = resolveAssetDir(__dirname3, "skills");
1537
+ if (!dir) {
1538
+ const [primary, fallback] = assetDirCandidates(__dirname3, "skills");
1539
+ console.warn("[AgentMoss] Skills directory not found:", primary, "and fallback:", fallback);
1321
1540
  return cachedSkills;
1322
1541
  }
1323
1542
  const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
1324
1543
  for (const f of files) {
1325
1544
  const name = f.replace(".md", "");
1326
- cachedSkills[name] = readFileSync3(resolve2(dir, f), "utf-8");
1545
+ cachedSkills[name] = readFileSync3(resolve3(dir, f), "utf-8");
1327
1546
  }
1328
1547
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1329
1548
  return cachedSkills;
1330
1549
  }
1550
+ function userSkillsDir() {
1551
+ return resolve3(getDataRoot(), "agent_moss_skills");
1552
+ }
1553
+ function runtimeCustomSkillKeywords() {
1554
+ try {
1555
+ const rt = runtimeConfigBridge.current?.loadRuntimeConfig?.();
1556
+ if (!rt) return {};
1557
+ const out = {};
1558
+ for (const cat of Object.values(rt.L3_skills || {})) {
1559
+ if (!cat || typeof cat !== "object" || cat.category_enabled === false) continue;
1560
+ for (const sk of cat.skills || []) {
1561
+ if (!sk || sk.builtin !== false || sk.enabled === false || !sk.id) continue;
1562
+ out[sk.id] = sk.keywords || [];
1563
+ }
1564
+ }
1565
+ return out;
1566
+ } catch {
1567
+ return {};
1568
+ }
1569
+ }
1570
+ function loadUserSkill(name) {
1571
+ if (userSkillCache[name] !== void 0) return userSkillCache[name];
1572
+ try {
1573
+ const p = resolve3(userSkillsDir(), `${name}.md`);
1574
+ userSkillCache[name] = existsSync4(p) ? readFileSync3(p, "utf-8") : "";
1575
+ } catch {
1576
+ userSkillCache[name] = "";
1577
+ }
1578
+ return userSkillCache[name];
1579
+ }
1331
1580
  function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
1332
1581
  const skills = loadAllSkills();
1333
1582
  const combined = `${actionType} ${actionDetail} ${reason}`.toLowerCase();
1334
1583
  const actionDetailLower = actionDetail.toLowerCase();
1335
1584
  const actionTypeLower = actionType.toLowerCase();
1336
1585
  const reasonLower = reason.toLowerCase();
1586
+ const disabledSkillIds = runtimeConfigBridge.current?.disabledSkillIds() ?? /* @__PURE__ */ new Set();
1587
+ const skillMap = { ...SKILL_KEYWORD_MAP, ...runtimeCustomSkillKeywords() };
1337
1588
  const scores = {};
1338
- for (const [skillName, keywords] of Object.entries(SKILL_KEYWORD_MAP)) {
1589
+ for (const [skillName, keywords] of Object.entries(skillMap)) {
1590
+ if (disabledSkillIds.has(skillName)) continue;
1339
1591
  let score = 0;
1340
1592
  for (const kw of keywords) {
1341
1593
  if (combined.includes(kw)) {
@@ -1352,7 +1604,7 @@ function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
1352
1604
  const maxScore = top[0]?.[1] || 1;
1353
1605
  const results = [];
1354
1606
  for (const [name, score] of top) {
1355
- const content = skills[name];
1607
+ const content = skills[name] || loadUserSkill(name);
1356
1608
  if (content) {
1357
1609
  results.push({
1358
1610
  skillName: name,
@@ -1377,16 +1629,17 @@ function formatSkillsForPrompt(skills) {
1377
1629
  ${s.skillContent}`
1378
1630
  ).join("\n\n---\n\n");
1379
1631
  }
1380
- var __filename3, __dirname3, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
1632
+ var __filename3, __dirname3, SKILL_KEYWORD_MAP, cachedSkills, userSkillCache;
1381
1633
  var init_skill_loader = __esm({
1382
1634
  "src/engine/skill-loader.ts"() {
1383
1635
  "use strict";
1384
1636
  init_esm_shims();
1385
1637
  init_rules_loader();
1386
- __filename3 = fileURLToPath4(import.meta.url);
1638
+ init_paths();
1639
+ init_asset_dir();
1640
+ init_runtime_config_bridge();
1641
+ __filename3 = fileURLToPath3(import.meta.url);
1387
1642
  __dirname3 = dirname2(__filename3);
1388
- SKILLS_DIR = resolve2(__dirname3, "../skills");
1389
- SKILLS_DIR_FALLBACK = resolve2(__dirname3, "../../../agent_moss/skills");
1390
1643
  SKILL_KEYWORD_MAP = (() => {
1391
1644
  const map = {};
1392
1645
  const cats = loadL3();
@@ -1399,6 +1652,7 @@ var init_skill_loader = __esm({
1399
1652
  return map;
1400
1653
  })();
1401
1654
  cachedSkills = null;
1655
+ userSkillCache = {};
1402
1656
  }
1403
1657
  });
1404
1658
 
@@ -1415,6 +1669,12 @@ __export(runtime_config_exports, {
1415
1669
  deleteCustomRule: () => deleteCustomRule,
1416
1670
  deleteCustomSkill: () => deleteCustomSkill,
1417
1671
  deleteScenarioRule: () => deleteScenarioRule,
1672
+ effectiveDangerousPatterns: () => effectiveDangerousPatterns,
1673
+ effectiveInjectionKeywords: () => effectiveInjectionKeywords,
1674
+ effectiveIntentPatterns: () => effectiveIntentPatterns,
1675
+ effectivePasswordPatterns: () => effectivePasswordPatterns,
1676
+ effectiveSensitivePaths: () => effectiveSensitivePaths,
1677
+ effectiveUserDeletionPatterns: () => effectiveUserDeletionPatterns,
1418
1678
  filterEnabledRules: () => filterEnabledRules,
1419
1679
  generateSourceDefaults: () => generateSourceDefaults,
1420
1680
  getAgentRules: () => getAgentRules,
@@ -1458,9 +1718,9 @@ __export(runtime_config_exports, {
1458
1718
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1459
1719
  updateSkillEnabled: () => updateSkillEnabled
1460
1720
  });
1461
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3, statSync } from "fs";
1721
+ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync3, statSync } from "fs";
1462
1722
  import { join as join3 } from "path";
1463
- import { fileURLToPath as fileURLToPath5 } from "url";
1723
+ import { fileURLToPath as fileURLToPath4 } from "url";
1464
1724
  import { dirname as dirname3 } from "path";
1465
1725
  function runtimeDir() {
1466
1726
  return getRuntimeConfigDir();
@@ -1473,7 +1733,7 @@ function getRuntimeConfigPath() {
1473
1733
  function saveRuntimeConfig(config) {
1474
1734
  const p = getRuntimeConfigPath();
1475
1735
  const dir = p.substring(0, p.lastIndexOf("/"));
1476
- if (dir && !existsSync4(dir)) mkdirSync(dir, { recursive: true });
1736
+ if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
1477
1737
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
1478
1738
  invalidateRuntimeConfigCache();
1479
1739
  }
@@ -1491,10 +1751,11 @@ function emptyRuntimeConfig() {
1491
1751
  }
1492
1752
  function invalidateRuntimeConfigCache() {
1493
1753
  runtimeConfigCache = null;
1754
+ hasRuntimeConfigCache.clear();
1494
1755
  }
1495
1756
  function loadRuntimeConfig() {
1496
1757
  const p = getRuntimeConfigPath();
1497
- if (!existsSync4(p)) {
1758
+ if (!existsSync5(p)) {
1498
1759
  const seeded = mergeConfig({}, generateSourceDefaults());
1499
1760
  try {
1500
1761
  saveRuntimeConfig(seeded);
@@ -1515,7 +1776,13 @@ function loadRuntimeConfig() {
1515
1776
  layers: {
1516
1777
  L1_heuristic: data.layers?.L1_heuristic ?? true,
1517
1778
  L2_logic_rules: data.layers?.L2_logic_rules ?? true,
1518
- L3_llm_analysis: data.layers?.L3_llm_analysis ?? true
1779
+ L3_llm_analysis: data.layers?.L3_llm_analysis ?? true,
1780
+ // ⚠️ agent_rules_enabled / scenario_rules_enabled 必须一并带上:
1781
+ // isLayerEnabled 的判据是 `layerKey in layers`,键丢了就恒真,
1782
+ // 于是控制台的「agent 规则 / 场景规则」总开关**永远是开**(点了没反应)。
1783
+ // 这两个键本来就写在 seed 里(DEFAULT_LAYERS),只是这里没透传。
1784
+ agent_rules_enabled: data.layers?.agent_rules_enabled ?? true,
1785
+ scenario_rules_enabled: data.layers?.scenario_rules_enabled ?? true
1519
1786
  },
1520
1787
  L1_rules: merged.L1_rules || {},
1521
1788
  L2_rules: merged.L2_rules || {},
@@ -1557,21 +1824,48 @@ function filterEnabledRules(rules, categoryEnabled = true) {
1557
1824
  if (!categoryEnabled) return [];
1558
1825
  return rules.filter((r) => r.enabled !== false);
1559
1826
  }
1560
- function getEnabledL1CommandPatterns(runtime) {
1827
+ function _filterRulesByOs(rules, osType) {
1828
+ const accepted = acceptedOsValues(osType);
1829
+ return rules.filter((r) => accepted.includes(String(r.os || "linux").toLowerCase()));
1830
+ }
1831
+ function _userCustomRules(runtime, layers, requiredKey) {
1832
+ const list = Array.isArray(layers) ? layers : [layers];
1833
+ const out = [];
1834
+ for (const layer of list) {
1835
+ const cat = runtime?.[layer]?.user_custom;
1836
+ const rules = filterEnabledRules(cat?.rules || [], cat?.category_enabled);
1837
+ for (const r of rules) {
1838
+ if (!r[requiredKey]) continue;
1839
+ out.push(r.risk_level ? r : { ...r, risk_level: "high" });
1840
+ }
1841
+ }
1842
+ return out;
1843
+ }
1844
+ function _mergeUserCustom(primary, runtime, layers, key) {
1845
+ const extra = _userCustomRules(runtime, layers, key);
1846
+ return extra.length > 0 ? [...primary, ...extra] : [...primary];
1847
+ }
1848
+ function getEnabledL1CommandPatterns(runtime, osType) {
1561
1849
  const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
1562
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
1850
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
1851
+ rules = _mergeUserCustom(rules, runtime, "L1_rules", "pattern");
1852
+ if (osType) rules = _filterRulesByOs(rules, osType);
1853
+ return rules.map((r) => ({
1563
1854
  pattern: r.pattern || "",
1564
1855
  risk_level: r.risk_level || "",
1565
1856
  risk_type: r.risk_type || "",
1566
1857
  reason: r.reason || ""
1567
1858
  }));
1568
1859
  }
1569
- function getEnabledL2SensitivePaths(runtime) {
1860
+ function getEnabledL2SensitivePaths(runtime, osType) {
1570
1861
  let cat = runtime.L1_rules?.sensitive_path_access;
1571
1862
  if (!cat || !cat.rules || cat.rules.length === 0) {
1572
1863
  cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
1573
1864
  }
1574
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => {
1865
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
1866
+ rules = _mergeUserCustom(rules, runtime, ["L1_rules", "L2_rules"], "path");
1867
+ if (osType) rules = _filterRulesByOs(rules, osType);
1868
+ return rules.map((r) => {
1575
1869
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1576
1870
  if (r.credential) out.credential = true;
1577
1871
  if (r.read_only) out.read_only = true;
@@ -1581,13 +1875,24 @@ function getEnabledL2SensitivePaths(runtime) {
1581
1875
  }
1582
1876
  function hasRuntimeConfig() {
1583
1877
  const p = getRuntimeConfigPath();
1584
- if (!existsSync4(p)) return false;
1878
+ let key;
1585
1879
  try {
1586
- JSON.parse(readFileSync4(p, "utf-8"));
1587
- return true;
1880
+ const st = statSync(p);
1881
+ key = `${p}|${st.mtimeMs}|${st.size}`;
1588
1882
  } catch {
1589
1883
  return false;
1590
1884
  }
1885
+ const cached = hasRuntimeConfigCache.get(key);
1886
+ if (cached !== void 0) return cached;
1887
+ let ok = false;
1888
+ try {
1889
+ JSON.parse(readFileSync4(p, "utf-8"));
1890
+ ok = true;
1891
+ } catch {
1892
+ ok = false;
1893
+ }
1894
+ hasRuntimeConfigCache.set(key, ok);
1895
+ return ok;
1591
1896
  }
1592
1897
  function getDisabledL2RulesWithSkipL3() {
1593
1898
  let runtime;
@@ -1652,15 +1957,28 @@ function _syncDenyModeFields(rule) {
1652
1957
  }
1653
1958
  }
1654
1959
  function _l1CommandPatternsToRules() {
1655
- return DANGEROUS_PATTERNS.map((p) => ({
1656
- id: _generatePatternId(p.pattern instanceof RegExp ? p.pattern.source : p.pattern),
1657
- pattern: p.pattern instanceof RegExp ? p.pattern.source : p.pattern,
1658
- risk_level: p.severity,
1659
- risk_type: p.riskType,
1660
- reason: p.description,
1661
- enabled: true,
1662
- builtin: true
1663
- }));
1960
+ const out = [];
1961
+ const seen = /* @__PURE__ */ new Set();
1962
+ for (const osName of ["linux", "windows", "macos", "harmony"]) {
1963
+ for (const p of getDangerousPatternsForOs(osName)) {
1964
+ const src = p.pattern instanceof RegExp ? p.pattern.source : String(p.pattern);
1965
+ const rule = {
1966
+ id: _generatePatternId(src),
1967
+ pattern: src,
1968
+ risk_level: p.severity,
1969
+ risk_type: p.riskType,
1970
+ reason: p.description,
1971
+ enabled: true,
1972
+ builtin: true,
1973
+ os: p.os || "linux"
1974
+ };
1975
+ const fp = _ruleFingerprint(rule);
1976
+ if (seen.has(fp)) continue;
1977
+ seen.add(fp);
1978
+ out.push(rule);
1979
+ }
1980
+ }
1981
+ return out;
1664
1982
  }
1665
1983
  function _l1InjectionKeywordsToRules() {
1666
1984
  return INJECTION_KEYWORDS.map((kw) => ({
@@ -1673,28 +1991,42 @@ function _l1InjectionKeywordsToRules() {
1673
1991
  }));
1674
1992
  }
1675
1993
  function _l2SensitivePathsToRules() {
1676
- const all = [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS, ...CREDENTIAL_PATHS];
1677
- return all.map((sp) => {
1678
- const denyMode = _deriveDenyMode({
1679
- deny_mode: sp.deny_mode,
1680
- credential: sp.credential,
1681
- read_only: sp.read_only
1682
- });
1683
- const rule = {
1684
- id: `path_${_slugify(sp.path)}`,
1685
- path: sp.path,
1686
- risk_level: sp.riskLevel,
1687
- desc: sp.desc,
1688
- deny_mode: denyMode,
1689
- source_deny_mode: denyMode,
1690
- skip_l3_on_disabled: true,
1691
- enabled: true,
1692
- builtin: true
1693
- };
1694
- _syncDenyModeFields(rule);
1695
- if (sp.dirDelOnly) rule.dir_del_only = true;
1696
- return rule;
1697
- });
1994
+ const out = [];
1995
+ const seen = /* @__PURE__ */ new Set();
1996
+ for (const osName of ["linux", "windows", "macos", "harmony"]) {
1997
+ const { root, home, credential } = getSensitivePathsForOsBuiltin(osName);
1998
+ const all = [...root, ...home, ...credential];
1999
+ for (const sp of all) {
2000
+ const os4 = String(sp.os || "linux").toLowerCase();
2001
+ const denyMode = _deriveDenyMode({
2002
+ deny_mode: sp.deny_mode,
2003
+ credential: sp.credential,
2004
+ read_only: sp.read_only
2005
+ });
2006
+ const rule = {
2007
+ // id = path_<平台>_<slug>_<hash8>:平台段消除跨平台同名(.ssh/.aws 各一条),
2008
+ // hash 段消除同平台 slug 撞车(macOS 的 /Library/LaunchAgents 与
2009
+ // Library/LaunchAgents)—— Console 按 id 操作,撞 id 会误改别的规则。
2010
+ id: `path_${os4}_${_slugify(sp.path)}_${_fnv1a32(sp.path)}`,
2011
+ path: sp.path,
2012
+ risk_level: sp.riskLevel,
2013
+ desc: sp.desc,
2014
+ deny_mode: denyMode,
2015
+ source_deny_mode: denyMode,
2016
+ skip_l3_on_disabled: true,
2017
+ enabled: true,
2018
+ builtin: true,
2019
+ os: os4
2020
+ };
2021
+ _syncDenyModeFields(rule);
2022
+ if (sp.dirDelOnly) rule.dir_del_only = true;
2023
+ const fp = _ruleFingerprint(rule);
2024
+ if (seen.has(fp)) continue;
2025
+ seen.add(fp);
2026
+ out.push(rule);
2027
+ }
2028
+ }
2029
+ return out;
1698
2030
  }
1699
2031
  function _l2IntentPatternsToRules() {
1700
2032
  return INTENT_DEVIATION_PATTERNS.map((pat, i) => ({
@@ -1771,8 +2103,8 @@ function _l3SkillsToRules() {
1771
2103
  }
1772
2104
  function _scenarioRulesToDefaults() {
1773
2105
  const out = {};
1774
- const scenDir = existsSync4(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync4(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
1775
- if (!existsSync4(scenDir)) return out;
2106
+ const scenDir = existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2107
+ if (!existsSync5(scenDir)) return out;
1776
2108
  let entries;
1777
2109
  try {
1778
2110
  entries = readdirSync3(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -1781,7 +2113,7 @@ function _scenarioRulesToDefaults() {
1781
2113
  }
1782
2114
  for (const scenarioId of entries) {
1783
2115
  const rulesFile = join3(scenDir, scenarioId, "rules.json");
1784
- if (!existsSync4(rulesFile)) continue;
2116
+ if (!existsSync5(rulesFile)) continue;
1785
2117
  let data;
1786
2118
  try {
1787
2119
  data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
@@ -1820,8 +2152,8 @@ function _scenarioRulesToDefaults() {
1820
2152
  }
1821
2153
  function _agentRulesToDefaults() {
1822
2154
  const out = {};
1823
- const agentsDir = existsSync4(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync4(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
1824
- if (!existsSync4(agentsDir)) return out;
2155
+ const agentsDir = existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2156
+ if (!existsSync5(agentsDir)) return out;
1825
2157
  let entries;
1826
2158
  try {
1827
2159
  entries = readdirSync3(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -1830,7 +2162,7 @@ function _agentRulesToDefaults() {
1830
2162
  }
1831
2163
  for (const agentId of entries) {
1832
2164
  const rulesFile = join3(agentsDir, agentId, "rules.json");
1833
- if (!existsSync4(rulesFile)) continue;
2165
+ if (!existsSync5(rulesFile)) continue;
1834
2166
  let data;
1835
2167
  try {
1836
2168
  data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
@@ -1926,7 +2258,7 @@ function _ltlRulesToRuntime() {
1926
2258
  let mc = {};
1927
2259
  for (const dir of cands) {
1928
2260
  const p = join3(dir, "mc_rules.json");
1929
- if (existsSync4(p)) {
2261
+ if (existsSync5(p)) {
1930
2262
  try {
1931
2263
  mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
1932
2264
  } catch {
@@ -1955,17 +2287,27 @@ function normalizePatternForFingerprint(pattern) {
1955
2287
  return pattern.replace(/\\\//g, "/");
1956
2288
  }
1957
2289
  function _ruleFingerprint(rule) {
2290
+ const osTag = String(rule.os || "linux").toLowerCase();
1958
2291
  if (rule.intent_keywords && rule.intent_keywords.length || rule.dangerous_actions && rule.dangerous_actions.length) {
1959
2292
  return JSON.stringify([
2293
+ osTag,
1960
2294
  [...rule.intent_keywords || []].sort(),
1961
2295
  [...rule.dangerous_actions || []].sort()
1962
2296
  ]);
1963
2297
  }
1964
- if (rule.path) return JSON.stringify([normalizePatternForFingerprint(rule.path)]);
1965
- if (rule.pattern) return JSON.stringify([normalizePatternForFingerprint(rule.pattern)]);
1966
- if (rule.keyword) return JSON.stringify([rule.keyword, rule.lang || ""]);
1967
- if (rule.value) return JSON.stringify([rule.value]);
1968
- return JSON.stringify([rule.id || ""]);
2298
+ if (rule.path) return JSON.stringify([osTag, normalizePatternForFingerprint(rule.path)]);
2299
+ if (rule.pattern) return JSON.stringify([osTag, normalizePatternForFingerprint(rule.pattern)]);
2300
+ if (rule.keyword) return JSON.stringify([osTag, rule.keyword, rule.lang || ""]);
2301
+ if (rule.value) return JSON.stringify([osTag, rule.value]);
2302
+ return JSON.stringify([osTag, rule.id || ""]);
2303
+ }
2304
+ function _applyRuleEnabled(rule, enabled) {
2305
+ rule.enabled = enabled;
2306
+ if (enabled) {
2307
+ delete rule.user_disabled;
2308
+ } else {
2309
+ rule.user_disabled = true;
2310
+ }
1969
2311
  }
1970
2312
  function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
1971
2313
  const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
@@ -1988,13 +2330,14 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
1988
2330
  srcFps.add(fp);
1989
2331
  if (userByFp.has(fp)) {
1990
2332
  const existing = userByFp.get(fp);
1991
- const userEnabled = existing.enabled;
2333
+ const userDisabled = existing.user_disabled === true;
1992
2334
  const userSkipL3 = existing.skip_l3_on_disabled;
1993
2335
  const userDenyMode = existing.deny_mode;
1994
2336
  const userSourceDeny = existing.source_deny_mode;
1995
2337
  Object.keys(existing).forEach((k) => delete existing[k]);
1996
2338
  Object.assign(existing, srcR);
1997
- existing.enabled = userEnabled;
2339
+ existing.enabled = !userDisabled;
2340
+ if (userDisabled) existing.user_disabled = true;
1998
2341
  if (userSkipL3 !== void 0) existing.skip_l3_on_disabled = userSkipL3;
1999
2342
  if (userDenyMode && userSourceDeny && userDenyMode !== userSourceDeny) {
2000
2343
  existing.deny_mode = userDenyMode;
@@ -2020,7 +2363,19 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
2020
2363
  }
2021
2364
  for (const catName of Object.keys(userLayer)) {
2022
2365
  if (catName === "user_custom") continue;
2023
- if (!(catName in srcLayer)) delete userLayer[catName];
2366
+ if (!(catName in srcLayer)) {
2367
+ const kept = (userLayer[catName].rules || []).filter((r) => r.builtin !== true);
2368
+ if (kept.length) {
2369
+ const uc = userLayer.user_custom || (userLayer.user_custom = {
2370
+ category_enabled: true,
2371
+ category_desc: "",
2372
+ rules: []
2373
+ });
2374
+ const existing = new Set((uc.rules || []).map((r) => r.id));
2375
+ uc.rules = [...uc.rules || [], ...kept.filter((r) => !existing.has(r.id))];
2376
+ }
2377
+ delete userLayer[catName];
2378
+ }
2024
2379
  }
2025
2380
  }
2026
2381
  function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
@@ -2068,10 +2423,11 @@ function _mergeAgentRules(userCfg, srcDefaults, partition = "agent_rules") {
2068
2423
  srcFps.add(fp);
2069
2424
  if (userByFp.has(fp)) {
2070
2425
  const existing = userByFp.get(fp);
2071
- const userEnabled = existing.enabled;
2426
+ const userDisabled = existing.user_disabled === true;
2072
2427
  Object.keys(existing).forEach((k) => delete existing[k]);
2073
2428
  Object.assign(existing, srcR);
2074
- existing.enabled = userEnabled;
2429
+ existing.enabled = !userDisabled;
2430
+ if (userDisabled) existing.user_disabled = true;
2075
2431
  } else {
2076
2432
  userRules.push({ ...srcR, enabled: true });
2077
2433
  }
@@ -2135,17 +2491,23 @@ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
2135
2491
  delete src[catName];
2136
2492
  }
2137
2493
  }
2138
- function getEnabledL1InjectionKeywords(runtime) {
2494
+ function getEnabledL1InjectionKeywords(runtime, osType) {
2139
2495
  const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
2140
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
2496
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
2497
+ rules = _mergeUserCustom(rules, runtime, "L1_rules", "keyword");
2498
+ if (osType) rules = _filterRulesByOs(rules, osType);
2499
+ return rules.map((r) => ({
2141
2500
  keyword: r.keyword || "",
2142
2501
  risk_level: r.risk_level || "",
2143
2502
  lang: r.lang || "en"
2144
2503
  }));
2145
2504
  }
2146
- function getEnabledL2IntentPatterns(runtime) {
2505
+ function getEnabledL2IntentPatterns(runtime, osType) {
2147
2506
  const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
2148
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
2507
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
2508
+ rules = _mergeUserCustom(rules, runtime, "L2_rules", "intent_keywords");
2509
+ if (osType) rules = _filterRulesByOs(rules, osType);
2510
+ return rules.map((r) => ({
2149
2511
  intent_keywords: r.intent_keywords || [],
2150
2512
  dangerous_actions: r.dangerous_actions || [],
2151
2513
  reason: r.reason || ""
@@ -2153,11 +2515,13 @@ function getEnabledL2IntentPatterns(runtime) {
2153
2515
  }
2154
2516
  function getEnabledL2PasswordPatterns(runtime) {
2155
2517
  const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
2156
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => r.pattern || "");
2518
+ const rules = _mergeUserCustom(filterEnabledRules(cat.rules || [], cat.category_enabled), runtime, "L2_rules", "pattern");
2519
+ return rules.map((r) => r.pattern || "");
2157
2520
  }
2158
2521
  function getEnabledL2UserDeletionPatterns(runtime) {
2159
2522
  const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
2160
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => r.pattern || "");
2523
+ const rules = _mergeUserCustom(filterEnabledRules(cat.rules || [], cat.category_enabled), runtime, "L2_rules", "pattern");
2524
+ return rules.map((r) => r.pattern || "");
2161
2525
  }
2162
2526
  function isL2CategoryEnabled(runtime, categoryKey) {
2163
2527
  const cat = runtime.L2_rules?.[categoryKey];
@@ -2176,45 +2540,126 @@ function getDisabledSkillIds(runtime) {
2176
2540
  }
2177
2541
  return disabled;
2178
2542
  }
2179
- function getEnabledL1CommandPatternsOrDefault(runtime) {
2180
- const enabled = getEnabledL1CommandPatterns(runtime);
2181
- if (enabled.length) return enabled;
2182
- if (!hasRuntimeConfig()) return _l1CommandPatternsToRules();
2183
- return [];
2184
- }
2185
- function getEnabledL1InjectionKeywordsOrDefault(runtime) {
2186
- const enabled = getEnabledL1InjectionKeywords(runtime);
2187
- if (enabled.length) return enabled;
2188
- if (!hasRuntimeConfig()) return _l1InjectionKeywordsToRules();
2189
- return [];
2190
- }
2191
- function getEnabledL2SensitivePathsOrDefault(runtime) {
2192
- const enabled = getEnabledL2SensitivePaths(runtime);
2193
- if (enabled.length) return enabled;
2194
- if (!hasRuntimeConfig()) return _l2SensitivePathsToRules();
2195
- return [];
2196
- }
2197
- function getEnabledL2IntentPatternsOrDefault(runtime) {
2198
- const enabled = getEnabledL2IntentPatterns(runtime);
2199
- if (enabled.length) return enabled;
2200
- if (!hasRuntimeConfig()) return _l2IntentPatternsToRules();
2201
- return [];
2202
- }
2203
- function getEnabledL2PasswordPatternsOrDefault(runtime) {
2204
- const enabled = getEnabledL2PasswordPatterns(runtime);
2205
- if (enabled.length) return enabled;
2206
- if (!hasRuntimeConfig()) {
2207
- return PASSWORD_MODIFY_PATTERNS.map((p) => p.source);
2208
- }
2209
- return [];
2210
- }
2211
- function getEnabledL2UserDeletionPatternsOrDefault(runtime) {
2212
- const enabled = getEnabledL2UserDeletionPatterns(runtime);
2213
- if (enabled.length) return enabled;
2214
- if (!hasRuntimeConfig()) {
2215
- return USER_DELETION_PATTERNS.map((p) => p.source);
2216
- }
2217
- return [];
2543
+ function _filterDefaultsByOs(rules, osType) {
2544
+ const accepted = acceptedOsValues(osType);
2545
+ return rules.filter((r) => !r.os || accepted.includes(String(r.os).toLowerCase()));
2546
+ }
2547
+ function _hasRulesForOs(rules, osType) {
2548
+ return _filterRulesByOs(rules || [], osType || "linux").length > 0;
2549
+ }
2550
+ function getEnabledL1CommandPatternsOrDefault(runtime, osType) {
2551
+ const cat = runtime.L1_rules?.dangerous_commands;
2552
+ if (_hasRulesForOs(cat?.rules, osType)) {
2553
+ return getEnabledL1CommandPatterns(runtime, osType);
2554
+ }
2555
+ return _mergeUserCustom(_filterDefaultsByOs(_l1CommandPatternsToRules(), osType), runtime, "L1_rules", "pattern");
2556
+ }
2557
+ function getEnabledL1InjectionKeywordsOrDefault(runtime, osType) {
2558
+ const cat = runtime.L1_rules?.injection_detection;
2559
+ if (_hasRulesForOs(cat?.rules, osType)) {
2560
+ return getEnabledL1InjectionKeywords(runtime, osType);
2561
+ }
2562
+ return _mergeUserCustom(_l1InjectionKeywordsToRules(), runtime, "L1_rules", "keyword");
2563
+ }
2564
+ function getEnabledL2SensitivePathsOrDefault(runtime, osType) {
2565
+ const spCat = _sensitivePathCategoryOf(runtime);
2566
+ if (_hasRulesForOs(spCat?.rules, osType)) {
2567
+ return getEnabledL2SensitivePaths(runtime, osType);
2568
+ }
2569
+ return _mergeUserCustom(_filterDefaultsByOs(_l2SensitivePathsToRules(), osType), runtime, ["L1_rules", "L2_rules"], "path");
2570
+ }
2571
+ function getEnabledL2IntentPatternsOrDefault(runtime, osType) {
2572
+ const cat = runtime.L2_rules?.intent_consistency;
2573
+ if (_hasRulesForOs(cat?.rules, osType)) {
2574
+ return getEnabledL2IntentPatterns(runtime, osType);
2575
+ }
2576
+ return _mergeUserCustom(_filterDefaultsByOs(_l2IntentPatternsToRules(), osType), runtime, "L2_rules", "intent_keywords");
2577
+ }
2578
+ function getEnabledL2PasswordPatternsOrDefault(runtime, osType) {
2579
+ const cat = runtime.L2_rules?.password_modify_consent;
2580
+ if (_hasRulesForOs(cat?.rules, osType)) {
2581
+ return getEnabledL2PasswordPatterns(runtime);
2582
+ }
2583
+ return PASSWORD_MODIFY_PATTERNS.map((p) => p.source);
2584
+ }
2585
+ function getEnabledL2UserDeletionPatternsOrDefault(runtime, osType) {
2586
+ const cat = runtime.L2_rules?.user_deletion_consent;
2587
+ if (_hasRulesForOs(cat?.rules, osType)) {
2588
+ return getEnabledL2UserDeletionPatterns(runtime);
2589
+ }
2590
+ return USER_DELETION_PATTERNS.map((p) => p.source);
2591
+ }
2592
+ function _effective(osType, key, compute) {
2593
+ const runtime = loadRuntimeConfig();
2594
+ let bucket = _effectiveCache.get(runtime);
2595
+ if (!bucket) {
2596
+ bucket = /* @__PURE__ */ new Map();
2597
+ _effectiveCache.set(runtime, bucket);
2598
+ }
2599
+ const k = `${key}|${osType || ""}`;
2600
+ if (bucket.has(k)) return bucket.get(k);
2601
+ const value = compute(runtime);
2602
+ bucket.set(k, value);
2603
+ return value;
2604
+ }
2605
+ function effectiveDangerousPatterns(osType) {
2606
+ return _effective(osType, "danger", (rt) => {
2607
+ const rules = getEnabledL1CommandPatternsOrDefault(rt, osType);
2608
+ return rules.filter((r) => r.pattern).map((r) => ({
2609
+ pattern: new RegExp(String(r.pattern), "i"),
2610
+ severity: String(r.risk_level || "medium"),
2611
+ riskType: String(r.risk_type || ""),
2612
+ description: String(r.reason || ""),
2613
+ os: String(r.os || "linux").toLowerCase()
2614
+ }));
2615
+ });
2616
+ }
2617
+ function effectiveInjectionKeywords(osType) {
2618
+ return _effective(osType, "injection", (rt) => {
2619
+ const rules = getEnabledL1InjectionKeywordsOrDefault(rt, osType);
2620
+ return rules.map((r) => ({
2621
+ keyword: String(r.keyword || ""),
2622
+ riskLevel: String(r.risk_level || ""),
2623
+ lang: String(r.lang || "en")
2624
+ })).filter((r) => r.keyword);
2625
+ });
2626
+ }
2627
+ function effectiveSensitivePaths(osType) {
2628
+ return _effective(osType, "sensitive", (rt) => {
2629
+ const rules = getEnabledL2SensitivePathsOrDefault(rt, osType);
2630
+ return rules.filter((r) => r.path).map((r) => ({
2631
+ path: String(r.path),
2632
+ riskLevel: String(r.risk_level || "high"),
2633
+ desc: String(r.desc || ""),
2634
+ credential: r.credential === true ? true : void 0,
2635
+ readOnly: r.read_only === true ? true : void 0,
2636
+ dirDelOnly: r.dir_del_only === true ? true : void 0,
2637
+ denyMode: String(r.deny_mode || ""),
2638
+ os: String(r.os || "linux").toLowerCase()
2639
+ }));
2640
+ });
2641
+ }
2642
+ function effectiveIntentPatterns(osType) {
2643
+ return _effective(osType, "intent", (rt) => {
2644
+ const rules = getEnabledL2IntentPatternsOrDefault(rt, osType);
2645
+ return rules.map((r) => ({
2646
+ intentKeywords: r.intent_keywords || [],
2647
+ dangerousActions: r.dangerous_actions || [],
2648
+ reason: String(r.reason || ""),
2649
+ os: String(r.os || "linux").toLowerCase()
2650
+ }));
2651
+ });
2652
+ }
2653
+ function effectivePasswordPatterns(osType) {
2654
+ return _effective(osType, "password", (rt) => getEnabledL2PasswordPatternsOrDefault(rt, osType).map((p) => new RegExp(p, "i")));
2655
+ }
2656
+ function effectiveUserDeletionPatterns(osType) {
2657
+ return _effective(osType, "userdel", (rt) => getEnabledL2UserDeletionPatternsOrDefault(rt, osType).map((p) => new RegExp(p, "i")));
2658
+ }
2659
+ function _sensitivePathCategoryOf(runtime) {
2660
+ const l1 = runtime.L1_rules?.sensitive_path_access;
2661
+ if (l1 && (l1.rules || []).length > 0) return l1;
2662
+ return runtime.L2_rules?.sensitive_path_access;
2218
2663
  }
2219
2664
  function _validateSkillId(skillId) {
2220
2665
  if (!skillId || !SKILL_ID_RE.test(skillId)) {
@@ -2231,7 +2676,7 @@ function updateRuleEnabled(layer, category, ruleId, enabled) {
2231
2676
  const runtime = loadRuntimeConfig();
2232
2677
  for (const r of runtime[layer]?.[category]?.rules || []) {
2233
2678
  if (r.id === ruleId) {
2234
- r.enabled = enabled;
2679
+ _applyRuleEnabled(r, enabled);
2235
2680
  break;
2236
2681
  }
2237
2682
  }
@@ -2301,7 +2746,7 @@ function deleteCustomRule(layer, category, ruleId) {
2301
2746
  function addCustomSkill(skillId, category, keywords, content) {
2302
2747
  _validateSkillId(skillId);
2303
2748
  const runtime = loadRuntimeConfig();
2304
- const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2749
+ const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
2305
2750
  try {
2306
2751
  mkdirSync(skillsDir, { recursive: true });
2307
2752
  } catch {
@@ -2321,9 +2766,9 @@ function deleteCustomSkill(skillId, category) {
2321
2766
  if (idx >= 0) {
2322
2767
  if (skills[idx].builtin) return null;
2323
2768
  skills.splice(idx, 1);
2324
- const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2769
+ const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
2325
2770
  try {
2326
- if (existsSync4(skillPath)) unlinkSync(skillPath);
2771
+ if (existsSync5(skillPath)) unlinkSync(skillPath);
2327
2772
  } catch {
2328
2773
  }
2329
2774
  saveRuntimeConfig(runtime);
@@ -2402,7 +2847,7 @@ function updateAgentRuleEnabled(agentId, category, ruleFp, enabled) {
2402
2847
  const targetFp = _ruleFingerprint(ruleFp);
2403
2848
  for (const r of cat.rules || []) {
2404
2849
  if (_ruleFingerprint(r) === targetFp) {
2405
- r.enabled = enabled;
2850
+ _applyRuleEnabled(r, enabled);
2406
2851
  break;
2407
2852
  }
2408
2853
  }
@@ -2430,137 +2875,419 @@ function deleteAgentRule(agentId, category, ruleFp) {
2430
2875
  saveRuntimeConfig(runtime);
2431
2876
  return runtime;
2432
2877
  }
2433
- function updateAgentCategoryEnabled(agentId, category, enabled) {
2434
- const runtime = loadRuntimeConfig();
2435
- const cat = runtime.agent_rules?.[agentId]?.[category];
2436
- if (cat) cat.category_enabled = enabled;
2437
- saveRuntimeConfig(runtime);
2438
- return runtime;
2878
+ function updateAgentCategoryEnabled(agentId, category, enabled) {
2879
+ const runtime = loadRuntimeConfig();
2880
+ const cat = runtime.agent_rules?.[agentId]?.[category];
2881
+ if (cat) cat.category_enabled = enabled;
2882
+ saveRuntimeConfig(runtime);
2883
+ return runtime;
2884
+ }
2885
+ function getScenarioRules(runtime) {
2886
+ return runtime.scenario_rules || {};
2887
+ }
2888
+ function getEnabledScenarioExtensions(runtime, scenarioId) {
2889
+ const scenData = runtime.scenario_rules?.[scenarioId];
2890
+ if (!scenData) return null;
2891
+ const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
2892
+ pattern: r.pattern,
2893
+ risk_level: r.risk_level || "",
2894
+ risk_type: r.risk_type || "",
2895
+ reason: r.reason || ""
2896
+ }));
2897
+ const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
2898
+ keyword: r.keyword,
2899
+ risk_level: r.risk_level || "",
2900
+ lang: r.lang || "en"
2901
+ }));
2902
+ const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
2903
+ path: r.path,
2904
+ risk_level: r.risk_level || "",
2905
+ desc: r.desc || "",
2906
+ credential: r.credential,
2907
+ read_only: r.read_only,
2908
+ deny_mode: r.deny_mode
2909
+ }));
2910
+ return {
2911
+ agent_id: scenarioId,
2912
+ description: "",
2913
+ extra_command_patterns: cmdPatterns,
2914
+ extra_injection_keywords: injKeywords,
2915
+ extra_sensitive_paths: sensitivePaths,
2916
+ builtin_safe_skills: [],
2917
+ builtin_safe_action_types: [],
2918
+ extra_skills: []
2919
+ };
2920
+ }
2921
+ function updateScenarioRuleEnabled(scenarioId, category, ruleFp, enabled) {
2922
+ const runtime = loadRuntimeConfig();
2923
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2924
+ if (cat) {
2925
+ const targetFp = _ruleFingerprint(ruleFp);
2926
+ for (const r of cat.rules || []) {
2927
+ if (_ruleFingerprint(r) === targetFp) {
2928
+ _applyRuleEnabled(r, enabled);
2929
+ break;
2930
+ }
2931
+ }
2932
+ }
2933
+ saveRuntimeConfig(runtime);
2934
+ return runtime;
2935
+ }
2936
+ function addScenarioRule(scenarioId, category, rule) {
2937
+ const runtime = loadRuntimeConfig();
2938
+ if (!runtime.scenario_rules) runtime.scenario_rules = {};
2939
+ if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
2940
+ const cats = runtime.scenario_rules[scenarioId];
2941
+ if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
2942
+ (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
2943
+ saveRuntimeConfig(runtime);
2944
+ return runtime;
2945
+ }
2946
+ function deleteScenarioRule(scenarioId, category, ruleFp) {
2947
+ const runtime = loadRuntimeConfig();
2948
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2949
+ if (cat) {
2950
+ const targetFp = _ruleFingerprint(ruleFp);
2951
+ cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
2952
+ }
2953
+ saveRuntimeConfig(runtime);
2954
+ return runtime;
2955
+ }
2956
+ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2957
+ const runtime = loadRuntimeConfig();
2958
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2959
+ if (cat) cat.category_enabled = enabled;
2960
+ saveRuntimeConfig(runtime);
2961
+ return runtime;
2962
+ }
2963
+ 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;
2964
+ var init_runtime_config = __esm({
2965
+ "src/infra/runtime-config.ts"() {
2966
+ "use strict";
2967
+ init_esm_shims();
2968
+ init_paths();
2969
+ init_heuristic();
2970
+ init_logic_rules();
2971
+ init_skill_loader();
2972
+ init_runtime_config_bridge();
2973
+ __dirname4 = dirname3(fileURLToPath4(import.meta.url));
2974
+ ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2975
+ RUNTIME_CONFIG_DIR = runtimeDir();
2976
+ RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2977
+ LAYER_DISABLE_ENV = {
2978
+ L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2979
+ L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2980
+ L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2981
+ };
2982
+ DEFAULT_LAYERS = {
2983
+ L1_heuristic: true,
2984
+ L2_logic_rules: true,
2985
+ L3_llm_analysis: true,
2986
+ agent_rules_enabled: true,
2987
+ scenario_rules_enabled: true
2988
+ };
2989
+ DENY_MODE_LABELS = {
2990
+ deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2991
+ deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2992
+ deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2993
+ };
2994
+ runtimeConfigCache = null;
2995
+ hasRuntimeConfigCache = /* @__PURE__ */ new Map();
2996
+ _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2997
+ _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2998
+ _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2999
+ _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3000
+ _effectiveCache = /* @__PURE__ */ new WeakMap();
3001
+ SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
3002
+ registerRuntimeConfigBridge({
3003
+ hasRuntimeConfig,
3004
+ loadRuntimeConfig,
3005
+ effectiveDangerousPatterns,
3006
+ effectiveInjectionKeywords,
3007
+ effectiveSensitivePaths,
3008
+ effectiveIntentPatterns,
3009
+ effectivePasswordPatterns,
3010
+ effectiveUserDeletionPatterns,
3011
+ disabledSkillIds: () => getDisabledSkillIds(loadRuntimeConfig())
3012
+ });
3013
+ }
3014
+ });
3015
+
3016
+ // src/config.ts
3017
+ var config_exports = {};
3018
+ __export(config_exports, {
3019
+ detectProvider: () => detectProvider,
3020
+ getCustomRules: () => getCustomRules,
3021
+ getLLMConfig: () => getLLMConfig,
3022
+ getLLMFailMode: () => getLLMFailMode,
3023
+ getProviderHeaders: () => getProviderHeaders,
3024
+ getRetryConfig: () => getRetryConfig,
3025
+ getTimeoutConfig: () => getTimeoutConfig,
3026
+ isBrainEnabled: () => isBrainEnabled,
3027
+ isFormalObserveMode: () => isFormalObserveMode,
3028
+ isHeuristicEnabled: () => isHeuristicEnabled,
3029
+ isLLMAvailable: () => isLLMAvailable,
3030
+ isLogicRulesEnabled: () => isLogicRulesEnabled,
3031
+ probeLLMConnection: () => probeLLMConnection,
3032
+ updateLLMConfig: () => updateLLMConfig
3033
+ });
3034
+ import fs2 from "fs";
3035
+ import path2 from "path";
3036
+ import { fileURLToPath as fileURLToPath5 } from "url";
3037
+ function loadConfigFile(filePath) {
3038
+ try {
3039
+ const raw = fs2.readFileSync(filePath, "utf-8");
3040
+ return JSON.parse(raw);
3041
+ } catch {
3042
+ return null;
3043
+ }
3044
+ }
3045
+ function resolveConfigPath() {
3046
+ const envPath = process.env.AGENT_MOSS_CONFIG_PATH;
3047
+ if (envPath) return envPath;
3048
+ const userConfig = path2.join(getConfigDir(), "config.json");
3049
+ if (fs2.existsSync(userConfig)) return userConfig;
3050
+ const defaultCandidates = [
3051
+ path2.join(__dirname, "config.json.example"),
3052
+ path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example")
3053
+ ];
3054
+ const defaultConfig = defaultCandidates.find((p) => fs2.existsSync(p));
3055
+ if (defaultConfig) {
3056
+ const configDir = path2.dirname(userConfig);
3057
+ fs2.mkdirSync(configDir, { recursive: true });
3058
+ const pkgConfig = loadConfigFile(defaultConfig);
3059
+ const safeConfig = pkgConfig ? { ...pkgConfig, llm: { ...pkgConfig.llm, api_key: "" } } : {};
3060
+ fs2.writeFileSync(userConfig, JSON.stringify(safeConfig, null, 4), "utf-8");
3061
+ console.log(`[AgentMoss] \u5DF2\u521B\u5EFA\u7528\u6237\u914D\u7F6E\u6587\u4EF6: ${userConfig}`);
3062
+ console.log(`[AgentMoss] \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\u914D\u7F6E\u4F60\u7684 LLM API Key`);
3063
+ }
3064
+ return userConfig;
3065
+ }
3066
+ function loadLLMConfigFromFile(configPath) {
3067
+ const data = loadConfigFile(configPath);
3068
+ if (!data?.llm) return {};
3069
+ const llm = data.llm;
3070
+ return {
3071
+ provider: llm.provider || "",
3072
+ baseUrl: llm.base_url || "",
3073
+ apiKey: llm.api_key || "",
3074
+ model: llm.model || "",
3075
+ temperature: llm.temperature ?? 0.1,
3076
+ maxTokens: llm.max_tokens ?? 4096,
3077
+ timeout: llm.timeout ?? 40
3078
+ };
3079
+ }
3080
+ function loadTimeoutRetryFromFile(configPath) {
3081
+ const data = loadConfigFile(configPath);
3082
+ const t = data?.timeout ?? {};
3083
+ const r = data?.retry ?? {};
3084
+ return {
3085
+ timeout: {
3086
+ totalTimeout: Number(t.total_timeout ?? 90),
3087
+ prompt1Timeout: Number(t.prompt1_timeout ?? 40),
3088
+ prompt2Timeout: Number(t.prompt2_timeout ?? 20),
3089
+ stepInterval: Number(t.step_interval ?? 0)
3090
+ },
3091
+ retry: {
3092
+ maxRetries: Number(r.max_retries ?? 2),
3093
+ retryInterval: Number(r.retry_interval ?? 2)
3094
+ }
3095
+ };
3096
+ }
3097
+ function getTimeoutConfig() {
3098
+ return timeoutConfig;
3099
+ }
3100
+ function getRetryConfig() {
3101
+ return retryConfig;
3102
+ }
3103
+ function wildcardToRegExp(pattern) {
3104
+ return pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
3105
+ }
3106
+ function loadCustomRules() {
3107
+ const rules = [];
3108
+ try {
3109
+ const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3110
+ if (raw) {
3111
+ const parsed = JSON.parse(raw);
3112
+ if (Array.isArray(parsed)) {
3113
+ for (const r of parsed) {
3114
+ if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3115
+ }
3116
+ }
3117
+ }
3118
+ } catch {
3119
+ }
3120
+ for (const rulesPath of USER_RULES_PATHS) {
3121
+ try {
3122
+ if (!fs2.existsSync(rulesPath)) continue;
3123
+ const data = JSON.parse(fs2.readFileSync(rulesPath, "utf-8"));
3124
+ const actions = Array.isArray(data?.sensitive_actions) ? data.sensitive_actions : [];
3125
+ for (const r of actions) {
3126
+ const p = r?.pattern;
3127
+ if (!p) continue;
3128
+ rules.push({ pattern: wildcardToRegExp(p), action: "Deny", severity: "high" });
3129
+ }
3130
+ break;
3131
+ } catch {
3132
+ }
3133
+ }
3134
+ return rules;
3135
+ }
3136
+ function getLLMConfig() {
3137
+ return llmConfig;
3138
+ }
3139
+ function updateLLMConfig(partial) {
3140
+ llmConfig = { ...llmConfig, ...partial };
3141
+ }
3142
+ function getCustomRules() {
3143
+ return customRules;
3144
+ }
3145
+ function isLLMAvailable() {
3146
+ if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
3147
+ if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
3148
+ return llmConfig.apiKey.length > 0;
3149
+ }
3150
+ async function probeLLMConnection(timeoutMs = 3e4) {
3151
+ if (!isLLMAvailable()) return false;
3152
+ const OpenAI3 = (await import("openai")).default;
3153
+ const config = getLLMConfig();
3154
+ const provider = detectProvider(config.baseUrl, config.provider);
3155
+ const extraHeaders = getProviderHeaders(provider);
3156
+ const client = new OpenAI3({
3157
+ baseURL: config.baseUrl,
3158
+ apiKey: config.apiKey,
3159
+ timeout: timeoutMs,
3160
+ maxRetries: 0,
3161
+ defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
3162
+ });
3163
+ try {
3164
+ const resp = await client.chat.completions.create({
3165
+ model: config.model,
3166
+ messages: [{ role: "user", content: "ping" }],
3167
+ max_tokens: 1
3168
+ });
3169
+ return !!(resp && resp.choices && resp.choices[0]);
3170
+ } catch {
3171
+ return false;
3172
+ }
3173
+ }
3174
+ function getLLMFailMode() {
3175
+ const val = (process.env.AGENT_MOSS_LLM_FAIL_MODE || "fail_open").toLowerCase();
3176
+ return val === "fail_closed" ? "fail_closed" : "fail_open";
3177
+ }
3178
+ function detectProvider(baseUrl, providerHint = "") {
3179
+ if (providerHint) {
3180
+ const hint = providerHint.toLowerCase();
3181
+ if (["claude", "anthropic"].includes(hint)) return "anthropic";
3182
+ if (["google", "gemini"].includes(hint)) return "gemini";
3183
+ if (["zai-coding-plan", "zhipu-coding-plan", "zhipuai-coding-plan"].includes(hint)) return "zai-coding-plan";
3184
+ if (["zai", "z-ai", "z.ai", "zai-cn", "zai-china", "zhipu", "glm-cn", "bigmodel"].includes(hint)) return "zhipu";
3185
+ if (["xai", "xai-grok"].includes(hint)) return "xai";
3186
+ if (["minimax", "minimax-openai"].includes(hint)) return "minimax";
3187
+ if (["minimax-anthropic"].includes(hint)) return "minimax-anthropic";
3188
+ if (["other", "custom", "local"].includes(hint)) return "openai-compatible";
3189
+ return hint;
3190
+ }
3191
+ const url = baseUrl.toLowerCase();
3192
+ if (url.includes("openrouter.ai")) return "openrouter";
3193
+ if (url.includes("api.openai.com")) return "openai";
3194
+ if (url.includes("anthropic.com") || url.includes("api.anthropic.com")) return "anthropic";
3195
+ if (url.includes("generativelanguage.googleapis.com") || url.includes("gemini")) return "gemini";
3196
+ if (url.includes("api.deepseek.com")) return "deepseek";
3197
+ if (url.includes("api.groq.com")) return "groq";
3198
+ if (url.includes("api.mistral.ai")) return "mistral";
3199
+ if (url.includes("api.together.xyz")) return "together";
3200
+ if (url.includes("api.x.ai")) return "xai";
3201
+ if (url.includes("api.minimaxi.com")) return "minimax";
3202
+ if (url.includes("api-ai.gitcode.com")) return "gitcode";
3203
+ if (url.includes("api.z.ai")) return "zai-coding-plan";
3204
+ if (url.includes("open.bigmodel.cn") || url.includes("bigmodel")) return "zhipu";
3205
+ if (url.includes(":11434")) return "ollama";
3206
+ return "openai-compatible";
2439
3207
  }
2440
- function getScenarioRules(runtime) {
2441
- return runtime.scenario_rules || {};
3208
+ function getProviderHeaders(provider) {
3209
+ return PROVIDER_HEADERS[provider] || {};
2442
3210
  }
2443
- function getEnabledScenarioExtensions(runtime, scenarioId) {
2444
- const scenData = runtime.scenario_rules?.[scenarioId];
2445
- if (!scenData) return null;
2446
- const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
2447
- pattern: r.pattern,
2448
- risk_level: r.risk_level || "",
2449
- risk_type: r.risk_type || "",
2450
- reason: r.reason || ""
2451
- }));
2452
- const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
2453
- keyword: r.keyword,
2454
- risk_level: r.risk_level || "",
2455
- lang: r.lang || "en"
2456
- }));
2457
- const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
2458
- path: r.path,
2459
- risk_level: r.risk_level || "",
2460
- desc: r.desc || "",
2461
- credential: r.credential,
2462
- read_only: r.read_only,
2463
- deny_mode: r.deny_mode
2464
- }));
2465
- return {
2466
- agent_id: scenarioId,
2467
- description: "",
2468
- extra_command_patterns: cmdPatterns,
2469
- extra_injection_keywords: injKeywords,
2470
- extra_sensitive_paths: sensitivePaths,
2471
- builtin_safe_skills: [],
2472
- builtin_safe_action_types: [],
2473
- extra_skills: []
2474
- };
3211
+ function isHeuristicEnabled() {
3212
+ if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
3213
+ return layerEnabledOrDefault("L1_heuristic");
2475
3214
  }
2476
- function updateScenarioRuleEnabled(scenarioId, category, ruleFp, enabled) {
2477
- const runtime = loadRuntimeConfig();
2478
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2479
- if (cat) {
2480
- const targetFp = _ruleFingerprint(ruleFp);
2481
- for (const r of cat.rules || []) {
2482
- if (_ruleFingerprint(r) === targetFp) {
2483
- r.enabled = enabled;
2484
- break;
2485
- }
2486
- }
3215
+ function layerEnabledOrDefault(key) {
3216
+ try {
3217
+ return isLayerEnabled(key);
3218
+ } catch {
3219
+ return true;
2487
3220
  }
2488
- saveRuntimeConfig(runtime);
2489
- return runtime;
2490
3221
  }
2491
- function addScenarioRule(scenarioId, category, rule) {
2492
- const runtime = loadRuntimeConfig();
2493
- if (!runtime.scenario_rules) runtime.scenario_rules = {};
2494
- if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
2495
- const cats = runtime.scenario_rules[scenarioId];
2496
- if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
2497
- (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
2498
- saveRuntimeConfig(runtime);
2499
- return runtime;
3222
+ function isBrainEnabled() {
3223
+ return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
2500
3224
  }
2501
- function deleteScenarioRule(scenarioId, category, ruleFp) {
2502
- const runtime = loadRuntimeConfig();
2503
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2504
- if (cat) {
2505
- const targetFp = _ruleFingerprint(ruleFp);
2506
- cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
2507
- }
2508
- saveRuntimeConfig(runtime);
2509
- return runtime;
3225
+ function isLogicRulesEnabled() {
3226
+ if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
3227
+ return layerEnabledOrDefault("L2_logic_rules");
2510
3228
  }
2511
- function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2512
- const runtime = loadRuntimeConfig();
2513
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2514
- if (cat) cat.category_enabled = enabled;
2515
- saveRuntimeConfig(runtime);
2516
- return runtime;
3229
+ function isFormalObserveMode() {
3230
+ return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
2517
3231
  }
2518
- 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;
2519
- var init_runtime_config = __esm({
2520
- "src/infra/runtime-config.ts"() {
3232
+ var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
3233
+ var init_config = __esm({
3234
+ "src/config.ts"() {
2521
3235
  "use strict";
2522
3236
  init_esm_shims();
2523
3237
  init_paths();
2524
- init_heuristic();
2525
- init_logic_rules();
2526
- init_skill_loader();
2527
- __dirname4 = dirname3(fileURLToPath5(import.meta.url));
2528
- ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2529
- RUNTIME_CONFIG_DIR = runtimeDir();
2530
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2531
- LAYER_DISABLE_ENV = {
2532
- L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2533
- L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2534
- L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2535
- };
2536
- DEFAULT_LAYERS = {
2537
- L1_heuristic: true,
2538
- L2_logic_rules: true,
2539
- L3_llm_analysis: true,
2540
- agent_rules_enabled: true,
2541
- scenario_rules_enabled: true
2542
- };
2543
- DENY_MODE_LABELS = {
2544
- deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2545
- deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2546
- deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
3238
+ init_runtime_config();
3239
+ llmConfig = (() => {
3240
+ const configPath = resolveConfigPath();
3241
+ const fileConfig = fs2.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
3242
+ const tr = loadTimeoutRetryFromFile(configPath);
3243
+ timeoutConfig = tr.timeout;
3244
+ retryConfig = tr.retry;
3245
+ return {
3246
+ provider: process.env.AGENT_MOSS_LLM_PROVIDER || fileConfig.provider || "openai",
3247
+ baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || fileConfig.baseUrl || "https://api.openai.com/v1",
3248
+ apiKey: process.env.AGENT_MOSS_LLM_API_KEY || process.env.OPENAI_API_KEY || fileConfig.apiKey || "",
3249
+ model: process.env.AGENT_MOSS_LLM_MODEL || fileConfig.model || "gpt-4o",
3250
+ temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || String(fileConfig.temperature ?? "0.1")),
3251
+ maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || String(fileConfig.maxTokens ?? "4096"), 10),
3252
+ // 优先用 env,否则用 timeout 块的 prompt1Timeout(对齐 py)
3253
+ timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
3254
+ };
3255
+ })();
3256
+ __configModuleDir = (() => {
3257
+ try {
3258
+ return path2.dirname(fileURLToPath5(import.meta.url));
3259
+ } catch {
3260
+ return typeof __dirname !== "undefined" ? __dirname : process.cwd();
3261
+ }
3262
+ })();
3263
+ USER_RULES_PATHS = [
3264
+ path2.join(__configModuleDir, "rules", "user_rules.json"),
3265
+ // 源在 ts/src/rules(一般不存在)
3266
+ path2.join(__configModuleDir, "..", "rules", "user_rules.json"),
3267
+ // dist: dist/engine/../rules = dist/rules ✅
3268
+ path2.join(__configModuleDir, "..", "..", "agent_moss", "rules", "user_rules.json")
3269
+ // dev src: ts/src/../../agent_moss
3270
+ ];
3271
+ customRules = loadCustomRules();
3272
+ PROVIDER_HEADERS = {
3273
+ "openrouter": {
3274
+ "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM",
3275
+ "X-OpenRouter-Title": "AgentMoss"
3276
+ },
3277
+ "xai": {
3278
+ "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM"
3279
+ }
2547
3280
  };
2548
- runtimeConfigCache = null;
2549
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2550
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2551
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2552
- _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2553
- SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2554
3281
  }
2555
3282
  });
2556
3283
 
2557
3284
  // src/agents/index.ts
2558
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
2559
- import { resolve as resolve3, dirname as dirname4 } from "path";
3285
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3286
+ import { resolve as resolve4, dirname as dirname4 } from "path";
2560
3287
  import { fileURLToPath as fileURLToPath6 } from "url";
2561
3288
  function resolveAgentsDir() {
2562
- if (existsSync5(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
2563
- if (existsSync5(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3289
+ if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3290
+ if (existsSync6(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
2564
3291
  return AGENTS_DIR_PROD;
2565
3292
  }
2566
3293
  function getAgentExtensions(agentId) {
@@ -2568,16 +3295,15 @@ function getAgentExtensions(agentId) {
2568
3295
  if (cache.has(agentId)) return cache.get(agentId) ?? null;
2569
3296
  let ext = null;
2570
3297
  try {
2571
- const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledAgentExtensions: getEnabledAgentExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
2572
- if (hasRuntimeConfig2()) {
2573
- ext = getEnabledAgentExtensions2(loadRuntimeConfig2(), agentId);
3298
+ if (hasRuntimeConfig()) {
3299
+ ext = getEnabledAgentExtensions(loadRuntimeConfig(), agentId);
2574
3300
  }
2575
3301
  } catch {
2576
3302
  }
2577
3303
  if (ext === null) {
2578
3304
  const agentsDir = resolveAgentsDir();
2579
- const rulesFile = resolve3(agentsDir, agentId, "rules.json");
2580
- if (!existsSync5(rulesFile)) {
3305
+ const rulesFile = resolve4(agentsDir, agentId, "rules.json");
3306
+ if (!existsSync6(rulesFile)) {
2581
3307
  cache.set(agentId, null);
2582
3308
  return null;
2583
3309
  }
@@ -2596,7 +3322,7 @@ function getAgentExtensions(agentId) {
2596
3322
  }
2597
3323
  }
2598
3324
  cache.set(agentId, ext);
2599
- console.log(
3325
+ console.error(
2600
3326
  `[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`
2601
3327
  );
2602
3328
  return ext;
@@ -2621,21 +3347,22 @@ var init_agents = __esm({
2621
3347
  "src/agents/index.ts"() {
2622
3348
  "use strict";
2623
3349
  init_esm_shims();
3350
+ init_runtime_config();
2624
3351
  __filename4 = fileURLToPath6(import.meta.url);
2625
3352
  __dirname5 = dirname4(__filename4);
2626
- AGENTS_DIR_PROD = resolve3(__dirname5, "agents");
2627
- AGENTS_DIR_DEV_FALLBACK = resolve3(__dirname5, "../../../agent_moss/agents");
3353
+ AGENTS_DIR_PROD = resolve4(__dirname5, "agents");
3354
+ AGENTS_DIR_DEV_FALLBACK = resolve4(__dirname5, "../../../agent_moss/agents");
2628
3355
  cache = /* @__PURE__ */ new Map();
2629
3356
  }
2630
3357
  });
2631
3358
 
2632
3359
  // src/scenarios/index.ts
2633
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
2634
- import { resolve as resolve4, dirname as dirname5 } from "path";
3360
+ import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3361
+ import { resolve as resolve5, dirname as dirname5 } from "path";
2635
3362
  import { fileURLToPath as fileURLToPath7 } from "url";
2636
3363
  function resolveScenariosDir() {
2637
- if (existsSync6(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
2638
- if (existsSync6(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3364
+ if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3365
+ if (existsSync7(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
2639
3366
  return SCENARIOS_DIR_PROD;
2640
3367
  }
2641
3368
  function getScenarioExtensions(scenarioId) {
@@ -2643,16 +3370,15 @@ function getScenarioExtensions(scenarioId) {
2643
3370
  if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
2644
3371
  let ext = null;
2645
3372
  try {
2646
- const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledScenarioExtensions: getEnabledScenarioExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
2647
- if (hasRuntimeConfig2()) {
2648
- ext = getEnabledScenarioExtensions2(loadRuntimeConfig2(), scenarioId);
3373
+ if (hasRuntimeConfig()) {
3374
+ ext = getEnabledScenarioExtensions(loadRuntimeConfig(), scenarioId);
2649
3375
  }
2650
3376
  } catch {
2651
3377
  }
2652
3378
  if (ext === null) {
2653
3379
  const scenariosDir = resolveScenariosDir();
2654
- const rulesFile = resolve4(scenariosDir, scenarioId, "rules.json");
2655
- if (!existsSync6(rulesFile)) {
3380
+ const rulesFile = resolve5(scenariosDir, scenarioId, "rules.json");
3381
+ if (!existsSync7(rulesFile)) {
2656
3382
  cache2.set(scenarioId, null);
2657
3383
  return null;
2658
3384
  }
@@ -2682,7 +3408,7 @@ function getScenarioExtensions(scenarioId) {
2682
3408
  }
2683
3409
  }
2684
3410
  cache2.set(scenarioId, ext);
2685
- console.log(
3411
+ console.error(
2686
3412
  `[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`
2687
3413
  );
2688
3414
  return ext;
@@ -2702,15 +3428,72 @@ var init_scenarios = __esm({
2702
3428
  "use strict";
2703
3429
  init_esm_shims();
2704
3430
  init_agents();
3431
+ init_runtime_config();
2705
3432
  __filename5 = fileURLToPath7(import.meta.url);
2706
3433
  __dirname6 = dirname5(__filename5);
2707
- SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
2708
- SCENARIOS_DIR_DEV_FALLBACK = resolve4(__dirname6, "../../../agent_moss/scenarios");
3434
+ SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
3435
+ SCENARIOS_DIR_DEV_FALLBACK = resolve5(__dirname6, "../../../agent_moss/scenarios");
2709
3436
  cache2 = /* @__PURE__ */ new Map();
2710
3437
  }
2711
3438
  });
2712
3439
 
2713
3440
  // src/engine/heuristic.ts
3441
+ function _effectiveDangerousPatterns(osType) {
3442
+ return runtimeConfigBridge.current?.effectiveDangerousPatterns(osType) ?? getDangerousPatternsForOs(osType);
3443
+ }
3444
+ function _loadDangerousPatterns(osType) {
3445
+ const accepted = acceptedOsValues(osType);
3446
+ const cmds = loadL1().dangerous_commands || [];
3447
+ return cmds.filter((c) => accepted.includes(String(c.os || "linux").toLowerCase())).map((c) => ({
3448
+ pattern: new RegExp(c.pattern, "i"),
3449
+ severity: c.risk_level,
3450
+ riskType: c.risk_type,
3451
+ description: c.reason,
3452
+ // 保留条目的原始平台归属(非查询用的 osType)—— seed 生成据此分组去重
3453
+ os: String(c.os || "linux").toLowerCase()
3454
+ }));
3455
+ }
3456
+ function getDangerousPatternsForOs(osType) {
3457
+ const t = String(osType || "").toLowerCase();
3458
+ if (t === "windows") return DANGEROUS_PATTERNS_WINDOWS;
3459
+ if (t === "macos") return DANGEROUS_PATTERNS_MACOS;
3460
+ if (t === "harmony" || t === "hmos" || t === "openharmony" || t === "hongmeng") return DANGEROUS_PATTERNS_HARMONY;
3461
+ return DANGEROUS_PATTERNS2;
3462
+ }
3463
+ function normalizeShellObfuscation(text) {
3464
+ let t = text.replace(/(?<=\w)(?:''|"")(?=\w)/g, "");
3465
+ t = t.replace(/\$\{IFS\}|\$IFS/gi, " ");
3466
+ return t;
3467
+ }
3468
+ function scanDangerousCommandPatterns(actionType, actionDetail, osType) {
3469
+ const scanDetail = normalizePathSegments(stripLiteralTextSegments(actionDetail));
3470
+ const scanCommands = String(osType || "").toLowerCase() === "windows" ? [
3471
+ `${actionType}: ${scanDetail}`,
3472
+ scanDetail,
3473
+ `${actionType}: ${scanDetail.replace(/\//g, "\\")}`,
3474
+ scanDetail.replace(/\//g, "\\")
3475
+ ] : [`${actionType}: ${scanDetail}`, scanDetail];
3476
+ const deob = normalizeShellObfuscation(scanDetail);
3477
+ if (deob !== scanDetail) scanCommands.push(`${actionType}: ${deob}`, deob);
3478
+ let top = null;
3479
+ for (const rule of _effectiveDangerousPatterns(osType)) {
3480
+ if (scanCommands.some((c) => rule.pattern.test(c))) {
3481
+ if (!top || severityRank(rule.severity) > severityRank(top.sev)) {
3482
+ top = { sev: rule.severity, type: rule.riskType, desc: rule.description };
3483
+ }
3484
+ }
3485
+ }
3486
+ if (!top) return null;
3487
+ return {
3488
+ allowed: false,
3489
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${top.desc}`,
3490
+ riskLevel: top.sev,
3491
+ riskType: top.type,
3492
+ confidence: 95,
3493
+ source: "heuristic",
3494
+ violatedLayers: ["L1.1"]
3495
+ };
3496
+ }
2714
3497
  function runHeuristicCheck(input) {
2715
3498
  const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
2716
3499
  const matched = [];
@@ -2737,11 +3520,23 @@ function runHeuristicCheck(input) {
2737
3520
  }
2738
3521
  }
2739
3522
  const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
2740
- const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
3523
+ const scanCommands = String(input.osType || "").toLowerCase() === "windows" ? [
3524
+ `${input.aNext.actionType}: ${scanDetail}`,
3525
+ scanDetail,
3526
+ `${input.aNext.actionType}: ${scanDetail.replace(/\//g, "\\")}`,
3527
+ scanDetail.replace(/\//g, "\\")
3528
+ ] : [`${input.aNext.actionType}: ${scanDetail}`, scanDetail];
3529
+ const deobfuscated = normalizeShellObfuscation(scanDetail);
3530
+ if (deobfuscated !== scanDetail) {
3531
+ scanCommands.push(
3532
+ `${input.aNext.actionType}: ${deobfuscated}`,
3533
+ deobfuscated
3534
+ );
3535
+ }
2741
3536
  let topRiskType = "";
2742
3537
  const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
2743
- for (const rule of DANGEROUS_PATTERNS) {
2744
- if (rule.pattern.test(scanCommand)) {
3538
+ for (const rule of _effectiveDangerousPatterns(input.osType)) {
3539
+ if (scanCommands.some((c) => rule.pattern.test(c))) {
2745
3540
  if (exemptKeygen && rule.riskType === "file_access") continue;
2746
3541
  matched.push(rule.description);
2747
3542
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
@@ -2759,7 +3554,7 @@ function runHeuristicCheck(input) {
2759
3554
  for (const rule of agentExt.extra_command_patterns) {
2760
3555
  try {
2761
3556
  const re = new RegExp(rule.pattern, "i");
2762
- if (re.test(scanCommand)) {
3557
+ if (scanCommands.some((c) => re.test(c))) {
2763
3558
  matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
2764
3559
  if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
2765
3560
  highestSeverity = rule.risk_level;
@@ -2771,7 +3566,7 @@ function runHeuristicCheck(input) {
2771
3566
  }
2772
3567
  }
2773
3568
  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();
2774
- const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
3569
+ const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords, input.osType);
2775
3570
  if (injectionHits.length > 0) {
2776
3571
  const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
2777
3572
  matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
@@ -2784,7 +3579,7 @@ function runHeuristicCheck(input) {
2784
3579
  const detailL = input.aNext.actionDetail.toLowerCase();
2785
3580
  const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
2786
3581
  const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
2787
- const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
3582
+ const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt, input.osType);
2788
3583
  const homeResult = checkHomeDeletion(actionTypeL, detailL);
2789
3584
  const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
2790
3585
  if (mergeResults.length > 0) {
@@ -2820,9 +3615,10 @@ function runHeuristicCheck(input) {
2820
3615
  if (lateralResult) return lateralResult;
2821
3616
  return null;
2822
3617
  }
2823
- function checkInjectionKeywords(text, extraKeywords) {
3618
+ function checkInjectionKeywords(text, extraKeywords, osType) {
2824
3619
  const hits = [];
2825
- for (const entry of INJECTION_KEYWORDS) {
3620
+ const baseKeywords = runtimeConfigBridge.current?.effectiveInjectionKeywords(osType) ?? INJECTION_KEYWORDS;
3621
+ for (const entry of baseKeywords) {
2826
3622
  if (text.includes(entry.keyword.toLowerCase())) {
2827
3623
  hits.push(entry);
2828
3624
  }
@@ -2882,7 +3678,7 @@ function buildPathContext(actionDetail) {
2882
3678
  if (!PATH_CONTEXT_TRIGGER_RE.test(actionDetail)) return "";
2883
3679
  const stripped = stripLiteralTextSegments(actionDetail);
2884
3680
  const lines = [];
2885
- for (const seg of splitCommandSegments(stripped)) {
3681
+ for (const seg of splitCommandSegments2(stripped)) {
2886
3682
  for (const [raw, lex] of resolvePathTokens(seg)) {
2887
3683
  lines.push(`- \u547D\u4EE4\u6BB5 \`${seg.trim().slice(0, 60)}\`\uFF1A\`${raw}\` \u8BCD\u6CD5\u89E3\u6790\u4E3A \`${lex}\``);
2888
3684
  }
@@ -2892,12 +3688,12 @@ function buildPathContext(actionDetail) {
2892
3688
  }
2893
3689
  function stripLiteralTextSegments(actionDetail) {
2894
3690
  const writeScriptExec = />\s*[/~]\S*\.(sh|py|pl|rb)\b/.test(actionDetail) && /(?:&&|\|\||;|\n|\|)\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)/.test(actionDetail);
2895
- const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)\b/.test(actionDetail);
3691
+ const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node|crontab|at)\b/.test(actionDetail);
2896
3692
  if (writeScriptExec || pipeToInterpreter) {
2897
3693
  return actionDetail;
2898
3694
  }
2899
3695
  const resultParts = [];
2900
- for (const seg of splitCommandSegments(actionDetail)) {
3696
+ for (const seg of splitCommandSegments2(actionDetail)) {
2901
3697
  const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
2902
3698
  if (fw === "echo" || fw === "printf") {
2903
3699
  const segUnquotedRedirect = seg.replace(
@@ -2917,7 +3713,7 @@ function stripHeredocContent(actionDetail) {
2917
3713
  "<<heredoc>>\n"
2918
3714
  );
2919
3715
  }
2920
- function normalizePathSegments(text) {
3716
+ function normalizePathSegments(text, foldQuotes = false) {
2921
3717
  const protTokens = [];
2922
3718
  let t = text.replace(/\b(https?|ftp|file):\/\//gi, (m) => {
2923
3719
  const scheme = m.slice(0, m.indexOf(":"));
@@ -2925,10 +3721,12 @@ function normalizePathSegments(text) {
2925
3721
  return `${scheme}__PROT${protTokens.length - 1}__`;
2926
3722
  });
2927
3723
  const quoteTokens = [];
2928
- t = t.replace(/'[^']*'|"[^"]*"/g, (m) => {
2929
- quoteTokens.push(m);
2930
- return `__Q${quoteTokens.length - 1}__`;
2931
- });
3724
+ if (!foldQuotes) {
3725
+ t = t.replace(/'[^']*'|"[^"]*"/g, (m) => {
3726
+ quoteTokens.push(m);
3727
+ return `__Q${quoteTokens.length - 1}__`;
3728
+ });
3729
+ }
2932
3730
  t = t.replace(/\/{2,}/g, "/");
2933
3731
  t = t.replace(/\/\.\//g, "/");
2934
3732
  for (let i = 0; i < quoteTokens.length; i++) {
@@ -2939,7 +3737,12 @@ function normalizePathSegments(text) {
2939
3737
  }
2940
3738
  return t;
2941
3739
  }
2942
- function splitCommandSegments(actionDetail) {
3740
+ function pathScanVariants(actionDetailLower) {
3741
+ const base = stripLiteralTextSegments(actionDetailLower);
3742
+ const deep = normalizePathSegments(base, true);
3743
+ return deep === base ? [base] : [base, deep];
3744
+ }
3745
+ function splitCommandSegments2(actionDetail) {
2943
3746
  const segments = [];
2944
3747
  let cur = "";
2945
3748
  let quote = null;
@@ -2988,11 +3791,15 @@ function isSearchActionType(actionType) {
2988
3791
  const at = actionType.toLowerCase();
2989
3792
  return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || ["websearch", "web_search", "search", "query"].includes(at);
2990
3793
  }
2991
- function checkSensitivePath(command, cwd, agentExt) {
3794
+ function checkSensitivePath(command, cwd, agentExt, osType) {
2992
3795
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
2993
- const isWriteOpFlag = isWriteOperation("", command);
3796
+ const { root: rootPaths, home: homePaths, credential: credentialPaths } = getSensitivePathsForOs(osType);
3797
+ const isWriteOpFlag = isWriteOperation("", command, void 0, osType);
2994
3798
  const keygenGenerate = isSshKeygenGenerate(command);
2995
- const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
3799
+ const pathScanTexts = pathScanVariants(command);
3800
+ if (String(osType || "").toLowerCase() === "windows") {
3801
+ pathScanTexts.push(...pathScanTexts.map((t) => t.replace(/\//g, "\\")));
3802
+ }
2996
3803
  const pathMatches = (text, path6) => {
2997
3804
  const parts = path6.split(/(\(\?!.*?\))/);
2998
3805
  const escaped = parts.map((part) => {
@@ -3002,8 +3809,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3002
3809
  const re = path6.startsWith(".") || path6.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
3003
3810
  return re.test(text);
3004
3811
  };
3005
- for (const sp of ROOT_SENSITIVE_PATHS) {
3006
- if (pathMatches(pathScanText, sp.path.toLowerCase())) {
3812
+ const pathMatchesAny = (path6) => pathScanTexts.some((t) => pathMatches(t, path6));
3813
+ for (const sp of rootPaths) {
3814
+ if (pathMatchesAny(sp.path.toLowerCase())) {
3007
3815
  if (keygenGenerate) continue;
3008
3816
  if (sp.path === "/dev/random") {
3009
3817
  if (isWriteOpFlag) {
@@ -3019,9 +3827,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3019
3827
  violatedLayers: ["L1.1"]
3020
3828
  };
3021
3829
  }
3022
- if (!isWriteOpFlag && !sp.credential) {
3023
- continue;
3024
- }
3830
+ const denyMode = sp.denyMode || (sp.credential ? "deny_both" : sp.readOnly ? "deny_read" : "deny_write");
3831
+ if (denyMode === "deny_write" && !isWriteOpFlag) continue;
3832
+ if (denyMode === "deny_read" && isWriteOpFlag) continue;
3025
3833
  return {
3026
3834
  allowed: false,
3027
3835
  reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
@@ -3034,17 +3842,19 @@ function checkSensitivePath(command, cwd, agentExt) {
3034
3842
  };
3035
3843
  }
3036
3844
  }
3037
- for (const sp of HOME_SENSITIVE_PATHS) {
3845
+ for (const sp of homePaths) {
3038
3846
  const expanded = `${expandedCwd}/${sp.path}`;
3039
3847
  let pPath = sp.path;
3040
3848
  if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
3041
3849
  let dirPrefixMatch = null;
3042
3850
  if (sp.dirDelOnly) {
3043
3851
  const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
3044
- dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
3852
+ dirPrefixMatch = pathScanTexts.some(
3853
+ (t) => dirRe(sp.path).test(t) || dirRe(expanded.toLowerCase()).test(t)
3854
+ );
3045
3855
  }
3046
- const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
3047
- const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
3856
+ const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatchesAny(pPath);
3857
+ const expandedReMatch = sp.dirDelOnly ? false : pathMatchesAny(expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
3048
3858
  if (pathReMatch || expandedReMatch) {
3049
3859
  if (keygenGenerate) continue;
3050
3860
  if (sp.dirDelOnly) {
@@ -3074,9 +3884,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3074
3884
  }
3075
3885
  continue;
3076
3886
  }
3077
- if (!isWriteOpFlag && !sp.credential) {
3078
- continue;
3079
- }
3887
+ const denyMode = sp.denyMode || (sp.credential ? "deny_both" : sp.readOnly ? "deny_read" : "deny_write");
3888
+ if (denyMode === "deny_write" && !isWriteOpFlag) continue;
3889
+ if (denyMode === "deny_read" && isWriteOpFlag) continue;
3080
3890
  return {
3081
3891
  allowed: false,
3082
3892
  reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
@@ -3113,7 +3923,7 @@ function checkSensitivePath(command, cwd, agentExt) {
3113
3923
  }
3114
3924
  }
3115
3925
  }
3116
- for (const cp of CREDENTIAL_PATHS) {
3926
+ for (const cp of credentialPaths) {
3117
3927
  const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3118
3928
  const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
3119
3929
  if (re.test(command)) {
@@ -3233,7 +4043,7 @@ function isDeleteActionForDir(command, dirPath) {
3233
4043
  }
3234
4044
  for (const seg of low.split(/&&|\|\||;|\n/)) {
3235
4045
  const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
3236
- if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
4046
+ if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && seg.includes(dpath)) {
3237
4047
  return true;
3238
4048
  }
3239
4049
  }
@@ -3416,7 +4226,7 @@ function checkLateralMovement(actionDetail) {
3416
4226
  }
3417
4227
  return null;
3418
4228
  }
3419
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
4229
+ 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;
3420
4230
  var init_heuristic = __esm({
3421
4231
  "src/engine/heuristic.ts"() {
3422
4232
  "use strict";
@@ -3425,6 +4235,7 @@ var init_heuristic = __esm({
3425
4235
  init_agents();
3426
4236
  init_scenarios();
3427
4237
  init_runtime_config();
4238
+ init_runtime_config_bridge();
3428
4239
  init_rules_loader();
3429
4240
  init_logic_rules();
3430
4241
  INJECTION_KEYWORDS = (() => {
@@ -3435,15 +4246,10 @@ var init_heuristic = __esm({
3435
4246
  lang: k.lang || "en"
3436
4247
  }));
3437
4248
  })();
3438
- DANGEROUS_PATTERNS = (() => {
3439
- const cmds = loadL1().dangerous_commands || [];
3440
- return cmds.map((c) => ({
3441
- pattern: new RegExp(c.pattern, "i"),
3442
- severity: c.risk_level,
3443
- riskType: c.risk_type,
3444
- description: c.reason
3445
- }));
3446
- })();
4249
+ DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
4250
+ DANGEROUS_PATTERNS_WINDOWS = _loadDangerousPatterns("windows");
4251
+ DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4252
+ DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
3447
4253
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
3448
4254
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
3449
4255
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
@@ -3454,20 +4260,21 @@ var init_heuristic = __esm({
3454
4260
  });
3455
4261
 
3456
4262
  // src/engine/template-loader.ts
3457
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
3458
- import { resolve as resolve5, dirname as dirname6 } from "path";
4263
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4264
+ import { resolve as resolve6, dirname as dirname6 } from "path";
3459
4265
  import { fileURLToPath as fileURLToPath8 } from "url";
3460
4266
  function loadTemplates() {
3461
4267
  if (cachedTemplates) return cachedTemplates;
3462
4268
  cachedTemplates = {};
3463
- const dir = existsSync7(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
3464
- if (!existsSync7(dir)) {
3465
- console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
4269
+ const dir = resolveAssetDir(__dirname7, "templates");
4270
+ if (!dir) {
4271
+ const [primary, fallback] = assetDirCandidates(__dirname7, "templates");
4272
+ console.warn("[AgentMoss] Templates directory not found:", primary, "and fallback:", fallback);
3466
4273
  return cachedTemplates;
3467
4274
  }
3468
4275
  for (const file of TEMPLATE_FILES) {
3469
- const filePath = resolve5(dir, file);
3470
- if (existsSync7(filePath)) {
4276
+ const filePath = resolve6(dir, file);
4277
+ if (existsSync8(filePath)) {
3471
4278
  const key = file.replace(".txt", "").replace("_template", "");
3472
4279
  cachedTemplates[key] = readFileSync7(filePath, "utf-8");
3473
4280
  }
@@ -3475,15 +4282,14 @@ function loadTemplates() {
3475
4282
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
3476
4283
  return cachedTemplates;
3477
4284
  }
3478
- var __filename6, __dirname7, TEMPLATES_DIR, TEMPLATES_DIR_FALLBACK, TEMPLATE_FILES, cachedTemplates;
4285
+ var __filename6, __dirname7, TEMPLATE_FILES, cachedTemplates;
3479
4286
  var init_template_loader = __esm({
3480
4287
  "src/engine/template-loader.ts"() {
3481
4288
  "use strict";
3482
4289
  init_esm_shims();
4290
+ init_asset_dir();
3483
4291
  __filename6 = fileURLToPath8(import.meta.url);
3484
4292
  __dirname7 = dirname6(__filename6);
3485
- TEMPLATES_DIR = resolve5(__dirname7, "../templates");
3486
- TEMPLATES_DIR_FALLBACK = resolve5(__dirname7, "../../../agent_moss/templates");
3487
4293
  TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
3488
4294
  cachedTemplates = null;
3489
4295
  }
@@ -3491,7 +4297,7 @@ var init_template_loader = __esm({
3491
4297
 
3492
4298
  // src/engine/script-analyzer.ts
3493
4299
  import { readFileSync as readFileSync8 } from "fs";
3494
- import { resolve as resolve6, join as join4 } from "path";
4300
+ import { resolve as resolve7, join as join4 } from "path";
3495
4301
  function emptyResult() {
3496
4302
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
3497
4303
  }
@@ -3544,7 +4350,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3544
4350
  }
3545
4351
  function readScriptContent(scriptPath, maxLines = 500) {
3546
4352
  try {
3547
- const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
4353
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
3548
4354
  const raw = readFileSync8(expandedPath, "utf-8");
3549
4355
  const lines = raw.split("\n").slice(0, maxLines);
3550
4356
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -4494,9 +5300,20 @@ function evalExpr2(text, env) {
4494
5300
  const result = [];
4495
5301
  for (const p of parts) {
4496
5302
  if (!p) continue;
4497
- const brace = p.match(/^\$\{([^}]+)\}$/);
5303
+ const brace = p.match(/^\$\{([^}]*)\}$/);
4498
5304
  if (brace) {
4499
5305
  const inner = brace[1];
5306
+ const dm = inner.match(/^([A-Za-z_][A-Za-z0-9_]*|\d+):?-(.*)$/s);
5307
+ if (dm) {
5308
+ const key = /^\d+$/.test(dm[1]) ? `$${dm[1]}` : dm[1];
5309
+ const bound = env.get(key);
5310
+ if (bound && !(bound.kind === "concrete" && !bound.value)) {
5311
+ result.push(bound);
5312
+ } else {
5313
+ result.push(evalExpr2(dm[2], env));
5314
+ }
5315
+ continue;
5316
+ }
4500
5317
  if (/^\d+$/.test(inner)) {
4501
5318
  const v = env.get(`$${inner}`);
4502
5319
  result.push(v || svalSym(`$${inner}`));
@@ -4712,7 +5529,7 @@ var init_symbolic = __esm({
4712
5529
  LOOP_DONE_RE = /\bdone\b/g;
4713
5530
  FN_DEF_RE = /^\s*(?:function\s+)?[A-Za-z_][A-Za-z0-9_]*\s*\(\s*\)\s*\{/;
4714
5531
  FN_DEF_KW_RE = /^\s*function\s+[A-Za-z_][A-Za-z0-9_]*\s*\{?/;
4715
- EXPR_SPLIT_RE = /(\$\{?\d+\}?|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)/;
5532
+ EXPR_SPLIT_RE = /(\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$\d+)/;
4716
5533
  HAS_GLOB = /[*?[]/;
4717
5534
  }
4718
5535
  });
@@ -4993,6 +5810,14 @@ var init_backends = __esm({
4993
5810
  import * as fs3 from "fs";
4994
5811
  import * as path3 from "path";
4995
5812
  import * as os from "os";
5813
+ function maxFileSizeFromEnv() {
5814
+ const raw = (process.env.AGENT_MOSS_SCRIPT_MAX_FILE_SIZE || "").trim();
5815
+ if (raw) {
5816
+ const v = Number(raw);
5817
+ if (Number.isFinite(v) && v > 0) return v;
5818
+ }
5819
+ return 2 * 1024 * 1024;
5820
+ }
4996
5821
  function pickBackend(pathStr) {
4997
5822
  const lower = pathStr.toLowerCase();
4998
5823
  for (const ext of Object.keys(BACKENDS)) {
@@ -5035,6 +5860,7 @@ function emptyNode(pathStr, depth) {
5035
5860
  depth,
5036
5861
  childPaths: [],
5037
5862
  error: "",
5863
+ unanalyzable: false,
5038
5864
  l1Hit: false,
5039
5865
  l1RiskLevel: "",
5040
5866
  l1RiskType: "",
@@ -5060,7 +5886,7 @@ var init_tracker = __esm({
5060
5886
  init_backends();
5061
5887
  DEFAULT_MAX_DEPTH = 14;
5062
5888
  DEFAULT_MAX_NODES = 64;
5063
- DEFAULT_MAX_FILE_SIZE = 2 * 1024 * 1024;
5889
+ DEFAULT_MAX_FILE_SIZE = maxFileSizeFromEnv();
5064
5890
  DEFAULT_MAX_TOTAL_SIZE = 8 * 1024 * 1024;
5065
5891
  BACKENDS = {
5066
5892
  ".py": new PythonBackend()
@@ -5131,6 +5957,7 @@ var init_tracker = __esm({
5131
5957
  node2.error = error;
5132
5958
  node2.fileSize = fileSize;
5133
5959
  node2.lineCount = lineCount;
5960
+ node2.unanalyzable = error.startsWith("\u811A\u672C\u6587\u4EF6\u8FC7\u5927");
5134
5961
  this.result.nodes.push(node2);
5135
5962
  this.visiting.delete(pathStr);
5136
5963
  return;
@@ -5177,9 +6004,9 @@ var init_tracker = __esm({
5177
6004
  });
5178
6005
 
5179
6006
  // src/engine/script-content-analyzer.ts
5180
- import { statSync as statSync3, existsSync as existsSync9 } from "fs";
5181
- import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
5182
- import { homedir as homedir4 } from "os";
6007
+ import { statSync as statSync3, existsSync as existsSync10 } from "fs";
6008
+ import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
6009
+ import { homedir as homedir3 } from "os";
5183
6010
  function emptyChain() {
5184
6011
  return {
5185
6012
  nodes: [],
@@ -5285,12 +6112,12 @@ function extractInlineScriptPath(actionDetail) {
5285
6112
  const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
5286
6113
  while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
5287
6114
  for (const cand of candidates) {
5288
- const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
6115
+ const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : shlexSplit(cand);
5289
6116
  for (const tok of tokens) {
5290
6117
  if (looksLikeScriptPath(tok)) {
5291
6118
  try {
5292
6119
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
5293
- if (existsSync9(resolved)) {
6120
+ if (existsSync10(resolved)) {
5294
6121
  const stat = statSync3(resolved);
5295
6122
  if (stat.isFile()) return resolved;
5296
6123
  }
@@ -5304,7 +6131,7 @@ function extractInlineScriptPath(actionDetail) {
5304
6131
  const p = m[1];
5305
6132
  try {
5306
6133
  const resolved = resolvePath2(p);
5307
- if (existsSync9(resolved)) {
6134
+ if (existsSync10(resolved)) {
5308
6135
  const stat = statSync3(resolved);
5309
6136
  if (stat.isFile()) return resolved;
5310
6137
  }
@@ -5320,7 +6147,7 @@ function extractInlineScriptPath(actionDetail) {
5320
6147
  let resolved;
5321
6148
  try {
5322
6149
  resolved = resolvePath2(p);
5323
- if (existsSync9(resolved)) {
6150
+ if (existsSync10(resolved)) {
5324
6151
  const stat = statSync3(resolved);
5325
6152
  if (stat.isFile()) return resolved;
5326
6153
  }
@@ -5329,12 +6156,41 @@ function extractInlineScriptPath(actionDetail) {
5329
6156
  }
5330
6157
  return null;
5331
6158
  }
6159
+ function shlexSplit(s) {
6160
+ const out = [];
6161
+ let cur = "";
6162
+ let quote = null;
6163
+ let quoted = false;
6164
+ for (let i = 0; i < s.length; i++) {
6165
+ const ch = s[i];
6166
+ if (quote) {
6167
+ if (ch === quote) quote = null;
6168
+ else if (ch === "\\" && quote === '"' && i + 1 < s.length) cur += s[++i];
6169
+ else cur += ch;
6170
+ } else if (ch === '"' || ch === "'") {
6171
+ quote = ch;
6172
+ quoted = true;
6173
+ } else if (ch === "\\" && i + 1 < s.length) {
6174
+ cur += s[++i];
6175
+ } else if (/\s/.test(ch)) {
6176
+ if (cur || quoted) {
6177
+ out.push(cur);
6178
+ cur = "";
6179
+ quoted = false;
6180
+ }
6181
+ } else {
6182
+ cur += ch;
6183
+ }
6184
+ }
6185
+ if (cur || quoted) out.push(cur);
6186
+ return out;
6187
+ }
5332
6188
  function resolvePath2(p) {
5333
6189
  let expanded = p;
5334
6190
  if (expanded.startsWith("~/") || expanded === "~") {
5335
- expanded = join6(homedir4(), expanded.slice(1));
6191
+ expanded = join6(homedir3(), expanded.slice(1));
5336
6192
  }
5337
- return resolve8(expanded);
6193
+ return resolve9(expanded);
5338
6194
  }
5339
6195
  function scanScriptChainWithL1(chain, input) {
5340
6196
  for (const node of chain.nodes) {
@@ -5343,7 +6199,7 @@ function scanScriptChainWithL1(chain, input) {
5343
6199
  ...input,
5344
6200
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
5345
6201
  };
5346
- const l1 = runHeuristicCheck(virtualInput);
6202
+ const l1 = scanDangerousCommandPatterns("bash", node.preprocessedContent, input.osType);
5347
6203
  if (l1 && !l1.allowed) {
5348
6204
  node.l1Hit = true;
5349
6205
  node.l1RiskLevel = l1.riskLevel;
@@ -5366,18 +6222,25 @@ function scanScriptChainWithL2(chain, input) {
5366
6222
  ...input,
5367
6223
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
5368
6224
  };
5369
- const l2 = runLogicCheck(virtualInput);
5370
- if (l2 && !l2.allowed) {
5371
- node.l2Hit = true;
5372
- node.l2ViolatedRule = `script_logic:${node.scriptPath}`;
5373
- node.l2RiskLevel = l2.riskLevel;
5374
- node.l2RiskType = l2.riskType;
5375
- node.l2Reason = `\u811A\u672C ${node.scriptPath} (\u6DF1\u5EA6 ${node.depth}) \u4E2D: ${l2.reason}`;
5376
- if (l2.riskLevel === "critical") {
5377
- chain.hasCritical = true;
5378
- chain.firstHitNode = node;
5379
- break;
5380
- }
6225
+ let l2 = null;
6226
+ if (isLogicRulesEnabled()) {
6227
+ l2 = checkSensitivePath(
6228
+ node.preprocessedContent.toLowerCase(),
6229
+ input.cwd || "/",
6230
+ null,
6231
+ input.osType
6232
+ ) || checkIntentConsistency(virtualInput);
6233
+ }
6234
+ if (!l2 || l2.allowed) continue;
6235
+ node.l2Hit = true;
6236
+ node.l2ViolatedRule = `script_logic:${node.scriptPath}`;
6237
+ node.l2RiskLevel = l2.riskLevel;
6238
+ node.l2RiskType = l2.riskType;
6239
+ node.l2Reason = `\u811A\u672C ${node.scriptPath} (\u6DF1\u5EA6 ${node.depth}) \u4E2D: ${l2.reason}`;
6240
+ if (l2.riskLevel === "critical") {
6241
+ chain.hasCritical = true;
6242
+ chain.firstHitNode = node;
6243
+ break;
5381
6244
  }
5382
6245
  }
5383
6246
  if (!chain.hasHigh) {
@@ -5416,13 +6279,16 @@ function analyzeScriptContent2(input) {
5416
6279
  resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
5417
6280
  }
5418
6281
  const l1Adapter = (content) => {
5419
- const virtualInput = {
5420
- ...input,
5421
- aNext: { actionType: "bash", actionDetail: content }
5422
- };
5423
- const j = runHeuristicCheck(virtualInput);
6282
+ const j = scanDangerousCommandPatterns("bash", content, input.osType);
5424
6283
  if (j && !j.allowed) {
5425
- return { hit: true, riskLevel: j.riskLevel, riskType: j.riskType, reason: j.reason, matchedPatterns: [] };
6284
+ return {
6285
+ hit: true,
6286
+ riskLevel: j.riskLevel,
6287
+ riskType: j.riskType,
6288
+ reason: j.reason,
6289
+ matchedPatterns: []
6290
+ // SecurityJudgment 不带模式列表(见 types.ts);留空即可
6291
+ };
5426
6292
  }
5427
6293
  return { hit: false, riskLevel: "", riskType: "", reason: "", matchedPatterns: [] };
5428
6294
  };
@@ -5501,6 +6367,7 @@ var init_script_content_analyzer = __esm({
5501
6367
  "use strict";
5502
6368
  init_esm_shims();
5503
6369
  init_heuristic();
6370
+ init_config();
5504
6371
  init_logic_rules();
5505
6372
  init_inline_analyzer();
5506
6373
  init_tracker();
@@ -5599,14 +6466,14 @@ __export(token_stats_exports, {
5599
6466
  recordTokenUsage: () => recordTokenUsage,
5600
6467
  resetTokenStats: () => resetTokenStats
5601
6468
  });
5602
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
6469
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
5603
6470
  function getStatsPath() {
5604
6471
  const envPath = process.env[ENV_STATS_PATH];
5605
6472
  if (envPath) return envPath;
5606
6473
  return getTokenStatsPath();
5607
6474
  }
5608
6475
  function loadAllRecords(statsPath) {
5609
- if (!existsSync10(statsPath)) return [];
6476
+ if (!existsSync11(statsPath)) return [];
5610
6477
  try {
5611
6478
  const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
5612
6479
  if (Array.isArray(data)) return data;
@@ -5619,7 +6486,7 @@ function loadAllRecords(statsPath) {
5619
6486
  function saveRecords(statsPath, records) {
5620
6487
  try {
5621
6488
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
5622
- if (dir && !existsSync10(dir)) mkdirSync2(dir, { recursive: true });
6489
+ if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
5623
6490
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
5624
6491
  } catch (e) {
5625
6492
  }
@@ -5746,7 +6613,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
5746
6613
  }
5747
6614
  function resetTokenStats() {
5748
6615
  const statsPath = getStatsPath();
5749
- if (existsSync10(statsPath)) {
6616
+ if (existsSync11(statsPath)) {
5750
6617
  try {
5751
6618
  unlinkSync2(statsPath);
5752
6619
  } catch {
@@ -5976,7 +6843,7 @@ async function analyzeWithLLM(prompt, config) {
5976
6843
  }
5977
6844
  if (attempt < maxRetries) {
5978
6845
  console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
5979
- await new Promise((resolve9) => setTimeout(resolve9, retryCfg.retryInterval * 1e3));
6846
+ await new Promise((resolve10) => setTimeout(resolve10, retryCfg.retryInterval * 1e3));
5980
6847
  } else {
5981
6848
  clearTimeout(totalTimeoutTimer);
5982
6849
  console.error("[AgentMoss] LLM call failed after all retries:", err.message);
@@ -6003,9 +6870,9 @@ var init_llm_analyzer = __esm({
6003
6870
  });
6004
6871
 
6005
6872
  // src/brain/rule-store.ts
6006
- import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
6873
+ import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
6007
6874
  import { join as join7 } from "path";
6008
- import { homedir as homedir5 } from "os";
6875
+ import { homedir as homedir4 } from "os";
6009
6876
  function getRulesPath() {
6010
6877
  const envPath = process.env[ENV_RULES_PATH];
6011
6878
  if (envPath) return envPath;
@@ -6023,7 +6890,7 @@ var init_rule_store = __esm({
6023
6890
  "src/brain/rule-store.ts"() {
6024
6891
  "use strict";
6025
6892
  init_esm_shims();
6026
- DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
6893
+ DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
6027
6894
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
6028
6895
  STATUS_DRAFT = "draft";
6029
6896
  STATUS_APPROVED = "approved";
@@ -6040,7 +6907,7 @@ var init_rule_store = __esm({
6040
6907
  this.data = this.load();
6041
6908
  }
6042
6909
  load() {
6043
- if (!existsSync11(this.path)) {
6910
+ if (!existsSync12(this.path)) {
6044
6911
  return { version: 1, rules: [], pattern_candidates: [] };
6045
6912
  }
6046
6913
  try {
@@ -8055,18 +8922,21 @@ var coordinator_exports = {};
8055
8922
  __export(coordinator_exports, {
8056
8923
  TRUSTED_SKILLS_BASE: () => TRUSTED_SKILLS_BASE,
8057
8924
  _resetBrainForTest: () => _resetBrainForTest,
8058
- runAnalysis: () => runAnalysis
8925
+ extractFirstCommand: () => extractFirstCommand,
8926
+ matchesAny: () => matchesAny,
8927
+ runAnalysis: () => runAnalysis,
8928
+ splitCommandSegments: () => splitCommandSegments,
8929
+ whitelistTier: () => whitelistTier
8059
8930
  });
8060
- function extractFirstCommand(command) {
8061
- let main2 = command.split("|")[0].trim();
8062
- for (const sep of ["&&", "||"]) {
8063
- const idx = main2.indexOf(sep);
8064
- if (idx !== -1) {
8065
- main2 = main2.substring(0, idx).trim();
8066
- break;
8067
- }
8068
- }
8069
- return main2;
8931
+ function whitelistTier(command, fullySafe, readonlySensitive) {
8932
+ const segments = splitCommandSegments(command);
8933
+ if (segments.length === 0) return "";
8934
+ if (segments.some((seg) => hasCommandSubstitution(seg))) return "";
8935
+ const hitFull = (seg) => matchesAny(seg, fullySafe);
8936
+ const hitAny = (seg) => hitFull(seg) || matchesAny(seg, readonlySensitive);
8937
+ if (segments.every(hitFull)) return "full";
8938
+ if (segments.every(hitAny)) return "readonly";
8939
+ return "";
8070
8940
  }
8071
8941
  function extractRawCommand(detail) {
8072
8942
  const m = detail.match(/command=([^,]+)/);
@@ -8097,7 +8967,7 @@ async function runAnalysis(input) {
8097
8967
  if (heuristicResult && !heuristicResult.allowed) {
8098
8968
  const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && heuristicResult.riskType === "file_access";
8099
8969
  if (!skipInlineFileAccess) {
8100
- const shouldDenyL1 = heuristicResult.riskLevel === "high" || heuristicResult.riskLevel === "critical" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "file_access";
8970
+ const shouldDenyL1 = heuristicResult.riskLevel === "high" || heuristicResult.riskLevel === "critical" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "file_access" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "resource_exhaustion";
8101
8971
  if (shouldDenyL1) {
8102
8972
  return heuristicResult;
8103
8973
  }
@@ -8107,6 +8977,19 @@ async function runAnalysis(input) {
8107
8977
  if (input.aNext.actionType.toLowerCase() === "bash") {
8108
8978
  scriptChain = analyzeScriptContent2(input);
8109
8979
  if (scriptChain.nodes.length > 0) {
8980
+ for (const node of scriptChain.nodes) {
8981
+ if (node.unanalyzable) {
8982
+ return {
8983
+ allowed: false,
8984
+ 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`,
8985
+ riskLevel: "high",
8986
+ riskType: "script_execution",
8987
+ confidence: 80,
8988
+ source: "heuristic_script_scan",
8989
+ violatedLayers: ["L1.2"]
8990
+ };
8991
+ }
8992
+ }
8110
8993
  for (const node of scriptChain.nodes) {
8111
8994
  if (node.l1Hit && (node.l1RiskLevel === "high" || node.l1RiskLevel === "critical")) {
8112
8995
  return {
@@ -8194,11 +9077,15 @@ async function runAnalysis(input) {
8194
9077
  let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
8195
9078
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
8196
9079
  const firstCmd = extractFirstCommand(lowerDetail);
8197
- if (matchesAny(firstCmd, FULLY_SAFE_BASH_PATTERNS)) {
8198
- const fullCmdHit = DANGEROUS_PATTERNS.some(
9080
+ const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);
9081
+ const tier = whitelistTier(lowerDetail, fullySafeCommands, readonlySensitiveCommands);
9082
+ if (tier === "full") {
9083
+ const fastPassScanText = normalizePathSegments(stripLiteralTextSegments(lowerDetail));
9084
+ const osPatterns = getDangerousPatternsForOs(input.osType);
9085
+ const fullCmdHit = osPatterns.some(
8199
9086
  (p) => p.severity === "critical" || p.severity === "high"
8200
- ) && DANGEROUS_PATTERNS.some(
8201
- (p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(lowerDetail)
9087
+ ) && osPatterns.some(
9088
+ (p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(fastPassScanText)
8202
9089
  );
8203
9090
  const hasRedirectWrite = isWriteOperation(firstCmd, lowerDetail);
8204
9091
  if (!fullCmdHit && !hasRedirectWrite) {
@@ -8213,7 +9100,7 @@ async function runAnalysis(input) {
8213
9100
  };
8214
9101
  }
8215
9102
  }
8216
- if (matchesAny(firstCmd, READONLY_SENSITIVE_BASH_PATTERNS)) {
9103
+ if (tier !== "") {
8217
9104
  skipLLM = true;
8218
9105
  }
8219
9106
  }
@@ -8222,8 +9109,8 @@ async function runAnalysis(input) {
8222
9109
  const effectiveResult = logicResult && !logicResult.allowed ? logicResult : l1FileAccessHit ? heuristicResult : null;
8223
9110
  if (effectiveResult) {
8224
9111
  const isCredentialPath = !!effectiveResult.isCredential;
8225
- const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && effectiveResult.riskType === "file_access";
8226
- if (skipInlineFileAccess) {
9112
+ const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && l1FileAccessHit;
9113
+ if (isInlineScriptCmd || isLiteralTextCmd) {
8227
9114
  const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
8228
9115
  if (inlineJudgment.actionable) {
8229
9116
  if (!inlineJudgment.allowed) {
@@ -8419,13 +9306,15 @@ async function runAnalysis(input) {
8419
9306
  }
8420
9307
  return llmResult;
8421
9308
  }
8422
- var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS, _brainStore, _brainExtractor, _brainInitialized, formalEngine;
9309
+ var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, _brainStore, _brainExtractor, _brainInitialized, formalEngine;
8423
9310
  var init_coordinator = __esm({
8424
9311
  "src/engine/coordinator.ts"() {
8425
9312
  "use strict";
8426
9313
  init_esm_shims();
8427
9314
  init_heuristic();
8428
9315
  init_logic_rules();
9316
+ init_platform_traits();
9317
+ init_shell_split();
8429
9318
  init_inline_analyzer();
8430
9319
  init_llm_analyzer();
8431
9320
  init_script_content_analyzer();
@@ -8455,53 +9344,6 @@ var init_coordinator = __esm({
8455
9344
  "filemgr-readfile",
8456
9345
  "filemgr-grepfiles"
8457
9346
  ]);
8458
- FULLY_SAFE_BASH_PATTERNS = [
8459
- /^ls(?:\s|$)/i,
8460
- /^dir(?:\s|$)/i,
8461
- /^pwd(?:\s|$)/i,
8462
- /^which\s+/i,
8463
- /^whereis\s+/i,
8464
- /^realpath\s+/i,
8465
- /^basename\s+/i,
8466
- /^dirname\s+/i,
8467
- /^file\s+/i,
8468
- /^stat\s+/i,
8469
- /^du(?:\s|$)/i,
8470
- /^echo\s+/i,
8471
- /^printf\s+/i,
8472
- /^type\s+/i,
8473
- /^command\s+/i,
8474
- // 系统信息查询(只读,不涉及文件/网络/权限)
8475
- /^whoami(?:\s|$)/i,
8476
- /^id(?:\s|$)/i,
8477
- /^hostname(?:\s|$)/i,
8478
- /^uname(?:\s|$)/i,
8479
- /^date(?:\s|$)/i,
8480
- /^env(?:\s|$)/i,
8481
- /^printenv(?:\s|$)/i,
8482
- /^tty(?:\s|$)/i,
8483
- /^arch(?:\s|$)/i,
8484
- /^uptime(?:\s|$)/i,
8485
- /^groups(?:\s|$)/i,
8486
- /^logname(?:\s|$)/i
8487
- ];
8488
- READONLY_SENSITIVE_BASH_PATTERNS = [
8489
- /^cat\s+/i,
8490
- /^head(?:\s|$)/i,
8491
- /^tail(?:\s|$)/i,
8492
- /^less(?:\s|$)/i,
8493
- /^more(?:\s|$)/i,
8494
- /^wc(?:\s|$)/i,
8495
- /^grep(?:\s|$)/i,
8496
- /^find\s+/i,
8497
- /^ag(?:\s|$)/i,
8498
- /^rg(?:\s|$)/i,
8499
- /^awk\s+/i,
8500
- /^sed\s+/i,
8501
- /^cut\s+/i,
8502
- /^sort(?:\s|$)/i,
8503
- /^uniq(?:\s|$)/i
8504
- ];
8505
9347
  _brainStore = null;
8506
9348
  _brainExtractor = null;
8507
9349
  _brainInitialized = false;
@@ -8509,795 +9351,6 @@ var init_coordinator = __esm({
8509
9351
  }
8510
9352
  });
8511
9353
 
8512
- // node_modules/better-sqlite3/lib/util.js
8513
- var require_util = __commonJS({
8514
- "node_modules/better-sqlite3/lib/util.js"(exports) {
8515
- "use strict";
8516
- init_esm_shims();
8517
- exports.getBooleanOption = (options, key) => {
8518
- let value = false;
8519
- if (key in options && typeof (value = options[key]) !== "boolean") {
8520
- throw new TypeError(`Expected the "${key}" option to be a boolean`);
8521
- }
8522
- return value;
8523
- };
8524
- exports.cppdb = /* @__PURE__ */ Symbol();
8525
- exports.inspect = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
8526
- }
8527
- });
8528
-
8529
- // node_modules/better-sqlite3/lib/sqlite-error.js
8530
- var require_sqlite_error = __commonJS({
8531
- "node_modules/better-sqlite3/lib/sqlite-error.js"(exports, module) {
8532
- "use strict";
8533
- init_esm_shims();
8534
- var descriptor = { value: "SqliteError", writable: true, enumerable: false, configurable: true };
8535
- function SqliteError(message, code) {
8536
- if (new.target !== SqliteError) {
8537
- return new SqliteError(message, code);
8538
- }
8539
- if (typeof code !== "string") {
8540
- throw new TypeError("Expected second argument to be a string");
8541
- }
8542
- Error.call(this, message);
8543
- descriptor.value = "" + message;
8544
- Object.defineProperty(this, "message", descriptor);
8545
- Error.captureStackTrace(this, SqliteError);
8546
- this.code = code;
8547
- }
8548
- Object.setPrototypeOf(SqliteError, Error);
8549
- Object.setPrototypeOf(SqliteError.prototype, Error.prototype);
8550
- Object.defineProperty(SqliteError.prototype, "name", descriptor);
8551
- module.exports = SqliteError;
8552
- }
8553
- });
8554
-
8555
- // node_modules/file-uri-to-path/index.js
8556
- var require_file_uri_to_path = __commonJS({
8557
- "node_modules/file-uri-to-path/index.js"(exports, module) {
8558
- "use strict";
8559
- init_esm_shims();
8560
- var sep = __require("path").sep || "/";
8561
- module.exports = fileUriToPath;
8562
- function fileUriToPath(uri) {
8563
- if ("string" != typeof uri || uri.length <= 7 || "file://" != uri.substring(0, 7)) {
8564
- throw new TypeError("must pass in a file:// URI to convert to a file path");
8565
- }
8566
- var rest = decodeURI(uri.substring(7));
8567
- var firstSlash = rest.indexOf("/");
8568
- var host = rest.substring(0, firstSlash);
8569
- var path6 = rest.substring(firstSlash + 1);
8570
- if ("localhost" == host) host = "";
8571
- if (host) {
8572
- host = sep + sep + host;
8573
- }
8574
- path6 = path6.replace(/^(.+)\|/, "$1:");
8575
- if (sep == "\\") {
8576
- path6 = path6.replace(/\//g, "\\");
8577
- }
8578
- if (/^.+\:/.test(path6)) {
8579
- } else {
8580
- path6 = sep + path6;
8581
- }
8582
- return host + path6;
8583
- }
8584
- }
8585
- });
8586
-
8587
- // node_modules/bindings/bindings.js
8588
- var require_bindings = __commonJS({
8589
- "node_modules/bindings/bindings.js"(exports, module) {
8590
- "use strict";
8591
- init_esm_shims();
8592
- var fs7 = __require("fs");
8593
- var path6 = __require("path");
8594
- var fileURLToPath11 = require_file_uri_to_path();
8595
- var join11 = path6.join;
8596
- var dirname11 = path6.dirname;
8597
- var exists = fs7.accessSync && function(path7) {
8598
- try {
8599
- fs7.accessSync(path7);
8600
- } catch (e) {
8601
- return false;
8602
- }
8603
- return true;
8604
- } || fs7.existsSync || path6.existsSync;
8605
- var defaults = {
8606
- arrow: process.env.NODE_BINDINGS_ARROW || " \u2192 ",
8607
- compiled: process.env.NODE_BINDINGS_COMPILED_DIR || "compiled",
8608
- platform: process.platform,
8609
- arch: process.arch,
8610
- nodePreGyp: "node-v" + process.versions.modules + "-" + process.platform + "-" + process.arch,
8611
- version: process.versions.node,
8612
- bindings: "bindings.node",
8613
- try: [
8614
- // node-gyp's linked version in the "build" dir
8615
- ["module_root", "build", "bindings"],
8616
- // node-waf and gyp_addon (a.k.a node-gyp)
8617
- ["module_root", "build", "Debug", "bindings"],
8618
- ["module_root", "build", "Release", "bindings"],
8619
- // Debug files, for development (legacy behavior, remove for node v0.9)
8620
- ["module_root", "out", "Debug", "bindings"],
8621
- ["module_root", "Debug", "bindings"],
8622
- // Release files, but manually compiled (legacy behavior, remove for node v0.9)
8623
- ["module_root", "out", "Release", "bindings"],
8624
- ["module_root", "Release", "bindings"],
8625
- // Legacy from node-waf, node <= 0.4.x
8626
- ["module_root", "build", "default", "bindings"],
8627
- // Production "Release" buildtype binary (meh...)
8628
- ["module_root", "compiled", "version", "platform", "arch", "bindings"],
8629
- // node-qbs builds
8630
- ["module_root", "addon-build", "release", "install-root", "bindings"],
8631
- ["module_root", "addon-build", "debug", "install-root", "bindings"],
8632
- ["module_root", "addon-build", "default", "install-root", "bindings"],
8633
- // node-pre-gyp path ./lib/binding/{node_abi}-{platform}-{arch}
8634
- ["module_root", "lib", "binding", "nodePreGyp", "bindings"]
8635
- ]
8636
- };
8637
- function bindings(opts) {
8638
- if (typeof opts == "string") {
8639
- opts = { bindings: opts };
8640
- } else if (!opts) {
8641
- opts = {};
8642
- }
8643
- Object.keys(defaults).map(function(i2) {
8644
- if (!(i2 in opts)) opts[i2] = defaults[i2];
8645
- });
8646
- if (!opts.module_root) {
8647
- opts.module_root = exports.getRoot(exports.getFileName());
8648
- }
8649
- if (path6.extname(opts.bindings) != ".node") {
8650
- opts.bindings += ".node";
8651
- }
8652
- var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
8653
- var tries = [], i = 0, l = opts.try.length, n, b, err;
8654
- for (; i < l; i++) {
8655
- n = join11.apply(
8656
- null,
8657
- opts.try[i].map(function(p) {
8658
- return opts[p] || p;
8659
- })
8660
- );
8661
- tries.push(n);
8662
- try {
8663
- b = opts.path ? requireFunc.resolve(n) : requireFunc(n);
8664
- if (!opts.path) {
8665
- b.path = n;
8666
- }
8667
- return b;
8668
- } catch (e) {
8669
- if (e.code !== "MODULE_NOT_FOUND" && e.code !== "QUALIFIED_PATH_RESOLUTION_FAILED" && !/not find/i.test(e.message)) {
8670
- throw e;
8671
- }
8672
- }
8673
- }
8674
- err = new Error(
8675
- "Could not locate the bindings file. Tried:\n" + tries.map(function(a) {
8676
- return opts.arrow + a;
8677
- }).join("\n")
8678
- );
8679
- err.tries = tries;
8680
- throw err;
8681
- }
8682
- module.exports = exports = bindings;
8683
- exports.getFileName = function getFileName(calling_file) {
8684
- var origPST = Error.prepareStackTrace, origSTL = Error.stackTraceLimit, dummy = {}, fileName;
8685
- Error.stackTraceLimit = 10;
8686
- Error.prepareStackTrace = function(e, st) {
8687
- for (var i = 0, l = st.length; i < l; i++) {
8688
- fileName = st[i].getFileName();
8689
- if (fileName !== __filename) {
8690
- if (calling_file) {
8691
- if (fileName !== calling_file) {
8692
- return;
8693
- }
8694
- } else {
8695
- return;
8696
- }
8697
- }
8698
- }
8699
- };
8700
- Error.captureStackTrace(dummy);
8701
- dummy.stack;
8702
- Error.prepareStackTrace = origPST;
8703
- Error.stackTraceLimit = origSTL;
8704
- var fileSchema = "file://";
8705
- if (fileName.indexOf(fileSchema) === 0) {
8706
- fileName = fileURLToPath11(fileName);
8707
- }
8708
- return fileName;
8709
- };
8710
- exports.getRoot = function getRoot(file) {
8711
- var dir = dirname11(file), prev;
8712
- while (true) {
8713
- if (dir === ".") {
8714
- dir = process.cwd();
8715
- }
8716
- if (exists(join11(dir, "package.json")) || exists(join11(dir, "node_modules"))) {
8717
- return dir;
8718
- }
8719
- if (prev === dir) {
8720
- throw new Error(
8721
- 'Could not find module root given file: "' + file + '". Do you have a `package.json` file? '
8722
- );
8723
- }
8724
- prev = dir;
8725
- dir = join11(dir, "..");
8726
- }
8727
- };
8728
- }
8729
- });
8730
-
8731
- // node_modules/better-sqlite3/lib/methods/wrappers.js
8732
- var require_wrappers = __commonJS({
8733
- "node_modules/better-sqlite3/lib/methods/wrappers.js"(exports) {
8734
- "use strict";
8735
- init_esm_shims();
8736
- var { cppdb } = require_util();
8737
- exports.prepare = function prepare(sql) {
8738
- return this[cppdb].prepare(sql, this, false);
8739
- };
8740
- exports.exec = function exec(sql) {
8741
- this[cppdb].exec(sql);
8742
- return this;
8743
- };
8744
- exports.close = function close() {
8745
- this[cppdb].close();
8746
- return this;
8747
- };
8748
- exports.loadExtension = function loadExtension(...args) {
8749
- this[cppdb].loadExtension(...args);
8750
- return this;
8751
- };
8752
- exports.defaultSafeIntegers = function defaultSafeIntegers(...args) {
8753
- this[cppdb].defaultSafeIntegers(...args);
8754
- return this;
8755
- };
8756
- exports.unsafeMode = function unsafeMode(...args) {
8757
- this[cppdb].unsafeMode(...args);
8758
- return this;
8759
- };
8760
- exports.getters = {
8761
- name: {
8762
- get: function name() {
8763
- return this[cppdb].name;
8764
- },
8765
- enumerable: true
8766
- },
8767
- open: {
8768
- get: function open() {
8769
- return this[cppdb].open;
8770
- },
8771
- enumerable: true
8772
- },
8773
- inTransaction: {
8774
- get: function inTransaction() {
8775
- return this[cppdb].inTransaction;
8776
- },
8777
- enumerable: true
8778
- },
8779
- readonly: {
8780
- get: function readonly() {
8781
- return this[cppdb].readonly;
8782
- },
8783
- enumerable: true
8784
- },
8785
- memory: {
8786
- get: function memory() {
8787
- return this[cppdb].memory;
8788
- },
8789
- enumerable: true
8790
- }
8791
- };
8792
- }
8793
- });
8794
-
8795
- // node_modules/better-sqlite3/lib/methods/transaction.js
8796
- var require_transaction = __commonJS({
8797
- "node_modules/better-sqlite3/lib/methods/transaction.js"(exports, module) {
8798
- "use strict";
8799
- init_esm_shims();
8800
- var { cppdb } = require_util();
8801
- var controllers = /* @__PURE__ */ new WeakMap();
8802
- module.exports = function transaction(fn) {
8803
- if (typeof fn !== "function") throw new TypeError("Expected first argument to be a function");
8804
- const db = this[cppdb];
8805
- const controller = getController(db, this);
8806
- const { apply } = Function.prototype;
8807
- const properties = {
8808
- default: { value: wrapTransaction(apply, fn, db, controller.default) },
8809
- deferred: { value: wrapTransaction(apply, fn, db, controller.deferred) },
8810
- immediate: { value: wrapTransaction(apply, fn, db, controller.immediate) },
8811
- exclusive: { value: wrapTransaction(apply, fn, db, controller.exclusive) },
8812
- database: { value: this, enumerable: true }
8813
- };
8814
- Object.defineProperties(properties.default.value, properties);
8815
- Object.defineProperties(properties.deferred.value, properties);
8816
- Object.defineProperties(properties.immediate.value, properties);
8817
- Object.defineProperties(properties.exclusive.value, properties);
8818
- return properties.default.value;
8819
- };
8820
- var getController = (db, self) => {
8821
- let controller = controllers.get(db);
8822
- if (!controller) {
8823
- const shared = {
8824
- commit: db.prepare("COMMIT", self, false),
8825
- rollback: db.prepare("ROLLBACK", self, false),
8826
- savepoint: db.prepare("SAVEPOINT ` _bs3. `", self, false),
8827
- release: db.prepare("RELEASE ` _bs3. `", self, false),
8828
- rollbackTo: db.prepare("ROLLBACK TO ` _bs3. `", self, false)
8829
- };
8830
- controllers.set(db, controller = {
8831
- default: Object.assign({ begin: db.prepare("BEGIN", self, false) }, shared),
8832
- deferred: Object.assign({ begin: db.prepare("BEGIN DEFERRED", self, false) }, shared),
8833
- immediate: Object.assign({ begin: db.prepare("BEGIN IMMEDIATE", self, false) }, shared),
8834
- exclusive: Object.assign({ begin: db.prepare("BEGIN EXCLUSIVE", self, false) }, shared)
8835
- });
8836
- }
8837
- return controller;
8838
- };
8839
- var wrapTransaction = (apply, fn, db, { begin, commit, rollback, savepoint, release, rollbackTo }) => function sqliteTransaction() {
8840
- let before, after, undo;
8841
- if (db.inTransaction) {
8842
- before = savepoint;
8843
- after = release;
8844
- undo = rollbackTo;
8845
- } else {
8846
- before = begin;
8847
- after = commit;
8848
- undo = rollback;
8849
- }
8850
- before.run();
8851
- try {
8852
- const result = apply.call(fn, this, arguments);
8853
- if (result && typeof result.then === "function") {
8854
- throw new TypeError("Transaction function cannot return a promise");
8855
- }
8856
- after.run();
8857
- return result;
8858
- } catch (ex) {
8859
- if (db.inTransaction) {
8860
- undo.run();
8861
- if (undo !== rollback) after.run();
8862
- }
8863
- throw ex;
8864
- }
8865
- };
8866
- }
8867
- });
8868
-
8869
- // node_modules/better-sqlite3/lib/methods/pragma.js
8870
- var require_pragma = __commonJS({
8871
- "node_modules/better-sqlite3/lib/methods/pragma.js"(exports, module) {
8872
- "use strict";
8873
- init_esm_shims();
8874
- var { getBooleanOption, cppdb } = require_util();
8875
- module.exports = function pragma(source, options) {
8876
- if (options == null) options = {};
8877
- if (typeof source !== "string") throw new TypeError("Expected first argument to be a string");
8878
- if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
8879
- const simple = getBooleanOption(options, "simple");
8880
- const stmt = this[cppdb].prepare(`PRAGMA ${source}`, this, true);
8881
- return simple ? stmt.pluck().get() : stmt.all();
8882
- };
8883
- }
8884
- });
8885
-
8886
- // node_modules/better-sqlite3/lib/methods/backup.js
8887
- var require_backup = __commonJS({
8888
- "node_modules/better-sqlite3/lib/methods/backup.js"(exports, module) {
8889
- "use strict";
8890
- init_esm_shims();
8891
- var fs7 = __require("fs");
8892
- var path6 = __require("path");
8893
- var { promisify } = __require("util");
8894
- var { cppdb } = require_util();
8895
- var fsAccess = promisify(fs7.access);
8896
- module.exports = async function backup(filename, options) {
8897
- if (options == null) options = {};
8898
- if (typeof filename !== "string") throw new TypeError("Expected first argument to be a string");
8899
- if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
8900
- filename = filename.trim();
8901
- const attachedName = "attached" in options ? options.attached : "main";
8902
- const handler = "progress" in options ? options.progress : null;
8903
- if (!filename) throw new TypeError("Backup filename cannot be an empty string");
8904
- if (filename === ":memory:") throw new TypeError('Invalid backup filename ":memory:"');
8905
- if (typeof attachedName !== "string") throw new TypeError('Expected the "attached" option to be a string');
8906
- if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
8907
- if (handler != null && typeof handler !== "function") throw new TypeError('Expected the "progress" option to be a function');
8908
- await fsAccess(path6.dirname(filename)).catch(() => {
8909
- throw new TypeError("Cannot save backup because the directory does not exist");
8910
- });
8911
- const isNewFile = await fsAccess(filename).then(() => false, () => true);
8912
- return runBackup(this[cppdb].backup(this, attachedName, filename, isNewFile), handler || null);
8913
- };
8914
- var runBackup = (backup, handler) => {
8915
- let rate = 0;
8916
- let useDefault = true;
8917
- return new Promise((resolve9, reject) => {
8918
- setImmediate(function step() {
8919
- try {
8920
- const progress = backup.transfer(rate);
8921
- if (!progress.remainingPages) {
8922
- backup.close();
8923
- resolve9(progress);
8924
- return;
8925
- }
8926
- if (useDefault) {
8927
- useDefault = false;
8928
- rate = 100;
8929
- }
8930
- if (handler) {
8931
- const ret = handler(progress);
8932
- if (ret !== void 0) {
8933
- if (typeof ret === "number" && ret === ret) rate = Math.max(0, Math.min(2147483647, Math.round(ret)));
8934
- else throw new TypeError("Expected progress callback to return a number or undefined");
8935
- }
8936
- }
8937
- setImmediate(step);
8938
- } catch (err) {
8939
- backup.close();
8940
- reject(err);
8941
- }
8942
- });
8943
- });
8944
- };
8945
- }
8946
- });
8947
-
8948
- // node_modules/better-sqlite3/lib/methods/serialize.js
8949
- var require_serialize = __commonJS({
8950
- "node_modules/better-sqlite3/lib/methods/serialize.js"(exports, module) {
8951
- "use strict";
8952
- init_esm_shims();
8953
- var { cppdb } = require_util();
8954
- module.exports = function serialize(options) {
8955
- if (options == null) options = {};
8956
- if (typeof options !== "object") throw new TypeError("Expected first argument to be an options object");
8957
- const attachedName = "attached" in options ? options.attached : "main";
8958
- if (typeof attachedName !== "string") throw new TypeError('Expected the "attached" option to be a string');
8959
- if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
8960
- return this[cppdb].serialize(attachedName);
8961
- };
8962
- }
8963
- });
8964
-
8965
- // node_modules/better-sqlite3/lib/methods/function.js
8966
- var require_function = __commonJS({
8967
- "node_modules/better-sqlite3/lib/methods/function.js"(exports, module) {
8968
- "use strict";
8969
- init_esm_shims();
8970
- var { getBooleanOption, cppdb } = require_util();
8971
- module.exports = function defineFunction(name, options, fn) {
8972
- if (options == null) options = {};
8973
- if (typeof options === "function") {
8974
- fn = options;
8975
- options = {};
8976
- }
8977
- if (typeof name !== "string") throw new TypeError("Expected first argument to be a string");
8978
- if (typeof fn !== "function") throw new TypeError("Expected last argument to be a function");
8979
- if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
8980
- if (!name) throw new TypeError("User-defined function name cannot be an empty string");
8981
- const safeIntegers = "safeIntegers" in options ? +getBooleanOption(options, "safeIntegers") : 2;
8982
- const deterministic = getBooleanOption(options, "deterministic");
8983
- const directOnly = getBooleanOption(options, "directOnly");
8984
- const varargs = getBooleanOption(options, "varargs");
8985
- let argCount = -1;
8986
- if (!varargs) {
8987
- argCount = fn.length;
8988
- if (!Number.isInteger(argCount) || argCount < 0) throw new TypeError("Expected function.length to be a positive integer");
8989
- if (argCount > 100) throw new RangeError("User-defined functions cannot have more than 100 arguments");
8990
- }
8991
- this[cppdb].function(fn, name, argCount, safeIntegers, deterministic, directOnly);
8992
- return this;
8993
- };
8994
- }
8995
- });
8996
-
8997
- // node_modules/better-sqlite3/lib/methods/aggregate.js
8998
- var require_aggregate = __commonJS({
8999
- "node_modules/better-sqlite3/lib/methods/aggregate.js"(exports, module) {
9000
- "use strict";
9001
- init_esm_shims();
9002
- var { getBooleanOption, cppdb } = require_util();
9003
- module.exports = function defineAggregate(name, options) {
9004
- if (typeof name !== "string") throw new TypeError("Expected first argument to be a string");
9005
- if (typeof options !== "object" || options === null) throw new TypeError("Expected second argument to be an options object");
9006
- if (!name) throw new TypeError("User-defined function name cannot be an empty string");
9007
- const start = "start" in options ? options.start : null;
9008
- const step = getFunctionOption(options, "step", true);
9009
- const inverse = getFunctionOption(options, "inverse", false);
9010
- const result = getFunctionOption(options, "result", false);
9011
- const safeIntegers = "safeIntegers" in options ? +getBooleanOption(options, "safeIntegers") : 2;
9012
- const deterministic = getBooleanOption(options, "deterministic");
9013
- const directOnly = getBooleanOption(options, "directOnly");
9014
- const varargs = getBooleanOption(options, "varargs");
9015
- let argCount = -1;
9016
- if (!varargs) {
9017
- argCount = Math.max(getLength(step), inverse ? getLength(inverse) : 0);
9018
- if (argCount > 0) argCount -= 1;
9019
- if (argCount > 100) throw new RangeError("User-defined functions cannot have more than 100 arguments");
9020
- }
9021
- this[cppdb].aggregate(start, step, inverse, result, name, argCount, safeIntegers, deterministic, directOnly);
9022
- return this;
9023
- };
9024
- var getFunctionOption = (options, key, required) => {
9025
- const value = key in options ? options[key] : null;
9026
- if (typeof value === "function") return value;
9027
- if (value != null) throw new TypeError(`Expected the "${key}" option to be a function`);
9028
- if (required) throw new TypeError(`Missing required option "${key}"`);
9029
- return null;
9030
- };
9031
- var getLength = ({ length }) => {
9032
- if (Number.isInteger(length) && length >= 0) return length;
9033
- throw new TypeError("Expected function.length to be a positive integer");
9034
- };
9035
- }
9036
- });
9037
-
9038
- // node_modules/better-sqlite3/lib/methods/table.js
9039
- var require_table = __commonJS({
9040
- "node_modules/better-sqlite3/lib/methods/table.js"(exports, module) {
9041
- "use strict";
9042
- init_esm_shims();
9043
- var { cppdb } = require_util();
9044
- module.exports = function defineTable(name, factory) {
9045
- if (typeof name !== "string") throw new TypeError("Expected first argument to be a string");
9046
- if (!name) throw new TypeError("Virtual table module name cannot be an empty string");
9047
- let eponymous = false;
9048
- if (typeof factory === "object" && factory !== null) {
9049
- eponymous = true;
9050
- factory = defer(parseTableDefinition(factory, "used", name));
9051
- } else {
9052
- if (typeof factory !== "function") throw new TypeError("Expected second argument to be a function or a table definition object");
9053
- factory = wrapFactory(factory);
9054
- }
9055
- this[cppdb].table(factory, name, eponymous);
9056
- return this;
9057
- };
9058
- function wrapFactory(factory) {
9059
- return function virtualTableFactory(moduleName, databaseName, tableName, ...args) {
9060
- const thisObject = {
9061
- module: moduleName,
9062
- database: databaseName,
9063
- table: tableName
9064
- };
9065
- const def = apply.call(factory, thisObject, args);
9066
- if (typeof def !== "object" || def === null) {
9067
- throw new TypeError(`Virtual table module "${moduleName}" did not return a table definition object`);
9068
- }
9069
- return parseTableDefinition(def, "returned", moduleName);
9070
- };
9071
- }
9072
- function parseTableDefinition(def, verb, moduleName) {
9073
- if (!hasOwnProperty.call(def, "rows")) {
9074
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition without a "rows" property`);
9075
- }
9076
- if (!hasOwnProperty.call(def, "columns")) {
9077
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition without a "columns" property`);
9078
- }
9079
- const rows = def.rows;
9080
- if (typeof rows !== "function" || Object.getPrototypeOf(rows) !== GeneratorFunctionPrototype) {
9081
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "rows" property (should be a generator function)`);
9082
- }
9083
- let columns = def.columns;
9084
- if (!Array.isArray(columns) || !(columns = [...columns]).every((x) => typeof x === "string")) {
9085
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "columns" property (should be an array of strings)`);
9086
- }
9087
- if (columns.length !== new Set(columns).size) {
9088
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with duplicate column names`);
9089
- }
9090
- if (!columns.length) {
9091
- throw new RangeError(`Virtual table module "${moduleName}" ${verb} a table definition with zero columns`);
9092
- }
9093
- let parameters;
9094
- if (hasOwnProperty.call(def, "parameters")) {
9095
- parameters = def.parameters;
9096
- if (!Array.isArray(parameters) || !(parameters = [...parameters]).every((x) => typeof x === "string")) {
9097
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "parameters" property (should be an array of strings)`);
9098
- }
9099
- } else {
9100
- parameters = inferParameters(rows);
9101
- }
9102
- if (parameters.length !== new Set(parameters).size) {
9103
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with duplicate parameter names`);
9104
- }
9105
- if (parameters.length > 32) {
9106
- throw new RangeError(`Virtual table module "${moduleName}" ${verb} a table definition with more than the maximum number of 32 parameters`);
9107
- }
9108
- for (const parameter of parameters) {
9109
- if (columns.includes(parameter)) {
9110
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with column "${parameter}" which was ambiguously defined as both a column and parameter`);
9111
- }
9112
- }
9113
- let safeIntegers = 2;
9114
- if (hasOwnProperty.call(def, "safeIntegers")) {
9115
- const bool = def.safeIntegers;
9116
- if (typeof bool !== "boolean") {
9117
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "safeIntegers" property (should be a boolean)`);
9118
- }
9119
- safeIntegers = +bool;
9120
- }
9121
- let directOnly = false;
9122
- if (hasOwnProperty.call(def, "directOnly")) {
9123
- directOnly = def.directOnly;
9124
- if (typeof directOnly !== "boolean") {
9125
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "directOnly" property (should be a boolean)`);
9126
- }
9127
- }
9128
- const columnDefinitions = [
9129
- ...parameters.map(identifier).map((str) => `${str} HIDDEN`),
9130
- ...columns.map(identifier)
9131
- ];
9132
- return [
9133
- `CREATE TABLE x(${columnDefinitions.join(", ")});`,
9134
- wrapGenerator(rows, new Map(columns.map((x, i) => [x, parameters.length + i])), moduleName),
9135
- parameters,
9136
- safeIntegers,
9137
- directOnly
9138
- ];
9139
- }
9140
- function wrapGenerator(generator, columnMap, moduleName) {
9141
- return function* virtualTable(...args) {
9142
- const output = args.map((x) => Buffer.isBuffer(x) ? Buffer.from(x) : x);
9143
- for (let i = 0; i < columnMap.size; ++i) {
9144
- output.push(null);
9145
- }
9146
- for (const row of generator(...args)) {
9147
- if (Array.isArray(row)) {
9148
- extractRowArray(row, output, columnMap.size, moduleName);
9149
- yield output;
9150
- } else if (typeof row === "object" && row !== null) {
9151
- extractRowObject(row, output, columnMap, moduleName);
9152
- yield output;
9153
- } else {
9154
- throw new TypeError(`Virtual table module "${moduleName}" yielded something that isn't a valid row object`);
9155
- }
9156
- }
9157
- };
9158
- }
9159
- function extractRowArray(row, output, columnCount, moduleName) {
9160
- if (row.length !== columnCount) {
9161
- throw new TypeError(`Virtual table module "${moduleName}" yielded a row with an incorrect number of columns`);
9162
- }
9163
- const offset = output.length - columnCount;
9164
- for (let i = 0; i < columnCount; ++i) {
9165
- output[i + offset] = row[i];
9166
- }
9167
- }
9168
- function extractRowObject(row, output, columnMap, moduleName) {
9169
- let count = 0;
9170
- for (const key of Object.keys(row)) {
9171
- const index = columnMap.get(key);
9172
- if (index === void 0) {
9173
- throw new TypeError(`Virtual table module "${moduleName}" yielded a row with an undeclared column "${key}"`);
9174
- }
9175
- output[index] = row[key];
9176
- count += 1;
9177
- }
9178
- if (count !== columnMap.size) {
9179
- throw new TypeError(`Virtual table module "${moduleName}" yielded a row with missing columns`);
9180
- }
9181
- }
9182
- function inferParameters({ length }) {
9183
- if (!Number.isInteger(length) || length < 0) {
9184
- throw new TypeError("Expected function.length to be a positive integer");
9185
- }
9186
- const params = [];
9187
- for (let i = 0; i < length; ++i) {
9188
- params.push(`$${i + 1}`);
9189
- }
9190
- return params;
9191
- }
9192
- var { hasOwnProperty } = Object.prototype;
9193
- var { apply } = Function.prototype;
9194
- var GeneratorFunctionPrototype = Object.getPrototypeOf(function* () {
9195
- });
9196
- var identifier = (str) => `"${str.replace(/"/g, '""')}"`;
9197
- var defer = (x) => () => x;
9198
- }
9199
- });
9200
-
9201
- // node_modules/better-sqlite3/lib/methods/inspect.js
9202
- var require_inspect = __commonJS({
9203
- "node_modules/better-sqlite3/lib/methods/inspect.js"(exports, module) {
9204
- "use strict";
9205
- init_esm_shims();
9206
- var DatabaseInspection = function Database() {
9207
- };
9208
- module.exports = function inspect(depth, opts) {
9209
- return Object.assign(new DatabaseInspection(), this);
9210
- };
9211
- }
9212
- });
9213
-
9214
- // node_modules/better-sqlite3/lib/database.js
9215
- var require_database = __commonJS({
9216
- "node_modules/better-sqlite3/lib/database.js"(exports, module) {
9217
- "use strict";
9218
- init_esm_shims();
9219
- var fs7 = __require("fs");
9220
- var path6 = __require("path");
9221
- var util = require_util();
9222
- var SqliteError = require_sqlite_error();
9223
- var DEFAULT_ADDON;
9224
- function Database(filenameGiven, options) {
9225
- if (new.target == null) {
9226
- return new Database(filenameGiven, options);
9227
- }
9228
- let buffer;
9229
- if (Buffer.isBuffer(filenameGiven)) {
9230
- buffer = filenameGiven;
9231
- filenameGiven = ":memory:";
9232
- }
9233
- if (filenameGiven == null) filenameGiven = "";
9234
- if (options == null) options = {};
9235
- if (typeof filenameGiven !== "string") throw new TypeError("Expected first argument to be a string");
9236
- if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
9237
- if ("readOnly" in options) throw new TypeError('Misspelled option "readOnly" should be "readonly"');
9238
- if ("memory" in options) throw new TypeError('Option "memory" was removed in v7.0.0 (use ":memory:" filename instead)');
9239
- const filename = filenameGiven.trim();
9240
- const anonymous = filename === "" || filename === ":memory:";
9241
- const readonly = util.getBooleanOption(options, "readonly");
9242
- const fileMustExist = util.getBooleanOption(options, "fileMustExist");
9243
- const timeout = "timeout" in options ? options.timeout : 5e3;
9244
- const verbose = "verbose" in options ? options.verbose : null;
9245
- const nativeBinding = "nativeBinding" in options ? options.nativeBinding : null;
9246
- if (readonly && anonymous && !buffer) throw new TypeError("In-memory/temporary databases cannot be readonly");
9247
- if (!Number.isInteger(timeout) || timeout < 0) throw new TypeError('Expected the "timeout" option to be a positive integer');
9248
- if (timeout > 2147483647) throw new RangeError('Option "timeout" cannot be greater than 2147483647');
9249
- if (verbose != null && typeof verbose !== "function") throw new TypeError('Expected the "verbose" option to be a function');
9250
- if (nativeBinding != null && typeof nativeBinding !== "string" && typeof nativeBinding !== "object") throw new TypeError('Expected the "nativeBinding" option to be a string or addon object');
9251
- let addon;
9252
- if (nativeBinding == null) {
9253
- addon = DEFAULT_ADDON || (DEFAULT_ADDON = require_bindings()("better_sqlite3.node"));
9254
- } else if (typeof nativeBinding === "string") {
9255
- const requireFunc = typeof __non_webpack_require__ === "function" ? __non_webpack_require__ : __require;
9256
- addon = requireFunc(path6.resolve(nativeBinding).replace(/(\.node)?$/, ".node"));
9257
- } else {
9258
- addon = nativeBinding;
9259
- }
9260
- if (!addon.isInitialized) {
9261
- addon.setErrorConstructor(SqliteError);
9262
- addon.isInitialized = true;
9263
- }
9264
- if (!anonymous && !fs7.existsSync(path6.dirname(filename))) {
9265
- throw new TypeError("Cannot open database because the directory does not exist");
9266
- }
9267
- Object.defineProperties(this, {
9268
- [util.cppdb]: { value: new addon.Database(filename, filenameGiven, anonymous, readonly, fileMustExist, timeout, verbose || null, buffer || null) },
9269
- ...wrappers.getters
9270
- });
9271
- }
9272
- var wrappers = require_wrappers();
9273
- Database.prototype.prepare = wrappers.prepare;
9274
- Database.prototype.transaction = require_transaction();
9275
- Database.prototype.pragma = require_pragma();
9276
- Database.prototype.backup = require_backup();
9277
- Database.prototype.serialize = require_serialize();
9278
- Database.prototype.function = require_function();
9279
- Database.prototype.aggregate = require_aggregate();
9280
- Database.prototype.table = require_table();
9281
- Database.prototype.loadExtension = wrappers.loadExtension;
9282
- Database.prototype.exec = wrappers.exec;
9283
- Database.prototype.close = wrappers.close;
9284
- Database.prototype.defaultSafeIntegers = wrappers.defaultSafeIntegers;
9285
- Database.prototype.unsafeMode = wrappers.unsafeMode;
9286
- Database.prototype[util.inspect] = require_inspect();
9287
- module.exports = Database;
9288
- }
9289
- });
9290
-
9291
- // node_modules/better-sqlite3/lib/index.js
9292
- var require_lib = __commonJS({
9293
- "node_modules/better-sqlite3/lib/index.js"(exports, module) {
9294
- "use strict";
9295
- init_esm_shims();
9296
- module.exports = require_database();
9297
- module.exports.SqliteError = require_sqlite_error();
9298
- }
9299
- });
9300
-
9301
9354
  // src/cli.ts
9302
9355
  init_esm_shims();
9303
9356
 
@@ -9325,7 +9378,11 @@ var version = pkg.version;
9325
9378
  // src/models.ts
9326
9379
  var ActionItemSchema = z.object({
9327
9380
  action_type: z.string().describe("Action type, e.g. bash, file_read, file_write"),
9328
- action_detail: z.string().describe("Action detail / command content")
9381
+ action_detail: z.string().describe("Action detail / command content"),
9382
+ // 工具返回给 agent 的内容。仅 L2 的授权(consent)判定用:判断「用户是否已授权」
9383
+ // 看的是 ask_user_question 的**回答**,不是提问正文。历史 bug:模型里没有这个字段、
9384
+ // 路由也没透传,TS 的授权判定退化成「提问正文非空」,等于问过任何问题即已授权。
9385
+ output: z.string().optional().describe("Tool result / user answer (needed by L2 consent checks)")
9329
9386
  });
9330
9387
  var PromptTurnSchema = z.object({
9331
9388
  text: z.string().default("").describe("User prompt text at this turn"),
@@ -9395,6 +9452,16 @@ init_coordinator();
9395
9452
  // src/actrail.ts
9396
9453
  init_esm_shims();
9397
9454
  import * as fs5 from "fs";
9455
+
9456
+ // src/infra/esm-require.ts
9457
+ init_esm_shims();
9458
+ import { createRequire as createRequire2 } from "module";
9459
+ var requireFromHere = createRequire2(import.meta.url);
9460
+ function esmRequire(id) {
9461
+ return requireFromHere(id);
9462
+ }
9463
+
9464
+ // src/actrail.ts
9398
9465
  function escape(value) {
9399
9466
  return value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/=/g, "\\e");
9400
9467
  }
@@ -9419,7 +9486,7 @@ function reportEnforcementEvent(opts) {
9419
9486
  try {
9420
9487
  let Database;
9421
9488
  try {
9422
- Database = require_lib();
9489
+ Database = esmRequire("better-sqlite3");
9423
9490
  } catch {
9424
9491
  console.debug("[AgentMoss] better-sqlite3 not installed, AcTrail reporting disabled");
9425
9492
  return false;
@@ -9620,7 +9687,7 @@ function readJsonDir(dirPath, limit = 1e5) {
9620
9687
  function readSqlite(dbPath, limit = 1e5) {
9621
9688
  let Database;
9622
9689
  try {
9623
- Database = require_lib();
9690
+ Database = esmRequire("better-sqlite3");
9624
9691
  } catch {
9625
9692
  return [];
9626
9693
  }
@@ -9688,15 +9755,21 @@ async function analyzeRoute(c) {
9688
9755
  const result = await runAnalysis({
9689
9756
  sessionId: req.session_id,
9690
9757
  promptSession: req.prompt_session,
9758
+ // output 必须透传:L2 的「授权(consent)」判定看的是**用户对提问的回答**,
9759
+ // 不是提问正文。历史 bug:这里丢了 output,TS 的判定退化成 actionDetail 非空,
9760
+ // 于是只要会话里问过任何问题(无论用户答没答、答的是什么、问的是不是改密)
9761
+ // 都算「已授权」→ 未授权的改密/删用户被放行(fail-open)。
9691
9762
  actionHistory: req.action_history.map((h) => ({
9692
9763
  actionType: h.action_type,
9693
- actionDetail: h.action_detail
9764
+ actionDetail: h.action_detail,
9765
+ output: h.output
9694
9766
  })),
9695
9767
  sessionHistory: (req.session_history || []).map((s) => ({
9696
9768
  sessionId: s.session_id,
9697
9769
  actionHistory: (s.action_history || []).map((h) => ({
9698
9770
  actionType: h.action_type,
9699
- actionDetail: h.action_detail
9771
+ actionDetail: h.action_detail,
9772
+ output: h.output
9700
9773
  }))
9701
9774
  })),
9702
9775
  aNext: {
@@ -9890,7 +9963,7 @@ init_runtime_config();
9890
9963
  init_token_stats();
9891
9964
  init_rules_loader();
9892
9965
  import { Hono } from "hono";
9893
- import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
9966
+ import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
9894
9967
  import { join as join10, dirname as dirname10 } from "path";
9895
9968
  import { fileURLToPath as fileURLToPath9 } from "url";
9896
9969
  var __dirname8 = dirname10(fileURLToPath9(import.meta.url));
@@ -9919,7 +9992,7 @@ function createConsoleRouter() {
9919
9992
  console2.use("/api/*", authMiddleware());
9920
9993
  const serveIndex = (c) => {
9921
9994
  const indexFile = join10(STATIC_DIR, "index.html");
9922
- if (existsSync14(indexFile)) {
9995
+ if (existsSync15(indexFile)) {
9923
9996
  return c.html(readFileSync15(indexFile, "utf-8"));
9924
9997
  }
9925
9998
  return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
@@ -10085,9 +10158,9 @@ function createConsoleRouter() {
10085
10158
  return c.json({ error: "invalid skill_id" }, 400);
10086
10159
  }
10087
10160
  const skillPath = join10(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
10088
- if (!existsSync14(skillPath)) {
10161
+ if (!existsSync15(skillPath)) {
10089
10162
  const builtinPath = join10(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
10090
- if (existsSync14(builtinPath)) return c.text(readFileSync15(builtinPath, "utf-8"));
10163
+ if (existsSync15(builtinPath)) return c.text(readFileSync15(builtinPath, "utf-8"));
10091
10164
  return c.json({ error: "skill not found" }, 404);
10092
10165
  }
10093
10166
  return c.text(readFileSync15(skillPath, "utf-8"));
@@ -10206,6 +10279,38 @@ function createConsoleRouter() {
10206
10279
  }
10207
10280
 
10208
10281
  // src/server.ts
10282
+ init_runtime_config();
10283
+ init_rules_loader();
10284
+ function logDeploymentSelfCheck() {
10285
+ try {
10286
+ console.log("================ AgentMoss \u90E8\u7F72\u81EA\u68C0 ================");
10287
+ console.log(`runtime \u914D\u7F6E: ${getRuntimeConfigPath()}`);
10288
+ const layerKeys = ["L1_heuristic", "L2_logic_rules", "L3_llm_analysis", "agent_rules_enabled", "scenario_rules_enabled"];
10289
+ console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
10290
+ try {
10291
+ const overrides = overriddenRuleFiles();
10292
+ if (overrides.length > 0) {
10293
+ console.error(`\u26A0\uFE0F \u68C0\u6D4B\u5230\u7528\u6237\u89C4\u5219\u8986\u76D6\u76EE\u5F55\u4E0B\u6709 ${overrides.length} \u4E2A\u6587\u4EF6\uFF0C**\u6574\u4EFD\u66FF\u6362**\u4E86\u5185\u7F6E\u89C4\u5219\uFF1A`);
10294
+ for (const p of overrides) console.error(` ${p}`);
10295
+ console.error(
10296
+ " \u8FD9\u4E9B\u6587\u4EF6\u7531\u90E8\u7F72\u4FA7\u81EA\u884C\u7EF4\u62A4\uFF0C**\u5185\u7F6E\u89C4\u5219\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4EEC\u4E0A\u9762**\uFF1B\u5220\u9664\u5BF9\u5E94\u6587\u4EF6\u5373\u6062\u590D\u5185\u7F6E\u3002\u82E5\u53EA\u60F3\u9010\u6761\u542F\u505C/\u589E\u5220\uFF0C\u8BF7\u6539\u7528 runtime config\uFF08Console\uFF09\u3002"
10297
+ );
10298
+ }
10299
+ } catch {
10300
+ }
10301
+ const scenarioRaw = (process.env.AGENT_MOSS_SCENARIO || "").trim();
10302
+ if (scenarioRaw) {
10303
+ console.log(`\u573A\u666F\u89C4\u5219\u5DF2\u542F\u7528: ${scenarioRaw}\uFF08\u6765\u6E90\uFF1A\u73AF\u5883\u53D8\u91CF AGENT_MOSS_SCENARIO\uFF09`);
10304
+ } else {
10305
+ console.error(
10306
+ "\u26A0\uFE0F \u573A\u666F\u89C4\u5219\u3010\u672A\u542F\u7528\u3011\u2014\u2014 \u73AF\u5883\u53D8\u91CF AGENT_MOSS_SCENARIO \u672A\u8BBE\u7F6E\u3002\n \u573A\u666F\u5B9A\u5236\u89C4\u5219\uFF08\u5982\u94F6\u884C\u6C99\u7BB1\u7684\u76EE\u5F55\u767D\u540D\u5355 / \u7981 ssh\uFF09**\u4E0D\u4F1A\u751F\u6548**\uFF0C\u767D\u540D\u5355\u5916\u7684\u8DEF\u5F84\u4F1A\u88AB\u653E\u884C\u3002\n \u542F\u7528\u65B9\u5F0F\uFF1A\u8BBE AGENT_MOSS_SCENARIO=<\u573A\u666Fid>\uFF08\u5982 bank\uFF1B\u591A\u573A\u666F\u9017\u53F7\u5206\u9694\uFF09\u540E**\u91CD\u542F\u672C\u670D\u52A1**\u3002\n \uFF08HTTP \u8BF7\u6C42\u65E0\u6CD5\u6307\u5B9A\u573A\u666F id\uFF0C\u573A\u666F\u662F\u90E8\u7F72\u7EA7\u914D\u7F6E\u3002\uFF09"
10307
+ );
10308
+ }
10309
+ console.log("==================================================");
10310
+ } catch (exc) {
10311
+ console.error(`\u90E8\u7F72\u81EA\u68C0\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u670D\u52A1\uFF09: ${exc instanceof Error ? exc.message : String(exc)}`);
10312
+ }
10313
+ }
10209
10314
  function createApp() {
10210
10315
  const app = new Hono2();
10211
10316
  app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["*"] }));
@@ -10219,12 +10324,12 @@ function createApp() {
10219
10324
  }
10220
10325
  async function findFreePort(startPort, host = "127.0.0.1", scan = 100) {
10221
10326
  for (let port = startPort; port < startPort + scan; port++) {
10222
- const free = await new Promise((resolve9) => {
10327
+ const free = await new Promise((resolve10) => {
10223
10328
  const server = net.createServer();
10224
10329
  server.listen(port, host, () => {
10225
- server.close(() => resolve9(true));
10330
+ server.close(() => resolve10(true));
10226
10331
  });
10227
- server.on("error", () => resolve9(false));
10332
+ server.on("error", () => resolve10(false));
10228
10333
  });
10229
10334
  if (free) {
10230
10335
  if (port !== startPort) {
@@ -10235,6 +10340,37 @@ async function findFreePort(startPort, host = "127.0.0.1", scan = 100) {
10235
10340
  }
10236
10341
  return startPort;
10237
10342
  }
10343
+ var WARMUP_COMMANDS = {
10344
+ linux: "whoami",
10345
+ macos: "whoami",
10346
+ harmony: "whoami",
10347
+ windows: "dir"
10348
+ };
10349
+ async function warmUpEngine() {
10350
+ const prevSavePolicy = process.env.AGENT_MOSS_SAVE_POLICY;
10351
+ try {
10352
+ const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_coordinator(), coordinator_exports));
10353
+ process.env.AGENT_MOSS_SAVE_POLICY = "0";
10354
+ for (const [osType, command] of Object.entries(WARMUP_COMMANDS)) {
10355
+ await runAnalysis2({
10356
+ sessionId: "__startup_warmup__",
10357
+ promptSession: "",
10358
+ actionHistory: [],
10359
+ aNext: { actionType: "bash", actionDetail: command },
10360
+ reason: "",
10361
+ osType,
10362
+ cwd: "",
10363
+ agentId: ""
10364
+ });
10365
+ }
10366
+ console.log("[AgentMoss] \u5F15\u64CE\u9884\u70ED\u5B8C\u6210\uFF08\u9996\u4E2A\u8BF7\u6C42\u4E0D\u518D\u4ED8\u51B7\u542F\u52A8\u5F00\u9500\uFF09");
10367
+ } catch (err) {
10368
+ console.warn(`[AgentMoss] \u5F15\u64CE\u9884\u70ED\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u670D\u52A1\uFF09: ${String(err)}`);
10369
+ } finally {
10370
+ if (prevSavePolicy === void 0) delete process.env.AGENT_MOSS_SAVE_POLICY;
10371
+ else process.env.AGENT_MOSS_SAVE_POLICY = prevSavePolicy;
10372
+ }
10373
+ }
10238
10374
  async function startServer(host = "127.0.0.1", port = 9090) {
10239
10375
  const actualPort = await findFreePort(port, host);
10240
10376
  const app = createApp();
@@ -10242,6 +10378,8 @@ async function startServer(host = "127.0.0.1", port = 9090) {
10242
10378
  port: actualPort,
10243
10379
  serve: async () => {
10244
10380
  console.log(`AgentMoss HTTP server on ${host}:${actualPort}`);
10381
+ logDeploymentSelfCheck();
10382
+ await warmUpEngine();
10245
10383
  const { serve: honoServe } = await import("@hono/node-server");
10246
10384
  honoServe({ fetch: app.fetch, hostname: host, port: actualPort });
10247
10385
  }
@@ -10329,7 +10467,7 @@ async function main() {
10329
10467
  console.error(`\u9519\u8BEF: \u4E0D\u662F\u6587\u4EF6: ${target}`);
10330
10468
  process.exit(2);
10331
10469
  }
10332
- if (/\.(json|ya?ml|toml)$/i.test(target)) {
10470
+ if (/\.(json|ya?ml|toml)$/i.test(target) && !/\s/.test(target.trim())) {
10333
10471
  console.error(`\u9519\u8BEF: \u6587\u4EF6\u4E0D\u5B58\u5728: ${target}`);
10334
10472
  process.exit(2);
10335
10473
  }
@@ -10449,13 +10587,13 @@ async function main() {
10449
10587
  let version2 = "";
10450
10588
  for (let port = 9090; port <= 9095; port++) {
10451
10589
  try {
10452
- const data = await new Promise((resolve9, reject) => {
10590
+ const data = await new Promise((resolve10, reject) => {
10453
10591
  const req = http.get(`http://127.0.0.1:${port}/api/v1/health`, { timeout: 2e3 }, (res) => {
10454
10592
  let body = "";
10455
10593
  res.on("data", (chunk) => body += chunk);
10456
10594
  res.on("end", () => {
10457
10595
  try {
10458
- resolve9(JSON.parse(body));
10596
+ resolve10(JSON.parse(body));
10459
10597
  } catch {
10460
10598
  reject();
10461
10599
  }
@@ -10483,13 +10621,13 @@ async function main() {
10483
10621
  console.log(` Systemd: \u672A\u6CE8\u518C\uFF08\u8FD0\u884C agent-moss install --enable \u5B89\u88C5\uFF09`);
10484
10622
  }
10485
10623
  try {
10486
- const data = await new Promise((resolve9, reject) => {
10624
+ const data = await new Promise((resolve10, reject) => {
10487
10625
  const req = http.get(`http://127.0.0.1:${foundPort}/console/api/config`, { timeout: 3e3 }, (res) => {
10488
10626
  let body = "";
10489
10627
  res.on("data", (chunk) => body += chunk);
10490
10628
  res.on("end", () => {
10491
10629
  try {
10492
- resolve9(JSON.parse(body));
10630
+ resolve10(JSON.parse(body));
10493
10631
  } catch {
10494
10632
  reject();
10495
10633
  }
@@ -10567,4 +10705,3 @@ export {
10567
10705
  //! P6 mc-ltl-rules LTL 模型检测
10568
10706
  //! P7 low-integrity-command-exec 低完整性命令执行
10569
10707
  //! P8 sensitive-data-aggregation 敏感数据聚合外传
10570
- //# sourceMappingURL=cli.js.map