# IM 稳定契约与能力边界
本页只记录需要跨命令复用的 Runtime 契约。下面四个 marker 区块由
`check-multi-im-skill-chain.sh` 对照 Go typed descriptor 逐字校验;修改能力时先改 Runtime、
测试与 descriptor,再同步本页。命令参数仍以精确 leaf Schema 为准。
## 消息结果 `im.message-list.v1`
`+chat-messages`、`+search-msg`、`+messages-mget` 及相关消息读取使用同一兼容版本。
字段可能为空,但不能换名猜测;全量任务必须结合完整性 ledger 判断。
- `version`: `im.message-list.v1`
- `message_fields`: `messageId`, `conversationId`, `threadId`, `sender`, `senderId`, `senderType`, `messageType`, `text`, `createTime`, `updateTime`, `reactions`, `quotedMessage`, `forwarded`, `resourceRefs`
- `envelope_fields`: `contractVersion`, `messages`, `count`, `resolvedFilters`, `queryRange`, `pagesFetched`, `paginationKnown`, `complete`, `hasMore`, `nextPage`, `stopReason`, `truncated`, `truncatedByPageLimit`, `truncatedByResultLimit`, `failedCount`, `failures`, `partial`, `scope`, `resourceDownloads`
当 `complete=false` 时不能称为全量成功。`nextPage` 只能来自真实 lower boundary;
`failedCount/failures`、`partial`、总 `truncated` 和两个原因字段必须原样保留。
当 Runtime 解析并应用自然发送者条件时,`resolvedFilters.senders[]` 保留原查询及选中的
`userId/openDingTalkId`。消息展示名可以与通讯录姓名不同;只能用稳定 `senderId` 与解析结果关联,
不得重新做姓名字符串比较。
当查询声明时间范围或顺序时,`queryRange` 保留规范化的 `startTime`、`endTime`、`order` 与
`semantics=[start,end)`。排序只覆盖本次实际取得的 `messages`;`complete=false` 时不得把它描述为完整范围的全局排序。
## `+messages-send` 身份矩阵
| identity | targets | content types | natural targets | mention targets | idempotency keys | batch ledger |
|---|---|---|---|---|---:|---:|
| `user` | `group`
`direct-user`
`direct-open-dingtalk-id` | `text`
`markdown`
`image-media-id`
`file`
`audio-as-file`
`video-as-file` | `chat-query`
`user-query` | `open-dingtalk-id`
`all` | `true` | `false` |
| `bot` | `group`
`groups`
`direct-users`
`direct-open-dingtalk-ids` | `text`
`markdown` | — | `user-id`
`open-dingtalk-id`
`all` | `false` | `true` |
| `webhook` | `token-owned-group` | `text`
`markdown` | — | `user-id`
`mobile`
`all` | `false` | `false` |
Bot 多群用 `--groups` 或 `--groups-file`,Runtime 去重后输出
`im.batch-write.v1` 逐目标 ledger。Bot/Webhook 不支持的内容类型会在写前失败,不能降级为
另一身份或偷偷改成纯文本。
## 流式卡片
- `version`: `im.streaming-card.v1`
- `targets`: `group`, `direct-user`, `direct-open-dingtalk-id`
- `content_types`: `streaming-text`
- `flow_statuses`: `1=processing`, `2=typing`, `3=completed`, `4=executing`, `5=error`
- `callback_supported`: `false`
发送目标与状态范围由 Runtime 校验。当前不是 Lark Card JSON 编译器,也不消费按钮 callback;
具体创建和更新流程见 `card/` 下的精确 reference。
## 正向能力与负向边界
| capability | supported | current route / boundary |
|---|---:|---|
| `thread-write` | `false` | quote reply with +messages-reply; thread reading with +thread-replies |
| `bot-rich-media` | `false` | bot text/markdown, or current-user file/image send |
| `card-action-callback` | `false` | streaming text card create/update only |
| `resource-resume` | `false` | atomic whole-file download with explicit retry |
| `group-member-full-pagination` | `true` | +chat-members-list or +group-members |
| `group-owner-selection` | `true` | +chat-create owner flags |
`supported=false` 是执行门禁,不是待猜测字段。只有 lower interface、Runtime、测试、Schema 和
此页同时升级后,才能改变对外承诺。
话题圈会话仍禁止引用消息回复;向 Thread 追加回复使用 `chat thread reply --conversation-id `。