79992680c9
- README 对齐当前 C# / Avalonia 架构与安装方式 - docs: architecture / sequences / hooks-and-cli / interop / build-and-install
4.2 KiB
4.2 KiB
调用时序
本文用时序图说明各条执行路径,帮助理解项目的运行方式。
1. 整体生命周期
sequenceDiagram
autonumber
actor User as 用户
participant CC as Claude Code
participant Cli as notify (CLI)
participant State as 状态文件
participant Spool as spool 队列
participant Host as notify host
participant Win as 目标窗口
User->>CC: 发送消息
CC->>Cli: notify save (stdin: session_id + prompt)
Cli->>Cli: GetForegroundWindow / 进程树取图标 / WT 标签
Cli->>State: 写入状态
Cli-->>CC: 立即退出
Note over CC: Claude 处理中…
alt 任务完成
CC->>Cli: notify notify
else 需要输入 / 提问 / 出 Plan
CC->>Cli: notify input
end
Cli->>State: 读取状态(hwnd / 图标 / WT 标签)
Cli->>Spool: 原子写入 NotifyMessage
Cli->>Host: 不在则拉起(不等待)
Cli-->>CC: 立即退出(~100ms)
Host->>Spool: FileSystemWatcher 消费
Host-->>User: 弹出 toast
User->>Host: 左键点击 toast
Host->>Win: 抢前台激活 (+ 切回 WT 标签)
Win-->>User: 回到原窗口
User->>CC: 结束会话
CC->>State: notify cleanup 删除状态
2. 非阻塞投递(关键设计)
CLI 绝不能等 Host,否则会拖住 Claude Code。
sequenceDiagram
autonumber
participant Cli as notify notify/input
participant Spool as spool 目录
participant Mtx as 单例互斥量
participant Host as notify host
Cli->>Spool: 写 <guid>.tmp → 原子改名 <guid>.json
Cli->>Mtx: TryOpenExisting?
alt Host 已在跑
Mtx-->>Cli: 存在 → 不拉起
else Host 未运行
Cli->>Host: Process.Start("host", UseShellExecute=true)
Note over Cli,Host: UseShellExecute=true 让 Host 脱离<br/>本进程标准句柄,否则会攥住钩子<br/>stdout 管道导致 Claude 卡在 running hook
end
Cli-->>Cli: 立即返回(~100ms)
Host->>Spool: 启动时 DrainExisting + Watcher 增量
Host->>Host: 读取并删除文件 → UI 线程弹 toast
要点:
- 原子写入:先
.tmp再改名.json,Watcher 只认.json,避免读到半截文件。 - 拉起不等待:
Process.Start后立刻退出;通知由 Host 起来后从 spool 补弹。 UseShellExecute=true是必须的:否则常驻 Host 继承钩子的 stdout 管道、一直不关闭,Claude Code 等不到 EOF 会卡在 "running stop hook"。
3. 点击 toast → 激活原窗口
sequenceDiagram
autonumber
actor User as 用户
participant Toast as ToastWindow
participant Act as WindowActivator
participant WT as WinTerminalTabs
participant Win as 目标窗口
User->>Toast: 左键点击主体
Toast->>Act: Activate(targetHwnd)
Act->>Win: ALT 模拟 + AttachThreadInput +<br/>SetForegroundWindow 组合技
Win-->>User: 窗口回到前台
opt 目标是 Windows Terminal 且有 RuntimeId
Toast->>WT: SelectTab(hwnd, runtimeId)
WT->>Win: UIAutomation 找到标签 → Select()
end
Toast->>Toast: 淡出关闭
4. save 捕获(状态从哪来)
flowchart TD
A[notify save] --> B[读 stdin JSON: session_id + prompt]
B --> C[GetForegroundWindow → hwnd]
C --> D{窗口类是 Windows Terminal?}
D -->|是| E[UIAutomation 取当前标签 RuntimeId]
D -->|否| F[跳过]
E --> G[进程树上溯找调用方 App exe]
F --> G
G --> H[写状态文件: hwnd / prompt / wtRuntimeId / callerExePath]
5. Host 内:弹窗与堆叠
sequenceDiagram
autonumber
participant Watch as SpoolWatcher
participant App as App.OnNotify
participant Mgr as ToastManager
participant Win as ToastWindow
Watch->>App: NotifyMessage(线程池线程)
App->>App: 若 PlaySound 播提示音
App->>App: 目标已是前台?→ 用更短停留
App->>Mgr: UIThread.Post(Show(request))
alt 可见数 < 上限
Mgr->>Win: 新建并显示
Win->>Win: 工具窗口化 / 跨桌面 pin / 取图标
Mgr->>Mgr: Arrange(按 水平×垂直+margin 堆叠)
else 超出上限
Mgr->>Mgr: 入队,等有空位再弹
end