diff --git a/skills/mono/SKILL.md b/skills/mono/SKILL.md index 365010c4..fea54e86 100644 --- a/skills/mono/SKILL.md +++ b/skills/mono/SKILL.md @@ -60,7 +60,7 @@ cli_version: ">=1.0.15" | `sheet` | 在线电子表格(axls):工作表 CRUD/区域读写/CSV 批量写入/行列增删/合并/查找替换/筛选视图/全局筛选/排序/下拉列表/浮动图片/导出(两步) | [sheet.md](./references/products/sheet.md) | | `todo` | 待办:创建(含优先级/截止时间/循环)/查询/修改/标记完成/删除 | [todo.md](./references/products/todo.md) | | `wiki` | 知识库:空间创建/详情/列表/搜索 + 成员管理 | [wiki.md](./references/products/wiki.md) | -| `event` | 事件订阅:Stream 长连接订阅消息/审批/通讯录/日历事件,NDJSON 输出(实时驱动 Agent)| [event.md](./references/products/event.md) | +| `event` | 个人单聊事件:监听当前用户与指定用户的单聊消息,NDJSON 输出(实时驱动 Agent)| [event.md](./references/products/event.md) | ## 核心流程(每次请求必须执行,不得跳过) @@ -113,7 +113,7 @@ cli_version: ">=1.0.15" 用户提到"在线电子表格/钉钉表格/axls/工作表/单元格读写/合并单元格/筛选视图/导出 xlsx" → `sheet` 用户提到"待办/TODO/任务提醒/循环待办" → `todo` 用户提到"创建知识库/知识库列表/搜索知识库空间/wiki/团队空间/知识库成员管理/我的文档个人空间" → `wiki` -用户提到"实时监听/订阅事件/事件流/event consume/接收新消息/驱动 Agent/聊天机器人监听/审批触发/通讯录变更" → `event` +用户提到"监听我和某人的单聊消息/订阅个人单聊事件/实时接收某个用户发给我的消息/个人消息事件流/event consume user_im_message_receive_o2o/驱动 Agent 处理单聊消息" → `event` 关键区分: **dev(创建/配置/建联机器人)** vs **chat(查询/发消息已有机器人)**。`dws chat bot search/find` 只查询机器人;**建号**(创建钉钉智能体机器人)走 `dws dev app robot submit`;**建联**(把机器人接到本地 agent 的 Stream)走 `dws dev connect`。凡是"创建机器人""建机器人""接入 agent""建联"一律路由到 `dev`,禁止走 `chat`。 关键区分: aitable(数据表格) vs todo(待办任务) diff --git a/skills/mono/references/products/event.md b/skills/mono/references/products/event.md index 4bbb893a..ddac1041 100644 --- a/skills/mono/references/products/event.md +++ b/skills/mono/references/products/event.md @@ -1,51 +1,58 @@ -# `dws event` — 事件订阅 +# `dws event` — 个人单聊事件 -通过 DingTalk Stream 长连接实时订阅事件,NDJSON 输出到 stdout,用于驱动事件触发的 Agent。 +通过个人 Stream 长连接监听当前用户与指定用户的单聊消息,NDJSON 输出到 stdout,用于驱动事件触发的 Agent。 + +当前只暴露 `user_im_message_receive_o2o`。 ## 当用户说…… | 用户说 | 命令 | |--------|------| -| "监听消息" / "实时接收消息" | `dws event consume --event-types im.message.receive_v1 --compact --quiet` | -| "审批触发时执行" | `dws event consume --event-types approval.instance.status_changed --compact --quiet` | -| "看 bus 状态" | `dws event status` | -| "列出消费者" | `dws event list --all` | -| "停止订阅" | `dws event stop` | +| "监听我和 507971 的单聊消息" | `dws event consume`,事件码 `user_im_message_receive_o2o`,参数 `--peer-user-id 507971 -f ndjson` | +| "订阅某个 unionId 的单聊事件" | `dws event consume`,事件码 `user_im_message_receive_o2o`,参数 `--peer-union-id -f ndjson` | +| "查看个人单聊事件 schema" | `dws event schema`,事件码 `user_im_message_receive_o2o` | +| "看个人单聊订阅状态" | `dws event status --event user_im_message_receive_o2o` | +| "停止这个个人事件订阅" | `dws event stop ` | -## 子命令 +## 核心规则 -- `dws event consume [flags]` — 主命令,订阅 + 输出 -- `dws event list [--all] [--all-editions]` — 列消费者 -- `dws event status [--all] [--fail-on-orphan]` — bus 健康状态 -- `dws event stop` — 优雅停 bus +- `user` 是默认身份,不要加 `--as user`。 +- 必须提供对端身份:优先 `--peer-user-id`,没有时使用 `--peer-union-id`。 +- 缺少对端身份时先追问,不要猜测。 +- 不要主动运行 `dws event list` 作为能力菜单;本参考只承认 `user_im_message_receive_o2o`。 +- 当前 event 参考不暴露应用事件。用户明确询问应用事件时,回答“当前 event skill 暂不暴露应用事件”。 -## 凭证 +## 常用命令 -bot-only,需要 ClientID + ClientSecret: -- env:`DWS_CLIENT_ID` + `DWS_CLIENT_SECRET`(成组) -- 或:`dws config init` 写入 keychain +```bash +dws event schema user_im_message_receive_o2o +``` + +```bash +dws event consume user_im_message_receive_o2o \ + --peer-user-id 507971 \ + -f ndjson +``` + +```bash +dws event consume user_im_message_receive_o2o \ + --peer-user-id 507971 \ + --duration 10m \ + -f ndjson +``` + +```bash +dws event status --event user_im_message_receive_o2o +dws event stop +``` ## 输出格式 -- `-f ndjson`(默认):一行一对象 -- `-f compact`:扁平化 + 解析嵌套(**Agent 强推荐**) -- `-f json/pretty`:美化 JSON(必须 `--max-events` 或 `--duration`) -- `-f raw`:SDK 原始 payload - -## 关键 Flag - -| Flag | 说明 | -|------|------| -| `--event-types ` | 限定事件类型,下推到 bus | -| `--filter ` | 正则过滤事件类型 | -| `--max-events ` | 收 N 条后退出 | -| `--duration ` | 运行时长上限 (30s/5m) | -| `--output-dir ` | 每事件写一个文件 | -| `--route '=dir:'` | 按正则路由事件到目录 | -| `--foreground` | 不 fork daemon(systemd/k8s 友好)| -| `--dry-run` | 仅打印配置 | +- 推荐 `-f ndjson`:一行一个事件 JSON,适合 Agent 管道读取。 +- 人工取样可用 `-f json --max-events 1`。 +- `--debug-raw-events` 仅用于服务端联调,正常消费不要使用。 ## 完整文档 详细见 multi 模式:`skills/multi/dingtalk-event/SKILL.md` -端到端冒烟手册:`skills/multi/dingtalk-event/references/runbook.md` +自测流程:`skills/multi/dingtalk-event/references/runbook.md` diff --git a/skills/multi/dingtalk-event/SKILL.md b/skills/multi/dingtalk-event/SKILL.md index dc6f7fc2..07c4c7f3 100644 --- a/skills/multi/dingtalk-event/SKILL.md +++ b/skills/multi/dingtalk-event/SKILL.md @@ -1,183 +1,69 @@ --- name: dingtalk-event -description: 钉钉事件订阅。Use when 用户提到 实时监听消息/订阅事件/事件流/event consume/接收新消息/聊天机器人监听/审批事件/通讯录变更/日历事件/构建事件驱动 Agent。命令前缀:dws event。 -cli_version: ">=1.0.31" -metadata: - category: product - stability: experimental - requires: - bins: - - dws +description: 钉钉个人单聊事件监听、订阅与消费。Use when 用户提到 监听我和某人的单聊消息、订阅个人单聊事件、实时接收某个用户发给我的消息、dws event consume user_im_message_receive_o2o、个人消息事件流、用事件驱动 Agent 处理单聊消息。命令前缀:dws event。 --- -# 钉钉事件订阅 Skill +# 钉钉个人单聊事件 Skill -> 🧪 **EXPERIMENTAL** — DingTalk Stream 事件订阅 (v1)。提供 daemon + 多消费者本地 IPC,对齐 lark-cli 风格。 +本 skill 只暴露个人事件里已经实现的单聊事件:`user_im_message_receive_o2o`。 -> **PREREQUISITE:** 用 `dws config init` 配置 ClientID/ClientSecret,或通过 env var `DWS_CLIENT_ID` + `DWS_CLIENT_SECRET`(成组覆盖)。bot-only:不需要 `auth login`。 +不要暴露其它事件类型、身份模式或应用级事件能力。用户明确询问应用事件时,回答:当前 event skill 暂不暴露应用事件。 -> 命令参考:[dingtalk-event-consume.md](references/dingtalk-event-consume.md) · 端到端示例:[runbook.md](references/runbook.md) +## 前置条件 -## 意图表 +- 使用当前用户 OAuth 登录态;需要时先让用户执行 `dws auth login`。 +- 订阅单聊事件必须知道对端身份:优先 `peer-user-id`,没有时使用 `peer-union-id`。 +- 缺少对端身份时先追问,不要猜测 ID。 -| 用户说 | 命令 | -|--------|------| -| "监听新消息" | `dws event consume --event-types im.message.receive_v1 --compact --quiet` | -| "订阅审批状态变化" | `dws event consume --event-types approval.instance.status_changed` | -| "看 bus 状态" | `dws event status` | -| "列活跃消费者" | `dws event list --all` | -| "停 bus" | `dws event stop` | -| "调试 SDK 原始 payload" | `dws event consume -f raw --max-events 1` | -| "事件持久化到文件" | `dws event consume --output-dir ./events` | -| "按事件类型路由到不同目录" | `dws event consume --route '^im\.=dir:./im/' --route '^approval\.=dir:./approval/'` | +## 核心命令 -## 架构(一图) +| 意图 | 命令 | +|------|------| +| 查看单聊事件说明 | `dws event schema`,事件码 `user_im_message_receive_o2o` | +| 监听与指定 userId 的单聊消息 | `dws event consume`,事件码 `user_im_message_receive_o2o`,参数 `--peer-user-id -f ndjson` | +| 监听与指定 unionId 的单聊消息 | `dws event consume`,事件码 `user_im_message_receive_o2o`,参数 `--peer-union-id -f ndjson` | +| 查看个人单聊订阅状态 | `dws event status --event user_im_message_receive_o2o` | +| 停止指定个人订阅 | `dws event stop ` | +| 停止当前身份下所有本地记录的个人订阅 | `dws event stop --all` | -``` -钉钉 Stream Gateway (WebSocket) - │ (一个 ClientID 一条云端连接) - ▼ -┌───────────────────────────────────────────────────┐ -│ dws event _bus (per ClientID, auto-forked) │ -│ Source (SDK 长连接) → Dedup (event_id LRU) │ -│ → Hub (Hello pushdown 按订阅过滤) │ -│ → UDS (Unix) / Named Pipe (Windows) │ -└───────────────────────┬───────────────────────────┘ - │ NDJSON frames - ┌───────────────┼───────────────┐ - ▼ ▼ ▼ - dws event dws event dws event - consume (A) consume (B) status/list - im.* 订阅 approval.* ad-hoc RPC -``` +`user` 是 `dws event` 默认身份,不要额外加 `--as user`。 -**关键不变量:** -1. **emit 非阻塞** — SDK 回调里立即 ACK;慢消费者只丢自己(drop-oldest) -2. **dedup 必须有** — Stream 重连会重投递在途事件 -3. **单 ClientID 单 bus** — `/events///bus.lock` 强制 -4. **上游永远全订阅** — `--event-types` 只影响 bus → consumer 这一段;开放平台后台未勾选的事件类型即使设置 `--event-types` 也收不到 -5. **consumer EOF 自动清理** — SIGKILL/崩溃后 bus 通过 socket EOF 立即 unregister +## 常用模式 -## 子命令总览 - -| 子命令 | 用途 | -|--------|------| -| `dws event consume` | 订阅事件并 NDJSON 输出(主命令)| -| `dws event list` | 列出当前 / 全 edition 的消费者 | -| `dws event status` | 显示 bus 健康状态 + per-event-type 计数 | -| `dws event stop` | 优雅停 bus(SIGTERM)| -| `dws event _bus` | (隐藏)daemon 进程入口,consume 按需 fork | - -## 凭证解析顺序 - -1. **env var 覆盖**(CI/容器友好):`DWS_CLIENT_ID` + `DWS_CLIENT_SECRET` **同时设置** → 整组使用,跳过 keychain;任一缺失则不启用 env 通道(避免半套配置) -2. **keychain**:`dws config init` 配置后存储于系统 keychain -3. **明文 config**(不推荐):app config 文件中 `clientSecret` 字段直接为 plain string - -`status` / HelloAck 会显示 `credentials_source` (env / keychain / app_config / plain_config),方便用户验证实际生效的来源。 - -## 4 个 env var 调参(不需要新增 flag) - -| Env var | 默认 | 用途 | -|---------|------|------| -| `DWS_EVENT_BUS_IDLE_TIMEOUT` | `5m` | bus 在 0 consumer 后自停时间 | -| `DWS_EVENT_CONSUMER_BUFFER` | `100` | 每 consumer sendCh 容量 | -| `DWS_EVENT_DEDUP_LRU` | `8192` | event_id LRU 容量 | -| `DWS_EVENT_DROP_WARN_PCT` | `5` | 单事件类型 drop 比超阈值时写 bus.log WARN | - -## 常见 Agent pipeline - -### 收消息 → Claude 回复 +### 监听指定用户的单聊消息 ```bash -DWS_CLIENT_ID=ding_xxx DWS_CLIENT_SECRET=*** \ - dws event consume \ - --event-types im.message.receive_v1 \ - --compact --quiet \ - | while IFS= read -r ev; do - content=$(echo "$ev" | jq -r '.content // empty') - msg_id=$(echo "$ev" | jq -r '.message_id // empty') - [[ -z "$content" ]] && continue - reply=$(claude -p "用中文简短回复:$content") - dws chat message reply --message-id "$msg_id" --text "$reply" - done +dws event consume user_im_message_receive_o2o \ + --peer-user-id 507971 \ + -f ndjson ``` -### 审批 → 写入飞书文档 +### 有界自测 ```bash -dws event consume \ - --event-types approval.instance.status_changed \ - --compact --quiet \ - | while IFS= read -r ev; do - title=$(echo "$ev" | jq -r '.title') - status=$(echo "$ev" | jq -r '.to_status') - lark-cli docs +update --doc "DOC_URL" --mode append \ - --markdown "- $(date '+%H:%M') 审批 [$title] → $status" - done +dws event consume user_im_message_receive_o2o \ + --peer-user-id 507971 \ + --duration 10m \ + -f ndjson ``` -### 事件持久化(审计/回放) +### 抓一条样本 ```bash -# 每事件落一个 JSON 文件,按事件类型分目录 -dws event consume \ - --route '^im\.=dir:./events/im/' \ - --route '^approval\.=dir:./events/approval/' \ - --route '^contact\.=dir:./events/contact/' \ - --output-dir ./events/other/ +dws event consume user_im_message_receive_o2o \ + --peer-user-id 507971 \ + --max-events 1 \ + -f json ``` -## 关键陷阱速查 +## 输出处理 -| 陷阱 | 症状 | 解决 | -|------|------|------| -| 开放平台后台没勾事件 | consume 跑着但收不到事件 | 钉钉开发者后台 → 应用 → 事件订阅 → 长连接 → 勾对应事件类型 | -| `--force` 不带 `--foreground` | validation error | daemon 模式不能 force(会导致多 bus 抢同 socket);要重启 bus 用 `event stop && event consume` | -| `--format json` 无界流 | validation error | json 必须配 `--max-events` 或 `--duration`;流式用 `ndjson`(默认)| -| CI/容器没 keychain | "ClientSecret resolution failed" | 用 env var:`DWS_CLIENT_ID + DWS_CLIENT_SECRET` 成组设置 | -| orphan bus 残留 | 上次 `kill -9` 后 status 显示 orphan | 下次 `dws event consume` 会自动清理 stale lock;手动可 `dws event stop` 或 `rm -rf` workdir | -| 多副本(K8s)只 1 个能跑 | 单 ClientID 单 bus 设计 | 每副本用独立 ClientID;不要共享 | -| stdout 被污染 | NDJSON 流里混入 daemon 日志 | bus daemon stdio 已 detach;日志在 `/bus.log`;如果污染必是 bug 请 report | - -## 输出格式(事件流默认 ndjson) - -| `-f` 值 | 输出 | 用途 | -|---------|------|------| -| `ndjson` (默认) | 一行一个对象 | 管道 / jq / Agent | -| `json` | 多行美化 JSON | 人工调试(要求 bounded:必须 `--max-events` 或 `--duration`)| -| `pretty` | 同 json | 同上 | -| `raw` | 仅 SDK 原始 payload | 调试 SDK | -| `compact` | 扁平化 + 解析嵌套 + 语义字段 | **Agent 强推荐**(IM/审批/通讯录/日历/考勤都有处理器)| -| `table` / `csv` | fallback 到 ndjson + stderr WARN | 事件流无意义 | - -`-f compact` 已注册 8 个处理器:im.message.receive_v1 / im.message.read_v1 / approval.instance.status_changed / approval.task.created / contact.user.{created,updated,deleted}_v3 / cal.event.{created,updated,deleted}_v1 / attendance.check_v1。未注册类型走 generic processor(payload JSON 字段扁平化到顶层)。 - -## 与 lark-cli event 对齐和超越 - -| 维度 | lark-cli | dws event | -|------|---------|-----------| -| 用户命令 | `consume ` + skill `+subscribe` 套娃 | **`consume [--event-types ...]` 一层搞定** | -| `--compact/--filter/--route` | 在 `+subscribe` skill 里 | **全部下放到 `consume`** | -| 隐藏 daemon | `_bus.go` 按需 fork ✅ | 同 | -| event_id 去重 | ✅ | 同 | -| 连接状态 | 日志正则反推 | **inferred state machine + state_source 字段** | -| `status` 视图 | per-consumer 计数 | + **per-event-type 总计 + drop-rate WARN** | -| Identity | `--as user/bot/auto` | **bot-only**(命令面更干净)| -| 前台模式 | 强 daemon | + **`--foreground`**(systemd/k8s 友好)| -| Hello 下推过滤 | EventKey + 基础 | **+ filter regex / event_types 下推到 bus,减少 IPC 量** | -| 跨平台 | Unix Socket + Windows Named Pipe ✅ | 同 | - -## 实施细节 - -- v1 仅 `RegisterAllEventRouter`(generic events);ChatBot @-消息和卡片回调 v2 加(需要同步 ACK 业务响应体) -- v1 stop 走 SIGTERM;Windows graceful 是 v2 todo -- catch-all 列表 v1 为空(= bus 上游全订阅);待 P0 真测 DingTalk event_type 字符串后填具体清单 -- workdir:`/events///`,clientIDHash = `sha256(clientID)[:16]` +- 默认推荐 `-f ndjson`:stdout 每行一个事件 JSON,适合 Agent 管道读取。 +- 人工查看单条样本可用 `-f json --max-events 1`。 +- 长时间监听时用 `--duration` 或外部进程管理控制生命周期。 +- `--debug-raw-events` 只用于和服务端联调,正常 Agent 消费不要使用。 ## 参考 -- 详细命令参考:[references/dingtalk-event-consume.md](references/dingtalk-event-consume.md) -- 端到端冒烟手册:[references/runbook.md](references/runbook.md) -- 实施计划:`plans/2026-05-28_event_capability_v1.plan.md` -- P0 SDK 验证记录:`plans/2026-05-28_event_capability_v1.p0-sdk-probe.md` +- 详细消费参数:[references/dingtalk-event-consume.md](references/dingtalk-event-consume.md) +- 自测流程:[references/runbook.md](references/runbook.md) diff --git a/skills/multi/dingtalk-event/references/dingtalk-event-consume.md b/skills/multi/dingtalk-event/references/dingtalk-event-consume.md index 9dc68340..f526026b 100644 --- a/skills/multi/dingtalk-event/references/dingtalk-event-consume.md +++ b/skills/multi/dingtalk-event/references/dingtalk-event-consume.md @@ -1,137 +1,105 @@ -# `dws event consume` 详细参考 +# `dws event consume` 个人单聊参考 -订阅 DingTalk Stream 事件并将每条事件以 NDJSON 输出到 stdout。 +本参考只覆盖个人单聊事件 `user_im_message_receive_o2o`。 ## Synopsis -``` -dws event consume [flags] +```bash +dws event consume user_im_message_receive_o2o [flags] ``` -## Flags +## 必填参数 -| Flag | 默认 | 说明 | -|------|------|------| -| `--event-types ` | catch-all | 逗号分隔事件类型,下推到 bus;省略 = 接收 bus 上游所有事件 | -| `--filter ` | — | 客户端正则过滤事件类型(下推到 bus)| -| `--compact` | false | 提示 bus 期望 compact 渲染(语义透传)| -| `-f, --format ` | `ndjson` | 输出格式:`ndjson` (默认) / `json` / `pretty` / `raw` / `compact`;`table/csv` fallback 到 ndjson | -| `--output-dir ` | stdout | 每事件写一个文件 `{type}_{id}_{ts}.json`;与 stdout 互斥 | -| `--route ` | — | `=dir:`,可重复;未命中走 stdout/--output-dir | -| `--max-events ` | 0 | 收到 N 条后退出 (0 = 不限) | -| `--duration ` | 不限 | Go duration (30s/5m),事件流专用 | -| `--quiet` | false | 抑制 stderr 状态信息 | -| `--force` | false | 仅 `--foreground` 模式有意义;daemon 模式 → validation error | -| `--dry-run` | false | 仅打印解析后的配置,不连接 bus / 云端 | -| `--foreground` | false | 不 fork daemon,当前进程跑 bus (systemd/k8s 友好) | +必须二选一: -## 凭证 +| 参数 | 说明 | +|------|------| +| `--peer-user-id ` | 对端用户的 userId,优先使用 | +| `--peer-union-id ` | 对端用户的 unionId | -- env var (覆盖式优先):`DWS_CLIENT_ID` + `DWS_CLIENT_SECRET` 同时设置即生效 -- keychain(默认):`dws config init` 写入 OS keychain -- bot-only:不需要 `dws auth login` +缺少对端身份时,先向用户追问。 -## 输出契约 +## 常用参数 -- 默认 `-f ndjson`:每事件一行 JSON。字段:`type/seq/event_id/event_born_time/event_corp_id/event_type/event_unified_app_id/data/headers/received_at_unix_ms` -- `-f compact`:经 processor 扁平化后的 map。常见字段(IM):`type/event_id/timestamp/corp_id/app_id/message_id/chat_id/chat_type/message_type/content/sender_id` -- `-f raw`:仅 `event.Data`(SDK 原始 payload string),无 dws 封装 -- `-f json/pretty`:每事件多行美化 JSON;**必须** 配 `--max-events` 或 `--duration`,否则 validation error -- stderr:默认输出连接 banner `connected bus pid=N source=... state=...`;`--quiet` 抑制 +| 参数 | 说明 | +|------|------| +| `-f, --format ndjson` | 推荐输出,一行一个事件 JSON | +| `--max-events ` | 收到 N 条后退出 | +| `--duration ` | 到时退出,如 `30s`、`10m` | +| `--output-dir ` | 每个事件写入一个文件 | +| `--route '=dir:'` | 按事件类型路由到目录 | +| `--subscribe-id ` | 复用已有个人订阅 | +| `--personal-event-base-url ` | 联调环境覆盖控制面 base URL | +| `--stream-ticket-url ` | 联调环境覆盖取票 URL | +| `--stream-source-id ` | 联调环境覆盖 sourceId | +| `--debug-raw-events` | 联调用:输出当前 personal stream bus 收到的全部可解析事件 | -## Sink 选择 +不要使用未在本参考中列出的事件选择参数;本 skill 只消费 `user_im_message_receive_o2o`。 -| Flag 组合 | 行为 | -|----------|------| -| (默认) | stdout NDJSON | -| `--output-dir ` | 每事件写一个文件到该目录 | -| `--route '=dir:'` | 匹配事件路由到该目录,未匹配走 stdout | -| `--route ... --output-dir ` | 匹配事件路由,未匹配走 fallback 目录 | +## 输出 -`--output-dir` / `--route` 与全局隐藏 `-o/--output`(单文件捕获)互斥;同时出现 → validation error。 +`-f ndjson` 每行是一个事件对象,常见字段: -## 退出码 +| 字段 | 说明 | +|------|------| +| `event_type` | 应为 `user_im_message_receive_o2o` | +| `subscribe_id` | 个人订阅 ID | +| `source_id` | 当前 sourceId | +| `data` | 服务端业务 payload 原文 | +| `headers` | Stream 帧 headers | +| `received_at_unix_ms` | 本地接收时间 | -| 场景 | 退出码 | -|------|--------| -| 优雅退出(ctx cancel / max-events / duration / bye)| 0 | -| 下游 stdout 管道关(SIGPIPE)| 0(不被视为错误)| -| validation error(flag 互斥、`--force` 不带 `--foreground` 等)| 5 | -| 凭证缺失 | 5 | -| bus 连接失败(discover deadline)| 5 | - -## 信号 - -- `SIGINT` / `SIGTERM`:优雅退出,向 bus 发 Bye,返回 0 -- 下游 EPIPE:检测到 stdout 关后立即退出 0 - -## 跨平台 - -- Unix:`/events///bus.sock`(权限 0600) -- Windows:Named Pipe `\\.\pipe\dws-event--`(只允许当前用户) +业务消息内容通常在 `data` 内部 payload 中,读取前先查看实际样本。 ## 示例 -### 基础订阅(catch-all 到 stdout) +### 持续监听 ```bash -dws event consume +dws event consume user_im_message_receive_o2o \ + --peer-user-id 507971 \ + -f ndjson ``` -### Agent 友好(推荐) +### 监听 10 分钟 ```bash -dws event consume --event-types im.message.receive_v1 --compact --quiet +dws event consume user_im_message_receive_o2o \ + --peer-user-id 507971 \ + --duration 10m \ + -f ndjson ``` -### 多类型订阅 + 过滤 +### 获取一条 JSON 样本 ```bash -dws event consume \ - --event-types im.message.receive_v1,im.message.at_v1,approval.instance.status_changed \ - --filter '^im\.' \ - --max-events 10 +dws event consume user_im_message_receive_o2o \ + --peer-user-id 507971 \ + --max-events 1 \ + -f json ``` -### 调试单条 SDK payload +### 联调预发环境 ```bash -dws event consume -f raw --max-events 1 +dws event consume user_im_message_receive_o2o \ + --peer-user-id 507971 \ + --personal-event-base-url https://pre-mcp.dingtalk.com/dws \ + --stream-ticket-mode normal \ + --stream-source-id pre_open_source \ + --stream-ticket-url https://pre-mcp.dingtalk.com/stream/connections/ticket \ + -f ndjson ``` -### 持久化 + 路由 +## 停止 ```bash -dws event consume \ - --route '^im\.=dir:./events/im/' \ - --route '^approval\.=dir:./events/approval/' \ - --output-dir ./events/other/ +dws event status --event user_im_message_receive_o2o +dws event stop ``` -### 前台模式(systemd / k8s) +如果只想清理当前身份下本地记录的所有个人订阅: ```bash -# Unit file 直接调用,不 fork daemon -ExecStart=/usr/local/bin/dws event consume --foreground --quiet +dws event stop --all ``` - -### 一次性运行(CI / 测试) - -```bash -DWS_CLIENT_ID=$CI_DING_ID DWS_CLIENT_SECRET=$CI_DING_SECRET \ - dws event consume --max-events 1 --duration 30s --dry-run -``` - -## 错误消息和恢复指引 - -- `--force is only meaningful with --foreground (...) To restart the bus: dws event stop && dws event consume` -- `--format json requires --max-events or --duration (...) Use --format ndjson for unbounded streams.` -- `app config missing: run \`dws config init\` or set DWS_CLIENT_ID/DWS_CLIENT_SECRET env vars` -- `ClientSecret resolution failed (keychain unavailable?); try DWS_CLIENT_ID/DWS_CLIENT_SECRET env vars` -- `WARN: only one of DWS_CLIENT_ID/DWS_CLIENT_SECRET is set; env fallback disabled, using keychain/app config`(半套 env 警告) - -## See Also - -- [`dws event status`](runbook.md#status) -- [`dws event list`](runbook.md#list) -- [`dws event stop`](runbook.md#stop) diff --git a/skills/multi/dingtalk-event/references/runbook.md b/skills/multi/dingtalk-event/references/runbook.md index 4bc40c46..962e4989 100644 --- a/skills/multi/dingtalk-event/references/runbook.md +++ b/skills/multi/dingtalk-event/references/runbook.md @@ -1,171 +1,81 @@ -# 端到端冒烟手册 (Runbook) +# 个人单聊事件自测流程 -从零到看见第一条事件的全流程。 +## 1. 确认登录 -## 1. 钉钉开发者后台配置 - -1. 访问 https://open.dingtalk.com/app → 创建应用(或选择已有应用) -2. **应用能力** → 添加 **机器人**(如果做 IM 监听) -3. **权限管理** → 申请: - - `im:message`(接收消息) - - `im:message.group_at_msg`(群里被 @) - - `im:message.p2p_msg`(私聊消息) - - `im:message:send_as_bot`(发送消息,可选) -4. **事件订阅** → 选 "使用 **Stream 模式** 接收事件"(非 HTTP 推送)→ 勾选要订阅的事件类型,例如 `im.message.receive_v1` -5. 在应用基本信息页拿 **AppKey** (= ClientID) 和 **AppSecret** (= ClientSecret) - -## 2. 本机配置凭证 - -### 方式 A:keychain(macOS/Linux desktop 推荐) +个人事件使用当前用户 OAuth 登录态。未登录或 token 失效时,先执行: ```bash -dws config init -# 按提示输入 AppKey 和 AppSecret,存入 OS keychain +dws auth login ``` -### 方式 B:env var(CI/容器/SSH 推荐) +## 2. 查看单聊事件说明 ```bash -export DWS_CLIENT_ID=ding_abcdef123 -export DWS_CLIENT_SECRET=YOUR_APP_SECRET +dws event schema user_im_message_receive_o2o ``` -**注意**:两个 env var 必须**同时设置**才生效;只设一个会触发 stderr WARN 并回退到 keychain。 +确认事件规则是 `singleChat`,必填参数是 `peer-user-id` 或 `peer-union-id`。 -## 3. 第一条消息 +## 3. 启动监听 -终端 1: +优先用对端 `userId`: ```bash -dws event consume --event-types im.message.receive_v1 --compact --quiet +dws event consume user_im_message_receive_o2o \ + --peer-user-id 507971 \ + --duration 10m \ + -f ndjson ``` -终端 2:用钉钉客户端给应用机器人发一条消息 "hello" - -终端 1 应该看到一行: - -```json -{"type":"im.message.receive_v1","event_id":"ev_xxx","message_id":"om_xxx","chat_id":"oc_xxx","content":"hello","sender_id":"ou_xxx","timestamp":1700000000123} -``` - -按 `Ctrl+C` 退出。 - -## 4. 查看 bus 状态 +如果只有 `unionId`: ```bash -dws event status +dws event consume user_im_message_receive_o2o \ + --peer-union-id \ + --duration 10m \ + -f ndjson ``` -输出(运行中): +## 4. 触发事件 -``` -ClientID: ding_abcdef123 - Edition : open - Workdir : /Users/me/.dws/events/open/e1092ee444e129c8... - Bus : running pid=12345 uptime=15m20s - Source : state=connected state_source=inferred reconnects=0 - Consumers: 1 active - Per-event-type counters (since bus start): - im.message.receive_v1 received=3 dropped=0 - Consumers: - PID EVENT KEYS RECEIVED DROPPED - 12350 im.message.receive_v1 3 0 -``` +让对端用户给当前登录用户发送一条单聊消息。stdout 应输出一行事件 JSON。 -orphan 状态(bus 进程被 `kill -9` 后): +如果没有输出: -``` -Bus : orphan (last_pid=12345 not alive) -Action : run `dws event consume` to force-restart, or rm -rf the workdir -``` +1. 确认对端身份参数正确。 +2. 用 `dws event status --event user_im_message_receive_o2o` 查看订阅和本地连接状态。 +3. 联调服务端时临时加 `--debug --debug-raw-events`,观察当前 personal stream bus 是否收到服务端推送。 -## 5. 列出所有 consumer +## 5. 停止监听 + +先查订阅 ID: ```bash -# 当前 ClientID -dws event list - -# 当前 edition 下所有 ClientID -dws event list --all - -# 跨 edition(调试用) -dws event list --all-editions +dws event status --event user_im_message_receive_o2o ``` -## 6. 优雅停止 bus +停止指定订阅: ```bash -dws event stop -# bus stopped +dws event stop ``` -工作机制:读 `bus.lock` 内 PID → SIGTERM → 等进程退出(默认 5s timeout)。 - -## 7. 健康检查(CI/监控) +清理当前身份下本地记录的全部个人订阅: ```bash -# orphan 时退出码 5(适合 cron / k8s liveness) -dws event status --fail-on-orphan -echo $? # 0 if healthy, 5 if orphan present - -# JSON 输出供脚本消费 -dws event status -f json | jq '.[0].entry.state' -# "running" | "orphan" | "not_running" +dws event stop --all ``` -## 8. systemd 部署 +## 6. 安装到 Agent -`/etc/systemd/system/dws-event.service`: - -```ini -[Unit] -Description=DingTalk Event Consumer -After=network.target - -[Service] -Type=simple -User=dws -Environment=DWS_CLIENT_ID=ding_abcdef123 -Environment=DWS_CLIENT_SECRET=YOUR_SECRET -Environment=DWS_EVENT_BUS_IDLE_TIMEOUT=24h -ExecStart=/usr/local/bin/dws event consume \ - --foreground \ - --event-types im.message.receive_v1 \ - --compact --quiet \ - --output-dir /var/log/dws-events/ -Restart=on-failure -RestartSec=10 - -[Install] -WantedBy=multi-user.target -``` - -启用: +安装 multi skill 的 event 子 skill: ```bash -sudo systemctl daemon-reload -sudo systemctl enable --now dws-event -sudo journalctl -u dws-event -f +dws skill setup --mode multi -s event --target codex --source --yes ``` -## 9. 故障排查 +安装 mono skill: -| 现象 | 排查步骤 | -|------|---------| -| 跑 `consume` 立刻报 `app config missing` | 检查 `~/.dws/app.json` 是否存在;或设 env var | -| `consume` 跑着但收不到事件 | (1) 开放平台后台确认事件订阅模式是 **Stream 长连接** 不是 HTTP;(2) 确认对应事件类型已勾选;(3) `dws event status` 看 source state | -| `dws event status` 显示 orphan | bus 进程死了但 lock 没清。下次 `consume` 会自动清理;或 `dws event stop` 强制 | -| stderr 频繁 `WARN: event type backpressure` | drop rate 超阈值。consumer 处理太慢。增大 `DWS_EVENT_CONSUMER_BUFFER` 或加快消费 | -| `WARN: only one of DWS_CLIENT_ID/DWS_CLIENT_SECRET is set` | 两个 env var 必须同时设置;要么都设,要么都 unset | -| `dws event status --all-editions` 显示意外的 wukong/* 条目 | 历史遗留(之前用过 wukong overlay)。安全:`rm -rf /events/wukong/` | - -## 10. 数据位置 - -| 路径 | 用途 | -|------|------| -| `~/.dws/events///bus.lock` | 单文件锁 + PID | -| `~/.dws/events///bus.meta` | 元数据(原 ClientID/edition/started_at)| -| `~/.dws/events///bus.sock` | Unix Socket(Linux/macOS)| -| `~/.dws/events///bus.log` | daemon slog 日志(含 drop WARN)| - -Windows 用 `\\.\pipe\dws-event--` 代替 sock。 +```bash +dws skill setup --mode mono --target codex --source --yes +``` diff --git a/test/skill_tests.md b/test/skill_tests.md index 83a0992e..329f1e58 100644 --- a/test/skill_tests.md +++ b/test/skill_tests.md @@ -47,6 +47,7 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然 | `devdoc` | `references/products/simple.md` | 2 | 7 | | `dev` | `skills/multi/dingtalk-dev/SKILL.md` | 19 | 25 | | `ding` | `references/products/ding.md` | 2 | 5 | +| `event` | `references/products/event.md` | 1 | 3 | | `report` | `references/products/report.md` | 6 | 26 | | `routing` | `references/intent-guide.md` | 1 | 3 | | `todo` | `references/products/todo.md` | 6 | 30 | @@ -993,6 +994,26 @@ Agent 安装 dws skill 后,仅依据 skill 提供的参考文档,将自然 --- +### event(3 条) + +#### `dws event consume user_im_message_receive_o2o` + +**event_event_consume_o2o_001** +- Prompt: 监听我和 userId 507971 的单聊消息 +- Expected: `dws event consume user_im_message_receive_o2o --peer-user-id 507971 -f ndjson` +- Flags: `--peer-user-id` = `507971` + +**event_event_consume_o2o_002** +- Prompt: 订阅 unionId union123 给我发来的单聊消息 +- Expected: `dws event consume user_im_message_receive_o2o --peer-union-id union123 -f ndjson` +- Flags: `--peer-union-id` = `union123` + +**event_event_consume_o2o_003** `[ASK_USER]` +- Prompt: 监听我的个人单聊消息 +- Expected: `dws event consume user_im_message_receive_o2o -f ndjson` + +--- + ### ding(5 条) #### `dws ding message recall`