79992680c9
- README 对齐当前 C# / Avalonia 架构与安装方式 - docs: architecture / sequences / hooks-and-cli / interop / build-and-install
137 lines
4.2 KiB
Markdown
137 lines
4.2 KiB
Markdown
# 调用时序
|
||
|
||
本文用时序图说明各条执行路径,帮助理解项目的运行方式。
|
||
|
||
## 1. 整体生命周期
|
||
|
||
```mermaid
|
||
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。
|
||
|
||
```mermaid
|
||
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
|
||
```
|
||
|
||
要点:
|
||
|
||
1. **原子写入**:先 `.tmp` 再改名 `.json`,Watcher 只认 `.json`,避免读到半截文件。
|
||
2. **拉起不等待**:`Process.Start` 后立刻退出;通知由 Host 起来后从 spool 补弹。
|
||
3. **`UseShellExecute=true` 是必须的**:否则常驻 Host 继承钩子的 stdout 管道、一直不关闭,Claude Code 等不到 EOF 会卡在 "running stop hook"。
|
||
|
||
## 3. 点击 toast → 激活原窗口
|
||
|
||
```mermaid
|
||
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 捕获(状态从哪来)
|
||
|
||
```mermaid
|
||
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 内:弹窗与堆叠
|
||
|
||
```mermaid
|
||
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
|
||
```
|