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:
@@ -6,6 +6,9 @@ import com.claudecode.command.CommandRegistry;
|
||||
import com.claudecode.console.*;
|
||||
import com.claudecode.core.AgentLoop;
|
||||
import com.claudecode.core.ConversationPersistence;
|
||||
import com.claudecode.permission.DangerousPatterns;
|
||||
import com.claudecode.permission.PermissionTypes.PermissionChoice;
|
||||
import com.claudecode.permission.PermissionTypes.PermissionDecision;
|
||||
import com.claudecode.tool.ToolRegistry;
|
||||
import com.claudecode.tool.impl.AskUserQuestionTool;
|
||||
import org.jline.reader.*;
|
||||
@@ -430,11 +433,21 @@ public class ReplSession {
|
||||
|
||||
/**
|
||||
* 显示权限确认提示并等待用户输入。
|
||||
* 用于危险操作(文件写入、bash 执行等)前的安全确认。
|
||||
* 支持 4 种选项:Y(允许一次) / A(始终允许) / N(拒绝) / D(始终拒绝)。
|
||||
*/
|
||||
private boolean promptPermission(AgentLoop.PermissionRequest request) {
|
||||
private PermissionChoice promptPermission(AgentLoop.PermissionRequest request) {
|
||||
out.println();
|
||||
out.println(AnsiStyle.yellow(" ⚠ Permission Required"));
|
||||
|
||||
// 检查是否为危险命令
|
||||
PermissionDecision decision = request.decision();
|
||||
boolean isDangerous = (decision != null && decision.reason() != null
|
||||
&& decision.reason().startsWith("⚠ DANGEROUS"));
|
||||
|
||||
if (isDangerous) {
|
||||
out.println(AnsiStyle.red(" ⚠ DANGEROUS Operation"));
|
||||
} else {
|
||||
out.println(AnsiStyle.yellow(" ⚠ Permission Required"));
|
||||
}
|
||||
out.println(" " + "─".repeat(50));
|
||||
out.println(" " + AnsiStyle.bold("Tool: ") + AnsiStyle.cyan(request.toolName()));
|
||||
out.println(" " + AnsiStyle.bold("Action: ") + request.activityDescription());
|
||||
@@ -448,30 +461,46 @@ public class ReplSession {
|
||||
out.println(" " + AnsiStyle.dim("Args: " + argsPreview));
|
||||
}
|
||||
|
||||
// 显示建议规则
|
||||
String suggestedRule = null;
|
||||
if (decision != null && decision.commandPrefix() != null) {
|
||||
suggestedRule = request.toolName() + "(" + decision.commandPrefix() + ":*)";
|
||||
}
|
||||
|
||||
out.println(" " + "─".repeat(50));
|
||||
out.print(" " + AnsiStyle.bold("Allow execution?") + AnsiStyle.dim(" [Y/n/always] ") + AnsiStyle.BOLD + AnsiStyle.BRIGHT_CYAN + "→ " + AnsiStyle.RESET);
|
||||
out.println(" " + AnsiStyle.green("[Y]") + " Allow once");
|
||||
if (suggestedRule != null && !isDangerous) {
|
||||
out.println(" " + AnsiStyle.green("[A]") + " Always allow " + AnsiStyle.cyan(suggestedRule));
|
||||
}
|
||||
out.println(" " + AnsiStyle.red("[N]") + " Deny");
|
||||
if (suggestedRule != null) {
|
||||
out.println(" " + AnsiStyle.red("[D]") + " Always deny this pattern");
|
||||
}
|
||||
out.print(" " + AnsiStyle.bold("Choice") + AnsiStyle.dim(" [Y/a/n/d] ") + AnsiStyle.BOLD + AnsiStyle.BRIGHT_CYAN + "→ " + AnsiStyle.RESET);
|
||||
out.flush();
|
||||
|
||||
String answer = readLineForPermission();
|
||||
if (answer == null) return false;
|
||||
if (answer == null) return PermissionChoice.DENY_ONCE;
|
||||
|
||||
answer = answer.strip().toLowerCase();
|
||||
|
||||
// "always" → 禁用后续权限确认
|
||||
if (answer.equals("always") || answer.equals("a")) {
|
||||
agentLoop.setOnPermissionRequest(null); // 移除权限回调
|
||||
out.println(AnsiStyle.green(" ✓ All subsequent operations authorized"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// 空字符串或 y/yes → 允许
|
||||
if (answer.isEmpty() || answer.equals("y") || answer.equals("yes")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 其他输入 → 拒绝
|
||||
out.println(AnsiStyle.red(" ✗ Operation denied"));
|
||||
return false;
|
||||
return switch (answer) {
|
||||
case "a", "always" -> {
|
||||
out.println(AnsiStyle.green(" ✓ Rule saved: always allow " +
|
||||
(suggestedRule != null ? suggestedRule : request.toolName())));
|
||||
yield PermissionChoice.ALWAYS_ALLOW;
|
||||
}
|
||||
case "d" -> {
|
||||
out.println(AnsiStyle.red(" ✗ Rule saved: always deny " +
|
||||
(suggestedRule != null ? suggestedRule : request.toolName())));
|
||||
yield PermissionChoice.ALWAYS_DENY;
|
||||
}
|
||||
case "n", "no" -> {
|
||||
out.println(AnsiStyle.red(" ✗ Operation denied"));
|
||||
yield PermissionChoice.DENY_ONCE;
|
||||
}
|
||||
default -> PermissionChoice.ALLOW_ONCE; // 空字符串、y、yes → 允许
|
||||
};
|
||||
}
|
||||
|
||||
/** 读取权限确认的用户输入(兼容 JLine 和 Scanner 模式) */
|
||||
|
||||
Reference in New Issue
Block a user