Files
notify/docs/sequences.md
T
chuan 79992680c9 docs: 重写 README 并新增 docs 调用时序文档
- README 对齐当前 C# / Avalonia 架构与安装方式
- docs: architecture / sequences / hooks-and-cli / interop / build-and-install
2026-06-22 16:40:27 +08:00

4.2 KiB
Raw Blame History

调用时序

本文用时序图说明各条执行路径,帮助理解项目的运行方式。

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

要点:

  1. 原子写入:先 .tmp 再改名 .jsonWatcher 只认 .json,避免读到半截文件。
  2. 拉起不等待Process.Start 后立刻退出;通知由 Host 起来后从 spool 补弹。
  3. 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