feat: 多层上下文压缩 + 多级权限管理系统
权限管理: - PermissionTypes: 权限行为/模式/规则/决策/选择类型系统 - PermissionSettings: 用户级/项目级/会话级权限持久化(settings.json) - PermissionRuleEngine: 多层规则引擎(deny→allow→readOnly→mode→dangerous→ASK) - DangerousPatterns: 危险shell命令检测(rm -rf, eval, exec等) - ReplSession: Y/A/N/D四选项权限确认UI - ConfigCommand: permission-mode/list/reset子命令 - AgentLoop: 集成规则引擎,PermissionChoice回调 上下文压缩: - TokenTracker: 上下文窗口监控(93%自动压缩/82%警告/98%阻塞) - MicroCompact: 微压缩,裁剪旧tool_result(无API调用) - SessionMemoryCompact: Session Memory压缩,AI摘要旧消息保留近期段 - FullCompact: 全量压缩+PTL重试(按API Round逐步丢弃) - AutoCompactManager: 压缩编排器(micro→session→full),含熔断机制 - CompactCommand: 改造为委托FullCompact - StatusLine: token使用百分比+颜色告警(绿→黄→红→闪烁) - AppConfig: 注册PermissionSettings/RuleEngine/AutoCompactManager Bean Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
package com.claudecode.permission;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 危险命令模式检测 —— 识别可能有害的 shell 命令。
|
||||
* <p>
|
||||
* 即使在 BYPASS 模式下也会对这些命令发出警告。
|
||||
*/
|
||||
public final class DangerousPatterns {
|
||||
|
||||
private DangerousPatterns() {}
|
||||
|
||||
/** 危险 shell 命令前缀(不区分大小写匹配) */
|
||||
private static final List<String> DANGEROUS_BASH_PREFIXES = List.of(
|
||||
"rm -rf /",
|
||||
"rm -rf ~",
|
||||
"rm -rf .",
|
||||
"rm -r /",
|
||||
"rmdir /s",
|
||||
"del /f /s /q",
|
||||
"format ",
|
||||
"mkfs.",
|
||||
"dd if=",
|
||||
"> /dev/sda",
|
||||
"chmod -R 777 /",
|
||||
"chown -R",
|
||||
":(){:|:&};:" // fork bomb
|
||||
);
|
||||
|
||||
/** 危险代码执行模式 */
|
||||
private static final List<String> CODE_EXECUTION_PATTERNS = List.of(
|
||||
"eval ",
|
||||
"exec ",
|
||||
"python -c",
|
||||
"python3 -c",
|
||||
"node -e",
|
||||
"ruby -e",
|
||||
"perl -e",
|
||||
"| sh",
|
||||
"| bash",
|
||||
"| zsh",
|
||||
"| powershell",
|
||||
"| pwsh",
|
||||
"curl | sh",
|
||||
"wget | sh",
|
||||
"Invoke-Expression",
|
||||
"iex ",
|
||||
"Start-Process",
|
||||
"Add-Type"
|
||||
);
|
||||
|
||||
/** 在规则匹配中应自动拒绝的工具级通配符 */
|
||||
private static final Set<String> DANGEROUS_TOOL_WILDCARDS = Set.of(
|
||||
"Bash", // 不应允许所有 bash 命令
|
||||
"Bash(*)",
|
||||
"PowerShell",
|
||||
"PowerShell(*)"
|
||||
);
|
||||
|
||||
/**
|
||||
* 检测命令是否包含危险模式
|
||||
*
|
||||
* @param command shell 命令文本
|
||||
* @return 如果危险返回原因描述,否则返回 null
|
||||
*/
|
||||
public static String detectDangerous(String command) {
|
||||
if (command == null || command.isBlank()) return null;
|
||||
String lower = command.toLowerCase().trim();
|
||||
|
||||
for (String prefix : DANGEROUS_BASH_PREFIXES) {
|
||||
if (lower.startsWith(prefix.toLowerCase()) || lower.contains(prefix.toLowerCase())) {
|
||||
return "Dangerous command detected: " + prefix.trim();
|
||||
}
|
||||
}
|
||||
|
||||
for (String pattern : CODE_EXECUTION_PATTERNS) {
|
||||
if (lower.contains(pattern.toLowerCase())) {
|
||||
return "Code execution pattern detected: " + pattern.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为危险的工具级通配符规则
|
||||
* <p>
|
||||
* 用于防止用户添加过于宽泛的 "always allow" 规则。
|
||||
*/
|
||||
public static boolean isDangerousWildcard(String ruleStr) {
|
||||
return DANGEROUS_TOOL_WILDCARDS.contains(ruleStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取危险原因的简短描述
|
||||
*/
|
||||
public static String getDangerLevel(String command) {
|
||||
String reason = detectDangerous(command);
|
||||
if (reason == null) return "LOW";
|
||||
if (reason.contains("Dangerous command")) return "HIGH";
|
||||
return "MEDIUM";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.claudecode.permission;
|
||||
|
||||
import com.claudecode.permission.PermissionTypes.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 权限规则引擎 —— 根据规则、模式和工具属性做出权限决策。
|
||||
* <p>
|
||||
* 决策流程:
|
||||
* <ol>
|
||||
* <li>检查全局模式(BYPASS → 全部允许,DONT_ASK → 拒绝需确认的)</li>
|
||||
* <li>检查 alwaysDeny 规则 → 匹配则 DENY</li>
|
||||
* <li>检查 alwaysAllow 规则 → 匹配则 ALLOW</li>
|
||||
* <li>只读工具 → ALLOW</li>
|
||||
* <li>ACCEPT_EDITS 模式下文件操作 → ALLOW</li>
|
||||
* <li>检查危险命令 → 强制 ASK</li>
|
||||
* <li>默认 → ASK</li>
|
||||
* </ol>
|
||||
*/
|
||||
public class PermissionRuleEngine {
|
||||
|
||||
private static final Set<String> FILE_EDIT_TOOLS = Set.of("Write", "Edit", "NotebookEdit");
|
||||
private static final Set<String> READ_ONLY_TOOLS = Set.of(
|
||||
"Read", "Glob", "Grep", "ListFiles", "WebFetch", "WebSearch",
|
||||
"TodoRead", "TaskGet", "TaskList", "AskUserQuestion"
|
||||
);
|
||||
|
||||
private final PermissionSettings settings;
|
||||
|
||||
public PermissionRuleEngine(PermissionSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 评估工具调用的权限
|
||||
*
|
||||
* @param toolName 工具名称
|
||||
* @param input 工具参数
|
||||
* @param isReadOnly 工具是否为只读
|
||||
* @return 权限决策
|
||||
*/
|
||||
public PermissionDecision evaluate(String toolName, Map<String, Object> input, boolean isReadOnly) {
|
||||
PermissionMode mode = settings.getCurrentMode();
|
||||
|
||||
// BYPASS 模式:全部允许
|
||||
if (mode == PermissionMode.BYPASS) {
|
||||
return PermissionDecision.allow("Bypass mode enabled");
|
||||
}
|
||||
|
||||
// 获取命令内容(用于 Bash/PowerShell 的命令匹配)
|
||||
String command = extractCommand(toolName, input);
|
||||
|
||||
// 检查所有持久化规则
|
||||
List<PermissionRule> rules = settings.getAllRules();
|
||||
|
||||
// 1. 检查 alwaysDeny 规则
|
||||
for (var rule : rules) {
|
||||
if (rule.behavior() == PermissionBehavior.DENY && matchesRule(rule, toolName, command)) {
|
||||
return PermissionDecision.deny("Denied by rule: " + PermissionSettings.formatRule(rule));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查 alwaysAllow 规则
|
||||
for (var rule : rules) {
|
||||
if (rule.behavior() == PermissionBehavior.ALLOW && matchesRule(rule, toolName, command)) {
|
||||
return PermissionDecision.allow("Allowed by rule: " + PermissionSettings.formatRule(rule));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 只读工具直接放行
|
||||
if (isReadOnly || READ_ONLY_TOOLS.contains(toolName)) {
|
||||
return PermissionDecision.allow("Read-only tool");
|
||||
}
|
||||
|
||||
// 4. ACCEPT_EDITS 模式:文件操作工具自动允许
|
||||
if (mode == PermissionMode.ACCEPT_EDITS && FILE_EDIT_TOOLS.contains(toolName)) {
|
||||
return PermissionDecision.allow("File edits auto-allowed in accept-edits mode");
|
||||
}
|
||||
|
||||
// 5. DONT_ASK 模式:自动拒绝
|
||||
if (mode == PermissionMode.DONT_ASK) {
|
||||
return PermissionDecision.deny("Auto-denied in dont-ask mode");
|
||||
}
|
||||
|
||||
// 6. 检查危险命令(强制 ASK,附带警告)
|
||||
if (command != null) {
|
||||
String danger = DangerousPatterns.detectDangerous(command);
|
||||
if (danger != null) {
|
||||
String prefix = extractCommandPrefix(command);
|
||||
return new PermissionDecision(
|
||||
PermissionBehavior.ASK,
|
||||
"⚠ DANGEROUS: " + danger,
|
||||
toolName, prefix, List.of()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 默认:需要用户确认
|
||||
String prefix = extractCommandPrefix(command);
|
||||
return PermissionDecision.ask(toolName, prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户选择应用权限变更
|
||||
*/
|
||||
public void applyChoice(PermissionChoice choice, String toolName, String command) {
|
||||
String prefix = extractCommandPrefix(command);
|
||||
switch (choice) {
|
||||
case ALWAYS_ALLOW -> {
|
||||
var rule = prefix != null
|
||||
? PermissionRule.forCommand(toolName, prefix, PermissionBehavior.ALLOW)
|
||||
: PermissionRule.forTool(toolName, PermissionBehavior.ALLOW);
|
||||
// 检查是否为危险通配符
|
||||
String ruleStr = PermissionSettings.formatRule(rule);
|
||||
if (!DangerousPatterns.isDangerousWildcard(ruleStr)) {
|
||||
settings.addUserRule(rule);
|
||||
}
|
||||
}
|
||||
case ALWAYS_DENY -> {
|
||||
var rule = prefix != null
|
||||
? PermissionRule.forCommand(toolName, prefix, PermissionBehavior.DENY)
|
||||
: PermissionRule.forTool(toolName, PermissionBehavior.DENY);
|
||||
settings.addUserRule(rule);
|
||||
}
|
||||
case ALLOW_ONCE, DENY_ONCE -> {
|
||||
// 单次操作,不持久化
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── 内部匹配方法 ──
|
||||
|
||||
/** 检查规则是否匹配当前工具和命令 */
|
||||
boolean matchesRule(PermissionRule rule, String toolName, String command) {
|
||||
// 工具名不匹配直接跳过
|
||||
if (!rule.toolName().equalsIgnoreCase(toolName)) return false;
|
||||
|
||||
String content = rule.ruleContent();
|
||||
// 通配符 * 匹配所有命令
|
||||
if ("*".equals(content)) return true;
|
||||
|
||||
// 前缀匹配模式:npm:* 匹配以 "npm" 开头的命令
|
||||
if (content.endsWith(":*") && command != null) {
|
||||
String prefix = content.substring(0, content.length() - 2);
|
||||
return command.toLowerCase().startsWith(prefix.toLowerCase());
|
||||
}
|
||||
|
||||
// 精确匹配
|
||||
return content.equalsIgnoreCase(command);
|
||||
}
|
||||
|
||||
/** 从工具参数中提取命令文本 */
|
||||
private String extractCommand(String toolName, Map<String, Object> input) {
|
||||
if (input == null) return null;
|
||||
return switch (toolName) {
|
||||
case "Bash" -> (String) input.get("command");
|
||||
case "Write" -> (String) input.get("file_path");
|
||||
case "Edit" -> (String) input.get("file_path");
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/** 提取命令前缀(第一个空格前的部分) */
|
||||
private String extractCommandPrefix(String command) {
|
||||
if (command == null || command.isBlank()) return null;
|
||||
String trimmed = command.trim();
|
||||
int space = trimmed.indexOf(' ');
|
||||
return space > 0 ? trimmed.substring(0, space) : trimmed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package com.claudecode.permission;
|
||||
|
||||
import com.claudecode.permission.PermissionTypes.PermissionBehavior;
|
||||
import com.claudecode.permission.PermissionTypes.PermissionRule;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 权限设置持久化 —— 管理用户级和项目级权限规则文件。
|
||||
* <p>
|
||||
* 存储结构:
|
||||
* <ul>
|
||||
* <li>用户级: ~/.claude-code-java/settings.json</li>
|
||||
* <li>项目级: .claude-code-java/settings.json</li>
|
||||
* </ul>
|
||||
* 加载优先级: 项目级 > 用户级 > 会话级
|
||||
*/
|
||||
public class PermissionSettings {
|
||||
|
||||
private static final String SETTINGS_DIR = ".claude-code-java";
|
||||
private static final String SETTINGS_FILE = "settings.json";
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper()
|
||||
.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
|
||||
/** 内存中的合并规则(从所有来源加载后合并) */
|
||||
private final List<PermissionRule> sessionRules = new ArrayList<>();
|
||||
private PermissionTypes.PermissionMode currentMode = PermissionTypes.PermissionMode.DEFAULT;
|
||||
|
||||
private final Path userSettingsPath;
|
||||
private final Path projectSettingsPath;
|
||||
|
||||
private SettingsData userData = new SettingsData();
|
||||
private SettingsData projectData = new SettingsData();
|
||||
|
||||
public PermissionSettings() {
|
||||
this(Path.of(System.getProperty("user.home")),
|
||||
Path.of(System.getProperty("user.dir")));
|
||||
}
|
||||
|
||||
public PermissionSettings(Path userHome, Path projectDir) {
|
||||
this.userSettingsPath = userHome.resolve(SETTINGS_DIR).resolve(SETTINGS_FILE);
|
||||
this.projectSettingsPath = projectDir.resolve(SETTINGS_DIR).resolve(SETTINGS_FILE);
|
||||
}
|
||||
|
||||
/** 从磁盘加载所有设置 */
|
||||
public void load() {
|
||||
userData = loadFromFile(userSettingsPath);
|
||||
projectData = loadFromFile(projectSettingsPath);
|
||||
// 项目级模式优先
|
||||
if (projectData.permissions.mode != null) {
|
||||
currentMode = projectData.permissions.mode;
|
||||
} else if (userData.permissions.mode != null) {
|
||||
currentMode = userData.permissions.mode;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取所有合并后的规则(项目级 > 用户级 > 会话级) */
|
||||
public List<PermissionRule> getAllRules() {
|
||||
var rules = new ArrayList<PermissionRule>();
|
||||
// 项目级优先
|
||||
rules.addAll(toRules(projectData.permissions.alwaysAllow, PermissionBehavior.ALLOW));
|
||||
rules.addAll(toRules(projectData.permissions.alwaysDeny, PermissionBehavior.DENY));
|
||||
// 用户级
|
||||
rules.addAll(toRules(userData.permissions.alwaysAllow, PermissionBehavior.ALLOW));
|
||||
rules.addAll(toRules(userData.permissions.alwaysDeny, PermissionBehavior.DENY));
|
||||
// 会话级
|
||||
rules.addAll(sessionRules);
|
||||
return rules;
|
||||
}
|
||||
|
||||
/** 添加规则并保存到用户级设置 */
|
||||
public void addUserRule(PermissionRule rule) {
|
||||
if (rule.behavior() == PermissionBehavior.ALLOW) {
|
||||
userData.permissions.alwaysAllow.add(formatRule(rule));
|
||||
} else if (rule.behavior() == PermissionBehavior.DENY) {
|
||||
userData.permissions.alwaysDeny.add(formatRule(rule));
|
||||
}
|
||||
saveToFile(userSettingsPath, userData);
|
||||
}
|
||||
|
||||
/** 添加规则到会话级(不持久化) */
|
||||
public void addSessionRule(PermissionRule rule) {
|
||||
sessionRules.add(rule);
|
||||
}
|
||||
|
||||
/** 移除用户级规则 */
|
||||
public void removeUserRule(String ruleStr) {
|
||||
userData.permissions.alwaysAllow.remove(ruleStr);
|
||||
userData.permissions.alwaysDeny.remove(ruleStr);
|
||||
saveToFile(userSettingsPath, userData);
|
||||
}
|
||||
|
||||
/** 清除所有规则 */
|
||||
public void clearAll() {
|
||||
userData.permissions.alwaysAllow.clear();
|
||||
userData.permissions.alwaysDeny.clear();
|
||||
projectData.permissions.alwaysAllow.clear();
|
||||
projectData.permissions.alwaysDeny.clear();
|
||||
sessionRules.clear();
|
||||
saveToFile(userSettingsPath, userData);
|
||||
}
|
||||
|
||||
public PermissionTypes.PermissionMode getCurrentMode() {
|
||||
return currentMode;
|
||||
}
|
||||
|
||||
public void setCurrentMode(PermissionTypes.PermissionMode mode) {
|
||||
this.currentMode = mode;
|
||||
userData.permissions.mode = mode;
|
||||
saveToFile(userSettingsPath, userData);
|
||||
}
|
||||
|
||||
/** 获取所有已保存规则的可读列表 */
|
||||
public List<String> listRules() {
|
||||
var result = new ArrayList<String>();
|
||||
for (var r : userData.permissions.alwaysAllow) {
|
||||
result.add("[user] ALLOW " + r);
|
||||
}
|
||||
for (var r : userData.permissions.alwaysDeny) {
|
||||
result.add("[user] DENY " + r);
|
||||
}
|
||||
for (var r : projectData.permissions.alwaysAllow) {
|
||||
result.add("[proj] ALLOW " + r);
|
||||
}
|
||||
for (var r : projectData.permissions.alwaysDeny) {
|
||||
result.add("[proj] DENY " + r);
|
||||
}
|
||||
for (var r : sessionRules) {
|
||||
result.add("[sess] " + r.behavior() + " " + formatRule(r));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── 内部方法 ──
|
||||
|
||||
private SettingsData loadFromFile(Path path) {
|
||||
if (!Files.exists(path)) return new SettingsData();
|
||||
try {
|
||||
return MAPPER.readValue(path.toFile(), SettingsData.class);
|
||||
} catch (IOException e) {
|
||||
return new SettingsData();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveToFile(Path path, SettingsData data) {
|
||||
try {
|
||||
Files.createDirectories(path.getParent());
|
||||
MAPPER.writeValue(path.toFile(), data);
|
||||
} catch (IOException e) {
|
||||
// 静默失败,不影响主流程
|
||||
}
|
||||
}
|
||||
|
||||
private List<PermissionRule> toRules(List<String> ruleStrings, PermissionBehavior behavior) {
|
||||
return ruleStrings.stream()
|
||||
.map(s -> parseRule(s, behavior))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/** 解析规则字符串,格式: "ToolName(pattern)" 或 "ToolName" */
|
||||
static PermissionRule parseRule(String ruleStr, PermissionBehavior behavior) {
|
||||
int parenStart = ruleStr.indexOf('(');
|
||||
if (parenStart > 0 && ruleStr.endsWith(")")) {
|
||||
String toolName = ruleStr.substring(0, parenStart);
|
||||
String content = ruleStr.substring(parenStart + 1, ruleStr.length() - 1);
|
||||
return new PermissionRule(toolName, content, behavior);
|
||||
}
|
||||
return PermissionRule.forTool(ruleStr, behavior);
|
||||
}
|
||||
|
||||
/** 格式化规则为字符串 */
|
||||
static String formatRule(PermissionRule rule) {
|
||||
if ("*".equals(rule.ruleContent())) {
|
||||
return rule.toolName();
|
||||
}
|
||||
return rule.toolName() + "(" + rule.ruleContent() + ")";
|
||||
}
|
||||
|
||||
// ── JSON 数据结构 ──
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static class SettingsData {
|
||||
public PermissionsBlock permissions = new PermissionsBlock();
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static class PermissionsBlock {
|
||||
public PermissionTypes.PermissionMode mode;
|
||||
public List<String> alwaysAllow = new ArrayList<>();
|
||||
public List<String> alwaysDeny = new ArrayList<>();
|
||||
public List<String> additionalDirectories = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.claudecode.permission;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 权限管理类型定义 —— 对应 claude-code 中的 permissions.ts。
|
||||
*/
|
||||
public final class PermissionTypes {
|
||||
|
||||
private PermissionTypes() {}
|
||||
|
||||
/** 权限行为 */
|
||||
public enum PermissionBehavior {
|
||||
ALLOW, // 允许执行
|
||||
DENY, // 拒绝执行
|
||||
ASK // 需要用户确认
|
||||
}
|
||||
|
||||
/** 权限模式 */
|
||||
public enum PermissionMode {
|
||||
/** 默认模式:非只读工具需要用户确认 */
|
||||
DEFAULT,
|
||||
/** 自动允许文件编辑,shell 命令仍需确认 */
|
||||
ACCEPT_EDITS,
|
||||
/** 跳过所有权限检查(不安全) */
|
||||
BYPASS,
|
||||
/** 自动拒绝而非询问用户(无头模式) */
|
||||
DONT_ASK
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限规则 —— 定义工具和命令模式的权限行为。
|
||||
* <p>
|
||||
* 示例:
|
||||
* <ul>
|
||||
* <li>{@code PermissionRule("Bash", "npm:*", ALLOW)} — 允许所有 npm 命令</li>
|
||||
* <li>{@code PermissionRule("Bash", "rm -rf:*", DENY)} — 拒绝 rm -rf</li>
|
||||
* <li>{@code PermissionRule("Write", "*", ALLOW)} — 允许所有文件写入</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param toolName 工具名称(如 Bash, Write, Edit)
|
||||
* @param ruleContent 规则内容,支持通配符 *(如 "npm:*", "git:*", "*")
|
||||
* @param behavior 权限行为
|
||||
*/
|
||||
public record PermissionRule(
|
||||
String toolName,
|
||||
String ruleContent,
|
||||
PermissionBehavior behavior
|
||||
) {
|
||||
/** 匹配整个工具(无命令模式限制) */
|
||||
public static PermissionRule forTool(String toolName, PermissionBehavior behavior) {
|
||||
return new PermissionRule(toolName, "*", behavior);
|
||||
}
|
||||
|
||||
/** 匹配工具的特定命令前缀 */
|
||||
public static PermissionRule forCommand(String toolName, String prefix, PermissionBehavior behavior) {
|
||||
return new PermissionRule(toolName, prefix + ":*", behavior);
|
||||
}
|
||||
}
|
||||
|
||||
/** 权限决策结果 */
|
||||
public record PermissionDecision(
|
||||
PermissionBehavior behavior,
|
||||
String reason,
|
||||
String toolName,
|
||||
String commandPrefix,
|
||||
List<PermissionRule> suggestedRules
|
||||
) {
|
||||
public static PermissionDecision allow(String reason) {
|
||||
return new PermissionDecision(PermissionBehavior.ALLOW, reason, null, null, List.of());
|
||||
}
|
||||
|
||||
public static PermissionDecision deny(String reason) {
|
||||
return new PermissionDecision(PermissionBehavior.DENY, reason, null, null, List.of());
|
||||
}
|
||||
|
||||
public static PermissionDecision ask(String toolName, String commandPrefix) {
|
||||
// 生成建议规则供用户选择 "always allow"
|
||||
var suggested = List.of(
|
||||
PermissionRule.forCommand(toolName, commandPrefix, PermissionBehavior.ALLOW)
|
||||
);
|
||||
return new PermissionDecision(PermissionBehavior.ASK, "Requires user confirmation",
|
||||
toolName, commandPrefix, suggested);
|
||||
}
|
||||
|
||||
public boolean isAllowed() {
|
||||
return behavior == PermissionBehavior.ALLOW;
|
||||
}
|
||||
|
||||
public boolean isDenied() {
|
||||
return behavior == PermissionBehavior.DENY;
|
||||
}
|
||||
|
||||
public boolean needsAsk() {
|
||||
return behavior == PermissionBehavior.ASK;
|
||||
}
|
||||
}
|
||||
|
||||
/** 权限确认选项(用户在 UI 中的选择) */
|
||||
public enum PermissionChoice {
|
||||
/** 允许本次执行 */
|
||||
ALLOW_ONCE,
|
||||
/** 始终允许此模式 */
|
||||
ALWAYS_ALLOW,
|
||||
/** 拒绝本次执行 */
|
||||
DENY_ONCE,
|
||||
/** 始终拒绝此模式 */
|
||||
ALWAYS_DENY
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user