AgentPad

AgentPad 后续计划

起草日期:2026-05-07(重排 2026-05-26) 适用工程:AgentPad.xcworkspace 主要语言:Swift / AppKit / Core Data 范围:当前未落地的功能演进

进度说明:原计划 1(简易映射模式)/ 2(其他蓝牙手柄 backend)/ 3(按 App 透传)/ 4(Accessibility 引导)/ 5(Agent 捕获模式 v1)已全部实现并合入主干(见 git log),本文档不再保留。计划 5 完整正文已归档至 docs/completed.md。剩余存量计划保留原编号(6),新增 Agent 监控相关计划以「A / B」前缀编号。


计划 A:Agent 监控后端(进程发现 + 三态引擎)

A.1 现状分析

A.2 目标

提供与「手柄遥控」并列的另一根产品支柱:

A.3 方案设计

A.3.1 数据结构

新建 AgentPad/DataModels/AgentMonitor/

struct AgentProcess: Identifiable, Equatable {
    let pid: pid_t
    let name: String            // proc_name
    let executablePath: String? // proc_pidpath
    let cwd: String?            // PROC_PIDVNODEPATHINFO
    let startedAt: Date
    let state: AgentState
    let detail: AgentStateDetail
    let source: AgentStateSource
}

enum AgentState: Int, Comparable {
    case working = 3   // 优先级最高
    case callingAPI = 2
    case idle = 1
}

enum AgentStateDetail {
    case toolUse(name: String?)             // Working: JSONL 路给出工具名;PTY 路给出匹配关键词
    case streaming                           // Calling API
    case waitingInput(prompt: String?)       // Idle: PTY 路可附等待中的提示串
    case unknown
}

enum AgentStateSource {
    case jsonl(sessionId: String)
    case pty(window: String)
    case none                                // 两路均未命中,默认 Idle
}

enum AgentMonitorEvent {
    case updated(processes: [AgentProcess])
    case empty
    case pollingFailed(consecutiveFailures: Int)
}

A.3.2 进程发现

A.3.3 主路:Session JSONL Tail

新建 JSONLSessionProbe

A.3.4 兜底路:PTY 内容抓取

新建 PTYStateProbe

A.3.5 路径合并

每轮 poll 对每个命中进程依次跑 JSONL 路 → PTY 路:

不再做”同时满足多条件取高优先级”的逻辑(旧版 Working/CallingAPI 子进程+TCP 合并),因为同源已直接给出唯一状态;保留 AgentStateComparable 仅用于 UI 排序。

A.3.6 轮询调度

新建 AgentMonitor 单例:

final class AgentMonitor {
    static let shared = AgentMonitor()
    private(set) var lastEvent: AgentMonitorEvent = .empty
    var eventHandler: ((AgentMonitorEvent) -> Void)?

    private var timer: DispatchSourceTimer?
    private var consecutiveFailures: Int = 0
    private let failureThreshold: Int       // 默认 3,可配
    private var interval: TimeInterval      // 默认 3s

    func start()
    func stop()
    func restart(interval: TimeInterval)    // 设置变更时调
    func pollOnce()                          // 供 Retry 按钮调用
}

A.3.7 配置接入

AppSettings 新增 namespace:

struct AgentMonitorSettings: Codable {
    var patterns: [String] = ["claude", "opencode", "codex"]
    var pollIntervalSec: Int = 3            // 允许 2/3/5/10
    var pollFailureThreshold: Int = 3
    var showCountInMenuBar: Bool = true
    var enablePTYProbe: Bool = true         // 关闭则只跑 JSONL 路
}

存到 UserDefaults,键 agent.monitor.settings。修改 patterns / interval / enablePTYProbe → 立即 AgentMonitor.shared.restart(interval:)

A.4 数据模型影响

