From 30314311e4fd1ae4d41f67a2eb1c289197cf7e72 Mon Sep 17 00:00:00 2001 From: Dennis Date: Mon, 3 Aug 2026 20:39:27 +0800 Subject: [PATCH] fix(im): harden live audit contracts --- docs/dws-multi-im-optimization-plan.md | 40 ++- internal/cli/schema_agent_metadata/index.json | 2 +- internal/cli/schema_agent_metadata_audit.json | 246 +++++++++--------- internal/cli/schema_catalog/catalog.json | 4 +- internal/shortcut/chat/chat_conversation.go | 4 +- .../chat/compatibility_coverage_test.go | 9 + scripts/run_chat_shortcut_live_audit.py | 22 +- scripts/run_chat_shortcut_live_audit_test.py | 57 ++++ scripts/run_chat_shortcut_live_write_audit.py | 38 ++- skills/mono/references/products/chat.md | 14 +- .../references/chat/chat-conversation.md | 7 +- 11 files changed, 295 insertions(+), 148 deletions(-) create mode 100644 scripts/run_chat_shortcut_live_audit_test.py diff --git a/docs/dws-multi-im-optimization-plan.md b/docs/dws-multi-im-optimization-plan.md index 021e9e61..a9c449a8 100644 --- a/docs/dws-multi-im-optimization-plan.md +++ b/docs/dws-multi-im-optimization-plan.md @@ -495,9 +495,47 @@ make generate-schema - Skill 命令面通过:1009 条可执行 path; - Runtime confirmation truth 通过:137 个 gated 工具; - Schema Catalog、CommandRegistry 和两次独立生成漂移检查通过; -- `DWS_PACKAGE_VERSION=0.0.0-test go test ./...` 通过; +- `DWS_PACKAGE_VERSION=0.0.0-test go test -p 1 ./...` 通过;默认包级并行复跑曾因 + `internal/helpers` 的共享测试环境竞争失败一次,该包隔离强制复跑通过; - `go build -o /tmp/dws-multi-im-build ./cmd` 通过。 +### 9.2 真实环境端到端审计 + +2026-08-03 使用当前分支构建的 CLI、有效个人登录态和真实 DingTalk 后端完成两组审计。 +报告只保留命令名、下层工具名、数量和脱敏错误类别,不落业务 payload: + +```bash +python3 scripts/run_chat_shortcut_live_audit.py \ + --dws /tmp/dws-multi-im-final \ + --out-dir /tmp/dws-im-live-audit-019fc653/read-current-branch \ + --timeout 90 --max-group-probes 8 --max-member-probes 8 + +python3 scripts/run_chat_shortcut_live_write_audit.py \ + --dws /tmp/dws-multi-im-final \ + --out-dir /tmp/dws-im-live-audit-019fc653/write-current-branch \ + --timeout 90 --yes-live +``` + +结果: + +- 读链路共 34 项:28 项 `pass`,2 项 `pass_empty`,4 项 `fixture_blocked`,无投影 + 不一致、下层错误或超时; +- 写链路共 57 项:46 项 `pass`,11 项 `external_fixture_required`,无执行错误; +- 写审计使用单用户临时群,并在 `finally` 中恢复可逆开关、解散临时群;按 4 类审计 + 名称前缀复查,残留为 0; +- 未覆盖项都保留精确 fixture 原因:数字 `groupId`、真实 `openTaskId`、媒体消息、配置 + 完成的机器人/Webhook、第二测试成员和待审批入群请求;不使用业务数据伪造通过; +- `+conversation-mute-at-all` 和 `+conversation-mute-red-envelope` 均在真实环境通过,旧 Mono + reference 的“当前不可用”结论已移除;真实前置条件是先开启总免打扰,否则平台返回 + `NotificationOffNotEnabled`;连续审计两个子开关时,红包应先于 @所有人,或在恢复 + @所有人通知后重新开启总免打扰; +- `+at-me` 曾因上层同时提供 `messages`/`items` 兼容别名被审计器重复计数,现固定选择一个 + canonical message projection;该问题属于审计器误报,不是 Runtime 数据丢失; +- 首轮 `+messages-list-unread-conversations` 出现一次瞬时后端超时,同一原子入口和最终 + 完整复测均通过,未将瞬时故障掩盖为 contract-only。 + +真实审计补充确定性门禁,但不替代 Schema、policy、单元测试和全量 Go 测试。 + ## 10. 完成定义 DWS Multi IM 优化完成应同时满足: diff --git a/internal/cli/schema_agent_metadata/index.json b/internal/cli/schema_agent_metadata/index.json index 764f96ec..0cc7fe5a 100644 --- a/internal/cli/schema_agent_metadata/index.json +++ b/internal/cli/schema_agent_metadata/index.json @@ -1,6 +1,6 @@ { "version": 1, - "source_hash": "sha256:61d739bfc037130d350e6ff1b8279262f22c73335c2b0ebda227f4dadb594de7", + "source_hash": "sha256:faa53064e9fff1f0869cd3f7cd522f9588512d2f416328a1b16524fd99b3c3e5", "surface_hash": "sha256:4891325eea3c74dcdbc3590139e69f38e39cfabd9491c3de75a09aed01d39cae", "coverage": { "surface_products": 26, diff --git a/internal/cli/schema_agent_metadata_audit.json b/internal/cli/schema_agent_metadata_audit.json index 55bde1e9..4de98eb8 100644 --- a/internal/cli/schema_agent_metadata_audit.json +++ b/internal/cli/schema_agent_metadata_audit.json @@ -1,6 +1,6 @@ { "version": 1, - "source_hash": "sha256:61d739bfc037130d350e6ff1b8279262f22c73335c2b0ebda227f4dadb594de7", + "source_hash": "sha256:faa53064e9fff1f0869cd3f7cd522f9588512d2f416328a1b16524fd99b3c3e5", "surface_hash": "sha256:4891325eea3c74dcdbc3590139e69f38e39cfabd9491c3de75a09aed01d39cae", "source_files": 160, "hint_files": 54, @@ -1676,7 +1676,7 @@ { "tool_path": "chat mute-red-envelope", "source": "skills/mono/references/products/chat.md", - "line": 1679, + "line": 1680, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1690,7 +1690,7 @@ { "tool_path": "chat mute-red-envelope", "source": "skills/mono/references/products/chat.md", - "line": 1680, + "line": 1681, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1704,7 +1704,7 @@ { "tool_path": "chat mark-unread", "source": "skills/mono/references/products/chat.md", - "line": 1700, + "line": 1702, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1718,7 +1718,7 @@ { "tool_path": "chat mark-unread", "source": "skills/mono/references/products/chat.md", - "line": 1701, + "line": 1703, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1732,7 +1732,7 @@ { "tool_path": "chat clear-red-point", "source": "skills/mono/references/products/chat.md", - "line": 1719, + "line": 1721, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1746,21 +1746,7 @@ { "tool_path": "chat clear-red-point", "source": "skills/mono/references/products/chat.md", - "line": 1720, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat clear-all-red-point", - "source": "skills/mono/references/products/chat.md", - "line": 1736, + "line": 1722, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1786,23 +1772,9 @@ } }, { - "tool_path": "chat list-all-conversations", + "tool_path": "chat clear-all-red-point", "source": "skills/mono/references/products/chat.md", - "line": 1752, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat list-all-conversations", - "source": "skills/mono/references/products/chat.md", - "line": 1753, + "line": 1740, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1842,9 +1814,23 @@ } }, { - "tool_path": "chat clear-messages", + "tool_path": "chat list-all-conversations", "source": "skills/mono/references/products/chat.md", - "line": 1775, + "line": 1756, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat list-all-conversations", + "source": "skills/mono/references/products/chat.md", + "line": 1757, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1858,7 +1844,21 @@ { "tool_path": "chat clear-messages", "source": "skills/mono/references/products/chat.md", - "line": 1776, + "line": 1777, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat clear-messages", + "source": "skills/mono/references/products/chat.md", + "line": 1778, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1872,7 +1872,7 @@ { "tool_path": "chat mark-read", "source": "skills/mono/references/products/chat.md", - "line": 1794, + "line": 1796, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1886,7 +1886,7 @@ { "tool_path": "chat mark-read", "source": "skills/mono/references/products/chat.md", - "line": 1795, + "line": 1797, "candidates": [ "chat +at-me", "chat +bot-find", @@ -1897,34 +1897,6 @@ "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" } }, - { - "tool_path": "chat group list-join-validations", - "source": "skills/mono/references/products/chat.md", - "line": 1838, - "candidates": [ - "chat group bots", - "chat group create", - "chat group dismiss" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat group list-join-validations", - "source": "skills/mono/references/products/chat.md", - "line": 1839, - "candidates": [ - "chat group bots", - "chat group create", - "chat group dismiss" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, { "tool_path": "chat group list-join-validations", "source": "skills/mono/references/products/chat.md", @@ -1940,9 +1912,23 @@ } }, { - "tool_path": "chat group audit-join-validation", + "tool_path": "chat group list-join-validations", "source": "skills/mono/references/products/chat.md", - "line": 1860, + "line": 1841, + "candidates": [ + "chat group bots", + "chat group create", + "chat group dismiss" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group list-join-validations", + "source": "skills/mono/references/products/chat.md", + "line": 1842, "candidates": [ "chat group bots", "chat group create", @@ -1956,7 +1942,21 @@ { "tool_path": "chat group audit-join-validation", "source": "skills/mono/references/products/chat.md", - "line": 1861, + "line": 1862, + "candidates": [ + "chat group bots", + "chat group create", + "chat group dismiss" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group audit-join-validation", + "source": "skills/mono/references/products/chat.md", + "line": 1863, "candidates": [ "chat group bots", "chat group create", @@ -1970,7 +1970,7 @@ { "tool_path": "chat group members", "source": "skills/mono/references/products/chat.md", - "line": 1893, + "line": 1895, "candidates": [ "chat group members add", "chat group members add-bot", @@ -1984,7 +1984,7 @@ { "tool_path": "chat group update-alias", "source": "skills/mono/references/products/chat.md", - "line": 1898, + "line": 1900, "candidates": [ "chat group bots", "chat group create", @@ -1998,7 +1998,7 @@ { "tool_path": "chat group members list-by-ids", "source": "skills/mono/references/products/chat.md", - "line": 1902, + "line": 1904, "candidates": [ "chat message list-by-ids", "chat group members add", @@ -2012,7 +2012,7 @@ { "tool_path": "chat category create", "source": "skills/mono/references/products/chat.md", - "line": 1929, + "line": 1931, "candidates": [ "chat group create", "chat category batch-info", @@ -2026,7 +2026,7 @@ { "tool_path": "chat category delete", "source": "skills/mono/references/products/chat.md", - "line": 1931, + "line": 1933, "candidates": [ "chat category batch-info", "chat category create-smart", @@ -2040,7 +2040,7 @@ { "tool_path": "chat category rename", "source": "skills/mono/references/products/chat.md", - "line": 1932, + "line": 1934, "candidates": [ "chat group rename", "chat category batch-info", @@ -2054,7 +2054,7 @@ { "tool_path": "chat category add-conv", "source": "skills/mono/references/products/chat.md", - "line": 1933, + "line": 1935, "candidates": [ "chat category batch-info", "chat category create-smart", @@ -2068,7 +2068,7 @@ { "tool_path": "chat category remove-conv", "source": "skills/mono/references/products/chat.md", - "line": 1934, + "line": 1936, "candidates": [ "chat category batch-info", "chat category create-smart", @@ -2082,7 +2082,7 @@ { "tool_path": "chat group share-invite", "source": "skills/mono/references/products/chat.md", - "line": 1945, + "line": 1947, "candidates": [ "chat group bots", "chat group create", @@ -2096,7 +2096,7 @@ { "tool_path": "chat group notice create/edit/get/list", "source": "skills/mono/references/products/chat.md", - "line": 1946, + "line": 1948, "candidates": [ "chat group bots", "chat group create", @@ -2110,7 +2110,7 @@ { "tool_path": "chat message list-emotion-replies", "source": "skills/mono/references/products/chat.md", - "line": 1948, + "line": 1950, "candidates": [ "chat message add-emoji", "chat message add-favorite", @@ -2124,34 +2124,6 @@ { "tool_path": "chat hide", "source": "skills/mono/references/products/chat.md", - "line": 1956, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat mute-at-all", - "source": "skills/mono/references/products/chat.md", - "line": 1957, - "candidates": [ - "chat +at-me", - "chat +bot-find", - "chat +bot-search" - ], - "review": { - "status": "stale", - "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" - } - }, - { - "tool_path": "chat mute-at-all", - "source": "skills/mono/references/products/chat.md", "line": 1958, "candidates": [ "chat +at-me", @@ -2164,7 +2136,7 @@ } }, { - "tool_path": "chat mute-red-envelope", + "tool_path": "chat mute-at-all", "source": "skills/mono/references/products/chat.md", "line": 1959, "candidates": [ @@ -2178,7 +2150,7 @@ } }, { - "tool_path": "chat mute-red-envelope", + "tool_path": "chat mute-at-all", "source": "skills/mono/references/products/chat.md", "line": 1960, "candidates": [ @@ -2192,7 +2164,7 @@ } }, { - "tool_path": "chat mark-unread", + "tool_path": "chat mute-red-envelope", "source": "skills/mono/references/products/chat.md", "line": 1961, "candidates": [ @@ -2206,7 +2178,7 @@ } }, { - "tool_path": "chat mark-read", + "tool_path": "chat mute-red-envelope", "source": "skills/mono/references/products/chat.md", "line": 1962, "candidates": [ @@ -2220,7 +2192,7 @@ } }, { - "tool_path": "chat clear-red-point", + "tool_path": "chat mark-unread", "source": "skills/mono/references/products/chat.md", "line": 1963, "candidates": [ @@ -2234,7 +2206,7 @@ } }, { - "tool_path": "chat clear-all-red-point", + "tool_path": "chat mark-read", "source": "skills/mono/references/products/chat.md", "line": 1964, "candidates": [ @@ -2248,7 +2220,7 @@ } }, { - "tool_path": "chat list-all-conversations", + "tool_path": "chat clear-red-point", "source": "skills/mono/references/products/chat.md", "line": 1965, "candidates": [ @@ -2262,7 +2234,7 @@ } }, { - "tool_path": "chat clear-messages", + "tool_path": "chat clear-all-red-point", "source": "skills/mono/references/products/chat.md", "line": 1966, "candidates": [ @@ -2276,9 +2248,37 @@ } }, { - "tool_path": "chat group list-join-validations", + "tool_path": "chat list-all-conversations", + "source": "skills/mono/references/products/chat.md", + "line": 1967, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat clear-messages", "source": "skills/mono/references/products/chat.md", "line": 1968, + "candidates": [ + "chat +at-me", + "chat +bot-find", + "chat +bot-search" + ], + "review": { + "status": "stale", + "reason": "旧版 Skill 或说明性引用,当前公开命令面无等价 leaf,禁止词法模糊映射" + } + }, + { + "tool_path": "chat group list-join-validations", + "source": "skills/mono/references/products/chat.md", + "line": 1970, "candidates": [ "chat group bots", "chat group create", @@ -2292,7 +2292,7 @@ { "tool_path": "chat group audit-join-validation", "source": "skills/mono/references/products/chat.md", - "line": 1969, + "line": 1971, "candidates": [ "chat group bots", "chat group create", @@ -2306,7 +2306,7 @@ { "tool_path": "chat text translate", "source": "skills/mono/references/products/chat.md", - "line": 1989, + "line": 1991, "candidates": [ "chat +at-me", "chat +bot-find", diff --git a/internal/cli/schema_catalog/catalog.json b/internal/cli/schema_catalog/catalog.json index 2ef61d9e..294e7321 100644 --- a/internal/cli/schema_catalog/catalog.json +++ b/internal/cli/schema_catalog/catalog.json @@ -1,12 +1,12 @@ { "version": 1, "surface_hash": "sha256:4891325eea3c74dcdbc3590139e69f38e39cfabd9491c3de75a09aed01d39cae", - "source_hash": "sha256:bcc29fd37a6f54daa88aa0824ade6c50176a58bcd14d7cc86ad05751428d4ce4", + "source_hash": "sha256:9f5b873d3996df76f7f584c7217adbba98beb9848db3c6557a9ab94c8fad802c", "catalog": { "agent_metadata": { "products_with_metadata": 26, "source": "embedded-skill-metadata", - "source_hash": "sha256:61d739bfc037130d350e6ff1b8279262f22c73335c2b0ebda227f4dadb594de7", + "source_hash": "sha256:faa53064e9fff1f0869cd3f7cd522f9588512d2f416328a1b16524fd99b3c3e5", "surface_hash": "sha256:4891325eea3c74dcdbc3590139e69f38e39cfabd9491c3de75a09aed01d39cae", "surface_products": 26, "surface_tools": 849, diff --git a/internal/shortcut/chat/chat_conversation.go b/internal/shortcut/chat/chat_conversation.go index 3f87d7eb..feda0838 100644 --- a/internal/shortcut/chat/chat_conversation.go +++ b/internal/shortcut/chat/chat_conversation.go @@ -137,7 +137,7 @@ var ConversationMuteAtAll = shortcut.Shortcut{ Command: "+conversation-mute-at-all", Product: "im", Description: "关闭/开启 @所有人消息提醒", - Intent: "当你在某个群里不想再被'@所有人'打扰、或想恢复该提醒时使用;会实际修改该会话的@所有人提醒开关,需传 openConversationId。", + Intent: "当你已对某个会话开启消息免打扰,并希望额外关闭或恢复'@所有人'提醒时使用;这是免打扰的子开关,若尚未开启总免打扰,先执行 +conversation-mute,否则平台会返回 NotificationOffNotEnabled。", Risk: shortcut.RiskWrite, Flags: []shortcut.Flag{ {Name: "conversation-id", Type: shortcut.FlagString, Desc: "会话 openConversationId", Required: true}, @@ -158,7 +158,7 @@ var ConversationMuteRedEnvelope = shortcut.Shortcut{ Command: "+conversation-mute-red-envelope", Product: "im", Description: "关闭/开启红包消息提醒", - Intent: "当你想在某个会话里关闭或恢复红包消息提醒时使用;会实际修改该会话的红包提醒开关,需传 openConversationId。", + Intent: "当你已对某个会话开启消息免打扰,并希望额外关闭或恢复红包提醒时使用;这是免打扰的子开关,若尚未开启总免打扰,或刚恢复过@所有人提醒,先执行 +conversation-mute,否则平台会返回 NotificationOffNotEnabled。", Risk: shortcut.RiskWrite, Flags: []shortcut.Flag{ {Name: "conversation-id", Type: shortcut.FlagString, Desc: "会话 openConversationId", Required: true}, diff --git a/internal/shortcut/chat/compatibility_coverage_test.go b/internal/shortcut/chat/compatibility_coverage_test.go index 31f96297..3757981c 100644 --- a/internal/shortcut/chat/compatibility_coverage_test.go +++ b/internal/shortcut/chat/compatibility_coverage_test.go @@ -220,6 +220,15 @@ func TestCrossPlatformCoverageCompatibilityAliases(t *testing.T) { } } +func TestConversationNotificationSubSwitchesPublishMutePrerequisite(t *testing.T) { + for _, command := range []shortcut.Shortcut{ConversationMuteAtAll, ConversationMuteRedEnvelope} { + if !strings.Contains(command.Intent, "+conversation-mute") || + !strings.Contains(command.Intent, "NotificationOffNotEnabled") { + t.Errorf("%s intent does not publish the live platform prerequisite: %q", command.Command, command.Intent) + } + } +} + func TestCrossPlatformCoverageChatIDHelpers(t *testing.T) { t.Run("recognize open DingTalk IDs", func(t *testing.T) { tests := []struct { diff --git a/scripts/run_chat_shortcut_live_audit.py b/scripts/run_chat_shortcut_live_audit.py index 3047dca5..27cd9972 100755 --- a/scripts/run_chat_shortcut_live_audit.py +++ b/scripts/run_chat_shortcut_live_audit.py @@ -306,6 +306,23 @@ def reaction_message_count(value: Any, *, upper: bool) -> int: return count +def primary_projection_for_message_metrics(value: Any) -> Any: + """Select one canonical projected collection for message-level metrics. + + Some stable Shortcut payloads publish compatibility aliases such as + ``messages`` and ``items`` with the same rows. Walking the entire envelope + would count reactions and thread IDs once per alias and report a false + projection mismatch even though the lower and upper message sets agree. + """ + if not isinstance(value, dict): + return value + for key in ("messages", "replies", "results", "items"): + items = value.get(key) + if isinstance(items, list): + return items + return value + + def thread_message_count(value: Any, *, upper: bool) -> int: keys = {"threadId"} if upper else { "openConvThreadId", @@ -393,9 +410,10 @@ def summarize_capture(capture: Capture) -> dict[str, Any]: else 0 ) exact_passthrough = bool(capture.raw_calls and upper == raw) - upper_reactions = reaction_message_count(upper, upper=True) + upper_message_projection = primary_projection_for_message_metrics(upper) + upper_reactions = reaction_message_count(upper_message_projection, upper=True) lower_reactions = reaction_message_count(raw, upper=False) - upper_threads = thread_message_count(upper, upper=True) + upper_threads = thread_message_count(upper_message_projection, upper=True) lower_threads = thread_message_count(raw, upper=False) upper_pagination = pagination_meta(upper) lower_pagination = pagination_meta(pagination_raw) diff --git a/scripts/run_chat_shortcut_live_audit_test.py b/scripts/run_chat_shortcut_live_audit_test.py new file mode 100644 index 00000000..e56a9b44 --- /dev/null +++ b/scripts/run_chat_shortcut_live_audit_test.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Deterministic regression checks for the live Chat audit summarizer.""" + +from __future__ import annotations + +from run_chat_shortcut_live_audit import ( + primary_projection_for_message_metrics, + reaction_message_count, + thread_message_count, +) + + +def check(name: str, condition: bool) -> None: + if not condition: + raise AssertionError(f"FAIL: {name}") + print(f" ok: {name}") + + +def test_compatibility_aliases_are_counted_once() -> None: + rows = [{"threadId": "thread-1", "reactions": [{"type": "LIKE"}]}] + payload = {"messages": rows, "items": rows, "count": 1} + projection = primary_projection_for_message_metrics(payload) + + check("messages is the canonical projection", projection is rows) + check("reaction-bearing message counted once", reaction_message_count(projection, upper=True) == 1) + check("thread-bearing message counted once", thread_message_count(projection, upper=True) == 1) + + +def test_projection_fallback_order() -> None: + replies = [{"threadId": "thread-2"}] + items = [{"threadId": "thread-3"}] + check( + "replies precedes items", + primary_projection_for_message_metrics({"replies": replies, "items": items}) is replies, + ) + check("items remains a supported fallback", primary_projection_for_message_metrics({"items": items}) is items) + + +def test_non_envelope_passthrough() -> None: + rows = [{"threadId": "thread-4"}] + check("bare list passes through", primary_projection_for_message_metrics(rows) is rows) + + +def main() -> None: + tests = [ + test_compatibility_aliases_are_counted_once, + test_projection_fallback_order, + test_non_envelope_passthrough, + ] + for test in tests: + print(f"{test.__name__}:") + test() + print(f"\nAll {len(tests)} test groups passed.") + + +if __name__ == "__main__": + main() diff --git a/scripts/run_chat_shortcut_live_write_audit.py b/scripts/run_chat_shortcut_live_write_audit.py index 87731fb9..33f9ddf6 100644 --- a/scripts/run_chat_shortcut_live_write_audit.py +++ b/scripts/run_chat_shortcut_live_write_audit.py @@ -474,9 +474,22 @@ def run_live(binary: Path, timeout: int) -> list[dict[str, Any]]: ) role_id = "" + # The platform only permits the @all and red-envelope sub-switches + # while the conversation-wide notification-off switch is enabled. + # Keep that prerequisite active across both cases, then restore it. + conversation_mute_captures = [ + run_capture( + binary, + "+conversation-mute", + ["--conversation-id", group_id, "--yes"], + timeout, + ) + ] + # Red-envelope must be exercised first. Toggling the @all sub-switch + # can make the service clear the prerequisite state for the next call. shortcut_case( - "+conversation-mute", - "update_notification_off", + "+conversation-mute-red-envelope", + "update_red_env_notification_off", [ ["--conversation-id", group_id], ["--conversation-id", group_id, "--off"], @@ -490,13 +503,20 @@ def run_live(binary: Path, timeout: int) -> list[dict[str, Any]]: ["--conversation-id", group_id, "--off"], ], ) - shortcut_case( - "+conversation-mute-red-envelope", - "update_red_env_notification_off", - [ - ["--conversation-id", group_id], - ["--conversation-id", group_id, "--off"], - ], + conversation_mute_captures.append( + run_capture( + binary, + "+conversation-mute", + ["--conversation-id", group_id, "--off", "--yes"], + timeout, + ) + ) + results.append( + summarize_action( + "+conversation-mute", + "update_notification_off", + conversation_mute_captures, + ) ) shortcut_case( "+conversation-set-top", diff --git a/skills/mono/references/products/chat.md b/skills/mono/references/products/chat.md index 26ad0255..c00867fb 100644 --- a/skills/mono/references/products/chat.md +++ b/skills/mono/references/products/chat.md @@ -1648,7 +1648,7 @@ Flags: #### 关闭/开启 @所有人消息提醒 — 关闭或开启会话中 @所有人的消息通知 -> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。命令本身参数合法,但调用不会生效。 +> 前置条件:先为会话开启总免打扰(`dws chat mute --conversation-id `),否则平台返回 `NotificationOffNotEnabled`。 ``` Usage: dws chat mute-at-all [flags] @@ -1664,6 +1664,7 @@ Flags: 注意: - 默认行为是关闭 @所有人通知,传 --off 则恢复接收通知 + - 该子开关依赖总免打扰;恢复 @所有人通知后,再修改红包子开关前应重新开启总免打扰 - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 ``` @@ -1671,7 +1672,7 @@ Flags: #### 关闭/开启红包消息提醒 — 关闭或开启会话中的红包消息通知 -> ⚠️ 当前不可用:该命令当前调用常失败(服务端返回 1002 系统繁忙),多为服务端侧限制。命令本身参数合法,但调用不会生效。 +> 前置条件:先为会话开启总免打扰(`dws chat mute --conversation-id `),否则平台返回 `NotificationOffNotEnabled`。 ``` Usage: dws chat mute-red-envelope [flags] @@ -1687,6 +1688,7 @@ Flags: 注意: - 默认行为是关闭红包通知,传 --off 则恢复接收通知 + - 若刚恢复了 @所有人通知,应先重新开启总免打扰,再修改红包通知 - 支持单聊和群聊,openConversationId 可通过 chat search(群聊)或 chat conversation-info(单聊)获取 ``` @@ -2020,8 +2022,8 @@ Flags: - `chat category list-by-conv` / `chat category batch-info` — 查询会话所属分组 / 批量查询分组信息 - `chat mute` — 开启/关闭会话消息免打扰(默认开启,--off 关闭) - `chat hide` — 在会话列表中隐藏会话(支持单聊/群聊,收到新消息时重新出现) -- `chat mute-at-all` — 关闭/开启 @所有人消息提醒(默认关闭,--off 恢复) -- `chat mute-red-envelope` — 关闭/开启红包消息提醒(默认关闭,--off 恢复) +- `chat mute-at-all` — 关闭/开启 @所有人消息提醒(默认关闭,--off 恢复;需先开启总免打扰) +- `chat mute-red-envelope` — 关闭/开启红包消息提醒(默认关闭,--off 恢复;需先开启总免打扰) - `chat mark-unread` / `chat mark-read` — 标记会话未读 / 标记指定消息及之前的消息已读 - `chat clear-red-point` / `chat clear-all-red-point` — 清除单个会话红点 / 一键清除所有会话红点(全部已读) - `chat list-all-conversations` — 分页拉取当前用户全部会话列表(单聊+群聊,与 list-top-conversations 的区别是不限置顶) @@ -2308,8 +2310,8 @@ Flags: - `chat category list` 无需参数;`category list-conversations` 需传 --category-id(通过 category list 获取) - `chat mute` 默认开启免打扰,传 --off 关闭;--conversation-id / --id / --chat 三个别名均可用于传入会话 ID - `chat hide` 隐藏会话,需传 --conversation-id(openConversationId,支持单聊/群聊),隐藏后不显示在列表中,收到新消息时重新出现 -- `chat mute-at-all` 关闭/开启 @所有人消息提醒,需传 --conversation-id(openConversationId),默认关闭通知,传 --off 恢复接收 -- `chat mute-red-envelope` 关闭/开启红包消息提醒,需传 --conversation-id(openConversationId),默认关闭通知,传 --off 恢复接收 +- `chat mute-at-all` 关闭/开启 @所有人消息提醒,需传 --conversation-id(openConversationId),默认关闭通知,传 --off 恢复接收;调用前需先开启总免打扰 +- `chat mute-red-envelope` 关闭/开启红包消息提醒,需传 --conversation-id(openConversationId),默认关闭通知,传 --off 恢复接收;调用前需先开启总免打扰 - `chat message reply` 引用回复消息(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段)、--ref-msg-id(被引用消息 openMessageId)、--ref-sender(被引用消息发送者 openDingTalkId)、--text(回复内容);目前回复类型仅支持 text - `chat message forward` 转发单条消息(**源/目标会话均支持单聊/群聊**,常见组合:群→群、群→单、单→群、单→单),需传 --src-conversation-id(源会话 openConversationId)、--msg-id(源消息 openMessageId)、--dest-conversation-id(目标会话 openConversationId) - `chat set-top` 设置/取消会话置顶(**单聊/群聊均可**),需传 --conversation-id(openConversationId,单聊与群聊使用同一字段),默认置顶,传 --off 取消 diff --git a/skills/multi/dingtalk-chat/references/chat/chat-conversation.md b/skills/multi/dingtalk-chat/references/chat/chat-conversation.md index 66d0d467..148bc6db 100644 --- a/skills/multi/dingtalk-chat/references/chat/chat-conversation.md +++ b/skills/multi/dingtalk-chat/references/chat/chat-conversation.md @@ -49,8 +49,11 @@ dws chat conversation-info --open-dingtalk-id --format json | `set-top` | 设置/取消会话置顶 | `--conversation-id`;默认置顶,`--off` 取消 | | `mute` | 开启/关闭会话免打扰 | `--conversation-id`;默认开启,`--off` 关闭 | | `hide` | 隐藏会话 | `--conversation-id` | -| `mute-at-all` | 关闭/恢复 @所有人通知 | `--conversation-id`;默认关闭,`--off` 恢复 | -| `mute-red-envelope` | 关闭/恢复红包通知 | `--conversation-id`;默认关闭,`--off` 恢复 | +| `mute-at-all` | 关闭/恢复 @所有人通知 | `--conversation-id`;默认关闭,`--off` 恢复;必须先开启会话总免打扰 | +| `mute-red-envelope` | 关闭/恢复红包通知 | `--conversation-id`;默认关闭,`--off` 恢复;必须先开启会话总免打扰 | + +若连续操作两个子开关,优先操作红包通知;恢复 @所有人通知后,平台可能清除子开关所需的 +总免打扰状态,此时要先重新开启总免打扰,再操作红包通知。 ```bash dws chat set-top --conversation-id