@kenhkl/agent-moss 0.15.7 → 0.16.4

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, "");
@@ -488,14 +220,15 @@ function analyzeInlineScript(command) {
488
220
  riskType: "file_access"
489
221
  };
490
222
  }
491
- 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;
492
224
  var init_inline_analyzer = __esm({
493
225
  "src/engine/inline-analyzer.ts"() {
494
226
  "use strict";
495
227
  init_esm_shims();
228
+ init_shell_split();
496
229
  INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
497
230
  ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
498
- ECHO_CMD_RE = /(?:^|\s|;|&&|\|\|)(?:echo|printf)\s/gi;
231
+ ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
499
232
  READ_WRITE_KEYWORDS = [
500
233
  // 函数调用形态(关键词后跟 '(')
501
234
  "open(",
@@ -544,9 +277,34 @@ var init_inline_analyzer = __esm({
544
277
  "passthru",
545
278
  "shell_exec",
546
279
  "proc_open",
547
- // Perl-Ruby
280
+ // Perl/Ruby
548
281
  "<stdin>",
549
- "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")'
550
308
  ];
551
309
  WRITE_EXEC_CHAIN_PATTERNS = [
552
310
  // echo/cat/printf "..." > file && bash/sh/python file
@@ -601,15 +359,53 @@ var init_inline_analyzer = __esm({
601
359
  }
602
360
  });
603
361
 
604
- // src/infra/rules-loader.ts
605
- import { readFileSync as readFileSync2, existsSync } from "fs";
606
- import { resolve, dirname, join as join2 } from "path";
607
- import { fileURLToPath as fileURLToPath3 } from "url";
608
- import { homedir as homedir2 } from "os";
609
- function dataRootDir() {
610
- return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
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";
379
+ function dataRootDir() {
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;
611
405
  }
612
406
  function findRulesFile(name) {
407
+ const override = resolve(overrideRulesDir(), name);
408
+ if (existsSync(override)) return override;
613
409
  for (const dir of RULES_DIR_CANDS) {
614
410
  const p = resolve(dir, name);
615
411
  if (existsSync(p)) return p;
@@ -619,11 +415,16 @@ function findRulesFile(name) {
619
415
  function loadJson(name) {
620
416
  const p = findRulesFile(name);
621
417
  if (!p) return {};
418
+ const isOverride = p === resolve(overrideRulesDir(), name);
419
+ let data;
622
420
  try {
623
- return JSON.parse(readFileSync2(p, "utf-8")) || {};
421
+ data = JSON.parse(readFileSync2(p, "utf-8")) || {};
624
422
  } catch {
423
+ if (isOverride) warnOverride(name, p, {});
625
424
  return {};
626
425
  }
426
+ if (isOverride) warnOverride(name, p, data);
427
+ return data;
627
428
  }
628
429
  function loadL1() {
629
430
  return loadJson("l1_rules.json");
@@ -638,8 +439,8 @@ function loadMC() {
638
439
  const mc = loadJson("mc_rules.json");
639
440
  const rules = mc.rules || [];
640
441
  try {
641
- const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
642
- if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
442
+ const rt = runtimeConfigBridge.current;
443
+ if (rules.length > 0 && rt?.hasRuntimeConfig()) {
643
444
  const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
644
445
  const rtRules = ltl.rules || [];
645
446
  const enabledById = {};
@@ -657,16 +458,26 @@ function loadMC() {
657
458
  function loadToolRegistry() {
658
459
  return loadJson("tool_registry.json");
659
460
  }
660
- var __filename2, __dirname2, RULES_DIR_CANDS;
461
+ var __filename2, __dirname2, EXPECTED_KEYS, warnedOverride, RULES_DIR_CANDS;
661
462
  var init_rules_loader = __esm({
662
463
  "src/infra/rules-loader.ts"() {
663
464
  "use strict";
664
465
  init_esm_shims();
665
- __filename2 = fileURLToPath3(import.meta.url);
466
+ init_paths();
467
+ init_runtime_config_bridge();
468
+ __filename2 = fileURLToPath2(import.meta.url);
666
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();
667
478
  RULES_DIR_CANDS = (() => {
668
479
  const cands = [
669
- // 用户数据目录覆盖优先(与 Python rules_loader._load_json 的
480
+ // 用户数据目录覆盖优先(与 Python rules_loader.resolve_rules_file 的
670
481
  // <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
671
482
  // mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
672
483
  resolve(dataRootDir(), "rules"),
@@ -685,21 +496,455 @@ var init_rules_loader = __esm({
685
496
  }
686
497
  });
687
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
+
688
870
  // src/engine/logic-rules.ts
689
- import * as fs2 from "fs";
690
- 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);
691
881
  const sps = loadL1().sensitive_path_access || [];
692
- 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) => ({
693
883
  path: sp.path,
694
884
  riskLevel: sp.risk_level,
695
885
  desc: sp.desc,
696
886
  credential: sp.deny_mode === "deny_both" ? true : void 0,
697
- 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()
698
892
  }));
699
893
  }
700
- 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) {
701
945
  if (writeKeywords.some((kw) => actionType.includes(kw) || actionDetail.includes(kw))) return true;
702
- 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;
703
948
  if (/\bsed\s+-[a-zA-Z]*i/i.test(actionDetail)) return true;
704
949
  for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
705
950
  const trimmed = seg.trim();
@@ -733,6 +978,12 @@ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWOR
733
978
  }
734
979
  return false;
735
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
+ }
736
987
  function matchDangerousAction(actionDetail, action) {
737
988
  if (/[^\x00-\x7f]/.test(action)) {
738
989
  return actionDetail.includes(action);
@@ -767,38 +1018,47 @@ function collectIntentTexts(promptSession, promptHistory) {
767
1018
  }
768
1019
  return deduped;
769
1020
  }
770
- 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) {
771
1049
  const { actionHistory, aNext, cwd } = input;
772
1050
  const actionDetail = aNext.actionDetail.toLowerCase();
773
1051
  const actionType = aNext.actionType.toLowerCase();
774
1052
  const resolvedCwd = cwd || "/";
775
1053
  const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
776
1054
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
777
- const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd);
778
- if (rbwResult) return rbwResult;
779
- if (!isExcludedType) {
780
- if (!INLINE_SCRIPT_RE.test(actionDetail)) {
781
- const intentTexts = collectIntentTexts(input.promptSession, input.promptHistory);
782
- for (const pattern of INTENT_DEVIATION_PATTERNS) {
783
- const intentMatched = intentTexts.some(
784
- (text) => pattern.intentKeywords.some((kw) => text.includes(kw))
785
- );
786
- const dangerousMatched = pattern.dangerousActions.some((kw) => matchDangerousAction(actionDetail, kw));
787
- if (intentMatched && dangerousMatched) {
788
- return {
789
- allowed: false,
790
- reason: pattern.reason,
791
- riskLevel: "high",
792
- riskType: "intent_deviation",
793
- confidence: 85,
794
- source: "logic_rule",
795
- violatedLayers: ["L2.1"]
796
- };
797
- }
798
- }
799
- }
1055
+ if (!opts?.skipReadBeforeWrite) {
1056
+ const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
1057
+ if (rbwResult) return rbwResult;
800
1058
  }
801
- 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);
802
1062
  if (indirectResult) return indirectResult;
803
1063
  if (INLINE_SCRIPT_RE.test(actionDetail)) {
804
1064
  } else if (containsPrivilegeEscalation(actionDetail)) {
@@ -821,9 +1081,9 @@ function runLogicCheck(input) {
821
1081
  }
822
1082
  }
823
1083
  }
824
- const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory);
1084
+ const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory, input.osType);
825
1085
  if (pwdResult) return pwdResult;
826
- const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory);
1086
+ const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory, input.osType);
827
1087
  if (userDelResult) return userDelResult;
828
1088
  const comboResult = checkComboChain(actionHistory, aNext.actionDetail);
829
1089
  if (comboResult) return comboResult;
@@ -874,22 +1134,33 @@ function checkComboChain(actionHistory, actionDetailRaw) {
874
1134
  }
875
1135
  return null;
876
1136
  }
877
- function extractFilePaths(text) {
1137
+ function extractFilePaths(text, osType) {
878
1138
  const paths = [];
879
1139
  const unixPaths = text.match(/(?:^|\s)(\/[^\s;|&><'"]+)/g);
880
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
+ }
881
1148
  const textNoQuotes = text.replace(/'[^']*'|"[^"]*"/g, "");
882
1149
  const kvPaths = textNoQuotes.match(/(?:=)(\/[^\s;|&><'"]+)/g);
883
1150
  if (kvPaths) paths.push(...kvPaths.map((p) => p.replace(/^=/, "")));
884
1151
  const quotedPaths = text.match(/["']\/([^"']+)["']/g);
885
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
+ }
886
1157
  return [...new Set(paths)];
887
1158
  }
888
- function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd) {
1159
+ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd, osType) {
889
1160
  if (NON_WRITE_TOOL_PREFIXES.some((p) => actionType.startsWith(p))) return null;
890
1161
  if (NON_WRITE_TOOLS.has(actionType)) return null;
891
1162
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
892
- 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));
893
1164
  if (!isWrite) return null;
894
1165
  const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
895
1166
  if (isBashLike) {
@@ -933,7 +1204,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
933
1204
  if (firstWord === "cd") {
934
1205
  continue;
935
1206
  }
936
- if (READ_ONLY_COMMANDS.has(firstWord)) {
1207
+ if (getPlatformTraits(osType).readOnlyCommands.has(firstWord)) {
937
1208
  const segUnquoted = trimmed.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
938
1209
  const segNoDevnull = segUnquoted.replace(DEVNULL_REDIRECT_RE, "");
939
1210
  const segNoQuotes = segNoDevnull.replace(/'[^']*'|"[^"]*"/g, "");
@@ -941,7 +1212,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
941
1212
  if (m) targetPaths.push(m[1] || m[2] || m[3]);
942
1213
  continue;
943
1214
  }
944
- const segPaths = extractFilePaths(trimmed);
1215
+ const segPaths = extractFilePaths(trimmed, osType);
945
1216
  const segPathsFiltered = firstWord === "wget" || firstWord === "curl" ? segPaths.filter((p) => p !== "/dev/null") : segPaths;
946
1217
  if (SOURCE_ARG_CMDS.has(firstWord)) {
947
1218
  if (segPathsFiltered.length > 1) targetPaths.push(...segPathsFiltered.slice(1));
@@ -963,20 +1234,30 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
963
1234
  for (const action of actionHistory) {
964
1235
  const histDetail = action.actionDetail.toLowerCase();
965
1236
  const histType = action.actionType.toLowerCase();
966
- 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));
967
1238
  if (isRead) {
968
- for (const p of extractFilePaths(histDetail)) {
1239
+ for (const p of extractFilePaths(histDetail, osType)) {
969
1240
  historyReadPaths.add(p);
970
1241
  }
971
1242
  }
972
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
+ }
973
1254
  const unreadPaths = [];
974
- for (const path6 of uniqueTargetPaths) {
1255
+ for (const path6 of filteredPaths) {
975
1256
  const hasRead = [...historyReadPaths].some(
976
1257
  (rp) => path6 === rp || path6.startsWith(rp) || rp.startsWith(path6)
977
1258
  );
978
1259
  if (!hasRead) {
979
- let isCreate = !fs2.existsSync(path6);
1260
+ let isCreate = !fs.existsSync(path6);
980
1261
  if (!isCreate) {
981
1262
  isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
982
1263
  }
@@ -998,14 +1279,18 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
998
1279
  }
999
1280
  return null;
1000
1281
  }
1001
- 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) {
1002
1289
  if (actionType !== "bash") return null;
1003
- 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));
1004
1292
  if (!isPasswordModify) return null;
1005
- const hasConsent = actionHistory.some(
1006
- (h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
1007
- );
1008
- if (hasConsent) return null;
1293
+ if (hasUserConsent(actionHistory)) return null;
1009
1294
  return {
1010
1295
  allowed: false,
1011
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",
@@ -1016,14 +1301,12 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
1016
1301
  violatedLayers: ["L2.1"]
1017
1302
  };
1018
1303
  }
1019
- function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
1304
+ function checkUserDeletionConsent(actionType, actionDetail, actionHistory, osType) {
1020
1305
  if (actionType !== "bash") return null;
1021
- 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));
1022
1308
  if (!isUserDeletion) return null;
1023
- const hasConsent = actionHistory.some(
1024
- (h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
1025
- );
1026
- if (hasConsent) return null;
1309
+ if (hasUserConsent(actionHistory)) return null;
1027
1310
  return {
1028
1311
  allowed: false,
1029
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",
@@ -1046,7 +1329,7 @@ function containsPrivilegeEscalation(actionDetail) {
1046
1329
  }
1047
1330
  return false;
1048
1331
  }
1049
- function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1332
+ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1050
1333
  if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") {
1051
1334
  return null;
1052
1335
  }
@@ -1059,14 +1342,14 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1059
1342
  if (!hasTraversal) {
1060
1343
  return null;
1061
1344
  }
1062
- 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"];
1063
1346
  const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1064
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"];
1065
1348
  const OPEN_KEYWORDS = ["open(", "fopen("];
1066
1349
  const code = extractInlineCode(actionDetail);
1067
1350
  if (!code) return null;
1068
1351
  const codeLower = code.toLowerCase();
1069
- 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));
1070
1353
  if (!hasAnyOp) {
1071
1354
  return null;
1072
1355
  }
@@ -1074,15 +1357,16 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1074
1357
  const hasExec = EXEC_KEYWORDS.some((kw) => codeLower.includes(kw));
1075
1358
  const blockAllSensitive = hasWrite || hasExec;
1076
1359
  let actualFiles = [];
1077
- if (cwd && cwd !== "/") {
1360
+ if (cwd) {
1078
1361
  try {
1079
- actualFiles = fs2.readdirSync(cwd);
1362
+ actualFiles = fs.readdirSync(cwd);
1080
1363
  } catch {
1081
1364
  }
1082
1365
  }
1083
1366
  if (actualFiles.length > 0) {
1084
- for (const cp of CREDENTIAL_PATHS) {
1085
- 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;
1086
1370
  if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
1087
1371
  return {
1088
1372
  allowed: false,
@@ -1097,8 +1381,8 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1097
1381
  }
1098
1382
  }
1099
1383
  if (blockAllSensitive) {
1100
- for (const sp of [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS]) {
1101
- const fname = sp.path.split("/").pop() || sp.path;
1384
+ for (const sp of [...rootPaths, ...homePaths]) {
1385
+ const fname = sp.path.split(/[/\\]/).pop() || sp.path;
1102
1386
  if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
1103
1387
  return {
1104
1388
  allowed: false,
@@ -1124,16 +1408,28 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1124
1408
  violatedLayers: ["L2.1"]
1125
1409
  };
1126
1410
  }
1127
- 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;
1128
1412
  var init_logic_rules = __esm({
1129
1413
  "src/engine/logic-rules.ts"() {
1130
1414
  "use strict";
1131
1415
  init_esm_shims();
1132
1416
  init_inline_analyzer();
1417
+ init_runtime_config_bridge();
1133
1418
  init_rules_loader();
1419
+ init_platform_traits();
1134
1420
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1135
1421
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1136
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;
1137
1433
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1138
1434
  "ask_user_question",
1139
1435
  "glob",
@@ -1155,111 +1451,7 @@ var init_logic_rules = __esm({
1155
1451
  ]);
1156
1452
  NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1157
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"];
1158
- WRITE_COMMAND_RES = [
1159
- /\brm\b/,
1160
- /\bunlink\b/,
1161
- /\brmdir\b/,
1162
- /\bshred\b/,
1163
- /\bcp\b/,
1164
- /\bmv\b/,
1165
- /\binstall\b/,
1166
- /\btee\b/,
1167
- /\bdd\b/,
1168
- /\bmkfs\b/,
1169
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
1170
- /\bchmod\b/,
1171
- /\bchown\b/,
1172
- /\btruncate\b/
1173
- ];
1174
- READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
1175
- "cat",
1176
- "head",
1177
- "tail",
1178
- "less",
1179
- "more",
1180
- "grep",
1181
- "find",
1182
- "awk",
1183
- "sed",
1184
- "sort",
1185
- "uniq",
1186
- "wc",
1187
- "cut",
1188
- "strings",
1189
- "od",
1190
- "xxd",
1191
- "hexdump",
1192
- "tr",
1193
- "file",
1194
- "stat",
1195
- "du",
1196
- "df",
1197
- "ls",
1198
- "dir",
1199
- "tree",
1200
- "nl",
1201
- "tac",
1202
- "rev",
1203
- // NEW-FP-I: echo/printf 是文本输出命令,参数是字面量非写目标。加入此集让
1204
- // read_before_write 的 echo 段走只读命令分支(只提取重定向目标,不提取参数路径),
1205
- // 避免 echo "cat /etc/shadow" > /tmp/x.sh 的引号内 /etc/shadow 被当写目标误报。
1206
- // echo "x" > /tmp/x.sh 的重定向目标 /tmp/x.sh 仍被提取(真实写不漏报)。
1207
- "echo",
1208
- "printf",
1209
- "lsblk",
1210
- "blockdev",
1211
- "smartctl",
1212
- "udevadm",
1213
- "dmidecode",
1214
- "lscpu",
1215
- "lspci",
1216
- "lsusb",
1217
- "lsmem",
1218
- "lsns",
1219
- "lsof",
1220
- "hwinfo",
1221
- "inxi",
1222
- "fdisk",
1223
- "parted",
1224
- "dmesg",
1225
- "journalctl",
1226
- "systemctl",
1227
- "hostnamectl",
1228
- "localectl",
1229
- "timedatectl",
1230
- "ps",
1231
- "top",
1232
- "free",
1233
- "vmstat",
1234
- "iostat",
1235
- "mpstat",
1236
- "pidof",
1237
- "pgrep",
1238
- "ip",
1239
- "ifconfig",
1240
- "route",
1241
- "ss",
1242
- "netstat",
1243
- "arp",
1244
- "ethtool",
1245
- "nmcli",
1246
- "uname",
1247
- "arch",
1248
- "nproc",
1249
- "getconf",
1250
- "getent",
1251
- "id",
1252
- "whoami",
1253
- "who",
1254
- "w",
1255
- "last",
1256
- "uptime",
1257
- "lsmod",
1258
- "modinfo",
1259
- "sysctl"
1260
- ]);
1261
1454
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
1262
- 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"];
1263
1455
  CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
1264
1456
  PASSWORD_MODIFY_PATTERNS = (() => {
1265
1457
  const pats = loadL2().password_modify_consent || [];
@@ -1274,7 +1466,8 @@ var init_logic_rules = __esm({
1274
1466
  return pats.map((p) => ({
1275
1467
  intentKeywords: p.intent_keywords,
1276
1468
  dangerousActions: p.dangerous_actions,
1277
- reason: p.reason
1469
+ reason: p.reason,
1470
+ os: String(p.os || "linux").toLowerCase()
1278
1471
  }));
1279
1472
  })();
1280
1473
  COMBO_STAGES = (() => {
@@ -1334,9 +1527,9 @@ var init_asset_dir = __esm({
1334
1527
  });
1335
1528
 
1336
1529
  // src/engine/skill-loader.ts
1337
- import { readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
1530
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2, existsSync as existsSync4 } from "fs";
1338
1531
  import { resolve as resolve3, dirname as dirname2 } from "path";
1339
- import { fileURLToPath as fileURLToPath4 } from "url";
1532
+ import { fileURLToPath as fileURLToPath3 } from "url";
1340
1533
  function loadAllSkills() {
1341
1534
  if (cachedSkills) return cachedSkills;
1342
1535
  cachedSkills = {};
@@ -1354,14 +1547,47 @@ function loadAllSkills() {
1354
1547
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1355
1548
  return cachedSkills;
1356
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
+ }
1357
1580
  function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
1358
1581
  const skills = loadAllSkills();
1359
1582
  const combined = `${actionType} ${actionDetail} ${reason}`.toLowerCase();
1360
1583
  const actionDetailLower = actionDetail.toLowerCase();
1361
1584
  const actionTypeLower = actionType.toLowerCase();
1362
1585
  const reasonLower = reason.toLowerCase();
1586
+ const disabledSkillIds = runtimeConfigBridge.current?.disabledSkillIds() ?? /* @__PURE__ */ new Set();
1587
+ const skillMap = { ...SKILL_KEYWORD_MAP, ...runtimeCustomSkillKeywords() };
1363
1588
  const scores = {};
1364
- 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;
1365
1591
  let score = 0;
1366
1592
  for (const kw of keywords) {
1367
1593
  if (combined.includes(kw)) {
@@ -1378,7 +1604,7 @@ function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
1378
1604
  const maxScore = top[0]?.[1] || 1;
1379
1605
  const results = [];
1380
1606
  for (const [name, score] of top) {
1381
- const content = skills[name];
1607
+ const content = skills[name] || loadUserSkill(name);
1382
1608
  if (content) {
1383
1609
  results.push({
1384
1610
  skillName: name,
@@ -1403,14 +1629,16 @@ function formatSkillsForPrompt(skills) {
1403
1629
  ${s.skillContent}`
1404
1630
  ).join("\n\n---\n\n");
1405
1631
  }
1406
- var __filename3, __dirname3, SKILL_KEYWORD_MAP, cachedSkills;
1632
+ var __filename3, __dirname3, SKILL_KEYWORD_MAP, cachedSkills, userSkillCache;
1407
1633
  var init_skill_loader = __esm({
1408
1634
  "src/engine/skill-loader.ts"() {
1409
1635
  "use strict";
1410
1636
  init_esm_shims();
1411
1637
  init_rules_loader();
1638
+ init_paths();
1412
1639
  init_asset_dir();
1413
- __filename3 = fileURLToPath4(import.meta.url);
1640
+ init_runtime_config_bridge();
1641
+ __filename3 = fileURLToPath3(import.meta.url);
1414
1642
  __dirname3 = dirname2(__filename3);
1415
1643
  SKILL_KEYWORD_MAP = (() => {
1416
1644
  const map = {};
@@ -1424,6 +1652,7 @@ var init_skill_loader = __esm({
1424
1652
  return map;
1425
1653
  })();
1426
1654
  cachedSkills = null;
1655
+ userSkillCache = {};
1427
1656
  }
1428
1657
  });
1429
1658
 
@@ -1440,6 +1669,12 @@ __export(runtime_config_exports, {
1440
1669
  deleteCustomRule: () => deleteCustomRule,
1441
1670
  deleteCustomSkill: () => deleteCustomSkill,
1442
1671
  deleteScenarioRule: () => deleteScenarioRule,
1672
+ effectiveDangerousPatterns: () => effectiveDangerousPatterns,
1673
+ effectiveInjectionKeywords: () => effectiveInjectionKeywords,
1674
+ effectiveIntentPatterns: () => effectiveIntentPatterns,
1675
+ effectivePasswordPatterns: () => effectivePasswordPatterns,
1676
+ effectiveSensitivePaths: () => effectiveSensitivePaths,
1677
+ effectiveUserDeletionPatterns: () => effectiveUserDeletionPatterns,
1443
1678
  filterEnabledRules: () => filterEnabledRules,
1444
1679
  generateSourceDefaults: () => generateSourceDefaults,
1445
1680
  getAgentRules: () => getAgentRules,
@@ -1483,9 +1718,9 @@ __export(runtime_config_exports, {
1483
1718
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1484
1719
  updateSkillEnabled: () => updateSkillEnabled
1485
1720
  });
1486
- 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";
1487
1722
  import { join as join3 } from "path";
1488
- import { fileURLToPath as fileURLToPath5 } from "url";
1723
+ import { fileURLToPath as fileURLToPath4 } from "url";
1489
1724
  import { dirname as dirname3 } from "path";
1490
1725
  function runtimeDir() {
1491
1726
  return getRuntimeConfigDir();
@@ -1498,7 +1733,7 @@ function getRuntimeConfigPath() {
1498
1733
  function saveRuntimeConfig(config) {
1499
1734
  const p = getRuntimeConfigPath();
1500
1735
  const dir = p.substring(0, p.lastIndexOf("/"));
1501
- if (dir && !existsSync4(dir)) mkdirSync(dir, { recursive: true });
1736
+ if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
1502
1737
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
1503
1738
  invalidateRuntimeConfigCache();
1504
1739
  }
@@ -1516,10 +1751,11 @@ function emptyRuntimeConfig() {
1516
1751
  }
1517
1752
  function invalidateRuntimeConfigCache() {
1518
1753
  runtimeConfigCache = null;
1754
+ hasRuntimeConfigCache.clear();
1519
1755
  }
1520
1756
  function loadRuntimeConfig() {
1521
1757
  const p = getRuntimeConfigPath();
1522
- if (!existsSync4(p)) {
1758
+ if (!existsSync5(p)) {
1523
1759
  const seeded = mergeConfig({}, generateSourceDefaults());
1524
1760
  try {
1525
1761
  saveRuntimeConfig(seeded);
@@ -1540,7 +1776,13 @@ function loadRuntimeConfig() {
1540
1776
  layers: {
1541
1777
  L1_heuristic: data.layers?.L1_heuristic ?? true,
1542
1778
  L2_logic_rules: data.layers?.L2_logic_rules ?? true,
1543
- 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
1544
1786
  },
1545
1787
  L1_rules: merged.L1_rules || {},
1546
1788
  L2_rules: merged.L2_rules || {},
@@ -1582,21 +1824,48 @@ function filterEnabledRules(rules, categoryEnabled = true) {
1582
1824
  if (!categoryEnabled) return [];
1583
1825
  return rules.filter((r) => r.enabled !== false);
1584
1826
  }
1585
- 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) {
1586
1849
  const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
1587
- 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) => ({
1588
1854
  pattern: r.pattern || "",
1589
1855
  risk_level: r.risk_level || "",
1590
1856
  risk_type: r.risk_type || "",
1591
1857
  reason: r.reason || ""
1592
1858
  }));
1593
1859
  }
1594
- function getEnabledL2SensitivePaths(runtime) {
1860
+ function getEnabledL2SensitivePaths(runtime, osType) {
1595
1861
  let cat = runtime.L1_rules?.sensitive_path_access;
1596
1862
  if (!cat || !cat.rules || cat.rules.length === 0) {
1597
1863
  cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
1598
1864
  }
1599
- 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) => {
1600
1869
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1601
1870
  if (r.credential) out.credential = true;
1602
1871
  if (r.read_only) out.read_only = true;
@@ -1606,13 +1875,24 @@ function getEnabledL2SensitivePaths(runtime) {
1606
1875
  }
1607
1876
  function hasRuntimeConfig() {
1608
1877
  const p = getRuntimeConfigPath();
1609
- if (!existsSync4(p)) return false;
1878
+ let key;
1610
1879
  try {
1611
- JSON.parse(readFileSync4(p, "utf-8"));
1612
- return true;
1880
+ const st = statSync(p);
1881
+ key = `${p}|${st.mtimeMs}|${st.size}`;
1613
1882
  } catch {
1614
1883
  return false;
1615
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;
1616
1896
  }
1617
1897
  function getDisabledL2RulesWithSkipL3() {
1618
1898
  let runtime;
@@ -1677,15 +1957,28 @@ function _syncDenyModeFields(rule) {
1677
1957
  }
1678
1958
  }
1679
1959
  function _l1CommandPatternsToRules() {
1680
- return DANGEROUS_PATTERNS.map((p) => ({
1681
- id: _generatePatternId(p.pattern instanceof RegExp ? p.pattern.source : p.pattern),
1682
- pattern: p.pattern instanceof RegExp ? p.pattern.source : p.pattern,
1683
- risk_level: p.severity,
1684
- risk_type: p.riskType,
1685
- reason: p.description,
1686
- enabled: true,
1687
- builtin: true
1688
- }));
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;
1689
1982
  }
1690
1983
  function _l1InjectionKeywordsToRules() {
1691
1984
  return INJECTION_KEYWORDS.map((kw) => ({
@@ -1698,28 +1991,42 @@ function _l1InjectionKeywordsToRules() {
1698
1991
  }));
1699
1992
  }
1700
1993
  function _l2SensitivePathsToRules() {
1701
- const all = [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS, ...CREDENTIAL_PATHS];
1702
- return all.map((sp) => {
1703
- const denyMode = _deriveDenyMode({
1704
- deny_mode: sp.deny_mode,
1705
- credential: sp.credential,
1706
- read_only: sp.read_only
1707
- });
1708
- const rule = {
1709
- id: `path_${_slugify(sp.path)}`,
1710
- path: sp.path,
1711
- risk_level: sp.riskLevel,
1712
- desc: sp.desc,
1713
- deny_mode: denyMode,
1714
- source_deny_mode: denyMode,
1715
- skip_l3_on_disabled: true,
1716
- enabled: true,
1717
- builtin: true
1718
- };
1719
- _syncDenyModeFields(rule);
1720
- if (sp.dirDelOnly) rule.dir_del_only = true;
1721
- return rule;
1722
- });
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;
1723
2030
  }
1724
2031
  function _l2IntentPatternsToRules() {
1725
2032
  return INTENT_DEVIATION_PATTERNS.map((pat, i) => ({
@@ -1796,8 +2103,8 @@ function _l3SkillsToRules() {
1796
2103
  }
1797
2104
  function _scenarioRulesToDefaults() {
1798
2105
  const out = {};
1799
- const scenDir = existsSync4(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync4(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
1800
- 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;
1801
2108
  let entries;
1802
2109
  try {
1803
2110
  entries = readdirSync3(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -1806,7 +2113,7 @@ function _scenarioRulesToDefaults() {
1806
2113
  }
1807
2114
  for (const scenarioId of entries) {
1808
2115
  const rulesFile = join3(scenDir, scenarioId, "rules.json");
1809
- if (!existsSync4(rulesFile)) continue;
2116
+ if (!existsSync5(rulesFile)) continue;
1810
2117
  let data;
1811
2118
  try {
1812
2119
  data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
@@ -1845,8 +2152,8 @@ function _scenarioRulesToDefaults() {
1845
2152
  }
1846
2153
  function _agentRulesToDefaults() {
1847
2154
  const out = {};
1848
- const agentsDir = existsSync4(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync4(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
1849
- 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;
1850
2157
  let entries;
1851
2158
  try {
1852
2159
  entries = readdirSync3(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -1855,7 +2162,7 @@ function _agentRulesToDefaults() {
1855
2162
  }
1856
2163
  for (const agentId of entries) {
1857
2164
  const rulesFile = join3(agentsDir, agentId, "rules.json");
1858
- if (!existsSync4(rulesFile)) continue;
2165
+ if (!existsSync5(rulesFile)) continue;
1859
2166
  let data;
1860
2167
  try {
1861
2168
  data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
@@ -1951,7 +2258,7 @@ function _ltlRulesToRuntime() {
1951
2258
  let mc = {};
1952
2259
  for (const dir of cands) {
1953
2260
  const p = join3(dir, "mc_rules.json");
1954
- if (existsSync4(p)) {
2261
+ if (existsSync5(p)) {
1955
2262
  try {
1956
2263
  mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
1957
2264
  } catch {
@@ -1980,17 +2287,27 @@ function normalizePatternForFingerprint(pattern) {
1980
2287
  return pattern.replace(/\\\//g, "/");
1981
2288
  }
1982
2289
  function _ruleFingerprint(rule) {
2290
+ const osTag = String(rule.os || "linux").toLowerCase();
1983
2291
  if (rule.intent_keywords && rule.intent_keywords.length || rule.dangerous_actions && rule.dangerous_actions.length) {
1984
2292
  return JSON.stringify([
2293
+ osTag,
1985
2294
  [...rule.intent_keywords || []].sort(),
1986
2295
  [...rule.dangerous_actions || []].sort()
1987
2296
  ]);
1988
2297
  }
1989
- if (rule.path) return JSON.stringify([normalizePatternForFingerprint(rule.path)]);
1990
- if (rule.pattern) return JSON.stringify([normalizePatternForFingerprint(rule.pattern)]);
1991
- if (rule.keyword) return JSON.stringify([rule.keyword, rule.lang || ""]);
1992
- if (rule.value) return JSON.stringify([rule.value]);
1993
- 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
+ }
1994
2311
  }
1995
2312
  function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
1996
2313
  const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
@@ -2013,13 +2330,14 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
2013
2330
  srcFps.add(fp);
2014
2331
  if (userByFp.has(fp)) {
2015
2332
  const existing = userByFp.get(fp);
2016
- const userEnabled = existing.enabled;
2333
+ const userDisabled = existing.user_disabled === true;
2017
2334
  const userSkipL3 = existing.skip_l3_on_disabled;
2018
2335
  const userDenyMode = existing.deny_mode;
2019
2336
  const userSourceDeny = existing.source_deny_mode;
2020
2337
  Object.keys(existing).forEach((k) => delete existing[k]);
2021
2338
  Object.assign(existing, srcR);
2022
- existing.enabled = userEnabled;
2339
+ existing.enabled = !userDisabled;
2340
+ if (userDisabled) existing.user_disabled = true;
2023
2341
  if (userSkipL3 !== void 0) existing.skip_l3_on_disabled = userSkipL3;
2024
2342
  if (userDenyMode && userSourceDeny && userDenyMode !== userSourceDeny) {
2025
2343
  existing.deny_mode = userDenyMode;
@@ -2045,7 +2363,19 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
2045
2363
  }
2046
2364
  for (const catName of Object.keys(userLayer)) {
2047
2365
  if (catName === "user_custom") continue;
2048
- 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
+ }
2049
2379
  }
2050
2380
  }
2051
2381
  function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
@@ -2093,10 +2423,11 @@ function _mergeAgentRules(userCfg, srcDefaults, partition = "agent_rules") {
2093
2423
  srcFps.add(fp);
2094
2424
  if (userByFp.has(fp)) {
2095
2425
  const existing = userByFp.get(fp);
2096
- const userEnabled = existing.enabled;
2426
+ const userDisabled = existing.user_disabled === true;
2097
2427
  Object.keys(existing).forEach((k) => delete existing[k]);
2098
2428
  Object.assign(existing, srcR);
2099
- existing.enabled = userEnabled;
2429
+ existing.enabled = !userDisabled;
2430
+ if (userDisabled) existing.user_disabled = true;
2100
2431
  } else {
2101
2432
  userRules.push({ ...srcR, enabled: true });
2102
2433
  }
@@ -2160,17 +2491,23 @@ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
2160
2491
  delete src[catName];
2161
2492
  }
2162
2493
  }
2163
- function getEnabledL1InjectionKeywords(runtime) {
2494
+ function getEnabledL1InjectionKeywords(runtime, osType) {
2164
2495
  const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
2165
- 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) => ({
2166
2500
  keyword: r.keyword || "",
2167
2501
  risk_level: r.risk_level || "",
2168
2502
  lang: r.lang || "en"
2169
2503
  }));
2170
2504
  }
2171
- function getEnabledL2IntentPatterns(runtime) {
2505
+ function getEnabledL2IntentPatterns(runtime, osType) {
2172
2506
  const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
2173
- 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) => ({
2174
2511
  intent_keywords: r.intent_keywords || [],
2175
2512
  dangerous_actions: r.dangerous_actions || [],
2176
2513
  reason: r.reason || ""
@@ -2178,11 +2515,13 @@ function getEnabledL2IntentPatterns(runtime) {
2178
2515
  }
2179
2516
  function getEnabledL2PasswordPatterns(runtime) {
2180
2517
  const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
2181
- 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 || "");
2182
2520
  }
2183
2521
  function getEnabledL2UserDeletionPatterns(runtime) {
2184
2522
  const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
2185
- 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 || "");
2186
2525
  }
2187
2526
  function isL2CategoryEnabled(runtime, categoryKey) {
2188
2527
  const cat = runtime.L2_rules?.[categoryKey];
@@ -2201,45 +2540,126 @@ function getDisabledSkillIds(runtime) {
2201
2540
  }
2202
2541
  return disabled;
2203
2542
  }
2204
- function getEnabledL1CommandPatternsOrDefault(runtime) {
2205
- const enabled = getEnabledL1CommandPatterns(runtime);
2206
- if (enabled.length) return enabled;
2207
- if (!hasRuntimeConfig()) return _l1CommandPatternsToRules();
2208
- return [];
2209
- }
2210
- function getEnabledL1InjectionKeywordsOrDefault(runtime) {
2211
- const enabled = getEnabledL1InjectionKeywords(runtime);
2212
- if (enabled.length) return enabled;
2213
- if (!hasRuntimeConfig()) return _l1InjectionKeywordsToRules();
2214
- return [];
2215
- }
2216
- function getEnabledL2SensitivePathsOrDefault(runtime) {
2217
- const enabled = getEnabledL2SensitivePaths(runtime);
2218
- if (enabled.length) return enabled;
2219
- if (!hasRuntimeConfig()) return _l2SensitivePathsToRules();
2220
- return [];
2221
- }
2222
- function getEnabledL2IntentPatternsOrDefault(runtime) {
2223
- const enabled = getEnabledL2IntentPatterns(runtime);
2224
- if (enabled.length) return enabled;
2225
- if (!hasRuntimeConfig()) return _l2IntentPatternsToRules();
2226
- return [];
2227
- }
2228
- function getEnabledL2PasswordPatternsOrDefault(runtime) {
2229
- const enabled = getEnabledL2PasswordPatterns(runtime);
2230
- if (enabled.length) return enabled;
2231
- if (!hasRuntimeConfig()) {
2232
- return PASSWORD_MODIFY_PATTERNS.map((p) => p.source);
2233
- }
2234
- return [];
2235
- }
2236
- function getEnabledL2UserDeletionPatternsOrDefault(runtime) {
2237
- const enabled = getEnabledL2UserDeletionPatterns(runtime);
2238
- if (enabled.length) return enabled;
2239
- if (!hasRuntimeConfig()) {
2240
- return USER_DELETION_PATTERNS.map((p) => p.source);
2241
- }
2242
- 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;
2243
2663
  }
2244
2664
  function _validateSkillId(skillId) {
2245
2665
  if (!skillId || !SKILL_ID_RE.test(skillId)) {
@@ -2256,7 +2676,7 @@ function updateRuleEnabled(layer, category, ruleId, enabled) {
2256
2676
  const runtime = loadRuntimeConfig();
2257
2677
  for (const r of runtime[layer]?.[category]?.rules || []) {
2258
2678
  if (r.id === ruleId) {
2259
- r.enabled = enabled;
2679
+ _applyRuleEnabled(r, enabled);
2260
2680
  break;
2261
2681
  }
2262
2682
  }
@@ -2326,7 +2746,7 @@ function deleteCustomRule(layer, category, ruleId) {
2326
2746
  function addCustomSkill(skillId, category, keywords, content) {
2327
2747
  _validateSkillId(skillId);
2328
2748
  const runtime = loadRuntimeConfig();
2329
- const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2749
+ const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
2330
2750
  try {
2331
2751
  mkdirSync(skillsDir, { recursive: true });
2332
2752
  } catch {
@@ -2346,9 +2766,9 @@ function deleteCustomSkill(skillId, category) {
2346
2766
  if (idx >= 0) {
2347
2767
  if (skills[idx].builtin) return null;
2348
2768
  skills.splice(idx, 1);
2349
- const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2769
+ const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
2350
2770
  try {
2351
- if (existsSync4(skillPath)) unlinkSync(skillPath);
2771
+ if (existsSync5(skillPath)) unlinkSync(skillPath);
2352
2772
  } catch {
2353
2773
  }
2354
2774
  saveRuntimeConfig(runtime);
@@ -2427,7 +2847,7 @@ function updateAgentRuleEnabled(agentId, category, ruleFp, enabled) {
2427
2847
  const targetFp = _ruleFingerprint(ruleFp);
2428
2848
  for (const r of cat.rules || []) {
2429
2849
  if (_ruleFingerprint(r) === targetFp) {
2430
- r.enabled = enabled;
2850
+ _applyRuleEnabled(r, enabled);
2431
2851
  break;
2432
2852
  }
2433
2853
  }
@@ -2455,137 +2875,419 @@ function deleteAgentRule(agentId, category, ruleFp) {
2455
2875
  saveRuntimeConfig(runtime);
2456
2876
  return runtime;
2457
2877
  }
2458
- function updateAgentCategoryEnabled(agentId, category, enabled) {
2459
- const runtime = loadRuntimeConfig();
2460
- const cat = runtime.agent_rules?.[agentId]?.[category];
2461
- if (cat) cat.category_enabled = enabled;
2462
- saveRuntimeConfig(runtime);
2463
- 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";
2464
3207
  }
2465
- function getScenarioRules(runtime) {
2466
- return runtime.scenario_rules || {};
3208
+ function getProviderHeaders(provider) {
3209
+ return PROVIDER_HEADERS[provider] || {};
2467
3210
  }
2468
- function getEnabledScenarioExtensions(runtime, scenarioId) {
2469
- const scenData = runtime.scenario_rules?.[scenarioId];
2470
- if (!scenData) return null;
2471
- const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
2472
- pattern: r.pattern,
2473
- risk_level: r.risk_level || "",
2474
- risk_type: r.risk_type || "",
2475
- reason: r.reason || ""
2476
- }));
2477
- const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
2478
- keyword: r.keyword,
2479
- risk_level: r.risk_level || "",
2480
- lang: r.lang || "en"
2481
- }));
2482
- const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
2483
- path: r.path,
2484
- risk_level: r.risk_level || "",
2485
- desc: r.desc || "",
2486
- credential: r.credential,
2487
- read_only: r.read_only,
2488
- deny_mode: r.deny_mode
2489
- }));
2490
- return {
2491
- agent_id: scenarioId,
2492
- description: "",
2493
- extra_command_patterns: cmdPatterns,
2494
- extra_injection_keywords: injKeywords,
2495
- extra_sensitive_paths: sensitivePaths,
2496
- builtin_safe_skills: [],
2497
- builtin_safe_action_types: [],
2498
- extra_skills: []
2499
- };
3211
+ function isHeuristicEnabled() {
3212
+ if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
3213
+ return layerEnabledOrDefault("L1_heuristic");
2500
3214
  }
2501
- function updateScenarioRuleEnabled(scenarioId, category, ruleFp, enabled) {
2502
- const runtime = loadRuntimeConfig();
2503
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2504
- if (cat) {
2505
- const targetFp = _ruleFingerprint(ruleFp);
2506
- for (const r of cat.rules || []) {
2507
- if (_ruleFingerprint(r) === targetFp) {
2508
- r.enabled = enabled;
2509
- break;
2510
- }
2511
- }
3215
+ function layerEnabledOrDefault(key) {
3216
+ try {
3217
+ return isLayerEnabled(key);
3218
+ } catch {
3219
+ return true;
2512
3220
  }
2513
- saveRuntimeConfig(runtime);
2514
- return runtime;
2515
3221
  }
2516
- function addScenarioRule(scenarioId, category, rule) {
2517
- const runtime = loadRuntimeConfig();
2518
- if (!runtime.scenario_rules) runtime.scenario_rules = {};
2519
- if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
2520
- const cats = runtime.scenario_rules[scenarioId];
2521
- if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
2522
- (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
2523
- saveRuntimeConfig(runtime);
2524
- return runtime;
3222
+ function isBrainEnabled() {
3223
+ return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
2525
3224
  }
2526
- function deleteScenarioRule(scenarioId, category, ruleFp) {
2527
- const runtime = loadRuntimeConfig();
2528
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2529
- if (cat) {
2530
- const targetFp = _ruleFingerprint(ruleFp);
2531
- cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
2532
- }
2533
- saveRuntimeConfig(runtime);
2534
- return runtime;
3225
+ function isLogicRulesEnabled() {
3226
+ if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
3227
+ return layerEnabledOrDefault("L2_logic_rules");
2535
3228
  }
2536
- function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2537
- const runtime = loadRuntimeConfig();
2538
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2539
- if (cat) cat.category_enabled = enabled;
2540
- saveRuntimeConfig(runtime);
2541
- return runtime;
3229
+ function isFormalObserveMode() {
3230
+ return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
2542
3231
  }
2543
- 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;
2544
- var init_runtime_config = __esm({
2545
- "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"() {
2546
3235
  "use strict";
2547
3236
  init_esm_shims();
2548
3237
  init_paths();
2549
- init_heuristic();
2550
- init_logic_rules();
2551
- init_skill_loader();
2552
- __dirname4 = dirname3(fileURLToPath5(import.meta.url));
2553
- ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2554
- RUNTIME_CONFIG_DIR = runtimeDir();
2555
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2556
- LAYER_DISABLE_ENV = {
2557
- L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2558
- L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2559
- L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2560
- };
2561
- DEFAULT_LAYERS = {
2562
- L1_heuristic: true,
2563
- L2_logic_rules: true,
2564
- L3_llm_analysis: true,
2565
- agent_rules_enabled: true,
2566
- scenario_rules_enabled: true
2567
- };
2568
- DENY_MODE_LABELS = {
2569
- deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2570
- deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2571
- 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
+ }
2572
3280
  };
2573
- runtimeConfigCache = null;
2574
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2575
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2576
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2577
- _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2578
- SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2579
3281
  }
2580
3282
  });
2581
3283
 
2582
3284
  // src/agents/index.ts
2583
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
3285
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
2584
3286
  import { resolve as resolve4, dirname as dirname4 } from "path";
2585
3287
  import { fileURLToPath as fileURLToPath6 } from "url";
2586
3288
  function resolveAgentsDir() {
2587
- if (existsSync5(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
2588
- 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;
2589
3291
  return AGENTS_DIR_PROD;
2590
3292
  }
2591
3293
  function getAgentExtensions(agentId) {
@@ -2593,16 +3295,15 @@ function getAgentExtensions(agentId) {
2593
3295
  if (cache.has(agentId)) return cache.get(agentId) ?? null;
2594
3296
  let ext = null;
2595
3297
  try {
2596
- const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledAgentExtensions: getEnabledAgentExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
2597
- if (hasRuntimeConfig2()) {
2598
- ext = getEnabledAgentExtensions2(loadRuntimeConfig2(), agentId);
3298
+ if (hasRuntimeConfig()) {
3299
+ ext = getEnabledAgentExtensions(loadRuntimeConfig(), agentId);
2599
3300
  }
2600
3301
  } catch {
2601
3302
  }
2602
3303
  if (ext === null) {
2603
3304
  const agentsDir = resolveAgentsDir();
2604
3305
  const rulesFile = resolve4(agentsDir, agentId, "rules.json");
2605
- if (!existsSync5(rulesFile)) {
3306
+ if (!existsSync6(rulesFile)) {
2606
3307
  cache.set(agentId, null);
2607
3308
  return null;
2608
3309
  }
@@ -2621,7 +3322,7 @@ function getAgentExtensions(agentId) {
2621
3322
  }
2622
3323
  }
2623
3324
  cache.set(agentId, ext);
2624
- console.log(
3325
+ console.error(
2625
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`
2626
3327
  );
2627
3328
  return ext;
@@ -2646,6 +3347,7 @@ var init_agents = __esm({
2646
3347
  "src/agents/index.ts"() {
2647
3348
  "use strict";
2648
3349
  init_esm_shims();
3350
+ init_runtime_config();
2649
3351
  __filename4 = fileURLToPath6(import.meta.url);
2650
3352
  __dirname5 = dirname4(__filename4);
2651
3353
  AGENTS_DIR_PROD = resolve4(__dirname5, "agents");
@@ -2655,12 +3357,12 @@ var init_agents = __esm({
2655
3357
  });
2656
3358
 
2657
3359
  // src/scenarios/index.ts
2658
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
3360
+ import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
2659
3361
  import { resolve as resolve5, dirname as dirname5 } from "path";
2660
3362
  import { fileURLToPath as fileURLToPath7 } from "url";
2661
3363
  function resolveScenariosDir() {
2662
- if (existsSync6(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
2663
- 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;
2664
3366
  return SCENARIOS_DIR_PROD;
2665
3367
  }
2666
3368
  function getScenarioExtensions(scenarioId) {
@@ -2668,16 +3370,15 @@ function getScenarioExtensions(scenarioId) {
2668
3370
  if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
2669
3371
  let ext = null;
2670
3372
  try {
2671
- const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledScenarioExtensions: getEnabledScenarioExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
2672
- if (hasRuntimeConfig2()) {
2673
- ext = getEnabledScenarioExtensions2(loadRuntimeConfig2(), scenarioId);
3373
+ if (hasRuntimeConfig()) {
3374
+ ext = getEnabledScenarioExtensions(loadRuntimeConfig(), scenarioId);
2674
3375
  }
2675
3376
  } catch {
2676
3377
  }
2677
3378
  if (ext === null) {
2678
3379
  const scenariosDir = resolveScenariosDir();
2679
3380
  const rulesFile = resolve5(scenariosDir, scenarioId, "rules.json");
2680
- if (!existsSync6(rulesFile)) {
3381
+ if (!existsSync7(rulesFile)) {
2681
3382
  cache2.set(scenarioId, null);
2682
3383
  return null;
2683
3384
  }
@@ -2707,7 +3408,7 @@ function getScenarioExtensions(scenarioId) {
2707
3408
  }
2708
3409
  }
2709
3410
  cache2.set(scenarioId, ext);
2710
- console.log(
3411
+ console.error(
2711
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`
2712
3413
  );
2713
3414
  return ext;
@@ -2727,6 +3428,7 @@ var init_scenarios = __esm({
2727
3428
  "use strict";
2728
3429
  init_esm_shims();
2729
3430
  init_agents();
3431
+ init_runtime_config();
2730
3432
  __filename5 = fileURLToPath7(import.meta.url);
2731
3433
  __dirname6 = dirname5(__filename5);
2732
3434
  SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
@@ -2736,6 +3438,62 @@ var init_scenarios = __esm({
2736
3438
  });
2737
3439
 
2738
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
+ }
2739
3497
  function runHeuristicCheck(input) {
2740
3498
  const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
2741
3499
  const matched = [];
@@ -2762,11 +3520,23 @@ function runHeuristicCheck(input) {
2762
3520
  }
2763
3521
  }
2764
3522
  const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
2765
- 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
+ }
2766
3536
  let topRiskType = "";
2767
3537
  const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
2768
- for (const rule of DANGEROUS_PATTERNS) {
2769
- if (rule.pattern.test(scanCommand)) {
3538
+ for (const rule of _effectiveDangerousPatterns(input.osType)) {
3539
+ if (scanCommands.some((c) => rule.pattern.test(c))) {
2770
3540
  if (exemptKeygen && rule.riskType === "file_access") continue;
2771
3541
  matched.push(rule.description);
2772
3542
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
@@ -2784,7 +3554,7 @@ function runHeuristicCheck(input) {
2784
3554
  for (const rule of agentExt.extra_command_patterns) {
2785
3555
  try {
2786
3556
  const re = new RegExp(rule.pattern, "i");
2787
- if (re.test(scanCommand)) {
3557
+ if (scanCommands.some((c) => re.test(c))) {
2788
3558
  matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
2789
3559
  if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
2790
3560
  highestSeverity = rule.risk_level;
@@ -2796,7 +3566,7 @@ function runHeuristicCheck(input) {
2796
3566
  }
2797
3567
  }
2798
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();
2799
- const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
3569
+ const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords, input.osType);
2800
3570
  if (injectionHits.length > 0) {
2801
3571
  const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
2802
3572
  matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
@@ -2809,7 +3579,7 @@ function runHeuristicCheck(input) {
2809
3579
  const detailL = input.aNext.actionDetail.toLowerCase();
2810
3580
  const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
2811
3581
  const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
2812
- const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
3582
+ const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt, input.osType);
2813
3583
  const homeResult = checkHomeDeletion(actionTypeL, detailL);
2814
3584
  const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
2815
3585
  if (mergeResults.length > 0) {
@@ -2845,9 +3615,10 @@ function runHeuristicCheck(input) {
2845
3615
  if (lateralResult) return lateralResult;
2846
3616
  return null;
2847
3617
  }
2848
- function checkInjectionKeywords(text, extraKeywords) {
3618
+ function checkInjectionKeywords(text, extraKeywords, osType) {
2849
3619
  const hits = [];
2850
- for (const entry of INJECTION_KEYWORDS) {
3620
+ const baseKeywords = runtimeConfigBridge.current?.effectiveInjectionKeywords(osType) ?? INJECTION_KEYWORDS;
3621
+ for (const entry of baseKeywords) {
2851
3622
  if (text.includes(entry.keyword.toLowerCase())) {
2852
3623
  hits.push(entry);
2853
3624
  }
@@ -2907,7 +3678,7 @@ function buildPathContext(actionDetail) {
2907
3678
  if (!PATH_CONTEXT_TRIGGER_RE.test(actionDetail)) return "";
2908
3679
  const stripped = stripLiteralTextSegments(actionDetail);
2909
3680
  const lines = [];
2910
- for (const seg of splitCommandSegments(stripped)) {
3681
+ for (const seg of splitCommandSegments2(stripped)) {
2911
3682
  for (const [raw, lex] of resolvePathTokens(seg)) {
2912
3683
  lines.push(`- \u547D\u4EE4\u6BB5 \`${seg.trim().slice(0, 60)}\`\uFF1A\`${raw}\` \u8BCD\u6CD5\u89E3\u6790\u4E3A \`${lex}\``);
2913
3684
  }
@@ -2922,7 +3693,7 @@ function stripLiteralTextSegments(actionDetail) {
2922
3693
  return actionDetail;
2923
3694
  }
2924
3695
  const resultParts = [];
2925
- for (const seg of splitCommandSegments(actionDetail)) {
3696
+ for (const seg of splitCommandSegments2(actionDetail)) {
2926
3697
  const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
2927
3698
  if (fw === "echo" || fw === "printf") {
2928
3699
  const segUnquotedRedirect = seg.replace(
@@ -2942,7 +3713,7 @@ function stripHeredocContent(actionDetail) {
2942
3713
  "<<heredoc>>\n"
2943
3714
  );
2944
3715
  }
2945
- function normalizePathSegments(text) {
3716
+ function normalizePathSegments(text, foldQuotes = false) {
2946
3717
  const protTokens = [];
2947
3718
  let t = text.replace(/\b(https?|ftp|file):\/\//gi, (m) => {
2948
3719
  const scheme = m.slice(0, m.indexOf(":"));
@@ -2950,10 +3721,12 @@ function normalizePathSegments(text) {
2950
3721
  return `${scheme}__PROT${protTokens.length - 1}__`;
2951
3722
  });
2952
3723
  const quoteTokens = [];
2953
- t = t.replace(/'[^']*'|"[^"]*"/g, (m) => {
2954
- quoteTokens.push(m);
2955
- return `__Q${quoteTokens.length - 1}__`;
2956
- });
3724
+ if (!foldQuotes) {
3725
+ t = t.replace(/'[^']*'|"[^"]*"/g, (m) => {
3726
+ quoteTokens.push(m);
3727
+ return `__Q${quoteTokens.length - 1}__`;
3728
+ });
3729
+ }
2957
3730
  t = t.replace(/\/{2,}/g, "/");
2958
3731
  t = t.replace(/\/\.\//g, "/");
2959
3732
  for (let i = 0; i < quoteTokens.length; i++) {
@@ -2964,7 +3737,12 @@ function normalizePathSegments(text) {
2964
3737
  }
2965
3738
  return t;
2966
3739
  }
2967
- 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) {
2968
3746
  const segments = [];
2969
3747
  let cur = "";
2970
3748
  let quote = null;
@@ -3013,11 +3791,15 @@ function isSearchActionType(actionType) {
3013
3791
  const at = actionType.toLowerCase();
3014
3792
  return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || ["websearch", "web_search", "search", "query"].includes(at);
3015
3793
  }
3016
- function checkSensitivePath(command, cwd, agentExt) {
3794
+ function checkSensitivePath(command, cwd, agentExt, osType) {
3017
3795
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
3018
- const isWriteOpFlag = isWriteOperation("", command);
3796
+ const { root: rootPaths, home: homePaths, credential: credentialPaths } = getSensitivePathsForOs(osType);
3797
+ const isWriteOpFlag = isWriteOperation("", command, void 0, osType);
3019
3798
  const keygenGenerate = isSshKeygenGenerate(command);
3020
- 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
+ }
3021
3803
  const pathMatches = (text, path6) => {
3022
3804
  const parts = path6.split(/(\(\?!.*?\))/);
3023
3805
  const escaped = parts.map((part) => {
@@ -3027,8 +3809,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3027
3809
  const re = path6.startsWith(".") || path6.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
3028
3810
  return re.test(text);
3029
3811
  };
3030
- for (const sp of ROOT_SENSITIVE_PATHS) {
3031
- 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())) {
3032
3815
  if (keygenGenerate) continue;
3033
3816
  if (sp.path === "/dev/random") {
3034
3817
  if (isWriteOpFlag) {
@@ -3044,9 +3827,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3044
3827
  violatedLayers: ["L1.1"]
3045
3828
  };
3046
3829
  }
3047
- if (!isWriteOpFlag && !sp.credential) {
3048
- continue;
3049
- }
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;
3050
3833
  return {
3051
3834
  allowed: false,
3052
3835
  reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
@@ -3059,17 +3842,19 @@ function checkSensitivePath(command, cwd, agentExt) {
3059
3842
  };
3060
3843
  }
3061
3844
  }
3062
- for (const sp of HOME_SENSITIVE_PATHS) {
3845
+ for (const sp of homePaths) {
3063
3846
  const expanded = `${expandedCwd}/${sp.path}`;
3064
3847
  let pPath = sp.path;
3065
3848
  if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
3066
3849
  let dirPrefixMatch = null;
3067
3850
  if (sp.dirDelOnly) {
3068
3851
  const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
3069
- 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
+ );
3070
3855
  }
3071
- const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
3072
- 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)"));
3073
3858
  if (pathReMatch || expandedReMatch) {
3074
3859
  if (keygenGenerate) continue;
3075
3860
  if (sp.dirDelOnly) {
@@ -3099,9 +3884,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3099
3884
  }
3100
3885
  continue;
3101
3886
  }
3102
- if (!isWriteOpFlag && !sp.credential) {
3103
- continue;
3104
- }
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;
3105
3890
  return {
3106
3891
  allowed: false,
3107
3892
  reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
@@ -3138,7 +3923,7 @@ function checkSensitivePath(command, cwd, agentExt) {
3138
3923
  }
3139
3924
  }
3140
3925
  }
3141
- for (const cp of CREDENTIAL_PATHS) {
3926
+ for (const cp of credentialPaths) {
3142
3927
  const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3143
3928
  const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
3144
3929
  if (re.test(command)) {
@@ -3258,7 +4043,7 @@ function isDeleteActionForDir(command, dirPath) {
3258
4043
  }
3259
4044
  for (const seg of low.split(/&&|\|\||;|\n/)) {
3260
4045
  const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
3261
- if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
4046
+ if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && seg.includes(dpath)) {
3262
4047
  return true;
3263
4048
  }
3264
4049
  }
@@ -3441,7 +4226,7 @@ function checkLateralMovement(actionDetail) {
3441
4226
  }
3442
4227
  return null;
3443
4228
  }
3444
- 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;
3445
4230
  var init_heuristic = __esm({
3446
4231
  "src/engine/heuristic.ts"() {
3447
4232
  "use strict";
@@ -3450,6 +4235,7 @@ var init_heuristic = __esm({
3450
4235
  init_agents();
3451
4236
  init_scenarios();
3452
4237
  init_runtime_config();
4238
+ init_runtime_config_bridge();
3453
4239
  init_rules_loader();
3454
4240
  init_logic_rules();
3455
4241
  INJECTION_KEYWORDS = (() => {
@@ -3460,15 +4246,10 @@ var init_heuristic = __esm({
3460
4246
  lang: k.lang || "en"
3461
4247
  }));
3462
4248
  })();
3463
- DANGEROUS_PATTERNS = (() => {
3464
- const cmds = loadL1().dangerous_commands || [];
3465
- return cmds.map((c) => ({
3466
- pattern: new RegExp(c.pattern, "i"),
3467
- severity: c.risk_level,
3468
- riskType: c.risk_type,
3469
- description: c.reason
3470
- }));
3471
- })();
4249
+ DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
4250
+ DANGEROUS_PATTERNS_WINDOWS = _loadDangerousPatterns("windows");
4251
+ DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4252
+ DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
3472
4253
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
3473
4254
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
3474
4255
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
@@ -3479,7 +4260,7 @@ var init_heuristic = __esm({
3479
4260
  });
3480
4261
 
3481
4262
  // src/engine/template-loader.ts
3482
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
4263
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
3483
4264
  import { resolve as resolve6, dirname as dirname6 } from "path";
3484
4265
  import { fileURLToPath as fileURLToPath8 } from "url";
3485
4266
  function loadTemplates() {
@@ -3493,7 +4274,7 @@ function loadTemplates() {
3493
4274
  }
3494
4275
  for (const file of TEMPLATE_FILES) {
3495
4276
  const filePath = resolve6(dir, file);
3496
- if (existsSync7(filePath)) {
4277
+ if (existsSync8(filePath)) {
3497
4278
  const key = file.replace(".txt", "").replace("_template", "");
3498
4279
  cachedTemplates[key] = readFileSync7(filePath, "utf-8");
3499
4280
  }
@@ -3569,7 +4350,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3569
4350
  }
3570
4351
  function readScriptContent(scriptPath, maxLines = 500) {
3571
4352
  try {
3572
- const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve7(scriptPath);
4353
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
3573
4354
  const raw = readFileSync8(expandedPath, "utf-8");
3574
4355
  const lines = raw.split("\n").slice(0, maxLines);
3575
4356
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -4519,9 +5300,20 @@ function evalExpr2(text, env) {
4519
5300
  const result = [];
4520
5301
  for (const p of parts) {
4521
5302
  if (!p) continue;
4522
- const brace = p.match(/^\$\{([^}]+)\}$/);
5303
+ const brace = p.match(/^\$\{([^}]*)\}$/);
4523
5304
  if (brace) {
4524
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
+ }
4525
5317
  if (/^\d+$/.test(inner)) {
4526
5318
  const v = env.get(`$${inner}`);
4527
5319
  result.push(v || svalSym(`$${inner}`));
@@ -4737,7 +5529,7 @@ var init_symbolic = __esm({
4737
5529
  LOOP_DONE_RE = /\bdone\b/g;
4738
5530
  FN_DEF_RE = /^\s*(?:function\s+)?[A-Za-z_][A-Za-z0-9_]*\s*\(\s*\)\s*\{/;
4739
5531
  FN_DEF_KW_RE = /^\s*function\s+[A-Za-z_][A-Za-z0-9_]*\s*\{?/;
4740
- EXPR_SPLIT_RE = /(\$\{?\d+\}?|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)/;
5532
+ EXPR_SPLIT_RE = /(\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$\d+)/;
4741
5533
  HAS_GLOB = /[*?[]/;
4742
5534
  }
4743
5535
  });
@@ -5018,6 +5810,14 @@ var init_backends = __esm({
5018
5810
  import * as fs3 from "fs";
5019
5811
  import * as path3 from "path";
5020
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
+ }
5021
5821
  function pickBackend(pathStr) {
5022
5822
  const lower = pathStr.toLowerCase();
5023
5823
  for (const ext of Object.keys(BACKENDS)) {
@@ -5060,6 +5860,7 @@ function emptyNode(pathStr, depth) {
5060
5860
  depth,
5061
5861
  childPaths: [],
5062
5862
  error: "",
5863
+ unanalyzable: false,
5063
5864
  l1Hit: false,
5064
5865
  l1RiskLevel: "",
5065
5866
  l1RiskType: "",
@@ -5085,7 +5886,7 @@ var init_tracker = __esm({
5085
5886
  init_backends();
5086
5887
  DEFAULT_MAX_DEPTH = 14;
5087
5888
  DEFAULT_MAX_NODES = 64;
5088
- DEFAULT_MAX_FILE_SIZE = 2 * 1024 * 1024;
5889
+ DEFAULT_MAX_FILE_SIZE = maxFileSizeFromEnv();
5089
5890
  DEFAULT_MAX_TOTAL_SIZE = 8 * 1024 * 1024;
5090
5891
  BACKENDS = {
5091
5892
  ".py": new PythonBackend()
@@ -5156,6 +5957,7 @@ var init_tracker = __esm({
5156
5957
  node2.error = error;
5157
5958
  node2.fileSize = fileSize;
5158
5959
  node2.lineCount = lineCount;
5960
+ node2.unanalyzable = error.startsWith("\u811A\u672C\u6587\u4EF6\u8FC7\u5927");
5159
5961
  this.result.nodes.push(node2);
5160
5962
  this.visiting.delete(pathStr);
5161
5963
  return;
@@ -5202,9 +6004,9 @@ var init_tracker = __esm({
5202
6004
  });
5203
6005
 
5204
6006
  // src/engine/script-content-analyzer.ts
5205
- import { statSync as statSync3, existsSync as existsSync9 } from "fs";
6007
+ import { statSync as statSync3, existsSync as existsSync10 } from "fs";
5206
6008
  import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
5207
- import { homedir as homedir4 } from "os";
6009
+ import { homedir as homedir3 } from "os";
5208
6010
  function emptyChain() {
5209
6011
  return {
5210
6012
  nodes: [],
@@ -5310,12 +6112,12 @@ function extractInlineScriptPath(actionDetail) {
5310
6112
  const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
5311
6113
  while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
5312
6114
  for (const cand of candidates) {
5313
- 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);
5314
6116
  for (const tok of tokens) {
5315
6117
  if (looksLikeScriptPath(tok)) {
5316
6118
  try {
5317
6119
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
5318
- if (existsSync9(resolved)) {
6120
+ if (existsSync10(resolved)) {
5319
6121
  const stat = statSync3(resolved);
5320
6122
  if (stat.isFile()) return resolved;
5321
6123
  }
@@ -5329,7 +6131,7 @@ function extractInlineScriptPath(actionDetail) {
5329
6131
  const p = m[1];
5330
6132
  try {
5331
6133
  const resolved = resolvePath2(p);
5332
- if (existsSync9(resolved)) {
6134
+ if (existsSync10(resolved)) {
5333
6135
  const stat = statSync3(resolved);
5334
6136
  if (stat.isFile()) return resolved;
5335
6137
  }
@@ -5345,7 +6147,7 @@ function extractInlineScriptPath(actionDetail) {
5345
6147
  let resolved;
5346
6148
  try {
5347
6149
  resolved = resolvePath2(p);
5348
- if (existsSync9(resolved)) {
6150
+ if (existsSync10(resolved)) {
5349
6151
  const stat = statSync3(resolved);
5350
6152
  if (stat.isFile()) return resolved;
5351
6153
  }
@@ -5354,10 +6156,39 @@ function extractInlineScriptPath(actionDetail) {
5354
6156
  }
5355
6157
  return null;
5356
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
+ }
5357
6188
  function resolvePath2(p) {
5358
6189
  let expanded = p;
5359
6190
  if (expanded.startsWith("~/") || expanded === "~") {
5360
- expanded = join6(homedir4(), expanded.slice(1));
6191
+ expanded = join6(homedir3(), expanded.slice(1));
5361
6192
  }
5362
6193
  return resolve9(expanded);
5363
6194
  }
@@ -5368,7 +6199,7 @@ function scanScriptChainWithL1(chain, input) {
5368
6199
  ...input,
5369
6200
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
5370
6201
  };
5371
- const l1 = runHeuristicCheck(virtualInput);
6202
+ const l1 = scanDangerousCommandPatterns("bash", node.preprocessedContent, input.osType);
5372
6203
  if (l1 && !l1.allowed) {
5373
6204
  node.l1Hit = true;
5374
6205
  node.l1RiskLevel = l1.riskLevel;
@@ -5391,18 +6222,25 @@ function scanScriptChainWithL2(chain, input) {
5391
6222
  ...input,
5392
6223
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
5393
6224
  };
5394
- const l2 = runLogicCheck(virtualInput);
5395
- if (l2 && !l2.allowed) {
5396
- node.l2Hit = true;
5397
- node.l2ViolatedRule = `script_logic:${node.scriptPath}`;
5398
- node.l2RiskLevel = l2.riskLevel;
5399
- node.l2RiskType = l2.riskType;
5400
- node.l2Reason = `\u811A\u672C ${node.scriptPath} (\u6DF1\u5EA6 ${node.depth}) \u4E2D: ${l2.reason}`;
5401
- if (l2.riskLevel === "critical") {
5402
- chain.hasCritical = true;
5403
- chain.firstHitNode = node;
5404
- break;
5405
- }
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;
5406
6244
  }
5407
6245
  }
5408
6246
  if (!chain.hasHigh) {
@@ -5441,13 +6279,16 @@ function analyzeScriptContent2(input) {
5441
6279
  resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
5442
6280
  }
5443
6281
  const l1Adapter = (content) => {
5444
- const virtualInput = {
5445
- ...input,
5446
- aNext: { actionType: "bash", actionDetail: content }
5447
- };
5448
- const j = runHeuristicCheck(virtualInput);
6282
+ const j = scanDangerousCommandPatterns("bash", content, input.osType);
5449
6283
  if (j && !j.allowed) {
5450
- 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
+ };
5451
6292
  }
5452
6293
  return { hit: false, riskLevel: "", riskType: "", reason: "", matchedPatterns: [] };
5453
6294
  };
@@ -5526,6 +6367,7 @@ var init_script_content_analyzer = __esm({
5526
6367
  "use strict";
5527
6368
  init_esm_shims();
5528
6369
  init_heuristic();
6370
+ init_config();
5529
6371
  init_logic_rules();
5530
6372
  init_inline_analyzer();
5531
6373
  init_tracker();
@@ -5624,14 +6466,14 @@ __export(token_stats_exports, {
5624
6466
  recordTokenUsage: () => recordTokenUsage,
5625
6467
  resetTokenStats: () => resetTokenStats
5626
6468
  });
5627
- 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";
5628
6470
  function getStatsPath() {
5629
6471
  const envPath = process.env[ENV_STATS_PATH];
5630
6472
  if (envPath) return envPath;
5631
6473
  return getTokenStatsPath();
5632
6474
  }
5633
6475
  function loadAllRecords(statsPath) {
5634
- if (!existsSync10(statsPath)) return [];
6476
+ if (!existsSync11(statsPath)) return [];
5635
6477
  try {
5636
6478
  const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
5637
6479
  if (Array.isArray(data)) return data;
@@ -5644,7 +6486,7 @@ function loadAllRecords(statsPath) {
5644
6486
  function saveRecords(statsPath, records) {
5645
6487
  try {
5646
6488
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
5647
- if (dir && !existsSync10(dir)) mkdirSync2(dir, { recursive: true });
6489
+ if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
5648
6490
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
5649
6491
  } catch (e) {
5650
6492
  }
@@ -5771,7 +6613,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
5771
6613
  }
5772
6614
  function resetTokenStats() {
5773
6615
  const statsPath = getStatsPath();
5774
- if (existsSync10(statsPath)) {
6616
+ if (existsSync11(statsPath)) {
5775
6617
  try {
5776
6618
  unlinkSync2(statsPath);
5777
6619
  } catch {
@@ -6028,9 +6870,9 @@ var init_llm_analyzer = __esm({
6028
6870
  });
6029
6871
 
6030
6872
  // src/brain/rule-store.ts
6031
- 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";
6032
6874
  import { join as join7 } from "path";
6033
- import { homedir as homedir5 } from "os";
6875
+ import { homedir as homedir4 } from "os";
6034
6876
  function getRulesPath() {
6035
6877
  const envPath = process.env[ENV_RULES_PATH];
6036
6878
  if (envPath) return envPath;
@@ -6048,7 +6890,7 @@ var init_rule_store = __esm({
6048
6890
  "src/brain/rule-store.ts"() {
6049
6891
  "use strict";
6050
6892
  init_esm_shims();
6051
- DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
6893
+ DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
6052
6894
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
6053
6895
  STATUS_DRAFT = "draft";
6054
6896
  STATUS_APPROVED = "approved";
@@ -6065,7 +6907,7 @@ var init_rule_store = __esm({
6065
6907
  this.data = this.load();
6066
6908
  }
6067
6909
  load() {
6068
- if (!existsSync11(this.path)) {
6910
+ if (!existsSync12(this.path)) {
6069
6911
  return { version: 1, rules: [], pattern_candidates: [] };
6070
6912
  }
6071
6913
  try {
@@ -8080,18 +8922,21 @@ var coordinator_exports = {};
8080
8922
  __export(coordinator_exports, {
8081
8923
  TRUSTED_SKILLS_BASE: () => TRUSTED_SKILLS_BASE,
8082
8924
  _resetBrainForTest: () => _resetBrainForTest,
8083
- runAnalysis: () => runAnalysis
8925
+ extractFirstCommand: () => extractFirstCommand,
8926
+ matchesAny: () => matchesAny,
8927
+ runAnalysis: () => runAnalysis,
8928
+ splitCommandSegments: () => splitCommandSegments,
8929
+ whitelistTier: () => whitelistTier
8084
8930
  });
8085
- function extractFirstCommand(command) {
8086
- let main2 = command.split("|")[0].trim();
8087
- for (const sep of ["&&", "||"]) {
8088
- const idx = main2.indexOf(sep);
8089
- if (idx !== -1) {
8090
- main2 = main2.substring(0, idx).trim();
8091
- break;
8092
- }
8093
- }
8094
- 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 "";
8095
8940
  }
8096
8941
  function extractRawCommand(detail) {
8097
8942
  const m = detail.match(/command=([^,]+)/);
@@ -8122,7 +8967,7 @@ async function runAnalysis(input) {
8122
8967
  if (heuristicResult && !heuristicResult.allowed) {
8123
8968
  const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && heuristicResult.riskType === "file_access";
8124
8969
  if (!skipInlineFileAccess) {
8125
- 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";
8126
8971
  if (shouldDenyL1) {
8127
8972
  return heuristicResult;
8128
8973
  }
@@ -8132,6 +8977,19 @@ async function runAnalysis(input) {
8132
8977
  if (input.aNext.actionType.toLowerCase() === "bash") {
8133
8978
  scriptChain = analyzeScriptContent2(input);
8134
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
+ }
8135
8993
  for (const node of scriptChain.nodes) {
8136
8994
  if (node.l1Hit && (node.l1RiskLevel === "high" || node.l1RiskLevel === "critical")) {
8137
8995
  return {
@@ -8219,11 +9077,15 @@ async function runAnalysis(input) {
8219
9077
  let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
8220
9078
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
8221
9079
  const firstCmd = extractFirstCommand(lowerDetail);
8222
- if (matchesAny(firstCmd, FULLY_SAFE_BASH_PATTERNS)) {
8223
- 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(
8224
9086
  (p) => p.severity === "critical" || p.severity === "high"
8225
- ) && DANGEROUS_PATTERNS.some(
8226
- (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)
8227
9089
  );
8228
9090
  const hasRedirectWrite = isWriteOperation(firstCmd, lowerDetail);
8229
9091
  if (!fullCmdHit && !hasRedirectWrite) {
@@ -8238,7 +9100,7 @@ async function runAnalysis(input) {
8238
9100
  };
8239
9101
  }
8240
9102
  }
8241
- if (matchesAny(firstCmd, READONLY_SENSITIVE_BASH_PATTERNS)) {
9103
+ if (tier !== "") {
8242
9104
  skipLLM = true;
8243
9105
  }
8244
9106
  }
@@ -8247,8 +9109,8 @@ async function runAnalysis(input) {
8247
9109
  const effectiveResult = logicResult && !logicResult.allowed ? logicResult : l1FileAccessHit ? heuristicResult : null;
8248
9110
  if (effectiveResult) {
8249
9111
  const isCredentialPath = !!effectiveResult.isCredential;
8250
- const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && effectiveResult.riskType === "file_access";
8251
- if (skipInlineFileAccess) {
9112
+ const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && l1FileAccessHit;
9113
+ if (isInlineScriptCmd || isLiteralTextCmd) {
8252
9114
  const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
8253
9115
  if (inlineJudgment.actionable) {
8254
9116
  if (!inlineJudgment.allowed) {
@@ -8444,13 +9306,15 @@ async function runAnalysis(input) {
8444
9306
  }
8445
9307
  return llmResult;
8446
9308
  }
8447
- 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;
8448
9310
  var init_coordinator = __esm({
8449
9311
  "src/engine/coordinator.ts"() {
8450
9312
  "use strict";
8451
9313
  init_esm_shims();
8452
9314
  init_heuristic();
8453
9315
  init_logic_rules();
9316
+ init_platform_traits();
9317
+ init_shell_split();
8454
9318
  init_inline_analyzer();
8455
9319
  init_llm_analyzer();
8456
9320
  init_script_content_analyzer();
@@ -8480,53 +9344,6 @@ var init_coordinator = __esm({
8480
9344
  "filemgr-readfile",
8481
9345
  "filemgr-grepfiles"
8482
9346
  ]);
8483
- FULLY_SAFE_BASH_PATTERNS = [
8484
- /^ls(?:\s|$)/i,
8485
- /^dir(?:\s|$)/i,
8486
- /^pwd(?:\s|$)/i,
8487
- /^which\s+/i,
8488
- /^whereis\s+/i,
8489
- /^realpath\s+/i,
8490
- /^basename\s+/i,
8491
- /^dirname\s+/i,
8492
- /^file\s+/i,
8493
- /^stat\s+/i,
8494
- /^du(?:\s|$)/i,
8495
- /^echo\s+/i,
8496
- /^printf\s+/i,
8497
- /^type\s+/i,
8498
- /^command\s+/i,
8499
- // 系统信息查询(只读,不涉及文件/网络/权限)
8500
- /^whoami(?:\s|$)/i,
8501
- /^id(?:\s|$)/i,
8502
- /^hostname(?:\s|$)/i,
8503
- /^uname(?:\s|$)/i,
8504
- /^date(?:\s|$)/i,
8505
- /^env(?:\s|$)/i,
8506
- /^printenv(?:\s|$)/i,
8507
- /^tty(?:\s|$)/i,
8508
- /^arch(?:\s|$)/i,
8509
- /^uptime(?:\s|$)/i,
8510
- /^groups(?:\s|$)/i,
8511
- /^logname(?:\s|$)/i
8512
- ];
8513
- READONLY_SENSITIVE_BASH_PATTERNS = [
8514
- /^cat\s+/i,
8515
- /^head(?:\s|$)/i,
8516
- /^tail(?:\s|$)/i,
8517
- /^less(?:\s|$)/i,
8518
- /^more(?:\s|$)/i,
8519
- /^wc(?:\s|$)/i,
8520
- /^grep(?:\s|$)/i,
8521
- /^find\s+/i,
8522
- /^ag(?:\s|$)/i,
8523
- /^rg(?:\s|$)/i,
8524
- /^awk\s+/i,
8525
- /^sed\s+/i,
8526
- /^cut\s+/i,
8527
- /^sort(?:\s|$)/i,
8528
- /^uniq(?:\s|$)/i
8529
- ];
8530
9347
  _brainStore = null;
8531
9348
  _brainExtractor = null;
8532
9349
  _brainInitialized = false;
@@ -8534,795 +9351,6 @@ var init_coordinator = __esm({
8534
9351
  }
8535
9352
  });
8536
9353
 
8537
- // node_modules/better-sqlite3/lib/util.js
8538
- var require_util = __commonJS({
8539
- "node_modules/better-sqlite3/lib/util.js"(exports) {
8540
- "use strict";
8541
- init_esm_shims();
8542
- exports.getBooleanOption = (options, key) => {
8543
- let value = false;
8544
- if (key in options && typeof (value = options[key]) !== "boolean") {
8545
- throw new TypeError(`Expected the "${key}" option to be a boolean`);
8546
- }
8547
- return value;
8548
- };
8549
- exports.cppdb = /* @__PURE__ */ Symbol();
8550
- exports.inspect = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
8551
- }
8552
- });
8553
-
8554
- // node_modules/better-sqlite3/lib/sqlite-error.js
8555
- var require_sqlite_error = __commonJS({
8556
- "node_modules/better-sqlite3/lib/sqlite-error.js"(exports, module) {
8557
- "use strict";
8558
- init_esm_shims();
8559
- var descriptor = { value: "SqliteError", writable: true, enumerable: false, configurable: true };
8560
- function SqliteError(message, code) {
8561
- if (new.target !== SqliteError) {
8562
- return new SqliteError(message, code);
8563
- }
8564
- if (typeof code !== "string") {
8565
- throw new TypeError("Expected second argument to be a string");
8566
- }
8567
- Error.call(this, message);
8568
- descriptor.value = "" + message;
8569
- Object.defineProperty(this, "message", descriptor);
8570
- Error.captureStackTrace(this, SqliteError);
8571
- this.code = code;
8572
- }
8573
- Object.setPrototypeOf(SqliteError, Error);
8574
- Object.setPrototypeOf(SqliteError.prototype, Error.prototype);
8575
- Object.defineProperty(SqliteError.prototype, "name", descriptor);
8576
- module.exports = SqliteError;
8577
- }
8578
- });
8579
-
8580
- // node_modules/file-uri-to-path/index.js
8581
- var require_file_uri_to_path = __commonJS({
8582
- "node_modules/file-uri-to-path/index.js"(exports, module) {
8583
- "use strict";
8584
- init_esm_shims();
8585
- var sep = __require("path").sep || "/";
8586
- module.exports = fileUriToPath;
8587
- function fileUriToPath(uri) {
8588
- if ("string" != typeof uri || uri.length <= 7 || "file://" != uri.substring(0, 7)) {
8589
- throw new TypeError("must pass in a file:// URI to convert to a file path");
8590
- }
8591
- var rest = decodeURI(uri.substring(7));
8592
- var firstSlash = rest.indexOf("/");
8593
- var host = rest.substring(0, firstSlash);
8594
- var path6 = rest.substring(firstSlash + 1);
8595
- if ("localhost" == host) host = "";
8596
- if (host) {
8597
- host = sep + sep + host;
8598
- }
8599
- path6 = path6.replace(/^(.+)\|/, "$1:");
8600
- if (sep == "\\") {
8601
- path6 = path6.replace(/\//g, "\\");
8602
- }
8603
- if (/^.+\:/.test(path6)) {
8604
- } else {
8605
- path6 = sep + path6;
8606
- }
8607
- return host + path6;
8608
- }
8609
- }
8610
- });
8611
-
8612
- // node_modules/bindings/bindings.js
8613
- var require_bindings = __commonJS({
8614
- "node_modules/bindings/bindings.js"(exports, module) {
8615
- "use strict";
8616
- init_esm_shims();
8617
- var fs7 = __require("fs");
8618
- var path6 = __require("path");
8619
- var fileURLToPath11 = require_file_uri_to_path();
8620
- var join11 = path6.join;
8621
- var dirname11 = path6.dirname;
8622
- var exists = fs7.accessSync && function(path7) {
8623
- try {
8624
- fs7.accessSync(path7);
8625
- } catch (e) {
8626
- return false;
8627
- }
8628
- return true;
8629
- } || fs7.existsSync || path6.existsSync;
8630
- var defaults = {
8631
- arrow: process.env.NODE_BINDINGS_ARROW || " \u2192 ",
8632
- compiled: process.env.NODE_BINDINGS_COMPILED_DIR || "compiled",
8633
- platform: process.platform,
8634
- arch: process.arch,
8635
- nodePreGyp: "node-v" + process.versions.modules + "-" + process.platform + "-" + process.arch,
8636
- version: process.versions.node,
8637
- bindings: "bindings.node",
8638
- try: [
8639
- // node-gyp's linked version in the "build" dir
8640
- ["module_root", "build", "bindings"],
8641
- // node-waf and gyp_addon (a.k.a node-gyp)
8642
- ["module_root", "build", "Debug", "bindings"],
8643
- ["module_root", "build", "Release", "bindings"],
8644
- // Debug files, for development (legacy behavior, remove for node v0.9)
8645
- ["module_root", "out", "Debug", "bindings"],
8646
- ["module_root", "Debug", "bindings"],
8647
- // Release files, but manually compiled (legacy behavior, remove for node v0.9)
8648
- ["module_root", "out", "Release", "bindings"],
8649
- ["module_root", "Release", "bindings"],
8650
- // Legacy from node-waf, node <= 0.4.x
8651
- ["module_root", "build", "default", "bindings"],
8652
- // Production "Release" buildtype binary (meh...)
8653
- ["module_root", "compiled", "version", "platform", "arch", "bindings"],
8654
- // node-qbs builds
8655
- ["module_root", "addon-build", "release", "install-root", "bindings"],
8656
- ["module_root", "addon-build", "debug", "install-root", "bindings"],
8657
- ["module_root", "addon-build", "default", "install-root", "bindings"],
8658
- // node-pre-gyp path ./lib/binding/{node_abi}-{platform}-{arch}
8659
- ["module_root", "lib", "binding", "nodePreGyp", "bindings"]
8660
- ]
8661
- };
8662
- function bindings(opts) {
8663
- if (typeof opts == "string") {
8664
- opts = { bindings: opts };
8665
- } else if (!opts) {
8666
- opts = {};
8667
- }
8668
- Object.keys(defaults).map(function(i2) {
8669
- if (!(i2 in opts)) opts[i2] = defaults[i2];
8670
- });
8671
- if (!opts.module_root) {
8672
- opts.module_root = exports.getRoot(exports.getFileName());
8673
- }
8674
- if (path6.extname(opts.bindings) != ".node") {
8675
- opts.bindings += ".node";
8676
- }
8677
- var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
8678
- var tries = [], i = 0, l = opts.try.length, n, b, err;
8679
- for (; i < l; i++) {
8680
- n = join11.apply(
8681
- null,
8682
- opts.try[i].map(function(p) {
8683
- return opts[p] || p;
8684
- })
8685
- );
8686
- tries.push(n);
8687
- try {
8688
- b = opts.path ? requireFunc.resolve(n) : requireFunc(n);
8689
- if (!opts.path) {
8690
- b.path = n;
8691
- }
8692
- return b;
8693
- } catch (e) {
8694
- if (e.code !== "MODULE_NOT_FOUND" && e.code !== "QUALIFIED_PATH_RESOLUTION_FAILED" && !/not find/i.test(e.message)) {
8695
- throw e;
8696
- }
8697
- }
8698
- }
8699
- err = new Error(
8700
- "Could not locate the bindings file. Tried:\n" + tries.map(function(a) {
8701
- return opts.arrow + a;
8702
- }).join("\n")
8703
- );
8704
- err.tries = tries;
8705
- throw err;
8706
- }
8707
- module.exports = exports = bindings;
8708
- exports.getFileName = function getFileName(calling_file) {
8709
- var origPST = Error.prepareStackTrace, origSTL = Error.stackTraceLimit, dummy = {}, fileName;
8710
- Error.stackTraceLimit = 10;
8711
- Error.prepareStackTrace = function(e, st) {
8712
- for (var i = 0, l = st.length; i < l; i++) {
8713
- fileName = st[i].getFileName();
8714
- if (fileName !== __filename) {
8715
- if (calling_file) {
8716
- if (fileName !== calling_file) {
8717
- return;
8718
- }
8719
- } else {
8720
- return;
8721
- }
8722
- }
8723
- }
8724
- };
8725
- Error.captureStackTrace(dummy);
8726
- dummy.stack;
8727
- Error.prepareStackTrace = origPST;
8728
- Error.stackTraceLimit = origSTL;
8729
- var fileSchema = "file://";
8730
- if (fileName.indexOf(fileSchema) === 0) {
8731
- fileName = fileURLToPath11(fileName);
8732
- }
8733
- return fileName;
8734
- };
8735
- exports.getRoot = function getRoot(file) {
8736
- var dir = dirname11(file), prev;
8737
- while (true) {
8738
- if (dir === ".") {
8739
- dir = process.cwd();
8740
- }
8741
- if (exists(join11(dir, "package.json")) || exists(join11(dir, "node_modules"))) {
8742
- return dir;
8743
- }
8744
- if (prev === dir) {
8745
- throw new Error(
8746
- 'Could not find module root given file: "' + file + '". Do you have a `package.json` file? '
8747
- );
8748
- }
8749
- prev = dir;
8750
- dir = join11(dir, "..");
8751
- }
8752
- };
8753
- }
8754
- });
8755
-
8756
- // node_modules/better-sqlite3/lib/methods/wrappers.js
8757
- var require_wrappers = __commonJS({
8758
- "node_modules/better-sqlite3/lib/methods/wrappers.js"(exports) {
8759
- "use strict";
8760
- init_esm_shims();
8761
- var { cppdb } = require_util();
8762
- exports.prepare = function prepare(sql) {
8763
- return this[cppdb].prepare(sql, this, false);
8764
- };
8765
- exports.exec = function exec(sql) {
8766
- this[cppdb].exec(sql);
8767
- return this;
8768
- };
8769
- exports.close = function close() {
8770
- this[cppdb].close();
8771
- return this;
8772
- };
8773
- exports.loadExtension = function loadExtension(...args) {
8774
- this[cppdb].loadExtension(...args);
8775
- return this;
8776
- };
8777
- exports.defaultSafeIntegers = function defaultSafeIntegers(...args) {
8778
- this[cppdb].defaultSafeIntegers(...args);
8779
- return this;
8780
- };
8781
- exports.unsafeMode = function unsafeMode(...args) {
8782
- this[cppdb].unsafeMode(...args);
8783
- return this;
8784
- };
8785
- exports.getters = {
8786
- name: {
8787
- get: function name() {
8788
- return this[cppdb].name;
8789
- },
8790
- enumerable: true
8791
- },
8792
- open: {
8793
- get: function open() {
8794
- return this[cppdb].open;
8795
- },
8796
- enumerable: true
8797
- },
8798
- inTransaction: {
8799
- get: function inTransaction() {
8800
- return this[cppdb].inTransaction;
8801
- },
8802
- enumerable: true
8803
- },
8804
- readonly: {
8805
- get: function readonly() {
8806
- return this[cppdb].readonly;
8807
- },
8808
- enumerable: true
8809
- },
8810
- memory: {
8811
- get: function memory() {
8812
- return this[cppdb].memory;
8813
- },
8814
- enumerable: true
8815
- }
8816
- };
8817
- }
8818
- });
8819
-
8820
- // node_modules/better-sqlite3/lib/methods/transaction.js
8821
- var require_transaction = __commonJS({
8822
- "node_modules/better-sqlite3/lib/methods/transaction.js"(exports, module) {
8823
- "use strict";
8824
- init_esm_shims();
8825
- var { cppdb } = require_util();
8826
- var controllers = /* @__PURE__ */ new WeakMap();
8827
- module.exports = function transaction(fn) {
8828
- if (typeof fn !== "function") throw new TypeError("Expected first argument to be a function");
8829
- const db = this[cppdb];
8830
- const controller = getController(db, this);
8831
- const { apply } = Function.prototype;
8832
- const properties = {
8833
- default: { value: wrapTransaction(apply, fn, db, controller.default) },
8834
- deferred: { value: wrapTransaction(apply, fn, db, controller.deferred) },
8835
- immediate: { value: wrapTransaction(apply, fn, db, controller.immediate) },
8836
- exclusive: { value: wrapTransaction(apply, fn, db, controller.exclusive) },
8837
- database: { value: this, enumerable: true }
8838
- };
8839
- Object.defineProperties(properties.default.value, properties);
8840
- Object.defineProperties(properties.deferred.value, properties);
8841
- Object.defineProperties(properties.immediate.value, properties);
8842
- Object.defineProperties(properties.exclusive.value, properties);
8843
- return properties.default.value;
8844
- };
8845
- var getController = (db, self) => {
8846
- let controller = controllers.get(db);
8847
- if (!controller) {
8848
- const shared = {
8849
- commit: db.prepare("COMMIT", self, false),
8850
- rollback: db.prepare("ROLLBACK", self, false),
8851
- savepoint: db.prepare("SAVEPOINT ` _bs3. `", self, false),
8852
- release: db.prepare("RELEASE ` _bs3. `", self, false),
8853
- rollbackTo: db.prepare("ROLLBACK TO ` _bs3. `", self, false)
8854
- };
8855
- controllers.set(db, controller = {
8856
- default: Object.assign({ begin: db.prepare("BEGIN", self, false) }, shared),
8857
- deferred: Object.assign({ begin: db.prepare("BEGIN DEFERRED", self, false) }, shared),
8858
- immediate: Object.assign({ begin: db.prepare("BEGIN IMMEDIATE", self, false) }, shared),
8859
- exclusive: Object.assign({ begin: db.prepare("BEGIN EXCLUSIVE", self, false) }, shared)
8860
- });
8861
- }
8862
- return controller;
8863
- };
8864
- var wrapTransaction = (apply, fn, db, { begin, commit, rollback, savepoint, release, rollbackTo }) => function sqliteTransaction() {
8865
- let before, after, undo;
8866
- if (db.inTransaction) {
8867
- before = savepoint;
8868
- after = release;
8869
- undo = rollbackTo;
8870
- } else {
8871
- before = begin;
8872
- after = commit;
8873
- undo = rollback;
8874
- }
8875
- before.run();
8876
- try {
8877
- const result = apply.call(fn, this, arguments);
8878
- if (result && typeof result.then === "function") {
8879
- throw new TypeError("Transaction function cannot return a promise");
8880
- }
8881
- after.run();
8882
- return result;
8883
- } catch (ex) {
8884
- if (db.inTransaction) {
8885
- undo.run();
8886
- if (undo !== rollback) after.run();
8887
- }
8888
- throw ex;
8889
- }
8890
- };
8891
- }
8892
- });
8893
-
8894
- // node_modules/better-sqlite3/lib/methods/pragma.js
8895
- var require_pragma = __commonJS({
8896
- "node_modules/better-sqlite3/lib/methods/pragma.js"(exports, module) {
8897
- "use strict";
8898
- init_esm_shims();
8899
- var { getBooleanOption, cppdb } = require_util();
8900
- module.exports = function pragma(source, options) {
8901
- if (options == null) options = {};
8902
- if (typeof source !== "string") throw new TypeError("Expected first argument to be a string");
8903
- if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
8904
- const simple = getBooleanOption(options, "simple");
8905
- const stmt = this[cppdb].prepare(`PRAGMA ${source}`, this, true);
8906
- return simple ? stmt.pluck().get() : stmt.all();
8907
- };
8908
- }
8909
- });
8910
-
8911
- // node_modules/better-sqlite3/lib/methods/backup.js
8912
- var require_backup = __commonJS({
8913
- "node_modules/better-sqlite3/lib/methods/backup.js"(exports, module) {
8914
- "use strict";
8915
- init_esm_shims();
8916
- var fs7 = __require("fs");
8917
- var path6 = __require("path");
8918
- var { promisify } = __require("util");
8919
- var { cppdb } = require_util();
8920
- var fsAccess = promisify(fs7.access);
8921
- module.exports = async function backup(filename, options) {
8922
- if (options == null) options = {};
8923
- if (typeof filename !== "string") throw new TypeError("Expected first argument to be a string");
8924
- if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
8925
- filename = filename.trim();
8926
- const attachedName = "attached" in options ? options.attached : "main";
8927
- const handler = "progress" in options ? options.progress : null;
8928
- if (!filename) throw new TypeError("Backup filename cannot be an empty string");
8929
- if (filename === ":memory:") throw new TypeError('Invalid backup filename ":memory:"');
8930
- if (typeof attachedName !== "string") throw new TypeError('Expected the "attached" option to be a string');
8931
- if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
8932
- if (handler != null && typeof handler !== "function") throw new TypeError('Expected the "progress" option to be a function');
8933
- await fsAccess(path6.dirname(filename)).catch(() => {
8934
- throw new TypeError("Cannot save backup because the directory does not exist");
8935
- });
8936
- const isNewFile = await fsAccess(filename).then(() => false, () => true);
8937
- return runBackup(this[cppdb].backup(this, attachedName, filename, isNewFile), handler || null);
8938
- };
8939
- var runBackup = (backup, handler) => {
8940
- let rate = 0;
8941
- let useDefault = true;
8942
- return new Promise((resolve10, reject) => {
8943
- setImmediate(function step() {
8944
- try {
8945
- const progress = backup.transfer(rate);
8946
- if (!progress.remainingPages) {
8947
- backup.close();
8948
- resolve10(progress);
8949
- return;
8950
- }
8951
- if (useDefault) {
8952
- useDefault = false;
8953
- rate = 100;
8954
- }
8955
- if (handler) {
8956
- const ret = handler(progress);
8957
- if (ret !== void 0) {
8958
- if (typeof ret === "number" && ret === ret) rate = Math.max(0, Math.min(2147483647, Math.round(ret)));
8959
- else throw new TypeError("Expected progress callback to return a number or undefined");
8960
- }
8961
- }
8962
- setImmediate(step);
8963
- } catch (err) {
8964
- backup.close();
8965
- reject(err);
8966
- }
8967
- });
8968
- });
8969
- };
8970
- }
8971
- });
8972
-
8973
- // node_modules/better-sqlite3/lib/methods/serialize.js
8974
- var require_serialize = __commonJS({
8975
- "node_modules/better-sqlite3/lib/methods/serialize.js"(exports, module) {
8976
- "use strict";
8977
- init_esm_shims();
8978
- var { cppdb } = require_util();
8979
- module.exports = function serialize(options) {
8980
- if (options == null) options = {};
8981
- if (typeof options !== "object") throw new TypeError("Expected first argument to be an options object");
8982
- const attachedName = "attached" in options ? options.attached : "main";
8983
- if (typeof attachedName !== "string") throw new TypeError('Expected the "attached" option to be a string');
8984
- if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
8985
- return this[cppdb].serialize(attachedName);
8986
- };
8987
- }
8988
- });
8989
-
8990
- // node_modules/better-sqlite3/lib/methods/function.js
8991
- var require_function = __commonJS({
8992
- "node_modules/better-sqlite3/lib/methods/function.js"(exports, module) {
8993
- "use strict";
8994
- init_esm_shims();
8995
- var { getBooleanOption, cppdb } = require_util();
8996
- module.exports = function defineFunction(name, options, fn) {
8997
- if (options == null) options = {};
8998
- if (typeof options === "function") {
8999
- fn = options;
9000
- options = {};
9001
- }
9002
- if (typeof name !== "string") throw new TypeError("Expected first argument to be a string");
9003
- if (typeof fn !== "function") throw new TypeError("Expected last argument to be a function");
9004
- if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
9005
- if (!name) throw new TypeError("User-defined function name cannot be an empty string");
9006
- const safeIntegers = "safeIntegers" in options ? +getBooleanOption(options, "safeIntegers") : 2;
9007
- const deterministic = getBooleanOption(options, "deterministic");
9008
- const directOnly = getBooleanOption(options, "directOnly");
9009
- const varargs = getBooleanOption(options, "varargs");
9010
- let argCount = -1;
9011
- if (!varargs) {
9012
- argCount = fn.length;
9013
- if (!Number.isInteger(argCount) || argCount < 0) throw new TypeError("Expected function.length to be a positive integer");
9014
- if (argCount > 100) throw new RangeError("User-defined functions cannot have more than 100 arguments");
9015
- }
9016
- this[cppdb].function(fn, name, argCount, safeIntegers, deterministic, directOnly);
9017
- return this;
9018
- };
9019
- }
9020
- });
9021
-
9022
- // node_modules/better-sqlite3/lib/methods/aggregate.js
9023
- var require_aggregate = __commonJS({
9024
- "node_modules/better-sqlite3/lib/methods/aggregate.js"(exports, module) {
9025
- "use strict";
9026
- init_esm_shims();
9027
- var { getBooleanOption, cppdb } = require_util();
9028
- module.exports = function defineAggregate(name, options) {
9029
- if (typeof name !== "string") throw new TypeError("Expected first argument to be a string");
9030
- if (typeof options !== "object" || options === null) throw new TypeError("Expected second argument to be an options object");
9031
- if (!name) throw new TypeError("User-defined function name cannot be an empty string");
9032
- const start = "start" in options ? options.start : null;
9033
- const step = getFunctionOption(options, "step", true);
9034
- const inverse = getFunctionOption(options, "inverse", false);
9035
- const result = getFunctionOption(options, "result", false);
9036
- const safeIntegers = "safeIntegers" in options ? +getBooleanOption(options, "safeIntegers") : 2;
9037
- const deterministic = getBooleanOption(options, "deterministic");
9038
- const directOnly = getBooleanOption(options, "directOnly");
9039
- const varargs = getBooleanOption(options, "varargs");
9040
- let argCount = -1;
9041
- if (!varargs) {
9042
- argCount = Math.max(getLength(step), inverse ? getLength(inverse) : 0);
9043
- if (argCount > 0) argCount -= 1;
9044
- if (argCount > 100) throw new RangeError("User-defined functions cannot have more than 100 arguments");
9045
- }
9046
- this[cppdb].aggregate(start, step, inverse, result, name, argCount, safeIntegers, deterministic, directOnly);
9047
- return this;
9048
- };
9049
- var getFunctionOption = (options, key, required) => {
9050
- const value = key in options ? options[key] : null;
9051
- if (typeof value === "function") return value;
9052
- if (value != null) throw new TypeError(`Expected the "${key}" option to be a function`);
9053
- if (required) throw new TypeError(`Missing required option "${key}"`);
9054
- return null;
9055
- };
9056
- var getLength = ({ length }) => {
9057
- if (Number.isInteger(length) && length >= 0) return length;
9058
- throw new TypeError("Expected function.length to be a positive integer");
9059
- };
9060
- }
9061
- });
9062
-
9063
- // node_modules/better-sqlite3/lib/methods/table.js
9064
- var require_table = __commonJS({
9065
- "node_modules/better-sqlite3/lib/methods/table.js"(exports, module) {
9066
- "use strict";
9067
- init_esm_shims();
9068
- var { cppdb } = require_util();
9069
- module.exports = function defineTable(name, factory) {
9070
- if (typeof name !== "string") throw new TypeError("Expected first argument to be a string");
9071
- if (!name) throw new TypeError("Virtual table module name cannot be an empty string");
9072
- let eponymous = false;
9073
- if (typeof factory === "object" && factory !== null) {
9074
- eponymous = true;
9075
- factory = defer(parseTableDefinition(factory, "used", name));
9076
- } else {
9077
- if (typeof factory !== "function") throw new TypeError("Expected second argument to be a function or a table definition object");
9078
- factory = wrapFactory(factory);
9079
- }
9080
- this[cppdb].table(factory, name, eponymous);
9081
- return this;
9082
- };
9083
- function wrapFactory(factory) {
9084
- return function virtualTableFactory(moduleName, databaseName, tableName, ...args) {
9085
- const thisObject = {
9086
- module: moduleName,
9087
- database: databaseName,
9088
- table: tableName
9089
- };
9090
- const def = apply.call(factory, thisObject, args);
9091
- if (typeof def !== "object" || def === null) {
9092
- throw new TypeError(`Virtual table module "${moduleName}" did not return a table definition object`);
9093
- }
9094
- return parseTableDefinition(def, "returned", moduleName);
9095
- };
9096
- }
9097
- function parseTableDefinition(def, verb, moduleName) {
9098
- if (!hasOwnProperty.call(def, "rows")) {
9099
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition without a "rows" property`);
9100
- }
9101
- if (!hasOwnProperty.call(def, "columns")) {
9102
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition without a "columns" property`);
9103
- }
9104
- const rows = def.rows;
9105
- if (typeof rows !== "function" || Object.getPrototypeOf(rows) !== GeneratorFunctionPrototype) {
9106
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "rows" property (should be a generator function)`);
9107
- }
9108
- let columns = def.columns;
9109
- if (!Array.isArray(columns) || !(columns = [...columns]).every((x) => typeof x === "string")) {
9110
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "columns" property (should be an array of strings)`);
9111
- }
9112
- if (columns.length !== new Set(columns).size) {
9113
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with duplicate column names`);
9114
- }
9115
- if (!columns.length) {
9116
- throw new RangeError(`Virtual table module "${moduleName}" ${verb} a table definition with zero columns`);
9117
- }
9118
- let parameters;
9119
- if (hasOwnProperty.call(def, "parameters")) {
9120
- parameters = def.parameters;
9121
- if (!Array.isArray(parameters) || !(parameters = [...parameters]).every((x) => typeof x === "string")) {
9122
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "parameters" property (should be an array of strings)`);
9123
- }
9124
- } else {
9125
- parameters = inferParameters(rows);
9126
- }
9127
- if (parameters.length !== new Set(parameters).size) {
9128
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with duplicate parameter names`);
9129
- }
9130
- if (parameters.length > 32) {
9131
- throw new RangeError(`Virtual table module "${moduleName}" ${verb} a table definition with more than the maximum number of 32 parameters`);
9132
- }
9133
- for (const parameter of parameters) {
9134
- if (columns.includes(parameter)) {
9135
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with column "${parameter}" which was ambiguously defined as both a column and parameter`);
9136
- }
9137
- }
9138
- let safeIntegers = 2;
9139
- if (hasOwnProperty.call(def, "safeIntegers")) {
9140
- const bool = def.safeIntegers;
9141
- if (typeof bool !== "boolean") {
9142
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "safeIntegers" property (should be a boolean)`);
9143
- }
9144
- safeIntegers = +bool;
9145
- }
9146
- let directOnly = false;
9147
- if (hasOwnProperty.call(def, "directOnly")) {
9148
- directOnly = def.directOnly;
9149
- if (typeof directOnly !== "boolean") {
9150
- throw new TypeError(`Virtual table module "${moduleName}" ${verb} a table definition with an invalid "directOnly" property (should be a boolean)`);
9151
- }
9152
- }
9153
- const columnDefinitions = [
9154
- ...parameters.map(identifier).map((str) => `${str} HIDDEN`),
9155
- ...columns.map(identifier)
9156
- ];
9157
- return [
9158
- `CREATE TABLE x(${columnDefinitions.join(", ")});`,
9159
- wrapGenerator(rows, new Map(columns.map((x, i) => [x, parameters.length + i])), moduleName),
9160
- parameters,
9161
- safeIntegers,
9162
- directOnly
9163
- ];
9164
- }
9165
- function wrapGenerator(generator, columnMap, moduleName) {
9166
- return function* virtualTable(...args) {
9167
- const output = args.map((x) => Buffer.isBuffer(x) ? Buffer.from(x) : x);
9168
- for (let i = 0; i < columnMap.size; ++i) {
9169
- output.push(null);
9170
- }
9171
- for (const row of generator(...args)) {
9172
- if (Array.isArray(row)) {
9173
- extractRowArray(row, output, columnMap.size, moduleName);
9174
- yield output;
9175
- } else if (typeof row === "object" && row !== null) {
9176
- extractRowObject(row, output, columnMap, moduleName);
9177
- yield output;
9178
- } else {
9179
- throw new TypeError(`Virtual table module "${moduleName}" yielded something that isn't a valid row object`);
9180
- }
9181
- }
9182
- };
9183
- }
9184
- function extractRowArray(row, output, columnCount, moduleName) {
9185
- if (row.length !== columnCount) {
9186
- throw new TypeError(`Virtual table module "${moduleName}" yielded a row with an incorrect number of columns`);
9187
- }
9188
- const offset = output.length - columnCount;
9189
- for (let i = 0; i < columnCount; ++i) {
9190
- output[i + offset] = row[i];
9191
- }
9192
- }
9193
- function extractRowObject(row, output, columnMap, moduleName) {
9194
- let count = 0;
9195
- for (const key of Object.keys(row)) {
9196
- const index = columnMap.get(key);
9197
- if (index === void 0) {
9198
- throw new TypeError(`Virtual table module "${moduleName}" yielded a row with an undeclared column "${key}"`);
9199
- }
9200
- output[index] = row[key];
9201
- count += 1;
9202
- }
9203
- if (count !== columnMap.size) {
9204
- throw new TypeError(`Virtual table module "${moduleName}" yielded a row with missing columns`);
9205
- }
9206
- }
9207
- function inferParameters({ length }) {
9208
- if (!Number.isInteger(length) || length < 0) {
9209
- throw new TypeError("Expected function.length to be a positive integer");
9210
- }
9211
- const params = [];
9212
- for (let i = 0; i < length; ++i) {
9213
- params.push(`$${i + 1}`);
9214
- }
9215
- return params;
9216
- }
9217
- var { hasOwnProperty } = Object.prototype;
9218
- var { apply } = Function.prototype;
9219
- var GeneratorFunctionPrototype = Object.getPrototypeOf(function* () {
9220
- });
9221
- var identifier = (str) => `"${str.replace(/"/g, '""')}"`;
9222
- var defer = (x) => () => x;
9223
- }
9224
- });
9225
-
9226
- // node_modules/better-sqlite3/lib/methods/inspect.js
9227
- var require_inspect = __commonJS({
9228
- "node_modules/better-sqlite3/lib/methods/inspect.js"(exports, module) {
9229
- "use strict";
9230
- init_esm_shims();
9231
- var DatabaseInspection = function Database() {
9232
- };
9233
- module.exports = function inspect(depth, opts) {
9234
- return Object.assign(new DatabaseInspection(), this);
9235
- };
9236
- }
9237
- });
9238
-
9239
- // node_modules/better-sqlite3/lib/database.js
9240
- var require_database = __commonJS({
9241
- "node_modules/better-sqlite3/lib/database.js"(exports, module) {
9242
- "use strict";
9243
- init_esm_shims();
9244
- var fs7 = __require("fs");
9245
- var path6 = __require("path");
9246
- var util = require_util();
9247
- var SqliteError = require_sqlite_error();
9248
- var DEFAULT_ADDON;
9249
- function Database(filenameGiven, options) {
9250
- if (new.target == null) {
9251
- return new Database(filenameGiven, options);
9252
- }
9253
- let buffer;
9254
- if (Buffer.isBuffer(filenameGiven)) {
9255
- buffer = filenameGiven;
9256
- filenameGiven = ":memory:";
9257
- }
9258
- if (filenameGiven == null) filenameGiven = "";
9259
- if (options == null) options = {};
9260
- if (typeof filenameGiven !== "string") throw new TypeError("Expected first argument to be a string");
9261
- if (typeof options !== "object") throw new TypeError("Expected second argument to be an options object");
9262
- if ("readOnly" in options) throw new TypeError('Misspelled option "readOnly" should be "readonly"');
9263
- if ("memory" in options) throw new TypeError('Option "memory" was removed in v7.0.0 (use ":memory:" filename instead)');
9264
- const filename = filenameGiven.trim();
9265
- const anonymous = filename === "" || filename === ":memory:";
9266
- const readonly = util.getBooleanOption(options, "readonly");
9267
- const fileMustExist = util.getBooleanOption(options, "fileMustExist");
9268
- const timeout = "timeout" in options ? options.timeout : 5e3;
9269
- const verbose = "verbose" in options ? options.verbose : null;
9270
- const nativeBinding = "nativeBinding" in options ? options.nativeBinding : null;
9271
- if (readonly && anonymous && !buffer) throw new TypeError("In-memory/temporary databases cannot be readonly");
9272
- if (!Number.isInteger(timeout) || timeout < 0) throw new TypeError('Expected the "timeout" option to be a positive integer');
9273
- if (timeout > 2147483647) throw new RangeError('Option "timeout" cannot be greater than 2147483647');
9274
- if (verbose != null && typeof verbose !== "function") throw new TypeError('Expected the "verbose" option to be a function');
9275
- if (nativeBinding != null && typeof nativeBinding !== "string" && typeof nativeBinding !== "object") throw new TypeError('Expected the "nativeBinding" option to be a string or addon object');
9276
- let addon;
9277
- if (nativeBinding == null) {
9278
- addon = DEFAULT_ADDON || (DEFAULT_ADDON = require_bindings()("better_sqlite3.node"));
9279
- } else if (typeof nativeBinding === "string") {
9280
- const requireFunc = typeof __non_webpack_require__ === "function" ? __non_webpack_require__ : __require;
9281
- addon = requireFunc(path6.resolve(nativeBinding).replace(/(\.node)?$/, ".node"));
9282
- } else {
9283
- addon = nativeBinding;
9284
- }
9285
- if (!addon.isInitialized) {
9286
- addon.setErrorConstructor(SqliteError);
9287
- addon.isInitialized = true;
9288
- }
9289
- if (!anonymous && !fs7.existsSync(path6.dirname(filename))) {
9290
- throw new TypeError("Cannot open database because the directory does not exist");
9291
- }
9292
- Object.defineProperties(this, {
9293
- [util.cppdb]: { value: new addon.Database(filename, filenameGiven, anonymous, readonly, fileMustExist, timeout, verbose || null, buffer || null) },
9294
- ...wrappers.getters
9295
- });
9296
- }
9297
- var wrappers = require_wrappers();
9298
- Database.prototype.prepare = wrappers.prepare;
9299
- Database.prototype.transaction = require_transaction();
9300
- Database.prototype.pragma = require_pragma();
9301
- Database.prototype.backup = require_backup();
9302
- Database.prototype.serialize = require_serialize();
9303
- Database.prototype.function = require_function();
9304
- Database.prototype.aggregate = require_aggregate();
9305
- Database.prototype.table = require_table();
9306
- Database.prototype.loadExtension = wrappers.loadExtension;
9307
- Database.prototype.exec = wrappers.exec;
9308
- Database.prototype.close = wrappers.close;
9309
- Database.prototype.defaultSafeIntegers = wrappers.defaultSafeIntegers;
9310
- Database.prototype.unsafeMode = wrappers.unsafeMode;
9311
- Database.prototype[util.inspect] = require_inspect();
9312
- module.exports = Database;
9313
- }
9314
- });
9315
-
9316
- // node_modules/better-sqlite3/lib/index.js
9317
- var require_lib = __commonJS({
9318
- "node_modules/better-sqlite3/lib/index.js"(exports, module) {
9319
- "use strict";
9320
- init_esm_shims();
9321
- module.exports = require_database();
9322
- module.exports.SqliteError = require_sqlite_error();
9323
- }
9324
- });
9325
-
9326
9354
  // src/cli.ts
9327
9355
  init_esm_shims();
9328
9356
 
@@ -9350,7 +9378,11 @@ var version = pkg.version;
9350
9378
  // src/models.ts
9351
9379
  var ActionItemSchema = z.object({
9352
9380
  action_type: z.string().describe("Action type, e.g. bash, file_read, file_write"),
9353
- 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)")
9354
9386
  });
9355
9387
  var PromptTurnSchema = z.object({
9356
9388
  text: z.string().default("").describe("User prompt text at this turn"),
@@ -9420,6 +9452,16 @@ init_coordinator();
9420
9452
  // src/actrail.ts
9421
9453
  init_esm_shims();
9422
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
9423
9465
  function escape(value) {
9424
9466
  return value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/=/g, "\\e");
9425
9467
  }
@@ -9444,7 +9486,7 @@ function reportEnforcementEvent(opts) {
9444
9486
  try {
9445
9487
  let Database;
9446
9488
  try {
9447
- Database = require_lib();
9489
+ Database = esmRequire("better-sqlite3");
9448
9490
  } catch {
9449
9491
  console.debug("[AgentMoss] better-sqlite3 not installed, AcTrail reporting disabled");
9450
9492
  return false;
@@ -9645,7 +9687,7 @@ function readJsonDir(dirPath, limit = 1e5) {
9645
9687
  function readSqlite(dbPath, limit = 1e5) {
9646
9688
  let Database;
9647
9689
  try {
9648
- Database = require_lib();
9690
+ Database = esmRequire("better-sqlite3");
9649
9691
  } catch {
9650
9692
  return [];
9651
9693
  }
@@ -9713,15 +9755,21 @@ async function analyzeRoute(c) {
9713
9755
  const result = await runAnalysis({
9714
9756
  sessionId: req.session_id,
9715
9757
  promptSession: req.prompt_session,
9758
+ // output 必须透传:L2 的「授权(consent)」判定看的是**用户对提问的回答**,
9759
+ // 不是提问正文。历史 bug:这里丢了 output,TS 的判定退化成 actionDetail 非空,
9760
+ // 于是只要会话里问过任何问题(无论用户答没答、答的是什么、问的是不是改密)
9761
+ // 都算「已授权」→ 未授权的改密/删用户被放行(fail-open)。
9716
9762
  actionHistory: req.action_history.map((h) => ({
9717
9763
  actionType: h.action_type,
9718
- actionDetail: h.action_detail
9764
+ actionDetail: h.action_detail,
9765
+ output: h.output
9719
9766
  })),
9720
9767
  sessionHistory: (req.session_history || []).map((s) => ({
9721
9768
  sessionId: s.session_id,
9722
9769
  actionHistory: (s.action_history || []).map((h) => ({
9723
9770
  actionType: h.action_type,
9724
- actionDetail: h.action_detail
9771
+ actionDetail: h.action_detail,
9772
+ output: h.output
9725
9773
  }))
9726
9774
  })),
9727
9775
  aNext: {
@@ -9915,7 +9963,7 @@ init_runtime_config();
9915
9963
  init_token_stats();
9916
9964
  init_rules_loader();
9917
9965
  import { Hono } from "hono";
9918
- import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
9966
+ import { readFileSync as readFileSync15, existsSync as existsSync15 } from "fs";
9919
9967
  import { join as join10, dirname as dirname10 } from "path";
9920
9968
  import { fileURLToPath as fileURLToPath9 } from "url";
9921
9969
  var __dirname8 = dirname10(fileURLToPath9(import.meta.url));
@@ -9944,7 +9992,7 @@ function createConsoleRouter() {
9944
9992
  console2.use("/api/*", authMiddleware());
9945
9993
  const serveIndex = (c) => {
9946
9994
  const indexFile = join10(STATIC_DIR, "index.html");
9947
- if (existsSync14(indexFile)) {
9995
+ if (existsSync15(indexFile)) {
9948
9996
  return c.html(readFileSync15(indexFile, "utf-8"));
9949
9997
  }
9950
9998
  return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
@@ -10110,9 +10158,9 @@ function createConsoleRouter() {
10110
10158
  return c.json({ error: "invalid skill_id" }, 400);
10111
10159
  }
10112
10160
  const skillPath = join10(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
10113
- if (!existsSync14(skillPath)) {
10161
+ if (!existsSync15(skillPath)) {
10114
10162
  const builtinPath = join10(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
10115
- if (existsSync14(builtinPath)) return c.text(readFileSync15(builtinPath, "utf-8"));
10163
+ if (existsSync15(builtinPath)) return c.text(readFileSync15(builtinPath, "utf-8"));
10116
10164
  return c.json({ error: "skill not found" }, 404);
10117
10165
  }
10118
10166
  return c.text(readFileSync15(skillPath, "utf-8"));
@@ -10231,6 +10279,38 @@ function createConsoleRouter() {
10231
10279
  }
10232
10280
 
10233
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
+ }
10234
10314
  function createApp() {
10235
10315
  const app = new Hono2();
10236
10316
  app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["*"] }));
@@ -10260,6 +10340,37 @@ async function findFreePort(startPort, host = "127.0.0.1", scan = 100) {
10260
10340
  }
10261
10341
  return startPort;
10262
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
+ }
10263
10374
  async function startServer(host = "127.0.0.1", port = 9090) {
10264
10375
  const actualPort = await findFreePort(port, host);
10265
10376
  const app = createApp();
@@ -10267,6 +10378,8 @@ async function startServer(host = "127.0.0.1", port = 9090) {
10267
10378
  port: actualPort,
10268
10379
  serve: async () => {
10269
10380
  console.log(`AgentMoss HTTP server on ${host}:${actualPort}`);
10381
+ logDeploymentSelfCheck();
10382
+ await warmUpEngine();
10270
10383
  const { serve: honoServe } = await import("@hono/node-server");
10271
10384
  honoServe({ fetch: app.fetch, hostname: host, port: actualPort });
10272
10385
  }
@@ -10354,7 +10467,7 @@ async function main() {
10354
10467
  console.error(`\u9519\u8BEF: \u4E0D\u662F\u6587\u4EF6: ${target}`);
10355
10468
  process.exit(2);
10356
10469
  }
10357
- if (/\.(json|ya?ml|toml)$/i.test(target)) {
10470
+ if (/\.(json|ya?ml|toml)$/i.test(target) && !/\s/.test(target.trim())) {
10358
10471
  console.error(`\u9519\u8BEF: \u6587\u4EF6\u4E0D\u5B58\u5728: ${target}`);
10359
10472
  process.exit(2);
10360
10473
  }