A.5 实施步骤

  1. 新增 AgentPad/DataModels/AgentMonitor/{AgentProcess,AgentMonitor,ProcessScanner,JSONLSessionProbe,ClaudeCodeSessionLocator,PTYStateProbe,TerminalHostResolver}.swift
  2. AppSettings 增加 AgentMonitorSettings + 持久化读写。
  3. AppDelegate.applicationDidFinishLaunching 末尾调 AgentMonitor.shared.start(),并接入设置变更通知。
  4. 单元测试:
    • 用 fixture JSONL(涵盖 streaming / tool_use / 完成 / 待响应 user 末行四种)覆盖 JSONL 路状态判定。
    • 用 mock AXUIElement 文本输入覆盖 PTY 路关键词命中 / 不命中 / 失败兜底。
    • 路径合并:JSONL=working + PTY=idle → working;JSONL=unknown + PTY=callingAPI → callingAPI;两路均失败 → idle。
    • PollingFailed 阈值边界(默认 3 / 改为 1)。

A.6 验证点

A.7 风险与待决项


计划 B:Agent 监控前端(菜单栏图标 + Popover + Settings 分页 + NSMenu 改名)

B.1 现状分析

B.2 目标

完成 design.md §1–§4 描述的所有 UI 入口:

  1. 菜单栏图标按 AgentMonitorEvent 重绘(Empty / 1–4 / 5+ / PollingFailed 四态)。
  2. 左键单击 toggle Agent Monitor Popover;右键 / Ctrl+Click 仍弹既有 NSMenu。
  3. Popover 实现 320×400 三区块(Header / List / Footer),含空态与错误态。
  4. 主设置窗口新增第四分页 Agent Monitor
  5. 既有 NSMenu 仅做 title 改名(不改 IBAction / IBOutlet)。

B.3 方案设计

B.3.1 菜单栏图标重绘

新建 AgentPad/Views/StatusBar/StatusBarIconRenderer.swift

enum StatusBarIconState {
    case empty
    case agents(states: [AgentState], totalCount: Int)   // states.count ≤ 3 后用 +M
    case pollingFailed
}

enum StatusBarIconRenderer {
    static func image(for state: StatusBarIconState, showCount: Bool) -> NSImage
    // 配色:Working=systemGreen, CallingAPI=systemOrange, Idle=systemGray
    // Empty: SF Symbol "circle.dashed"
    // PollingFailed: SF Symbol "exclamationmark.circle"
    // 1–4 agents: N 个 8pt 圆点横排 + (showCount?N : "")
    // 5+ agents: 3 圆点 + "+M" 灰底胶囊 + (showCount?N : "")
}

B.3.2 菜单栏图标交互

StatusBarController(新增,承接现有 statusItem 持有职责):

final class StatusBarController {
    let statusItem: NSStatusItem
    let popoverController: AgentMonitorPopoverController
    weak var legacyMenu: NSMenu?            // 既有 NSMenu(计划 B.3.5 改名后)

    init() {
        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
        statusItem.button?.target = self
        statusItem.button?.action = #selector(handleClick(_:))
        statusItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
    }

    @objc func handleClick(_ sender: NSStatusBarButton) {
        guard let event = NSApp.currentEvent else { return }
        let isRight = event.type == .rightMouseUp ||
                      (event.type == .leftMouseUp && event.modifierFlags.contains(.control))
        if isRight {
            statusItem.menu = legacyMenu
            statusItem.button?.performClick(nil)   // 弹完即解绑,避免下次左键也弹菜单
            statusItem.menu = nil
        } else {
            popoverController.toggle(relativeTo: sender)
        }
    }
}

订阅 AgentMonitor.shared.eventHandler → 重绘图标。

B.3.3 Agent Monitor Popover

新建 AgentPad/Views/AgentMonitor/

AgentMonitorPopoverController.swift   // NSPopover 持有 + show/hide/toggle
AgentMonitorViewController.swift      // 320×400 主视图
AgentRowView.swift                    // NSTableCellView 子类,单行 64pt
AgentMonitorEmptyView.swift           // 空态
AgentMonitorErrorView.swift           // 错误态(含 Retry 按钮)

