From dbee2de1d5c40e1f2f7919d08a4311481b07da7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=95=BF=E7=9C=9F?= Date: Wed, 12 Aug 2026 22:33:15 +0800 Subject: [PATCH] fix(chat): align im id flag migration scope --- internal/cli/param_aliases_generated.go | 162 ++++++++++-------- internal/cli/param_concepts.json | 26 +-- internal/cli/schema_catalog_test.go | 37 ++-- internal/corecmd/corecmd_test.go | 10 ++ internal/helpers/chat.go | 151 +++++++++++++--- internal/helpers/chat_command_edges_test.go | 79 ++++++++- internal/helpers/chat_message_help_test.go | 28 +-- internal/helpers/chat_ports_coverage_test.go | 74 ++++++-- .../wukong_weekly_sync_chat_todo_test.go | 2 +- 9 files changed, 414 insertions(+), 155 deletions(-) diff --git a/internal/cli/param_aliases_generated.go b/internal/cli/param_aliases_generated.go index e5fc9df1..63c62457 100644 --- a/internal/cli/param_aliases_generated.go +++ b/internal/cli/param_aliases_generated.go @@ -889,11 +889,9 @@ var generatedParamAliases = []ParamAliasEntry{ Blocked: []string{"cursor"}, }, { - CLIPath: "chat category add-conv", - Aliases: map[string]string{ - "chat-id": "conversation-id", - }, - Blocked: []string{"category-id", "conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, + CLIPath: "chat category add-conv", + Blocked: []string{"category-id"}, + Ambiguous: []string{"chat", "chat-id", "open-conversation-id"}, }, { CLIPath: "chat category create", @@ -918,11 +916,9 @@ var generatedParamAliases = []ParamAliasEntry{ Blocked: []string{"category-ids"}, }, { - CLIPath: "chat category remove-conv", - Aliases: map[string]string{ - "chat-id": "conversation-id", - }, - Blocked: []string{"category-id", "conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, + CLIPath: "chat category remove-conv", + Blocked: []string{"category-id"}, + Ambiguous: []string{"chat", "chat-id", "open-conversation-id"}, }, { CLIPath: "chat category rename", @@ -934,37 +930,44 @@ var generatedParamAliases = []ParamAliasEntry{ { CLIPath: "chat chmod", Aliases: map[string]string{ - "chat-id": "conversation-id", - "staff-id": "user", - "uid": "user", - "user-id": "user", - "userid": "user", + "chat": "conversation-id", + "chat-id": "conversation-id", + "group": "conversation-id", + "open-conversation-id": "conversation-id", + "staff-id": "user", + "uid": "user", + "user-id": "user", + "userid": "user", }, - Blocked: []string{"at-user-ids", "conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target", "to-user", "user-ids", "users"}, + Blocked: []string{"at-user-ids", "conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "id", "name", "open-conversation-ids", "source", "src-conversation-id", "target", "to-user", "user-ids", "users"}, }, { CLIPath: "chat clear-messages", Aliases: map[string]string{ - "chat-id": "conversation-id", + "chat-id": "conversation-id", + "group": "conversation-id", + "open-conversation-id": "conversation-id", }, Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, }, { CLIPath: "chat clear-red-point", Aliases: map[string]string{ - "chat-id": "conversation-id", + "chat-id": "conversation-id", + "group": "conversation-id", + "open-conversation-id": "conversation-id", }, Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, }, { CLIPath: "chat conversation-info", Aliases: map[string]string{ - "chat-id": "conversation-id", "staff-id": "user", "uid": "user", "userid": "user", }, - Blocked: []string{"at-user-ids", "conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target", "to-user", "user-ids", "users"}, + Blocked: []string{"at-user-ids", "name", "to-user", "user-ids", "users"}, + Ambiguous: []string{"chat-id", "open-conversation-id"}, }, { CLIPath: "chat group audit-join-validation", @@ -976,9 +979,12 @@ var generatedParamAliases = []ParamAliasEntry{ { CLIPath: "chat group bots", Aliases: map[string]string{ - "chat-id": "conversation-id", + "chat": "group", + "chat-id": "group", + "conversation-id": "group", + "open-conversation-id": "group", }, - Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, + Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "id", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, }, { CLIPath: "chat group create", @@ -1181,19 +1187,16 @@ var generatedParamAliases = []ParamAliasEntry{ Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, }, { - CLIPath: "chat group-mute", - Aliases: map[string]string{ - "chat-id": "conversation-id", - }, - Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, + CLIPath: "chat group-mute", + Ambiguous: []string{"chat-id", "open-conversation-id"}, }, { CLIPath: "chat group-mute-member", Aliases: map[string]string{ - "chat-id": "conversation-id", "user-ids": "users", }, - Blocked: []string{"at-user-ids", "conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "staff-id", "target", "uid", "userid"}, + Blocked: []string{"at-user-ids", "staff-id", "uid", "userid"}, + Ambiguous: []string{"chat-id", "open-conversation-id"}, }, { CLIPath: "chat group-role add", @@ -1256,21 +1259,29 @@ var generatedParamAliases = []ParamAliasEntry{ { CLIPath: "chat hide", Aliases: map[string]string{ - "chat-id": "conversation-id", + "chat-id": "conversation-id", + "group": "conversation-id", + "open-conversation-id": "conversation-id", }, Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, }, { CLIPath: "chat mark-read", Aliases: map[string]string{ - "chat-id": "conversation-id", + "chat-id": "conversation-id", + "group": "conversation-id", + "msg-id": "message-id", + "open-conversation-id": "conversation-id", + "open-message-id": "message-id", }, Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "message-ids", "msg-ids", "name", "open-conversation-ids", "open-message-ids", "open-task-id", "ref-msg-id", "resource-id", "source", "src-conversation-id", "src-msg-id", "target", "topic-id"}, }, { CLIPath: "chat mark-unread", Aliases: map[string]string{ - "chat-id": "conversation-id", + "chat-id": "conversation-id", + "group": "conversation-id", + "open-conversation-id": "conversation-id", }, Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, }, @@ -1279,7 +1290,8 @@ var generatedParamAliases = []ParamAliasEntry{ Aliases: map[string]string{ "chat-id": "conversation-id", }, - Blocked: []string{"conversation-ids", "group-id", "group-ids", "message-ids", "msg-ids", "open-conversation-ids", "open-message-ids", "open-task-id", "ref-msg-id", "resource-id", "src-msg-id", "topic-id"}, + Blocked: []string{"conversation-ids", "group-id", "group-ids", "open-conversation-ids"}, + Ambiguous: []string{"open-message-id"}, }, { CLIPath: "chat message add-favorite", @@ -1298,7 +1310,8 @@ var generatedParamAliases = []ParamAliasEntry{ Aliases: map[string]string{ "chat-id": "conversation-id", }, - Blocked: []string{"conversation-ids", "group-id", "group-ids", "message-ids", "msg-ids", "open-conversation-ids", "open-message-ids", "open-task-id", "ref-msg-id", "resource-id", "src-msg-id", "topic-id"}, + Blocked: []string{"conversation-ids", "group-id", "group-ids", "open-conversation-ids"}, + Ambiguous: []string{"open-message-id"}, }, { CLIPath: "chat message combine-forward", @@ -1353,7 +1366,6 @@ var generatedParamAliases = []ParamAliasEntry{ { CLIPath: "chat message list", Aliases: map[string]string{ - "chat-id": "conversation-id", "max-result": "limit", "max-results": "limit", "page-size": "limit", @@ -1366,7 +1378,8 @@ var generatedParamAliases = []ParamAliasEntry{ "user-id": "user", "userid": "user", }, - Blocked: []string{"at-user-ids", "conversation-ids", "count", "cursor", "dest-conversation-id", "end", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "page", "source", "src-conversation-id", "target", "to-user", "user-ids", "users"}, + Blocked: []string{"at-user-ids", "count", "cursor", "end", "name", "page", "to-user", "user-ids", "users"}, + Ambiguous: []string{"chat-id", "open-conversation-id"}, }, { CLIPath: "chat message list-all", @@ -1413,11 +1426,8 @@ var generatedParamAliases = []ParamAliasEntry{ }, }, { - CLIPath: "chat message list-mentions", - Aliases: map[string]string{ - "chat-id": "conversation-id", - }, - Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, + CLIPath: "chat message list-mentions", + Ambiguous: []string{"chat-id", "open-conversation-id"}, }, { CLIPath: "chat message list-pin-msg", @@ -1446,32 +1456,37 @@ var generatedParamAliases = []ParamAliasEntry{ { CLIPath: "chat message read-status", Aliases: map[string]string{ - "chat-id": "conversation-id", - "user-ids": "users", + "chat-id": "conversation-id", + "msg-id": "message-id", + "open-conversation-id": "conversation-id", + "open-message-id": "message-id", + "user-ids": "users", }, Blocked: []string{"at-user-ids", "conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "message-ids", "msg-ids", "name", "open-conversation-ids", "open-message-ids", "open-task-id", "ref-msg-id", "resource-id", "source", "src-conversation-id", "src-msg-id", "staff-id", "target", "topic-id", "uid", "userid"}, }, { CLIPath: "chat message recall", Aliases: map[string]string{ - "chat-id": "conversation-id", + "chat-id": "conversation-id", + "open-conversation-id": "conversation-id", }, Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "message-ids", "msg-ids", "name", "open-conversation-ids", "open-message-ids", "open-task-id", "ref-msg-id", "resource-id", "source", "src-conversation-id", "src-msg-id", "target", "topic-id"}, }, { CLIPath: "chat message recall-by-bot", Aliases: map[string]string{ - "chat-id": "conversation-id", - "robot": "robot-code", + "robot": "robot-code", }, - Blocked: []string{"bot-code", "bot-id", "conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-bot-id", "open-conversation-ids", "robot-id", "source", "src-conversation-id", "target"}, + Blocked: []string{"bot-code", "bot-id", "open-bot-id", "robot-id"}, + Ambiguous: []string{"chat-id", "open-conversation-id"}, }, { CLIPath: "chat message remove-emoji", Aliases: map[string]string{ "chat-id": "conversation-id", }, - Blocked: []string{"conversation-ids", "group-id", "group-ids", "message-ids", "msg-ids", "open-conversation-ids", "open-message-ids", "open-task-id", "ref-msg-id", "resource-id", "src-msg-id", "topic-id"}, + Blocked: []string{"conversation-ids", "group-id", "group-ids", "open-conversation-ids"}, + Ambiguous: []string{"open-message-id"}, }, { CLIPath: "chat message remove-favorite", @@ -1490,22 +1505,23 @@ var generatedParamAliases = []ParamAliasEntry{ Aliases: map[string]string{ "chat-id": "conversation-id", }, - Blocked: []string{"conversation-ids", "group-id", "group-ids", "message-ids", "msg-ids", "open-conversation-ids", "open-message-ids", "open-task-id", "ref-msg-id", "resource-id", "src-msg-id", "topic-id"}, + Blocked: []string{"conversation-ids", "group-id", "group-ids", "open-conversation-ids"}, + Ambiguous: []string{"open-message-id"}, }, { CLIPath: "chat message reply", Aliases: map[string]string{ - "chat-id": "conversation-id", - "ref-message-id": "ref-msg-id", + "chat": "conversation-id", + "chat-id": "conversation-id", + "group": "conversation-id", + "open-conversation-id": "conversation-id", + "ref-message-id": "ref-msg-id", }, - Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "message-id", "msg-id", "msg-ids", "name", "open-conversation-ids", "open-message-id", "source", "src-conversation-id", "src-msg-id", "staff-id", "target", "uid", "user", "user-id", "userid"}, + Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "id", "message-id", "msg-id", "msg-ids", "name", "open-conversation-ids", "open-message-id", "source", "src-conversation-id", "src-msg-id", "staff-id", "target", "uid", "user", "user-id", "userid"}, }, { - CLIPath: "chat message search", - Aliases: map[string]string{ - "chat-id": "conversation-id", - }, - Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, + CLIPath: "chat message search", + Ambiguous: []string{"chat-id", "open-conversation-id"}, }, { CLIPath: "chat message search-advanced", @@ -1518,7 +1534,6 @@ var generatedParamAliases = []ParamAliasEntry{ { CLIPath: "chat message send", Aliases: map[string]string{ - "chat-id": "conversation-id", "file": "file-path", "staff-id": "user", "to-user": "user", @@ -1526,18 +1541,18 @@ var generatedParamAliases = []ParamAliasEntry{ "user-id": "user", "userid": "user", }, - Blocked: []string{"at-user-ids", "conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target", "user-ids", "users"}, + Blocked: []string{"at-user-ids", "name", "user-ids", "users"}, + Ambiguous: []string{"chat-id", "open-conversation-id"}, }, { CLIPath: "chat message send-by-bot", Aliases: map[string]string{ "at-users": "at-user-ids", - "chat-id": "conversation-id", "robot": "robot-code", "user-ids": "users", }, - Blocked: []string{"bot-code", "bot-id", "conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-bot-id", "open-conversation-ids", "robot-id", "source", "src-conversation-id", "staff-id", "target", "to-user-id", "uid", "user", "user-id", "userid"}, - Ambiguous: []string{"at-ids"}, + Blocked: []string{"bot-code", "bot-id", "open-bot-id", "robot-id", "staff-id", "to-user-id", "uid", "user", "user-id", "userid"}, + Ambiguous: []string{"at-ids", "chat-id", "open-conversation-id"}, }, { CLIPath: "chat message send-by-webhook", @@ -1546,11 +1561,9 @@ var generatedParamAliases = []ParamAliasEntry{ }, }, { - CLIPath: "chat message send-card", - Aliases: map[string]string{ - "chat-id": "conversation-id", - }, - Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "staff-id", "target", "uid", "user", "user-id", "userid"}, + CLIPath: "chat message send-card", + Blocked: []string{"staff-id", "uid", "user", "user-id", "userid"}, + Ambiguous: []string{"chat", "chat-id", "open-conversation-id"}, }, { CLIPath: "chat message set-pin-msg", @@ -1602,23 +1615,30 @@ var generatedParamAliases = []ParamAliasEntry{ { CLIPath: "chat mute-at-all", Aliases: map[string]string{ - "chat-id": "conversation-id", + "chat-id": "conversation-id", + "group": "conversation-id", + "open-conversation-id": "conversation-id", }, Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, }, { CLIPath: "chat mute-red-envelope", Aliases: map[string]string{ - "chat-id": "conversation-id", + "chat-id": "conversation-id", + "group": "conversation-id", + "open-conversation-id": "conversation-id", }, Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, }, { CLIPath: "chat set-top", Aliases: map[string]string{ - "chat-id": "conversation-id", + "chat": "conversation-id", + "chat-id": "conversation-id", + "group": "conversation-id", + "open-conversation-id": "conversation-id", }, - Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, + Blocked: []string{"conversation-ids", "dest-conversation-id", "group-id", "group-ids", "group-name", "id", "name", "open-conversation-ids", "source", "src-conversation-id", "target"}, }, { CLIPath: "contact +dept-members", diff --git a/internal/cli/param_concepts.json b/internal/cli/param_concepts.json index 7d37a432..1bba1a07 100644 --- a/internal/cli/param_concepts.json +++ b/internal/cli/param_concepts.json @@ -49,10 +49,16 @@ "chat group members": {"bind": {"id": "open_conversation_id"}}, "chat group members add": {"bind": {"id": "open_conversation_id"}, "block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a list and may contain mixed userId/openDingTalkId values; singular inputs are not promoted automatically."}, "chat group members remove": {"bind": {"id": "open_conversation_id"}}, - "chat message add-emoji": {"scoped_aliases": {"chat-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id/--open-conversation-id stay native; numeric groupId and list spellings are rejected."}, - "chat message add-text-emotion": {"scoped_aliases": {"chat-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id/--open-conversation-id stay native; numeric groupId and list spellings are rejected."}, - "chat message remove-emoji": {"scoped_aliases": {"chat-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id/--open-conversation-id stay native; numeric groupId and list spellings are rejected."}, - "chat message remove-text-emotion": {"scoped_aliases": {"chat-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --chat/--group/--id/--conversation-id/--open-conversation-id stay native; numeric groupId and list spellings are rejected."}, + "chat conversation-info": {"ambiguous": ["chat-id", "open-conversation-id"], "note": "Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, + "chat group-mute": {"ambiguous": ["chat-id", "open-conversation-id"], "note": "Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, + "chat group-mute-member": {"ambiguous": ["chat-id", "open-conversation-id"], "note": "Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, + "chat message add-emoji": {"scoped_aliases": {"chat-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "ambiguous": ["open-message-id"], "note": "Native --chat/--group/--id/--conversation-id/--open-conversation-id and visible --message-id/--msg-id stay executable; numeric/list spellings are rejected."}, + "chat message add-text-emotion": {"scoped_aliases": {"chat-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "ambiguous": ["open-message-id"], "note": "Native --chat/--group/--id/--conversation-id/--open-conversation-id and visible --message-id/--msg-id stay executable; numeric/list spellings are rejected."}, + "chat message list-mentions": {"ambiguous": ["chat-id", "open-conversation-id"], "note": "Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, + "chat message recall-by-bot": {"ambiguous": ["chat-id", "open-conversation-id"], "note": "Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, + "chat message remove-emoji": {"scoped_aliases": {"chat-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "ambiguous": ["open-message-id"], "note": "Native --chat/--group/--id/--conversation-id/--open-conversation-id and visible --message-id/--msg-id stay executable; numeric/list spellings are rejected."}, + "chat message remove-text-emotion": {"scoped_aliases": {"chat-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "ambiguous": ["open-message-id"], "note": "Native --chat/--group/--id/--conversation-id/--open-conversation-id and visible --message-id/--msg-id stay executable; numeric/list spellings are rejected."}, + "chat message search": {"ambiguous": ["chat-id", "open-conversation-id"], "note": "Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, "chat mute": {"scoped_aliases": {"chat-id": "conversation-id"}, "block": ["group-id", "group-ids", "conversation-ids", "open-conversation-ids"], "note": "Native --conversation-id and hidden compatibility --group/--id/--chat/--open-conversation-id remain unchanged; reviewed chat-id reduces to --conversation-id."}, "drive list": {"ambiguous": ["space"], "note": "both --space-id and native compatibility --workspace-id/--workspace exist; bare --space cannot choose one"}, "drive upload": {"ambiguous": ["space"], "note": "both --space-id and native compatibility --workspace-id/--workspace exist; bare --space cannot choose one"}, @@ -73,12 +79,12 @@ "chat +unread-chats": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."}, "chat message list-unread-conversations": {"scoped_aliases": {"limit": "count", "size": "count"}, "scope_strict": true, "note": "On this exact command, limit and size both denote the returned unread-conversation count."}, "chat +messages-list-direct": {"scoped_aliases": {"start": "time"}, "block": ["end"], "scope_strict": true, "note": "This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented."}, - "chat message list": {"scoped_aliases": {"start": "time"}, "block": ["end"], "scope_strict": true, "note": "This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented."}, + "chat message list": {"scoped_aliases": {"start": "time"}, "block": ["end"], "ambiguous": ["chat-id", "open-conversation-id"], "scope_strict": true, "note": "This exact command accepts one start boundary in yyyy-MM-dd HH:mm:ss; an end-only input cannot be represented. Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, "chat message list-by-sender": {"scoped_aliases": {"user-id": "sender-user-id", "open-dingtalk-id": "sender-open-dingtalk-id"}, "block": ["time"], "scope_strict": true, "note": "Only same-role sender identifiers are mapped; --time cannot supply the required RFC3339 start/end range."}, "contact +resolve-dept": {"bind": {"name": "search_query"}, "note": "The real --name is a department-name search keyword and carries the search_query concept on this shortcut."}, "contact +list-sub-depts": {"block": ["name", "query"], "note": "--dept is an integer department id; names and search queries require a separate resolution command"}, "contact +dept-members": {"bind": {"dept": "search_query"}, "scoped_aliases": {"name": "dept"}, "note": "The real --dept is a department-name search keyword; search spellings come from search_query, while --name remains command-scoped."}, - "chat message send": {"scoped_aliases": {"to-user": "user", "file": "file-path"}, "note": "Recipient and local-file-path aliases are exact to this command; obsolete file metadata flags remain unsupported."}, + "chat message send": {"scoped_aliases": {"to-user": "user", "file": "file-path"}, "ambiguous": ["chat-id", "open-conversation-id"], "note": "Recipient and local-file-path aliases are exact to this command; obsolete file metadata flags remain unsupported. Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, "chat +group-members": {"bind": {"group": "group_name"}, "note": "The real --group is a group-name search keyword on this shortcut, not an identifier."}, "chat +category-create": {"scoped_aliases": {"name": "title"}, "scope_strict": true, "note": "The reviewed name/title mapping preserves the category display-name value on this exact shortcut."}, "chat category create": {"scoped_aliases": {"name": "title"}, "scope_strict": true, "note": "The reviewed name/title mapping preserves the category display-name value on this exact command."}, @@ -87,8 +93,8 @@ "chat +category-delete": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."}, "chat category delete": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."}, "chat category list-conversations": {"block": ["category-ids"], "note": "This command requires one category id; list cardinality is not reduced automatically."}, - "chat category add-conv": {"block": ["category-id"], "note": "This command requires a category-id list; one id is not promoted into a batch input."}, - "chat category remove-conv": {"block": ["category-id"], "note": "This command requires a category-id list; one id is not promoted into a batch input."}, + "chat category add-conv": {"block": ["category-id"], "ambiguous": ["chat", "chat-id", "open-conversation-id"], "note": "This command requires a category-id list; one id is not promoted into a batch input. Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, + "chat category remove-conv": {"block": ["category-id"], "ambiguous": ["chat", "chat-id", "open-conversation-id"], "note": "This command requires a category-id list; one id is not promoted into a batch input. Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, "chat +chat-role-update": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."}, "chat group-role remove": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."}, "chat group-role update": {"block": ["role-ids"], "note": "This command requires one role id; list cardinality is not reduced automatically."}, @@ -98,7 +104,7 @@ "chat +messages-send-by-webhook": {"scoped_aliases": {"at-user-ids": "at-users"}, "scope_strict": true, "note": "Both names denote the same userId list used for @ mentions on this exact shortcut."}, "chat message send-by-webhook": {"scoped_aliases": {"at-user-ids": "at-users"}, "scope_strict": true, "note": "Both names denote the same userId list used for @ mentions on this exact command."}, "doc block insert": {"block": ["before-block-id"], "note": "Parent and reference roles remain distinct. --before-block-id needs both --ref-block and --where before, while role-free --block-id cannot choose parent versus reference.", "scoped_aliases": {"parent-block-id": "parent-block", "ref-block-id": "ref-block", "reference-block-id": "ref-block"}, "ambiguous": ["block-id"], "scope_strict": true}, - "chat message send-by-bot": {"scoped_aliases": {"at-users": "at-user-ids"}, "block": ["user-id", "to-user-id"], "ambiguous": ["at-ids"], "note": "The reviewed @ userId-list alias is exact; singular recipients are not promoted, and bare --at-ids cannot choose an identifier domain."}, + "chat message send-by-bot": {"scoped_aliases": {"at-users": "at-user-ids"}, "block": ["user-id", "to-user-id"], "ambiguous": ["at-ids", "chat-id", "open-conversation-id"], "note": "The reviewed @ userId-list alias is exact; singular recipients are not promoted, and bare --at-ids cannot choose an identifier domain. Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, "doc +export-get": {"block": ["doc-id", "document-id", "file-id", "node", "node-id", "task-id", "url"], "note": "This command queries one export jobId. Document node identifiers and import taskId spellings are different entities and are rejected.", "scoped_aliases": {"export-job-id": "job-id"}, "scope_strict": true}, "doc block delete": {"block": ["index"], "note": "index (position) vs node (node id) are different"}, "doc +copy": {"scoped_aliases": {"folder-id": "folder", "parent-folder": "folder", "parent-folder-id": "folder", "parent-node-id": "folder"}, "block": ["parent-id"], "scope_strict": true, "note": "This exact Doc command expects a Doc folder nodeId/dentryUuid/URL. Reviewed folder spellings preserve that value; generic --parent-id stays blocked because it may carry a numeric Drive dentryId."}, @@ -130,7 +136,7 @@ "chat +messages-read-status": {"block": ["user-id", "open-dingtalk-id"], "note": "The real --users is a mixed userId/openDingTalkId list."}, "chat category create-smart": {"bind": {"members": "open_dingtalk_ids"}, "scoped_aliases": {"title": "name"}, "note": "The real --members is an openDingTalkId list; the reviewed title/name alias is exact to the category display name."}, "chat message reply": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "The required --ref-sender is a role-specific openDingTalkId and must not accept generic userId spellings."}, - "chat message send-card": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "note": "The real --receiver is a role-specific openDingTalkId and must not accept generic userId spellings."}, + "chat message send-card": {"block": ["user", "user-id", "userid", "uid", "staff-id"], "ambiguous": ["chat", "chat-id", "open-conversation-id"], "note": "The real --receiver is a role-specific openDingTalkId and must not accept generic userId spellings. Visible --conversation-id and legacy --group both remain executable outside the approved hidden-alias migration set."}, "chat +category-add-conversation": {"block": ["category-id"], "note": "The real --category-ids is a list; a singular category ID is not promoted automatically."}, "chat +category-list-conversations": {"block": ["category-ids"], "note": "The real --category-id is singular; list cardinality is not reduced automatically."}, "chat +category-remove-conversation": {"block": ["category-id"], "note": "The real --category-ids is a list; a singular category ID is not promoted automatically."}, diff --git a/internal/cli/schema_catalog_test.go b/internal/cli/schema_catalog_test.go index 355c8a42..9c0cd111 100644 --- a/internal/cli/schema_catalog_test.go +++ b/internal/cli/schema_catalog_test.go @@ -1396,7 +1396,8 @@ func TestDeliveryCatalogChatParamDeclsFrom87910880Reviewed(t *testing.T) { } } - // Hidden conversation aliases must stay unpublished (merge-base parity). + // Manifest-covered migrations hide legacy aliases; manifest-external + // commands keep their existing visible flags for compatibility. editLeaf, err := queryDeliverySchemaPayload([]string{"chat message edit"}) if err != nil { t.Fatal(err) @@ -1415,10 +1416,11 @@ func TestDeliveryCatalogChatParamDeclsFrom87910880Reviewed(t *testing.T) { if _, ok := listParams["conversation-id"]; !ok { t.Fatalf("chat category list-by-conv missing public canonical --conversation-id") } - for _, hidden := range []string{"group", "id"} { - if _, ok := listParams[hidden]; ok { - t.Fatalf("chat category list-by-conv unexpectedly publishes hidden alias --%s", hidden) - } + if _, ok := listParams["group"]; !ok { + t.Fatalf("chat category list-by-conv unexpectedly hides manifest-external --group") + } + if _, ok := listParams["id"]; ok { + t.Fatalf("chat category list-by-conv unexpectedly publishes hidden alias --id") } for _, path := range []string{ @@ -1435,9 +1437,9 @@ func TestDeliveryCatalogChatParamDeclsFrom87910880Reviewed(t *testing.T) { if _, ok := params["conversation-id"]; !ok { t.Fatalf("%s missing public canonical --conversation-id", path) } - for _, hidden := range []string{"group", "id", "chat"} { - if _, ok := params[hidden]; ok { - t.Fatalf("%s unexpectedly publishes hidden alias --%s", path, hidden) + for _, visible := range []string{"group", "id", "chat"} { + if _, ok := params[visible]; !ok { + t.Fatalf("%s unexpectedly hides manifest-external --%s", path, visible) } } } @@ -1447,17 +1449,16 @@ func TestDeliveryCatalogChatParamDeclsFrom87910880Reviewed(t *testing.T) { t.Fatal(err) } groupBotsParams := schemaMap(groupBots["parameters"]) - if _, ok := groupBotsParams["conversation-id"]; !ok { - t.Fatal("chat group bots missing public canonical --conversation-id") + group := groupBotsParams["group"] + if group == nil { + t.Fatal("chat group bots missing public legacy --group") } - groupName := groupBotsParams["group-name"] - if groupName == nil { - t.Fatal("chat group bots missing public canonical --group-name") + if group["property"] != "openConversationId" { + t.Fatalf("chat group bots --group property = %#v, want openConversationId", group["property"]) } - if groupName["property"] != "groupName" { - t.Fatalf("chat group bots --group-name property = %#v, want groupName", groupName["property"]) - } - if _, ok := groupBotsParams["group"]; ok { - t.Fatal("chat group bots unexpectedly publishes hidden --group alias") + for _, migrated := range []string{"conversation-id", "group-name"} { + if _, ok := groupBotsParams[migrated]; ok { + t.Fatalf("chat group bots unexpectedly publishes migrated --%s", migrated) + } } } diff --git a/internal/corecmd/corecmd_test.go b/internal/corecmd/corecmd_test.go index 570b268a..a5ae5b96 100644 --- a/internal/corecmd/corecmd_test.go +++ b/internal/corecmd/corecmd_test.go @@ -116,6 +116,16 @@ func TestCrossPlatformCoverageRegisterFlagsAllKinds(t *testing.T) { } } +func TestCrossPlatformCoverageAnnotateFlagAliasIgnoresMissingInputs(t *testing.T) { + AnnotateFlagAlias(nil, "alias", "canonical") + + cmd := newTestCommand() + AnnotateFlagAlias(cmd, "missing", "canonical") + if flag := cmd.Flags().Lookup("missing"); flag != nil { + t.Fatalf("unexpected missing flag registered: %#v", flag) + } +} + // ── effective value fallback chain ───────────────────────────────── func TestCrossPlatformCoverageEffectiveValueFallbackChain(t *testing.T) { diff --git a/internal/helpers/chat.go b/internal/helpers/chat.go index ce76621c..e0ea7d4e 100644 --- a/internal/helpers/chat.go +++ b/internal/helpers/chat.go @@ -238,18 +238,101 @@ func chatMessageID(cmd *cobra.Command) (string, error) { } func installChatIMIDFlagAliases(root *cobra.Command) { - var visit func(*cobra.Command) - visit = func(cmd *cobra.Command) { - installChatFlagAliases(cmd, "conversation-id", []string{"group", "id", "chat", "open-conversation-id"}, requireChatConversationID) - installChatFlagAliases(cmd, "message-id", []string{"msg-id", "open-message-id"}, chatMessageID) - clearChatAliasRequiredAnnotations(cmd, "group", "id", "chat", "open-conversation-id", "msg-id", "open-message-id") + conversationMigrationPaths := chatMigrationPathSet(chatPendingConversationIDMigrationPaths) + messageMigrationPaths := chatMigrationPathSet(chatPendingMessageIDMigrationPaths) + var visit func(*cobra.Command, []string) + visit = func(cmd *cobra.Command, path []string) { + key := strings.Join(path, " ") + if conversationMigrationPaths[key] { + installChatFlagAliases(cmd, "conversation-id", []string{"group", "id", "chat", "open-conversation-id"}, requireChatConversationID) + clearChatAliasRequiredAnnotations(cmd, "group", "id", "chat", "open-conversation-id") + } + if messageMigrationPaths[key] { + installChatFlagAliases(cmd, "message-id", []string{"msg-id", "open-message-id"}, chatMessageID) + clearChatAliasRequiredAnnotations(cmd, "msg-id", "open-message-id") + } for _, child := range cmd.Commands() { - visit(child) + visit(child, append(path, child.Name())) } } - visit(root) + visit(root, nil) restoreChatGroupBotsLegacyRequired(root) restoreChatPendingMigrationCanonicalRequired(root) + restoreChatManifestExternalVisibleFlags(root) +} + +func chatMigrationPathSet(paths [][]string) map[string]bool { + set := make(map[string]bool, len(paths)) + for _, path := range paths { + set[strings.Join(path, " ")] = true + } + return set +} + +func restoreChatManifestExternalVisibleFlags(root *cobra.Command) { + if root == nil { + return + } + for _, spec := range chatManifestExternalVisibleFlagSpecs { + cmd, _, err := root.Find(spec.path) + if err != nil || cmd == nil { + continue + } + for _, flag := range spec.flags { + ensureVisibleStringFlag(cmd, flag.name, flag.usage) + } + for _, flag := range spec.optionalFlags { + if f := cmd.Flags().Lookup(flag); f != nil && f.Annotations != nil { + delete(f.Annotations, cobra.BashCompOneRequiredFlag) + } + } + } +} + +func ensureVisibleStringFlag(cmd *cobra.Command, name, usage string) { + flag := cmd.Flags().Lookup(name) + if flag == nil { + cmd.Flags().String(name, "", usage) + flag = cmd.Flags().Lookup(name) + } + if flag != nil { + flag.Hidden = false + } +} + +type chatVisibleFlagSpec struct { + name string + usage string +} + +type chatVisibleFlagCommandSpec struct { + path []string + flags []chatVisibleFlagSpec + optionalFlags []string +} + +var chatManifestExternalVisibleFlagSpecs = []chatVisibleFlagCommandSpec{ + {path: []string{"category", "add-conv"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"category", "list-by-conv"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}, optionalFlags: []string{"conversation-id"}}, + {path: []string{"category", "remove-conv"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"conversation-info"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"file", "upload"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"group", "get-mute-config"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"group-mute"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"group-mute-member"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"message", "add-emoji"}, flags: []chatVisibleFlagSpec{{"msg-id", "--message-id 的别名"}}}, + {path: []string{"message", "add-text-emotion"}, flags: []chatVisibleFlagSpec{{"msg-id", "--message-id 的别名"}}}, + {path: []string{"message", "list"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"message", "list-mentions"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"message", "recall-by-bot"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"message", "remove-emoji"}, flags: []chatVisibleFlagSpec{{"msg-id", "--message-id 的别名"}}}, + {path: []string{"message", "remove-text-emotion"}, flags: []chatVisibleFlagSpec{{"msg-id", "--message-id 的别名"}}}, + {path: []string{"message", "search"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"message", "send"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"message", "send-by-bot"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}}}, + {path: []string{"message", "send-card"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}, {"receiver", "--open-dingtalk-id 的兼容别名"}}}, + {path: []string{"message", "update-text-emotion"}, flags: []chatVisibleFlagSpec{{"group", "--conversation-id 的别名"}, {"id", "--conversation-id 的别名"}, {"chat", "--conversation-id 的别名"}}, optionalFlags: []string{"conversation-id"}}, + {path: []string{"mute"}, flags: []chatVisibleFlagSpec{{"chat", "--conversation-id 的别名"}, {"id", "--conversation-id 的别名"}}}, } func restoreChatGroupBotsLegacyRequired(root *cobra.Command) { @@ -5330,12 +5413,11 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id Example: ` dws chat category list-by-conv --conversation-id # 查询群 ID: dws chat search --query "群名"`, RunE: func(cmd *cobra.Command, args []string) error { - groupID := flagOrFallback(cmd, "conversation-id", "group", "id") - if groupID == "" { - return fmt.Errorf("flag --group is required") + if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id"); err != nil { + return err } return callMCPToolOnServer("im", "list_conv_categories_by_conv", map[string]any{ - "openConversationId": groupID, + "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id"), }) }, } @@ -5366,6 +5448,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id }, Parameters: []contract.ParamDecl{ {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(true)}, + {Name: "group", Property: "openConversationId", Required: boolPtr(false)}, {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, }, }, @@ -5522,7 +5605,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id Example: ` dws chat message add-emoji --conversation-id --message-id --emoji "赞" # 查询会话 ID: dws chat search --query "群名"`, RunE: func(cmd *cobra.Command, args []string) error { - if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat"); err != nil { + if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat", "open-conversation-id"); err != nil { return err } if _, err := chatMessageID(cmd); err != nil { @@ -5532,7 +5615,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id return err } return callMCPToolOnServer("im", "add_emoji_reaction", map[string]any{ - "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat"), + "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat", "open-conversation-id"), "openMsgId": flagOrFallback(cmd, "message-id", "msg-id"), "emojiName": mustGetFlag(cmd, "emoji"), }) @@ -5574,6 +5657,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id chatMessageAddEmojiCmd.Flags().String("group", "", "--conversation-id 的别名") chatMessageAddEmojiCmd.Flags().String("id", "", "--conversation-id 的别名") chatMessageAddEmojiCmd.Flags().String("chat", "", "--conversation-id 的别名") + chatMessageAddEmojiCmd.Flags().String("open-conversation-id", "", "--conversation-id 的别名") chatMessageAddEmojiCmd.Flags().String("message-id", "", "消息 openMsgId (必填)") chatMessageAddEmojiCmd.Flags().String("emoji", "", "emoji 表情名称 (必填)") @@ -5583,7 +5667,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id Example: ` dws chat message remove-emoji --conversation-id --message-id --emoji "赞" # 查询会话 ID: dws chat search --query "群名"`, RunE: func(cmd *cobra.Command, args []string) error { - if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat"); err != nil { + if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat", "open-conversation-id"); err != nil { return err } if _, err := chatMessageID(cmd); err != nil { @@ -5593,7 +5677,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id return err } return callMCPToolOnServer("im", "remove_emoji_reaction", map[string]any{ - "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat"), + "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat", "open-conversation-id"), "openMsgId": flagOrFallback(cmd, "message-id", "msg-id"), "emojiName": mustGetFlag(cmd, "emoji"), }) @@ -5635,6 +5719,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id chatMessageRemoveEmojiCmd.Flags().String("group", "", "--conversation-id 的别名") chatMessageRemoveEmojiCmd.Flags().String("id", "", "--conversation-id 的别名") chatMessageRemoveEmojiCmd.Flags().String("chat", "", "--conversation-id 的别名") + chatMessageRemoveEmojiCmd.Flags().String("open-conversation-id", "", "--conversation-id 的别名") chatMessageRemoveEmojiCmd.Flags().String("message-id", "", "消息 openMsgId (必填)") chatMessageRemoveEmojiCmd.Flags().String("emoji", "", "emoji 表情名称 (必填)") @@ -5643,7 +5728,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id Short: "对消息添加文字表情回应", Example: ` dws chat message add-text-emotion --conversation-id --message-id --emotion-id --emotion-name "赞" --text "nice" --background-id im_bg_5`, RunE: func(cmd *cobra.Command, args []string) error { - if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat"); err != nil { + if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat", "open-conversation-id"); err != nil { return err } if _, err := chatMessageID(cmd); err != nil { @@ -5653,7 +5738,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id return err } return callMCPToolOnServer("im", "add_text_emotion", map[string]any{ - "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat"), + "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat", "open-conversation-id"), "openMsgId": flagOrFallback(cmd, "message-id", "msg-id"), "emotionId": mustGetFlag(cmd, "emotion-id"), "emotionName": mustGetFlag(cmd, "emotion-name"), @@ -5697,6 +5782,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id chatMessageAddTextEmotionCmd.Flags().String("group", "", "--conversation-id 的别名") chatMessageAddTextEmotionCmd.Flags().String("id", "", "--conversation-id 的别名") chatMessageAddTextEmotionCmd.Flags().String("chat", "", "--conversation-id 的别名") + chatMessageAddTextEmotionCmd.Flags().String("open-conversation-id", "", "--conversation-id 的别名") chatMessageAddTextEmotionCmd.Flags().String("message-id", "", "消息 openMsgId (必填)") chatMessageAddTextEmotionCmd.Flags().String("emotion-id", "", "表情 ID (必填,通过 create-text-emotion 获取)") chatMessageAddTextEmotionCmd.Flags().String("emotion-name", "", "表情名称 (必填)") @@ -5708,7 +5794,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id Short: "移除消息的文字表情回应", Example: ` dws chat message remove-text-emotion --conversation-id --message-id --emotion-id --emotion-name "赞" --text "nice" --background-id `, RunE: func(cmd *cobra.Command, args []string) error { - if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat"); err != nil { + if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat", "open-conversation-id"); err != nil { return err } if _, err := chatMessageID(cmd); err != nil { @@ -5718,7 +5804,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id return err } return callMCPToolOnServer("im", "remove_text_emotion", map[string]any{ - "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat"), + "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat", "open-conversation-id"), "openMsgId": flagOrFallback(cmd, "message-id", "msg-id"), "emotionId": mustGetFlag(cmd, "emotion-id"), "emotionName": mustGetFlag(cmd, "emotion-name"), @@ -5762,6 +5848,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id chatMessageRemoveTextEmotionCmd.Flags().String("group", "", "--conversation-id 的别名") chatMessageRemoveTextEmotionCmd.Flags().String("id", "", "--conversation-id 的别名") chatMessageRemoveTextEmotionCmd.Flags().String("chat", "", "--conversation-id 的别名") + chatMessageRemoveTextEmotionCmd.Flags().String("open-conversation-id", "", "--conversation-id 的别名") chatMessageRemoveTextEmotionCmd.Flags().String("message-id", "", "消息 openMsgId (必填)") chatMessageRemoveTextEmotionCmd.Flags().String("emotion-id", "", "表情 ID (必填)") chatMessageRemoveTextEmotionCmd.Flags().String("emotion-name", "", "表情名称 (必填)") @@ -5773,7 +5860,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id Short: "更新消息的文字表情回应", Example: ` dws chat message update-text-emotion --conversation-id --message-id --old-emotion-id --emotion-id --emotion-name "赞" --text "nice" --background-id im_bg_5`, RunE: func(cmd *cobra.Command, args []string) error { - if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat"); err != nil { + if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group", "id", "chat", "open-conversation-id"); err != nil { return err } if _, err := chatMessageID(cmd); err != nil { @@ -5783,7 +5870,7 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id return err } return callMCPToolOnServer("im", "update_text_emotion", map[string]any{ - "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat"), + "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat", "open-conversation-id"), "openMsgId": flagOrFallback(cmd, "message-id", "msg-id"), "oldEmotionId": mustGetFlag(cmd, "old-emotion-id"), "emotionId": mustGetFlag(cmd, "emotion-id"), @@ -5823,16 +5910,21 @@ chat message edit 或 chat message recall 的 --message-id 和 --conversation-id }, Parameters: []contract.ParamDecl{ {Name: "background-id", Property: "backgroundId", Required: boolPtr(true), Description: "新文字表情的背景 ID"}, + {Name: "chat", Property: "openConversationId", Required: boolPtr(false), Description: "会话 openConversationId 兼容入口"}, {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false), Description: "会话 openConversationId"}, {Name: "emotion-id", Property: "emotionId", Required: boolPtr(true), Description: "新的文字表情 ID,可通过 create-text-emotion 获取"}, {Name: "emotion-name", Property: "emotionName", Required: boolPtr(true), Description: "新的文字表情名称"}, + {Name: "group", Property: "openConversationId", Required: boolPtr(false), Description: "会话 openConversationId 兼容入口"}, + {Name: "id", Property: "openConversationId", Required: boolPtr(false), Description: "会话 openConversationId 兼容入口"}, {Name: "message-id", Property: "openMsgId", Required: boolPtr(true), Description: "需要原地更新文字表情的消息 openMsgId"}, {Name: "old-emotion-id", Property: "oldEmotionId", Required: boolPtr(true), Description: "消息上当前文字表情的 emotionId"}, + {Name: "open-conversation-id", Property: "openConversationId", Required: boolPtr(false), Description: "会话 openConversationId 兼容入口"}, {Name: "text", Property: "text", Required: boolPtr(true), Description: "新的文字表情内容"}, }, }, }) chatMessageUpdateTextEmotionCmd.Flags().String("conversation-id", "", "会话 openConversationId (必填,支持单聊/群聊)") + chatMessageUpdateTextEmotionCmd.Flags().String("open-conversation-id", "", "--conversation-id 的别名") chatMessageUpdateTextEmotionCmd.Flags().String("message-id", "", "消息 openMsgId (必填)") chatMessageUpdateTextEmotionCmd.Flags().String("old-emotion-id", "", "待更新的原表情 ID (必填)") chatMessageUpdateTextEmotionCmd.Flags().String("emotion-id", "", "新表情 ID (必填)") @@ -6751,9 +6843,12 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 - 群聊:dws chat search --query "群名" - 单聊:dws chat conversation-info --open-dingtalk-id (openDingTalkId 可通过 dws contact user search --query "姓名" 获取)`, - Example: ` dws chat message forward --src-conversation-id --msg-id --dest-conversation-id `, + Example: ` dws chat message forward --src-conversation-id --message-id --dest-conversation-id `, RunE: func(cmd *cobra.Command, args []string) error { - if err := validateRequiredFlags(cmd, "src-conversation-id", "msg-id", "dest-conversation-id"); err != nil { + if err := validateRequiredFlags(cmd, "src-conversation-id", "dest-conversation-id"); err != nil { + return err + } + if err := validateRequiredFlagWithAliases(cmd, "message-id", "msg-id"); err != nil { return err } toolArgs := map[string]any{ @@ -6790,7 +6885,7 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 AgentSummary: "把一条已有消息转发到另一个会话", UseWhen: []string{"已知源消息与源、目标会话 ID 时"}, AvoidWhen: []string{"合并转发多条消息时使用 chat message combine-forward"}, - Examples: []string{"dws chat message forward --src-conversation-id --msg-id --dest-conversation-id "}, + Examples: []string{"dws chat message forward --src-conversation-id --message-id --dest-conversation-id "}, }, Parameters: []contract.ParamDecl{ {Name: "dest-conversation-id", Property: "destOpenCid"}, @@ -6874,11 +6969,11 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 返回的是原始配置记录,不等同于当前被禁言成员列表;全员禁言开关也不在本命令的返回范围内。`, Example: ` dws chat group get-mute-config --conversation-id `, RunE: func(cmd *cobra.Command, args []string) error { - if err := validateRequiredFlags(cmd, "conversation-id"); err != nil { + if err := validateRequiredFlagWithAliases(cmd, "conversation-id", "group"); err != nil { return err } return callMCPToolOnServer("im", "get_group_mute_config", map[string]any{ - "openConversationId": flagOrFallback(cmd, "conversation-id", "group", "id", "chat"), + "openConversationId": flagOrFallback(cmd, "conversation-id", "group"), }) }, } @@ -6907,9 +7002,13 @@ flow-status 取值:1=处理中(PROCESSING),2=输入中(INPUTTING),3=完成 AvoidWhen: []string{"设置全员禁言用 chat group-mute;禁言个人用 chat group-mute-member"}, Examples: []string{"dws chat group get-mute-config --conversation-id --format json"}, }, + Parameters: []contract.ParamDecl{ + {Name: "group", Property: "openConversationId", Required: boolPtr(false)}, + }, }, }) chatGroupGetMuteConfigCmd.Flags().String("conversation-id", "", "群聊 openConversationId (必填)") + chatGroupGetMuteConfigCmd.Flags().String("group", "", "--conversation-id 的别名") chatGroupCmd.AddCommand(chatGroupGetMuteConfigCmd) // ── group-mute: 全员禁言 ─────────────────────────────── diff --git a/internal/helpers/chat_command_edges_test.go b/internal/helpers/chat_command_edges_test.go index 0f42875b..b8553d05 100644 --- a/internal/helpers/chat_command_edges_test.go +++ b/internal/helpers/chat_command_edges_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/edition" + "github.com/spf13/cobra" ) func runChatCoverageCommand(t *testing.T, caller edition.ToolCaller, args ...string) error { @@ -126,6 +127,80 @@ func TestCrossPlatformCoverageChatStableCompatibilityHintsRemainAvailable(t *tes } } +func TestCrossPlatformCoverageChatAliasInstallerRemainingEdges(t *testing.T) { + restoreChatManifestExternalVisibleFlags(nil) + + mismatchedRoot := &cobra.Command{Use: "chat"} + mismatchedRoot.AddCommand(&cobra.Command{Use: "other"}) + restoreChatManifestExternalVisibleFlags(mismatchedRoot) + + missingPrimary := &cobra.Command{Use: "leaf"} + installChatFlagAliases(missingPrimary, "conversation-id", []string{"group"}, requireChatConversationID) + if flag := missingPrimary.Flags().Lookup("group"); flag != nil { + t.Fatalf("alias registered without canonical flag: %#v", flag) + } + + skipGroup := &cobra.Command{Use: "leaf", RunE: func(cmd *cobra.Command, args []string) error { return nil }} + skipGroup.Flags().String("conversation-id", "", "") + skipGroup.Flags().String("group-name", "", "") + installChatFlagAliases(skipGroup, "conversation-id", []string{"group", "chat"}, requireChatConversationID) + if flag := skipGroup.Flags().Lookup("group"); flag != nil { + t.Fatalf("group alias registered beside group-name: %#v", flag) + } + if flag := skipGroup.Flags().Lookup("chat"); flag == nil { + t.Fatal("non-group alias was not registered") + } + + preRunCalled := false + withPreRun := &cobra.Command{ + Use: "leaf", + PreRunE: func(cmd *cobra.Command, args []string) error { + preRunCalled = true + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { return nil }, + } + withPreRun.Flags().String("conversation-id", "", "") + installChatFlagAliases(withPreRun, "conversation-id", []string{"group"}, requireChatConversationID) + withPreRun.SetArgs([]string{"--group", "cid"}) + if err := withPreRun.ExecuteContext(context.Background()); err != nil { + t.Fatalf("execute with alias and previous PreRunE: %v", err) + } + if !preRunCalled { + t.Fatal("previous PreRunE was not called") + } +} + +func TestCrossPlatformCoverageChatMessageForwardRequiresMessageID(t *testing.T) { + caller := &productExampleCaller{} + InitDeps(caller) + deps.Out.w = io.Discard + deps.Out.errW = io.Discard + root := newChatCommand() + command, _, err := root.Find([]string{"message", "forward"}) + if err != nil { + t.Fatal(err) + } + for _, name := range []string{"message-id", "msg-id"} { + if flag := command.Flags().Lookup(name); flag != nil && flag.Annotations != nil { + delete(flag.Annotations, cobra.BashCompOneRequiredFlag) + } + } + if err := command.Flags().Set("src-conversation-id", "src"); err != nil { + t.Fatal(err) + } + if err := command.Flags().Set("dest-conversation-id", "dest"); err != nil { + t.Fatal(err) + } + err = command.RunE(command, nil) + if err == nil || !strings.Contains(err.Error(), "missing required flag: --message-id") { + t.Fatalf("forward missing message id error = %v", err) + } + if caller.calls != 0 { + t.Fatalf("tool calls = %d, want 0", caller.calls) + } +} + func TestCrossPlatformCoverageChatGroupUpdateIconAcceptsUploadedMediaIDPrefixes(t *testing.T) { previousDeps, previousArgs := deps, os.Args os.Args = []string{"dws", "chat"} @@ -439,8 +514,8 @@ func TestCrossPlatformCoverageChatIMIDMigrationRequiredFlagErrors(t *testing.T) {name: "role set user missing conversation", path: []string{"group-role", "set-user"}, flag: map[string]string{"user": "D1", "role-ids": "r1"}, want: "conversation-id"}, {name: "role remove user missing conversation", path: []string{"group-role", "remove-user"}, flag: map[string]string{"user": "D1", "role-ids": "r1"}, want: "conversation-id"}, {name: "role query user missing conversation", path: []string{"group-role", "query-user"}, flag: map[string]string{"user": "D1"}, want: "conversation-id"}, - {name: "bots conflicting conversation aliases", path: []string{"group", "bots"}, flag: map[string]string{"conversation-id": "cid1", "id": "cid2"}, want: "conflicts"}, - {name: "bots conflicting group aliases", path: []string{"group", "bots"}, flag: map[string]string{"group-name": "项目群", "group": "cid"}, want: "conflicts"}, + {name: "bots missing legacy group", path: []string{"group", "bots"}, want: "group"}, + {name: "bots rejects migrated conversation id", path: []string{"group", "bots"}, flag: map[string]string{"conversation-id": "cid"}, want: "no such flag"}, {name: "dismiss missing conversation", path: []string{"group", "dismiss"}, flag: map[string]string{"yes": "true"}, want: "conversation-id"}, {name: "set history missing conversation", path: []string{"group", "set-history"}, flag: map[string]string{"option": "ALL"}, want: "conversation-id"}, {name: "set pin missing message", path: []string{"message", "set-pin-msg"}, flag: map[string]string{"open-conversation-id": "cid"}, want: "message-id"}, diff --git a/internal/helpers/chat_message_help_test.go b/internal/helpers/chat_message_help_test.go index 4c7323ac..bc11c366 100644 --- a/internal/helpers/chat_message_help_test.go +++ b/internal/helpers/chat_message_help_test.go @@ -171,7 +171,7 @@ func TestCrossPlatformCoverageChatMessageHelpDocumentsPostSendIDChain(t *testing } } -func TestCrossPlatformCoverageChatReactionHelpHidesConversationAliases(t *testing.T) { +func TestCrossPlatformCoverageChatReactionHelpKeepsManifestExternalAliasesVisible(t *testing.T) { for _, command := range []string{"add-emoji", "remove-emoji", "add-text-emotion", "remove-text-emotion"} { t.Run(command, func(t *testing.T) { cmd := newChatCommand() @@ -187,16 +187,16 @@ func TestCrossPlatformCoverageChatReactionHelpHidesConversationAliases(t *testin if !strings.Contains(help, "--conversation-id") { t.Fatalf("chat message %s help missing --conversation-id:\n%s", command, help) } - for _, hidden := range []string{"--group", "--id", "--chat"} { - if strings.Contains(help, hidden+" string") { - t.Fatalf("chat message %s help exposes hidden alias %s:\n%s", command, hidden, help) + for _, visible := range []string{"--group", "--id", "--chat"} { + if !strings.Contains(help, visible+" string") { + t.Fatalf("chat message %s help hides manifest-external alias %s:\n%s", command, visible, help) } } }) } } -func TestCrossPlatformCoverageChatGroupBotsHelpSplitsGroupName(t *testing.T) { +func TestCrossPlatformCoverageChatGroupBotsHelpKeepsLegacyGroup(t *testing.T) { cmd := newChatCommand() var output bytes.Buffer cmd.SetOut(&output) @@ -207,13 +207,13 @@ func TestCrossPlatformCoverageChatGroupBotsHelpSplitsGroupName(t *testing.T) { } help := output.String() - for _, visible := range []string{"--conversation-id", "--group-name"} { - if !strings.Contains(help, visible) { - t.Fatalf("chat group bots help missing %s:\n%s", visible, help) - } + if !strings.Contains(help, "--group string") { + t.Fatalf("chat group bots help missing visible --group:\n%s", help) } - if strings.Contains(help, "--group string") { - t.Fatalf("chat group bots help exposes hidden --group alias:\n%s", help) + for _, hidden := range []string{"--conversation-id", "--group-name"} { + if strings.Contains(help, hidden) { + t.Fatalf("chat group bots help exposes migrated flag %s:\n%s", hidden, help) + } } } @@ -233,9 +233,9 @@ func TestCrossPlatformCoverageChatSendCardHelpUsesCanonicalIDFlags(t *testing.T) t.Fatalf("send-card help missing %s:\n%s", visible, help) } } - for _, hidden := range []string{"--group", "--receiver"} { - if strings.Contains(help, hidden+" string") { - t.Fatalf("send-card help exposes hidden alias %s:\n%s", hidden, help) + for _, visible := range []string{"--group", "--receiver"} { + if !strings.Contains(help, visible+" string") { + t.Fatalf("send-card help hides manifest-external alias %s:\n%s", visible, help) } } } diff --git a/internal/helpers/chat_ports_coverage_test.go b/internal/helpers/chat_ports_coverage_test.go index a7d8a6d2..025e31f9 100644 --- a/internal/helpers/chat_ports_coverage_test.go +++ b/internal/helpers/chat_ports_coverage_test.go @@ -81,6 +81,32 @@ func TestCrossPlatformCoverageChatUpdateTextEmotion(t *testing.T) { }, }, }, + { + name: "open-conversation-id alias", + args: []string{ + "message", "update-text-emotion", + "--open-conversation-id", "conv-3", + "--message-id", "msg-3", + "--old-emotion-id", "old-3", + "--emotion-id", "new-3", + "--emotion-name", "smile", + "--text", "done", + "--background-id", "im_bg_2", + }, + want: guardedMutationCall{ + productID: "im", + toolName: "update_text_emotion", + args: map[string]any{ + "openConversationId": "conv-3", + "openMsgId": "msg-3", + "oldEmotionId": "old-3", + "emotionId": "new-3", + "emotionName": "smile", + "text": "done", + "backgroundId": "im_bg_2", + }, + }, + }, } for _, test := range tests { test := test @@ -163,19 +189,41 @@ func TestCrossPlatformCoverageChatUpdateTextEmotionRequiredFlags(t *testing.T) { } func TestCrossPlatformCoverageChatGroupGetMuteConfig(t *testing.T) { - caller := &guardedMutationCaller{} - err := executeGuardedMutationCommand(t, caller, newChatCommand, - "group", "get-mute-config", "--group", "conv-1") - if err != nil { - t.Fatalf("get-mute-config returned error: %v", err) - } - want := guardedMutationCall{ - productID: "im", - toolName: "get_group_mute_config", - args: map[string]any{"openConversationId": "conv-1"}, - } - if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], want) { - t.Fatalf("tool calls = %#v, want %#v", caller.calls, want) + for _, test := range []struct { + name string + args []string + want guardedMutationCall + }{ + { + name: "legacy group alias", + args: []string{"group", "get-mute-config", "--group", "conv-1"}, + want: guardedMutationCall{ + productID: "im", + toolName: "get_group_mute_config", + args: map[string]any{"openConversationId": "conv-1"}, + }, + }, + { + name: "canonical conversation id", + args: []string{"group", "get-mute-config", "--conversation-id", "conv-2"}, + want: guardedMutationCall{ + productID: "im", + toolName: "get_group_mute_config", + args: map[string]any{"openConversationId": "conv-2"}, + }, + }, + } { + test := test + t.Run(test.name, func(t *testing.T) { + caller := &guardedMutationCaller{} + err := executeGuardedMutationCommand(t, caller, newChatCommand, test.args...) + if err != nil { + t.Fatalf("get-mute-config returned error: %v", err) + } + if len(caller.calls) != 1 || !reflect.DeepEqual(caller.calls[0], test.want) { + t.Fatalf("tool calls = %#v, want %#v", caller.calls, test.want) + } + }) } } diff --git a/internal/helpers/wukong_weekly_sync_chat_todo_test.go b/internal/helpers/wukong_weekly_sync_chat_todo_test.go index 3820586d..efe5d871 100644 --- a/internal/helpers/wukong_weekly_sync_chat_todo_test.go +++ b/internal/helpers/wukong_weekly_sync_chat_todo_test.go @@ -117,7 +117,7 @@ func TestCrossPlatformCoverageWukongWeeklyChatCategoryQueries(t *testing.T) { caller := &wukongWeeklySyncCaller{} _, _, err := executeWukongWeeklySyncCommand(t, "chat", caller, newChatCommand, "category", "list-by-conv") if err == nil || !strings.Contains(err.Error(), "missing required flag: --conversation-id") { - t.Fatalf("missing group error = %v", err) + t.Fatalf("missing conversation ID error = %v", err) } requireWukongWeeklySyncNoCalls(t, caller)