# 实时事件

## 能做什么

通过 DingTalk Stream 长连接实时监听：

| 类别 | 事件 |
| --- | --- |
| 个人 IM | 收到消息、@我、已读回执、消息撤回、表情回应 |
| 群生命周期 | 成员加入 / 退出、群改名、群解散 |
| OA 审批 | 实例发起 / 抄送 / 终止 / 完成；任务创建 / 完成 / 转交 |

## 为什么不能轮询

钉钉官方执行契约**明文禁止**：

> 不要写脚本轮询消息历史或审批列表。

原因不只是效率——轮询拿不到「已读」「撤回」这类**只在发生瞬间存在**的事件，而且会撞限流。

助手会正确识别「关心未来会发生的事」与「查已经发生的事」：

| 你说 | 助手判定 | 走哪 |
| --- | --- | --- |
| 「盯着发给我的消息，有新的通知我」 | 未来事件 | `event +listen-im` 长连接 |
| 「我最近收到哪些消息」 | 已有数据 | 对应产品的查询命令 |

真机实测（用例 e4）：助手查了 `dws schema --cli-path "event +listen-im" --compact --format json`，并用 `TerminalControl` 管理长驻进程——**没有用 `while true` + `sleep` 轮询模拟**。

## 实际用法

### 监听发给我的消息

```
帮我盯着发给我的钉钉消息，有新的就通知我
```

普通 IM 消息、reaction、已读、撤回走：

```bash
dws event +listen-im ...
```

### 监听审批与群生命周期

OA 审批、群成员变化、明确的原始 EventKey、Filter DSL、subscribe_id 走：

```bash
dws event consume ... --flatten
```

输出是 NDJSON 长连接流。

## 平台侧依赖

长连接需要**宿主管理进程并持续读取 stdout**——这是钉钉官方明确留给宿主的职责：

> 无界任务需要宿主管理进程并持续读取 stdout。
> 定时调度由外层工作流负责。

DesireCore 侧对应的能力是**长驻输出流事件接收器**（把长驻子进程 stdout 按行分帧后触发 Agent）。该能力目前在 PR 阶段，尚未接上工具入口。

**在它落地前**，助手会如实说明实时监听能力受限，**不会用轮询假装实现**。

## 握手协议

`+listen-im` 启动后会在 stderr 打 ready marker：

```
[event] ready event_key=... bus_pid=... subscribe_id=...
```

**必须等这个 marker 再认为订阅生效**，不要用 sleep 猜。停止时不要 `kill -9`，走正常的取消订阅路径（`dws event stop`），否则服务端订阅会残留。