布局严格按 design.md §2.1:

空态 / 错误态按 design.md §2.1 原样实现,错误态 RetryAgentMonitor.shared.pollOnce()

Popover 显示期间持续接收 AgentMonitor 事件并刷新表格,不暂停轮询。点击 Popover 外区域自动关闭(NSPopover.behavior = .transient)。

B.3.4 主设置窗口新增 Agent Monitor 分页

AgentPadWindowController 顶部 toolbar 增加第 4 项 “Agent Monitor”:

AgentPad/Views/Settings/AgentMonitorSettings/
    AgentMonitorSettingsViewController.swift
    AgentMonitorSettingsViewController.xib

字段(与 AgentMonitorSettings 一一对应):

所有改动即时生效:

新增 Popover Footer 的 Settings 按钮跳转时,通过 AgentPadWindowController.show(tab: .agentMonitor) API 直接选中该分页。

B.3.5 既有 NSMenu 改名

按 design.md §4.1 改名映射表:

既有 title 改名后 title
Enable key mappings 不变
Controllers(子菜单) 不变
Open AgentPad…(如有) Open Agent Monitor…
Preferences… Settings…keyEquivalent 保持 ,
Quit AgentPad 不变

@IBOutlet weak var menu: NSMenu?@IBOutlet weak var controllersMenu: NSMenuItem? 及对应 action / target 一律不动;改名仅在 storyboard 的 title 属性 + Localizable.strings 中完成。

Open Agent Monitor… 的现有 action 改为调用 StatusBarController.popoverController.show(relativeTo: statusItem.button),与左键单击等价。

B.4 数据模型影响

B.5 本地化

新增字符串(Misc/en.lproj/Localizable.strings + Misc/ja.lproj/Localizable.strings):

key EN JA
agent.monitor.header.title AGENT MONITOR エージェントモニター
agent.monitor.header.count.fmt %d running %d 件実行中
agent.monitor.header.idle idle アイドル
agent.monitor.empty.title No agent running エージェントは実行されていません
agent.monitor.empty.subtitle Start Claude Code / opencode / codex in a terminal to monitor it. Claude Code / opencode / codex をターミナルで起動すると監視されます。
agent.monitor.error.title Failed to enumerate processes. プロセスの列挙に失敗しました。
agent.monitor.error.retry Retry 再試行
agent.monitor.state.working Working 実行中
agent.monitor.state.callingAPI Calling API API 呼び出し中
agent.monitor.state.idle Idle アイドル
settings.tab.agentMonitor Agent Monitor エージェントモニター
settings.agentMonitor.patterns.title Monitored process patterns 監視するプロセスパターン
settings.agentMonitor.patterns.hint case-insensitive substring 大文字小文字を区別しない部分一致
settings.agentMonitor.interval Polling interval ポーリング間隔
settings.agentMonitor.showCount Show count in menu bar メニューバーに件数を表示
settings.agentMonitor.launchAtLogin Launch at login ログイン時に起動
menu.openAgentMonitor Open Agent Monitor… エージェントモニターを開く…
menu.settings Settings… 設定…

B.6 实施步骤

  1. StatusBarIconRenderer + 单元测试(四态产物渲染)。
  2. StatusBarController + 左/右键路由;接入 AgentMonitor.eventHandler
  3. AgentMonitorPopoverController + AgentMonitorViewController(含 Header / List / Footer / 空态 / 错误态五个子视图)。
  4. AgentMonitorSettingsViewController + storyboard / xib;接入 AgentMonitorSettings
  5. AgentPadWindowController toolbar 增加 Agent Monitor 分页 + 切换 API。
  6. NSMenu title 改名 + Open Agent Monitor… action 重接。
  7. 本地化字符串 EN / JA 各 18 条。
  8. 回归:旧右键菜单所有 IBAction 行为保持不变;手柄遥控功能零影响。

B.7 验证点

B.8 风险与待决项