会话操控 + 浮窗答题 + 架构整理。
- 会话行 hover 出停止按钮:鼠标移到 session 主行上,右侧浮出红色停止图标,单击对 pid 发
SIGINT(ProcessTerminator封装kill(pid, SIGINT))。idle 行不挂按钮。高亮态走NSMenuDelegate.willHighlight+wantsLayer双轨触发,避免 NSTrackingArea 在嵌套自定义视图里漏 enter/exit。 - fresh session 行下挂 resume 子菜单:菜单底部「Fresh Session...」打开后,二级菜单列出当前 cwd 下最近 5 条 jsonl 会话(
RecentConversationsReader解析每个 jsonl 首句 user prompt 作为标题,按 mtime 排序,空文件 / <128B 文件跳过),点击复制claude --resume <sessionId>到剪贴板。支持扁平 + 子目录两种 projects/ 布局。 - AskUserQuestion 浮窗内代答:0.7.0 只展示「跳回终端答」;现在浮窗内可以直接答题。每题一个 Tab pill(自绘选中底栏),Tab 循环切题、↑↓ 移焦点环、空格切选、回车跳第一个未答 / 全答完提交,Other 选项选中后焦点自动跳到 textField。N>8 个选项启用内层滚动。双 hook(
PermissionRequest+PreToolUse)安装,helper 内分流到不同处理路径。 - ExitPlanMode 专属 3 选项:hook 透传 plan mode 时浮窗给「回终端反馈 / 手动审批 / 自动接受」三按钮,后两者会吐出
setMode的updatedPermissions短路 CLI 自带的 plan 退出 UI。Bash 含动态 shell 语法时只给「拒绝 / 允许」两键(对齐 CLI 终端无 always-allow 的行为)。按钮列表由新PromptOptionsBuilder静态推导,不再硬编码在 panel。 - session 离开 waiting 自动关闭浮窗:新增
PermissionPromptSessionExitDetector,session 状态从waiting转走时调permissionStore.abandonAll(sessionId:),把该 sessionId 下所有未答浮窗关掉。这是「用户在终端答了 prompt」的规范信号 —— 老版本只能靠 5 分钟超时。 - 偏好设置 · 通用 tab 加显示开关:「本 5 小时用量 / 总用量」两行菜单条目可独立开关隐藏,降低菜单噪音。
- 偏好设置 · Hook 配置独立成 tab:
PermissionRequest与PreToolUse两个 hook 编辑器从「通用」tab 抽出来,放进新的「Hook」tab(symbolbolt.horizontal),tab 顺序:通用 / 外观 / Hook / 关于。两个 tab 的 NSStackView 换成翻转坐标系子类FlippedStackView,初始滚动锚到顶部。
- AppDelegate 拆成 wiring 层 + 三个协调器:
MenuController(菜单 + 状态栏图标 + 热键)、NotificationOrchestrator(detector 三件套 + reminder + 通知派发)、TerminalActivator(sessionId/pid → 终端 app / Finder)。AppDelegate 只剩持有 + 把 Combine 流串起来 + 必须的@objc selector。 SessionDetailsStore缓存 jsonl 派生数据:形态与SessionContextStore平行,30s 全量刷 + sessions 增减时增量扫,MenuBuilder永远从字典读,绝不在主线程同步触发 jsonl I/O。- 所有浮窗共享
FloatingPanelStack:权限浮窗 / AskUserQuestion 浮窗都注册到同一个 stack 实例,stack 只管几何排版(右上向下、x 右对齐、y 累加 + stackGap)。manager 不再手算 y 起点。 - 菜单 session 行接入
SessionRowView:从NSMenuItem.title字符串改成自绘 NSView,为 hover 按钮 + 高亮态背景腾位置。
- 过滤 CLI 注入的伪 user 消息:
RecentConversationsReader跳过 slash command(<command-name>)、本地命令、system-reminder等非真实 user 输入的 jsonl 行,避免子菜单标题出现一堆「...」。 - hook JSON 编辑框高度:从 200 缩到 100,避免在小屏 + 多 hook 时 tabbar 被设置面板裁掉。
Behavior.allowAlways从 wire 协议删除。 App→helper 现在只发behavior: allow|deny+ 可选updatedPermissions: [JSONValue](原样转发 hook 的permission_suggestions,或由PromptOptionsBuilder推导出的setMode)。helper 不再 hand-roll session rule,直接透传到hookSpecificOutput.decision.updatedPermissions。- 老版 CLI(不发
permission_suggestions的)看不到「一直允许」按钮 —— 有意为之的不兼容,与「不替用户猜规则」一致。
- macOS 13+
信息密度提升的三件套。
- 状态栏图标角标:右上角红圈数字,表示「需要你」类事件总数 —— waiting 会话 + 待处理权限/AskUserQuestion 浮窗。计数按 sessionId 取并集,避免同一会话被 waiting 状态和浮窗双计。
>=10显示9+。badgeCount > 0时图标强制非模板,保证红色不被 AppKit 反相成灰。 - 菜单条目副行:每条 session 在主行下增加灰色小字第二行,按状态切换:waiting →
⏳ <waitingFor 或最近 prompt>;working →▸ <toolName>: <key>(Bash 截 60 字、Edit/Write/Read/NotebookEdit 取 file_path basename);idle →» <最近 prompt 截 50 字>。数据反扫~/.claude/projects/.../*.jsonl,30s 全量刷新 + sessions 增减时增量扫(新SessionContextReader+SessionContextStore)。 - AskUserQuestion 改弹浮窗:原本一闪即逝的「Claude Code 需要你回答」系统通知,改成跟权限浮窗同风格的右上角浮窗,展示完整问题文案 + 全部选项 label/description + 终端按键序号 ① ② ③。本期不代答(CLI 协议不支持外部代答),只提供「跳回终端答」按钮 + ✕。✕ 维持 abandon 语义,不会把 AskUserQuestion 转成 deny。
- 不变。
PermissionPromptStore现在被两个 manager 共用(PermissionPromptPanelManager处理常规权限,AskUserQuestionPanelManager处理 AskUserQuestion),hook helper 二进制不需要重发。
授权面板的两处行为微调。
- ✕ 不再当成 deny:点浮窗右上角的 ✕,主 app 不替用户决策,而是断开 hook 的 socket 让 helper exit(0) 不写 stdout,CLI 端的终端 prompt 完整接管(等用户去终端按 y/n)。新增
PermissionPromptStore.abandon(id:),Reply类型由(Decision) -> Void改为(Decision?) -> Void,nil 即 abandon 信号。原来的"✕ → 静默 deny"是个误伤:用户想"我去终端答",结果工具调用被拒了。 AskUserQuestion不弹浮窗,改用系统通知:LLM 的 askUserQuestion 工具是结构化多选题,只能在终端答。主 app 检测toolName == "AskUserQuestion"时:- PanelManager 跳过(不弹浮窗)
- AppDelegate 弹一条系统通知「Claude Code 需要你回答 · {项目名} · 请回到终端选择」,点击跳回对应终端
- 立刻 abandon 让 hook exit,CLI 那边终端 prompt 完整接管,askUserQuestion 的多选题正常出现等用户答
- 若要给其他工具加同样路由,改
PermissionPromptPanelManager.toolsRoutedAwayFromPanel一处即可。
- 拆出
PermissionPromptPanel.Outcomeenum(allow / allowAlways / deny / abandon)作为浮窗内部回调类型。PermissionPromptDecision.Behavior(wire 类型)保持只有allow / deny / allowAlways三个 case ——abandon不该被 JSON encode,分两个 enum 是为了不让它误漏到 wire。
授权面板的三处修复。
- 「一直允许」按钮:浮窗按钮变成 拒绝 / 一直允许 / 允许 三个。「一直允许」把规则加进当前 CLI 会话的临时白名单(scope =
session),退出claude即失效,不会写到~/.claude/settings.json。Bash 按命令字符串精确匹配;其他工具是 tool-wide-for-this-session(每种工具的ruleContent语法不同,helper 不去猜)。Tab 顺序: 拒绝 → 一直允许 → 允许。无全局热键,在面板里点。
- 重复通知:浮窗弹出的同时还会收到「Claude Code 等待响应」系统通知 banner —— 现在
AppDelegate通过PermissionPromptStore.pendingSessionIds()过滤掉这些会话,transition 通知和 5s 周期的二次提醒都跳过。完成通知(busy → idle)不在此列。 - 终端先动作时浮窗不消失:
PermissionPromptListener现在用DispatchSource.makeReadSource监听已 accept 的客户端 fd 的 EOF。CLI 因为终端赢 race 杀掉 hook 子进程时,EOF 会触发store.resolveDeny→store.resolved信号 → 面板消失。 - SIGPIPE 闪退:在已 accept 的 socket 上 set
SO_NOSIGPIPE。否则在 helper 已断开后写回响应(终端先动 / 5min 超时后 panel 才点)会被 EPIPE → SIGPIPE 整个 app 拉死。这是 0.6.0 起就潜伏的 bug,只是上面这条修好之后才会有 reliable 重现。
PermissionPromptDecision.Behavior新增allowAlways(rawValue"allow_always")。app → helper 的 wire 形态多一种;helper → CLI 的 envelope 仍然是behavior: "allow",「always」语义放进新增的updatedPermissions: [{type:"addRules", behavior:"allow", destination:"session", rules:[{toolName, ruleContent?}]}]字段。
新功能:状态栏权限审批面板。
- 权限审批浮窗:Claude Code 触发工具权限请求时,屏幕右上角弹一个浮动小面板,**单击「允许 / 拒绝」**直接回应,不用切回终端。面板里显示工具名、会话名(项目目录的 basename)、可滚动的命令详情。(
docs/permission-prompt.md) - 全局热键:
Ctrl+Shift+Y允许、Ctrl+Shift+N拒绝最新一条待审批请求。仅在至少一个面板可见时注册,不污染全局快捷键。 - 多面板堆叠:并发权限请求纵向堆叠在右上角,每个面板独立处理。
- race-with-terminal:用户在终端直接按 y/n,CLI 会赢 race,对应面板自动消失(基于
store.resolved信号)。 - 内置
ClaudeStatusBarHook二进制:作为 Claude CodePermissionRequesthook 在 .app bundle 里随主 app 一起分发。注册方式见配置说明。
- 系统通知重定向:原本会话进入
waiting状态时弹的「Claude Code 等待响应」banner 不再使用——权限请求由面板处理。banner 改成任务完成触发:CLI 会话从busy转回idle时弹「Claude Code 任务完成 · {项目名}」,点击仍跳回对应终端。 scripts/package.sh:构建前先swift package clean防止删除文件后增量缓存残留旧.o;不再用--product过滤构建(在某些 Swift 版本会静默跳过主 target);同时打包主 app 和 hook helper 进同一个.appbundle。
WaitingTransitionDetector/WaitingReminderTracker及其测试 ——waiting状态由面板覆盖,这两个类无用武之地。WaitingNotifier.notify(session:)—— 旧的"等待响应" banner 入口。
PermissionPromptRequest新增cwd: String?、sessionId: String?两个可选字段,由 hook helper 从 stdin 透传。- 移除
toolUseId(MCP 路径残留,hook 路径用不到)。
- 为什么是
PermissionRequesthook 而不是--permission-prompt-toolMCP:MCP 需要用户给claudealias 加启动 flag;hook 只在~/.claude/settings.json配一次即可,对 vanillaclaude直接生效,且 hook 与终端原生 prompt 在 CLI 内部是Promise.race——任一边先回应另一边自动 abort。 - 为什么是自家
NSPanel而不是UNUserNotificationCenter:macOS 通知里多个 action 会被折叠到「选项」按钮后面,破坏了"单击允许 / 拒绝"的体验;浮动面板不依赖系统通知权限和 Focus 模式。 - 多个 binary 放进一个
.app:分发对外仍是一个 .app,但 hook helper 保持 Foundation-only 体积小、冷启动快(CLI 每次权限请求都要 spawn)。
- macOS 13+(
Network.frameworkUnix socket、NSPanel行为) - 验证 Claude Code CLI 版本:2.1.140
见 git history。本仓库此前没有 CHANGELOG.md。