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:
liuzh
2026-04-02 23:05:37 +08:00
co-authored by Copilot
parent c989d3b451
commit 730551cb3f
16 changed files with 1759 additions and 127 deletions
@@ -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<>();
}
}